diff --git a/.cmake.conf b/.cmake.conf index 889ca7d8..898d0cde 100644 --- a/.cmake.conf +++ b/.cmake.conf @@ -5,10 +5,9 @@ # (directly by qtbase) we actually add the extra definitions if (NOT DEFINED QT_SUPERBUILD OR DEFINED QT_REPO_MODULE_VERSION) set(QT_EXTRA_INTERNAL_TARGET_DEFINES "QT_LEAN_HEADERS=1") - list(APPEND QT_EXTRA_INTERNAL_TARGET_DEFINES "QT_NO_AS_CONST=1") endif() -set(QT_REPO_MODULE_VERSION "6.5.3") +set(QT_REPO_MODULE_VERSION "6.6.0") set(QT_REPO_MODULE_PRERELEASE_VERSION_SEGMENT "alpha1") set(QT_COPYRIGHT_YEAR "2023") @@ -17,12 +16,14 @@ set(QT_COPYRIGHT "Copyright (C) ${QT_COPYRIGHT_YEAR} The Qt Company Ltd and othe # Minimum requirement for building Qt set(QT_SUPPORTED_MIN_CMAKE_VERSION_FOR_BUILDING_QT_SHARED "3.16") set(QT_SUPPORTED_MIN_CMAKE_VERSION_FOR_BUILDING_QT_STATIC "3.21") +set(QT_SUPPORTED_MIN_CMAKE_VERSION_FOR_BUILDING_QT_APPLE "3.21") # Minimum requirement for consuming Qt in a user project. # This might be different in the future, e.g. be lower than the requirement for # building Qt. set(QT_SUPPORTED_MIN_CMAKE_VERSION_FOR_USING_QT_SHARED "3.16") set(QT_SUPPORTED_MIN_CMAKE_VERSION_FOR_USING_QT_STATIC "3.21") +set(QT_SUPPORTED_MIN_CMAKE_VERSION_FOR_USING_QT_APPLE "3.21") # Policy settings for commands defined by qtbase. These will also be injected # into the top level policy scope of each Qt module when building Qt so that diff --git a/.release-timestamp b/.release-timestamp index e6b6e860..c49f3ba9 100644 --- a/.release-timestamp +++ b/.release-timestamp @@ -1 +1 @@ -QT_PACKAGEDATE_STR=2023-09-25 \ No newline at end of file +QT_PACKAGEDATE_STR=2023-10-04 \ No newline at end of file diff --git a/.tag b/.tag index dd5a7f29..9caab974 100644 --- a/.tag +++ b/.tag @@ -1 +1 @@ -372eaedc5b8c771c46acc4c96e91bbade4ca3624 +33f5e985e480283bb0ca9dea5f82643e825ba87c diff --git a/CMakeLists.txt b/CMakeLists.txt index fd370a54..c774e297 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -1,8 +1,6 @@ # Copyright (C) 2022 The Qt Company Ltd. # SPDX-License-Identifier: BSD-3-Clause -# special case skip regeneration - # Need an explicit call at the top level. This is the absolute minimum version # needed to configure the project with any combination of enabled features. # The call to qt_build_repo_begin() will upgrade policies further. diff --git a/bin/qt-cmake-create.bat.in b/bin/qt-cmake-create.bat.in new file mode 100644 index 00000000..ff8f7310 --- /dev/null +++ b/bin/qt-cmake-create.bat.in @@ -0,0 +1,18 @@ +@echo off +:: The directory of this script is the expanded absolute path of the "$qt_prefix/bin" directory. +set script_dir_path=%~dp0 + +:: Try to use original cmake, otherwise to make it relocatable, use any cmake found in PATH. +set cmake_path=@CMAKE_COMMAND@ +if not exist "%cmake_path%" set cmake_path=cmake + +if NOT "%~2" == "" goto :showhelp +if NOT "%~1" == "" (set PROJECT_DIR=%~1) else (set PROJECT_DIR=%cd%) + +"%cmake_path%" -DPROJECT_DIR="%PROJECT_DIR%" -P "%script_dir_path%\@__GlobalConfig_relative_path_from_bin_dir_to_cmake_config_dir@\QtInitProject.cmake" +exit /b %errorlevel% + +:showhelp +echo Usage +echo. qt-cmake-create +exit /b 1 diff --git a/bin/qt-cmake-create.in b/bin/qt-cmake-create.in new file mode 100644 index 00000000..7865d0fe --- /dev/null +++ b/bin/qt-cmake-create.in @@ -0,0 +1,29 @@ +#!/bin/sh + +HELP_MESSAGE="Usage + qt-cmake-create " + +# The directory of this script is the expanded absolute path of the "$qt_prefix/bin" directory. +script_dir_path=`dirname $0` +script_dir_path=`(cd "$script_dir_path"; /bin/pwd)` + +# Try to use original cmake, otherwise to make it relocatable, use any cmake found in PATH. +original_cmake_path="@CMAKE_COMMAND@" +cmake_path=$original_cmake_path +if ! test -f "$cmake_path"; then + cmake_path="cmake" +fi + +if [ "$#" -gt 1 ]; then + echo "Invalid number of arguments" + echo "$HELP_MESSAGE" + exit 1 +fi + +if [ "$#" -gt 0 ]; then + PROJECT_DIR=$1 +else + PROJECT_DIR=$PWD +fi +exec "$cmake_path" -DPROJECT_DIR="$PROJECT_DIR" -P \ + "$script_dir_path/@__GlobalConfig_relative_path_from_bin_dir_to_cmake_config_dir@/QtInitProject.cmake" diff --git a/cmake/FindMimer.cmake b/cmake/FindMimer.cmake new file mode 100644 index 00000000..5cbc6e69 --- /dev/null +++ b/cmake/FindMimer.cmake @@ -0,0 +1,98 @@ +# Copyright (C) 2022 The Qt Company Ltd. +# Copyright (C) 2022 Mimer Information Technology +# SPDX-License-Identifier: BSD-3-Clause + +# FindMimer +# --------- +# Try to locate the Mimer SQL client library +if(NOT DEFINED MimerSQL_ROOT) + if(DEFINED ENV{MIMERSQL_DEV_ROOT}) + set(MimerSQL_ROOT "$ENV{MIMERSQL_DEV_ROOT}") + endif() +endif() + +if(NOT DEFINED MimerSQL_ROOT) + find_package(PkgConfig QUIET) +endif() +if(PkgConfig_FOUND AND NOT DEFINED MimerSQL_ROOT) + pkg_check_modules(PC_Mimer QUIET mimcontrol) + set(MimerSQL_include_dir_hints "${PC_MimerSQL_INCLUDEDIR}") + set(MimerSQL_library_hints "${PC_MimerSQL_LIBDIR}") +else() + if(DEFINED MimerSQL_ROOT) + if(WIN32) + set(MimerSQL_include_dir_hints "${MimerSQL_ROOT}\\include") + if(CMAKE_SYSTEM_PROCESSOR MATCHES "^(x86|X86)$") + set(MimerSQL_library_hints "${MimerSQL_ROOT}\\lib\\x86") + elseif(CMAKE_SYSTEM_PROCESSOR MATCHES "^(amd64|AMD64)$") + set(MimerSQL_library_hints "${MimerSQL_ROOT}\\lib\\amd64") + else() + set(MimerSQL_library_hints "") + endif() + else() + set(MimerSQL_include_dir_hints "${MimerSQL_ROOT}/include") + set(MimerSQL_library_hints "${MimerSQL_ROOT}/lib") + endif() + else() + if(WIN32) + set(MimerSQL_include_dir_hints "C:\\MimerSQLDev\\include") + if(CMAKE_SYSTEM_PROCESSOR MATCHES "^(x86|X86)$") + set(MimerSQL_library_hints "C:\\MimerSQLDev\\lib\\x86") + elseif(CMAKE_SYSTEM_PROCESSOR MATCHES "^(amd64|AMD64)$") + set(MimerSQL_library_hints "C:\\MimerSQLDev\\lib\\amd64") + else() + set(MimerSQL_library_hints "") + endif() + elseif(APPLE AND ${CMAKE_SYSTEM_NAME} MATCHES "Darwin") + set(MimerSQL_library_hints "/usr/local/lib") + set(MimerSQL_include_dir_hints "/usr/local/include") + else() + set(MimerSQL_include_dir_hints "") + set(MimerSQL_library_hints "") + endif() + endif() +endif() + +find_path(Mimer_INCLUDE_DIR + NAMES mimerapi.h + HINTS ${MimerSQL_include_dir_hints}) + +if(WIN32) + if(CMAKE_SYSTEM_PROCESSOR MATCHES "^(x86|X86)$") + set(MIMER_LIBS_NAMES mimapi32) + elseif(CMAKE_SYSTEM_PROCESSOR MATCHES "^(amd64|AMD64)$") + set(MIMER_LIBS_NAMES mimapi64) + endif() +else() + set(MIMER_LIBS_NAMES mimerapi) +endif() + +find_library(Mimer_LIBRARIES + NAMES ${MIMER_LIBS_NAMES} + HINTS ${MimerSQL_library_hints}) + +include(FindPackageHandleStandardArgs) + +find_package_handle_standard_args(Mimer + REQUIRED_VARS Mimer_LIBRARIES Mimer_INCLUDE_DIR) + + + +# Now try to get the include and library path. +if(Mimer_FOUND) + set(Mimer_INCLUDE_DIRS ${Mimer_INCLUDE_DIR}) + set(Mimer_LIBRARY_DIRS ${Mimer_LIBRARIES}) + if (NOT TARGET MimerSQL::MimerSQL) + add_library(MimerSQL::MimerSQL UNKNOWN IMPORTED) + set_target_properties(MimerSQL::MimerSQL PROPERTIES + IMPORTED_LOCATION "${Mimer_LIBRARY_DIRS}" + INTERFACE_INCLUDE_DIRECTORIES "${Mimer_INCLUDE_DIRS}") + endif () +endif() + +mark_as_advanced(Mimer_INCLUDE_DIR Mimer_LIBRARIES) + +include(FeatureSummary) +set_package_properties(MimerSQL PROPERTIES + URL "https://www.mimer.com" + DESCRIPTION "Mimer client library") diff --git a/cmake/FindWrapResolv.cmake b/cmake/FindWrapResolv.cmake new file mode 100644 index 00000000..a0d1e786 --- /dev/null +++ b/cmake/FindWrapResolv.cmake @@ -0,0 +1,53 @@ +# Copyright (C) 2022 The Qt Company Ltd. +# Copyright (C) 2023 Intel Corpotation. +# SPDX-License-Identifier: BSD-3-Clause + +# We can't create the same interface imported target multiple times, CMake will complain if we do +# that. This can happen if the find_package call is done in multiple different subdirectories. +if(TARGET WrapResolv::WrapResolv) + set(WrapResolv_FOUND ON) + return() +endif() + +set(WrapResolv_FOUND OFF) + +include(CheckCXXSourceCompiles) +include(CMakePushCheckState) + +if(QNX) + find_library(LIBRESOLV socket) +else() + find_library(LIBRESOLV resolv) +endif() + +cmake_push_check_state() +if(LIBRESOLV) + list(APPEND CMAKE_REQUIRED_LIBRARIES "${LIBRESOLV}") +endif() + +check_cxx_source_compiles(" +#include +#include + +int main(int, char **argv) +{ + res_state statep; + int n = res_nmkquery(statep, 0, argv[1], 0, 0, NULL, 0, NULL, NULL, 0); + n = res_nsend(statep, NULL, 0, NULL, 0); + n = dn_expand(NULL, NULL, NULL, NULL, 0); + return n; +} +" HAVE_LIBRESOLV_FUNCTIONS) + +cmake_pop_check_state() + +if(HAVE_LIBRESOLV_FUNCTIONS) + set(WrapResolv_FOUND ON) + add_library(WrapResolv::WrapResolv INTERFACE IMPORTED) + if(LIBRESOLV) + target_link_libraries(WrapResolv::WrapResolv INTERFACE "${LIBRESOLV}") + endif() +endif() + +include(FindPackageHandleStandardArgs) +find_package_handle_standard_args(WrapResolv DEFAULT_MSG WrapResolv_FOUND) diff --git a/cmake/FindWrapRt.cmake b/cmake/FindWrapRt.cmake index 91651c32..b394b062 100644 --- a/cmake/FindWrapRt.cmake +++ b/cmake/FindWrapRt.cmake @@ -21,17 +21,31 @@ if(LIBRT) endif() check_cxx_source_compiles(" -#include #include +#include int main(int, char **) { - timespec ts; clock_gettime(CLOCK_REALTIME, &ts); -}" HAVE_GETTIME) + struct timespec ts; + clock_gettime(CLOCK_REALTIME, &ts); + return 0; +} +" HAVE_GETTIME) + +check_cxx_source_compiles(" +#include +#include +#include + +int main(int, char **) { + shm_open(\"test\", O_RDWR | O_CREAT | O_EXCL, 0666); + shm_unlink(\"test\"); + return 0; +} +" HAVE_SHM_OPEN_SHM_UNLINK) cmake_pop_check_state() - -if(HAVE_GETTIME) +if(HAVE_GETTIME OR HAVE_SHM_OPEN_SHM_UNLINK) set(WrapRt_FOUND ON) add_library(WrapRt::WrapRt INTERFACE IMPORTED) if (LIBRT) diff --git a/cmake/FindWrapVulkanHeaders.cmake b/cmake/FindWrapVulkanHeaders.cmake index 79f5dfc9..92510ae0 100644 --- a/cmake/FindWrapVulkanHeaders.cmake +++ b/cmake/FindWrapVulkanHeaders.cmake @@ -48,6 +48,26 @@ if(Vulkan_INCLUDE_DIR) target_include_directories(WrapVulkanHeaders::WrapVulkanHeaders INTERFACE ${__qt_molten_vk_homebrew_include_path}) endif() + + # Check for homebrew vulkan-headers folder structure + # If instead of molten-vk folder, CMAKE_PREFIX_PATH points to Homebrew's + # vulkan-headers installation, then we will not be able to find molten-vk + # headers. If we assume that user has installed the molten-vk formula as + # well, then we might have a chance to pick it up like this. + if(Vulkan_INCLUDE_DIR MATCHES "/homebrew/Cellar/") + set(__qt_standalone_molten_vk_homebrew_include_path + "${Vulkan_INCLUDE_DIR}/../../../../opt/molten-vk/include") + else() + set(__qt_standalone_molten_vk_homebrew_include_path + "${Vulkan_INCLUDE_DIR}/../../molten-vk/include") + endif() + get_filename_component( + __qt_standalone_molten_vk_homebrew_include_path + "${__qt_standalone_molten_vk_homebrew_include_path}" ABSOLUTE) + if(EXISTS "${__qt_standalone_molten_vk_homebrew_include_path}") + target_include_directories(WrapVulkanHeaders::WrapVulkanHeaders INTERFACE + ${__qt_standalone_molten_vk_homebrew_include_path}) + endif() endif() endif() diff --git a/cmake/FindWrapZSTD.cmake b/cmake/FindWrapZSTD.cmake index fb86c276..fb424236 100644 --- a/cmake/FindWrapZSTD.cmake +++ b/cmake/FindWrapZSTD.cmake @@ -28,10 +28,10 @@ include(FindPackageHandleStandardArgs) if(TARGET zstd::libzstd_static OR TARGET zstd::libzstd_shared) find_package_handle_standard_args(WrapZSTD REQUIRED_VARS zstd_VERSION VERSION_VAR zstd_VERSION) - if(TARGET zstd::libzstd_static) - set(zstdtargetsuffix "_static") - else() + if(TARGET zstd::libzstd_shared) set(zstdtargetsuffix "_shared") + else() + set(zstdtargetsuffix "_static") endif() if(NOT TARGET WrapZSTD::WrapZSTD) add_library(WrapZSTD::WrapZSTD INTERFACE IMPORTED) diff --git a/cmake/ModuleDescription.json.in b/cmake/ModuleDescription.json.in index 4d07bfae..93f9a5ed 100644 --- a/cmake/ModuleDescription.json.in +++ b/cmake/ModuleDescription.json.in @@ -7,6 +7,7 @@ "compiler_target": "${CMAKE_CXX_COMPILER_TARGET}", "compiler_version": "${CMAKE_CXX_COMPILER_VERSION}", "cross_compiled": ${cross_compilation}, - "target_system": "${CMAKE_SYSTEM_NAME}" + "target_system": "${CMAKE_SYSTEM_NAME}", + "architecture": "${TEST_architecture_arch}" } } diff --git a/cmake/QtAutoDetect.cmake b/cmake/QtAutoDetect.cmake index 07afc1a8..0e2ede24 100644 --- a/cmake/QtAutoDetect.cmake +++ b/cmake/QtAutoDetect.cmake @@ -8,6 +8,9 @@ # Make sure to not run detection when building standalone tests, because the detection was already # done when initially configuring qtbase. +# This needs to be here because QtAutoDetect loads before any other modules +option(QT_USE_VCPKG "Enable the use of vcpkg" ON) + function(qt_internal_ensure_static_qt_config) if(NOT DEFINED BUILD_SHARED_LIBS) set(BUILD_SHARED_LIBS OFF CACHE BOOL "Build Qt statically or dynamically" FORCE) @@ -161,7 +164,7 @@ function(qt_auto_detect_android) endfunction() function(qt_auto_detect_vcpkg) - if(DEFINED ENV{VCPKG_ROOT}) + if(QT_USE_VCPKG AND DEFINED ENV{VCPKG_ROOT}) set(vcpkg_toolchain_file "$ENV{VCPKG_ROOT}/scripts/buildsystems/vcpkg.cmake") get_filename_component(vcpkg_toolchain_file "${vcpkg_toolchain_file}" ABSOLUTE) @@ -174,8 +177,8 @@ function(qt_auto_detect_vcpkg) endif() set(CMAKE_TOOLCHAIN_FILE "${vcpkg_toolchain_file}" CACHE STRING "" FORCE) message(STATUS "Using vcpkg from $ENV{VCPKG_ROOT}") - if(DEFINED ENV{VCPKG_DEFAULT_TRIPLET} AND NOT DEFINED VCPKG_TARGET_TRIPLET) - set(VCPKG_TARGET_TRIPLET "$ENV{VCPKG_DEFAULT_TRIPLET}" CACHE STRING "") + if(DEFINED ENV{QT_VCPKG_TARGET_TRIPLET} AND NOT DEFINED VCPKG_TARGET_TRIPLET) + set(VCPKG_TARGET_TRIPLET "$ENV{QT_VCPKG_TARGET_TRIPLET}" CACHE STRING "") message(STATUS "Using vcpkg triplet ${VCPKG_TARGET_TRIPLET}") endif() unset(vcpkg_toolchain_file) @@ -229,7 +232,9 @@ function(qt_auto_detect_ios) endif() set(CMAKE_OSX_ARCHITECTURES "${osx_architectures}" CACHE STRING "") - qt_internal_ensure_static_qt_config() + if(NOT DEFINED BUILD_SHARED_LIBS) + qt_internal_ensure_static_qt_config() + endif() # Disable qt rpaths for iOS, just like mkspecs/common/uikit.conf does, due to those # bundles not being able to use paths outside the app bundle. Not sure this is strictly @@ -239,7 +244,15 @@ function(qt_auto_detect_ios) endfunction() function(qt_auto_detect_cmake_config) - if(CMAKE_CONFIGURATION_TYPES) + # If CMAKE_CONFIGURATION_TYPES are not set for the multi-config generator use Release and + # Debug configurations by default, instead of those are proposed by the CMake internal logic. + get_property(is_multi GLOBAL PROPERTY GENERATOR_IS_MULTI_CONFIG) + if(is_multi) + if(NOT CMAKE_CONFIGURATION_TYPES) + set(CMAKE_CONFIGURATION_TYPES Release Debug) + set(CMAKE_CONFIGURATION_TYPES "${CMAKE_CONFIGURATION_TYPES}" PARENT_SCOPE) + endif() + # Allow users to specify this option. if(NOT QT_MULTI_CONFIG_FIRST_CONFIG) list(GET CMAKE_CONFIGURATION_TYPES 0 first_config_type) @@ -476,6 +489,12 @@ function(qt_auto_detect_integrity) endif() endfunction() +# Save the build type before project() might set one. +# This allows us to determine if the user has set an explicit build type that we should use. +function(qt_auto_detect_cmake_build_type) + set(__qt_auto_detect_cmake_build_type_before_project_call "${CMAKE_BUILD_TYPE}" PARENT_SCOPE) +endfunction() + # Let CMake load our custom platform modules. # CMake-provided platform modules take precedence. if(NOT QT_AVOID_CUSTOM_PLATFORM_MODULES) @@ -495,3 +514,4 @@ qt_auto_detect_wasm() qt_auto_detect_win32_arm() qt_auto_detect_linux_x86() qt_auto_detect_integrity() +qt_auto_detect_cmake_build_type() diff --git a/cmake/QtBaseGlobalTargets.cmake b/cmake/QtBaseGlobalTargets.cmake index cb5b6736..18116a35 100644 --- a/cmake/QtBaseGlobalTargets.cmake +++ b/cmake/QtBaseGlobalTargets.cmake @@ -216,6 +216,7 @@ qt_copy_or_install(FILES cmake/QtAndroidHelpers.cmake cmake/QtAppHelpers.cmake cmake/QtAutogenHelpers.cmake + cmake/QtBaseTopLevelHelpers.cmake cmake/QtBuild.cmake cmake/QtBuildInformation.cmake cmake/QtCMakeHelpers.cmake @@ -332,6 +333,7 @@ set(__public_cmake_helpers cmake/QtCopyFileIfDifferent.cmake cmake/QtFeature.cmake cmake/QtFeatureCommon.cmake + cmake/QtInitProject.cmake cmake/QtPublicAppleHelpers.cmake cmake/QtPublicCMakeHelpers.cmake cmake/QtPublicCMakeVersionHelpers.cmake @@ -395,17 +397,30 @@ if(QT_WILL_INSTALL) ) endif() -if(MACOS) - qt_copy_or_install(FILES - cmake/macos/MacOSXBundleInfo.plist.in - DESTINATION "${__GlobalConfig_install_dir}/macos" +if(APPLE) + if(MACOS) + set(platform_shortname "macos") + elseif(IOS) + set(platform_shortname "ios") + endif() + + qt_copy_or_install(FILES "cmake/${platform_shortname}/Info.plist.app.in" + DESTINATION "${__GlobalConfig_install_dir}/${platform_shortname}" ) -elseif(IOS) - qt_copy_or_install(FILES - cmake/ios/Info.plist.app.in - cmake/ios/LaunchScreen.storyboard - DESTINATION "${__GlobalConfig_install_dir}/ios" + # For examples built as part of prefix build before install + file(COPY "cmake/${platform_shortname}/Info.plist.app.in" + DESTINATION "${__GlobalConfig_build_dir}/${platform_shortname}" ) + + if(IOS) + qt_copy_or_install(FILES "cmake/ios/LaunchScreen.storyboard" + DESTINATION "${__GlobalConfig_install_dir}/ios" + ) + # For examples built as part of prefix build before install + file(COPY "cmake/ios/LaunchScreen.storyboard" + DESTINATION "${__GlobalConfig_build_dir}/ios" + ) + endif() elseif(WASM) configure_file("${CMAKE_CURRENT_SOURCE_DIR}/util/wasm/wasmtestrunner/qt-wasmtestrunner.py" "${QT_BUILD_DIR}/${INSTALL_LIBEXECDIR}/qt-wasmtestrunner.py" @ONLY) diff --git a/cmake/QtBaseTopLevelHelpers.cmake b/cmake/QtBaseTopLevelHelpers.cmake new file mode 100644 index 00000000..27f0852b --- /dev/null +++ b/cmake/QtBaseTopLevelHelpers.cmake @@ -0,0 +1,85 @@ +# Copyright (C) 2023 The Qt Company Ltd. +# SPDX-License-Identifier: BSD-3-Clause + +# Depends on __qt6_qtbase_src_path being set in the top-level dir. +macro(qt_internal_top_level_setup_autodetect) + # Run platform auto-detection /before/ the first project() call and thus + # before the toolchain file is loaded. + # Don't run auto-detection when doing standalone tests. In that case, the detection + # results are taken from either QtBuildInternals or the qt.toolchain.cmake file. + + if(NOT QT_BUILD_STANDALONE_TESTS) + set(__qt6_auto_detect_path "${__qt6_qtbase_src_path}/cmake/QtAutoDetect.cmake") + if(NOT EXISTS "${__qt6_auto_detect_path}") + message(FATAL_ERROR "Required file does not exist: '${__qt6_auto_detect_path}'") + endif() + include("${__qt6_auto_detect_path}") + endif() +endmacro() + +macro(qt_internal_top_level_setup_after_project) + # TODO: Remove this variable once the top-level calls this function and + # qt_internal_qt_configure_end is not called in qt_print_build_instructions anymore. + set(__qt6_top_level_after_project_called TRUE) + + qt_internal_top_level_setup_testing() +endmacro() + +macro(qt_internal_top_level_setup_testing) + # Required so we can call ctest from the root build directory + enable_testing() +endmacro() + +# Depends on __qt6_qtbase_src_path being set in the top-level dir. +macro(qt_internal_top_level_setup_cmake_module_path) + if (NOT QT_BUILD_STANDALONE_TESTS) + set(__qt6_cmake_module_path "${__qt6_qtbase_src_path}/cmake") + if(NOT EXISTS "${__qt6_cmake_module_path}") + message(FATAL_ERROR "Required directory does not exist: '${__qt6_cmake_module_path}'") + endif() + + list(APPEND CMAKE_MODULE_PATH "${__qt6_cmake_module_path}") + + list(APPEND CMAKE_MODULE_PATH + "${__qt6_cmake_module_path}/3rdparty/extra-cmake-modules/find-modules") + list(APPEND CMAKE_MODULE_PATH "${__qt6_cmake_module_path}/3rdparty/kwin") + endif() +endmacro() + +macro(qt_internal_top_level_before_build_submodules) + qt_internal_top_level_setup_no_create_targets() +endmacro() + +macro(qt_internal_top_level_setup_no_create_targets) + # Also make sure the CMake config files do not recreate the already-existing targets + if (NOT QT_BUILD_STANDALONE_TESTS) + set(QT_NO_CREATE_TARGETS TRUE) + endif() +endmacro() + +macro(qt_internal_top_level_end) + qt_internal_print_top_level_info() + + # Depends on QtBuildInternalsConfig being included, which is the case whenver any repo is + # configured. + qt_internal_qt_configure_end() +endmacro() + +function(qt_internal_print_top_level_info) + if(NOT QT_BUILD_STANDALONE_TESTS) + # Display a summary of everything + include(QtBuildInformation) + include(QtPlatformSupport) + qt_print_feature_summary() + qt_print_build_instructions() + endif() +endfunction() + +macro(qt_internal_top_level_after_add_subdirectory) + if(module STREQUAL "qtbase") + if (NOT QT_BUILD_STANDALONE_TESTS) + list(APPEND CMAKE_PREFIX_PATH "${QtBase_BINARY_DIR}/${INSTALL_LIBDIR}/cmake") + list(APPEND CMAKE_FIND_ROOT_PATH "${QtBase_BINARY_DIR}") + endif() + endif() +endmacro() diff --git a/cmake/QtBuild.cmake b/cmake/QtBuild.cmake index 97aa76fe..1dc576d2 100644 --- a/cmake/QtBuild.cmake +++ b/cmake/QtBuild.cmake @@ -281,7 +281,7 @@ qt_setup_tool_path_command() # Platform define path, etc. if(WIN32) set(QT_DEFAULT_PLATFORM_DEFINITIONS WIN32 _ENABLE_EXTENDED_ALIGNED_STORAGE) - if(CMAKE_SIZEOF_VOID_P EQUAL 8) + if(QT_64BIT) list(APPEND QT_DEFAULT_PLATFORM_DEFINITIONS WIN64 _WIN64) endif() @@ -434,7 +434,7 @@ set(QT_TOP_LEVEL_SOURCE_DIR "${CMAKE_CURRENT_SOURCE_DIR}") # Prevent warnings about object files without any symbols. This is a common # thing in Qt as we tend to build files unconditionally, and then use ifdefs # to compile out parts that are not relevant. -if(CMAKE_HOST_APPLE AND APPLE) +if(CMAKE_CXX_COMPILER_ID MATCHES "AppleClang") foreach(lang ASM C CXX) # We have to tell 'ar' to not run ranlib by itself, by passing the 'S' option set(CMAKE_${lang}_ARCHIVE_CREATE " qcS ") @@ -577,11 +577,6 @@ endif() _qt_internal_determine_if_host_info_package_needed(__qt_build_requires_host_info_package) _qt_internal_find_host_info_package("${__qt_build_requires_host_info_package}") -# Create tool script wrapper if necessary. -# TODO: Remove once all direct usages of QT_TOOL_COMMAND_WRAPPER_PATH are replaced with function -# calls. -_qt_internal_generate_tool_command_wrapper() - # This sets up the poor man's scope finalizer mechanism. # For newer CMake versions, we use cmake_language(DEFER CALL) instead. if(CMAKE_VERSION VERSION_LESS "3.19.0") diff --git a/cmake/QtBuildInformation.cmake b/cmake/QtBuildInformation.cmake index 6929f913..063c5866 100644 --- a/cmake/QtBuildInformation.cmake +++ b/cmake/QtBuildInformation.cmake @@ -111,6 +111,13 @@ from the build directory") if(QT_SUPERBUILD) qt_internal_save_previously_visited_packages() endif() + + # TODO: Abuse qt_print_build_instructions being called as the last command in a top-level build. + # Instead we should call this explicitly at the end of the top-level project. + # TODO: Remove this once the top-level calls qt_internal_top_level_setup_after_project + if(QT_SUPERBUILD AND NOT __qt6_top_level_after_project_called) + qt_internal_qt_configure_end() + endif() endfunction() function(qt_configure_print_summary_helper summary_reports force_show) diff --git a/cmake/QtBuildInternals/QtBuildInternalsConfig.cmake b/cmake/QtBuildInternals/QtBuildInternalsConfig.cmake index fe4cbe3e..e8e98154 100644 --- a/cmake/QtBuildInternals/QtBuildInternalsConfig.cmake +++ b/cmake/QtBuildInternals/QtBuildInternalsConfig.cmake @@ -105,6 +105,30 @@ endif() # build. include(QtPlatformSupport) +# Set FEATURE_${feature} if INPUT_${feature} is set in certain circumstances. +# +# Needs to be in QtBuildInternalsConfig.cmake instead of QtFeature.cmake because it's used in +# qt_build_internals_disable_pkg_config_if_needed. +function(qt_internal_compute_feature_value_from_possible_input feature) + # If FEATURE_ is not defined try to use the INPUT_ variable to enable/disable feature. + # If FEATURE_ is defined and the configure script is being used (so + # QT_INTERNAL_CALLED_FROM_CONFIGURE is TRUE), ignore the FEATURE_ variable, and take into + # account the INPUT_ variable instead, because a command line argument takes priority over + # a pre-cached FEATURE_ variable. + if((NOT DEFINED FEATURE_${feature} OR QT_INTERNAL_CALLED_FROM_CONFIGURE) + AND DEFINED INPUT_${feature} + AND NOT "${INPUT_${feature}}" STREQUAL "undefined" + AND NOT "${INPUT_${feature}}" STREQUAL "") + if(INPUT_${feature}) + set(FEATURE_${feature} ON) + else() + set(FEATURE_${feature} OFF) + endif() + + set(FEATURE_${feature} "${FEATURE_${feature}}" PARENT_SCOPE) + endif() +endfunction() + function(qt_build_internals_disable_pkg_config_if_needed) # pkg-config should not be used by default on Darwin and Windows platforms (and QNX), as defined # in the qtbase/configure.json. Unfortunately by the time the feature is evaluated there are @@ -131,15 +155,7 @@ function(qt_build_internals_disable_pkg_config_if_needed) endif() # Features won't have been evaluated yet if this is the first run, have to evaluate this here - if ((NOT DEFINED "FEATURE_pkg_config") AND (DEFINED "INPUT_pkg_config") - AND (NOT "${INPUT_pkg_config}" STREQUAL "undefined") - AND (NOT "${INPUT_pkg_config}" STREQUAL "")) - if(INPUT_pkg_config) - set(FEATURE_pkg_config ON) - else() - set(FEATURE_pkg_config OFF) - endif() - endif() + qt_internal_compute_feature_value_from_possible_input(pkg_config) # If user explicitly specified a value for the feature, honor it, even if it might break # the build. @@ -296,7 +312,27 @@ function(qt_build_internals_add_toplevel_targets) COMMENT "Building everything in ${qt_repo_targets_name}/${qt_repo_target_basename}") add_dependencies("${qt_repo_target_name}" ${qt_repo_targets}) list(APPEND qt_repo_target_all "${qt_repo_target_name}") + + # Create special dependency target for External Project examples excluding targets + # marked as skipped. + set(qt_repo_target_name + "${qt_repo_targets_name}_${qt_repo_target_basename}_for_examples") + add_custom_target("${qt_repo_target_name}") + + set(unskipped_targets "") + foreach(target IN LISTS qt_repo_targets) + if(TARGET "${target}") + qt_internal_is_target_skipped_for_examples("${target}" is_skipped) + if(NOT is_skipped) + list(APPEND unskipped_targets "${target}") + endif() + endif() + endforeach() + if(unskipped_targets) + add_dependencies("${qt_repo_target_name}" ${unskipped_targets}) + endif() endif() + endforeach() if (qt_repo_target_all) # Note qt_repo_targets_name is different from qt_repo_target_name that is used above. @@ -570,9 +606,27 @@ macro(qt_build_repo_end) set(QT_INTERNAL_FRESH_REQUESTED "FALSE" CACHE INTERNAL "") endif() + if(NOT QT_SUPERBUILD) + qt_internal_qt_configure_end() + endif() + list(POP_BACK CMAKE_MESSAGE_CONTEXT) endmacro() +# Function called either at the end of per-repo configuration, or at the end of configuration of +# a super build. +# At the moment it is called before examples are configured in a per-repo build. We might want +# to change that at some point if needed. +function(qt_internal_qt_configure_end) + # If Qt is configued via the configure script, remove the marker variable, so that any future + # reconfigurations that are done by calling cmake directly don't trigger configure specific + # logic. + unset(QT_INTERNAL_CALLED_FROM_CONFIGURE CACHE) + + # Clean up stale feature input values. + qt_internal_clean_feature_inputs() +endfunction() + macro(qt_build_repo) qt_build_repo_begin(${ARGN}) @@ -856,7 +910,7 @@ macro(qt_examples_build_begin) set(QT_EXAMPLE_DEPENDENCIES ${qt_repo_plugins_recursive} ${arg_DEPENDS}) if(TARGET ${qt_repo_targets_name}_src) - list(APPEND QT_EXAMPLE_DEPENDENCIES ${qt_repo_targets_name}_src) + list(APPEND QT_EXAMPLE_DEPENDENCIES ${qt_repo_targets_name}_src_for_examples) endif() if(TARGET ${qt_repo_targets_name}_tools) @@ -980,19 +1034,118 @@ set(CMAKE_INSTALL_PREFIX \"\${_qt_internal_examples_cmake_install_prefix_backup} set(CMAKE_UNITY_BUILD ${QT_UNITY_BUILD}) endmacro() +# Allows building an example either as an ExternalProject or in-tree with the Qt build. +# Also allows installing the example sources. function(qt_internal_add_example subdir) - if(NOT QT_IS_EXTERNAL_EXAMPLES_BUILD) - qt_internal_add_example_in_tree(${ARGV}) - else() - qt_internal_add_example_external_project(${ARGV}) + # Pre-compute unique example name based on the subdir, in case of target name clashes. + qt_internal_get_example_unique_name(unique_example_name "${subdir}") + + # QT_INTERNAL_NO_CONFIGURE_EXAMPLES is not meant to be used by Qt builders, it's here for faster + # testing of the source installation code path for build system engineers. + if(NOT QT_INTERNAL_NO_CONFIGURE_EXAMPLES) + if(NOT QT_IS_EXTERNAL_EXAMPLES_BUILD) + qt_internal_add_example_in_tree("${subdir}") + else() + qt_internal_add_example_external_project("${subdir}" + NAME "${unique_example_name}") + endif() endif() + + if(QT_INSTALL_EXAMPLES_SOURCES) + string(TOLOWER ${PROJECT_NAME} project_name_lower) + + qt_internal_install_example_sources("${subdir}" + NAME "${unique_example_name}" + REPO_NAME "${project_name_lower}") + endif() +endfunction() + +# Gets the install prefix where an example should be installed. +# Used for computing the final installation path. +function(qt_internal_get_example_install_prefix out_var) + # Allow customizing the installation path of the examples. Will be used in CI. + if(QT_INTERNAL_EXAMPLES_INSTALL_PREFIX) + set(qt_example_install_prefix "${QT_INTERNAL_EXAMPLES_INSTALL_PREFIX}") + else() + set(qt_example_install_prefix "${CMAKE_INSTALL_PREFIX}/${INSTALL_EXAMPLESDIR}") + endif() + file(TO_CMAKE_PATH "${qt_example_install_prefix}" qt_example_install_prefix) + set(${out_var} "${qt_example_install_prefix}" PARENT_SCOPE) +endfunction() + +# Gets the install prefix where an example's sources should be installed. +# Used for computing the final installation path. +function(qt_internal_get_examples_sources_install_prefix out_var) + # Allow customizing the installation path of the examples source specifically. + if(QT_INTERNAL_EXAMPLES_SOURCES_INSTALL_PREFIX) + set(qt_example_install_prefix "${QT_INTERNAL_EXAMPLES_SOURCES_INSTALL_PREFIX}") + else() + qt_internal_get_example_install_prefix(qt_example_install_prefix) + endif() + file(TO_CMAKE_PATH "${qt_example_install_prefix}" qt_example_install_prefix) + set(${out_var} "${qt_example_install_prefix}" PARENT_SCOPE) +endfunction() + +# Gets the relative path of an example, relative to the current repo's examples source dir. +# QT_EXAMPLE_BASE_DIR is meant to be already set in a parent scope. +function(qt_internal_get_example_rel_path out_var subdir) + file(RELATIVE_PATH example_rel_path + "${QT_EXAMPLE_BASE_DIR}" "${CMAKE_CURRENT_SOURCE_DIR}/${subdir}") + set(${out_var} "${example_rel_path}" PARENT_SCOPE) +endfunction() + +# Gets the install path where an example should be installed. +function(qt_internal_get_example_install_path out_var subdir) + qt_internal_get_example_install_prefix(qt_example_install_prefix) + qt_internal_get_example_rel_path(example_rel_path "${subdir}") + set(example_install_path "${qt_example_install_prefix}/${example_rel_path}") + + set(${out_var} "${example_install_path}" PARENT_SCOPE) +endfunction() + +# Gets the install path where an example's sources should be installed. +function(qt_internal_get_examples_sources_install_path out_var subdir) + qt_internal_get_examples_sources_install_prefix(qt_example_install_prefix) + qt_internal_get_example_rel_path(example_rel_path "${subdir}") + set(example_install_path "${qt_example_install_prefix}/${example_rel_path}") + + set(${out_var} "${example_install_path}" PARENT_SCOPE) +endfunction() + +# Get the unique name of an example project based on its subdir or explicitly given name. +# Makes the name unique by appending a short sha1 hash of the relative path of the example +# if a target of the same name already exist. +function(qt_internal_get_example_unique_name out_var subdir) + qt_internal_get_example_rel_path(example_rel_path "${subdir}") + + set(name "${subdir}") + + # qtdeclarative has calls like qt_internal_add_example(imagine/automotive) + # so passing a nested subdirectory. Custom targets (and thus ExternalProjects) can't contain + # slashes, so extract the last part of the path to be used as a name. + if(name MATCHES "/") + string(REPLACE "/" ";" exploded_path "${name}") + list(POP_BACK exploded_path last_dir) + if(NOT last_dir) + message(FATAL_ERROR "Example subdirectory must have a name.") + else() + set(name "${last_dir}") + endif() + endif() + + # Likely a clash with an example subdir ExternalProject custom target of the same name in a + # top-level build. + if(TARGET "${name}") + string(SHA1 rel_path_hash "${example_rel_path}") + string(SUBSTRING "${rel_path_hash}" 0 4 short_hash) + set(name "${name}-${short_hash}") + endif() + + set(${out_var} "${name}" PARENT_SCOPE) endfunction() # Use old non-ExternalProject approach, aka build in-tree with the Qt build. function(qt_internal_add_example_in_tree subdir) - file(RELATIVE_PATH example_rel_path - "${QT_EXAMPLE_BASE_DIR}" "${CMAKE_CURRENT_SOURCE_DIR}/${subdir}") - # Unset the default CMAKE_INSTALL_PREFIX that's generated in # ${CMAKE_CURRENT_BINARY_DIR}/cmake_install.cmake # so we can override it with a different value in @@ -1006,15 +1159,8 @@ unset(CMAKE_INSTALL_PREFIX) # Override the install prefix in the subdir cmake_install.cmake, so that # relative install(TARGETS DESTINATION) calls in example projects install where we tell them to. - # Allow customizing the installation path of the examples. Will be used in CI. - if(QT_INTERNAL_EXAMPLES_INSTALL_PREFIX) - set(qt_example_install_prefix "${QT_INTERNAL_EXAMPLES_INSTALL_PREFIX}") - else() - set(qt_example_install_prefix "${CMAKE_INSTALL_PREFIX}/${INSTALL_EXAMPLESDIR}") - endif() - file(TO_CMAKE_PATH "${qt_example_install_prefix}" qt_example_install_prefix) - - set(CMAKE_INSTALL_PREFIX "${qt_example_install_prefix}/${example_rel_path}") + qt_internal_get_example_install_path(example_install_path "${subdir}") + set(CMAKE_INSTALL_PREFIX "${example_install_path}") # Make sure unclean example projects have their INSTALL_EXAMPLEDIR set to "." # Won't have any effect on example projects that don't use INSTALL_EXAMPLEDIR. @@ -1024,7 +1170,7 @@ unset(CMAKE_INSTALL_PREFIX) # TODO: Remove once all repositories use qt_internal_add_example instead of add_subdirectory. set(QT_INTERNAL_SET_EXAMPLE_INSTALL_DIR_TO_DOT ON) - add_subdirectory(${subdir} ${ARGN}) + add_subdirectory(${subdir}) endfunction() function(qt_internal_add_example_external_project subdir) @@ -1034,33 +1180,6 @@ function(qt_internal_add_example_external_project subdir) cmake_parse_arguments(PARSE_ARGV 1 arg "${options}" "${singleOpts}" "${multiOpts}") - file(RELATIVE_PATH example_rel_path - "${QT_EXAMPLE_BASE_DIR}" "${CMAKE_CURRENT_SOURCE_DIR}/${subdir}") - - if(NOT arg_NAME) - set(arg_NAME "${subdir}") - - # qtdeclarative has calls like qt_internal_add_example(imagine/automotive) - # so passing a nested subdirectory. Custom targets (and thus ExternalProjects) can't contain - # slashes, so extract the last part of the path to be used as a name. - if(arg_NAME MATCHES "/") - string(REPLACE "/" ";" exploded_path "${arg_NAME}") - list(POP_BACK exploded_path last_dir) - if(NOT last_dir) - message(FATAL_ERROR "Example subdirectory must have a name.") - else() - set(arg_NAME "${last_dir}") - endif() - endif() - endif() - - # Likely a clash with an example subdir ExternalProject custom target of the same name. - if(TARGET "${arg_NAME}") - string(SHA1 rel_path_hash "${example_rel_path}") - string(SUBSTRING "${rel_path_hash}" 0 4 short_hash) - set(arg_NAME "${arg_NAME}-${short_hash}") - endif() - # TODO: Fix example builds when using Conan / install prefixes are different for each repo. if(QT_SUPERBUILD OR QtBase_BINARY_DIR) # When doing a top-level build or when building qtbase, @@ -1281,15 +1400,7 @@ function(qt_internal_add_example_external_project subdir) # example_source_dir, use _qt_internal_override_example_install_dir_to_dot to ensure # INSTALL_EXAMPLEDIR does not interfere. - # Allow customizing the installation path of the examples. Will be used in CI. - if(QT_INTERNAL_EXAMPLES_INSTALL_PREFIX) - set(qt_example_install_prefix "${QT_INTERNAL_EXAMPLES_INSTALL_PREFIX}") - else() - set(qt_example_install_prefix "${CMAKE_INSTALL_PREFIX}/${INSTALL_EXAMPLESDIR}") - endif() - file(TO_CMAKE_PATH "${qt_example_install_prefix}" qt_example_install_prefix) - - set(example_install_prefix "${qt_example_install_prefix}/${example_rel_path}") + qt_internal_get_example_install_path(example_install_path "${subdir}") set(ep_binary_dir "${CMAKE_CURRENT_BINARY_DIR}/${subdir}") @@ -1304,7 +1415,7 @@ function(qt_internal_add_example_external_project subdir) PREFIX "${CMAKE_CURRENT_BINARY_DIR}/${subdir}-ep" STAMP_DIR "${CMAKE_CURRENT_BINARY_DIR}/${subdir}-ep/stamp" BINARY_DIR "${ep_binary_dir}" - INSTALL_DIR "${example_install_prefix}" + INSTALL_DIR "${example_install_path}" INSTALL_COMMAND "" ${build_command} TEST_COMMAND "" @@ -1358,6 +1469,54 @@ execute_process( endfunction() +function(qt_internal_install_example_sources subdir) + set(options "") + set(single_args NAME REPO_NAME) + set(multi_args "") + + cmake_parse_arguments(PARSE_ARGV 1 arg "${options}" "${single_args}" "${multi_args}") + + qt_internal_get_examples_sources_install_path(example_install_path "${subdir}") + + # The trailing slash is important to avoid duplicate nested directory names. + set(example_source_dir "${subdir}/") + + # Allow controlling whether sources should be part of the default install target. + if(QT_INSTALL_EXAMPLES_SOURCES_BY_DEFAULT) + set(exclude_from_all "") + else() + set(exclude_from_all "EXCLUDE_FROM_ALL") + endif() + + # Create an install component for all example sources. Can also be part of the default + # install target if EXCLUDE_FROM_ALL is not passed. + install( + DIRECTORY "${example_source_dir}" + DESTINATION "${example_install_path}" + COMPONENT "examples_sources" + USE_SOURCE_PERMISSIONS + ${exclude_from_all} + ) + + # Also create a specific install component just for this repo's examples. + install( + DIRECTORY "${example_source_dir}" + DESTINATION "${example_install_path}" + COMPONENT "examples_sources_${arg_REPO_NAME}" + USE_SOURCE_PERMISSIONS + EXCLUDE_FROM_ALL + ) + + # Also create a specific install component just for the current example's sources. + install( + DIRECTORY "${example_source_dir}" + DESTINATION "${example_install_path}" + COMPONENT "examples_sources_${arg_NAME}" + USE_SOURCE_PERMISSIONS + EXCLUDE_FROM_ALL + ) +endfunction() + if ("STANDALONE_TEST" IN_LIST Qt6BuildInternals_FIND_COMPONENTS) include(${CMAKE_CURRENT_LIST_DIR}/QtStandaloneTestTemplateProject/Main.cmake) if (NOT PROJECT_VERSION_MAJOR) diff --git a/cmake/QtBuildInternalsExtra.cmake.in b/cmake/QtBuildInternalsExtra.cmake.in index 2dc906b6..c035a216 100644 --- a/cmake/QtBuildInternalsExtra.cmake.in +++ b/cmake/QtBuildInternalsExtra.cmake.in @@ -103,6 +103,9 @@ set(QT_BUILD_EXAMPLES_AS_EXTERNAL "@QT_BUILD_EXAMPLES_AS_EXTERNAL@" CACHE BOOL # Propagate usage of ccache. set(QT_USE_CCACHE @QT_USE_CCACHE@ CACHE BOOL "Enable the use of ccache") +# Propagate usage of vcpkg, ON by default. +set(QT_USE_VCPKG @QT_USE_VCPKG@ CACHE BOOL "Enable the use of vcpkg") + # Propagate usage of unity build. set(QT_UNITY_BUILD @QT_UNITY_BUILD@ CACHE BOOL "Enable unity (jumbo) build") set(QT_UNITY_BUILD_BATCH_SIZE "@QT_UNITY_BUILD_BATCH_SIZE@" CACHE STRING "Unity build batch size") @@ -163,6 +166,7 @@ function(qt_internal_force_set_cmake_build_type_conditionally value) AND NOT QT_NO_FORCE_SET_CMAKE_BUILD_TYPE AND NOT __qt_internal_extras_is_multi_config) set(CMAKE_BUILD_TYPE "${value}" CACHE STRING "Choose the type of build." FORCE) + set(__qt_build_internals_cmake_build_type "${value}" PARENT_SCOPE) endif() endfunction() diff --git a/cmake/QtCMakeVersionHelpers.cmake b/cmake/QtCMakeVersionHelpers.cmake index c17a75e2..322e58ee 100644 --- a/cmake/QtCMakeVersionHelpers.cmake +++ b/cmake/QtCMakeVersionHelpers.cmake @@ -14,6 +14,8 @@ function(qt_internal_get_supported_min_cmake_version_for_building_qt out_var) set(supported_version "${QT_SUPPORTED_MIN_CMAKE_VERSION_FOR_BUILDING_QT}") # We're building qtbase so the values come from .cmake.conf. + elseif(APPLE) + set(supported_version "${QT_SUPPORTED_MIN_CMAKE_VERSION_FOR_BUILDING_QT_APPLE}") elseif(BUILD_SHARED_LIBS) set(supported_version "${QT_SUPPORTED_MIN_CMAKE_VERSION_FOR_BUILDING_QT_SHARED}") else() @@ -30,7 +32,9 @@ function(qt_internal_get_supported_min_cmake_version_for_using_qt out_var) "It should have been set by this point.") endif() - if(BUILD_SHARED_LIBS) + if(APPLE) + set(supported_version "${QT_SUPPORTED_MIN_CMAKE_VERSION_FOR_USING_QT_APPLE}") + elseif(BUILD_SHARED_LIBS) set(supported_version "${QT_SUPPORTED_MIN_CMAKE_VERSION_FOR_USING_QT_SHARED}") else() set(supported_version "${QT_SUPPORTED_MIN_CMAKE_VERSION_FOR_USING_QT_STATIC}") diff --git a/cmake/QtCompilerOptimization.cmake b/cmake/QtCompilerOptimization.cmake index a093ed4d..e1d363af 100644 --- a/cmake/QtCompilerOptimization.cmake +++ b/cmake/QtCompilerOptimization.cmake @@ -92,10 +92,10 @@ endif() # Windows MSVC if(MSVC) - set(QT_CFLAGS_OPTIMIZE "-O2") + set(QT_CFLAGS_OPTIMIZE "-O2 -Ob3") # -Ob3 was introduced in Visual Studio 2019 version 16.0 set(QT_CFLAGS_OPTIMIZE_DEBUG "-Od") set(QT_CFLAGS_OPTIMIZE_SIZE "-O1") - set(QT_CFLAGS_OPTIMIZE_VALID_VALUES "/O2" "/O1" "/Od" "/Ob0" "/Ob1" "/Ob2" "/O0" "-O0") + set(QT_CFLAGS_OPTIMIZE_VALID_VALUES "/O2" "/O1" "/Od" "/Ob0" "/Ob1" "/Ob2" "/Ob3" "/O0" "-O0") if(CLANG) set(QT_CFLAGS_OPTIMIZE_FULL "/clang:-O3") diff --git a/cmake/QtConfig.cmake.in b/cmake/QtConfig.cmake.in index 2e498284..ba287447 100644 --- a/cmake/QtConfig.cmake.in +++ b/cmake/QtConfig.cmake.in @@ -29,13 +29,12 @@ list(APPEND CMAKE_MODULE_PATH "${_qt_import_prefix}") list(APPEND CMAKE_MODULE_PATH "${_qt_import_prefix}/3rdparty/extra-cmake-modules/find-modules") list(APPEND CMAKE_MODULE_PATH "${_qt_import_prefix}/3rdparty/kwin") -if(APPLE AND (NOT CMAKE_SYSTEM_NAME OR CMAKE_SYSTEM_NAME STREQUAL "Darwin")) - # Add module directory to pick up custom Info.plist template for macOS - list(APPEND CMAKE_MODULE_PATH "${_qt_import_prefix}/macos") -elseif(APPLE AND CMAKE_SYSTEM_NAME STREQUAL "iOS") - # Add module directory to pick up custom Info.plist template for iOS - set(__qt_internal_cmake_ios_support_files_path "${_qt_import_prefix}/ios") - list(APPEND CMAKE_MODULE_PATH "${__qt_internal_cmake_ios_support_files_path}") +if(APPLE) + if(NOT CMAKE_SYSTEM_NAME OR CMAKE_SYSTEM_NAME STREQUAL "Darwin") + set(__qt_internal_cmake_apple_support_files_path "${_qt_import_prefix}/macos") + elseif(CMAKE_SYSTEM_NAME STREQUAL "iOS") + set(__qt_internal_cmake_apple_support_files_path "${_qt_import_prefix}/ios") + endif() endif() # Public helpers available to all Qt packages. diff --git a/cmake/QtExecutableHelpers.cmake b/cmake/QtExecutableHelpers.cmake index c863d3f4..7295eec4 100644 --- a/cmake/QtExecutableHelpers.cmake +++ b/cmake/QtExecutableHelpers.cmake @@ -148,7 +148,7 @@ function(qt_internal_add_executable name) if(WASM) # WASM unconditionally sets DISABLE_EXCEPTION_CATCHING=1 - qt_internal_set_exceptions_flags("${name}" NO_EXCEPTIONS) + qt_internal_set_exceptions_flags("${name}" FALSE) else() qt_internal_set_exceptions_flags("${name}" ${arg_EXCEPTIONS}) endif() @@ -422,7 +422,7 @@ function(qt_internal_add_configure_time_executable target) ) set(should_build_at_configure_time TRUE) - if(EXISTS "${target_binary_path}") + if(EXISTS "${target_binary_path}" AND EXISTS "${timestamp_file}") set(last_ts 0) foreach(source IN LISTS sources) file(TIMESTAMP "${source}" ts "%s") diff --git a/cmake/QtFeature.cmake b/cmake/QtFeature.cmake index c778fa29..207abd58 100644 --- a/cmake/QtFeature.cmake +++ b/cmake/QtFeature.cmake @@ -177,15 +177,20 @@ function(qt_evaluate_config_expression resultVar) set(${resultVar} ${result} PARENT_SCOPE) endfunction() +function(_qt_internal_get_feature_condition_keywords out_var) + set(keywords "EQUAL" "LESS" "LESS_EQUAL" "GREATER" "GREATER_EQUAL" "STREQUAL" "STRLESS" + "STRLESS_EQUAL" "STRGREATER" "STRGREATER_EQUAL" "VERSION_EQUAL" "VERSION_LESS" + "VERSION_LESS_EQUAL" "VERSION_GREATER" "VERSION_GREATER_EQUAL" "MATCHES" + "EXISTS" "COMMAND" "DEFINED" "NOT" "AND" "OR" "TARGET" "EXISTS" "IN_LIST" "(" ")") + set(${out_var} "${keywords}" PARENT_SCOPE) +endfunction() + function(_qt_internal_dump_expression_values expression_dump expression) set(dump "") set(skipNext FALSE) set(isTargetExpression FALSE) - set(keywords "EQUAL" "LESS" "LESS_EQUAL" "GREATER" "GREATER_EQUAL" "STREQUAL" "STRLESS" - "STRLESS_EQUAL" "STRGREATER" "STRGREATER_EQUAL" "VERSION_EQUAL" "VERSION_LESS" - "VERSION_LESS_EQUAL" "VERSION_GREATER" "VERSION_GREATER_EQUAL" "MATCHES" - "EXISTS" "COMMAND" "DEFINED" "NOT" "AND" "OR" "TARGET" "EXISTS" "IN_LIST" "(" ")") + _qt_internal_get_feature_condition_keywords(keywords) list(LENGTH expression length) math(EXPR length "${length}-1") @@ -239,19 +244,44 @@ endfunction() # ${computed} is also stored when reconfiguring and the condition does not align with the user # provided value. # -function(qt_feature_check_and_save_user_provided_value resultVar feature condition computed label) +function(qt_feature_check_and_save_user_provided_value + resultVar feature condition condition_expression computed label) if (DEFINED "FEATURE_${feature}") # Revisit new user provided value set(user_value "${FEATURE_${feature}}") - string(TOUPPER "${user_value}" result) + string(TOUPPER "${user_value}" user_value_upper) + set(result "${user_value_upper}") - # If the build is marked as dirty and the user_value doesn't meet the new condition, - # reset it to the computed one. + # If ${feature} depends on another dirty feature, reset the ${feature} value to + # ${computed}. get_property(dirty_build GLOBAL PROPERTY _qt_dirty_build) - if(NOT condition AND result AND dirty_build) - set(result "${computed}") - message(WARNING "Reset FEATURE_${feature} value to ${result}, because it doesn't \ -meet its condition after reconfiguration.") + if(dirty_build) + _qt_internal_feature_compute_feature_dependencies(deps "${feature}") + if(deps) + get_property(dirty_features GLOBAL PROPERTY _qt_dirty_features) + foreach(dirty_feature ${dirty_features}) + if(dirty_feature IN_LIST deps AND NOT "${result}" STREQUAL "${computed}") + set(result "${computed}") + message(WARNING + "Auto-resetting 'FEATURE_${feature}' from '${user_value_upper}' to " + "'${computed}', " + "because the dependent feature '${dirty_feature}' was marked dirty.") + + # Append ${feature} as a new dirty feature. + set_property(GLOBAL APPEND PROPERTY _qt_dirty_features "${feature}") + break() + endif() + endforeach() + endif() + + # If the build is marked as dirty and the feature doesn't meet its condition, + # reset its value to the computed one, which is likely OFF. + if(NOT condition AND result) + set(result "${computed}") + message(WARNING "Resetting 'FEATURE_${feature}' from '${user_value_upper}' to " + "'${computed}' because it doesn't meet its condition after reconfiguration. " + "Condition expression is: '${condition_expression}'") + endif() endif() set(bool_values OFF NO FALSE N ON YES TRUE Y) @@ -299,6 +329,14 @@ condition:\n ${conditionString}\nCondition values dump:\n ${conditionDump} set(QT_KNOWN_FEATURES "${QT_KNOWN_FEATURES}" CACHE INTERNAL "" FORCE) endmacro() +macro(_qt_internal_parse_feature_definition feature) + cmake_parse_arguments(arg + "PRIVATE;PUBLIC" + "LABEL;PURPOSE;SECTION;" + "AUTODETECT;CONDITION;ENABLE;DISABLE;EMIT_IF" + ${_QT_FEATURE_DEFINITION_${feature}}) +endmacro() + # The build system stores 2 CMake cache variables for each feature, to allow detecting value changes # during subsequent reconfigurations. @@ -334,9 +372,7 @@ function(qt_evaluate_feature feature) message(FATAL_ERROR "Attempting to evaluate feature ${feature} but its definition is missing. Either the feature does not exist or a dependency to the module that defines it is missing") endif() - cmake_parse_arguments(arg - "PRIVATE;PUBLIC" - "LABEL;PURPOSE;SECTION;" "AUTODETECT;CONDITION;ENABLE;DISABLE;EMIT_IF" ${_QT_FEATURE_DEFINITION_${feature}}) + _qt_internal_parse_feature_definition("${feature}") if("${arg_ENABLE}" STREQUAL "") set(arg_ENABLE OFF) @@ -374,16 +410,7 @@ function(qt_evaluate_feature feature) qt_evaluate_config_expression(emit_if ${arg_EMIT_IF}) endif() - # If FEATURE_ is not defined trying to use INPUT_ variable to enable/disable feature. - if ((NOT DEFINED "FEATURE_${feature}") AND (DEFINED "INPUT_${feature}") - AND (NOT "${INPUT_${feature}}" STREQUAL "undefined") - AND (NOT "${INPUT_${feature}}" STREQUAL "")) - if(INPUT_${feature}) - set(FEATURE_${feature} ON) - else() - set(FEATURE_${feature} OFF) - endif() - endif() + qt_internal_compute_feature_value_from_possible_input("${feature}") # Warn about a feature which is not emitted, but the user explicitly provided a value for it. if(NOT emit_if AND DEFINED FEATURE_${feature}) @@ -401,7 +428,8 @@ function(qt_evaluate_feature feature) # Only save the user provided value if the feature was emitted. if(emit_if) qt_feature_check_and_save_user_provided_value( - saved_user_value "${feature}" "${condition}" "${computed}" "${arg_LABEL}") + saved_user_value + "${feature}" "${condition}" "${arg_CONDITION}" "${computed}" "${arg_LABEL}") else() # Make sure the feature internal value is OFF if not emitted. set(saved_user_value OFF) @@ -414,6 +442,60 @@ function(qt_evaluate_feature feature) set(QT_FEATURE_LABEL_${feature} "${arg_LABEL}" CACHE INTERNAL "") endfunction() +# Collect feature names that ${feature} depends on, by inspecting the given expression. +function(_qt_internal_feature_extract_feature_dependencies_from_expression out_var expression) + list(LENGTH expression length) + math(EXPR length "${length}-1") + + if(length LESS 0) + set(${out_var} "" PARENT_SCOPE) + return() + endif() + + set(deps "") + + foreach(memberIdx RANGE ${length}) + list(GET expression ${memberIdx} member) + if(member MATCHES "^QT_FEATURE_(.+)") + list(APPEND deps "${CMAKE_MATCH_1}") + endif() + endforeach() + set(${out_var} "${deps}" PARENT_SCOPE) +endfunction() + +# Collect feature names that ${feature} depends on, based on feature names that appear +# in the ${feature}'s condition expressions. +function(_qt_internal_feature_compute_feature_dependencies out_var feature) + # Only compute the deps once per feature. + get_property(deps_computed GLOBAL PROPERTY _qt_feature_deps_computed_${feature}) + if(deps_computed) + get_property(deps GLOBAL PROPERTY _qt_feature_deps_${feature}) + set(${out_var} "${deps}" PARENT_SCOPE) + return() + endif() + + _qt_internal_parse_feature_definition("${feature}") + + set(options_to_check AUTODETECT CONDITION ENABLE DISABLE EMIT_IF) + set(deps "") + + # Go through each option that takes condition expressions and collect the feature names. + foreach(option ${options_to_check}) + set(option_value "${arg_${option}}") + if(option_value) + _qt_internal_feature_extract_feature_dependencies_from_expression( + option_deps "${option_value}") + if(option_deps) + list(APPEND deps ${option_deps}) + endif() + endif() + endforeach() + + set_property(GLOBAL PROPERTY _qt_feature_deps_computed_${feature} TRUE) + set_property(GLOBAL PROPERTY _qt_feature_deps_${feature} "${deps}") + set(${out_var} "${deps}" PARENT_SCOPE) +endfunction() + function(qt_feature_config feature config_var_name) qt_feature_normalize_name("${feature}" feature) cmake_parse_arguments(PARSE_ARGV 2 arg @@ -786,6 +868,49 @@ function(qt_feature_copy_global_config_features_to_core target) endif() endfunction() +function(qt_internal_detect_dirty_features) + # We need to clean up QT_FEATURE_*, but only once per configuration cycle + get_property(qt_feature_clean GLOBAL PROPERTY _qt_feature_clean) + if(NOT qt_feature_clean AND NOT QT_NO_FEATURE_AUTO_RESET) + message(STATUS "Checking for feature set changes") + set_property(GLOBAL PROPERTY _qt_feature_clean TRUE) + foreach(feature ${QT_KNOWN_FEATURES}) + qt_internal_compute_feature_value_from_possible_input("${feature}") + + if(DEFINED "FEATURE_${feature}" AND + NOT "${QT_FEATURE_${feature}}" STREQUAL "${FEATURE_${feature}}") + message(" '${feature}' was changed from ${QT_FEATURE_${feature}} " + "to ${FEATURE_${feature}}") + set(dirty_build TRUE) + set_property(GLOBAL APPEND PROPERTY _qt_dirty_features "${feature}") + endif() + unset("QT_FEATURE_${feature}" CACHE) + endforeach() + + set(QT_KNOWN_FEATURES "" CACHE INTERNAL "" FORCE) + + if(dirty_build) + set_property(GLOBAL PROPERTY _qt_dirty_build TRUE) + message(WARNING + "Due to detected feature set changes, dependent features " + "will be re-computed automatically. This might cause a lot of files to be rebuilt. " + "To disable this behavior, configure with -DQT_NO_FEATURE_AUTO_RESET=ON") + endif() + endif() +endfunction() + +function(qt_internal_clean_feature_inputs) + foreach(feature IN LISTS QT_KNOWN_FEATURES) + # Unset the INPUT_foo cache variables after they were used in feature evaluation, to + # ensure stale values don't influence features upon reconfiguration when + # QT_INTERNAL_CALLED_FROM_CONFIGURE is TRUE and the INPUT_foo variable is not passed. + # e.g. first configure -no-gui, then manually toggle FEATURE_gui to ON in + # CMakeCache.txt, then reconfigure (with the configure script) without -no-gui. + # Without this unset(), we'd have switched FEATURE_gui to OFF again. + unset(INPUT_${feature} CACHE) + endforeach() +endfunction() + function(qt_config_compile_test name) if(DEFINED "TEST_${name}") return() @@ -924,6 +1049,7 @@ function(qt_config_compile_test name) # fail instead of cmake abort later via CMAKE_REQUIRED_LIBRARIES. string(FIND "${library}" "::" cmake_target_namespace_separator) if(NOT cmake_target_namespace_separator EQUAL -1) + message(STATUS "Performing Test ${arg_LABEL} - Failed because ${library} not found") set(HAVE_${name} FALSE) break() endif() diff --git a/cmake/QtFindPackageHelpers.cmake b/cmake/QtFindPackageHelpers.cmake index 15651b31..92fd0719 100644 --- a/cmake/QtFindPackageHelpers.cmake +++ b/cmake/QtFindPackageHelpers.cmake @@ -16,6 +16,15 @@ function(qt_find_package_promote_targets_to_global_scope target) "qt_find_package_targets_dict" "promote_global") endfunction() +# As an optimization when using -developer-build, qt_find_package records which +# packages were found during the initial configuration. Then on subsequent +# reconfigurations it skips looking for packages that were not found on the +# initial run. +# For the build system to pick up a newly added qt_find_package call, you need to: +# - Start with a clean build dir +# - Or remove the /CMakeCache.txt file and configure from scratch +# - Or remove the QT_INTERNAL_PREVIOUSLY_FOUND_PACKAGES cache variable (by +# editing CMakeCache.txt) and reconfigure. macro(qt_find_package) # Get the target names we expect to be provided by the package. set(find_package_options CONFIG NO_MODULE MODULE REQUIRED) diff --git a/cmake/QtFrameworkHelpers.cmake b/cmake/QtFrameworkHelpers.cmake index 9a48bd9c..6d67bc4a 100644 --- a/cmake/QtFrameworkHelpers.cmake +++ b/cmake/QtFrameworkHelpers.cmake @@ -4,6 +4,7 @@ macro(qt_find_apple_system_frameworks) if(APPLE) qt_internal_find_apple_system_framework(FWAppKit AppKit) + qt_internal_find_apple_system_framework(FWCFNetwork CFNetwork) qt_internal_find_apple_system_framework(FWAssetsLibrary AssetsLibrary) qt_internal_find_apple_system_framework(FWPhotos Photos) qt_internal_find_apple_system_framework(FWAudioToolbox AudioToolbox) @@ -58,7 +59,7 @@ function(qt_internal_find_apple_system_framework out_var framework_name) endif() endfunction() -# Copy header files to QtXYZ.framework/Versions/A/Headers/ +# Copy header files to the framework's Headers directory # Use this function for header files that # - are not added as source files to the target # - are not marked as PUBLIC_HEADER @@ -71,7 +72,7 @@ function(qt_copy_framework_headers target) set(options) set(oneValueArgs) - set(multiValueArgs PUBLIC PRIVATE QPA) + set(multiValueArgs PUBLIC PRIVATE QPA RHI) cmake_parse_arguments(arg "${options}" "${oneValueArgs}" "${multiValueArgs}" ${ARGN}) qt_internal_get_framework_info(fw ${target}) @@ -79,10 +80,11 @@ function(qt_copy_framework_headers target) set(output_dir_PUBLIC "${output_dir}/${fw_versioned_header_dir}") set(output_dir_PRIVATE "${output_dir}/${fw_private_module_header_dir}/private") set(output_dir_QPA "${output_dir}/${fw_private_module_header_dir}/qpa") + set(output_dir_RHI "${output_dir}/${fw_private_module_header_dir}/rhi") set(out_files) - foreach(type IN ITEMS PUBLIC PRIVATE QPA) + foreach(type IN ITEMS PUBLIC PRIVATE QPA RHI) set(fw_output_header_dir "${output_dir_${type}}") foreach(hdr IN LISTS arg_${type}) get_filename_component(in_file_path ${hdr} ABSOLUTE) @@ -164,8 +166,13 @@ function(qt_internal_get_framework_info out_var target) set(${out_var}_name "${module}") set(${out_var}_dir "${${out_var}_name}.framework") set(${out_var}_header_dir "${${out_var}_dir}/Headers") - set(${out_var}_versioned_header_dir "${${out_var}_dir}/Versions/${${out_var}_version}/Headers") - set(${out_var}_private_header_dir "${${out_var}_header_dir}/${${out_var}_bundle_version}") + if(UIKIT) + # iOS frameworks do not version their headers + set(${out_var}_versioned_header_dir "${${out_var}_header_dir}") + else() + set(${out_var}_versioned_header_dir "${${out_var}_dir}/Versions/${${out_var}_version}/Headers") + endif() + set(${out_var}_private_header_dir "${${out_var}_versioned_header_dir}/${${out_var}_bundle_version}") set(${out_var}_private_module_header_dir "${${out_var}_private_header_dir}/${module}") set(${out_var}_name "${${out_var}_name}" PARENT_SCOPE) diff --git a/cmake/QtHeadersClean.cmake b/cmake/QtHeadersClean.cmake index d04a80d9..53cd0035 100644 --- a/cmake/QtHeadersClean.cmake +++ b/cmake/QtHeadersClean.cmake @@ -104,6 +104,7 @@ function(qt_internal_add_headersclean_target module_target module_headers) set(hcleanFLAGS -Wall -Wextra -Werror -Woverloaded-virtual -Wshadow -Wundef -Wfloat-equal -Wnon-virtual-dtor -Wpointer-arith -Wformat-security -Wno-long-long -Wno-variadic-macros + -fno-operator-names -pedantic-errors) if(QT_FEATURE_reduce_relocations AND UNIX) @@ -181,9 +182,7 @@ function(qt_internal_add_headersclean_target module_target module_headers) ) set(input_header_path_type ABSOLUTE) elseif ("${CMAKE_CXX_COMPILER_ID}" STREQUAL "MSVC") - # -Za would enable strict standards behavior, but we can't add it because - # and violate the standards. - set(hcleanFLAGS -std:c++latest -Zc:__cplusplus -WX -W3) + set(hcleanFLAGS -std:c++latest -Zc:__cplusplus -WX -W3 -EHsc) # Because we now add `-DNOMINMAX` to `PlatformCommonInternal`. set(hcleanUDEFS -UNOMINMAX) diff --git a/cmake/QtInitProject.cmake b/cmake/QtInitProject.cmake new file mode 100644 index 00000000..a42f59f5 --- /dev/null +++ b/cmake/QtInitProject.cmake @@ -0,0 +1,214 @@ +# Copyright (C) 2023 The Qt Company Ltd. +# SPDX-License-Identifier: BSD-3-Clause +cmake_minimum_required(VERSION 3.16) + +if(NOT PROJECT_DIR) + set(PROJECT_DIR "${CMAKE_SOURCE_DIR}") +endif() + +get_filename_component(project_name "${PROJECT_DIR}" NAME) + +get_filename_component(project_abs_dir "${PROJECT_DIR}" ABSOLUTE) +if(NOT IS_DIRECTORY "${project_abs_dir}") + message(FATAL_ERROR "Unable to scan ${project_abs_dir}. The directory doesn't exist.") +endif() + +set(known_extensions "") +set(types "") + +# The function allows extending the capabilities of this script and establishes simple relation +# chains between the file types. +# Option Arguments: +# EXPERIMENTAL +# Marks that the support of the following files is experimental and the required Qt modules +# are in Technical preview state. +# DEPRECATED +# Marks that the support of the following files will be discontinued soon and the required +# Qt modules are deprecated. +# One-value Arguments: +# TEMPLATE +# The CMake code template. Use the '@files@' string for the files substitution. +# Multi-value Arguments: +# EXTENSIONS +# List of the file extensions treated as this source 'type'. +# MODULES +# List of Qt modules required for these file types. +# DEPENDS +# The prerequisite source 'type' needed by this source 'type' +macro(handle_type type) + cmake_parse_arguments(arg + "EXPERIMENTAL;DEPRECATED" + "TEMPLATE" + "EXTENSIONS;MODULES;DEPENDS" + ${ARGN} + ) + + if(NOT arg_EXTENSIONS) + message(FATAL_ERROR "Unexpected call handle_type of with no EXTENSIONS specified." + " This is the Qt issue, please report a bug at https://bugreports.qt.io.") + endif() + set(unique_extensions_subset "${known_extensions}") + list(REMOVE_ITEM unique_extensions_subset ${arg_EXTENSIONS}) + if(NOT "${known_extensions}" STREQUAL "${unique_extensions_subset}") + message(FATAL_ERROR "${type} contains duplicated extensions, this is not supported." + " This is the Qt issue, please report a bug at https://bugreports.qt.io.") + endif() + set(${type}_file_extensions "${arg_EXTENSIONS}") + + if(NOT arg_TEMPLATE) + message(FATAL_ERROR "Unexpected call handle_type of with no TEMPLATE specified." + " This is the Qt issue, please report a bug at https://bugreports.qt.io.") + endif() + set(${type}_template "${arg_TEMPLATE}") + + if(arg_MODULES) + set(${type}_required_modules "${arg_MODULES}") + endif() + + list(APPEND types ${type}) + + if(arg_EXPERIMENTAL) + set(${type}_is_experimental TRUE) + else() + set(${type}_is_experimental FALSE) + endif() + + if(arg_DEPRECATED) + set(${type}_is_deprecated TRUE) + else() + set(${type}_is_deprecated FALSE) + endif() + + if(arg_DEPENDS) + set(${type}_dependencies ${arg_DEPENDS}) + endif() +endmacro() + +handle_type(cpp EXTENSIONS .c .cc .cpp .cxx .h .hh .hxx .hpp MODULES Core TEMPLATE +"\n\nqt_add_executable(${project_name} + @files@ +)" +) + +handle_type(qml EXTENSIONS .qml .js .mjs MODULES Gui Qml Quick TEMPLATE +"\n\nqt_add_qml_module(${project_name} + URI ${project_name} + OUTPUT_DIRECTORY qml + VERSION 1.0 + RESOURCE_PREFIX /qt/qml + QML_FILES + @files@ +)" +) + +handle_type(ui EXTENSIONS .ui MODULES Gui Widgets DEPENDS cpp TEMPLATE +"\n\ntarget_sources(${project_name} + PRIVATE + @files@ +)" +) + +handle_type(qrc EXTENSIONS .qrc DEPENDS cpp TEMPLATE +"\n\nqt_add_resources(${project_name}_resources @files@) +target_sources(${project_name} + PRIVATE + \\\${${project_name}_resources} +)" +) + +handle_type(protobuf EXPERIMENTAL EXTENSIONS .proto MODULES Protobuf Grpc TEMPLATE +"\n\nqt_add_protobuf(${project_name} + GENERATE_PACKAGE_SUBFOLDERS + PROTO_FILES + @files@ +)" +) + +set(extra_packages "") +file(GLOB_RECURSE files RELATIVE "${project_abs_dir}" "${project_abs_dir}/*") +foreach(f IN LISTS files) + get_filename_component(file_extension "${f}" LAST_EXT) + string(TOLOWER "${file_extension}" file_extension) + + foreach(type IN LISTS types) + if(file_extension IN_LIST ${type}_file_extensions) + list(APPEND ${type}_sources "${f}") + list(APPEND packages ${${type}_required_modules}) + if(${type}_is_experimental) + message("We found files with the following extensions in your directory:" + " ${${type}_file_extensions}\n" + "Note that the modules ${${type}_required_modules} are" + " in the technical preview state.") + endif() + if(${type}_is_deprecated) + message("We found files with the following extensions in your directory:" + " ${${type}_file_extensions}\n" + "Note that the modules ${${type}_required_modules} are deprecated.") + endif() + break() + endif() + endforeach() +endforeach() + +if(packages) + list(REMOVE_DUPLICATES packages) + list(JOIN packages " " packages_string) + list(JOIN packages "\n Qt::" deps_string) + set(deps_string "Qt::${deps_string}") +endif() + +set(content +"cmake_minimum_required(VERSION 3.16) +project(${project_name} LANGUAGES CXX) + +find_package(Qt6 REQUIRED COMPONENTS ${packages_string}) +qt_standard_project_setup()" +) + +set(has_useful_sources FALSE) +foreach(type IN LISTS types) + if(${type}_sources) + set(skip FALSE) + foreach(dep IN LISTS ${type}_dependencies) + if(NOT ${dep}_sources) + set(skip TRUE) + message("Sources of type ${${type}_file_extensions} cannot live in the project" + " without ${${dep}_file_extensions} files. Skipping.") + break() + endif() + endforeach() + if(skip) + continue() + endif() + + set(has_useful_sources TRUE) + string(REGEX MATCH "( +)@files@" unused "${${type}_template}") + list(JOIN ${type}_sources "\n${CMAKE_MATCH_1}" ${type}_sources) + string(REPLACE "@files@" "${${type}_sources}" ${type}_content + "${${type}_template}") + string(APPEND content "${${type}_content}") + endif() +endforeach() + +string(APPEND content "\n\ntarget_link_libraries(${project_name} + PRIVATE + ${deps_string} +)\n" +) + +if(EXISTS "${project_abs_dir}/CMakeLists.txt") + message(FATAL_ERROR "Project is already initialized in current directory." + " Please remove CMakeLists.txt if you want to regenerate the project.") +endif() + +if(NOT has_useful_sources) + message(FATAL_ERROR "Could not find any files to generate the project.") +endif() +file(WRITE "${project_abs_dir}/CMakeLists.txt" "${content}") + +message("The project file is successfully generated. To build the project run:" + "\nmkdir build" + "\ncd build" + "\nqt-cmake ${project_abs_dir}" + "\ncmake --build ${project_abs_dir}" +) diff --git a/cmake/QtInternalTargets.cmake b/cmake/QtInternalTargets.cmake index b5831659..faa724e6 100644 --- a/cmake/QtInternalTargets.cmake +++ b/cmake/QtInternalTargets.cmake @@ -156,6 +156,8 @@ qt_internal_add_target_aliases(PlatformToolInternal) target_link_libraries(PlatformToolInternal INTERFACE PlatformAppInternal) qt_internal_add_global_definition(QT_NO_JAVA_STYLE_ITERATORS) +qt_internal_add_global_definition(QT_NO_AS_CONST) +qt_internal_add_global_definition(QT_NO_QEXCHANGE) qt_internal_add_global_definition(QT_NO_NARROWING_CONVERSIONS_IN_CONNECT) qt_internal_add_global_definition(QT_EXPLICIT_QFILE_CONSTRUCTION_FROM_PATH) diff --git a/cmake/QtLalrHelpers.cmake b/cmake/QtLalrHelpers.cmake index 8f7c6d78..a63d8e95 100644 --- a/cmake/QtLalrHelpers.cmake +++ b/cmake/QtLalrHelpers.cmake @@ -35,6 +35,15 @@ function(qt_process_qlalr consuming_target input_file_list flags) return() endif() + qt_internal_is_skipped_test(skipped ${consuming_target}) + if(skipped) + return() + endif() + qt_internal_is_in_test_batch(in_batch ${consuming_target}) + if(in_batch) + _qt_internal_test_batch_target_name(consuming_target) + endif() + foreach(input_file ${input_file_list}) file(STRINGS ${input_file} input_file_lines) qt_qlalr_find_option_in_list("${input_file_lines}" "^%parser(.+)" "parser") diff --git a/cmake/QtModuleConfig.cmake.in b/cmake/QtModuleConfig.cmake.in index 55402f50..cdb79f4b 100644 --- a/cmake/QtModuleConfig.cmake.in +++ b/cmake/QtModuleConfig.cmake.in @@ -27,6 +27,8 @@ endif() if (NOT QT_NO_CREATE_TARGETS AND @INSTALL_CMAKE_NAMESPACE@@target@_FOUND) include("${CMAKE_CURRENT_LIST_DIR}/@INSTALL_CMAKE_NAMESPACE@@target@Targets.cmake") include("${CMAKE_CURRENT_LIST_DIR}/@INSTALL_CMAKE_NAMESPACE@@target@AdditionalTargetInfo.cmake") + include("${CMAKE_CURRENT_LIST_DIR}/@INSTALL_CMAKE_NAMESPACE@@target@ExtraProperties.cmake" + OPTIONAL) if(NOT QT_NO_CREATE_VERSIONLESS_TARGETS) include("${CMAKE_CURRENT_LIST_DIR}/@INSTALL_CMAKE_NAMESPACE@@target@VersionlessTargets.cmake") endif() diff --git a/cmake/QtModuleHelpers.cmake b/cmake/QtModuleHelpers.cmake index 0b42991e..21630860 100644 --- a/cmake/QtModuleHelpers.cmake +++ b/cmake/QtModuleHelpers.cmake @@ -30,6 +30,7 @@ macro(qt_internal_get_internal_add_module_keywords option_args single_args multi EXTERNAL_HEADERS_DIR PRIVATE_HEADER_FILTERS QPA_HEADER_FILTERS + RHI_HEADER_FILTERS HEADER_SYNC_SOURCE_DIRECTORY ${__default_target_info_args} ) @@ -114,6 +115,10 @@ endfunction() # The regular expressions that filter QPA header files out of target sources. # The value must use the following format 'regex1|regex2|regex3'. # +# RHI_HEADER_FILTERS +# The regular expressions that filter RHI header files out of target sources. +# The value must use the following format 'regex1|regex2|regex3'. +# # HEADER_SYNC_SOURCE_DIRECTORY # The source directory for header sync procedure. Header files outside this directory will be # ignored by syncqt. The specifying this directory allows to skip the parsing of the whole @@ -326,14 +331,24 @@ function(qt_internal_add_module target) EXPORT_PROPERTIES "${export_properties}") endif() + # FIXME: This workaround is needed because the deployment logic + # for iOS and WASM just copies/embeds the directly linked library, + # which will just be a versioned symlink to the actual library. + if((UIKIT OR WASM) AND BUILD_SHARED_LIBS) + set(version_args "") + else() + set(version_args + VERSION ${PROJECT_VERSION} + SOVERSION ${PROJECT_VERSION_MAJOR}) + endif() + if(NOT arg_HEADER_MODULE) set_target_properties(${target} PROPERTIES LIBRARY_OUTPUT_DIRECTORY "${QT_BUILD_DIR}/${INSTALL_LIBDIR}" RUNTIME_OUTPUT_DIRECTORY "${QT_BUILD_DIR}/${INSTALL_BINDIR}" ARCHIVE_OUTPUT_DIRECTORY "${QT_BUILD_DIR}/${INSTALL_LIBDIR}" - VERSION ${PROJECT_VERSION} - SOVERSION ${PROJECT_VERSION_MAJOR} - ) + ${version_args} + ) qt_set_target_info_properties(${target} ${ARGN}) qt_handle_multi_config_output_dirs("${target}") @@ -436,6 +451,13 @@ function(qt_internal_add_module target) set_target_properties(${target} PROPERTIES _qt_module_qpa_headers_filter_regex "${qpa_filter_regex}") + set(rhi_filter_regex "") + if(arg_RHI_HEADER_FILTERS) + set(rhi_filter_regex "${arg_RHI_HEADER_FILTERS}") + endif() + set_target_properties(${target} + PROPERTIES _qt_module_rhi_headers_filter_regex "${rhi_filter_regex}") + set(private_filter_regex ".+_p(ch)?\\.h") if(arg_PRIVATE_HEADER_FILTERS) set(private_filter_regex "${private_filter_regex}|${arg_PRIVATE_HEADER_FILTERS}") @@ -793,6 +815,11 @@ set(QT_ALLOW_MISSING_TOOLS_PACKAGES TRUE)") EXPORT_NAME_PREFIX ${INSTALL_CMAKE_NAMESPACE}${target} CONFIG_INSTALL_DIR "${config_install_dir}") + qt_internal_export_genex_properties(TARGETS ${target} + EXPORT_NAME_PREFIX ${INSTALL_CMAKE_NAMESPACE}${target} + CONFIG_INSTALL_DIR "${config_install_dir}" + ) + ### fixme: cmake is missing a built-in variable for this. We want to apply it only to modules and plugins # that belong to Qt. if(NOT arg_HEADER_MODULE) @@ -878,6 +905,7 @@ function(qt_finalize_module target) PUBLIC ${module_headers_public} "${module_depends_header}" PRIVATE ${module_headers_private} QPA ${module_headers_qpa} + RHI ${module_headers_rhi} ) qt_finalize_framework_headers_copy(${target}) @@ -910,6 +938,7 @@ endfunction() # * foo_versioned_inner_include_dir with the value "QtCore/6.2.0/QtCore" # * foo_private_include_dir with the value "QtCore/6.2.0/QtCore/private" # * foo_qpa_include_dir with the value "QtCore/6.2.0/QtCore/qpa" +# * foo_rhi_include_dir with the value "QtCore/6.2.0/QtCore/rhi" # * foo_interface_name the interface name of the module stored in _qt_module_interface_name # property, e.g. Core. # @@ -932,6 +961,9 @@ endfunction() # * foo__qpa_include_dir with # qtbase_build_dir/include/QtCore/6.2.0/QtCore/qpa for build interface and # include/QtCore/6.2.0/QtCore/qpa for install interface. +# * foo__rhi_include_dir with +# qtbase_build_dir/include/QtCore/6.2.0/QtCore/rhi for build interface and +# include/QtCore/6.2.0/QtCore/rhi for install interface. # The following values are set by the function and might be useful in caller's scope: # * repo_install_interface_include_dir contains path to the top-level repository include directory, # e.g. qtbase_build_dir/include @@ -966,6 +998,8 @@ the different base name for the module info variables.") "${${result}_versioned_inner_include_dir}/private") set("${result}_qpa_include_dir" "${${result}_versioned_inner_include_dir}/qpa") + set("${result}_rhi_include_dir" + "${${result}_versioned_inner_include_dir}/rhi") # Module build interface directories set(repo_build_interface_include_dir "${QT_BUILD_DIR}/include") @@ -979,6 +1013,8 @@ the different base name for the module info variables.") "${repo_build_interface_include_dir}/${${result}_private_include_dir}") set("${result}_build_interface_qpa_include_dir" "${repo_build_interface_include_dir}/${${result}_qpa_include_dir}") + set("${result}_build_interface_rhi_include_dir" + "${repo_build_interface_include_dir}/${${result}_rhi_include_dir}") # Module install interface directories set(repo_install_interface_include_dir "${INSTALL_INCLUDEDIR}") @@ -992,6 +1028,8 @@ the different base name for the module info variables.") "${repo_install_interface_include_dir}/${${result}_private_include_dir}") set("${result}_install_interface_qpa_include_dir" "${repo_install_interface_include_dir}/${${result}_qpa_include_dir}") + set("${result}_install_interface_rhi_include_dir" + "${repo_install_interface_include_dir}/${${result}_rhi_include_dir}") set("${result}" "${module}" PARENT_SCOPE) set("${result}_versioned" "${module_versioned}" PARENT_SCOPE) @@ -1005,6 +1043,7 @@ the different base name for the module info variables.") "${${result}_versioned_inner_include_dir}" PARENT_SCOPE) set("${result}_private_include_dir" "${${result}_private_include_dir}" PARENT_SCOPE) set("${result}_qpa_include_dir" "${${result}_qpa_include_dir}" PARENT_SCOPE) + set("${result}_rhi_include_dir" "${${result}_rhi_include_dir}" PARENT_SCOPE) set("${result}_interface_name" "${module_interface_name}" PARENT_SCOPE) # Setting module build interface directories in parent scope @@ -1019,6 +1058,8 @@ the different base name for the module info variables.") "${${result}_build_interface_private_include_dir}" PARENT_SCOPE) set("${result}_build_interface_qpa_include_dir" "${${result}_build_interface_qpa_include_dir}" PARENT_SCOPE) + set("${result}_build_interface_rhi_include_dir" + "${${result}_build_interface_rhi_include_dir}" PARENT_SCOPE) # Setting module install interface directories in parent scope set(repo_install_interface_include_dir "${repo_install_interface_include_dir}" PARENT_SCOPE) @@ -1032,6 +1073,8 @@ the different base name for the module info variables.") "${${result}_install_interface_private_include_dir}" PARENT_SCOPE) set("${result}_install_interface_qpa_include_dir" "${${result}_install_interface_qpa_include_dir}" PARENT_SCOPE) + set("${result}_install_interface_rhi_include_dir" + "${${result}_install_interface_rhi_include_dir}" PARENT_SCOPE) endfunction() function(qt_internal_list_to_json_array out_var list_var) @@ -1075,6 +1118,10 @@ function(qt_describe_module target) endif() set(extra_build_information "") + if(NOT QT_NAMESPACE STREQUAL "") + string(APPEND extra_build_information " + \"namespace\": \"${QT_NAMESPACE}\",") + endif() if(ANDROID) string(APPEND extra_build_information " \"android\": { @@ -1135,7 +1182,7 @@ endfunction() function(qt_internal_install_module_headers target) set(options) set(one_value_args) - set(multi_value_args PUBLIC PRIVATE QPA) + set(multi_value_args PUBLIC PRIVATE QPA RHI) cmake_parse_arguments(arg "${options}" "${one_value_args}" "${multi_value_args}" ${ARGN}) qt_internal_module_info(module ${target}) @@ -1160,6 +1207,7 @@ function(qt_internal_install_module_headers target) PUBLIC ${arg_PUBLIC} PRIVATE ${arg_PRIVATE} QPA ${arg_QPA} + RHI ${arg_RHI} ) else() if(arg_PUBLIC) @@ -1173,6 +1221,9 @@ function(qt_internal_install_module_headers target) if(arg_QPA) qt_install(FILES ${arg_QPA} DESTINATION "${module_install_interface_qpa_include_dir}") endif() + if(arg_RHI) + qt_install(FILES ${arg_RHI} DESTINATION "${module_install_interface_rhi_include_dir}") + endif() endif() endfunction() @@ -1180,6 +1231,7 @@ function(qt_internal_collect_module_headers out_var target) set(${out_var}_public "") set(${out_var}_private "") set(${out_var}_qpa "") + set(${out_var}_rhi "") set(${out_var}_all "") qt_internal_get_target_sources(sources ${target}) @@ -1200,6 +1252,7 @@ function(qt_internal_collect_module_headers out_var target) get_target_property(public_filter ${target} _qt_module_public_headers_filter_regex) get_target_property(private_filter ${target} _qt_module_private_headers_filter_regex) get_target_property(qpa_filter ${target} _qt_module_qpa_headers_filter_regex) + get_target_property(rhi_filter ${target} _qt_module_rhi_headers_filter_regex) set(condition_independent_headers_warning "") foreach(file_path IN LISTS sources) @@ -1251,6 +1304,8 @@ function(qt_internal_collect_module_headers out_var target) list(APPEND ${out_var}_all "${file_path}") if(qpa_filter AND file_name MATCHES "${qpa_filter}") list(APPEND ${out_var}_qpa "${file_path}") + elseif(rhi_filter AND file_name MATCHES "${rhi_filter}") + list(APPEND ${out_var}_rhi "${file_path}") elseif(private_filter AND file_name MATCHES "${private_filter}") list(APPEND ${out_var}_private "${file_path}") elseif((NOT public_filter OR file_name MATCHES "${public_filter}") @@ -1274,7 +1329,7 @@ function(qt_internal_collect_module_headers out_var target) endif() - set(header_types public private qpa) + set(header_types public private qpa rhi) set(has_header_types_properties "") foreach(header_type IN LISTS header_types) get_target_property(current_propety_value ${target} _qt_module_has_${header_type}_headers) @@ -1296,5 +1351,6 @@ function(qt_internal_collect_module_headers out_var target) _qt_module_has_public_headers _qt_module_has_private_headers _qt_module_has_qpa_headers + _qt_module_has_rhi_headers ) endfunction() diff --git a/cmake/QtProcessConfigureArgs.cmake b/cmake/QtProcessConfigureArgs.cmake index 85f5b714..f8f6d309 100644 --- a/cmake/QtProcessConfigureArgs.cmake +++ b/cmake/QtProcessConfigureArgs.cmake @@ -148,6 +148,41 @@ while(NOT "${configure_args}" STREQUAL "") endif() endwhile() +# Read the specified manually generator value from CMake command line. +# The '-cmake-generator' argument has higher priority than CMake command line. +if(NOT generator) + set(is_next_arg_generator_name FALSE) + foreach(arg IN LISTS cmake_args) + if(is_next_arg_generator_name) + set(is_next_arg_generator_name FALSE) + if(NOT arg MATCHES "^-.*") + set(generator "${arg}") + set(auto_detect_generator FALSE) + endif() + elseif(arg MATCHES "^-G(.*)") + set(generator "${CMAKE_MATCH_1}") + if(generator) + set(auto_detect_generator FALSE) + else() + set(is_next_arg_generator_name TRUE) + endif() + endif() + endforeach() +endif() + +# Attempt to detect the generator type, either single or multi-config +if("${generator}" STREQUAL "Xcode" + OR "${generator}" STREQUAL "Ninja Multi-Config" + OR "${generator}" MATCHES "^Visual Studio") + set(multi_config ON) +else() + set(multi_config OFF) +endif() + +# Tell the build system we are configuring via the configure script so we can act on that. +# The cache variable is unset at the end of configuration. +push("-DQT_INTERNAL_CALLED_FROM_CONFIGURE:BOOL=TRUE") + if(FRESH_REQUESTED) push("-DQT_INTERNAL_FRESH_REQUESTED:BOOL=TRUE") if(CMAKE_VERSION VERSION_GREATER_EQUAL "3.24") @@ -826,6 +861,7 @@ translate_boolean_input(precompile_header BUILD_WITH_PCH) translate_boolean_input(unity_build QT_UNITY_BUILD) translate_string_input(unity_build_batch_size QT_UNITY_BUILD_BATCH_SIZE) translate_boolean_input(ccache QT_USE_CCACHE) +translate_boolean_input(vcpkg QT_USE_VCPKG) translate_boolean_input(shared BUILD_SHARED_LIBS) translate_boolean_input(warnings_are_errors WARNINGS_ARE_ERRORS) translate_string_input(qt_namespace QT_NAMESPACE) @@ -875,6 +911,7 @@ endif() drop_input(make) drop_input(nomake) +translate_boolean_input(install-examples-sources QT_INSTALL_EXAMPLES_SOURCES) check_qt_build_parts(nomake) check_qt_build_parts(make) @@ -896,9 +933,9 @@ if(INPUT_force_debug_info) endif() list(LENGTH build_configs nr_of_build_configs) -if(nr_of_build_configs EQUAL 1) +if(nr_of_build_configs EQUAL 1 AND NOT multi_config) push("-DCMAKE_BUILD_TYPE=${build_configs}") -elseif(nr_of_build_configs GREATER 1) +elseif(nr_of_build_configs GREATER 1 OR multi_config) set(multi_config ON) string(REPLACE ";" "[[;]]" escaped_build_configs "${build_configs}") # We must not use the push macro here to avoid variable expansion. @@ -983,6 +1020,14 @@ endforeach() push("${MODULE_ROOT}") +if(INPUT_sysroot) + qtConfAddWarning("The -sysroot option is deprecated and no longer has any effect. " + "It is recommended to use a toolchain file instead, i.e., " + "-DCMAKE_TOOLCHAIN_FILE=. " + "Alternatively, you may use -DCMAKE_SYSROOT option " + "to pass the sysroot to CMake.\n") +endif() + # Restore the escaped semicolons in arguments that are lists list(TRANSFORM cmake_args REPLACE "\\[\\[;\\]\\]" "\\\\;") diff --git a/cmake/QtPublicAppleHelpers.cmake b/cmake/QtPublicAppleHelpers.cmake index 1d489f09..7d9007ce 100644 --- a/cmake/QtPublicAppleHelpers.cmake +++ b/cmake/QtPublicAppleHelpers.cmake @@ -27,7 +27,7 @@ function(_qt_internal_handle_ios_launch_screen target) if(NOT launch_screen AND NOT QT_NO_SET_DEFAULT_IOS_LAUNCH_SCREEN) set(is_default_launch_screen TRUE) set(launch_screen - "${__qt_internal_cmake_ios_support_files_path}/LaunchScreen.storyboard") + "${__qt_internal_cmake_apple_support_files_path}/LaunchScreen.storyboard") endif() # Check that the launch screen exists. @@ -554,15 +554,13 @@ function(_qt_internal_set_xcode_bitcode_enablement target) "NO") endfunction() -function(_qt_internal_generate_ios_info_plist target) +function(_qt_internal_copy_info_plist target) # If the project already specifies a custom file, we don't override it. - get_target_property(existing_plist "${target}" MACOSX_BUNDLE_INFO_PLIST) - if(existing_plist) - return() + get_target_property(info_plist_in "${target}" MACOSX_BUNDLE_INFO_PLIST) + if(NOT info_plist_in) + set(info_plist_in "${__qt_internal_cmake_apple_support_files_path}/Info.plist.app.in") endif() - set(info_plist_in "${__qt_internal_cmake_ios_support_files_path}/Info.plist.app.in") - string(MAKE_C_IDENTIFIER "${target}" target_identifier) set(info_plist_out_dir "${CMAKE_CURRENT_BINARY_DIR}/.qt/info_plist/${target_identifier}") @@ -592,6 +590,62 @@ function(_qt_internal_generate_ios_info_plist target) set_target_properties("${target}" PROPERTIES MACOSX_BUNDLE_INFO_PLIST "${info_plist_out}") endfunction() +function(_qt_internal_plist_buddy plist_file) + cmake_parse_arguments(PARSE_ARGV 1 arg + "" "OUTPUT_VARIABLE;ERROR_VARIABLE" "COMMANDS") + foreach(command ${arg_COMMANDS}) + execute_process(COMMAND "/usr/libexec/PlistBuddy" + -c "${command}" "${plist_file}" + OUTPUT_VARIABLE plist_buddy_output + ERROR_VARIABLE plist_buddy_error) + string(STRIP "${plist_buddy_output}" plist_buddy_output) + if(arg_OUTPUT_VARIABLE) + list(APPEND ${arg_OUTPUT_VARIABLE} ${plist_buddy_output}) + set(${arg_OUTPUT_VARIABLE} ${${arg_OUTPUT_VARIABLE}} PARENT_SCOPE) + endif() + if(arg_ERROR_VARIABLE) + list(APPEND ${arg_ERROR_VARIABLE} ${plist_buddy_error}) + set(${arg_ERROR_VARIABLE} ${${arg_ERROR_VARIABLE}} PARENT_SCOPE) + endif() + if(plist_buddy_error) + return() + endif() + endforeach() +endfunction() + +function(_qt_internal_set_apple_localizations target) + if(QT_NO_SET_PLIST_LOCALIZATIONS) + return() + endif() + + get_target_property(supported_languages "${target}" _qt_apple_supported_languages) + if("${supported_languages}" STREQUAL "supported_languages-NOTFOUND") + return() + endif() + get_target_property(plist_file "${target}" MACOSX_BUNDLE_INFO_PLIST) + if (NOT plist_file) + return() + endif() + + _qt_internal_plist_buddy("${plist_file}" + COMMANDS "print CFBundleLocalizations" + OUTPUT_VARIABLE existing_localizations + ) + if(existing_localizations) + return() + endif() + + list(TRANSFORM supported_languages PREPEND + "Add CFBundleLocalizations: string ") + + _qt_internal_plist_buddy("${plist_file}" + COMMANDS + "Add CFBundleLocalizations array" + ${supported_languages} + "Delete CFBundleAllowMixedLocalizations" + ) +endfunction() + function(_qt_internal_set_ios_simulator_arch target) if(CMAKE_XCODE_ATTRIBUTE_ARCHS OR QT_NO_SET_XCODE_ARCHS) @@ -621,6 +675,9 @@ endfunction() function(_qt_internal_finalize_apple_app target) # Shared between macOS and iOS apps + _qt_internal_copy_info_plist("${target}") + _qt_internal_set_apple_localizations("${target}") + # Only set the various properties if targeting the Xcode generator, otherwise the various # Xcode tokens are embedded as-is instead of being dynamically evaluated. # This affects things like the version number or application name as reported by Qt API. @@ -637,12 +694,12 @@ function(_qt_internal_finalize_apple_app target) endfunction() function(_qt_internal_finalize_ios_app target) - _qt_internal_finalize_apple_app("${target}") + # Must be called before we generate the Info.plist + _qt_internal_handle_ios_launch_screen("${target}") + _qt_internal_finalize_apple_app("${target}") _qt_internal_set_xcode_targeted_device_family("${target}") _qt_internal_set_xcode_bitcode_enablement("${target}") - _qt_internal_handle_ios_launch_screen("${target}") - _qt_internal_generate_ios_info_plist("${target}") _qt_internal_set_ios_simulator_arch("${target}") endfunction() diff --git a/cmake/QtPublicWasmToolchainHelpers.cmake b/cmake/QtPublicWasmToolchainHelpers.cmake index 798258db..4ed223ed 100644 --- a/cmake/QtPublicWasmToolchainHelpers.cmake +++ b/cmake/QtPublicWasmToolchainHelpers.cmake @@ -53,7 +53,7 @@ endfunction() function(__qt_internal_get_emcc_recommended_version out_var) # This version of Qt needs this version of emscripten. - set(QT_EMCC_RECOMMENDED_VERSION "3.1.25") + set(QT_EMCC_RECOMMENDED_VERSION "3.1.37") set(${out_var} "${QT_EMCC_RECOMMENDED_VERSION}" PARENT_SCOPE) endfunction() @@ -81,12 +81,16 @@ function(__qt_internal_get_qt_build_emsdk_version out_var) endif() if(EXISTS "${WASM_BUILD_DIR}/src/corelib/global/qconfig.h") file(READ "${WASM_BUILD_DIR}/src/corelib/global/qconfig.h" ver) - else() + elseif(EXISTS "${WASM_BUILD_DIR}/include/QtCore/qconfig.h") file(READ "${WASM_BUILD_DIR}/include/QtCore/qconfig.h" ver) + else() + message("qconfig.h not found, unable to determine Qt build Emscripten version") + endif() + if (ver) + string(REGEX MATCH "#define QT_EMCC_VERSION.\"[0-9]+\\.[0-9]+\\.[0-9]+\"" emOutput ${ver}) + string(REGEX MATCH "[0-9]+\\.[0-9]+\\.[0-9]+" build_emcc_version "${emOutput}") + set(${out_var} "${build_emcc_version}" PARENT_SCOPE) endif() - string(REGEX MATCH "#define QT_EMCC_VERSION.\"[0-9]+\\.[0-9]+\\.[0-9]+\"" emOutput ${ver}) - string(REGEX MATCH "[0-9]+\\.[0-9]+\\.[0-9]+" build_emcc_version "${emOutput}") - set(${out_var} "${build_emcc_version}" PARENT_SCOPE) endfunction() function(_qt_test_emscripten_version) diff --git a/cmake/QtQmakeHelpers.cmake b/cmake/QtQmakeHelpers.cmake index 51ca444e..89284b09 100644 --- a/cmake/QtQmakeHelpers.cmake +++ b/cmake/QtQmakeHelpers.cmake @@ -37,8 +37,6 @@ function(qt_generate_qconfig_cpp in_file out_file) set(QT_SYS_CONF_DIR "${INSTALL_SYSCONFDIR}") # Compute and set relocation prefixes. - # TODO: Clean this up, there's a bunch of unrealistic assumptions here. - # See qtConfOutput_preparePaths in qtbase/configure.pri. if(WIN32) set(lib_location_absolute_path "${QT_BUILD_INTERNALS_RELOCATABLE_INSTALL_PREFIX}/${INSTALL_BINDIR}") @@ -213,6 +211,7 @@ function(qt_get_qmake_module_name result module) string(REGEX REPLACE "^Qt6" "" module "${module}") string(REGEX REPLACE "Private$" "_private" module "${module}") string(REGEX REPLACE "Qpa$" "_qpa_lib_private" module "${module}") + string(REGEX REPLACE "Rhi$" "_rhi_lib_private" module "${module}") string(TOLOWER "${module}" module) set(${result} ${module} PARENT_SCOPE) endfunction() diff --git a/cmake/QtResourceHelpers.cmake b/cmake/QtResourceHelpers.cmake index a3f86613..2df1fed5 100644 --- a/cmake/QtResourceHelpers.cmake +++ b/cmake/QtResourceHelpers.cmake @@ -3,10 +3,14 @@ function(qt_internal_add_resource target resourceName) if(NOT TARGET "${target}") - qt_internal_is_in_test_batch(in_batch ${target}) - if(NOT in_batch) - message(FATAL_ERROR "Trying to add resource to a non-existing target \"${target}\".") - endif() + message(FATAL_ERROR "${target} is not a target.") + endif() + qt_internal_is_skipped_test(skipped ${target}) + if(skipped) + return() + endif() + qt_internal_is_in_test_batch(in_batch ${target}) + if(in_batch) _qt_internal_test_batch_target_name(target) endif() diff --git a/cmake/QtSetup.cmake b/cmake/QtSetup.cmake index c8209840..762299f9 100644 --- a/cmake/QtSetup.cmake +++ b/cmake/QtSetup.cmake @@ -8,18 +8,14 @@ set(QT_BUILDING_QT TRUE CACHE BOOL "When this is present and set to true, it signals that we are building Qt from source.") -# Pre-calculate the developer_build feature if it's set by the user via INPUT_developer_build -if(NOT FEATURE_developer_build AND INPUT_developer_build - AND NOT "${INPUT_developer_build}" STREQUAL "undefined") - set(FEATURE_developer_build ON) -endif() +# Pre-calculate the developer_build feature if it's set by the user via the INPUT_developer_build +# variable when using the configure script. When not using configure, don't take the INPUT variable +# into account, so that users can toggle the feature directly in the cache or via IDE. +qt_internal_compute_feature_value_from_possible_input(developer_build) # Pre-calculate the no_prefix feature if it's set by configure via INPUT_no_prefix. # This needs to be done before qtbase/configure.cmake is processed. -if(NOT FEATURE_no_prefix AND INPUT_no_prefix - AND NOT "${INPUT_no_prefix}" STREQUAL "undefined") - set(FEATURE_no_prefix ON) -endif() +qt_internal_compute_feature_value_from_possible_input(no_prefix) set(_default_build_type "Release") if(FEATURE_developer_build) @@ -49,7 +45,20 @@ unset(QT_EXTRA_BUILD_INTERNALS_VARS) # Save the global property in a variable to make it available to feature conditions. get_property(QT_GENERATOR_IS_MULTI_CONFIG GLOBAL PROPERTY GENERATOR_IS_MULTI_CONFIG) -if(NOT CMAKE_BUILD_TYPE AND NOT CMAKE_CONFIGURATION_TYPES) +# Try to detect if an explicit CMAKE_BUILD_TYPE was set by the user. +# CMake sets CMAKE_BUILD_TYPE_INIT to Debug on most Windows platforms and doesn't set +# anything for UNIXes. CMake assigns CMAKE_BUILD_TYPE_INIT to CMAKE_BUILD_TYPE during +# first project() if CMAKE_BUILD_TYPE has no previous value. +# We use extra information about the state of CMAKE_BUILD_TYPE before the first +# project() call that's set in QtAutodetect. +# STREQUAL check needs to have expanded variables because an undefined var is not equal +# to an empty defined var. +# See also qt_internal_force_set_cmake_build_type_conditionally which is used +# to set the build type when building other repos or tests. +if("${CMAKE_BUILD_TYPE}" STREQUAL "${CMAKE_BUILD_TYPE_INIT}" + AND NOT __qt_auto_detect_cmake_build_type_before_project_call + AND NOT __qt_build_internals_cmake_build_type + AND NOT CMAKE_CONFIGURATION_TYPES) message(STATUS "Setting build type to '${_default_build_type}' as none was specified.") set(CMAKE_BUILD_TYPE "${_default_build_type}" CACHE STRING "Choose the type of build." FORCE) set_property(CACHE CMAKE_BUILD_TYPE @@ -193,6 +202,8 @@ else() set(QT_INTERNAL_CONFIGURE_FROM_IDE FALSE CACHE INTERNAL "Configuring Qt Project from IDE") endif() +set(_qt_sync_headers_at_configure_time_default ${QT_INTERNAL_CONFIGURE_FROM_IDE}) + if(FEATURE_developer_build) if(DEFINED QT_CMAKE_EXPORT_COMPILE_COMMANDS) set(CMAKE_EXPORT_COMPILE_COMMANDS ${QT_CMAKE_EXPORT_COMPILE_COMMANDS}) @@ -213,11 +224,26 @@ if(FEATURE_developer_build) if (CMAKE_BUILD_TYPE AND CMAKE_BUILD_TYPE STREQUAL Debug) set(__build_benchmarks OFF) endif() + + # Sync headers during the initial configuration of a -developer-build to facilitate code + # navigation for code editors that use an LSP-based code model. + set(_qt_sync_headers_at_configure_time_default TRUE) else() set(_qt_build_tests_default OFF) set(__build_benchmarks OFF) endif() +# Sync Qt header files at configure time +option(QT_SYNC_HEADERS_AT_CONFIGURE_TIME "Run syncqt at configure time already" + ${_qt_sync_headers_at_configure_time_default}) +unset(_qt_sync_headers_at_configure_time_default) + +# In static Ninja Multi-Config builds the sync_headers dependencies(and other autogen dependencies +# are not added to '_autogen/timestamp' targets. See QTBUG-113974. +if(CMAKE_GENERATOR STREQUAL "Ninja Multi-Config" AND NOT QT_BUILD_SHARED_LIBS) + set(QT_SYNC_HEADERS_AT_CONFIGURE_TIME TRUE CACHE BOOL "" FORCE) +endif() + # Build Benchmarks option(QT_BUILD_BENCHMARKS "Build Qt Benchmarks" ${__build_benchmarks}) if(QT_BUILD_BENCHMARKS) @@ -255,10 +281,10 @@ endif() option(QT_BUILD_TESTS_BATCHED "Link all tests into a single binary." ${_qt_batch_tests}) -if(QT_BUILD_TESTS AND QT_BUILD_TESTS_BATCHED AND CMAKE_VERSION VERSION_LESS "3.18") +if(QT_BUILD_TESTS AND QT_BUILD_TESTS_BATCHED AND CMAKE_VERSION VERSION_LESS "3.19") message(FATAL_ERROR - "Test batching requires at least CMake 3.18, due to requiring per-source " - "TARGET_DIRECTORY assignments.") + "Test batching requires at least CMake 3.19, due to requiring per-source " + "TARGET_DIRECTORY assignments and DEFER calls.") endif() # QT_BUILD_TOOLS_WHEN_CROSSCOMPILING -> QT_FORCE_BUILD_TOOLS @@ -285,6 +311,9 @@ enable_testing() option(QT_BUILD_EXAMPLES "Build Qt examples" OFF) option(QT_BUILD_EXAMPLES_BY_DEFAULT "Should examples be built as part of the default 'all' target." ON) +option(QT_INSTALL_EXAMPLES_SOURCES "Install example sources" OFF) +option(QT_INSTALL_EXAMPLES_SOURCES_BY_DEFAULT + "Install example sources as part of the default 'install' target" ON) # FIXME: Support prefix builds as well QTBUG-96232 if(QT_WILL_INSTALL) @@ -354,31 +383,11 @@ endif() option(QT_ALLOW_SYMLINK_IN_PATHS "Allows symlinks in paths." OFF) -# We need to clean up QT_FEATURE_*, but only once per configuration cycle -get_property(qt_feature_clean GLOBAL PROPERTY _qt_feature_clean) -if(NOT qt_feature_clean) - message(STATUS "Check for feature set changes") - set_property(GLOBAL PROPERTY _qt_feature_clean TRUE) - foreach(feature ${QT_KNOWN_FEATURES}) - if(DEFINED "FEATURE_${feature}" AND - NOT "${QT_FEATURE_${feature}}" STREQUAL "${FEATURE_${feature}}") - message(" '${feature}' is changed from ${QT_FEATURE_${feature}} \ -to ${FEATURE_${feature}}") - set(dirty_build TRUE) - endif() - unset("QT_FEATURE_${feature}" CACHE) - endforeach() - - set(QT_KNOWN_FEATURES "" CACHE INTERNAL "" FORCE) - - if(dirty_build) - set_property(GLOBAL PROPERTY _qt_dirty_build TRUE) - message(WARNING "Re-configuring in existing build folder. \ -Some features will be re-evaluated automatically.") - endif() -endif() +qt_internal_detect_dirty_features() if(NOT QT_BUILD_EXAMPLES) # Disable deployment setup to avoid warnings about missing patchelf with CMake < 3.21. set(QT_SKIP_SETUP_DEPLOYMENT ON) endif() + +option(QT_ALLOW_DOWNLOAD "Allows files to be downloaded when building Qt." OFF) diff --git a/cmake/QtSyncQtHelpers.cmake b/cmake/QtSyncQtHelpers.cmake index f120a968..4dba8fcc 100644 --- a/cmake/QtSyncQtHelpers.cmake +++ b/cmake/QtSyncQtHelpers.cmake @@ -79,6 +79,7 @@ function(qt_internal_target_sync_headers target module_headers module_headers_ge endif() get_target_property(qpa_filter_regex ${target} _qt_module_qpa_headers_filter_regex) + get_target_property(rhi_filter_regex ${target} _qt_module_rhi_headers_filter_regex) get_target_property(private_filter_regex ${target} _qt_module_private_headers_filter_regex) # We need to use the real paths since otherwise it may lead to the invalid work of the @@ -96,6 +97,12 @@ function(qt_internal_target_sync_headers target module_headers module_headers_ge ) endif() + if(rhi_filter_regex) + set(rhi_filter_argument + -rhiHeadersFilter "${rhi_filter_regex}" + ) + endif() + set(common_syncqt_arguments -module "${module}" -sourceDir "${source_dir_real}" @@ -104,8 +111,10 @@ function(qt_internal_target_sync_headers target module_headers module_headers_ge -includeDir "${module_build_interface_include_dir}" -privateIncludeDir "${module_build_interface_private_include_dir}" -qpaIncludeDir "${module_build_interface_qpa_include_dir}" + -rhiIncludeDir "${module_build_interface_rhi_include_dir}" -generatedHeaders ${module_headers_generated} ${qpa_filter_argument} + ${rhi_filter_argument} ${public_namespaces_filter} ${non_qt_module_argument} ${internal_module_argument} @@ -178,6 +187,7 @@ function(qt_internal_target_sync_headers target module_headers module_headers_ge ${syncqt_args_rsp} ${module_headers} ${QT_CMAKE_EXPORT_NAMESPACE}::syncqt + "$>" COMMENT "Running syncqt.cpp for module: ${module}" VERBATIM @@ -194,6 +204,8 @@ function(qt_internal_target_sync_headers target module_headers module_headers_ge ${syncqt_outputs} ) add_dependencies(sync_headers ${target}_sync_headers) + set_target_properties(${target} + PROPERTIES _qt_internal_sync_headers_target ${target}_sync_headers) if(is_3rd_party_library) add_dependencies(thirdparty_sync_headers ${target}_sync_headers) @@ -224,7 +236,7 @@ function(qt_internal_target_sync_headers target module_headers module_headers_ge endif() add_dependencies(sync_all_public_headers ${target}_sync_all_public_headers) - if(NOT is_3rd_party_library AND NOT is_framework) + if(NOT is_3rd_party_library AND NOT is_framework AND module_headers) # Install all the CaMeL style aliases of header files from the staging directory in one rule qt_install(DIRECTORY "${syncqt_staging_dir}/" DESTINATION "${module_install_interface_include_dir}" @@ -246,7 +258,7 @@ function(qt_internal_target_sync_headers target module_headers module_headers_ge # Run sync Qt first time at configure step to make all header files available for the code model # of IDEs. get_property(synced_modules GLOBAL PROPERTY _qt_synced_modules) - if(NOT "${module}" IN_LIST synced_modules) + if(NOT "${module}" IN_LIST synced_modules AND QT_SYNC_HEADERS_AT_CONFIGURE_TIME) message(STATUS "Running syncqt.cpp for module: ${module}") get_target_property(syncqt_location ${QT_CMAKE_EXPORT_NAMESPACE}::syncqt LOCATION) execute_process( diff --git a/cmake/QtTargetHelpers.cmake b/cmake/QtTargetHelpers.cmake index 6e7ff453..180ec33b 100644 --- a/cmake/QtTargetHelpers.cmake +++ b/cmake/QtTargetHelpers.cmake @@ -20,10 +20,14 @@ # Skip the specified source files by PRECOMPILE_HEADERS feature. function(qt_internal_extend_target target) if(NOT TARGET "${target}") - qt_internal_is_in_test_batch(in_batch ${target}) - if(NOT in_batch) - message(FATAL_ERROR "Trying to extend a non-existing target \"${target}\".") - endif() + message(FATAL_ERROR "${target} is not a target.") + endif() + qt_internal_is_skipped_test(skipped ${target}) + if(skipped) + return() + endif() + qt_internal_is_in_test_batch(in_batch ${target}) + if(in_batch) _qt_internal_test_batch_target_name(target) endif() @@ -109,6 +113,19 @@ function(qt_internal_extend_target target) if(NOT base_lib STREQUAL lib) qt_create_nolink_target("${base_lib}" ${target}) endif() + + # Collect _sync_headers targets from libraries that the target depends on. This is + # heuristic way of building the dependency tree between the _sync_headers targets of + # different Qt modules. + if(TARGET "${lib}") + get_target_property(is_private ${lib} _qt_is_private_module) + if(is_private) + get_target_property(lib ${lib} _qt_public_module_target_name) + endif() + set(out_genex "$") + set_property(TARGET ${target} + APPEND PROPERTY _qt_internal_sync_headers_deps "${out_genex}") + endif() endforeach() # Set-up the target @@ -1018,6 +1035,24 @@ function(qt_internal_mark_as_internal_target target) set_target_properties(${target} PROPERTIES _qt_is_internal_target TRUE) endfunction() +# Marks a target with a property to skip it adding it as a dependency when building examples as +# ExternalProjects. +# Needed to create a ${repo}_src global target that examples can depend on in multi-config builds +# due to a bug in AUTOUIC. +# +# See QTBUG-110369. +function(qt_internal_skip_dependency_for_examples target) + set_target_properties(${target} PROPERTIES _qt_skip_dependency_for_examples TRUE) +endfunction() + +function(qt_internal_is_target_skipped_for_examples target out_var) + get_property(is_skipped TARGET ${target} PROPERTY _qt_skip_dependency_for_examples) + if(NOT is_skipped) + set(is_skipped FALSE) + endif() + set(${out_var} "${is_skipped}" PARENT_SCOPE) +endfunction() + function(qt_internal_link_internal_platform_for_object_library target) # We need to apply iOS bitcode flags to object libraries that are associated with internal # modules or plugins (e.g. object libraries added by qt_internal_add_resource, @@ -1080,11 +1115,15 @@ endfunction() # The function disables one or multiple internal global definitions that are defined by the # qt_internal_add_global_definition function for a specific 'target'. function(qt_internal_undefine_global_definition target) - if(NOT TARGET ${target}) - qt_internal_is_in_test_batch(in_batch ${target}) - if(NOT ${in_batch}) - message(FATAL_ERROR "${target} is not a target.") - endif() + if(NOT TARGET "${target}") + message(FATAL_ERROR "${target} is not a target.") + endif() + qt_internal_is_skipped_test(skipped ${target}) + if(skipped) + return() + endif() + qt_internal_is_in_test_batch(in_batch ${target}) + if(in_batch) _qt_internal_test_batch_target_name(target) endif() @@ -1135,3 +1174,157 @@ function(qt_internal_get_target_sources_property out_var) endif() set(${out_var} "${${out_var}}" PARENT_SCOPE) endfunction() + +# This function collects target properties that contain generator expressions and needs to be +# exported. This function is needed since the CMake EXPORT_PROPERTIES property doesn't support +# properties that contain generator expressions. +# Usage: qt_internal_add_genex_properties_export(target properties...) +function(qt_internal_add_genex_properties_export target) + get_cmake_property(is_multi_config GENERATOR_IS_MULTI_CONFIG) + + set(config_check_begin "") + set(config_check_end "") + if(is_multi_config) + list(GET CMAKE_CONFIGURATION_TYPES 0 first_config_type) + + # The genex snippet is evaluated to '$>>' in the generated cmake file. + # The check is only applicable to the 'main' configuration. If user project doesn't use + # multi-config generator, then the check supposed to return true and the value from the + # 'main' configuration supposed to be used. + string(JOIN "" check_if_config_empty + "$<1:$>" + "$" + "$" + ) + + # The genex snippet is evaluated to '$' in the generated cmake + # file and checks if the config that user uses matches the generated cmake file config. + string(JOIN "" check_user_config + "$<1:$>$" + ) + + # The genex snippet is evaluated to '$<$>:'Property content'> + # for non-main Qt configs and to + # $<$,$>>>:'Property content'> for the + # main Qt config. This guard is required to choose the correct value of the property for the + # user project according to the user config type. + # All genexes need to be escaped properly to protect them from evaluation by the + # file(GENERATE call in the qt_internal_export_genex_properties function. + string(JOIN "" config_check_begin + "$<1:$><" + "$<1:$>:$${check_if_config_empty}>" + "$:" + ) + set(config_check_end "$") + endif() + set(target_name "${QT_CMAKE_EXPORT_NAMESPACE}::${target}") + foreach(property IN LISTS ARGN) + set(target_property_genex "$") + # All properties that contain lists need to be protected of processing by JOIN genex calls. + # So this escapes the semicolons for these list. + set(target_property_list_escape + "$,\;>") + set(property_value + "\"${config_check_begin}${target_property_list_escape}${config_check_end}\"") + set_property(TARGET ${target} APPEND PROPERTY _qt_export_genex_properties_content + "${property} ${property_value}") + endforeach() +endfunction() + +# This function executes generator expressions for the properties that are added by the +# qt_internal_add_genex_properties_export function and sets the calculated values to the +# corresponding properties in the generated ExtraProperties.cmake file. The file then needs to be +# included after the target creation routines in Config.cmake files. It also supports Multi-Config +# builds. +# Arguments: +# EXPORT_NAME_PREFIX: +# The portion of the file name before ExtraProperties.cmake +# CONFIG_INSTALL_DIR: +# Installation location for the file. +# TARGETS: +# The internal target names. +function(qt_internal_export_genex_properties) + set(option_args "") + set(single_args + EXPORT_NAME_PREFIX + CONFIG_INSTALL_DIR + ) + set(multi_args TARGETS) + cmake_parse_arguments(arg "${option_args}" "${single_args}" "${multi_args}" ${ARGN}) + + if(NOT arg_EXPORT_NAME_PREFIX) + message(FATAL_ERROR "qt_internal_export_genex_properties: " + "Missing EXPORT_NAME_PREFIX argument.") + endif() + + if(NOT arg_TARGETS) + message(FATAL_ERROR "qt_internal_export_genex_properties: " + "TARGETS argument must contain at least one target") + endif() + + foreach(target IN LISTS arg_TARGETS) + get_cmake_property(is_multi_config GENERATOR_IS_MULTI_CONFIG) + + set(output_file_base_name "${arg_EXPORT_NAME_PREFIX}ExtraProperties") + set(should_append "") + set(config_suffix "") + if(is_multi_config) + list(GET CMAKE_CONFIGURATION_TYPES 0 first_config_type) + set(config_suffix "$<$>:-$>") + # If the generated file belongs to the 'main' config type, we should set property + # but not append it. + string(JOIN "" should_append + "$<$>: APPEND>") + endif() + set(file_name "${output_file_base_name}${config_suffix}.cmake") + + qt_path_join(output_file "${arg_CONFIG_INSTALL_DIR}" + "${file_name}") + + if(NOT IS_ABSOLUTE "${output_file}") + qt_path_join(output_file "${QT_BUILD_DIR}" "${output_file}") + endif() + + set(target_name "${QT_CMAKE_EXPORT_NAMESPACE}::${target}") + + string(JOIN "" set_property_begin "set_property(TARGET " + "${target_name}${should_append} PROPERTY " + ) + set(set_property_end ")") + set(set_property_glue "${set_property_end}\n${set_property_begin}") + set(property_list + "$>") + string(JOIN "" set_property_content "${set_property_begin}" + "$" + "${set_property_end}") + + if(is_multi_config) + set(config_includes "") + foreach(config IN LISTS CMAKE_CONFIGURATION_TYPES) + if(NOT first_config_type STREQUAL config) + set(include_file_name + "${output_file_base_name}-${config}.cmake") + list(APPEND config_includes + "include(\"\${CMAKE_CURRENT_LIST_DIR}/${include_file_name}\")") + endif() + endforeach() + list(JOIN config_includes "\n" config_includes_string) + set(config_includes_string + "\n$<$:${config_includes_string}>") + endif() + + file(GENERATE OUTPUT "${output_file}" + CONTENT "$<$:${set_property_content}${config_includes_string}>" + CONDITION "$" + ) + endforeach() + + qt_install(FILES "$<$:${output_file}>" + DESTINATION "${arg_CONFIG_INSTALL_DIR}" + COMPONENT Devel + ) +endfunction() diff --git a/cmake/QtTestHelpers.cmake b/cmake/QtTestHelpers.cmake index 4c6b7d56..4978f955 100644 --- a/cmake/QtTestHelpers.cmake +++ b/cmake/QtTestHelpers.cmake @@ -247,6 +247,7 @@ function(qt_internal_add_test_to_batch batch_name name) # Lazy-init the test batch if(NOT TARGET ${target}) + qt_internal_library_deprecation_level(deprecation_define) qt_internal_add_executable(${target} ${exceptions_text} ${gui_text} @@ -254,11 +255,16 @@ function(qt_internal_add_test_to_batch batch_name name) NO_INSTALL OUTPUT_DIRECTORY "${CMAKE_BINARY_DIR}/build_dir" SOURCES "${QT_CMAKE_DIR}/qbatchedtestrunner.in.cpp" - DEFINES QTEST_BATCH_TESTS + DEFINES QTEST_BATCH_TESTS ${deprecation_define} INCLUDE_DIRECTORIES ${private_includes} LIBRARIES ${QT_CMAKE_EXPORT_NAMESPACE}::Core ${QT_CMAKE_EXPORT_NAMESPACE}::Test ${QT_CMAKE_EXPORT_NAMESPACE}::TestPrivate + # Add GUI by default so that the plugins link properly with non-standalone + # build of tests. Plugin handling is currently only done in + # qt_internal_add_executable if Gui is present. This should be reevaluated with + # multiple batches. + ${QT_CMAKE_EXPORT_NAMESPACE}::Gui ) set_property(TARGET ${target} PROPERTY _qt_has_exceptions ${arg_EXCEPTIONS}) @@ -309,17 +315,12 @@ function(qt_internal_add_test_to_batch batch_name name) list(PREPEND batched_test_list ${name}) set_property(GLOBAL PROPERTY _qt_batched_test_list_property ${batched_test_list}) - qt_internal_library_deprecation_level(deprecation_define) - # Merge the current test with the rest of the batch qt_internal_extend_target(${target} INCLUDE_DIRECTORIES ${arg_INCLUDE_DIRECTORIES} PUBLIC_LIBRARIES ${arg_PUBLIC_LIBRARIES} LIBRARIES ${arg_LIBRARIES} SOURCES ${arg_SOURCES} - DEFINES - ${arg_DEFINES} - ${deprecation_define} COMPILE_OPTIONS ${arg_COMPILE_OPTIONS} COMPILE_FLAGS ${arg_COMPILE_FLAGS} LINK_OPTIONS ${arg_LINK_OPTIONS} @@ -336,7 +337,7 @@ function(qt_internal_add_test_to_batch batch_name name) set_source_files_properties(${source} TARGET_DIRECTORY ${target} PROPERTIES COMPILE_DEFINITIONS - "BATCHED_TEST_NAME=\"${name}\"") + "BATCHED_TEST_NAME=\"${name}\";${arg_DEFINES}" ) endforeach() set(${batch_name} ${target} PARENT_SCOPE) @@ -363,6 +364,34 @@ function(qt_internal_is_in_test_batch out name) endif() endfunction() +function(qt_internal_is_skipped_test out name) + get_target_property(is_skipped_test ${name} _qt_is_skipped_test) + set(${out} ${is_skipped_test} PARENT_SCOPE) +endfunction() + +function(qt_internal_set_skipped_test name) + set_target_properties(${name} PROPERTIES _qt_is_skipped_test TRUE) +endfunction() + +function(qt_internal_is_qtbase_test out) + get_filename_component(dir "${CMAKE_CURRENT_BINARY_DIR}" ABSOLUTE) + set(${out} FALSE PARENT_SCOPE) + + while(TRUE) + get_filename_component(filename "${dir}" NAME) + if("${filename}" STREQUAL "qtbase") + set(${out} TRUE PARENT_SCOPE) + break() + endif() + + set(prev_dir "${dir}") + get_filename_component(dir "${dir}" DIRECTORY) + if("${dir}" STREQUAL "${prev_dir}") + break() + endif() + endwhile() +endfunction() + function(qt_internal_get_batched_test_arguments out testname) if(WASM) # Add a query string to the runner document, so that the script therein @@ -403,6 +432,32 @@ function(qt_internal_add_test name) _qt_internal_validate_all_args_are_parsed(arg) _qt_internal_validate_no_unity_build(arg) + set(batch_current_test FALSE) + if(QT_BUILD_TESTS_BATCHED AND NOT arg_NO_BATCH AND NOT arg_QMLTEST AND NOT arg_MANUAL + AND ("${QT_STANDALONE_TEST_PATH}" STREQUAL "" + OR DEFINED ENV{QT_BATCH_STANDALONE_TESTS})) + set(batch_current_test TRUE) + endif() + + if(batch_current_test OR (QT_BUILD_TESTS_BATCHED AND arg_QMLTEST)) + if (QT_SUPERBUILD OR DEFINED ENV{TESTED_MODULE_COIN}) + set(is_qtbase_test FALSE) + if(QT_SUPERBUILD) + qt_internal_is_qtbase_test(is_qtbase_test) + elseif($ENV{TESTED_MODULE_COIN} STREQUAL "qtbase") + set(is_qtbase_test TRUE) + endif() + if(NOT is_qtbase_test) + file(GENERATE OUTPUT "dummy${name}.cpp" CONTENT "int main() { return 0; }") + # Add a dummy target to tackle some potential problems + qt_internal_add_executable(${name} SOURCES "dummy${name}.cpp") + # Batched tests outside of qtbase are unsupported and skipped + qt_internal_set_skipped_test(${name}) + return() + endif() + endif() + endif() + if (NOT arg_OUTPUT_DIRECTORY) set(arg_OUTPUT_DIRECTORY "${CMAKE_CURRENT_BINARY_DIR}") endif() @@ -421,9 +476,8 @@ function(qt_internal_add_test name) "removed in a future Qt version. Use the LIBRARIES option instead.") endif() - if(NOT arg_NO_BATCH AND QT_BUILD_TESTS_BATCHED AND NOT arg_QMLTEST AND NOT arg_MANUAL) + if(batch_current_test) qt_internal_add_test_to_batch(name ${name} ${ARGN}) - set(setting_up_batched_test TRUE) elseif(arg_SOURCES) if(QT_BUILD_TESTS_BATCHED AND arg_QMLTEST) message(WARNING "QML tests won't be batched - unsupported (yet)") @@ -499,7 +553,6 @@ function(qt_internal_add_test name) qt_internal_extend_target("${name}" CONDITION ANDROID LIBRARIES ${QT_CMAKE_EXPORT_NAMESPACE}::Gui ) - set(setting_up_batched_test FALSE) set_target_properties(${name} PROPERTIES _qt_is_test_executable TRUE) set_target_properties(${name} PROPERTIES _qt_is_manual_test ${arg_MANUAL}) endif() @@ -550,7 +603,7 @@ function(qt_internal_add_test name) elseif(WASM) # The test script expects an html file. In case of batched tests, the # version specialized for running batches has to be supplied. - if(setting_up_batched_test) + if(batch_current_test) get_target_property(batch_output_dir ${name} RUNTIME_OUTPUT_DIRECTORY) set(test_executable "${batch_output_dir}/${name}.html") else() @@ -562,11 +615,16 @@ function(qt_internal_add_test name) list(APPEND extra_test_args "--silence_timeout=60") # TODO: Add functionality to specify browser list(APPEND extra_test_args "--browser=chrome") + list(APPEND extra_test_args "--browser_args=\"--password-store=basic\"") + list(APPEND extra_test_args "--kill_exit") - # We always want to enable asyncify for tests, as some of them use exec + # Tests may require asyncify if they use exec(). Enable asyncify for + # batched tests since this is the configuration used on the CI system. # Optimize for size (-Os), since asyncify tends to make the resulting # binary very large - target_link_options("${name}" PRIVATE "SHELL:-s ASYNCIFY" "-Os") + if(batch_current_test) + target_link_options("${name}" PRIVATE "SHELL:-s ASYNCIFY" "-Os") + endif() # This tells cmake to run the tests with this script, since wasm files can't be # executed directly @@ -588,9 +646,11 @@ function(qt_internal_add_test name) endif() if(NOT arg_MANUAL) - if(setting_up_batched_test) + if(batch_current_test) qt_internal_get_batched_test_arguments(batched_test_args ${testname}) list(PREPEND extra_test_args ${batched_test_args}) + elseif(WASM AND CMAKE_BUILD_TYPE STREQUAL "Debug") + list(PREPEND extra_test_args "qvisualoutput") endif() qt_internal_collect_command_environment(test_env_path test_env_plugin_path) @@ -661,10 +721,29 @@ function(qt_internal_add_test name) foreach(testdata IN LISTS arg_TESTDATA) list(APPEND builtin_files ${testdata}) endforeach() + foreach(file IN LISTS builtin_files) + set_source_files_properties(${file} + PROPERTIES QT_SKIP_QUICKCOMPILER TRUE + ) + endforeach() - set(blacklist_path "BLACKLIST") - if(EXISTS "${CMAKE_CURRENT_SOURCE_DIR}/${blacklist_path}") - list(APPEND builtin_files ${blacklist_path}) + if(batch_current_test) + set(blacklist_path "BLACKLIST") + if(EXISTS "${CMAKE_CURRENT_SOURCE_DIR}/${blacklist_path}") + get_target_property(blacklist_files ${name} _qt_blacklist_files) + if(NOT blacklist_files) + set_target_properties(${name} PROPERTIES _qt_blacklist_files "") + set(blacklist_files "") + cmake_language(EVAL CODE "cmake_language(DEFER DIRECTORY \"${CMAKE_SOURCE_DIR}\" CALL \"_qt_internal_finalize_batch\" \"${name}\") ") + endif() + list(PREPEND blacklist_files "${CMAKE_CURRENT_SOURCE_DIR}/${blacklist_path}") + set_target_properties(${name} PROPERTIES _qt_blacklist_files "${blacklist_files}") + endif() + else() + set(blacklist_path "BLACKLIST") + if(EXISTS "${CMAKE_CURRENT_SOURCE_DIR}/${blacklist_path}") + list(APPEND builtin_files ${blacklist_path}) + endif() endif() list(REMOVE_DUPLICATES builtin_files) @@ -762,7 +841,8 @@ for this function. Will be ignored") endif() set(executable_name ${arg_NAME}) - if(QT_BUILD_TESTS_BATCHED) + qt_internal_is_in_test_batch(is_in_batch ${executable_name}) + if(is_in_batch) _qt_internal_test_batch_target_name(executable_name) endif() add_test(NAME "${arg_NAME}" COMMAND "${CMAKE_COMMAND}" "-P" "${arg_OUTPUT_FILE}" @@ -828,8 +908,8 @@ function(qt_internal_add_test_helper name) set(extra_args_to_pass) if(NOT arg_OVERRIDE_OUTPUT_DIRECTORY) - if(QT_BUILD_TESTS_BATCHED) - _qt_internal_test_batch_target_name(test_batch_target_name) + _qt_internal_test_batch_target_name(test_batch_target_name) + if(QT_BUILD_TESTS_BATCHED AND TARGET ${test_batch_target_name}) get_target_property( test_batch_output_dir ${test_batch_target_name} RUNTIME_OUTPUT_DIRECTORY) set(extra_args_to_pass OUTPUT_DIRECTORY "${test_batch_output_dir}") diff --git a/cmake/QtWasmHelpers.cmake b/cmake/QtWasmHelpers.cmake index 614a677a..3eb97fa3 100644 --- a/cmake/QtWasmHelpers.cmake +++ b/cmake/QtWasmHelpers.cmake @@ -8,7 +8,8 @@ function (qt_internal_setup_wasm_target_properties wasmTarget) target_link_options("${wasmTarget}" INTERFACE "SHELL:-s MAX_WEBGL_VERSION=2" "SHELL:-s FETCH=1" - "SHELL:-s WASM_BIGINT=1") + "SHELL:-s WASM_BIGINT=1" + "SHELL:-s STACK_SIZE=5MB") target_link_libraries("${wasmTarget}" INTERFACE embind) @@ -93,17 +94,25 @@ function (qt_internal_setup_wasm_target_properties wasmTarget) set_property(GLOBAL PROPERTY TARGET_SUPPORTS_SHARED_LIBS TRUE) - # plugins are SIDE_MODULE - target_compile_options("${wasmTarget}" INTERFACE - "$<$,MODULE_LIBRARY>:" -s SIDE_MODULE=1>) - target_link_options("${wasmTarget}" INTERFACE - "$<$,MODULE_LIBRARY>:" -s SIDE_MODULE=1>) + set(side_modules + MODULE_LIBRARY SHARED_LIBRARY) + set(enable_side_module_if_needed + "$<$,${side_modules}>:SHELL:-s SIDE_MODULE=1>") + set(enable_main_module_if_needed + "$<$,EXECUTABLE>:SHELL:-s MAIN_MODULE=1>") + set(set_shared_module_type_if_needed + "${enable_side_module_if_needed}" + "${enable_main_module_if_needed}" + ) - # shared libs are SIDE_MODULE - target_compile_options("${wasmTarget}" INTERFACE - "$<$,SHARED_LIBRARY>:" -s SIDE_MODULE=1>) + # Add Qt libdir to linker library paths + set(qt_lib_location + "${QT_BUILD_INTERNALS_RELOCATABLE_INSTALL_PREFIX}/${INSTALL_LIBDIR}") target_link_options("${wasmTarget}" INTERFACE - "$<$,SHARED_LIBRARY>:" -s SIDE_MODULE=1>) + "$<$,EXECUTABLE>:SHELL:" -L${qt_lib_location}/>) + + target_compile_options("${wasmTarget}" INTERFACE "${set_shared_module_type_if_needed}") + target_link_options("${wasmTarget}" INTERFACE "${set_shared_module_type_if_needed}") else() target_link_options("${wasmTarget}" INTERFACE "SHELL:-s ERROR_ON_UNDEFINED_SYMBOLS=1") diff --git a/cmake/QtWrapperScriptHelpers.cmake b/cmake/QtWrapperScriptHelpers.cmake index bfb6d0d7..e87d0ad2 100644 --- a/cmake/QtWrapperScriptHelpers.cmake +++ b/cmake/QtWrapperScriptHelpers.cmake @@ -39,6 +39,20 @@ function(qt_internal_create_wrapper_scripts) DESTINATION "${INSTALL_BINDIR}") endif() + if(generate_unix) + configure_file("${CMAKE_CURRENT_SOURCE_DIR}/bin/qt-cmake-create.in" + "${QT_BUILD_DIR}/${INSTALL_BINDIR}/qt-cmake-create" @ONLY + NEWLINE_STYLE LF) + qt_install(PROGRAMS "${QT_BUILD_DIR}/${INSTALL_BINDIR}/qt-cmake-create" + DESTINATION "${INSTALL_BINDIR}") + endif() + if(generate_non_unix) + configure_file("${CMAKE_CURRENT_SOURCE_DIR}/bin/qt-cmake-create.bat.in" + "${QT_BUILD_DIR}/${INSTALL_BINDIR}/qt-cmake-create.bat" @ONLY + NEWLINE_STYLE CRLF) + qt_install(PROGRAMS "${QT_BUILD_DIR}/${INSTALL_BINDIR}/qt-cmake-create.bat" + DESTINATION "${INSTALL_BINDIR}") + endif() # Provide a private convenience wrapper with options which should not be propagated via the # public qt-cmake wrapper e.g. CMAKE_GENERATOR. # These options can not be set in a toolchain file, but only on the command line. diff --git a/cmake/README.md b/cmake/README.md index 9800dae1..ac9d1012 100644 --- a/cmake/README.md +++ b/cmake/README.md @@ -313,3 +313,16 @@ $ cd some/empty/directory $ ~/Qt/6.0.0/bin/qt-cmake-standalone-test ~/source/of/qtbase/test/auto/corelib/io/qprocess $ cmake --build . ``` + +## qt-cmake-create + +Generates a simple CMakeLists.txt based on source files in specified project directory. + +Example: + +``` +$ cd some/source/directory/ +$ qt-cmake-create +$ qt-cmake -S . -B /build/directory +$ cmake --build /build/directory +``` diff --git a/cmake/configure-cmake-mapping.md b/cmake/configure-cmake-mapping.md index 383c64ca..84d6b53b 100644 --- a/cmake/configure-cmake-mapping.md +++ b/cmake/configure-cmake-mapping.md @@ -17,7 +17,6 @@ The following table describes the mapping of configure options to CMake argument | -no-feature-foo | -DFEATURE_foo=OFF | | | -list-features | | At the moment: configure with cmake once, | | | | then use ccmake or cmake-gui to inspect the features. | -| -list-libraries | | | | -opensource | n/a | | | -commercial | n/a | | | -confirm-license | n/a | | @@ -60,6 +59,7 @@ The following table describes the mapping of configure options to CMake argument | -R | -DQT_EXTRA_RPATHS=path1;path2 | | | -rpath | negative CMAKE_SKIP_BUILD_RPATH | | | | negative CMAKE_SKIP_INSTALL_RPATH | | +| | negative CMAKE_MACOSX_RPATH | | | -reduce-exports | -DFEATURE_reduce_exports=ON | | | -reduce-relocations | -DFEATURE_reduce_relocations=ON | | | -plugin-manifests | | | @@ -75,16 +75,9 @@ The following table describes the mapping of configure options to CMake argument | -ccache | -DQT_USE_CCACHE=ON | | | -unity-build | -DQT_UNITY_BUILD=ON | | | -unity-build-batch-size | -DQT_UNITY_BUILD_BATCH_SIZE= | | -| -make-tool | n/a | | -| -mp | n/a | | | -warnings-are-errors | -DWARNINGS_ARE_ERRORS=ON | | -| -silent | n/a | | -| -sysroot | -DCMAKE_SYSROOT= | Should be provided by a toolchain file that's | -| | | passed via -DCMAKE_TOOLCHAIN_FILE= | -| -no-gcc-sysroot | n/a | The corresponding CMake variables are CMAKE_SYSROOT_LINK | -| | | and CMAKE_SYSROOT_COMPILE. | -| | | They are usually set in a toolchain file. | | -no-pkg-config | -DFEATURE_pkg_config=OFF | | +| -no-vcpkg | -DQT_USE_VCPKG=OFF | | | -D | -DQT_EXTRA_DEFINES=; | | | -I | -DQT_EXTRA_INCLUDEPATHS=; | | | -L | -DQT_EXTRA_LIBDIRS=; | | @@ -110,6 +103,7 @@ The following table describes the mapping of configure options to CMake argument | | | build them separately, after configuration. | | -nomake | -DQT_BUILD_TESTS=OFF | A way to turn off tools explicitly is missing. | | | -DQT_BUILD_EXAMPLES=OFF | | +| -install-examples-sources | -DQT_INSTALL_EXAMPLES_SOURCES=ON | | | -no-gui | -DFEATURE_gui=OFF | | | -no-widgets | -DFEATURE_widgets=OFF | | | -no-dbus | -DFEATURE_dbus=OFF | | diff --git a/cmake/macos/MacOSXBundleInfo.plist.in b/cmake/macos/Info.plist.app.in similarity index 100% rename from cmake/macos/MacOSXBundleInfo.plist.in rename to cmake/macos/Info.plist.app.in diff --git a/cmake/modulecppexports.h.in b/cmake/modulecppexports.h.in index 538c40e5..4d41a3a2 100644 --- a/cmake/modulecppexports.h.in +++ b/cmake/modulecppexports.h.in @@ -1,11 +1,13 @@ // Copyright (C) 2022 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 - #ifndef @header_base_name_upper@_H #define @header_base_name_upper@_H +#include +#include // Q_@module_define_infix@_EXPORT +#include // QT_IF_DEPRECATED_SINCE + #if defined(QT_SHARED) || !defined(QT_STATIC) # if defined(QT_BUILD_@module_define_infix@_LIB) # define Q_@module_define_infix@_EXPORT Q_DECL_EXPORT diff --git a/cmake/modulecppexports_p.h.in b/cmake/modulecppexports_p.h.in index 5f873e3c..c4e029d5 100644 --- a/cmake/modulecppexports_p.h.in +++ b/cmake/modulecppexports_p.h.in @@ -4,6 +4,8 @@ #ifndef @header_base_name_upper@_P_H #define @header_base_name_upper@_P_H +// This file is autogenerated. Changes will be overwritten. + // // W A R N I N G // ------------- diff --git a/coin/axivion/ci_config_linux.json b/coin/axivion/ci_config_linux.json new file mode 100644 index 00000000..23547815 --- /dev/null +++ b/coin/axivion/ci_config_linux.json @@ -0,0 +1,65 @@ +{ + "Project": { + "Git": { + "_active": true, + "sourceserver_gitdir": "/data/axivion/databases/$(env:TESTED_MODULE_COIN).git" + }, + "BuildSystemIntegration": { + "child_order": [ + "GCCSetup", + "CMake", + "LinkLibraries" + ] + }, + "CMake": { + "_active": true, + "_copy_from": "CMakeIntegration", + "build_environment": {}, + "build_options": "-j4", + "generate_options": "--fresh", + "generator": "Ninja" + }, + "GCCSetup": { + "_active": true, + "_copy_from": "Command", + "build_command": "gccsetup --cc gcc --cxx g++ --config ../../../axivion/" + }, + "LinkLibraries": { + "_active": true, + "_copy_from": "AxivionLinker", + "input_files": [ + "build/lib/lib*.so*.ir" + ], + "ir": "build/$(env:TESTED_MODULE_COIN).ir", + "plugin_files": [ + "build/plugins/*/lib*.so*.ir" + ] + }, + "Project-GlobalOptions": { + "ci_mode": { + "clean_before": false + }, + "directory": "../work/qt/$(env:TESTED_MODULE_COIN)", + "ir": "build/$(env:TESTED_MODULE_COIN).ir", + "name": "qt_$(env:TESTED_MODULE_COIN)_dev_$(env:TARGET_OS_COIN)" + } + }, + "Results": { + "Dashboard": { + "dashboard_url": "https://axivion-srv.ci.qt.io/axivion/" + }, + "Database": { + "ci_mode": { + "directory": "/data/axivion/databases" + } + } + }, + "_Format": "1.0", + "_Version": "trunk-9e0ef9c5818", + "_VersionNum": [ + 7, + 6, + 9999, + 11489 + ] +} diff --git a/coin/instructions/cmake_module_build_instructions.yaml b/coin/instructions/cmake_module_build_instructions.yaml index f83d8711..88717ba2 100644 --- a/coin/instructions/cmake_module_build_instructions.yaml +++ b/coin/instructions/cmake_module_build_instructions.yaml @@ -76,9 +76,14 @@ instructions: - !include "{{qt/qtbase}}/call_host_install.yaml" - type: SignPackage enable_if: - condition: property - property: host.os - equals_value: Windows + condition: and + conditions: + - condition: property + property: host.os + equals_value: Windows + - condition: property + property: features + contains_value: Packaging directory: "{{.InstallRoot}}/{{.AgentWorkingDir}}" maxTimeInSeconds: 1200 maxTimeBetweenOutput: 1200 diff --git a/coin/instructions/cmake_qtbase_build_instructions.yaml b/coin/instructions/cmake_qtbase_build_instructions.yaml index 399ae731..47c3fabc 100644 --- a/coin/instructions/cmake_qtbase_build_instructions.yaml +++ b/coin/instructions/cmake_qtbase_build_instructions.yaml @@ -73,9 +73,14 @@ instructions: - !include "{{qt/qtbase}}/call_host_install.yaml" - type: SignPackage enable_if: - condition: property - property: host.os - equals_value: Windows + condition: and + conditions: + - condition: property + property: host.os + equals_value: Windows + - condition: property + property: features + contains_value: Packaging directory: "{{.InstallRoot}}/{{.AgentWorkingDir}}" maxTimeInSeconds: 1200 maxTimeBetweenOutput: 1200 diff --git a/coin/instructions/coin_module_axivion_template_v2.yaml b/coin/instructions/coin_module_axivion_template_v2.yaml new file mode 100644 index 00000000..0eebe34e --- /dev/null +++ b/coin/instructions/coin_module_axivion_template_v2.yaml @@ -0,0 +1,98 @@ +analysis_instructions_axivion: &analysis_instructions_axivion + type: Group + instructions: + - type: Group + instructions: + - type: EnvironmentVariable + variableName: AXIVION_CHAINLOAD_TOOLCHAIN_FILE + variableValue: "{{.AgentWorkingDir}}/install/lib/cmake/Qt6/qt.toolchain.cmake" + - type: EnvironmentVariable + variableName: CMAKE_PREFIX_PATH + variableValue: "{{.AgentWorkingDir}}/install/lib/cmake" + enable_if: + condition: runtime + env_var: TESTED_MODULE_COIN + not_equals_value: "qtbase" + - type: Group + instructions: + - type: Rename + sourcePath: "{{.SourceDir}}/coin/axivion/ci_config_{{.Env.TARGET_OS_COIN}}.json" + targetPath: "{{.Env.HOME}}/axivion/ci_config.json" + userMessageOnFailure: "Moving ci_config.json failed. Make sure you have included the file in coin/axivion/ -folder" + - type: SetBuildDirectory + directory: "{{.SourceDir}}" + - type: ChangeDirectory + directory: "{{.BuildDir}}" + - type: ExecuteCommand + command: ["../../../axivion/start_analysis.sh"] + maxTimeInSeconds: 28800 + maxTimeBetweenOutput: 28800 + userMessageOnFailure: "Failed to run analysis" + +build_environment_axivion: &build_environment_axivion + type: Group + instructions: + - type: ExecuteCommand + command: ["sudo", "mkdir", "-p","/data/axivion"] + maxTimeInSeconds: 100 + maxTimeBetweenOutput: 100 + userMessageOnFailure: "Create mount point for results failed" + - type: ExecuteCommand + command: ["sudo", "mount", "-o", "rw", "10.212.0.93:/data/axivion", "/data/axivion"] + maxTimeInSeconds: 100 + maxTimeBetweenOutput: 100 + userMessageOnFailure: "Mount failed" + - type: ExecuteCommand + command: ["rm","-rf","{{.SourceDir}}"] + maxTimeInSeconds: 100 + maxTimeBetweenOutput: 100 + userMessageOnFailure: "Failed to remove source directory" + - type: MakeDirectory + directory: "{{.SourceDir}}" + - type: ChangeDirectory + directory: "{{.SourceDir}}" + - type: ExecuteCommand + command: ["git", "clone", "--jobs={{.NumCPU}}", "-n","--depth=50", "git://{{.Env.QT_COIN_GIT_DAEMON}}/qt-project/qt/{{.Env.TESTED_MODULE_COIN}}","."] + maxTimeInSeconds: 900 + maxTimeBetweenOutput: 900 + userMessageOnFailure: "Failed to clone repository" + - type: ExecuteCommand + command: ["git", "fetch", "--recurse-submodules", "origin", "{{.Env.TESTED_MODULE_REVISION_COIN}}"] + maxTimeInSeconds: 900 + maxTimeBetweenOutput: 900 + userMessageOnFailure: "Failed to fetch sources" + - type: ExecuteCommand + command: ["git", "checkout", "--force", "{{.Env.TESTED_MODULE_REVISION_COIN}}"] + maxTimeInSeconds: 900 + maxTimeBetweenOutput: 900 + userMessageOnFailure: "Failed to checkout sources" + - type: ExecuteCommand + command: ["git", "submodule", "update", "--init", "--recursive"] + maxTimeInSeconds: 1800 + maxTimeBetweenOutput: 900 + userMessageOnFailure: "Failed to initialize git submodules" + - type: Group + instructions: + - !include "{{qt/qtbase}}/cmake_module_build_instructions.yaml" + enable_if: + condition: runtime + env_var: TESTED_MODULE_COIN + not_equals_value: "qtbase" + - type: Group + instructions: + - !include "{{qt/qtbase}}/cmake_qtbase_build_instructions.yaml" + enable_if: + condition: runtime + env_var: TESTED_MODULE_COIN + equals_value: "qtbase" + +type: Group +instructions: + - !include "{{qt/qtbase}}/prepare_building_env.yaml" + - *build_environment_axivion + - *analysis_instructions_axivion +enable_if: + condition: property + property: features + contains_value: Axivion + diff --git a/coin/instructions/coin_module_build_template_v2.yaml b/coin/instructions/coin_module_build_template_v2.yaml index 14c85558..2b36fb1c 100644 --- a/coin/instructions/coin_module_build_template_v2.yaml +++ b/coin/instructions/coin_module_build_template_v2.yaml @@ -24,6 +24,33 @@ instructions: - condition: property property: features not_contains_value: "TargetBuildOnly" + - condition: property + property: features + not_contains_value: "DebianPackaging" + - condition: property + property: features + not_contains_value: Axivion + - type: Group + instructions: + - !include "{{qt/qtbase}}/coin_module_axivion_template_v2.yaml" + enable_if: + condition: and + conditions: + - condition: property + property: features + contains_value: Axivion + - condition: runtime + env_var: TESTED_MODULE_COIN + not_equals_value: "qtdoc" + - condition: runtime + env_var: TESTED_MODULE_COIN + not_equals_value: "qtquickeffectmaker" + - condition: runtime + env_var: TESTED_MODULE_COIN + not_equals_value: "qttranslations" + - condition: runtime + env_var: TESTED_MODULE_COIN + not_equals_value: "qtwebengine" - type: Group instructions: - !include "{{qt/qtbase}}/cmake_cross_compilation_module_build_instructions.yaml" @@ -48,3 +75,15 @@ instructions: - condition: property property: target.arch equals_value: ARM64 + - condition: property + property: features + not_contains_value: "DebianPackaging" + - type: Group + instructions: + - type: Group + instructions: + - !include "{{qt/qtbase}}/debian/debian_build_module.yaml" + enable_if: + condition: property + property: features + contains_value: "DebianPackaging" diff --git a/coin/instructions/coin_qtbase_build_template_v2.yaml b/coin/instructions/coin_qtbase_build_template_v2.yaml index a0edaa8e..e5ae8068 100644 --- a/coin/instructions/coin_qtbase_build_template_v2.yaml +++ b/coin/instructions/coin_qtbase_build_template_v2.yaml @@ -17,6 +17,19 @@ instructions: - condition: property property: features not_contains_value: "TargetBuildOnly" + - condition: property + property: features + not_contains_value: "DebianPackaging" + - condition: property + property: features + not_contains_value: Axivion + - type: Group + instructions: + - !include "{{qt/qtbase}}/coin_module_axivion_template_v2.yaml" + enable_if: + condition: property + property: features + contains_value: Axivion - type: Group instructions: - type: Group @@ -53,3 +66,15 @@ instructions: - condition: property property: target.arch equals_value: ARM64 + - condition: property + property: features + not_contains_value: "DebianPackaging" + - type: Group + instructions: + - type: Group + instructions: + - !include "{{qt/qtbase}}/debian/debian_build_module.yaml" + enable_if: + condition: property + property: features + contains_value: "DebianPackaging" diff --git a/coin/instructions/debian/debian_build_module.yaml b/coin/instructions/debian/debian_build_module.yaml new file mode 100644 index 00000000..a302c07b --- /dev/null +++ b/coin/instructions/debian/debian_build_module.yaml @@ -0,0 +1,125 @@ +type: Group +enable_if: + condition: property + property: features + contains_value: DebianPackaging +instructions: + - !include "{{qt/qtbase}}/debian/prepare_debian_env.yaml" + - type: EnvironmentVariable + variableName: GIT_SSH_COMMAND + variableValue: "ssh -o StrictHostKeyChecking=no" + - type: ChangeDirectory + directory: "{{.AgentWorkingDir}}" + - type: MakeDirectory + directory: output/debian_packages + - type: MakeDirectory + directory: debian_packages + - type: ExecuteCommand + command: "git clone git@git.qt.io:tqtc-debian/package_generator.git" + maxTimeInSeconds: 900 + maxTimeBetweenOutput: 900 + userMessageOnFailure: "Failed to clone package generator repo" + disable_if: + condition: runtime + env_var: COIN_SKIP_DEBIAN + contains_value: "MISSING_DEBIAN_INST" + - type: ExecuteCommand + command: "git clone -b 6.6 git@git.qt.io:tqtc-debian/qt6-{{.Env.TESTED_MODULE_PLAIN_COIN}}.git" + maxTimeInSeconds: 900 + maxTimeBetweenOutput: 900 + userMessageOnFailure: "Failed to clone debian packaging repo" + disable_if: + condition: runtime + env_var: COIN_SKIP_DEBIAN + contains_value: "MISSING_DEBIAN_INST" + - type: ChangeDirectory + directory: "qt6-{{.Env.TESTED_MODULE_PLAIN_COIN}}" + disable_if: + condition: runtime + env_var: COIN_SKIP_DEBIAN + contains_value: "MISSING_DEBIAN_INST" + - type: ExecuteCommand + command: "git checkout {{.Env.DEBIAN_RULES_REF}}" + maxTimeInSeconds: 900 + maxTimeBetweenOutput: 900 + userMessageOnFailure: "Failed to checkout debian rules branch" + disable_if: + condition: or + conditions: + - condition: runtime + env_var: DEBIAN_RULES_REF + equals_value: null + - condition: runtime + env_var: COIN_SKIP_DEBIAN + contains_value: "MISSING_DEBIAN_INST" + - type: ChangeDirectory + directory: "{{.AgentWorkingDir}}" + - type: ExecuteCommand + command: "wget -q {{.CoinDownloadURL}}/{{.Env.MODULE_SOURCES_RELATIVE_STORAGE_PATH}}" + maxTimeInSeconds: 900 + maxTimeBetweenOutput: 900 + userMessageOnFailure: "Failed get sources" + disable_if: + condition: runtime + env_var: COIN_SKIP_DEBIAN + contains_value: "MISSING_DEBIAN_INST" + - type: ExecuteCommand + command: "mv sources_unix.tar.gz qt-{{.Env.QT_REPO_MODULE_VERSION}}-{{.Env.TESTED_MODULE_PLAIN_COIN}}-src_{{.Env.QT_REPO_MODULE_VERSION}}.orig.tar.gz" + maxTimeInSeconds: 900 + maxTimeBetweenOutput: 900 + userMessageOnFailure: "Failed rename src pkg" + disable_if: + condition: runtime + env_var: COIN_SKIP_DEBIAN + contains_value: "MISSING_DEBIAN_INST" + - type: ChangeDirectory + directory: "{{.AgentWorkingDir}}/qt6-{{.Env.TESTED_MODULE_PLAIN_COIN}}" + disable_if: + condition: runtime + env_var: COIN_SKIP_DEBIAN + contains_value: "MISSING_DEBIAN_INST" + + + # rc is required currently by the script + - type: ExecuteCommand + command: "../package_generator/generate_packaging.sh --qt-version {{.Env.QT_REPO_MODULE_VERSION}} --deb-rev 1 --release tqtc-focal" + maxTimeInSeconds: 900 + maxTimeBetweenOutput: 900 + userMessageOnFailure: "Failed to generate pkg" + disable_if: + condition: runtime + env_var: COIN_SKIP_DEBIAN + contains_value: "MISSING_DEBIAN_INST" + - type: ChangeDirectory + directory: "{{.AgentWorkingDir}}" + - type: ExecuteCommand + command: "dpkg-source -b qt6-{{.Env.TESTED_MODULE_PLAIN_COIN}}" + maxTimeInSeconds: 900 + maxTimeBetweenOutput: 900 + userMessageOnFailure: "Failed dpkg-source" + disable_if: + condition: runtime + env_var: COIN_SKIP_DEBIAN + contains_value: "MISSING_DEBIAN_INST" + - type: ExecuteCommand + command: ["sbuild", + "--build-dep-resolver=apt", + "-sAd", "tqtc-focal", + "-c", "{{.Env.COIN_SBUILD_CHROOT}}", + "--build-dir", "output/debian_packages", + "--extra-repository={{.Env.COIN_EXTRA_DEBIAN_REPO}}", + "--extra-package={{.Env.COIN_EXTRA_DEBIAN_PACKAGES}}", + "--extra-package={{.AgentWorkingDir}}/debian_packages/", + "qt-{{.Env.QT_REPO_MODULE_VERSION}}-{{.Env.TESTED_MODULE_PLAIN_COIN}}-src_{{.Env.QT_REPO_MODULE_VERSION}}-1.dsc"] + maxTimeInSeconds: 18000 + maxTimeBetweenOutput: 18000 + userMessageOnFailure: "Failed build debian packages" + disable_if: + condition: runtime + env_var: COIN_SKIP_DEBIAN + contains_value: "MISSING_DEBIAN_INST" + - type: UploadArtifact + archiveDirectory: "{{.AgentWorkingDir}}/output" + transferType: UploadModuleBuildArtifact + maxTimeInSeconds: 1200 + maxTimeBetweenOutput: 1200 diff --git a/coin/instructions/debian/prepare_debian_env.yaml b/coin/instructions/debian/prepare_debian_env.yaml new file mode 100644 index 00000000..fd130ceb --- /dev/null +++ b/coin/instructions/debian/prepare_debian_env.yaml @@ -0,0 +1,85 @@ +type: Group +enable_if: + condition: property + property: features + contains_value: DebianPackaging +instructions: + - type: EnvironmentVariable + variableName: COIN_SBUILD_CHROOT + variableValue: "stable-arm64-sbuild" + enable_if: + condition: and + conditions: + - condition: runtime + env_var: COIN_SBUILD_CHROOT + equals_value: null + - condition: property + property: target.arch + equals_value: AARCH64 + - type: EnvironmentVariable + variableName: COIN_SBUILD_CHROOT + variableValue: "stable-amd64-sbuild" + enable_if: + condition: and + conditions: + - condition: runtime + env_var: COIN_SBUILD_CHROOT + equals_value: null + - condition: property + property: target.arch + equals_value: X86_64 + - type: EnvironmentVariable + variableName: COIN_SBUILD_DISTRO + variableValue: "arm64-focal" + enable_if: + condition: property + property: target.arch + equals_value: AARCH64 + - type: EnvironmentVariable + variableName: COIN_SBUILD_DISTRO + variableValue: "amd64-focal" + disable_if: + condition: property + property: target.arch + equals_value: AARCH64 + + - type: EnvironmentVariable + variableName: COIN_SKIP_DEBIAN + variableValue: "MISSING_DEBIAN_INST" + enable_if: + condition: or + conditions: + - condition: runtime + env_var: TESTED_MODULE_COIN + equals_value: "qtactiveqt" + - condition: runtime + env_var: TESTED_MODULE_COIN + equals_value: "qtqa" + - condition: runtime + env_var: TESTED_MODULE_COIN + equals_value: "qtdoc" + - condition: runtime + env_var: TESTED_MODULE_COIN + equals_value: "qt5" + # Set version info to environment + - type: ParseEnvironmentVariableFromFile + regex: "QT_REPO_MODULE_VERSION \"(?P.*)\"" + filename: "{{.SourceDir}}/.cmake.conf" + maxTimeInSeconds: 300 + maxTimeBetweenOutput: 300 + userMessageOnFailure: "Failed to parse version information from .cmake.conf" + disable_if: + condition: runtime + env_var: TESTED_MODULE_COIN + equals_value: "qt5" + - type: ParseEnvironmentVariableFromFile + regex: "QT_REPO_MODULE_PRERELEASE_VERSION_SEGMENT \"(?P.*)\"" + filename: "{{.SourceDir}}/.cmake.conf" + maxTimeInSeconds: 300 + maxTimeBetweenOutput: 300 + userMessageOnFailure: "Failed to parse status information from .cmake.conf" + disable_if: + condition: runtime + env_var: TESTED_MODULE_COIN + equals_value: "qt5" + diff --git a/coin/module_config.yaml b/coin/module_config.yaml index 619122a2..4c845fd9 100644 --- a/coin/module_config.yaml +++ b/coin/module_config.yaml @@ -4,9 +4,17 @@ accept_configuration: property: features not_contains_value: Disable +downstream_check: + mode: build + modules: + ../qtdeclarative: + ref: dev + configurations: + - rhel-8.4 + machine_type: Build: - cores: 4 + cores: 8 Test: cores: 4 diff --git a/config.tests/arch/arch.cpp b/config.tests/arch/arch.cpp index dfef8784..ffb6c787 100644 --- a/config.tests/arch/arch.cpp +++ b/config.tests/arch/arch.cpp @@ -141,7 +141,7 @@ const char msg2[] = "==Qt=magic=Qt== Sub-architecture:" // Leading-Zero bit count, Intel Core 4th Generation ("Haswell") " lzcnt" #endif -#ifdef __MMX__ +#if defined(__MMX__) && defined(__i386__) // Multimedia Extensions, Pentium MMX, AMD K6-2 " mmx" #endif @@ -198,11 +198,11 @@ const char msg2[] = "==Qt=magic=Qt== Sub-architecture:" // Shadow stack, Intel processor TBA " shstk" #endif -#if defined(__SSE__) || (defined(_M_IX86_FP) && _M_IX86_FP >= 1) || defined(_M_X64) +#if (defined(__SSE__) && defined(__i386__)) || (defined(_M_IX86_FP) && _M_IX86_FP >= 1 && defined(_M_IX86)) // Streaming SIMD Extensions, Intel Pentium III, AMD Athlon " sse" #endif -#if defined(__SSE2__) || (defined(_M_IX86_FP) && _M_IX86_FP >= 2) || defined(_M_X64) +#if (defined(__SSE2__) && defined(__i386__)) || (defined(_M_IX86_FP) && _M_IX86_FP >= 2 && defined(_M_IX86)) // SSE2, Intel Pentium-M, Intel Pentium 4, AMD Opteron and Athlon 64 " sse2" #endif diff --git a/config.tests/separate_debug_info/CMakeLists.txt b/config.tests/separate_debug_info/CMakeLists.txt index b325bf0c..1b38d23e 100644 --- a/config.tests/separate_debug_info/CMakeLists.txt +++ b/config.tests/separate_debug_info/CMakeLists.txt @@ -1,7 +1,6 @@ # Copyright (C) 2022 The Qt Company Ltd. # SPDX-License-Identifier: BSD-3-Clause -# special case skip regeneration cmake_minimum_required(VERSION 3.16) project(objcopytest LANGUAGES CXX) add_executable(objcopytest main.cpp) diff --git a/config_help.txt b/config_help.txt index 3c78115e..6436f3a5 100644 --- a/config_help.txt +++ b/config_help.txt @@ -64,10 +64,13 @@ Build options: -cmake-file-api ...... Let CMake store build metadata for loading the build into an IDE. [no; yes if -developer-build] -no-guess-compiler ... Do not guess the compiler from the target mkspec. - -release ............. Build Qt with debugging turned off [yes] - -debug ............... Build Qt with debugging turned on [no] - -debug-and-release ... Build two versions of Qt, with and without - debugging turned on [yes] (Apple and Windows only) + -release ............. Build Qt with optimizations and without debug + symbols [yes] + Note that -developer-build implies -debug unless + -release is also explicitly specified + -debug ............... Build Qt without optimizations and with debug symbols + [no] + -debug-and-release ... Build two versions of Qt in one build tree [no] -optimize-debug ...... Enable debug-friendly optimizations in debug builds [auto] (Not supported with MSVC or Clang toolchains) -optimize-size ....... Optimize release builds for size instead of speed [no] @@ -80,7 +83,8 @@ Build options: sections. [auto for static builds, otherwise no] -force-asserts ....... Enable Q_ASSERT even in release builds [no] -developer-build ..... Compile and link Qt for developing Qt itself - (exports for auto-tests, extra checks, etc.) [no] + (exports for auto-tests, extra checks, implies + -no-prefix, etc.) [no] -shared .............. Build shared Qt libraries [yes] (no for UIKit) -static .............. Build static Qt libraries [no] (yes for UIKit) @@ -157,10 +161,10 @@ Build options: Build environment: - -sysroot ....... Set as the target sysroot - -pkg-config .......... Use pkg-config [auto] (Unix only) + -vcpkg ............... Use vcpkg [yes] + -D .......... Pass additional preprocessor define -I .......... Pass additional include path -L .......... Pass additional library path @@ -198,6 +202,9 @@ Component selection: [default: libs and examples, also tools if not cross-building, also tests if -developer-build] -nomake ....... Exclude from the list of parts to be built. + -install-examples-sources Installs examples source code into the Qt prefix + Only possible when -make examples is also passed + [no] -gui ................. Build the Qt GUI module and dependencies [yes] -widgets ............. Build the Qt Widgets module and dependencies [yes] -no-dbus ............. Do not build the Qt D-Bus module @@ -289,6 +296,6 @@ Gui, printing, widget options: Database options: -sql- ........ Enable SQL plugin. Supported drivers: - db2 ibase mysql oci odbc psql sqlite + db2 ibase mysql oci odbc psql sqlite mimer [all auto] -sqlite .............. Select used sqlite [system/qt] diff --git a/configure.cmake b/configure.cmake index be1a4f3b..43de2aa0 100644 --- a/configure.cmake +++ b/configure.cmake @@ -109,7 +109,6 @@ SSL_free(SSL_new(0)); } ") -# special case end qt_find_package(WrapZSTD 1.3 PROVIDED_TARGETS WrapZSTD::WrapZSTD MODULE_NAME global QMAKE_LIB zstd) qt_find_package(WrapDBus1 1.2 PROVIDED_TARGETS dbus-1 MODULE_NAME global QMAKE_LIB dbus) qt_find_package(Libudev PROVIDED_TARGETS PkgConfig::Libudev MODULE_NAME global QMAKE_LIB libudev) @@ -208,51 +207,6 @@ endif() # machineTuple qt_config_compile_test_machine_tuple("machine tuple") -# cxx14 -qt_config_compile_test(cxx14 - LABEL "C++14 support" - CODE -"#if __cplusplus > 201103L -// Compiler claims to support C++14, trust it -#else -# error __cplusplus must be > 201103L (the value of C++11) -#endif - -int main(void) -{ - /* BEGIN TEST: */ - /* END TEST: */ - return 0; -} -" - CXX_STANDARD 14 -) - -# cxx17 -qt_config_compile_test(cxx17 - LABEL "C++17 support" - CODE -"#if __cplusplus > 201402L -// Compiler claims to support C++17, trust it -#else -# error __cplusplus must be > 201402L (the value for C++14) -#endif -#include // https://bugs.llvm.org//show_bug.cgi?id=33117 -#include - -int main(void) -{ - /* BEGIN TEST: */ -std::variant v(42); -int i = std::get(v); -std::visit([](const auto &) { return 1; }, v); - /* END TEST: */ - return 0; -} -" - CXX_STANDARD 17 -) - # cxx20 qt_config_compile_test(cxx20 LABEL "C++20 support" @@ -485,7 +439,6 @@ qt_feature("android-style-assets" PRIVATE ) qt_feature("shared" PUBLIC LABEL "Building shared libraries" - AUTODETECT NOT UIKIT CONDITION BUILD_SHARED_LIBS ) qt_feature_definition("shared" "QT_STATIC" NEGATE PREREQUISITE "!defined(QT_SHARED) && !defined(QT_STATIC)") @@ -517,7 +470,6 @@ qt_feature("optimize_size" CONDITION NOT QT_FEATURE_debug OR QT_FEATURE_debug_and_release ) qt_feature_config("optimize_size" QMAKE_PRIVATE_CONFIG) -# special case begin qt_feature("optimize_full" LABEL "Fully optimize release builds (-O3)" AUTODETECT OFF @@ -526,10 +478,10 @@ qt_feature_config("optimize_full" QMAKE_PRIVATE_CONFIG) qt_feature("msvc_obj_debug_info" LABEL "Embed debug info in object files (MSVC)" CONDITION MSVC + ENABLE QT_USE_CCACHE AUTODETECT OFF ) qt_feature_config("msvc_obj_debug_info" QMAKE_PRIVATE_CONFIG) -# special case end qt_feature("pkg-config" PUBLIC LABEL "Using pkg-config" AUTODETECT NOT APPLE AND NOT WIN32 AND NOT ANDROID @@ -667,29 +619,10 @@ qt_feature_config("plugin-manifests" QMAKE_PUBLIC_CONFIG NEGATE NAME "no_plugin_manifest" ) -qt_feature("c++11" PUBLIC - LABEL "C++11" -) -qt_feature_config("c++11" QMAKE_PUBLIC_QT_CONFIG) -qt_feature("c++14" PUBLIC - LABEL "C++14" - CONDITION QT_FEATURE_cxx11 AND TEST_cxx14 -) -qt_feature_config("c++14" QMAKE_PUBLIC_QT_CONFIG) -qt_feature("c++17" PUBLIC - LABEL "C++17" - CONDITION QT_FEATURE_cxx14 AND TEST_cxx17 -) -qt_feature_config("c++17" QMAKE_PUBLIC_QT_CONFIG) -qt_feature("c++1z" PUBLIC - LABEL "C++17" - CONDITION QT_FEATURE_cxx17 -) -qt_feature_config("c++1z" QMAKE_PUBLIC_QT_CONFIG) qt_feature("c++20" PUBLIC LABEL "C++20" AUTODETECT OFF - CONDITION QT_FEATURE_cxx17 AND TEST_cxx20 + CONDITION TEST_cxx20 ) qt_feature_config("c++20" QMAKE_PUBLIC_QT_CONFIG) qt_feature("c++2a" PUBLIC @@ -707,17 +640,6 @@ qt_feature("c++2b" PUBLIC AUTODETECT FALSE CONDITION QT_FEATURE_cxx20 AND (CMAKE_VERSION VERSION_GREATER_EQUAL "3.20") AND TEST_cxx2b ) -qt_feature("c89" - LABEL "C89" -) -qt_feature("c99" PUBLIC - LABEL "C99" - CONDITION c_std_99 IN_LIST CMAKE_C_COMPILE_FEATURES -) -qt_feature("c11" PUBLIC - LABEL "C11" - CONDITION QT_FEATURE_c99 AND c_std_11 IN_LIST CMAKE_C_COMPILE_FEATURES -) qt_feature("precompile_header" LABEL "Using precompiled headers" CONDITION BUILD_WITH_PCH AND TEST_precompile_header @@ -920,7 +842,9 @@ qt_feature_definition("mips_dspr2" "QT_COMPILER_SUPPORTS_MIPS_DSPR2" VALUE "1") qt_feature_config("mips_dspr2" QMAKE_PRIVATE_CONFIG) qt_feature("neon" PRIVATE LABEL "NEON" - CONDITION ( ( ( TEST_architecture_arch STREQUAL arm ) OR ( TEST_architecture_arch STREQUAL arm64 ) ) AND TEST_arch_${TEST_architecture_arch}_subarch_neon ) OR QT_FORCE_FEATURE_neon # special case + CONDITION ( ( ( TEST_architecture_arch STREQUAL arm ) OR + ( TEST_architecture_arch STREQUAL arm64 ) ) AND + TEST_arch_${TEST_architecture_arch}_subarch_neon ) OR QT_FORCE_FEATURE_neon ) qt_feature_definition("neon" "QT_COMPILER_SUPPORTS_NEON" VALUE "1") qt_feature_config("neon" QMAKE_PRIVATE_CONFIG) @@ -986,7 +910,6 @@ qt_feature("stdlib-libcpp" PRIVATE AUTODETECT OFF CONDITION LINUX AND NOT ANDROID ) -# special case begin # Check whether CMake was built with zstd support. # See https://gitlab.kitware.com/cmake/cmake/-/issues/21552 if(NOT DEFINED CACHE{QT_CMAKE_ZSTD_SUPPORT}) @@ -1003,7 +926,6 @@ if(NOT DEFINED CACHE{QT_CMAKE_ZSTD_SUPPORT}) unset(qt_check_zstd_exit_code) endif() endif() -# special case end qt_feature("thread" PUBLIC SECTION "Kernel" LABEL "Thread support" @@ -1143,22 +1065,10 @@ qt_configure_add_summary_entry( ARGS "optimize_size" CONDITION NOT QT_FEATURE_debug OR QT_FEATURE_debug_and_release ) -# special case begin qt_configure_add_summary_entry( ARGS "optimize_full" ) -# special case end qt_configure_add_summary_entry(ARGS "shared") -qt_configure_add_summary_entry( - TYPE "firstAvailableFeature" - ARGS "c11 c99 c89" - MESSAGE "Using C standard" -) -qt_configure_add_summary_entry( - TYPE "firstAvailableFeature" - ARGS "c++2b c++20 c++17 c++14 c++11" - MESSAGE "Using C++ standard" -) qt_configure_add_summary_entry( ARGS "ccache" CONDITION UNIX @@ -1232,6 +1142,13 @@ qt_configure_add_summary_entry(ARGS "sanitize_fuzzer_no_link") qt_configure_add_summary_entry(ARGS "sanitize_undefined") qt_configure_end_summary_section() # end of "Sanitizers" section qt_configure_add_summary_build_parts("Build parts") +if(QT_INSTALL_EXAMPLES_SOURCES) + set(_examples_sources_entry_message "yes") +else() + set(_examples_sources_entry_message "no") +endif() +qt_configure_add_summary_entry(ARGS "Install examples sources" TYPE "message" + MESSAGE "${_examples_sources_entry_message}") qt_configure_add_summary_entry( ARGS "appstore-compliant" CONDITION APPLE OR ANDROID OR WIN32 @@ -1251,6 +1168,14 @@ qt_configure_add_summary_entry(ARGS "xml") qt_configure_end_summary_section() # end of "Qt modules and options" section qt_configure_add_summary_section(NAME "Support enabled for") qt_configure_add_summary_entry(ARGS "pkg-config") + +if(QT_USE_VCPKG AND (DEFINED ENV{VCPKG_ROOT} OR VCPKG_TARGET_TRIPLET)) + set(_vcpkg_entry_message "yes") +else() + set(_vcpkg_entry_message "no") +endif() +qt_configure_add_summary_entry(ARGS "Using vcpkg" TYPE "message" MESSAGE "${_vcpkg_entry_message}") + qt_configure_add_summary_entry(ARGS "libudev") qt_configure_add_summary_entry(ARGS "openssl") qt_configure_add_summary_entry(ARGS "openssl-linked") @@ -1265,13 +1190,6 @@ qt_configure_add_report_entry( MESSAGE "Using static linking will disable the use of dynamically loaded plugins. Make sure to import all needed static plugins, or compile needed modules into the library." CONDITION NOT QT_FEATURE_shared ) -# special case begin -# qt_configure_add_report_entry( -# TYPE ERROR -# MESSAGE "Debug build without Release build is not currently supported on ios see QTBUG-71990. Use -debug-and-release." -# CONDITION IOS AND QT_FEATURE_debug AND NOT QT_FEATURE_debug_and_release -# ) -# special case end qt_configure_add_report_entry( TYPE WARNING MESSAGE "-debug-and-release is only supported on Darwin and Windows platforms. Qt can be built in release mode with separate debug information, so -debug-and-release is no longer necessary." @@ -1301,6 +1219,16 @@ if (TEST_architecture_arch STREQUAL x86_64 OR TEST_architecture_arch STREQUAL i3 MESSAGE [=[ All x86 intrinsics and SIMD support were disabled. If this was in error, check the result of the build in config.tests/x86intrin and report at https://bugreports.qt.io. +]=] + ) + elseif (MSVC AND CLANG) + # Warn only + qt_configure_add_report_entry( + TYPE WARNING + CONDITION (NOT QT_FEATURE_x86intrin) + MESSAGE [=[ +x86 intrinsics support is disabled for clang-cl build. This might be necessary due to +https://github.com/llvm/llvm-project/issues/53520 ]=] ) else() @@ -1317,7 +1245,6 @@ ${TEST_x86intrin_OUTPUT} ) endif() endif() -# special case begin qt_configure_add_report_entry( TYPE ERROR MESSAGE "Setting a library infix is not supported for framework builds." @@ -1336,7 +1263,6 @@ qt_configure_add_report_entry( if(WASM) qt_extra_definition("QT_EMCC_VERSION" "\"${EMCC_VERSION}\"" PUBLIC) endif() -# special case end qt_extra_definition("QT_VERSION_STR" "\"${PROJECT_VERSION}\"" PUBLIC) qt_extra_definition("QT_VERSION_MAJOR" ${PROJECT_VERSION_MAJOR} PUBLIC) qt_extra_definition("QT_VERSION_MINOR" ${PROJECT_VERSION_MINOR} PUBLIC) diff --git a/doc/CMakeLists.txt b/doc/CMakeLists.txt index f27c5d4e..bb7dd428 100644 --- a/doc/CMakeLists.txt +++ b/doc/CMakeLists.txt @@ -1,7 +1,6 @@ # Copyright (C) 2022 The Qt Company Ltd. # SPDX-License-Identifier: BSD-3-Clause -# special case skip regeneration # # Copy/Install doc configuration files to the build/install directory # diff --git a/doc/global/macros.qdocconf b/doc/global/macros.qdocconf index 5a7b826f..561cebbc 100644 --- a/doc/global/macros.qdocconf +++ b/doc/global/macros.qdocconf @@ -125,6 +125,7 @@ macro.cmakepropertywebassemblyonly = "\\note This property is used only if targe macro.cmakepropertyiosonly = "\\note This property is used only if targeting iOS." macro.cmakevariableiosonly = "\\note This variable is used only if targeting iOS." +macro.qtpolicydeprecatedbehavior = "\\note The \\c{OLD} behavior of a policy is deprecated, and may be removed in the future." #Appends the tech preview link to the brief sentence and adds to tech_preview #group. #Must be placed directly under a \brief command diff --git a/doc/global/manifest-meta.qdocconf b/doc/global/manifest-meta.qdocconf index 6c7ffd86..b757ae9a 100644 --- a/doc/global/manifest-meta.qdocconf +++ b/doc/global/manifest-meta.qdocconf @@ -35,7 +35,7 @@ manifestmeta.highlighted.attributes = isHighlighted:true manifestmeta.android.names = "Qt3D/Qt 3D: Basic Shapes C++ Example" \ "Qt3D/Qt 3D: Planets QML Example" \ "Qt3D/Qt 3D: Simple Custom Material QML Example" \ - "QtAndroidExtras/Qt Notifier" \ + "QtCore/Qt Android Notifier" \ "QtBluetooth/Bluetooth Low Energy Scanner Example" \ "QtBluetooth/Bluetooth Scanner Example" \ "QtBluetooth/QML Bluetooth Scanner Example" \ @@ -51,15 +51,12 @@ manifestmeta.android.names = "Qt3D/Qt 3D: Basic Shapes C++ Example" \ "QtLocation/Places Map (QML)" \ "QtLocation/Plane Spotter (QML)" \ "QtMultimedia/AudioEngine Example" \ - "QtMultimedia/Camera Example" \ "QtMultimedia/QML Camera Example" \ "QtMultimedia/QML Video Example" \ "QtMultimedia/QML Video Shader Effects Example" \ "QtNFC/Annotated URL Example" \ "QtNFC/QML Poster Example" \ - "QtOpenGL/2D Painting Example" \ "QtOpenGL/Hello GLES3 Example" \ - "QtOpenGL/Textures Example" \ "QtPositioning/SatelliteInfo (C++/QML)" \ "QtPositioning/Weather Info (C++/QML)" \ "QtPurchasing/Qt Purchasing Examples - QtHangman" \ @@ -94,8 +91,6 @@ manifestmeta.android.names = "Qt3D/Qt 3D: Basic Shapes C++ Example" \ "QtQuickControls/Qt Quick Controls - Flat Style" \ "QtQuickControls/Qt Quick Controls - Gallery" \ "QtQuickControls/Qt Quick Controls - Imagine Style Example: Automotive" \ - "QtQuickControls/Qt Quick Controls - Side Panel" \ - "QtQuickControls/Qt Quick Controls - Swipe to Remove" \ "QtQuickControls/Qt Quick Controls - Text Editor" \ "QtQuickControls/Qt Quick Controls - Wearable Demo" \ "QtQuickDialogs/*" \ @@ -107,47 +102,7 @@ manifestmeta.android.names = "Qt3D/Qt 3D: Basic Shapes C++ Example" \ "QtSQL/Master Detail Example" \ "QtSVG/Text Object Example" \ "QtUiTools/Text Finder Example" \ - "QtWebView/Qt WebView Examples - Minibrowser" \ - "QtWidgets/Address Book Example" \ - "QtWidgets/Affine Transformations" \ - "QtWidgets/Analog Clock Example" \ - "QtWidgets/Animated Tiles Example" \ - "QtWidgets/Application Chooser Example" \ - "QtWidgets/Basic Layouts Example" \ - "QtWidgets/Border Layout Example" \ - "QtWidgets/Code Editor Example" \ - "QtWidgets/Colliding Mice Example" \ - "QtWidgets/Concentric Circles Example" \ - "QtWidgets/Digital Clock Example" \ - "QtWidgets/Dynamic Layouts Example" \ - "QtWidgets/Easing Curves Example" \ - "QtWidgets/Editable Tree Model Example" \ - "QtWidgets/Elided Label Example" \ - "QtWidgets/Fade Message Effect Example" \ - "QtWidgets/Flow Layout Example" \ - "QtWidgets/Font Sampler Example" \ - "QtWidgets/Frozen Column Example" \ - "QtWidgets/Gradients" \ - "QtWidgets/Group Box Example" \ - "QtWidgets/Image Composition Example" \ - "QtWidgets/Line Edits Example" \ - "QtWidgets/Mouse Button Tester" \ - "QtWidgets/Move Blocks Example" \ - "QtWidgets/Painter Paths Example" \ - "QtWidgets/Painter Paths Example" \ - "QtWidgets/Path Stroking" \ - "QtWidgets/Pixelator Example" \ - "QtWidgets/Recent Files Example" \ - "QtWidgets/SDI Example" \ - "QtWidgets/Scribble Example" \ - "QtWidgets/Simple Tree Model Example" \ - "QtWidgets/Sliders Example" \ - "QtWidgets/Spreadsheet" \ - "QtWidgets/Touch Dials Example" \ - "QtWidgets/Transformations Example" \ - "QtWidgets/Undo Framework" \ - "QtWidgets/Vector Deformation" \ - "QtWidgets/Wiggly Example" + "QtWebView/Qt WebView Examples - Minibrowser" manifestmeta.android.tags = android diff --git a/examples/corelib/bindableproperties/CMakeLists.txt b/examples/corelib/bindableproperties/CMakeLists.txt index c6d9076f..9e23bf9b 100644 --- a/examples/corelib/bindableproperties/CMakeLists.txt +++ b/examples/corelib/bindableproperties/CMakeLists.txt @@ -1,2 +1,25 @@ -qt_internal_add_example(bindablesubscription) -qt_internal_add_example(subscription) +# Copyright (C) 2022 The Qt Company Ltd. +# SPDX-License-Identifier: BSD-3-Clause + +cmake_minimum_required(VERSION 3.16) +project(bindableproperties LANGUAGES CXX) + +if(NOT DEFINED INSTALL_EXAMPLESDIR) + set(INSTALL_EXAMPLESDIR "examples") +endif() + +set(INSTALL_EXAMPLEDIR "${INSTALL_EXAMPLESDIR}/corelib/bindableproperties") + +find_package(Qt6 REQUIRED COMPONENTS Core Gui Widgets) + +qt_standard_project_setup() + +add_subdirectory(shared) +add_subdirectory(subscription) +add_subdirectory(bindablesubscription) + +install(TARGETS subscription bindablesubscription + RUNTIME DESTINATION "${INSTALL_EXAMPLEDIR}" + BUNDLE DESTINATION "${INSTALL_EXAMPLEDIR}" + LIBRARY DESTINATION "${INSTALL_EXAMPLEDIR}" +) diff --git a/examples/corelib/bindableproperties/bindablesubscription/CMakeLists.txt b/examples/corelib/bindableproperties/bindablesubscription/CMakeLists.txt index c4a2e5fc..d7967e67 100644 --- a/examples/corelib/bindableproperties/bindablesubscription/CMakeLists.txt +++ b/examples/corelib/bindableproperties/bindablesubscription/CMakeLists.txt @@ -1,50 +1,15 @@ # Copyright (C) 2022 The Qt Company Ltd. # SPDX-License-Identifier: BSD-3-Clause -cmake_minimum_required(VERSION 3.16) -project(bindablesubscription LANGUAGES CXX) - -if(NOT DEFINED INSTALL_EXAMPLESDIR) - set(INSTALL_EXAMPLESDIR "examples") -endif() - -set(INSTALL_EXAMPLEDIR "${INSTALL_EXAMPLESDIR}/corelib/bindableproperties/bindablesubscription") - -find_package(Qt6 REQUIRED COMPONENTS Core Gui Widgets) - -qt_standard_project_setup() - qt_add_executable(bindablesubscription - ../shared/subscriptionwindow.cpp ../shared/subscriptionwindow.h ../shared/subscriptionwindow.ui main.cpp - bindablesubscription.cpp bindablesubscription.h - bindableuser.cpp bindableuser.h + bindablesubscription.cpp + bindablesubscription.h + bindableuser.cpp + bindableuser.h ) target_link_libraries(bindablesubscription PRIVATE - Qt6::Core - Qt6::Gui - Qt6::Widgets + bindableproperties_shared ) -# Resources: -set(countries_resource_files - "../shared/finland.png" - "../shared/germany.png" - "../shared/norway.png" -) - -qt_add_resources(bindablesubscription "countries" - PREFIX - "/" - BASE - "../shared" - FILES - ${countries_resource_files} -) - -install(TARGETS bindablesubscription - RUNTIME DESTINATION "${INSTALL_EXAMPLEDIR}" - BUNDLE DESTINATION "${INSTALL_EXAMPLEDIR}" - LIBRARY DESTINATION "${INSTALL_EXAMPLEDIR}" -) diff --git a/examples/corelib/bindableproperties/shared/CMakeLists.txt b/examples/corelib/bindableproperties/shared/CMakeLists.txt new file mode 100644 index 00000000..dee3ecc1 --- /dev/null +++ b/examples/corelib/bindableproperties/shared/CMakeLists.txt @@ -0,0 +1,23 @@ +# Copyright (C) 2023 The Qt Company Ltd. +# SPDX-License-Identifier: BSD-3-Clause + +add_library(bindableproperties_shared STATIC + subscriptionwindow.cpp + subscriptionwindow.h + subscriptionwindow.ui +) + +target_link_libraries(bindableproperties_shared PUBLIC + Qt6::Core + Qt6::Gui + Qt6::Widgets +) + +qt_add_resources(bindableproperties_shared "countries" + PREFIX + "/" + FILES + "finland.png" + "germany.png" + "norway.png" +) diff --git a/examples/corelib/bindableproperties/subscription/CMakeLists.txt b/examples/corelib/bindableproperties/subscription/CMakeLists.txt index 0dd027fc..15e41ad7 100644 --- a/examples/corelib/bindableproperties/subscription/CMakeLists.txt +++ b/examples/corelib/bindableproperties/subscription/CMakeLists.txt @@ -1,50 +1,12 @@ # Copyright (C) 2022 The Qt Company Ltd. # SPDX-License-Identifier: BSD-3-Clause -cmake_minimum_required(VERSION 3.16) -project(subscription LANGUAGES CXX) - -if(NOT DEFINED INSTALL_EXAMPLESDIR) - set(INSTALL_EXAMPLESDIR "examples") -endif() - -set(INSTALL_EXAMPLEDIR "${INSTALL_EXAMPLESDIR}/corelib/bindableproperties/subscription") - -find_package(Qt6 REQUIRED COMPONENTS Core Gui Widgets) - -qt_standard_project_setup() - qt_add_executable(subscription - ../shared/subscriptionwindow.cpp ../shared/subscriptionwindow.h ../shared/subscriptionwindow.ui main.cpp subscription.cpp subscription.h user.cpp user.h ) target_link_libraries(subscription PRIVATE - Qt6::Core - Qt6::Gui - Qt6::Widgets -) - -# Resources: -set(countries_resource_files - "../shared/finland.png" - "../shared/germany.png" - "../shared/norway.png" -) - -qt_add_resources(subscription "countries" - PREFIX - "/" - BASE - "../shared" - FILES - ${countries_resource_files} -) - -install(TARGETS subscription - RUNTIME DESTINATION "${INSTALL_EXAMPLEDIR}" - BUNDLE DESTINATION "${INSTALL_EXAMPLEDIR}" - LIBRARY DESTINATION "${INSTALL_EXAMPLEDIR}" + bindableproperties_shared ) diff --git a/examples/corelib/platform/androidnotifier/CMakeLists.txt b/examples/corelib/platform/androidnotifier/CMakeLists.txt index e05afc29..e5271edd 100644 --- a/examples/corelib/platform/androidnotifier/CMakeLists.txt +++ b/examples/corelib/platform/androidnotifier/CMakeLists.txt @@ -23,6 +23,8 @@ qt_add_executable(androidnotifier main.cpp notificationclient.cpp notificationclient.h + android/src/org/qtproject/example/androidnotifier/NotificationClient.java + android/AndroidManifest.xml ) target_link_libraries(androidnotifier PRIVATE diff --git a/examples/corelib/platform/androidnotifier/doc/src/androidnotifier-example.qdoc b/examples/corelib/platform/androidnotifier/doc/src/androidnotifier-example.qdoc index 37e81485..2d579c95 100644 --- a/examples/corelib/platform/androidnotifier/doc/src/androidnotifier-example.qdoc +++ b/examples/corelib/platform/androidnotifier/doc/src/androidnotifier-example.qdoc @@ -6,6 +6,7 @@ \example platform/androidnotifier \examplecategory {Mobile} \brief Demonstrates calling Java code from Qt in an Android application. + \ingroup androidplatform \image androidnotifier.png @@ -48,7 +49,7 @@ The call to the Java meethod use \l QJniObject which relies on the Java Native Interface (JNI) APIs to communicate with Java. Also, in the previous snippet, - we are passing the app's context object which the the static Java code can use + we are passing the app's context object, which the static Java code can use to tap into the app's specific properties and APIs. To make sure our smiley buttons do what they are supposed to, we add the diff --git a/examples/corelib/serialization/cbordump/cbortag.py b/examples/corelib/serialization/cbordump/cbortag.py new file mode 100644 index 00000000..26a0f969 --- /dev/null +++ b/examples/corelib/serialization/cbordump/cbortag.py @@ -0,0 +1,188 @@ +#!/bin/env python3 +# Copyright (C) 2023 The Qt Company Ltd. +# SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause +"""Digest cbor-tags.xml file into code for insertion into main.cpp + +See main.cpp's comment on how to regenerate its GENERATED CODE. +See ./cbortag.py --help for further details on how to invoke. +You can import this is a module without invoking the script. +""" + +def firstChild(parent, tag): + """Return parent's first child element with the given tag.""" + return next(node for node in parent.childNodes + if node.nodeType == parent.ELEMENT_NODE and node.nodeName == tag) + +def nodeAttrIs(node, attr, seek): + """Checks whether the node has a given value for an attribute + + Takes the node to check, the name of the attribute and the value + to check against. Returns true if the node does have that value + for the named attribute.""" + if node.nodeType != node.ELEMENT_NODE: + return False + if node.attributes is None or attr not in node.attributes: + return False + return node.attributes[attr].value == seek + +def getRfcValue(node): + """Extract RFC reference from an element + + Some of these have a reference including section details as the + body of the element, otherwise the data attribute should identify + the RFC. If neither is found, an empty string is returned.""" + if node.childNodes: + return node.childNodes[0].nodeValue # Maybe accumulate several children ? + if node.attributes is None or 'data' not in node.attributes: + return '' + return node.attributes['data'].value + +def readRegistry(filename): + """Handles the XML parsing and returns the relevant parts. + + Single argument is the path to the cbor-tags.xml file; returns a + twople of the title element's text and an interator over the + record nodes. Checks some things are as expected while doing so.""" + from xml.dom.minidom import parse + doc = parse(filename).documentElement + assert nodeAttrIs(doc, 'id', 'cbor-tags') + title = firstChild(doc, 'title').childNodes[0].nodeValue + registry = firstChild(doc, 'registry') + assert nodeAttrIs(registry, 'id', 'tags') + records = (node for node in registry.childNodes if node.nodeName == 'record') + return title, records + +def digest(record): + """Digest a single record from cbor-tags.xml + + If the record is not of interest, returns the twople (None, None). + For records of interest, returns (n, t) where n is the numeric tag + code of the record and t is a text describing it. If the record, + or its semantics field, has an xref child with type="rfc", the RFC + mentioned there is included with the text of the semantics; such a + record is of interest, provided it has a semantics field and no + dash in its value. Records with a value field containing a dash + (indicating a range) are not of interest. Records with a value of + 256 or above are only of interest if they include an RFC.""" + data = {} + for kid in record.childNodes: + if kid.nodeName == 'xref': + if not nodeAttrIs(kid, 'type', 'rfc'): + continue + rfc = getRfcValue(kid) + if rfc: + # Potentially stomping one taken from semantics + data['rfc'] = rfc + elif kid.nodeName == 'semantics': + text = rfc = '' + for part in kid.childNodes: + if part.nodeType == kid.TEXT_NODE: + text += part.nodeValue + elif part.nodeType == kid.ELEMENT_NODE: + if part.nodeName != 'xref' or not nodeAttrIs(part, 'type', 'rfc'): + continue # potentially append content to text + assert not rfc, ('Duplicate RFC ?', rfc, part) + rfc = getRfcValue(part) + if rfc: + if text.endswith('()'): + text = text[:-2].rstrip() + if 'rfc' not in data: + data['rfc'] = rfc + data['semantics'] = ' '.join(text.split()) + elif kid.nodeName == 'value': + data['value'] = kid.childNodes[0].nodeValue + text = data.get('semantics') + if not text or 'value' not in data or '-' in data['value']: + return None, None + value = int(data['value']) + if 'rfc' in data: + rfc = data["rfc"].replace('rfc', 'RFC') + text = f'{text} [{rfc}]' + elif value >= 256: + return None, None + return value, text + +def entries(records): + """Digest each record of interest into a value and text. + + The value and text form the raw material of the tagDescriptions + array in main.cpp; see digest for which records are retained.""" + for record in records: + value, text = digest(record) + if value is not None: + yield value, text + +def marginBound(text, prior, left, right): + """Split up a string literal for tidy display. + + The first parameter, text, is the content of the string literal; + quotes shall be added. It may be split into several fragments, + each quoted, so as to abide by line length constraints. + + The remaining parameters are integers: prior is the text already + present on the line before text is to be added; left is the width + of the left margin for all subsequent lines; and right is the + right margin to stay within, where possible. The returned string + is either a space with the whole quoted text following, to fit on + the line already started to length prior, or a sequence of quoted + strings, each preceded by a newline and indent of width left.""" + if prior + 3 + len(text) < right: # 1 for space, 2 for quotes + return f' "{text}"' + width = right - left - 2 # 2 for the quotes + words = iter(text.split(' ')) + lines, current = [''], [next(words)] + for word in words: + if len(word) + sum(len(w) + 1 for w in current) > width: + line = ' '.join(current) + lines.append(f'"{line}"') + current = ['', word] + else: + current.append(word) + line = ' '.join(current) + lines.append(f'"{line}"') + return ('\n' + ' ' * left).join(lines) + +def main(argv, speak): + """Takes care of driving the process. + + Takes the command-line argument list (whose first entry is the + name of this script) and standard output (or compatible stream of + your choosing) to which to write data. If the --out option is + specified in the arguments, the file it names is used in place of + this output stream.""" + from argparse import ArgumentParser, ArgumentDefaultsHelpFormatter + parser = ArgumentParser( + description='Digest cbor-tags.xml into code to insert in main.cpp', + formatter_class=ArgumentDefaultsHelpFormatter) + parser.add_argument('path', help='path of the cbor-tags.xml file', + default='cbor-tags.xml') + parser.add_argument('--out', help='file to write instead of standard output') + args = parser.parse_args(argv[1:]) + emit = (open(args.out) if args.out else speak).write + + title, records = readRegistry(args.path) + emit(f"""\ +struct CborTagDescription +{{ + QCborTag tag; + const char *description; // with space and parentheses +}}; + +// {title} +static const CborTagDescription tagDescriptions[] = {{ + // from https://www.iana.org/assignments/cbor-tags/cbor-tags.xhtml +""") + + for value, text in sorted(entries(records)): + prior = f' {{ QCborTag({value}),' + body = marginBound(f' ({text})', len(prior), 6, 96) + emit(f"{prior}{body} }},\n") + + emit("""\ + { QCborTag(-1), nullptr } +}; +""") + +if __name__ == '__main__': + import sys + sys.exit(main(sys.argv, sys.stdout)) diff --git a/examples/corelib/serialization/cbordump/doc/images/cbordump.png b/examples/corelib/serialization/cbordump/doc/images/cbordump.png index d951ff9b..d5ff1da0 100644 Binary files a/examples/corelib/serialization/cbordump/doc/images/cbordump.png and b/examples/corelib/serialization/cbordump/doc/images/cbordump.png differ diff --git a/examples/corelib/serialization/cbordump/main.cpp b/examples/corelib/serialization/cbordump/main.cpp index 56d4070e..fc97dd5e 100644 --- a/examples/corelib/serialization/cbordump/main.cpp +++ b/examples/corelib/serialization/cbordump/main.cpp @@ -19,18 +19,12 @@ using namespace Qt::StringLiterals; /* * To regenerate: * curl -O https://www.iana.org/assignments/cbor-tags/cbor-tags.xml - * xsltproc tag-transform.xslt cbor-tags.xml + * ./cbortag.py cbor-tags.xml * * The XHTML URL mentioned in the comment below is a human-readable version of * the same resource. */ -/* TODO (if possible): fix XSLT to replace each newline and surrounding space in - a semantics entry with a single space, instead of using a raw string to wrap - each, propagating the spacing from the XML to the output of cbordump. Also - auto-purge dangling spaces from the ends of generated lines. -*/ - // GENERATED CODE struct CborTagDescription { @@ -38,180 +32,101 @@ struct CborTagDescription const char *description; // with space and parentheses }; -// CBOR Tags +// Concise Binary Object Representation (CBOR) Tags static const CborTagDescription tagDescriptions[] = { // from https://www.iana.org/assignments/cbor-tags/cbor-tags.xhtml - { QCborTag(0), - R"r( (Standard date/time string; see Section 3.4.1 [RFC8949]))r" }, - { QCborTag(1), - R"r( (Epoch-based date/time; see Section 3.4.2 [RFC8949]))r" }, - { QCborTag(2), - R"r( (Positive bignum; see Section 3.4.3 [RFC8949]))r" }, - { QCborTag(3), - R"r( (Negative bignum; see Section 3.4.3 [RFC8949]))r" }, - { QCborTag(4), - R"r( (Decimal fraction; see Section 3.4.4 [RFC8949]))r" }, - { QCborTag(5), - R"r( (Bigfloat; see Section 3.4.4 [RFC8949]))r" }, - { QCborTag(16), - R"r( (COSE Single Recipient Encrypted Data Object [RFC9052]))r" }, - { QCborTag(17), - R"r( (COSE Mac w/o Recipients Object [RFC9052]))r" }, - { QCborTag(18), - R"r( (COSE Single Signer Data Object [RFC9052]))r" }, - { QCborTag(19), - R"r( (COSE standalone V2 countersignature [RFC9338]))r" }, + { QCborTag(0), " (Standard date/time string; see Section 3.4.1 [RFC8949])" }, + { QCborTag(1), " (Epoch-based date/time; see Section 3.4.2 [RFC8949])" }, + { QCborTag(2), " (Positive bignum; see Section 3.4.3 [RFC8949])" }, + { QCborTag(3), " (Negative bignum; see Section 3.4.3 [RFC8949])" }, + { QCborTag(4), " (Decimal fraction; see Section 3.4.4 [RFC8949])" }, + { QCborTag(5), " (Bigfloat; see Section 3.4.4 [RFC8949])" }, + { QCborTag(16), " (COSE Single Recipient Encrypted Data Object [RFC9052])" }, + { QCborTag(17), " (COSE Mac w/o Recipients Object [RFC9052])" }, + { QCborTag(18), " (COSE Single Signer Data Object [RFC9052])" }, + { QCborTag(19), " (COSE standalone V2 countersignature [RFC9338])" }, { QCborTag(21), - R"r( (Expected conversion to base64url encoding; see Section 3.4.5.2 [RFC8949]))r" }, - { QCborTag(22), - R"r( (Expected conversion to base64 encoding; see Section 3.4.5.2 [RFC8949]))r" }, - { QCborTag(23), - R"r( (Expected conversion to base16 encoding; see Section 3.4.5.2 [RFC8949]))r" }, - { QCborTag(24), - R"r( (Encoded CBOR data item; see Section 3.4.5.1 [RFC8949]))r" }, - { QCborTag(25), - R"r( (reference the nth previously seen string))r" }, - { QCborTag(26), - R"r( (Serialised Perl object with classname and constructor arguments))r" }, + " (Expected conversion to base64url encoding; see Section 3.4.5.2 [RFC8949])" }, + { QCborTag(22), " (Expected conversion to base64 encoding; see Section 3.4.5.2 [RFC8949])" }, + { QCborTag(23), " (Expected conversion to base16 encoding; see Section 3.4.5.2 [RFC8949])" }, + { QCborTag(24), " (Encoded CBOR data item; see Section 3.4.5.1 [RFC8949])" }, + { QCborTag(25), " (reference the nth previously seen string)" }, + { QCborTag(26), " (Serialised Perl object with classname and constructor arguments)" }, { QCborTag(27), - R"r( (Serialised language-independent object with type name and constructor arguments))r" }, - { QCborTag(28), - R"r( (mark value as (potentially) shared))r" }, - { QCborTag(29), - R"r( (reference nth marked value))r" }, - { QCborTag(30), - R"r( (Rational number))r" }, - { QCborTag(31), - R"r( (Absent value in a CBOR Array))r" }, - { QCborTag(32), - R"r( (URI; see Section 3.4.5.3 [RFC8949]))r" }, - { QCborTag(33), - R"r( (base64url; see Section 3.4.5.3 [RFC8949]))r" }, - { QCborTag(34), - R"r( (base64; see Section 3.4.5.3 [RFC8949]))r" }, - { QCborTag(35), - R"r( (Regular expression; see Section 2.4.4.3 [RFC7049]))r" }, - { QCborTag(36), - R"r( (MIME message; see Section 3.4.5.3 [RFC8949]))r" }, - { QCborTag(37), - R"r( (Binary UUID (RFC4122, Section 4.1.2)))r" }, - { QCborTag(38), - R"r( (Language-tagged string [RFC9290]))r" }, - { QCborTag(39), - R"r( (Identifier))r" }, - { QCborTag(40), - R"r( (Multi-dimensional Array, row-major order [RFC8746]))r" }, - { QCborTag(41), - R"r( (Homogeneous Array [RFC8746]))r" }, - { QCborTag(42), - R"r( (IPLD content identifier))r" }, - { QCborTag(43), - R"r( (YANG bits datatype; see Section 6.7. [RFC9254]))r" }, - { QCborTag(44), - R"r( (YANG enumeration datatype; see Section 6.6. [RFC9254]))r" }, - { QCborTag(45), - R"r( (YANG identityref datatype; see Section 6.10. [RFC9254]))r" }, - { QCborTag(46), - R"r( (YANG instance-identifier datatype; see Section 6.13. [RFC9254]))r" }, - { QCborTag(47), - R"r( (YANG Schema Item iDentifier (sid); see Section 3.2. [RFC9254]))r" }, - { QCborTag(52), - R"r( (IPv4, [prefixlen,IPv4], [IPv4,prefixpart] [RFC9164]))r" }, - { QCborTag(54), - R"r( (IPv6, [prefixlen,IPv6], [IPv6,prefixpart] [RFC9164]))r" }, - { QCborTag(61), - R"r( (CBOR Web Token (CWT) [RFC8392]))r" }, - { QCborTag(63), - R"r( (Encoded CBOR Sequence ))r" }, - { QCborTag(64), - R"r( (uint8 Typed Array [RFC8746]))r" }, - { QCborTag(65), - R"r( (uint16, big endian, Typed Array [RFC8746]))r" }, - { QCborTag(66), - R"r( (uint32, big endian, Typed Array [RFC8746]))r" }, - { QCborTag(67), - R"r( (uint64, big endian, Typed Array [RFC8746]))r" }, - { QCborTag(68), - R"r( (uint8 Typed Array, clamped arithmetic [RFC8746]))r" }, - { QCborTag(69), - R"r( (uint16, little endian, Typed Array [RFC8746]))r" }, - { QCborTag(70), - R"r( (uint32, little endian, Typed Array [RFC8746]))r" }, - { QCborTag(71), - R"r( (uint64, little endian, Typed Array [RFC8746]))r" }, - { QCborTag(72), - R"r( (sint8 Typed Array [RFC8746]))r" }, - { QCborTag(73), - R"r( (sint16, big endian, Typed Array [RFC8746]))r" }, - { QCborTag(74), - R"r( (sint32, big endian, Typed Array [RFC8746]))r" }, - { QCborTag(75), - R"r( (sint64, big endian, Typed Array [RFC8746]))r" }, - { QCborTag(76), - R"r( ((reserved) [RFC8746]))r" }, - { QCborTag(77), - R"r( (sint16, little endian, Typed Array [RFC8746]))r" }, - { QCborTag(78), - R"r( (sint32, little endian, Typed Array [RFC8746]))r" }, - { QCborTag(79), - R"r( (sint64, little endian, Typed Array [RFC8746]))r" }, - { QCborTag(80), - R"r( (IEEE 754 binary16, big endian, Typed Array [RFC8746]))r" }, - { QCborTag(81), - R"r( (IEEE 754 binary32, big endian, Typed Array [RFC8746]))r" }, - { QCborTag(82), - R"r( (IEEE 754 binary64, big endian, Typed Array [RFC8746]))r" }, - { QCborTag(83), - R"r( (IEEE 754 binary128, big endian, Typed Array [RFC8746]))r" }, - { QCborTag(84), - R"r( (IEEE 754 binary16, little endian, Typed Array [RFC8746]))r" }, - { QCborTag(85), - R"r( (IEEE 754 binary32, little endian, Typed Array [RFC8746]))r" }, - { QCborTag(86), - R"r( (IEEE 754 binary64, little endian, Typed Array [RFC8746]))r" }, - { QCborTag(87), - R"r( (IEEE 754 binary128, little endian, Typed Array [RFC8746]))r" }, - { QCborTag(96), - R"r( (COSE Encrypted Data Object [RFC9052]))r" }, - { QCborTag(97), - R"r( (COSE MACed Data Object [RFC9052]))r" }, - { QCborTag(98), - R"r( (COSE Signed Data Object [RFC9052]))r" }, - { QCborTag(100), - R"r( (Number of days since the epoch date 1970-01-01 [RFC8943]))r" }, - { QCborTag(101), - R"r( (alternatives as given by the uint + 128; see Section 9.1))r" }, - { QCborTag(103), - R"r( (Geographic Coordinates))r" }, - { QCborTag(104), - R"r( (Geographic Coordinate Reference System WKT or EPSG number))r" }, - { QCborTag(110), - R"r( (relative object identifier (BER encoding); SDNV sequence [RFC9090]))r" }, - { QCborTag(111), - R"r( (object identifier (BER encoding) [RFC9090]))r" }, - { QCborTag(112), - R"r( (object identifier (BER encoding), relative to 1.3.6.1.4.1 [RFC9090]))r" }, - { QCborTag(120), - R"r( (Internet of Things Data Point))r" }, + " (Serialised language-independent object with type name and constructor arguments)" }, + { QCborTag(28), " (mark value as (potentially) shared)" }, + { QCborTag(29), " (reference nth marked value)" }, + { QCborTag(30), " (Rational number)" }, + { QCborTag(31), " (Absent value in a CBOR Array)" }, + { QCborTag(32), " (URI; see Section 3.4.5.3 [RFC8949])" }, + { QCborTag(33), " (base64url; see Section 3.4.5.3 [RFC8949])" }, + { QCborTag(34), " (base64; see Section 3.4.5.3 [RFC8949])" }, + { QCborTag(35), " (Regular expression; see Section 2.4.4.3 [RFC7049])" }, + { QCborTag(36), " (MIME message; see Section 3.4.5.3 [RFC8949])" }, + { QCborTag(37), " (Binary UUID [RFC4122, Section 4.1.2])" }, + { QCborTag(38), " (Language-tagged string [RFC9290, Appendix A])" }, + { QCborTag(39), " (Identifier)" }, + { QCborTag(40), " (Multi-dimensional Array, row-major order [RFC8746])" }, + { QCborTag(41), " (Homogeneous Array [RFC8746])" }, + { QCborTag(42), " (IPLD content identifier)" }, + { QCborTag(43), " (YANG bits datatype; see Section 6.7. [RFC9254])" }, + { QCborTag(44), " (YANG enumeration datatype; see Section 6.6. [RFC9254])" }, + { QCborTag(45), " (YANG identityref datatype; see Section 6.10. [RFC9254])" }, + { QCborTag(46), " (YANG instance-identifier datatype; see Section 6.13. [RFC9254])" }, + { QCborTag(47), " (YANG Schema Item iDentifier (sid); see Section 3.2. [RFC9254])" }, + { QCborTag(52), " (IPv4, [prefixlen,IPv4], [IPv4,prefixpart] [RFC9164])" }, + { QCborTag(54), " (IPv6, [prefixlen,IPv6], [IPv6,prefixpart] [RFC9164])" }, + { QCborTag(61), " (CBOR Web Token (CWT) [RFC8392])" }, + { QCborTag(63), " (Encoded CBOR Sequence [RFC8742])" }, + { QCborTag(64), " (uint8 Typed Array [RFC8746])" }, + { QCborTag(65), " (uint16, big endian, Typed Array [RFC8746])" }, + { QCborTag(66), " (uint32, big endian, Typed Array [RFC8746])" }, + { QCborTag(67), " (uint64, big endian, Typed Array [RFC8746])" }, + { QCborTag(68), " (uint8 Typed Array, clamped arithmetic [RFC8746])" }, + { QCborTag(69), " (uint16, little endian, Typed Array [RFC8746])" }, + { QCborTag(70), " (uint32, little endian, Typed Array [RFC8746])" }, + { QCborTag(71), " (uint64, little endian, Typed Array [RFC8746])" }, + { QCborTag(72), " (sint8 Typed Array [RFC8746])" }, + { QCborTag(73), " (sint16, big endian, Typed Array [RFC8746])" }, + { QCborTag(74), " (sint32, big endian, Typed Array [RFC8746])" }, + { QCborTag(75), " (sint64, big endian, Typed Array [RFC8746])" }, + { QCborTag(76), " ((reserved) [RFC8746])" }, + { QCborTag(77), " (sint16, little endian, Typed Array [RFC8746])" }, + { QCborTag(78), " (sint32, little endian, Typed Array [RFC8746])" }, + { QCborTag(79), " (sint64, little endian, Typed Array [RFC8746])" }, + { QCborTag(80), " (IEEE 754 binary16, big endian, Typed Array [RFC8746])" }, + { QCborTag(81), " (IEEE 754 binary32, big endian, Typed Array [RFC8746])" }, + { QCborTag(82), " (IEEE 754 binary64, big endian, Typed Array [RFC8746])" }, + { QCborTag(83), " (IEEE 754 binary128, big endian, Typed Array [RFC8746])" }, + { QCborTag(84), " (IEEE 754 binary16, little endian, Typed Array [RFC8746])" }, + { QCborTag(85), " (IEEE 754 binary32, little endian, Typed Array [RFC8746])" }, + { QCborTag(86), " (IEEE 754 binary64, little endian, Typed Array [RFC8746])" }, + { QCborTag(87), " (IEEE 754 binary128, little endian, Typed Array [RFC8746])" }, + { QCborTag(96), " (COSE Encrypted Data Object [RFC9052])" }, + { QCborTag(97), " (COSE MACed Data Object [RFC9052])" }, + { QCborTag(98), " (COSE Signed Data Object [RFC9052])" }, + { QCborTag(100), " (Number of days since the epoch date 1970-01-01 [RFC8943])" }, + { QCborTag(101), " (alternatives as given by the uint + 128; see Section 9.1)" }, + { QCborTag(103), " (Geographic Coordinates)" }, + { QCborTag(104), " (Geographic Coordinate Reference System WKT or EPSG number)" }, + { QCborTag(110), " (relative object identifier (BER encoding); SDNV sequence [RFC9090])" }, + { QCborTag(111), " (object identifier (BER encoding) [RFC9090])" }, + { QCborTag(112), " (object identifier (BER encoding), relative to 1.3.6.1.4.1 [RFC9090])" }, + { QCborTag(120), " (Internet of Things Data Point)" }, { QCborTag(260), - R"r( (Network Address (IPv4 or IPv6 or MAC Address) (DEPRECATED in favor of 52 and 54 - for IP addresses) [http://www.employees.oRg/~RaviR/CboR-netwoRk.txt]))r" }, + " (Network Address (IPv4 or IPv6 or MAC Address) (DEPRECATED in favor of 52 and 54 for IP" + " addresses) [RFC9164])" }, { QCborTag(261), - R"r( (Network Address Prefix (IPv4 or IPv6 Address + Mask Length) (DEPRECATED in favor of 52 and 54 - for IP addresses) [https://github.Com/toRaviR/CBOR-Tag-SpeCs/blob/masteR/netwoRkPReFix.md]))r" }, + " (Network Address Prefix (IPv4 or IPv6 Address + Mask Length) (DEPRECATED in favor of 52" + " and 54 for IP addresses) [RFC9164])" }, { QCborTag(271), - R"r( (DDoS Open Threat Signaling (DOTS) signal channel object, - as defined in [RFC9132]))r" }, - { QCborTag(1004), - R"r( ( full-date string [RFC8943]))r" }, - { QCborTag(1040), - R"r( (Multi-dimensional Array, column-major order [RFC8746]))r" }, - { QCborTag(55799), - R"r( (Self-described CBOR; see Section 3.4.6 [RFC8949]))r" }, - { QCborTag(55800), - R"r( (indicates that the file contains CBOR Sequences [RFC9277]))r" }, + " (DDoS Open Threat Signaling (DOTS) signal channel object, as defined in [RFC9132])" }, + { QCborTag(1004), " (full-date string [RFC8943])" }, + { QCborTag(1040), " (Multi-dimensional Array, column-major order [RFC8746])" }, + { QCborTag(55799), " (Self-described CBOR; see Section 3.4.6 [RFC8949])" }, + { QCborTag(55800), " (indicates that the file contains CBOR Sequences [RFC9277])" }, { QCborTag(55801), - R"r( (indicates that the file starts with a CBOR-Labeled Non-CBOR Data label. [RFC9277]))r" }, + " (indicates that the file starts with a CBOR-Labeled Non-CBOR Data label. [RFC9277])" }, { QCborTag(-1), nullptr } }; // END GENERATED CODE diff --git a/examples/corelib/serialization/cbordump/tag-transform.xslt b/examples/corelib/serialization/cbordump/tag-transform.xslt deleted file mode 100644 index ad0bba98..00000000 --- a/examples/corelib/serialization/cbordump/tag-transform.xslt +++ /dev/null @@ -1,27 +0,0 @@ - - - -struct CborTagDescription -{ - QCborTag tag; - const char *description; // with space and parentheses -}; - -// -static const CborTagDescription tagDescriptions[] = { - // from https://www.iana.org/assignments/cbor-tags/cbor-tags.xhtml - - - - - - { QCborTag(-1), nullptr } -}; - - { QCborTag(), - R"r( ( ))r" }, - - [] - - diff --git a/examples/corelib/serialization/savegame/doc/src/savegame.qdoc b/examples/corelib/serialization/savegame/doc/src/savegame.qdoc index 36f02f98..c3fc9157 100644 --- a/examples/corelib/serialization/savegame/doc/src/savegame.qdoc +++ b/examples/corelib/serialization/savegame/doc/src/savegame.qdoc @@ -35,8 +35,8 @@ serializing to another format — for example XML or QDataStream, which require passing a document-like object — or when the object identity is important (QObject subclasses, for example), other patterns may be more suitable. See the - \l{xml/dombookmarks} and \l{xml/streambookmarks} examples for XML, and the - implementation of \l QListWidgetItem::read() and \l QListWidgetItem::write() + \l{dombookmarks} example for XML, and the implementation of + \l QListWidgetItem::read() and \l QListWidgetItem::write() for idiomatic QDataStream serialization. The \c{print()} functions in this example are good examples of QTextStream serialization, even though they, of course, lack the deserialization side. diff --git a/examples/corelib/threads/semaphores/semaphores.cpp b/examples/corelib/threads/semaphores/semaphores.cpp index 103f331b..5a495551 100644 --- a/examples/corelib/threads/semaphores/semaphores.cpp +++ b/examples/corelib/threads/semaphores/semaphores.cpp @@ -7,9 +7,9 @@ #include //! [0] -constexpr int DataSize = 100000; +const int DataSize = 100000; -constexpr int BufferSize = 8192; +const int BufferSize = 8192; char buffer[BufferSize]; QSemaphore freeBytes(BufferSize); diff --git a/examples/embedded/digiflip/digiflip.cpp b/examples/embedded/digiflip/digiflip.cpp index 7497f04e..ab5075a4 100644 --- a/examples/embedded/digiflip/digiflip.cpp +++ b/examples/embedded/digiflip/digiflip.cpp @@ -23,7 +23,8 @@ public: { setAttribute(Qt::WA_OpaquePaintEvent, true); setAttribute(Qt::WA_NoSystemBackground, true); - connect(&m_animator, SIGNAL(frameChanged(int)), SLOT(update())); + connect(&m_animator, &QTimeLine::frameChanged, + this, qOverload<>(&Digits::update)); m_animator.setFrameRange(0, 100); m_animator.setDuration(600); m_animator.setEasingCurve(QEasingCurve::InOutSine); @@ -272,9 +273,9 @@ public: QAction *slideAction = new QAction("&Slide", this); QAction *flipAction = new QAction("&Flip", this); QAction *rotateAction = new QAction("&Rotate", this); - connect(slideAction, SIGNAL(triggered()), SLOT(chooseSlide())); - connect(flipAction, SIGNAL(triggered()), SLOT(chooseFlip())); - connect(rotateAction, SIGNAL(triggered()), SLOT(chooseRotate())); + connect(slideAction, &QAction::triggered, this, &DigiFlip::chooseSlide); + connect(flipAction, &QAction::triggered, this, &DigiFlip::chooseFlip); + connect(rotateAction, &QAction::triggered, this, &DigiFlip::chooseRotate); addAction(slideAction); addAction(flipAction); addAction(rotateAction); diff --git a/examples/embedded/flightinfo/flightinfo.cpp b/examples/embedded/flightinfo/flightinfo.cpp index 96a3e272..f9d4b6e0 100644 --- a/examples/embedded/flightinfo/flightinfo.cpp +++ b/examples/embedded/flightinfo/flightinfo.cpp @@ -71,8 +71,8 @@ public: ui.searchBar->hide(); ui.infoBox->hide(); - connect(ui.searchButton, SIGNAL(clicked()), SLOT(startSearch())); - connect(ui.flightEdit, SIGNAL(returnPressed()), SLOT(startSearch())); + connect(ui.searchButton, &QPushButton::clicked, this, &FlightInfo::startSearch); + connect(ui.flightEdit, &QLineEdit::returnPressed, this, &FlightInfo::startSearch); setWindowTitle("Flight Info"); @@ -83,11 +83,11 @@ public: QAction *searchTodayAction = new QAction("Today's Flight", this); QAction *searchYesterdayAction = new QAction("Yesterday's Flight", this); QAction *randomAction = new QAction("Random Flight", this); - connect(searchTodayAction, SIGNAL(triggered()), SLOT(today())); - connect(searchYesterdayAction, SIGNAL(triggered()), SLOT(yesterday())); - connect(randomAction, SIGNAL(triggered()), SLOT(randomFlight())); - connect(&m_manager, SIGNAL(finished(QNetworkReply*)), - this, SLOT(handleNetworkData(QNetworkReply*))); + connect(searchTodayAction, &QAction::triggered, this, &FlightInfo::today); + connect(searchYesterdayAction, &QAction::triggered, this, &FlightInfo::yesterday); + connect(randomAction, &QAction::triggered, this, &FlightInfo::randomFlight); + connect(&m_manager, &QNetworkAccessManager::finished, + this, &FlightInfo::handleNetworkData); addAction(searchTodayAction); addAction(searchYesterdayAction); addAction(randomAction); diff --git a/examples/embedded/lightmaps/lightmaps.cpp b/examples/embedded/lightmaps/lightmaps.cpp index 5f62f926..566ba243 100644 --- a/examples/embedded/lightmaps/lightmaps.cpp +++ b/examples/embedded/lightmaps/lightmaps.cpp @@ -24,8 +24,8 @@ LightMaps::LightMaps(QWidget *parent) { m_normalMap = new SlippyMap(this); m_largeMap = new SlippyMap(this); - connect(m_normalMap, SIGNAL(updated(QRect)), SLOT(updateMap(QRect))); - connect(m_largeMap, SIGNAL(updated(QRect)), SLOT(update())); + connect(m_normalMap, &SlippyMap::updated, this, &LightMaps::updateMap); + connect(m_largeMap, &SlippyMap::updated, this, &LightMaps::updateMap); } void LightMaps::setCenter(qreal lat, qreal lng) diff --git a/examples/gui/CMakeLists.txt b/examples/gui/CMakeLists.txt index 8eb3981a..8eda006a 100644 --- a/examples/gui/CMakeLists.txt +++ b/examples/gui/CMakeLists.txt @@ -5,3 +5,4 @@ if(NOT TARGET Qt6::Gui) return() endif() qt_internal_add_example(rasterwindow) +qt_internal_add_example(rhiwindow) diff --git a/examples/gui/doc/images/rhiwindow_example.jpg b/examples/gui/doc/images/rhiwindow_example.jpg new file mode 100644 index 00000000..ca1e44ba Binary files /dev/null and b/examples/gui/doc/images/rhiwindow_example.jpg differ diff --git a/examples/gui/doc/src/rhiwindow.qdoc b/examples/gui/doc/src/rhiwindow.qdoc new file mode 100644 index 00000000..1dd5ffb7 --- /dev/null +++ b/examples/gui/doc/src/rhiwindow.qdoc @@ -0,0 +1,440 @@ +// Copyright (C) 2023 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GFDL-1.3-no-invariants-only + +/*! + \example rhiwindow + \title RHI Window Example + \examplecategory {Graphics & Multimedia} + + \brief This example shows how to create a minimal QWindow-based + application using QRhi. + + \image rhiwindow_example.jpg + + Qt 6.6 starts offering its accelerated 3D API and shader abstraction layer + for application use as well. Applications can now use the same 3D graphics + classes Qt itself uses to implement the Qt Quick scenegraph or the Qt Quick + 3D engine. In earlier Qt versions QRhi and the related classes were all + private APIs. From 6.6 on these classes are in a similar category as QPA + family of classes: neither fully public nor private, but something + in-between, with a more limited compatibility promise compared to public + APIs. On the other hand, QRhi and the related classes now come with full + documentation similarly to public APIs. + + There are multiple ways to use QRhi, the example here shows the most + low-level approach: targeting a QWindow, while not using Qt Quick, Qt Quick + 3D, or Widgets in any form, and setting up all the rendering and windowing + infrastructure in the application. + + In contrast, when writing a QML application with Qt Quick or Qt Quick 3D, + and wanting to add QRhi-based rendering to it, such an application is going + to rely on the window and rendering infrastructure Qt Quick has already + initialized, and it is likely going to query an existing QRhi instance from + the QQuickWindow. There dealing with QRhi::create(), platform/API specifics + such as \l{QVulkanInstance}{Vulkan instances}, or correctly handling + \l{QExposeEvent}{expose} and resize events for the window are all managed + by Qt Quick. Whereas in this example, all that is managed and taken care + of by the application itself. + + \note For QWidget-based applications in particular, it should be noted that + QWidget::createWindowContainer() allows embedding a QWindow (backed by a + native window) into the widget-based user interface. Therefore, the \c + HelloWindow class from this example is reusable in QWidget-based + applications, assuming the necessary initialization from \c main() is in + place as well. + + \section1 3D API Support + + The application supports all the current \l{QRhi::Implementation}{QRhi + backends}. When no command-line arguments are specified, platform-specific + defaults are used: Direct 3D 11 on Windows, OpenGL on Linux, Metal on + macOS/iOS. + + Running with \c{--help} shows the available command-line options: + + \list + \li -d or --d3d11 for Direct 3D 11 + \li -D or --d3d12 for Direct 3D 12 + \li -m or --metal for Metal + \li -v or --vulkan for Vulkan + \li -g or --opengl for OpenGL or OpenGL ES + \li -n or --null for the \l{QRhi::Null}{Null backend} + \endlist + + \section1 Build System Notes + + This application relies solely on the Qt GUI module. It does not use Qt + Widgets or Qt Quick. + + In order to access the RHI APIs, which are available to all Qt applications + but come with a limited compatibility promise, the \c target_link_libraries + CMake command lists \c{Qt6::GuiPrivate}. This is what enables the + \c{#include } include statement to compile successfully. + + \section1 Features + + The application features: + + \list + + \li A resizable QWindow, + + \li a swapchain and depth-stencil buffer that properly follows the size of + the window, + + \li logic to initialize, render, and tear down at the appropriate time + based on events such as \l QExposeEvent and \l QPlatformSurfaceEvent, + + \li rendering a fullscreen textured quad, using a texture the contents of + which is generated in a QImage via QPainter (using the raster paint engine, + i.e. the generating of the image's pixel data is all CPU-based, that data + is then uploaded into a GPU texture), + + \li rendering a triangle with blending and depth testing enabled, using a + perspective projection, while applying a model transform that changes on + every frame, + + \li an efficient, cross-platform render loop using + \l{QWindow::requestUpdate()}{requestUpdate()}. + + \endlist + + \section1 Shaders + + The application uses two sets of vertex and fragment shader pairs: + + \list + + \li one for the fullscreen quad, which uses no vertex inputs and the + fragment shader samples a texture (\c quad.vert, \c quad.frag), + + \li and another pair for the triangle, where vertex positions and colors + are provided in a vertex buffer and a modelview-projection matrix is + provided in a uniform buffer (\c color.vert, \c color.frag). + + \endlist + + The shaders are written as Vulkan-compatible GLSL source code. + + Due to being a Qt GUI module example, this example cannot have a dependency + on the \l{Qt Shader Tools} module. This means that CMake helper functions + such as \c{qt_add_shaders} are not available for use. Therefore, the + example has the pre-processed \c{.qsb} files included in the + \c{shaders/prebuilt} folder, and they are simply included within the + executable via \c{qt_add_resources}. This approach is not generally + recommended for applications, consider rather using \l{Qt Shader Tools + Build System Integration}{qt_add_shaders}, which avoids the need to + manually generate and manage the \c{.qsb} files. + + To generate the \c{.qsb} files for this example, the command \c{qsb --qt6 + color.vert -o prebuilt/color.vert.qsb} etc. was used. This leads to + compiling to \l{https://www.khronos.org/spir/}{SPIR-V} and than transpiling + into GLSL (\c{100 es} and \c 120), HLSL (5.0), and MSL (1.2). All the + shader versions are then packed together into a QShader and serialized to + disk. + + \section1 API-specific Initialization + + For some of the 3D APIs the main() function has to perform the appropriate + API-specific initialiation, e.g. to create a QVulkanInstance when using + Vulkan. For OpenGL we have to ensure a depth buffer is available, this is + done via QSurfaceFormat. These steps are not in the scope of QRhi since + QRhi backends for OpenGL or Vulkan build on the existing Qt facilities such + as QOpenGLContext or QVulkanInstance. + + \snippet rhiwindow/main.cpp api-setup + + \note For Vulkan, note how + QRhiVulkanInitParams::preferredInstanceExtensions() is taken into account + to ensure the appropriate extensions are enabled. + + \c HelloWindow is a subclass of \c RhiWindow, which in turn is a QWindow. + \c RhiWindow contains everything needed to manage a resizable window with + a\ swapchain (and depth-stencil buffer), and is potentially reusable in + other applications as well. \c HelloWindow contains the rendering logic + specific to this particular example application. + + In the QWindow subclass constructor the surface type is set based on the + selected 3D API. + + \snippet rhiwindow/rhiwindow.cpp rhiwindow-ctor + + Creating and initializing a QRhi object is implemented in + RhiWindow::init(). Note that this is invoked only when the window is + \c renderable, which is indicated by an \l{QExposeEvent}{expose event}. + + Depending on which 3D API we use, the appropriate InitParams struct needs + to be passed to QRhi::create(). With OpenGL for example, a + QOffscreenSurface (or some other QSurface) must be created by the + application and provided for use to the QRhi. With Vulkan, a successfully + initialized QVulkanInstance is required. Others, such as Direct 3D or Metal + need no additional information to be able to initialize. + + \snippet rhiwindow/rhiwindow.cpp rhi-init + + Apart from this, everything else, all the rendering code, is fully + cross-platform and has no branching or conditions specific to any of the 3D + API. + + \section1 Expose Events + + What \c renderable exactly means is platform-specific. For example, on + macOS a window that is fully obscured (fully behind some other window) is + not renderable, whereas on Windows obscuring has no significance. + Fortunately, the application needs no special knowledge about this: Qt's + platform plugins abstract the differences behind the expose event. However, + the \l{QWindow::exposeEvent()}{exposeEvent()} reimplementation also needs + to be aware that an empty output size (e.g. width and height of 0) is also + something that should be treated as a non-renderable situation. On Windows + for example, this is what is going to happen when minimizing the window. + Hence the check based on QRhiSwapChain::surfacePixelSize(). + + This implementation of expose event handling attempts to be robust, safe, + and portable. Qt Quick itself also implements a very similar logic in its + render loops. + + \snippet rhiwindow/rhiwindow.cpp expose + + In RhiWindow::render(), which is invoked in response to the + \l{QEvent::UpdateRequest}{UpdateRequest} event generated by + \l{QWindow::requestUpdate()}{requestUpdate()}, the following check is in + place, to prevent attempting to render when the swapchain initialization + failed, or when the window became non-renderable. + + \snippet rhiwindow/rhiwindow.cpp render-precheck + + \section1 Swapchain, Depth-Stencil buffer, and Resizing + + To render to the QWindow, a QRhiSwapChain is needed. In addition, a + QRhiRenderBuffer acting as the depth-stencil buffer is created as well + since the application demonstrates how depth testing can be enabled in a + graphics pipeline. With some legacy 3D APIs managing the depth/stencil + buffer for a window is part of the corresponding windowing system interface + API (EGL, WGL, GLX, etc., meaning the depth/stencil buffer is implicitly + managed together with the \c{window surface}), whereas with modern APIs + managing the depth-stencil buffer for a window-based render target is no + different from offscreen render targets. QRhi abstracts this, but for best + performance it still needs to be indicated that the QRhiRenderBuffer is + \l{QRhiRenderBuffer::UsedWithSwapChainOnly}{used with together with a + QRhiSwapChain}. + + The QRhiSwapChain is associated with the QWindow and the depth/stencil + buffer. + + \snippet rhiwindow/rhiwindow.h swapchain-data + \codeline + \snippet rhiwindow/rhiwindow.cpp swapchain-init + + When the window size changes, the swapchain needs to be resized as well. + This is implemented in resizeSwapChain(). + + \snippet rhiwindow/rhiwindow.cpp swapchain-resize + + Unlike other QRhiResource subclasses, QRhiSwapChain features slightly + different semantics when it comes to its create-function. As the name, + \l{QRhiSwapChain::createOrResize()}{createOrResize()}, suggests, this needs + to be called whenever it is known that the output window size may be out of + sync with what the swapchain was last initialized. The associated + QRhiRenderBuffer for depth-stencil gets its + \l{QRhiRenderBuffer::pixelSize()}{size} set automatically, and + \l{QRhiRenderBuffer::create()}{create()} is called on it implicitly from the + swapchain's createOrResize(). + + This is also a convenient place to (re)calculate the projection and view + matrices since the perspective projection we set up depends on the output + aspect ratio. + + \note To eliminate coordinate system differences, the + \l{QRhi::clipSpaceCorrMatrix()}{a backend/API-specific "correction" matrix} + is queried from QRhi and baked in to the projection matrix. This is what + allows the application to work with OpenGL-style vertex data, assuming a + coordinate system with the origin at the bottom-left. + + The resizeSwapChain() function is called from RhiWindow::render() when it + is discovered that the currently reported size is not the same anymore as + what the swapchain was last initialized with. + + See QRhiSwapChain::currentPixelSize() and QRhiSwapChain::surfacePixelSize() + for further details. + + High DPI support is built-in: the sizes, as the naming indicates, are + always in pixels, taking the window-specific + \l{QWindow::devicePixelRatio()}{scale factor} into account. On the QRhi + (and 3D API) level there is no concept of high DPI scaling, everything is + always in pixels. This means that a QWindow with a size() of 1280x720 and + a devicePixelRatio() of 2 is a render target (swapchain) with a (pixel) size + of 2560x1440. + + \snippet rhiwindow/rhiwindow.cpp render-resize + + \section1 Render Loop + + The application renders continuously, throttled by the presentation rate + (vsync). This is ensured by calling + \l{QWindow::requestUpdate()}{requestUpdate()} from RhiWindow::render() when + the currently recorded frame has been submitted. + + \snippet rhiwindow/rhiwindow.cpp request-update + + This eventually leads to getting a \l{QEvent::UpdateRequest}{UpdateRequest} + event. This is handled in the reimplementation of event(). + + \snippet rhiwindow/rhiwindow.cpp event + + \section1 Resource and Pipeline Setup + + The application records a single render pass that issues two draw calls, + with two different graphics pipelines. One is the "background", with the + texture containing the QPainter-generated image, then a single triangle is + rendered on top with blending enabled. + + The vertex and uniform buffer used with the triangle is created like this. + The size of the uniform buffer is 68 bytes since the shader specific a \c + mat4 and a \c float member in the uniform block. Watch out for the + \l{https://registry.khronos.org/OpenGL/specs/gl/glspec45.core.pdf#page=159}{std140 + layout rules}. This presents no surprises in this example since the \c + float member that follows the \c mat4 has the correct alignment without any + additional padding, but it may become relevant in other applications, + especially when working with types such as \c vec2 or \c vec3. When in + doubt, consider checking the QShaderDescription for the + \l{QShader::description()}{QShader}, or, what is often more convenient, run + the \c qsb tool on the \c{.qsb} file with the \c{-d} argument to inspect + the metadata in human-readable form. The printed information includes, + among other things, the uniform block member offsets, sizes, and the total + size in bytes of each uniform block. + + \snippet rhiwindow/rhiwindow.cpp render-init-1 + + The vertex and fragment shaders both need a uniform buffer at binding point + 0. This is ensured by the QRhiShaderResourceBindings object. The graphics + pipeline is then setup with the shaders and a number of additional + information. The example also relies on a number of convenient defaults, + e.g. the primitive topology is + \l{QRhiGraphicsPipeline::Triangles}{Triangles}, but that is the default, + and therefore it is not explicitly set. See QRhiGraphicsPipeline for + further details. + + In addition to specifying the topology and various state, the pipeline must + also be associated with: + + \list + + \li The vertex input layout in form of a QRhiVertexInputLayout. This + specifies the type and component count for each vertex input location, the + total stride in bytes per vertex, and other related data. + QRhiVertexInputLayout only holds data, not actual native resources, and is + copiable. + + \li A valid and successfully initialized QRhiShaderResourceBindings object. + This describes the layout of the resource bindings (uniform buffers, + textures, samplers) the shaders expect. This must either by the + QRhiShaderResourceBindings used when recording the draw calls, or another + that is + \l{QRhiShaderResourceBindings::isLayoutCompatible()}{layout-compatible with it}. + This simple application takes the former approach. + + \li A valid QRhiRenderPassDescriptor object. This must be retrieved from, + or \l{QRhiRenderPassDescriptor::isCompatible()}{be compatible with} the + render target. The example uses the former, by creating a + QRhiRenderPassDescriptor object via + QRhiSwapChain::newCompatibleRenderPassDescriptor(). + + \endlist + + \snippet rhiwindow/rhiwindow.cpp render-init-2 + + getShader() is a helper function that loads a \c{.qsb} file and + deserializes a QShader from it. + + \snippet rhiwindow/rhiwindow.cpp getshader + + The \c{color.vert} shader specifies the following as the vertex inputs: + + \badcode + layout(location = 0) in vec4 position; + layout(location = 1) in vec3 color; + \endcode + + The C++ code however provides vertex data as 2 floats for position, with 3 + floats for the color interleaved. (\c x, \c y, \c r, \c g, \c b for each + vertex) This is why the stride is \c{5 * sizeof(float)} and the inputs for + locations 0 and 1 are specified as \c Float2 and \c Float3, respectively. + This is valid, and the \c z and \c w of the \c vec4 position will get set + automatically. + + \section1 Rendering + + Recording a frame is started by calling \l{QRhi::beginFrame()} and finished + by calling \l{QRhi::endFrame()}. + + \snippet rhiwindow/rhiwindow.cpp beginframe + + Some of the resources (buffers, textures) have static data in the + application, meaning the content never changes. The vertex buffer's content + is provided in the initialization step for example, and is not changed + afterwards. These data update operations are recorded in \c + m_initialUpdates. When not yet done, the commands on this resource update + batch are merged into the per-frame batch. + + \snippet rhiwindow/rhiwindow.cpp render-1 + + Having a per-frame resource update batch is necessary since the uniform + buffer contents with the modelview-projection matrix and the opacity + changes on every frame. + + \snippet rhiwindow/rhiwindow.cpp render-rotation + + \snippet rhiwindow/rhiwindow.cpp render-opacity + + To begin recording the render pass, a QRhiCommandBuffer is queried, and the + output size is determined, which will be useful for setting up the viewport + and resizing our fullscreen texture, if needed. + + \snippet rhiwindow/rhiwindow.cpp render-cb + + Starting a render pass implies clearing the render target's color and + depth-stencil buffers (unless the render target flags indicate otherwise, + but that is only an option for texture-based render targets). Here we + specify black for color, 1.0f for depth, and 0 for stencil (unused). The + last argument, \c resourceUpdates, is what ensures that the data update + commands recorded on the batch get committed. Alternatively, we could have + used QRhiCommandBuffer::resourceUpdate() instead. The render pass targets a + swapchain, hence calling + \l{QRhiSwapChain::currentFrameRenderTarget()}{currentFrameRenderTarget()} + to get a valid QRhiRenderTarget. + + \snippet rhiwindow/rhiwindow.cpp render-pass + + Recording the draw call for the triangle is straightforward: set the + pipeline, set the shader resources, set the vertex/index buffer(s), and + record the draw call. Here we use a non-indexed draw with just 3 vertices. + + \snippet rhiwindow/rhiwindow.cpp render-pass-record + + The \l{QRhiCommandBuffer::setShaderResources()}{setShaderResources()} call + has no arguments given, which implies using \c m_colorTriSrb since that was + associated with the active QRhiGraphicsPipeline (\c m_colorPipeline). + + We will not dive into the details of the rendering of the fullscreen + background image. See the example source code for that. It is however worth + noting a common pattern for "resizing" a texture or buffer resource. There + is no such thing as changing the size of an existing native resource, so + changing a texture or buffer size must be followed by a call to create(), + to release and recreate the underlying native resources. To ensure that the + QRhiTexture always has the required size, the application implements the + following logic. Note that \c m_texture stays valid for the entire lifetime + of the window, which means object references to it, e.g. in a + QRhiShaderResourceBindings, continue to be valid all the time. It is only + the underlying native resources that come and go over time. + + \snippet rhiwindow/rhiwindow.cpp ensure-texture + + Once a QImage is generated and the QPainter-based drawing into it has + finished, we use + \l{QRhiResourceUpdateBatch::uploadTexture()}{uploadTexture()} to record a + texture upload on the resource update batch: + + \snippet rhiwindow/rhiwindow.cpp ensure-texture-2 + + \sa QRhi, QRhiSwapChain, QWindow, QRhiCommandBuffer, QRhiResourceUpdateBatch, QRhiBuffer, QRhiTexture + */ diff --git a/examples/gui/gui.pro b/examples/gui/gui.pro index 2afb8bbf..0696458d 100644 --- a/examples/gui/gui.pro +++ b/examples/gui/gui.pro @@ -4,4 +4,5 @@ TEMPLATE = subdirs QT_FOR_CONFIG += gui CONFIG += no_docs_target -SUBDIRS += rasterwindow +SUBDIRS += rasterwindow \ + rhiwindow diff --git a/examples/gui/rhiwindow/CMakeLists.txt b/examples/gui/rhiwindow/CMakeLists.txt new file mode 100644 index 00000000..3c50add1 --- /dev/null +++ b/examples/gui/rhiwindow/CMakeLists.txt @@ -0,0 +1,59 @@ +# Copyright (C) 2023 The Qt Company Ltd. +# SPDX-License-Identifier: BSD-3-Clause + +cmake_minimum_required(VERSION 3.16) +project(rhiwindow LANGUAGES CXX) + +if(NOT DEFINED INSTALL_EXAMPLESDIR) + set(INSTALL_EXAMPLESDIR "examples") +endif() + +set(INSTALL_EXAMPLEDIR "${INSTALL_EXAMPLESDIR}/gui/rhiwindow") + +find_package(Qt6 REQUIRED COMPONENTS Core Gui) + +qt_standard_project_setup() + +qt_add_executable(rhiwindow + main.cpp + rhiwindow.cpp rhiwindow.h +) + +set_target_properties(rhiwindow PROPERTIES + WIN32_EXECUTABLE TRUE + MACOSX_BUNDLE TRUE +) + +target_link_libraries(rhiwindow PRIVATE + Qt6::Core + Qt6::Gui + Qt6::GuiPrivate +) + +set_source_files_properties("shaders/prebuilt/color.vert.qsb" + PROPERTIES QT_RESOURCE_ALIAS "color.vert.qsb" +) +set_source_files_properties("shaders/prebuilt/color.frag.qsb" + PROPERTIES QT_RESOURCE_ALIAS "color.frag.qsb" +) +set_source_files_properties("shaders/prebuilt/quad.vert.qsb" + PROPERTIES QT_RESOURCE_ALIAS "quad.vert.qsb" +) +set_source_files_properties("shaders/prebuilt/quad.frag.qsb" + PROPERTIES QT_RESOURCE_ALIAS "quad.frag.qsb" +) +qt_add_resources(rhiwindow "rhiwindow" + PREFIX + "/" + FILES + "shaders/prebuilt/color.vert.qsb" + "shaders/prebuilt/color.frag.qsb" + "shaders/prebuilt/quad.vert.qsb" + "shaders/prebuilt/quad.frag.qsb" +) + +install(TARGETS rhiwindow + RUNTIME DESTINATION "${INSTALL_EXAMPLEDIR}" + BUNDLE DESTINATION "${INSTALL_EXAMPLEDIR}" + LIBRARY DESTINATION "${INSTALL_EXAMPLEDIR}" +) diff --git a/examples/gui/rhiwindow/main.cpp b/examples/gui/rhiwindow/main.cpp new file mode 100644 index 00000000..6ca38e7b --- /dev/null +++ b/examples/gui/rhiwindow/main.cpp @@ -0,0 +1,110 @@ +// Copyright (C) 2023 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause + +#include +#include +#include "rhiwindow.h" + +int main(int argc, char **argv) +{ + QGuiApplication app(argc, argv); + + QRhi::Implementation graphicsApi; + + // Use platform-specific defaults when no command-line arguments given. +#if defined(Q_OS_WIN) + graphicsApi = QRhi::D3D11; +#elif defined(Q_OS_MACOS) || defined(Q_OS_IOS) + graphicsApi = QRhi::Metal; +#elif QT_CONFIG(vulkan) + graphicsApi = QRhi::Vulkan; +#else + graphicsApi = QRhi::OpenGLES2; +#endif + + QCommandLineParser cmdLineParser; + cmdLineParser.addHelpOption(); + QCommandLineOption nullOption({ "n", "null" }, QLatin1String("Null")); + cmdLineParser.addOption(nullOption); + QCommandLineOption glOption({ "g", "opengl" }, QLatin1String("OpenGL")); + cmdLineParser.addOption(glOption); + QCommandLineOption vkOption({ "v", "vulkan" }, QLatin1String("Vulkan")); + cmdLineParser.addOption(vkOption); + QCommandLineOption d3d11Option({ "d", "d3d11" }, QLatin1String("Direct3D 11")); + cmdLineParser.addOption(d3d11Option); + QCommandLineOption d3d12Option({ "D", "d3d12" }, QLatin1String("Direct3D 12")); + cmdLineParser.addOption(d3d12Option); + QCommandLineOption mtlOption({ "m", "metal" }, QLatin1String("Metal")); + cmdLineParser.addOption(mtlOption); + + cmdLineParser.process(app); + if (cmdLineParser.isSet(nullOption)) + graphicsApi = QRhi::Null; + if (cmdLineParser.isSet(glOption)) + graphicsApi = QRhi::OpenGLES2; + if (cmdLineParser.isSet(vkOption)) + graphicsApi = QRhi::Vulkan; + if (cmdLineParser.isSet(d3d11Option)) + graphicsApi = QRhi::D3D11; + if (cmdLineParser.isSet(d3d12Option)) + graphicsApi = QRhi::D3D12; + if (cmdLineParser.isSet(mtlOption)) + graphicsApi = QRhi::Metal; + + //! [api-setup] + // For OpenGL, to ensure there is a depth/stencil buffer for the window. + // With other APIs this is under the application's control (QRhiRenderBuffer etc.) + // and so no special setup is needed for those. + QSurfaceFormat fmt; + fmt.setDepthBufferSize(24); + fmt.setStencilBufferSize(8); + // Special case macOS to allow using OpenGL there. + // (the default Metal is the recommended approach, though) + // gl_VertexID is a GLSL 130 feature, and so the default OpenGL 2.1 context + // we get on macOS is not sufficient. +#ifdef Q_OS_MACOS + fmt.setVersion(4, 1); + fmt.setProfile(QSurfaceFormat::CoreProfile); +#endif + QSurfaceFormat::setDefaultFormat(fmt); + + // For Vulkan. +#if QT_CONFIG(vulkan) + QVulkanInstance inst; + if (graphicsApi == QRhi::Vulkan) { + // Request validation, if available. This is completely optional + // and has a performance impact, and should be avoided in production use. + inst.setLayers({ "VK_LAYER_KHRONOS_validation" }); + // Play nice with QRhi. + inst.setExtensions(QRhiVulkanInitParams::preferredInstanceExtensions()); + if (!inst.create()) { + qWarning("Failed to create Vulkan instance, switching to OpenGL"); + graphicsApi = QRhi::OpenGLES2; + } + } +#endif +//! [api-setup] + + HelloWindow window(graphicsApi); + +#if QT_CONFIG(vulkan) + if (graphicsApi == QRhi::Vulkan) + window.setVulkanInstance(&inst); +#endif + window.resize(1280, 720); + window.setTitle(QCoreApplication::applicationName() + QLatin1String(" - ") + window.graphicsApiName()); + window.show(); + + int ret = app.exec(); + + // RhiWindow::event() will not get invoked when the + // PlatformSurfaceAboutToBeDestroyed event is sent during the QWindow + // destruction. That happens only when exiting via app::quit() instead of + // the more common QWindow::close(). Take care of it: if the QPlatformWindow + // is still around (there was no close() yet), get rid of the swapchain + // while it's not too late. + if (window.handle()) + window.releaseSwapChain(); + + return ret; +} diff --git a/examples/gui/rhiwindow/rhiwindow.cpp b/examples/gui/rhiwindow/rhiwindow.cpp new file mode 100644 index 00000000..5022244b --- /dev/null +++ b/examples/gui/rhiwindow/rhiwindow.cpp @@ -0,0 +1,435 @@ +// Copyright (C) 2020 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause + +#include "rhiwindow.h" +#include +#include +#include +#include + +//! [rhiwindow-ctor] +RhiWindow::RhiWindow(QRhi::Implementation graphicsApi) + : m_graphicsApi(graphicsApi) +{ + switch (graphicsApi) { + case QRhi::OpenGLES2: + setSurfaceType(OpenGLSurface); + break; + case QRhi::Vulkan: + setSurfaceType(VulkanSurface); + break; + case QRhi::D3D11: + case QRhi::D3D12: + setSurfaceType(Direct3DSurface); + break; + case QRhi::Metal: + setSurfaceType(MetalSurface); + break; + case QRhi::Null: + break; // RasterSurface + } +} +//! [rhiwindow-ctor] + +QString RhiWindow::graphicsApiName() const +{ + switch (m_graphicsApi) { + case QRhi::Null: + return QLatin1String("Null (no output)"); + case QRhi::OpenGLES2: + return QLatin1String("OpenGL"); + case QRhi::Vulkan: + return QLatin1String("Vulkan"); + case QRhi::D3D11: + return QLatin1String("Direct3D 11"); + case QRhi::D3D12: + return QLatin1String("Direct3D 12"); + case QRhi::Metal: + return QLatin1String("Metal"); + } + return QString(); +} + +//! [expose] +void RhiWindow::exposeEvent(QExposeEvent *) +{ + // initialize and start rendering when the window becomes usable for graphics purposes + if (isExposed() && !m_initialized) { + init(); + resizeSwapChain(); + m_initialized = true; + } + + const QSize surfaceSize = m_hasSwapChain ? m_sc->surfacePixelSize() : QSize(); + + // stop pushing frames when not exposed (or size is 0) + if ((!isExposed() || (m_hasSwapChain && surfaceSize.isEmpty())) && m_initialized && !m_notExposed) + m_notExposed = true; + + // Continue when exposed again and the surface has a valid size. Note that + // surfaceSize can be (0, 0) even though size() reports a valid one, hence + // trusting surfacePixelSize() and not QWindow. + if (isExposed() && m_initialized && m_notExposed && !surfaceSize.isEmpty()) { + m_notExposed = false; + m_newlyExposed = true; + } + + // always render a frame on exposeEvent() (when exposed) in order to update + // immediately on window resize. + if (isExposed() && !surfaceSize.isEmpty()) + render(); +} +//! [expose] + +//! [event] +bool RhiWindow::event(QEvent *e) +{ + switch (e->type()) { + case QEvent::UpdateRequest: + render(); + break; + + case QEvent::PlatformSurface: + // this is the proper time to tear down the swapchain (while the native window and surface are still around) + if (static_cast(e)->surfaceEventType() == QPlatformSurfaceEvent::SurfaceAboutToBeDestroyed) + releaseSwapChain(); + break; + + default: + break; + } + + return QWindow::event(e); +} +//! [event] + +//! [rhi-init] +void RhiWindow::init() +{ + if (m_graphicsApi == QRhi::Null) { + QRhiNullInitParams params; + m_rhi.reset(QRhi::create(QRhi::Null, ¶ms)); + } + +#if QT_CONFIG(opengl) + if (m_graphicsApi == QRhi::OpenGLES2) { + m_fallbackSurface.reset(QRhiGles2InitParams::newFallbackSurface()); + QRhiGles2InitParams params; + params.fallbackSurface = m_fallbackSurface.get(); + params.window = this; + m_rhi.reset(QRhi::create(QRhi::OpenGLES2, ¶ms)); + } +#endif + +#if QT_CONFIG(vulkan) + if (m_graphicsApi == QRhi::Vulkan) { + QRhiVulkanInitParams params; + params.inst = vulkanInstance(); + params.window = this; + m_rhi.reset(QRhi::create(QRhi::Vulkan, ¶ms)); + } +#endif + +#ifdef Q_OS_WIN + if (m_graphicsApi == QRhi::D3D11) { + QRhiD3D11InitParams params; + // Enable the debug layer, if available. This is optional + // and should be avoided in production builds. + params.enableDebugLayer = true; + m_rhi.reset(QRhi::create(QRhi::D3D11, ¶ms)); + } else if (m_graphicsApi == QRhi::D3D12) { + QRhiD3D12InitParams params; + // Enable the debug layer, if available. This is optional + // and should be avoided in production builds. + params.enableDebugLayer = true; + m_rhi.reset(QRhi::create(QRhi::D3D12, ¶ms)); + } +#endif + +#if defined(Q_OS_MACOS) || defined(Q_OS_IOS) + if (m_graphicsApi == QRhi::Metal) { + QRhiMetalInitParams params; + m_rhi.reset(QRhi::create(QRhi::Metal, ¶ms)); + } +#endif + + if (!m_rhi) + qFatal("Failed to create RHI backend"); +//! [rhi-init] + +//! [swapchain-init] + m_sc.reset(m_rhi->newSwapChain()); + m_ds.reset(m_rhi->newRenderBuffer(QRhiRenderBuffer::DepthStencil, + QSize(), // no need to set the size here, due to UsedWithSwapChainOnly + 1, + QRhiRenderBuffer::UsedWithSwapChainOnly)); + m_sc->setWindow(this); + m_sc->setDepthStencil(m_ds.get()); + m_rp.reset(m_sc->newCompatibleRenderPassDescriptor()); + m_sc->setRenderPassDescriptor(m_rp.get()); +//! [swapchain-init] + + customInit(); +} + +//! [swapchain-resize] +void RhiWindow::resizeSwapChain() +{ + m_hasSwapChain = m_sc->createOrResize(); // also handles m_ds + + const QSize outputSize = m_sc->currentPixelSize(); + m_viewProjection = m_rhi->clipSpaceCorrMatrix(); + m_viewProjection.perspective(45.0f, outputSize.width() / (float) outputSize.height(), 0.01f, 1000.0f); + m_viewProjection.translate(0, 0, -4); +} +//! [swapchain-resize] + +void RhiWindow::releaseSwapChain() +{ + if (m_hasSwapChain) { + m_hasSwapChain = false; + m_sc->destroy(); + } +} + +//! [render-precheck] +void RhiWindow::render() +{ + if (!m_hasSwapChain || m_notExposed) + return; +//! [render-precheck] + +//! [render-resize] + // If the window got resized or newly exposed, resize the swapchain. (the + // newly-exposed case is not actually required by some platforms, but is + // here for robustness and portability) + // + // This (exposeEvent + the logic here) is the only safe way to perform + // resize handling. Note the usage of the RHI's surfacePixelSize(), and + // never QWindow::size(). (the two may or may not be the same under the hood, + // depending on the backend and platform) + // + if (m_sc->currentPixelSize() != m_sc->surfacePixelSize() || m_newlyExposed) { + resizeSwapChain(); + if (!m_hasSwapChain) + return; + m_newlyExposed = false; + } +//! [render-resize] + +//! [beginframe] + QRhi::FrameOpResult result = m_rhi->beginFrame(m_sc.get()); + if (result == QRhi::FrameOpSwapChainOutOfDate) { + resizeSwapChain(); + if (!m_hasSwapChain) + return; + result = m_rhi->beginFrame(m_sc.get()); + } + if (result != QRhi::FrameOpSuccess) { + qWarning("beginFrame failed with %d, will retry", result); + requestUpdate(); + return; + } + + customRender(); +//! [beginframe] + +//! [request-update] + m_rhi->endFrame(m_sc.get()); + + // Always request the next frame via requestUpdate(). On some platforms this is backed + // by a platform-specific solution, e.g. CVDisplayLink on macOS, which is potentially + // more efficient than a timer, queued metacalls, etc. + requestUpdate(); +} +//! [request-update] + +static float vertexData[] = { + // Y up (note clipSpaceCorrMatrix in m_viewProjection), CCW + 0.0f, 0.5f, 1.0f, 0.0f, 0.0f, + -0.5f, -0.5f, 0.0f, 1.0f, 0.0f, + 0.5f, -0.5f, 0.0f, 0.0f, 1.0f, +}; + +//! [getshader] +static QShader getShader(const QString &name) +{ + QFile f(name); + if (f.open(QIODevice::ReadOnly)) + return QShader::fromSerialized(f.readAll()); + + return QShader(); +} +//! [getshader] + +HelloWindow::HelloWindow(QRhi::Implementation graphicsApi) + : RhiWindow(graphicsApi) +{ +} + +//! [ensure-texture] +void HelloWindow::ensureFullscreenTexture(const QSize &pixelSize, QRhiResourceUpdateBatch *u) +{ + if (m_texture && m_texture->pixelSize() == pixelSize) + return; + + if (!m_texture) + m_texture.reset(m_rhi->newTexture(QRhiTexture::RGBA8, pixelSize)); + else + m_texture->setPixelSize(pixelSize); + + m_texture->create(); + + QImage image(pixelSize, QImage::Format_RGBA8888_Premultiplied); +//! [ensure-texture] + QPainter painter(&image); + painter.fillRect(QRectF(QPointF(0, 0), pixelSize), QColor::fromRgbF(0.4f, 0.7f, 0.0f, 1.0f)); + painter.setPen(Qt::transparent); + painter.setBrush({ QGradient(QGradient::DeepBlue) }); + painter.drawRoundedRect(QRectF(QPointF(20, 20), pixelSize - QSize(40, 40)), 16, 16); + painter.setPen(Qt::black); + QFont font; + font.setPixelSize(0.05 * qMin(pixelSize.width(), pixelSize.height())); + painter.setFont(font); + painter.drawText(QRectF(QPointF(60, 60), pixelSize - QSize(120, 120)), 0, + QLatin1String("Rendering with QRhi to a resizable QWindow.\nThe 3D API is %1.\nUse the command-line options to choose a different API.") + .arg(graphicsApiName())); + painter.end(); + + if (m_rhi->isYUpInNDC()) + image = image.mirrored(); + +//! [ensure-texture-2] + u->uploadTexture(m_texture.get(), image); +//! [ensure-texture-2] +} + +//! [render-init-1] +void HelloWindow::customInit() +{ + m_initialUpdates = m_rhi->nextResourceUpdateBatch(); + + m_vbuf.reset(m_rhi->newBuffer(QRhiBuffer::Immutable, QRhiBuffer::VertexBuffer, sizeof(vertexData))); + m_vbuf->create(); + m_initialUpdates->uploadStaticBuffer(m_vbuf.get(), vertexData); + + static const quint32 UBUF_SIZE = 68; + m_ubuf.reset(m_rhi->newBuffer(QRhiBuffer::Dynamic, QRhiBuffer::UniformBuffer, UBUF_SIZE)); + m_ubuf->create(); +//! [render-init-1] + + ensureFullscreenTexture(m_sc->surfacePixelSize(), m_initialUpdates); + + m_sampler.reset(m_rhi->newSampler(QRhiSampler::Linear, QRhiSampler::Linear, QRhiSampler::None, + QRhiSampler::ClampToEdge, QRhiSampler::ClampToEdge)); + m_sampler->create(); + + //! [render-init-2] + m_colorTriSrb.reset(m_rhi->newShaderResourceBindings()); + static const QRhiShaderResourceBinding::StageFlags visibility = + QRhiShaderResourceBinding::VertexStage | QRhiShaderResourceBinding::FragmentStage; + m_colorTriSrb->setBindings({ + QRhiShaderResourceBinding::uniformBuffer(0, visibility, m_ubuf.get()) + }); + m_colorTriSrb->create(); + + m_colorPipeline.reset(m_rhi->newGraphicsPipeline()); + // Enable depth testing; not quite needed for a simple triangle, but we + // have a depth-stencil buffer so why not. + m_colorPipeline->setDepthTest(true); + m_colorPipeline->setDepthWrite(true); + // Blend factors default to One, OneOneMinusSrcAlpha, which is convenient. + QRhiGraphicsPipeline::TargetBlend premulAlphaBlend; + premulAlphaBlend.enable = true; + m_colorPipeline->setTargetBlends({ premulAlphaBlend }); + m_colorPipeline->setShaderStages({ + { QRhiShaderStage::Vertex, getShader(QLatin1String(":/color.vert.qsb")) }, + { QRhiShaderStage::Fragment, getShader(QLatin1String(":/color.frag.qsb")) } + }); + QRhiVertexInputLayout inputLayout; + inputLayout.setBindings({ + { 5 * sizeof(float) } + }); + inputLayout.setAttributes({ + { 0, 0, QRhiVertexInputAttribute::Float2, 0 }, + { 0, 1, QRhiVertexInputAttribute::Float3, 2 * sizeof(float) } + }); + m_colorPipeline->setVertexInputLayout(inputLayout); + m_colorPipeline->setShaderResourceBindings(m_colorTriSrb.get()); + m_colorPipeline->setRenderPassDescriptor(m_rp.get()); + m_colorPipeline->create(); +//! [render-init-2] + + m_fullscreenQuadSrb.reset(m_rhi->newShaderResourceBindings()); + m_fullscreenQuadSrb->setBindings({ + QRhiShaderResourceBinding::sampledTexture(0, QRhiShaderResourceBinding::FragmentStage, + m_texture.get(), m_sampler.get()) + }); + m_fullscreenQuadSrb->create(); + + m_fullscreenQuadPipeline.reset(m_rhi->newGraphicsPipeline()); + m_fullscreenQuadPipeline->setShaderStages({ + { QRhiShaderStage::Vertex, getShader(QLatin1String(":/quad.vert.qsb")) }, + { QRhiShaderStage::Fragment, getShader(QLatin1String(":/quad.frag.qsb")) } + }); + m_fullscreenQuadPipeline->setVertexInputLayout({}); + m_fullscreenQuadPipeline->setShaderResourceBindings(m_fullscreenQuadSrb.get()); + m_fullscreenQuadPipeline->setRenderPassDescriptor(m_rp.get()); + m_fullscreenQuadPipeline->create(); +} + +//! [render-1] +void HelloWindow::customRender() +{ + QRhiResourceUpdateBatch *resourceUpdates = m_rhi->nextResourceUpdateBatch(); + + if (m_initialUpdates) { + resourceUpdates->merge(m_initialUpdates); + m_initialUpdates->release(); + m_initialUpdates = nullptr; + } +//! [render-1] + +//! [render-rotation] + m_rotation += 1.0f; + QMatrix4x4 modelViewProjection = m_viewProjection; + modelViewProjection.rotate(m_rotation, 0, 1, 0); + resourceUpdates->updateDynamicBuffer(m_ubuf.get(), 0, 64, modelViewProjection.constData()); +//! [render-rotation] + +//! [render-opacity] + m_opacity += m_opacityDir * 0.005f; + if (m_opacity < 0.0f || m_opacity > 1.0f) { + m_opacityDir *= -1; + m_opacity = qBound(0.0f, m_opacity, 1.0f); + } + resourceUpdates->updateDynamicBuffer(m_ubuf.get(), 64, 4, &m_opacity); +//! [render-opacity] + +//! [render-cb] + QRhiCommandBuffer *cb = m_sc->currentFrameCommandBuffer(); + const QSize outputSizeInPixels = m_sc->currentPixelSize(); +//! [render-cb] + + // (re)create the texture with a size matching the output surface size, when necessary. + ensureFullscreenTexture(outputSizeInPixels, resourceUpdates); + +//! [render-pass] + cb->beginPass(m_sc->currentFrameRenderTarget(), Qt::black, { 1.0f, 0 }, resourceUpdates); +//! [render-pass] + + cb->setGraphicsPipeline(m_fullscreenQuadPipeline.get()); + cb->setViewport({ 0, 0, float(outputSizeInPixels.width()), float(outputSizeInPixels.height()) }); + cb->setShaderResources(); + cb->draw(3); + +//! [render-pass-record] + cb->setGraphicsPipeline(m_colorPipeline.get()); + cb->setShaderResources(); + const QRhiCommandBuffer::VertexInput vbufBinding(m_vbuf.get(), 0); + cb->setVertexInput(0, 1, &vbufBinding); + cb->draw(3); + + cb->endPass(); +//! [render-pass-record] +} diff --git a/examples/gui/rhiwindow/rhiwindow.h b/examples/gui/rhiwindow/rhiwindow.h new file mode 100644 index 00000000..520c3e8c --- /dev/null +++ b/examples/gui/rhiwindow/rhiwindow.h @@ -0,0 +1,78 @@ +// Copyright (C) 2020 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause + +#ifndef WINDOW_H +#define WINDOW_H + +#include +#include +#include + +class RhiWindow : public QWindow +{ +public: + RhiWindow(QRhi::Implementation graphicsApi); + QString graphicsApiName() const; + void releaseSwapChain(); + +protected: + virtual void customInit() = 0; + virtual void customRender() = 0; + + // destruction order matters to a certain degree: the fallbackSurface must + // outlive the rhi, the rhi must outlive all other resources. The resources + // need no special order when destroying. +#if QT_CONFIG(opengl) + std::unique_ptr m_fallbackSurface; +#endif + std::unique_ptr m_rhi; +//! [swapchain-data] + std::unique_ptr m_sc; + std::unique_ptr m_ds; + std::unique_ptr m_rp; +//! [swapchain-data] + bool m_hasSwapChain = false; + QMatrix4x4 m_viewProjection; + +private: + void init(); + void resizeSwapChain(); + void render(); + + void exposeEvent(QExposeEvent *) override; + bool event(QEvent *) override; + + QRhi::Implementation m_graphicsApi; + bool m_initialized = false; + bool m_notExposed = false; + bool m_newlyExposed = false; +}; + +class HelloWindow : public RhiWindow +{ +public: + HelloWindow(QRhi::Implementation graphicsApi); + + void customInit() override; + void customRender() override; + +private: + void ensureFullscreenTexture(const QSize &pixelSize, QRhiResourceUpdateBatch *u); + + std::unique_ptr m_vbuf; + std::unique_ptr m_ubuf; + std::unique_ptr m_texture; + std::unique_ptr m_sampler; + std::unique_ptr m_colorTriSrb; + std::unique_ptr m_colorPipeline; + std::unique_ptr m_fullscreenQuadSrb; + std::unique_ptr m_fullscreenQuadPipeline; + + QRhiResourceUpdateBatch *m_initialUpdates = nullptr; + + float m_rotation = 0; + float m_opacity = 1; + int m_opacityDir = -1; +}; + +#endif diff --git a/examples/gui/rhiwindow/rhiwindow.pri b/examples/gui/rhiwindow/rhiwindow.pri new file mode 100644 index 00000000..d0ef7a26 --- /dev/null +++ b/examples/gui/rhiwindow/rhiwindow.pri @@ -0,0 +1,4 @@ +INCLUDEPATH += $$PWD +SOURCES += $$PWD/rhiwindow.cpp +HEADERS += $$PWD/rhiwindow.h +RESOURCES += $$PWD/rhiwindow.qrc diff --git a/examples/gui/rhiwindow/rhiwindow.pro b/examples/gui/rhiwindow/rhiwindow.pro new file mode 100644 index 00000000..18f259aa --- /dev/null +++ b/examples/gui/rhiwindow/rhiwindow.pro @@ -0,0 +1,9 @@ +include(rhiwindow.pri) + +QT += gui-private + +SOURCES += \ + main.cpp + +target.path = $$[QT_INSTALL_EXAMPLES]/gui/rhiwindow +INSTALLS += target diff --git a/examples/gui/rhiwindow/rhiwindow.qrc b/examples/gui/rhiwindow/rhiwindow.qrc new file mode 100644 index 00000000..1009ec5d --- /dev/null +++ b/examples/gui/rhiwindow/rhiwindow.qrc @@ -0,0 +1,8 @@ + + + shaders/prebuilt/color.vert.qsb + shaders/prebuilt/color.frag.qsb + shaders/prebuilt/quad.vert.qsb + shaders/prebuilt/quad.frag.qsb + + diff --git a/examples/gui/rhiwindow/shaders/color.frag b/examples/gui/rhiwindow/shaders/color.frag new file mode 100644 index 00000000..6e0a3bc9 --- /dev/null +++ b/examples/gui/rhiwindow/shaders/color.frag @@ -0,0 +1,15 @@ +#version 440 + +layout(location = 0) in vec3 v_color; + +layout(location = 0) out vec4 fragColor; + +layout(std140, binding = 0) uniform buf { + mat4 mvp; + float opacity; +}; + +void main() +{ + fragColor = vec4(v_color * opacity, opacity); +} diff --git a/examples/gui/rhiwindow/shaders/color.vert b/examples/gui/rhiwindow/shaders/color.vert new file mode 100644 index 00000000..70852ab8 --- /dev/null +++ b/examples/gui/rhiwindow/shaders/color.vert @@ -0,0 +1,17 @@ +#version 440 + +layout(location = 0) in vec4 position; +layout(location = 1) in vec3 color; + +layout(location = 0) out vec3 v_color; + +layout(std140, binding = 0) uniform buf { + mat4 mvp; + float opacity; +}; + +void main() +{ + v_color = color; + gl_Position = mvp * position; +} diff --git a/examples/gui/rhiwindow/shaders/prebuilt/color.frag.qsb b/examples/gui/rhiwindow/shaders/prebuilt/color.frag.qsb new file mode 100644 index 00000000..b4db470e Binary files /dev/null and b/examples/gui/rhiwindow/shaders/prebuilt/color.frag.qsb differ diff --git a/examples/gui/rhiwindow/shaders/prebuilt/color.vert.qsb b/examples/gui/rhiwindow/shaders/prebuilt/color.vert.qsb new file mode 100644 index 00000000..ab046b77 Binary files /dev/null and b/examples/gui/rhiwindow/shaders/prebuilt/color.vert.qsb differ diff --git a/examples/gui/rhiwindow/shaders/prebuilt/quad.frag.qsb b/examples/gui/rhiwindow/shaders/prebuilt/quad.frag.qsb new file mode 100644 index 00000000..c2ea3cf2 Binary files /dev/null and b/examples/gui/rhiwindow/shaders/prebuilt/quad.frag.qsb differ diff --git a/examples/gui/rhiwindow/shaders/prebuilt/quad.vert.qsb b/examples/gui/rhiwindow/shaders/prebuilt/quad.vert.qsb new file mode 100644 index 00000000..f0b64f75 Binary files /dev/null and b/examples/gui/rhiwindow/shaders/prebuilt/quad.vert.qsb differ diff --git a/examples/gui/rhiwindow/shaders/quad.frag b/examples/gui/rhiwindow/shaders/quad.frag new file mode 100644 index 00000000..65882a42 --- /dev/null +++ b/examples/gui/rhiwindow/shaders/quad.frag @@ -0,0 +1,11 @@ +#version 440 + +layout(location = 0) in vec2 v_uv; +layout(location = 0) out vec4 fragColor; +layout(binding = 0) uniform sampler2D tex; + +void main() +{ + vec4 c = texture(tex, v_uv); + fragColor = vec4(c.rgb * c.a, c.a); +} diff --git a/examples/gui/rhiwindow/shaders/quad.vert b/examples/gui/rhiwindow/shaders/quad.vert new file mode 100644 index 00000000..359896d0 --- /dev/null +++ b/examples/gui/rhiwindow/shaders/quad.vert @@ -0,0 +1,10 @@ +#version 440 + +layout (location = 0) out vec2 v_uv; + +void main() +{ + // https://www.saschawillems.de/blog/2016/08/13/vulkan-tutorial-on-rendering-a-fullscreen-quad-without-buffers/ + v_uv = vec2((gl_VertexIndex << 1) & 2, gl_VertexIndex & 2); + gl_Position = vec4(v_uv * 2.0 - 1.0, 0.0, 1.0); +} diff --git a/examples/network/dnslookup/dnslookup.cpp b/examples/network/dnslookup/dnslookup.cpp index 9ebdfb1f..9e74ddcf 100644 --- a/examples/network/dnslookup/dnslookup.cpp +++ b/examples/network/dnslookup/dnslookup.cpp @@ -14,6 +14,7 @@ #include using namespace Qt::StringLiterals; + static std::optional typeFromParameter(QStringView type) { if (type.compare(u"a", Qt::CaseInsensitive) == 0) @@ -218,7 +219,7 @@ int main(int argc, char *argv[]) DnsManager manager; manager.setQuery(query); - QTimer::singleShot(0, &manager, SLOT(execute())); + QTimer::singleShot(0, &manager, &DnsManager::execute); return app.exec(); } diff --git a/examples/network/doc/src/network-chat.qdoc b/examples/network/doc/src/network-chat.qdoc index 29ba0652..da12f5f6 100644 --- a/examples/network/doc/src/network-chat.qdoc +++ b/examples/network/doc/src/network-chat.qdoc @@ -3,8 +3,10 @@ /*! \example network-chat - \title Network Chat Example + \title Network Chat \ingroup examples-network + \examplecategory {Networking} + \meta tag {network,serialization} \brief Demonstrates a stateful peer-to-peer Chat client. This example uses broadcasting with QUdpSocket and QNetworkInterface to diff --git a/examples/network/network-chat/CMakeLists.txt b/examples/network/network-chat/CMakeLists.txt index 6b6abd1c..dd3164a1 100644 --- a/examples/network/network-chat/CMakeLists.txt +++ b/examples/network/network-chat/CMakeLists.txt @@ -28,6 +28,10 @@ set_target_properties(network-chat PROPERTIES MACOSX_BUNDLE TRUE ) +target_compile_definitions(network-chat PRIVATE + QT_USE_QSTRINGBUILDER +) + target_link_libraries(network-chat PRIVATE Qt6::Core Qt6::Gui diff --git a/examples/network/network-chat/chatdialog.cpp b/examples/network/network-chat/chatdialog.cpp index ddec5c29..55c98fed 100644 --- a/examples/network/network-chat/chatdialog.cpp +++ b/examples/network/network-chat/chatdialog.cpp @@ -1,10 +1,14 @@ // Copyright (C) 2016 The Qt Company Ltd. // SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause -#include - #include "chatdialog.h" +#include +#include +#include +#include +#include + ChatDialog::ChatDialog(QWidget *parent) : QDialog(parent) { @@ -27,7 +31,7 @@ ChatDialog::ChatDialog(QWidget *parent) myNickName = client.nickName(); newParticipant(myNickName); tableFormat.setBorder(0); - QTimer::singleShot(10 * 1000, this, SLOT(showInformation())); + QTimer::singleShot(10 * 1000, this, &ChatDialog::showInformation); } void ChatDialog::appendMessage(const QString &from, const QString &message) diff --git a/examples/network/network-chat/chatdialog.h b/examples/network/network-chat/chatdialog.h index adda73fb..45a98588 100644 --- a/examples/network/network-chat/chatdialog.h +++ b/examples/network/network-chat/chatdialog.h @@ -12,7 +12,7 @@ class ChatDialog : public QDialog, private Ui::ChatDialog Q_OBJECT public: - ChatDialog(QWidget *parent = nullptr); + explicit ChatDialog(QWidget *parent = nullptr); public slots: void appendMessage(const QString &from, const QString &message); diff --git a/examples/network/network-chat/client.cpp b/examples/network/network-chat/client.cpp index bbce358c..54bde6cd 100644 --- a/examples/network/network-chat/client.cpp +++ b/examples/network/network-chat/client.cpp @@ -1,15 +1,18 @@ // Copyright (C) 2016 The Qt Company Ltd. // SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause -#include - #include "client.h" #include "connection.h" #include "peermanager.h" +#include + +#include +#include + Client::Client() + : peerManager(new PeerManager(this)) { - peerManager = new PeerManager(this); peerManager->setServerPort(server.serverPort()); peerManager->startBroadcasting(); @@ -36,14 +39,15 @@ QString Client::nickName() const bool Client::hasConnection(const QHostAddress &senderIp, int senderPort) const { - if (senderPort == -1) - return peers.contains(senderIp); - - if (!peers.contains(senderIp)) + auto [begin, end] = peers.equal_range(senderIp); + if (begin == peers.constEnd()) return false; - const QList connections = peers.values(senderIp); - for (const Connection *connection : connections) { + if (senderPort == -1) + return true; + + for (; begin != end; ++begin) { + Connection *connection = *begin; if (connection->peerPort() == senderPort) return true; } @@ -63,8 +67,7 @@ void Client::newConnection(Connection *connection) void Client::readyForUse() { Connection *connection = qobject_cast(sender()); - if (!connection || hasConnection(connection->peerAddress(), - connection->peerPort())) + if (!connection || hasConnection(connection->peerAddress(), connection->peerPort())) return; connect(connection, &Connection::newMessage, @@ -90,8 +93,7 @@ void Client::connectionError(QAbstractSocket::SocketError /* socketError */) void Client::removeConnection(Connection *connection) { - if (peers.contains(connection->peerAddress())) { - peers.remove(connection->peerAddress()); + if (peers.remove(connection->peerAddress(), connection) > 0) { QString nick = connection->name(); if (!nick.isEmpty()) emit participantLeft(nick); diff --git a/examples/network/network-chat/client.h b/examples/network/network-chat/client.h index 5d55bf53..55abd84c 100644 --- a/examples/network/network-chat/client.h +++ b/examples/network/network-chat/client.h @@ -4,12 +4,12 @@ #ifndef CLIENT_H #define CLIENT_H +#include "server.h" + #include #include #include -#include "server.h" - class PeerManager; class Client : public QObject diff --git a/examples/network/network-chat/connection.cpp b/examples/network/network-chat/connection.cpp index d89266a5..414faa80 100644 --- a/examples/network/network-chat/connection.cpp +++ b/examples/network/network-chat/connection.cpp @@ -4,7 +4,7 @@ #include "connection.h" -#include +#include static const int TransferTimeout = 30 * 1000; static const int PongTimeout = 60 * 1000; @@ -27,12 +27,6 @@ static const int PingInterval = 5 * 1000; Connection::Connection(QObject *parent) : QTcpSocket(parent), writer(this) { - greetingMessage = tr("undefined"); - username = tr("unknown"); - state = WaitingForGreeting; - currentDataType = Undefined; - transferTimerId = -1; - isGreetingMessageSent = false; pingTimer.setInterval(PingInterval); connect(this, &QTcpSocket::readyRead, this, diff --git a/examples/network/network-chat/connection.h b/examples/network/network-chat/connection.h index a3082247..77c116cf 100644 --- a/examples/network/network-chat/connection.h +++ b/examples/network/network-chat/connection.h @@ -32,8 +32,8 @@ public: Undefined }; - Connection(QObject *parent = nullptr); - Connection(qintptr socketDescriptor, QObject *parent = nullptr); + explicit Connection(QObject *parent = nullptr); + explicit Connection(qintptr socketDescriptor, QObject *parent = nullptr); ~Connection(); QString name() const; @@ -59,15 +59,15 @@ private: QCborStreamReader reader; QCborStreamWriter writer; - QString greetingMessage; - QString username; + QString greetingMessage = tr("undefined"); + QString username = tr("unknown"); QTimer pingTimer; QElapsedTimer pongTime; QString buffer; - ConnectionState state; - DataType currentDataType; - int transferTimerId; - bool isGreetingMessageSent; + ConnectionState state = WaitingForGreeting; + DataType currentDataType = Undefined; + int transferTimerId = -1; + bool isGreetingMessageSent = false; }; #endif diff --git a/examples/network/network-chat/main.cpp b/examples/network/network-chat/main.cpp index bc6c4ef6..5d679d28 100644 --- a/examples/network/network-chat/main.cpp +++ b/examples/network/network-chat/main.cpp @@ -1,11 +1,9 @@ // Copyright (C) 2016 The Qt Company Ltd. // SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause -#include - #include "chatdialog.h" -#include +#include int main(int argc, char *argv[]) { diff --git a/examples/network/network-chat/network-chat.pro b/examples/network/network-chat/network-chat.pro index 2e3cbbc4..626a7109 100644 --- a/examples/network/network-chat/network-chat.pro +++ b/examples/network/network-chat/network-chat.pro @@ -11,6 +11,7 @@ SOURCES = chatdialog.cpp \ server.cpp FORMS = chatdialog.ui QT += network widgets +DEFINES += QT_USE_QSTRINGBUILDER requires(qtConfig(udpsocket)) requires(qtConfig(listwidget)) diff --git a/examples/network/network-chat/peermanager.cpp b/examples/network/network-chat/peermanager.cpp index 45587404..da4210d8 100644 --- a/examples/network/network-chat/peermanager.cpp +++ b/examples/network/network-chat/peermanager.cpp @@ -2,20 +2,18 @@ // Copyright (C) 2018 Intel Corporation. // SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause -#include - #include "client.h" #include "connection.h" #include "peermanager.h" +#include + static const qint32 BroadcastInterval = 2000; static const unsigned broadcastPort = 45000; PeerManager::PeerManager(Client *client) - : QObject(client) + : QObject(client), client(client) { - this->client = client; - static const char *envVariables[] = { "USERNAME", "USER", "USERDOMAIN", "HOSTNAME", "DOMAINNAME" }; @@ -30,7 +28,6 @@ PeerManager::PeerManager(Client *client) username = "unknown"; updateAddresses(); - serverPort = 0; broadcastSocket.bind(QHostAddress::Any, broadcastPort, QUdpSocket::ShareAddress | QUdpSocket::ReuseAddressHint); @@ -59,11 +56,7 @@ void PeerManager::startBroadcasting() bool PeerManager::isLocalHostAddress(const QHostAddress &address) const { - for (const QHostAddress &localAddress : ipAddresses) { - if (address.isEqual(localAddress)) - return true; - } - return false; + return ipAddresses.contains(address); } void PeerManager::sendBroadcastDatagram() @@ -79,8 +72,7 @@ void PeerManager::sendBroadcastDatagram() bool validBroadcastAddresses = true; for (const QHostAddress &address : std::as_const(broadcastAddresses)) { - if (broadcastSocket.writeDatagram(datagram, address, - broadcastPort) == -1) + if (broadcastSocket.writeDatagram(datagram, address, broadcastPort) == -1) validBroadcastAddresses = false; } diff --git a/examples/network/network-chat/peermanager.h b/examples/network/network-chat/peermanager.h index b9ea8053..dadabd88 100644 --- a/examples/network/network-chat/peermanager.h +++ b/examples/network/network-chat/peermanager.h @@ -18,7 +18,7 @@ class PeerManager : public QObject Q_OBJECT public: - PeerManager(Client *client); + explicit PeerManager(Client *client); void setServerPort(int port); QString userName() const; @@ -35,13 +35,13 @@ private slots: private: void updateAddresses(); - Client *client; + Client *client = nullptr; QList broadcastAddresses; QList ipAddresses; QUdpSocket broadcastSocket; QTimer broadcastTimer; QString username; - int serverPort; + int serverPort = 0; }; #endif diff --git a/examples/network/network-chat/server.cpp b/examples/network/network-chat/server.cpp index 1537cbb0..afc96717 100644 --- a/examples/network/network-chat/server.cpp +++ b/examples/network/network-chat/server.cpp @@ -1,8 +1,6 @@ // Copyright (C) 2016 The Qt Company Ltd. // SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause -#include - #include "connection.h" #include "server.h" diff --git a/examples/network/network-chat/server.h b/examples/network/network-chat/server.h index f41ffeba..0557de30 100644 --- a/examples/network/network-chat/server.h +++ b/examples/network/network-chat/server.h @@ -13,7 +13,7 @@ class Server : public QTcpServer Q_OBJECT public: - Server(QObject *parent = nullptr); + explicit Server(QObject *parent = nullptr); signals: void newConnection(Connection *connection); diff --git a/examples/network/rsslisting/doc/src/rsslisting.qdoc b/examples/network/rsslisting/doc/src/rsslisting.qdoc index a1e91932..260d2681 100644 --- a/examples/network/rsslisting/doc/src/rsslisting.qdoc +++ b/examples/network/rsslisting/doc/src/rsslisting.qdoc @@ -2,7 +2,7 @@ // SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GFDL-1.3-no-invariants-only /*! - \example rsslisting + \example serialization/rsslisting \examplecategory {Networking} \meta tag {serialization} \title A minimal RSS listing application @@ -31,11 +31,11 @@ former. For the sake of illustration, it gives the widget the Qt blog's URL as default value for the resource to check. - \snippet rsslisting/main.cpp 0 + \snippet serialization/rsslisting/main.cpp 0 \section1 The RSSListing class - \snippet rsslisting/rsslisting.h 0 + \snippet serialization/rsslisting/rsslisting.h 0 The widget itself provides a simple user interface for specifying the URL to fetch and, once available updates are displayed, controlling the downloading @@ -51,7 +51,7 @@ \section2 Construction - \snippet rsslisting/rsslisting.cpp setup + \snippet serialization/rsslisting/rsslisting.cpp setup The constructor sets up the assorted components of the widget and connects their various signals to the slots it shall use to handle them. @@ -69,7 +69,7 @@ \section2 The slots - \snippet rsslisting/rsslisting.cpp slots + \snippet serialization/rsslisting/rsslisting.cpp slots All slots are kept simple by delegating any hard work to private methods. @@ -94,7 +94,7 @@ \section2 The get() method - \snippet rsslisting/rsslisting.cpp get + \snippet serialization/rsslisting/rsslisting.cpp get The private \c get() method is used by the \c fetch() slot to initiate an HTTP GET request. It first clears the XML stream reader and, if a reply is @@ -106,7 +106,7 @@ \section2 The parseXml() method - \snippet rsslisting/rsslisting.cpp parse + \snippet serialization/rsslisting/rsslisting.cpp parse When data is received, and thus made available to the XML stream reader, \c parseXml() reads from the XML stream, checking for \c item elements and, diff --git a/examples/network/secureudpclient/mainwindow.cpp b/examples/network/secureudpclient/mainwindow.cpp index 8b9ac24c..987eb739 100644 --- a/examples/network/secureudpclient/mainwindow.cpp +++ b/examples/network/secureudpclient/mainwindow.cpp @@ -83,7 +83,7 @@ void MainWindow::on_connectButton_clicked() return startNewConnection(remoteAddress); addInfoMessage(tr("Looking up the host ...")); - lookupId = QHostInfo::lookupHost(hostName, this, SLOT(lookupFinished(QHostInfo))); + lookupId = QHostInfo::lookupHost(hostName, this, &MainWindow::lookupFinished); updateUi(); } diff --git a/examples/network/torrent/ratecontroller.cpp b/examples/network/torrent/ratecontroller.cpp index c5e33ab6..338bb596 100644 --- a/examples/network/torrent/ratecontroller.cpp +++ b/examples/network/torrent/ratecontroller.cpp @@ -42,7 +42,7 @@ void RateController::scheduleTransfer() if (transferScheduled) return; transferScheduled = true; - QTimer::singleShot(50, this, SLOT(transfer())); + QTimer::singleShot(50, this, &RateController::transfer); } void RateController::transfer() diff --git a/examples/network/torrent/trackerclient.cpp b/examples/network/torrent/trackerclient.cpp index a2ef2bb6..12110ba0 100644 --- a/examples/network/torrent/trackerclient.cpp +++ b/examples/network/torrent/trackerclient.cpp @@ -20,7 +20,7 @@ TrackerClient::TrackerClient(TorrentClient *downloader, QObject *parent) void TrackerClient::start(const MetaInfo &info) { metaInfo = info; - QTimer::singleShot(0, this, SLOT(fetchPeerList())); + QTimer::singleShot(0, this, &TrackerClient::fetchPeerList); if (metaInfo.fileForm() == MetaInfo::SingleFileForm) { length = metaInfo.singleFile().length; diff --git a/examples/qmake/precompile/main.cpp b/examples/qmake/precompile/main.cpp index ece87dcc..58050df6 100644 --- a/examples/qmake/precompile/main.cpp +++ b/examples/qmake/precompile/main.cpp @@ -15,7 +15,8 @@ int main(int argc, char **argv) MyObject obj; MyDialog dialog; - dialog.connect(dialog.aButton, SIGNAL(clicked()), SLOT(close())); + QObject::connect(dialog.aButton, &QPushButton::clicked, + &dialog, &MyDialog::close); dialog.show(); return app.exec(); diff --git a/examples/widgets/doc/src/calendarwidget.qdoc b/examples/widgets/doc/src/calendarwidget.qdoc index 898f4bbd..01c6df69 100644 --- a/examples/widgets/doc/src/calendarwidget.qdoc +++ b/examples/widgets/doc/src/calendarwidget.qdoc @@ -3,7 +3,7 @@ /*! \title Calendar Widget Example - \examplecategory {Graphics & Multimedia} + \examplecategory {User Interface Components} \example widgets/calendarwidget \ingroup examples-widgets \ingroup examples-layout diff --git a/examples/widgets/doc/src/elasticnodes.qdoc b/examples/widgets/doc/src/elasticnodes.qdoc index 14ea63ef..10062ff0 100644 --- a/examples/widgets/doc/src/elasticnodes.qdoc +++ b/examples/widgets/doc/src/elasticnodes.qdoc @@ -236,7 +236,7 @@ This ensures that the edge items are not considered for mouse input at all (i.e., you cannot click the edges). Then, the source and destination pointers are updated, this edge is registered with each node, and we call - \c adjust() to update this edge's start end end position. + \c adjust() to update this edge's start and end position. \snippet graphicsview/elasticnodes/edge.cpp 1 diff --git a/examples/widgets/graphicsview/diagramscene/mainwindow.cpp b/examples/widgets/graphicsview/diagramscene/mainwindow.cpp index d6a389af..7588e437 100644 --- a/examples/widgets/graphicsview/diagramscene/mainwindow.cpp +++ b/examples/widgets/graphicsview/diagramscene/mainwindow.cpp @@ -432,7 +432,7 @@ void MainWindow::createToolbars() fontColorToolButton = new QToolButton; fontColorToolButton->setPopupMode(QToolButton::MenuButtonPopup); - fontColorToolButton->setMenu(createColorMenu(SLOT(textColorChanged()), Qt::black)); + fontColorToolButton->setMenu(createColorMenu(&MainWindow::textColorChanged, Qt::black)); textAction = fontColorToolButton->menu()->defaultAction(); fontColorToolButton->setIcon(createColorToolButtonIcon(":/images/textpointer.png", Qt::black)); fontColorToolButton->setAutoFillBackground(true); @@ -442,7 +442,7 @@ void MainWindow::createToolbars() //! [26] fillColorToolButton = new QToolButton; fillColorToolButton->setPopupMode(QToolButton::MenuButtonPopup); - fillColorToolButton->setMenu(createColorMenu(SLOT(itemColorChanged()), Qt::white)); + fillColorToolButton->setMenu(createColorMenu(&MainWindow::itemColorChanged, Qt::white)); fillAction = fillColorToolButton->menu()->defaultAction(); fillColorToolButton->setIcon(createColorToolButtonIcon( ":/images/floodfill.png", Qt::white)); @@ -452,7 +452,7 @@ void MainWindow::createToolbars() lineColorToolButton = new QToolButton; lineColorToolButton->setPopupMode(QToolButton::MenuButtonPopup); - lineColorToolButton->setMenu(createColorMenu(SLOT(lineColorChanged()), Qt::black)); + lineColorToolButton->setMenu(createColorMenu(&MainWindow::lineColorChanged, Qt::black)); lineAction = lineColorToolButton->menu()->defaultAction(); lineColorToolButton->setIcon(createColorToolButtonIcon( ":/images/linecolor.png", Qt::black)); @@ -547,7 +547,8 @@ QWidget *MainWindow::createCellWidget(const QString &text, DiagramItem::DiagramT //! [29] //! [30] -QMenu *MainWindow::createColorMenu(const char *slot, QColor defaultColor) +template +QMenu *MainWindow::createColorMenu(const PointerToMemberFunction &slot, QColor defaultColor) { QList colors; colors << Qt::black << Qt::white << Qt::red << Qt::blue << Qt::yellow; @@ -560,7 +561,7 @@ QMenu *MainWindow::createColorMenu(const char *slot, QColor defaultColor) QAction *action = new QAction(names.at(i), this); action->setData(colors.at(i)); action->setIcon(createColorIcon(colors.at(i))); - connect(action, SIGNAL(triggered()), this, slot); + connect(action, &QAction::triggered, this, slot); colorMenu->addAction(action); if (colors.at(i) == defaultColor) colorMenu->setDefaultAction(action); diff --git a/examples/widgets/graphicsview/diagramscene/mainwindow.h b/examples/widgets/graphicsview/diagramscene/mainwindow.h index 34f98f65..fe8a9190 100644 --- a/examples/widgets/graphicsview/diagramscene/mainwindow.h +++ b/examples/widgets/graphicsview/diagramscene/mainwindow.h @@ -64,7 +64,9 @@ private: const QString &image); QWidget *createCellWidget(const QString &text, DiagramItem::DiagramType type); - QMenu *createColorMenu(const char *slot, QColor defaultColor); + + template + QMenu *createColorMenu(const PointerToMemberFunction &slot, QColor defaultColor); QIcon createColorToolButtonIcon(const QString &image, QColor color); QIcon createColorIcon(QColor color); diff --git a/examples/widgets/painting/pathstroke/pathstroke.cpp b/examples/widgets/painting/pathstroke/pathstroke.cpp index 9bec5623..8c0548bd 100644 --- a/examples/widgets/painting/pathstroke/pathstroke.cpp +++ b/examples/widgets/painting/pathstroke/pathstroke.cpp @@ -341,7 +341,7 @@ PathStrokeWidget::PathStrokeWidget(bool smallScreen) connect(m_renderer, &PathStrokeRenderer::clicked, this, &PathStrokeWidget::showControls); connect(m_controls, &PathStrokeControls::okPressed, this, &PathStrokeWidget::hideControls); - connect(m_controls, SIGNAL(quitPressed()), QApplication::instance(), SLOT(quit())); + connect(m_controls, &PathStrokeControls::quitPressed, QApplication::instance(), &QApplication::quit); } void PathStrokeWidget::showControls() diff --git a/examples/widgets/tools/settingseditor/locationdialog.cpp b/examples/widgets/tools/settingseditor/locationdialog.cpp index cef83c0e..1c41d450 100644 --- a/examples/widgets/tools/settingseditor/locationdialog.cpp +++ b/examples/widgets/tools/settingseditor/locationdialog.cpp @@ -33,7 +33,6 @@ LocationDialog::LocationDialog(QWidget *parent) applicationComboBox = new QComboBox; applicationComboBox->addItem(tr("Any")); applicationComboBox->addItem(tr("Qt Creator")); - applicationComboBox->addItem(tr("Application Example")); applicationComboBox->addItem(tr("Assistant")); applicationComboBox->addItem(tr("Designer")); applicationComboBox->addItem(tr("Linguist")); diff --git a/examples/widgets/widgets/calculator/calculator.cpp b/examples/widgets/widgets/calculator/calculator.cpp index 5858d304..073b10f9 100644 --- a/examples/widgets/widgets/calculator/calculator.cpp +++ b/examples/widgets/widgets/calculator/calculator.cpp @@ -29,29 +29,29 @@ Calculator::Calculator(QWidget *parent) //! [4] for (int i = 0; i < NumDigitButtons; ++i) - digitButtons[i] = createButton(QString::number(i), SLOT(digitClicked())); + digitButtons[i] = createButton(QString::number(i), &Calculator::digitClicked); - Button *pointButton = createButton(tr("."), SLOT(pointClicked())); - Button *changeSignButton = createButton(tr("\302\261"), SLOT(changeSignClicked())); + Button *pointButton = createButton(tr("."), &Calculator::pointClicked); + Button *changeSignButton = createButton(tr("\302\261"), &Calculator::changeSignClicked); - Button *backspaceButton = createButton(tr("Backspace"), SLOT(backspaceClicked())); - Button *clearButton = createButton(tr("Clear"), SLOT(clear())); - Button *clearAllButton = createButton(tr("Clear All"), SLOT(clearAll())); + Button *backspaceButton = createButton(tr("Backspace"), &Calculator::backspaceClicked); + Button *clearButton = createButton(tr("Clear"), &Calculator::clear); + Button *clearAllButton = createButton(tr("Clear All"), &Calculator::clearAll); - Button *clearMemoryButton = createButton(tr("MC"), SLOT(clearMemory())); - Button *readMemoryButton = createButton(tr("MR"), SLOT(readMemory())); - Button *setMemoryButton = createButton(tr("MS"), SLOT(setMemory())); - Button *addToMemoryButton = createButton(tr("M+"), SLOT(addToMemory())); + Button *clearMemoryButton = createButton(tr("MC"), &Calculator::clearMemory); + Button *readMemoryButton = createButton(tr("MR"), &Calculator::readMemory); + Button *setMemoryButton = createButton(tr("MS"), &Calculator::setMemory); + Button *addToMemoryButton = createButton(tr("M+"), &Calculator::addToMemory); - Button *divisionButton = createButton(tr("\303\267"), SLOT(multiplicativeOperatorClicked())); - Button *timesButton = createButton(tr("\303\227"), SLOT(multiplicativeOperatorClicked())); - Button *minusButton = createButton(tr("-"), SLOT(additiveOperatorClicked())); - Button *plusButton = createButton(tr("+"), SLOT(additiveOperatorClicked())); + Button *divisionButton = createButton(tr("\303\267"), &Calculator::multiplicativeOperatorClicked); + Button *timesButton = createButton(tr("\303\227"), &Calculator::multiplicativeOperatorClicked); + Button *minusButton = createButton(tr("-"), &Calculator::additiveOperatorClicked); + Button *plusButton = createButton(tr("+"), &Calculator::additiveOperatorClicked); - Button *squareRootButton = createButton(tr("Sqrt"), SLOT(unaryOperatorClicked())); - Button *powerButton = createButton(tr("x\302\262"), SLOT(unaryOperatorClicked())); - Button *reciprocalButton = createButton(tr("1/x"), SLOT(unaryOperatorClicked())); - Button *equalButton = createButton(tr("="), SLOT(equalClicked())); + Button *squareRootButton = createButton(tr("Sqrt"), &Calculator::unaryOperatorClicked); + Button *powerButton = createButton(tr("x\302\262"), &Calculator::unaryOperatorClicked); + Button *reciprocalButton = createButton(tr("1/x"), &Calculator::unaryOperatorClicked); + Button *equalButton = createButton(tr("="), &Calculator::equalClicked); //! [4] //! [5] @@ -324,10 +324,11 @@ void Calculator::addToMemory() } //! [32] //! [34] -Button *Calculator::createButton(const QString &text, const char *member) +template +Button *Calculator::createButton(const QString &text, const PointerToMemberFunction &member) { Button *button = new Button(text); - connect(button, SIGNAL(clicked()), this, member); + connect(button, &Button::clicked, this, member); return button; } //! [34] diff --git a/examples/widgets/widgets/calculator/calculator.h b/examples/widgets/widgets/calculator/calculator.h index d4fa1129..42e9ac53 100644 --- a/examples/widgets/widgets/calculator/calculator.h +++ b/examples/widgets/widgets/calculator/calculator.h @@ -39,7 +39,8 @@ private slots: //! [1] private: //! [1] //! [2] - Button *createButton(const QString &text, const char *member); + template + Button *createButton(const QString &text, const PointerToMemberFunction &member); void abortOperation(); bool calculate(double rightOperand, const QString &pendingOperator); //! [2] diff --git a/examples/widgets/widgets/tooltips/sortingbox.cpp b/examples/widgets/widgets/tooltips/sortingbox.cpp index 1eb745e7..ee588eea 100644 --- a/examples/widgets/widgets/tooltips/sortingbox.cpp +++ b/examples/widgets/widgets/tooltips/sortingbox.cpp @@ -26,15 +26,15 @@ SortingBox::SortingBox(QWidget *parent) //! [3] newCircleButton = createToolButton(tr("New Circle"), QIcon(":/images/circle.png"), - SLOT(createNewCircle())); + &SortingBox::createNewCircle); newSquareButton = createToolButton(tr("New Square"), QIcon(":/images/square.png"), - SLOT(createNewSquare())); + &SortingBox::createNewSquare); newTriangleButton = createToolButton(tr("New Triangle"), QIcon(":/images/triangle.png"), - SLOT(createNewTriangle())); + &SortingBox::createNewTriangle); circlePath.addEllipse(QRect(0, 0, 100, 100)); squarePath.addRect(QRect(0, 0, 100, 100)); @@ -226,14 +226,15 @@ void SortingBox::createShapeItem(const QPainterPath &path, //! [21] //! [22] +template QToolButton *SortingBox::createToolButton(const QString &toolTip, - const QIcon &icon, const char *member) + const QIcon &icon, const PointerToMemberFunction &member) { QToolButton *button = new QToolButton(this); button->setToolTip(toolTip); button->setIcon(icon); button->setIconSize(QSize(32, 32)); - connect(button, SIGNAL(clicked()), this, member); + connect(button, &QToolButton::clicked, this, member); return button; } diff --git a/examples/widgets/widgets/tooltips/sortingbox.h b/examples/widgets/widgets/tooltips/sortingbox.h index 935db0d2..9f34cf7a 100644 --- a/examples/widgets/widgets/tooltips/sortingbox.h +++ b/examples/widgets/widgets/tooltips/sortingbox.h @@ -48,9 +48,10 @@ private: QPoint randomItemPosition(); QColor initialItemColor(); QColor randomItemColor(); + template QToolButton *createToolButton(const QString &toolTip, const QIcon &icon, //! [1] - const char *member); + const PointerToMemberFunction &member); //! [2] QList shapeItems; diff --git a/mkspecs/common/wasm/wasm.conf b/mkspecs/common/wasm/wasm.conf index 6fb1fb6e..b67d7213 100644 --- a/mkspecs/common/wasm/wasm.conf +++ b/mkspecs/common/wasm/wasm.conf @@ -36,7 +36,8 @@ EMCC_COMMON_LFLAGS += \ -s FETCH=1 \ -s MODULARIZE=1 \ -s EXPORT_NAME=createQtAppInstance \ - -s WASM_BIGINT=1 + -s WASM_BIGINT=1 \ + -s STACK_SIZE=5MB # The -s arguments can also be used with release builds, # but are here in debug for clarity. diff --git a/mkspecs/devices/integrity-armv8-SA8155P/qmake.conf b/mkspecs/devices/integrity-armv8-SA8155P/qmake.conf index 42e4fe3c..f1696e36 100644 --- a/mkspecs/devices/integrity-armv8-SA8155P/qmake.conf +++ b/mkspecs/devices/integrity-armv8-SA8155P/qmake.conf @@ -6,7 +6,7 @@ load(device_config) include(../../common/ghs-integrity-armv8.conf) -QMAKE_CFLAGS = --signed_fields --no_commons --diag_suppress=1,82,228,236,381,611,961,997,1795,1931,1974,3148 --defer_parse_function_templates --exceptions +QMAKE_CFLAGS = --signed_fields --no_commons --diag_suppress=1,82,228,236,381,611,961,997,1795,1931,1974,3148,1721,1424,193,940,620,111,128,185,826,186,68,830,177,554,550,175,1441,546 --defer_parse_function_templates --exceptions QMAKE_CXXFLAGS = $$QMAKE_CFLAGS --no_implicit_include --link_once_templates -non_shared --new_outside_of_constructor --c++17 QMAKE_LFLAGS += --thread_local_storage --exceptions diff --git a/mkspecs/features/mac/default_post.prf b/mkspecs/features/mac/default_post.prf index 4acf3b19..f3647167 100644 --- a/mkspecs/features/mac/default_post.prf +++ b/mkspecs/features/mac/default_post.prf @@ -50,29 +50,6 @@ contains(TEMPLATE, .*app) { !no_objective_c:CONFIG += objective_c -qt { - qtConfig(static) { - # C++11 support means using libc++ instead of libstd++. As the - # two libraries are incompatible we need to ensure the end user - # project is built using the same C++11 support/no support as Qt. - qtConfig(c++11) { - CONFIG += c++11 - } else: c++11 { - warning("Qt was not built with C++11 enabled, disabling feature") - CONFIG -= c++11 - } - - !c++11 { - # Explicitly use libstdc++ if C++11 support is not enabled, - # as otherwise the compiler will choose the standard library - # based on the deployment target, which for iOS 7 and OS X 10.9 - # is libc++, and we can't mix and match the two. - QMAKE_CXXFLAGS += -stdlib=libstdc++ - QMAKE_LFLAGS += -stdlib=libstdc++ - } - } -} - # Add the same default rpaths as Xcode does for new projects. # This is especially important for iOS/tvOS/watchOS where no other option is possible. !no_default_rpath { diff --git a/mkspecs/features/qt.prf b/mkspecs/features/qt.prf index 114c729f..d6a87b75 100644 --- a/mkspecs/features/qt.prf +++ b/mkspecs/features/qt.prf @@ -2,6 +2,9 @@ # due to required Qt modules being missing. !isEmpty(QMAKE_FAILED_REQUIREMENTS): return() +# hardcoded defaults +QT_CONFIG *= c99 c11 c++11 c++14 c++1z c++17 + qtConfig(thread): CONFIG *= thread #handle defines diff --git a/mkspecs/features/wasm/emcc_ver.prf b/mkspecs/features/wasm/emcc_ver.prf index 9d042df3..160ae639 100644 --- a/mkspecs/features/wasm/emcc_ver.prf +++ b/mkspecs/features/wasm/emcc_ver.prf @@ -1,5 +1,5 @@ defineReplace(qtEmccRecommendedVersion) { - return (3.1.25) + return (3.1.37) } defineReplace(qtSystemEmccVersion) { diff --git a/mkspecs/features/wasm/wasm.prf b/mkspecs/features/wasm/wasm.prf index 12230143..018ca904 100644 --- a/mkspecs/features/wasm/wasm.prf +++ b/mkspecs/features/wasm/wasm.prf @@ -9,9 +9,9 @@ exists($$QMAKE_QT_CONFIG) { ## qmake puts a space if done otherwise !isEmpty(QT_WASM_EXTRA_EXPORTED_METHODS): { - EXPORTED_METHODS = UTF16ToString,stringToUTF16,JSEvents,specialHTMLTargets,$$QT_WASM_EXTRA_EXPORTED_METHODS + EXPORTED_METHODS = UTF16ToString,stringToUTF16,JSEvents,specialHTMLTargets,FS,$$QT_WASM_EXTRA_EXPORTED_METHODS } else { - EXPORTED_METHODS = UTF16ToString,stringToUTF16,JSEvents,specialHTMLTargets + EXPORTED_METHODS = UTF16ToString,stringToUTF16,JSEvents,specialHTMLTargets,FS } EMCC_LFLAGS += -s EXPORTED_RUNTIME_METHODS=$$EXPORTED_METHODS @@ -47,6 +47,17 @@ exists($$QMAKE_QT_CONFIG) { QMAKE_LFLAGS_DEBUG += -msimd128 -msse -msse2 } + qtConfig(shared) { + contains(TEMPLATE, .*app) { + EMCC_MODULE_FLAGS = -s MAIN_MODULE=1 + } + contains(TEMPLATE, .*lib):!static: { + EMCC_MODULE_FLAGS = -s SIDE_MODULE=1 + } + EMCC_CFLAGS += $$EMCC_MODULE_FLAGS + EMCC_LFLAGS += $$EMCC_MODULE_FLAGS + } + QMAKE_LFLAGS += $$EMCC_LFLAGS QMAKE_LFLAGS_DEBUG += $$EMCC_LFLAGS QMAKE_CFLAGS += $$EMCC_CFLAGS @@ -81,11 +92,16 @@ contains(TEMPLATE, .*app) { WASM_PLUGIN_PATH = $$PWD/../../../src/plugins/platforms/wasm } + PRELOAD = "" + shared { + PRELOAD = "preload:\ ['qt_plugins.json',\ 'qt_qml_imports.json']," + } + # Copy/Generate main .html file (e.g. myapp.html) from the webassembly_shell.html by # replacing the app name placeholder with the actual app name. apphtml.name = application main html file apphtml.output = $$DESTDIR/$$TARGET_HTML - apphtml.commands = $$QMAKE_STREAM_EDITOR -e s/@APPNAME@/$$TARGET_BASE/g $$WASM_PLUGIN_PATH/wasm_shell.html > $$DESTDIR/$$TARGET_HTML + apphtml.commands = $$QMAKE_STREAM_EDITOR -e s/@APPNAME@/$$TARGET_BASE/g -e s/@PRELOAD@/$$PRELOAD/g $$WASM_PLUGIN_PATH/wasm_shell.html > $$DESTDIR/$$TARGET_HTML apphtml.input = $$WASM_PLUGIN_PATH/wasm_shell.html apphtml.depends = $$apphtml.input QMAKE_EXTRA_COMPILERS += apphtml diff --git a/qmake/CMakeLists.txt b/qmake/CMakeLists.txt index d3e8f56a..41bf2c77 100644 --- a/qmake/CMakeLists.txt +++ b/qmake/CMakeLists.txt @@ -118,7 +118,6 @@ qt_internal_extend_target(${target_name} CONDITION MACOS "-fconstant-cfstrings" ) -# special case big qt_internal_extend_target(${target_name} CONDITION WIN32 SOURCES library/registry.cpp @@ -132,18 +131,15 @@ qt_internal_extend_target(${target_name} CONDITION CLANG AND WIN32 "-Wno-microsoft-enum-value" ) -# special case: set_target_properties(${target_name} PROPERTIES AUTOMOC OFF AUTORCC OFF AUTOUIC OFF ) -qt_internal_apply_gc_binaries(${target_name} PRIVATE) # special case -qt_skip_warnings_are_errors(${target_name}) # special case +qt_internal_apply_gc_binaries(${target_name} PRIVATE) +qt_skip_warnings_are_errors(${target_name}) -# special case begin qt_internal_add_docs(${target_name} doc/qmake.qdocconf ) -# special case end diff --git a/qt_cmdline.cmake b/qt_cmdline.cmake index 372e6873..28e8bcc3 100644 --- a/qt_cmdline.cmake +++ b/qt_cmdline.cmake @@ -46,6 +46,7 @@ qt_commandline_option(c++std TYPE cxxstd) qt_commandline_option(unity-build TYPE boolean NAME unity_build) qt_commandline_option(unity-build-batch-size TYPE string NAME unity_build_batch_size) qt_commandline_option(ccache TYPE boolean NAME ccache) +qt_commandline_option(vcpkg TYPE boolean) qt_commandline_option(commercial TYPE void) qt_commandline_option(confirm-license TYPE void) qt_commandline_option(dbus TYPE optionalString VALUES no yes linked runtime) @@ -63,7 +64,6 @@ qt_commandline_option(force-pkg-config TYPE void NAME pkg-config) qt_commandline_option(framework TYPE boolean) qt_commandline_option(gc-binaries TYPE boolean NAME gc_binaries) qt_commandline_option(gdb-index TYPE boolean NAME enable_gdb_index) -qt_commandline_option(gcc-sysroot TYPE boolean) qt_commandline_option(gcov TYPE boolean) qt_commandline_option(gnumake TYPE boolean NAME GNUmake) qt_commandline_option(gui TYPE boolean) @@ -76,16 +76,13 @@ qt_commandline_option(openssl-runtime TYPE void NAME openssl VALUE runtime) qt_commandline_option(linker TYPE optionalString VALUES bfd gold lld mold) qt_commandline_option(ltcg TYPE boolean) qt_commandline_option(intelcet TYPE boolean) -# special case begin qt_commandline_option(make TYPE addString VALUES examples libs tests tools benchmarks manual-tests minimal-static-tests) -# special case end -qt_commandline_option(make-tool TYPE string) +qt_commandline_option(install-examples-sources TYPE boolean) qt_commandline_option(mips_dsp TYPE boolean) qt_commandline_option(mips_dspr2 TYPE boolean) -qt_commandline_option(mp TYPE boolean NAME msvc_mp) qt_commandline_option(nomake TYPE addString VALUES examples tests tools benchmarks - manual-tests minimal-static-tests) # special case + manual-tests minimal-static-tests) qt_commandline_option(opensource TYPE void NAME commercial VALUE no) qt_commandline_option(optimize-debug TYPE boolean NAME optimize_debug) qt_commandline_option(optimize-size TYPE boolean NAME optimize_size) diff --git a/src/3rdparty/D3D12MemoryAllocator/D3D12MemAlloc.cpp b/src/3rdparty/D3D12MemoryAllocator/D3D12MemAlloc.cpp new file mode 100644 index 00000000..248ec409 --- /dev/null +++ b/src/3rdparty/D3D12MemoryAllocator/D3D12MemAlloc.cpp @@ -0,0 +1,10565 @@ +// +// Copyright (c) 2019-2022 Advanced Micro Devices, Inc. All rights reserved. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. +// + +#include "D3D12MemAlloc.h" + +#include +#include +#include +#include +#include +#include +#include // for _aligned_malloc, _aligned_free +#ifndef _WIN32 + #include +#endif + +//////////////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////// +// +// Configuration Begin +// +//////////////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////// +#ifndef _D3D12MA_CONFIGURATION + +#ifdef _WIN32 + #if !defined(WINVER) || WINVER < 0x0600 + #error Required at least WinAPI version supporting: client = Windows Vista, server = Windows Server 2008. + #endif +#endif + +#ifndef D3D12MA_SORT + #define D3D12MA_SORT(beg, end, cmp) std::sort(beg, end, cmp) +#endif + +#ifndef D3D12MA_D3D12_HEADERS_ALREADY_INCLUDED + #include + #if D3D12MA_DXGI_1_4 + #include + #endif +#endif + +#ifndef D3D12MA_ASSERT + #include + #define D3D12MA_ASSERT(cond) assert(cond) +#endif + +// Assert that will be called very often, like inside data structures e.g. operator[]. +// Making it non-empty can make program slow. +#ifndef D3D12MA_HEAVY_ASSERT + #ifdef _DEBUG + #define D3D12MA_HEAVY_ASSERT(expr) //D3D12MA_ASSERT(expr) + #else + #define D3D12MA_HEAVY_ASSERT(expr) + #endif +#endif + +#ifndef D3D12MA_DEBUG_ALIGNMENT + /* + Minimum alignment of all allocations, in bytes. + Set to more than 1 for debugging purposes only. Must be power of two. + */ + #define D3D12MA_DEBUG_ALIGNMENT (1) +#endif + +#ifndef D3D12MA_DEBUG_MARGIN + // Minimum margin before and after every allocation, in bytes. + // Set nonzero for debugging purposes only. + #define D3D12MA_DEBUG_MARGIN (0) +#endif + +#ifndef D3D12MA_DEBUG_GLOBAL_MUTEX + /* + Set this to 1 for debugging purposes only, to enable single mutex protecting all + entry calls to the library. Can be useful for debugging multithreading issues. + */ + #define D3D12MA_DEBUG_GLOBAL_MUTEX (0) +#endif + +/* +Define this macro for debugging purposes only to force specific D3D12_RESOURCE_HEAP_TIER, +especially to test compatibility with D3D12_RESOURCE_HEAP_TIER_1 on modern GPUs. +*/ +//#define D3D12MA_FORCE_RESOURCE_HEAP_TIER D3D12_RESOURCE_HEAP_TIER_1 + +#ifndef D3D12MA_DEFAULT_BLOCK_SIZE + /// Default size of a block allocated as single ID3D12Heap. + #define D3D12MA_DEFAULT_BLOCK_SIZE (64ull * 1024 * 1024) +#endif + +#ifndef D3D12MA_DEBUG_LOG + #define D3D12MA_DEBUG_LOG(format, ...) + /* + #define D3D12MA_DEBUG_LOG(format, ...) do { \ + wprintf(format, __VA_ARGS__); \ + wprintf(L"\n"); \ + } while(false) + */ +#endif + +#endif // _D3D12MA_CONFIGURATION +//////////////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////// +// +// Configuration End +// +//////////////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////// + +#define D3D12MA_IID_PPV_ARGS(ppType) __uuidof(**(ppType)), reinterpret_cast(ppType) + +#ifdef __ID3D12Device8_INTERFACE_DEFINED__ + #define D3D12MA_CREATE_NOT_ZEROED_AVAILABLE 1 +#endif + +#if defined(__clang__) || defined(__GNUC__) +#pragma GCC diagnostic push +#pragma GCC diagnostic ignored "-Wunused-parameter" +#pragma GCC diagnostic ignored "-Wunused-variable" +#pragma GCC diagnostic ignored "-Wsign-compare" +#pragma GCC diagnostic ignored "-Wmissing-field-initializers" +#pragma GCC diagnostic ignored "-Wswitch" +#pragma GCC diagnostic ignored "-Wimplicit-fallthrough" +#pragma GCC diagnostic ignored "-Wunused-function" +#pragma GCC diagnostic ignored "-Wnonnull-compare" +#endif + +namespace D3D12MA +{ +static constexpr UINT HEAP_TYPE_COUNT = 4; +static constexpr UINT STANDARD_HEAP_TYPE_COUNT = 3; // Only DEFAULT, UPLOAD, READBACK. +static constexpr UINT DEFAULT_POOL_MAX_COUNT = 9; +static const UINT NEW_BLOCK_SIZE_SHIFT_MAX = 3; +// Minimum size of a free suballocation to register it in the free suballocation collection. +static const UINT64 MIN_FREE_SUBALLOCATION_SIZE_TO_REGISTER = 16; + +static const WCHAR* const HeapTypeNames[] = +{ + L"DEFAULT", + L"UPLOAD", + L"READBACK", + L"CUSTOM", +}; + +static const D3D12_HEAP_FLAGS RESOURCE_CLASS_HEAP_FLAGS = + D3D12_HEAP_FLAG_DENY_BUFFERS | D3D12_HEAP_FLAG_DENY_RT_DS_TEXTURES | D3D12_HEAP_FLAG_DENY_NON_RT_DS_TEXTURES; + +static const D3D12_RESIDENCY_PRIORITY D3D12_RESIDENCY_PRIORITY_NONE = D3D12_RESIDENCY_PRIORITY(0); + +#ifndef _D3D12MA_ENUM_DECLARATIONS + +// Local copy of this enum, as it is provided only by , so it may not be available. +enum DXGI_MEMORY_SEGMENT_GROUP_COPY +{ + DXGI_MEMORY_SEGMENT_GROUP_LOCAL_COPY = 0, + DXGI_MEMORY_SEGMENT_GROUP_NON_LOCAL_COPY = 1, + DXGI_MEMORY_SEGMENT_GROUP_COUNT +}; + +enum class ResourceClass +{ + Unknown, Buffer, Non_RT_DS_Texture, RT_DS_Texture +}; + +enum SuballocationType +{ + SUBALLOCATION_TYPE_FREE = 0, + SUBALLOCATION_TYPE_ALLOCATION = 1, +}; + +#endif // _D3D12MA_ENUM_DECLARATIONS + + +#ifndef _D3D12MA_FUNCTIONS + +static void* DefaultAllocate(size_t Size, size_t Alignment, void* /*pPrivateData*/) +{ +#ifdef _WIN32 + return _aligned_malloc(Size, Alignment); +#else + return aligned_alloc(Alignment, Size); +#endif +} +static void DefaultFree(void* pMemory, void* /*pPrivateData*/) +{ +#ifdef _WIN32 + return _aligned_free(pMemory); +#else + return free(pMemory); +#endif +} + +static void* Malloc(const ALLOCATION_CALLBACKS& allocs, size_t size, size_t alignment) +{ + void* const result = (*allocs.pAllocate)(size, alignment, allocs.pPrivateData); + D3D12MA_ASSERT(result); + return result; +} +static void Free(const ALLOCATION_CALLBACKS& allocs, void* memory) +{ + (*allocs.pFree)(memory, allocs.pPrivateData); +} + +template +static T* Allocate(const ALLOCATION_CALLBACKS& allocs) +{ + return (T*)Malloc(allocs, sizeof(T), __alignof(T)); +} +template +static T* AllocateArray(const ALLOCATION_CALLBACKS& allocs, size_t count) +{ + return (T*)Malloc(allocs, sizeof(T) * count, __alignof(T)); +} + +#define D3D12MA_NEW(allocs, type) new(D3D12MA::Allocate(allocs))(type) +#define D3D12MA_NEW_ARRAY(allocs, type, count) new(D3D12MA::AllocateArray((allocs), (count)))(type) + +template +void D3D12MA_DELETE(const ALLOCATION_CALLBACKS& allocs, T* memory) +{ + if (memory) + { + memory->~T(); + Free(allocs, memory); + } +} +template +void D3D12MA_DELETE_ARRAY(const ALLOCATION_CALLBACKS& allocs, T* memory, size_t count) +{ + if (memory) + { + for (size_t i = count; i--; ) + { + memory[i].~T(); + } + Free(allocs, memory); + } +} + +static void SetupAllocationCallbacks(ALLOCATION_CALLBACKS& outAllocs, const ALLOCATION_CALLBACKS* allocationCallbacks) +{ + if (allocationCallbacks) + { + outAllocs = *allocationCallbacks; + D3D12MA_ASSERT(outAllocs.pAllocate != NULL && outAllocs.pFree != NULL); + } + else + { + outAllocs.pAllocate = &DefaultAllocate; + outAllocs.pFree = &DefaultFree; + outAllocs.pPrivateData = NULL; + } +} + +#define SAFE_RELEASE(ptr) do { if(ptr) { (ptr)->Release(); (ptr) = NULL; } } while(false) + +#define D3D12MA_VALIDATE(cond) do { if(!(cond)) { \ + D3D12MA_ASSERT(0 && "Validation failed: " #cond); \ + return false; \ +} } while(false) + +template +static T D3D12MA_MIN(const T& a, const T& b) { return a <= b ? a : b; } +template +static T D3D12MA_MAX(const T& a, const T& b) { return a <= b ? b : a; } + +template +static void D3D12MA_SWAP(T& a, T& b) { T tmp = a; a = b; b = tmp; } + +// Scans integer for index of first nonzero bit from the Least Significant Bit (LSB). If mask is 0 then returns UINT8_MAX +static UINT8 BitScanLSB(UINT64 mask) +{ +#if defined(_MSC_VER) && defined(_WIN64) + unsigned long pos; + if (_BitScanForward64(&pos, mask)) + return static_cast(pos); + return UINT8_MAX; +#elif defined __GNUC__ || defined __clang__ + return static_cast(__builtin_ffsll(mask)) - 1U; +#else + UINT8 pos = 0; + UINT64 bit = 1; + do + { + if (mask & bit) + return pos; + bit <<= 1; + } while (pos++ < 63); + return UINT8_MAX; +#endif +} +// Scans integer for index of first nonzero bit from the Least Significant Bit (LSB). If mask is 0 then returns UINT8_MAX +static UINT8 BitScanLSB(UINT32 mask) +{ +#ifdef _MSC_VER + unsigned long pos; + if (_BitScanForward(&pos, mask)) + return static_cast(pos); + return UINT8_MAX; +#elif defined __GNUC__ || defined __clang__ + return static_cast(__builtin_ffs(mask)) - 1U; +#else + UINT8 pos = 0; + UINT32 bit = 1; + do + { + if (mask & bit) + return pos; + bit <<= 1; + } while (pos++ < 31); + return UINT8_MAX; +#endif +} + +// Scans integer for index of first nonzero bit from the Most Significant Bit (MSB). If mask is 0 then returns UINT8_MAX +static UINT8 BitScanMSB(UINT64 mask) +{ +#if defined(_MSC_VER) && defined(_WIN64) + unsigned long pos; + if (_BitScanReverse64(&pos, mask)) + return static_cast(pos); +#elif defined __GNUC__ || defined __clang__ + if (mask) + return 63 - static_cast(__builtin_clzll(mask)); +#else + UINT8 pos = 63; + UINT64 bit = 1ULL << 63; + do + { + if (mask & bit) + return pos; + bit >>= 1; + } while (pos-- > 0); +#endif + return UINT8_MAX; +} +// Scans integer for index of first nonzero bit from the Most Significant Bit (MSB). If mask is 0 then returns UINT8_MAX +static UINT8 BitScanMSB(UINT32 mask) +{ +#ifdef _MSC_VER + unsigned long pos; + if (_BitScanReverse(&pos, mask)) + return static_cast(pos); +#elif defined __GNUC__ || defined __clang__ + if (mask) + return 31 - static_cast(__builtin_clz(mask)); +#else + UINT8 pos = 31; + UINT32 bit = 1UL << 31; + do + { + if (mask & bit) + return pos; + bit >>= 1; + } while (pos-- > 0); +#endif + return UINT8_MAX; +} + +/* +Returns true if given number is a power of two. +T must be unsigned integer number or signed integer but always nonnegative. +For 0 returns true. +*/ +template +static bool IsPow2(T x) { return (x & (x - 1)) == 0; } + +// Aligns given value up to nearest multiply of align value. For example: AlignUp(11, 8) = 16. +// Use types like UINT, uint64_t as T. +template +static T AlignUp(T val, T alignment) +{ + D3D12MA_HEAVY_ASSERT(IsPow2(alignment)); + return (val + alignment - 1) & ~(alignment - 1); +} +// Aligns given value down to nearest multiply of align value. For example: AlignUp(11, 8) = 8. +// Use types like UINT, uint64_t as T. +template +static T AlignDown(T val, T alignment) +{ + D3D12MA_HEAVY_ASSERT(IsPow2(alignment)); + return val & ~(alignment - 1); +} + +// Division with mathematical rounding to nearest number. +template +static T RoundDiv(T x, T y) { return (x + (y / (T)2)) / y; } +template +static T DivideRoundingUp(T x, T y) { return (x + y - 1) / y; } + +static WCHAR HexDigitToChar(UINT8 digit) +{ + if(digit < 10) + return L'0' + digit; + else + return L'A' + (digit - 10); +} + +/* +Performs binary search and returns iterator to first element that is greater or +equal to `key`, according to comparison `cmp`. + +Cmp should return true if first argument is less than second argument. + +Returned value is the found element, if present in the collection or place where +new element with value (key) should be inserted. +*/ +template +static IterT BinaryFindFirstNotLess(IterT beg, IterT end, const KeyT& key, const CmpLess& cmp) +{ + size_t down = 0, up = (end - beg); + while (down < up) + { + const size_t mid = (down + up) / 2; + if (cmp(*(beg + mid), key)) + { + down = mid + 1; + } + else + { + up = mid; + } + } + return beg + down; +} + +/* +Performs binary search and returns iterator to an element that is equal to `key`, +according to comparison `cmp`. + +Cmp should return true if first argument is less than second argument. + +Returned value is the found element, if present in the collection or end if not +found. +*/ +template +static IterT BinaryFindSorted(const IterT& beg, const IterT& end, const KeyT& value, const CmpLess& cmp) +{ + IterT it = BinaryFindFirstNotLess(beg, end, value, cmp); + if (it == end || + (!cmp(*it, value) && !cmp(value, *it))) + { + return it; + } + return end; +} + +static UINT HeapTypeToIndex(D3D12_HEAP_TYPE type) +{ + switch (type) + { + case D3D12_HEAP_TYPE_DEFAULT: return 0; + case D3D12_HEAP_TYPE_UPLOAD: return 1; + case D3D12_HEAP_TYPE_READBACK: return 2; + case D3D12_HEAP_TYPE_CUSTOM: return 3; + default: D3D12MA_ASSERT(0); return UINT_MAX; + } +} + +static D3D12_HEAP_TYPE IndexToHeapType(UINT heapTypeIndex) +{ + D3D12MA_ASSERT(heapTypeIndex < 4); + // D3D12_HEAP_TYPE_DEFAULT starts at 1. + return (D3D12_HEAP_TYPE)(heapTypeIndex + 1); +} + +static UINT64 HeapFlagsToAlignment(D3D12_HEAP_FLAGS flags, bool denyMsaaTextures) +{ + /* + Documentation of D3D12_HEAP_DESC structure says: + + - D3D12_DEFAULT_RESOURCE_PLACEMENT_ALIGNMENT defined as 64KB. + - D3D12_DEFAULT_MSAA_RESOURCE_PLACEMENT_ALIGNMENT defined as 4MB. An + application must decide whether the heap will contain multi-sample + anti-aliasing (MSAA), in which case, the application must choose [this flag]. + + https://docs.microsoft.com/en-us/windows/desktop/api/d3d12/ns-d3d12-d3d12_heap_desc + */ + + if (denyMsaaTextures) + return D3D12_DEFAULT_RESOURCE_PLACEMENT_ALIGNMENT; + + const D3D12_HEAP_FLAGS denyAllTexturesFlags = + D3D12_HEAP_FLAG_DENY_NON_RT_DS_TEXTURES | D3D12_HEAP_FLAG_DENY_RT_DS_TEXTURES; + const bool canContainAnyTextures = + (flags & denyAllTexturesFlags) != denyAllTexturesFlags; + return canContainAnyTextures ? + D3D12_DEFAULT_MSAA_RESOURCE_PLACEMENT_ALIGNMENT : D3D12_DEFAULT_RESOURCE_PLACEMENT_ALIGNMENT; +} + +static ResourceClass HeapFlagsToResourceClass(D3D12_HEAP_FLAGS heapFlags) +{ + const bool allowBuffers = (heapFlags & D3D12_HEAP_FLAG_DENY_BUFFERS) == 0; + const bool allowRtDsTextures = (heapFlags & D3D12_HEAP_FLAG_DENY_RT_DS_TEXTURES) == 0; + const bool allowNonRtDsTextures = (heapFlags & D3D12_HEAP_FLAG_DENY_NON_RT_DS_TEXTURES) == 0; + + const uint8_t allowedGroupCount = (allowBuffers ? 1 : 0) + (allowRtDsTextures ? 1 : 0) + (allowNonRtDsTextures ? 1 : 0); + if (allowedGroupCount != 1) + return ResourceClass::Unknown; + + if (allowRtDsTextures) + return ResourceClass::RT_DS_Texture; + if (allowNonRtDsTextures) + return ResourceClass::Non_RT_DS_Texture; + return ResourceClass::Buffer; +} + +static bool IsHeapTypeStandard(D3D12_HEAP_TYPE type) +{ + return type == D3D12_HEAP_TYPE_DEFAULT || + type == D3D12_HEAP_TYPE_UPLOAD || + type == D3D12_HEAP_TYPE_READBACK; +} + +static D3D12_HEAP_PROPERTIES StandardHeapTypeToHeapProperties(D3D12_HEAP_TYPE type) +{ + D3D12MA_ASSERT(IsHeapTypeStandard(type)); + D3D12_HEAP_PROPERTIES result = {}; + result.Type = type; + return result; +} + +static bool IsFormatCompressed(DXGI_FORMAT format) +{ + switch (format) + { + case DXGI_FORMAT_BC1_TYPELESS: + case DXGI_FORMAT_BC1_UNORM: + case DXGI_FORMAT_BC1_UNORM_SRGB: + case DXGI_FORMAT_BC2_TYPELESS: + case DXGI_FORMAT_BC2_UNORM: + case DXGI_FORMAT_BC2_UNORM_SRGB: + case DXGI_FORMAT_BC3_TYPELESS: + case DXGI_FORMAT_BC3_UNORM: + case DXGI_FORMAT_BC3_UNORM_SRGB: + case DXGI_FORMAT_BC4_TYPELESS: + case DXGI_FORMAT_BC4_UNORM: + case DXGI_FORMAT_BC4_SNORM: + case DXGI_FORMAT_BC5_TYPELESS: + case DXGI_FORMAT_BC5_UNORM: + case DXGI_FORMAT_BC5_SNORM: + case DXGI_FORMAT_BC6H_TYPELESS: + case DXGI_FORMAT_BC6H_UF16: + case DXGI_FORMAT_BC6H_SF16: + case DXGI_FORMAT_BC7_TYPELESS: + case DXGI_FORMAT_BC7_UNORM: + case DXGI_FORMAT_BC7_UNORM_SRGB: + return true; + default: + return false; + } +} + +// Only some formats are supported. For others it returns 0. +static UINT GetBitsPerPixel(DXGI_FORMAT format) +{ + switch (format) + { + case DXGI_FORMAT_R32G32B32A32_TYPELESS: + case DXGI_FORMAT_R32G32B32A32_FLOAT: + case DXGI_FORMAT_R32G32B32A32_UINT: + case DXGI_FORMAT_R32G32B32A32_SINT: + return 128; + case DXGI_FORMAT_R32G32B32_TYPELESS: + case DXGI_FORMAT_R32G32B32_FLOAT: + case DXGI_FORMAT_R32G32B32_UINT: + case DXGI_FORMAT_R32G32B32_SINT: + return 96; + case DXGI_FORMAT_R16G16B16A16_TYPELESS: + case DXGI_FORMAT_R16G16B16A16_FLOAT: + case DXGI_FORMAT_R16G16B16A16_UNORM: + case DXGI_FORMAT_R16G16B16A16_UINT: + case DXGI_FORMAT_R16G16B16A16_SNORM: + case DXGI_FORMAT_R16G16B16A16_SINT: + return 64; + case DXGI_FORMAT_R32G32_TYPELESS: + case DXGI_FORMAT_R32G32_FLOAT: + case DXGI_FORMAT_R32G32_UINT: + case DXGI_FORMAT_R32G32_SINT: + return 64; + case DXGI_FORMAT_R32G8X24_TYPELESS: + case DXGI_FORMAT_D32_FLOAT_S8X24_UINT: + case DXGI_FORMAT_R32_FLOAT_X8X24_TYPELESS: + case DXGI_FORMAT_X32_TYPELESS_G8X24_UINT: + return 64; + case DXGI_FORMAT_R10G10B10A2_TYPELESS: + case DXGI_FORMAT_R10G10B10A2_UNORM: + case DXGI_FORMAT_R10G10B10A2_UINT: + case DXGI_FORMAT_R11G11B10_FLOAT: + return 32; + case DXGI_FORMAT_R8G8B8A8_TYPELESS: + case DXGI_FORMAT_R8G8B8A8_UNORM: + case DXGI_FORMAT_R8G8B8A8_UNORM_SRGB: + case DXGI_FORMAT_R8G8B8A8_UINT: + case DXGI_FORMAT_R8G8B8A8_SNORM: + case DXGI_FORMAT_R8G8B8A8_SINT: + return 32; + case DXGI_FORMAT_R16G16_TYPELESS: + case DXGI_FORMAT_R16G16_FLOAT: + case DXGI_FORMAT_R16G16_UNORM: + case DXGI_FORMAT_R16G16_UINT: + case DXGI_FORMAT_R16G16_SNORM: + case DXGI_FORMAT_R16G16_SINT: + return 32; + case DXGI_FORMAT_R32_TYPELESS: + case DXGI_FORMAT_D32_FLOAT: + case DXGI_FORMAT_R32_FLOAT: + case DXGI_FORMAT_R32_UINT: + case DXGI_FORMAT_R32_SINT: + return 32; + case DXGI_FORMAT_R24G8_TYPELESS: + case DXGI_FORMAT_D24_UNORM_S8_UINT: + case DXGI_FORMAT_R24_UNORM_X8_TYPELESS: + case DXGI_FORMAT_X24_TYPELESS_G8_UINT: + return 32; + case DXGI_FORMAT_R8G8_TYPELESS: + case DXGI_FORMAT_R8G8_UNORM: + case DXGI_FORMAT_R8G8_UINT: + case DXGI_FORMAT_R8G8_SNORM: + case DXGI_FORMAT_R8G8_SINT: + return 16; + case DXGI_FORMAT_R16_TYPELESS: + case DXGI_FORMAT_R16_FLOAT: + case DXGI_FORMAT_D16_UNORM: + case DXGI_FORMAT_R16_UNORM: + case DXGI_FORMAT_R16_UINT: + case DXGI_FORMAT_R16_SNORM: + case DXGI_FORMAT_R16_SINT: + return 16; + case DXGI_FORMAT_R8_TYPELESS: + case DXGI_FORMAT_R8_UNORM: + case DXGI_FORMAT_R8_UINT: + case DXGI_FORMAT_R8_SNORM: + case DXGI_FORMAT_R8_SINT: + case DXGI_FORMAT_A8_UNORM: + return 8; + case DXGI_FORMAT_BC1_TYPELESS: + case DXGI_FORMAT_BC1_UNORM: + case DXGI_FORMAT_BC1_UNORM_SRGB: + return 4; + case DXGI_FORMAT_BC2_TYPELESS: + case DXGI_FORMAT_BC2_UNORM: + case DXGI_FORMAT_BC2_UNORM_SRGB: + return 8; + case DXGI_FORMAT_BC3_TYPELESS: + case DXGI_FORMAT_BC3_UNORM: + case DXGI_FORMAT_BC3_UNORM_SRGB: + return 8; + case DXGI_FORMAT_BC4_TYPELESS: + case DXGI_FORMAT_BC4_UNORM: + case DXGI_FORMAT_BC4_SNORM: + return 4; + case DXGI_FORMAT_BC5_TYPELESS: + case DXGI_FORMAT_BC5_UNORM: + case DXGI_FORMAT_BC5_SNORM: + return 8; + case DXGI_FORMAT_BC6H_TYPELESS: + case DXGI_FORMAT_BC6H_UF16: + case DXGI_FORMAT_BC6H_SF16: + return 8; + case DXGI_FORMAT_BC7_TYPELESS: + case DXGI_FORMAT_BC7_UNORM: + case DXGI_FORMAT_BC7_UNORM_SRGB: + return 8; + default: + return 0; + } +} + +template +static ResourceClass ResourceDescToResourceClass(const D3D12_RESOURCE_DESC_T& resDesc) +{ + if (resDesc.Dimension == D3D12_RESOURCE_DIMENSION_BUFFER) + return ResourceClass::Buffer; + // Else: it's surely a texture. + const bool isRenderTargetOrDepthStencil = + (resDesc.Flags & (D3D12_RESOURCE_FLAG_ALLOW_RENDER_TARGET | D3D12_RESOURCE_FLAG_ALLOW_DEPTH_STENCIL)) != 0; + return isRenderTargetOrDepthStencil ? ResourceClass::RT_DS_Texture : ResourceClass::Non_RT_DS_Texture; +} + +// This algorithm is overly conservative. +template +static bool CanUseSmallAlignment(const D3D12_RESOURCE_DESC_T& resourceDesc) +{ + if (resourceDesc.Dimension != D3D12_RESOURCE_DIMENSION_TEXTURE2D) + return false; + if ((resourceDesc.Flags & (D3D12_RESOURCE_FLAG_ALLOW_RENDER_TARGET | D3D12_RESOURCE_FLAG_ALLOW_DEPTH_STENCIL)) != 0) + return false; + if (resourceDesc.SampleDesc.Count > 1) + return false; + if (resourceDesc.DepthOrArraySize != 1) + return false; + + UINT sizeX = (UINT)resourceDesc.Width; + UINT sizeY = resourceDesc.Height; + UINT bitsPerPixel = GetBitsPerPixel(resourceDesc.Format); + if (bitsPerPixel == 0) + return false; + + if (IsFormatCompressed(resourceDesc.Format)) + { + sizeX = DivideRoundingUp(sizeX, 4u); + sizeY = DivideRoundingUp(sizeY, 4u); + bitsPerPixel *= 16; + } + + UINT tileSizeX = 0, tileSizeY = 0; + switch (bitsPerPixel) + { + case 8: tileSizeX = 64; tileSizeY = 64; break; + case 16: tileSizeX = 64; tileSizeY = 32; break; + case 32: tileSizeX = 32; tileSizeY = 32; break; + case 64: tileSizeX = 32; tileSizeY = 16; break; + case 128: tileSizeX = 16; tileSizeY = 16; break; + default: return false; + } + + const UINT tileCount = DivideRoundingUp(sizeX, tileSizeX) * DivideRoundingUp(sizeY, tileSizeY); + return tileCount <= 16; +} + +static bool ValidateAllocateMemoryParameters( + const ALLOCATION_DESC* pAllocDesc, + const D3D12_RESOURCE_ALLOCATION_INFO* pAllocInfo, + Allocation** ppAllocation) +{ + return pAllocDesc && + pAllocInfo && + ppAllocation && + (pAllocInfo->Alignment == 0 || + pAllocInfo->Alignment == D3D12_DEFAULT_RESOURCE_PLACEMENT_ALIGNMENT || + pAllocInfo->Alignment == D3D12_DEFAULT_MSAA_RESOURCE_PLACEMENT_ALIGNMENT) && + pAllocInfo->SizeInBytes != 0 && + pAllocInfo->SizeInBytes % (64ull * 1024) == 0; +} + +#endif // _D3D12MA_FUNCTIONS + +#ifndef _D3D12MA_STATISTICS_FUNCTIONS + +static void ClearStatistics(Statistics& outStats) +{ + outStats.BlockCount = 0; + outStats.AllocationCount = 0; + outStats.BlockBytes = 0; + outStats.AllocationBytes = 0; +} + +static void ClearDetailedStatistics(DetailedStatistics& outStats) +{ + ClearStatistics(outStats.Stats); + outStats.UnusedRangeCount = 0; + outStats.AllocationSizeMin = UINT64_MAX; + outStats.AllocationSizeMax = 0; + outStats.UnusedRangeSizeMin = UINT64_MAX; + outStats.UnusedRangeSizeMax = 0; +} + +static void AddStatistics(Statistics& inoutStats, const Statistics& src) +{ + inoutStats.BlockCount += src.BlockCount; + inoutStats.AllocationCount += src.AllocationCount; + inoutStats.BlockBytes += src.BlockBytes; + inoutStats.AllocationBytes += src.AllocationBytes; +} + +static void AddDetailedStatistics(DetailedStatistics& inoutStats, const DetailedStatistics& src) +{ + AddStatistics(inoutStats.Stats, src.Stats); + inoutStats.UnusedRangeCount += src.UnusedRangeCount; + inoutStats.AllocationSizeMin = D3D12MA_MIN(inoutStats.AllocationSizeMin, src.AllocationSizeMin); + inoutStats.AllocationSizeMax = D3D12MA_MAX(inoutStats.AllocationSizeMax, src.AllocationSizeMax); + inoutStats.UnusedRangeSizeMin = D3D12MA_MIN(inoutStats.UnusedRangeSizeMin, src.UnusedRangeSizeMin); + inoutStats.UnusedRangeSizeMax = D3D12MA_MAX(inoutStats.UnusedRangeSizeMax, src.UnusedRangeSizeMax); +} + +static void AddDetailedStatisticsAllocation(DetailedStatistics& inoutStats, UINT64 size) +{ + inoutStats.Stats.AllocationCount++; + inoutStats.Stats.AllocationBytes += size; + inoutStats.AllocationSizeMin = D3D12MA_MIN(inoutStats.AllocationSizeMin, size); + inoutStats.AllocationSizeMax = D3D12MA_MAX(inoutStats.AllocationSizeMax, size); +} + +static void AddDetailedStatisticsUnusedRange(DetailedStatistics& inoutStats, UINT64 size) +{ + inoutStats.UnusedRangeCount++; + inoutStats.UnusedRangeSizeMin = D3D12MA_MIN(inoutStats.UnusedRangeSizeMin, size); + inoutStats.UnusedRangeSizeMax = D3D12MA_MAX(inoutStats.UnusedRangeSizeMax, size); +} + +#endif // _D3D12MA_STATISTICS_FUNCTIONS + + +#ifndef _D3D12MA_MUTEX + +#ifndef D3D12MA_MUTEX + class Mutex + { + public: + void Lock() { m_Mutex.lock(); } + void Unlock() { m_Mutex.unlock(); } + + private: + std::mutex m_Mutex; + }; + #define D3D12MA_MUTEX Mutex +#endif + +#ifndef D3D12MA_RW_MUTEX +#ifdef _WIN32 + class RWMutex + { + public: + RWMutex() { InitializeSRWLock(&m_Lock); } + void LockRead() { AcquireSRWLockShared(&m_Lock); } + void UnlockRead() { ReleaseSRWLockShared(&m_Lock); } + void LockWrite() { AcquireSRWLockExclusive(&m_Lock); } + void UnlockWrite() { ReleaseSRWLockExclusive(&m_Lock); } + + private: + SRWLOCK m_Lock; + }; +#else // #ifdef _WIN32 + class RWMutex + { + public: + RWMutex() {} + void LockRead() { m_Mutex.lock_shared(); } + void UnlockRead() { m_Mutex.unlock_shared(); } + void LockWrite() { m_Mutex.lock(); } + void UnlockWrite() { m_Mutex.unlock(); } + + private: + std::shared_timed_mutex m_Mutex; + }; +#endif // #ifdef _WIN32 + #define D3D12MA_RW_MUTEX RWMutex +#endif // #ifndef D3D12MA_RW_MUTEX + +// Helper RAII class to lock a mutex in constructor and unlock it in destructor (at the end of scope). +struct MutexLock +{ + D3D12MA_CLASS_NO_COPY(MutexLock); +public: + MutexLock(D3D12MA_MUTEX& mutex, bool useMutex = true) : + m_pMutex(useMutex ? &mutex : NULL) + { + if (m_pMutex) m_pMutex->Lock(); + } + ~MutexLock() { if (m_pMutex) m_pMutex->Unlock(); } + +private: + D3D12MA_MUTEX* m_pMutex; +}; + +// Helper RAII class to lock a RW mutex in constructor and unlock it in destructor (at the end of scope), for reading. +struct MutexLockRead +{ + D3D12MA_CLASS_NO_COPY(MutexLockRead); +public: + MutexLockRead(D3D12MA_RW_MUTEX& mutex, bool useMutex) + : m_pMutex(useMutex ? &mutex : NULL) + { + if(m_pMutex) + { + m_pMutex->LockRead(); + } + } + ~MutexLockRead() { if (m_pMutex) m_pMutex->UnlockRead(); } + +private: + D3D12MA_RW_MUTEX* m_pMutex; +}; + +// Helper RAII class to lock a RW mutex in constructor and unlock it in destructor (at the end of scope), for writing. +struct MutexLockWrite +{ + D3D12MA_CLASS_NO_COPY(MutexLockWrite); +public: + MutexLockWrite(D3D12MA_RW_MUTEX& mutex, bool useMutex) + : m_pMutex(useMutex ? &mutex : NULL) + { + if (m_pMutex) m_pMutex->LockWrite(); + } + ~MutexLockWrite() { if (m_pMutex) m_pMutex->UnlockWrite(); } + +private: + D3D12MA_RW_MUTEX* m_pMutex; +}; + +#if D3D12MA_DEBUG_GLOBAL_MUTEX + static D3D12MA_MUTEX g_DebugGlobalMutex; + #define D3D12MA_DEBUG_GLOBAL_MUTEX_LOCK MutexLock debugGlobalMutexLock(g_DebugGlobalMutex, true); +#else + #define D3D12MA_DEBUG_GLOBAL_MUTEX_LOCK +#endif +#endif // _D3D12MA_MUTEX + +#ifndef _D3D12MA_VECTOR +/* +Dynamically resizing continuous array. Class with interface similar to std::vector. +T must be POD because constructors and destructors are not called and memcpy is +used for these objects. +*/ +template +class Vector +{ +public: + using value_type = T; + using iterator = T*; + using const_iterator = const T*; + + // allocationCallbacks externally owned, must outlive this object. + Vector(const ALLOCATION_CALLBACKS& allocationCallbacks); + Vector(size_t count, const ALLOCATION_CALLBACKS& allocationCallbacks); + Vector(const Vector& src); + ~Vector(); + + const ALLOCATION_CALLBACKS& GetAllocs() const { return m_AllocationCallbacks; } + bool empty() const { return m_Count == 0; } + size_t size() const { return m_Count; } + T* data() { return m_pArray; } + const T* data() const { return m_pArray; } + void clear(bool freeMemory = false) { resize(0, freeMemory); } + + iterator begin() { return m_pArray; } + iterator end() { return m_pArray + m_Count; } + const_iterator cbegin() const { return m_pArray; } + const_iterator cend() const { return m_pArray + m_Count; } + const_iterator begin() const { return cbegin(); } + const_iterator end() const { return cend(); } + + void push_front(const T& src) { insert(0, src); } + void push_back(const T& src); + void pop_front(); + void pop_back(); + + T& front(); + T& back(); + const T& front() const; + const T& back() const; + + void reserve(size_t newCapacity, bool freeMemory = false); + void resize(size_t newCount, bool freeMemory = false); + void insert(size_t index, const T& src); + void remove(size_t index); + + template + size_t InsertSorted(const T& value, const CmpLess& cmp); + template + bool RemoveSorted(const T& value, const CmpLess& cmp); + + Vector& operator=(const Vector& rhs); + T& operator[](size_t index); + const T& operator[](size_t index) const; + +private: + const ALLOCATION_CALLBACKS& m_AllocationCallbacks; + T* m_pArray; + size_t m_Count; + size_t m_Capacity; +}; + +#ifndef _D3D12MA_VECTOR_FUNCTIONS +template +Vector::Vector(const ALLOCATION_CALLBACKS& allocationCallbacks) + : m_AllocationCallbacks(allocationCallbacks), + m_pArray(NULL), + m_Count(0), + m_Capacity(0) {} + +template +Vector::Vector(size_t count, const ALLOCATION_CALLBACKS& allocationCallbacks) + : m_AllocationCallbacks(allocationCallbacks), + m_pArray(count ? AllocateArray(allocationCallbacks, count) : NULL), + m_Count(count), + m_Capacity(count) {} + +template +Vector::Vector(const Vector& src) + : m_AllocationCallbacks(src.m_AllocationCallbacks), + m_pArray(src.m_Count ? AllocateArray(src.m_AllocationCallbacks, src.m_Count) : NULL), + m_Count(src.m_Count), + m_Capacity(src.m_Count) +{ + if (m_Count > 0) + { + memcpy(m_pArray, src.m_pArray, m_Count * sizeof(T)); + } +} + +template +Vector::~Vector() +{ + Free(m_AllocationCallbacks, m_pArray); +} + +template +void Vector::push_back(const T& src) +{ + const size_t newIndex = size(); + resize(newIndex + 1); + m_pArray[newIndex] = src; +} + +template +void Vector::pop_front() +{ + D3D12MA_HEAVY_ASSERT(m_Count > 0); + remove(0); +} + +template +void Vector::pop_back() +{ + D3D12MA_HEAVY_ASSERT(m_Count > 0); + resize(size() - 1); +} + +template +T& Vector::front() +{ + D3D12MA_HEAVY_ASSERT(m_Count > 0); + return m_pArray[0]; +} + +template +T& Vector::back() +{ + D3D12MA_HEAVY_ASSERT(m_Count > 0); + return m_pArray[m_Count - 1]; +} + +template +const T& Vector::front() const +{ + D3D12MA_HEAVY_ASSERT(m_Count > 0); + return m_pArray[0]; +} + +template +const T& Vector::back() const +{ + D3D12MA_HEAVY_ASSERT(m_Count > 0); + return m_pArray[m_Count - 1]; +} + +template +void Vector::reserve(size_t newCapacity, bool freeMemory) +{ + newCapacity = D3D12MA_MAX(newCapacity, m_Count); + + if ((newCapacity < m_Capacity) && !freeMemory) + { + newCapacity = m_Capacity; + } + + if (newCapacity != m_Capacity) + { + T* const newArray = newCapacity ? AllocateArray(m_AllocationCallbacks, newCapacity) : NULL; + if (m_Count != 0) + { + memcpy(newArray, m_pArray, m_Count * sizeof(T)); + } + Free(m_AllocationCallbacks, m_pArray); + m_Capacity = newCapacity; + m_pArray = newArray; + } +} + +template +void Vector::resize(size_t newCount, bool freeMemory) +{ + size_t newCapacity = m_Capacity; + if (newCount > m_Capacity) + { + newCapacity = D3D12MA_MAX(newCount, D3D12MA_MAX(m_Capacity * 3 / 2, (size_t)8)); + } + else if (freeMemory) + { + newCapacity = newCount; + } + + if (newCapacity != m_Capacity) + { + T* const newArray = newCapacity ? AllocateArray(m_AllocationCallbacks, newCapacity) : NULL; + const size_t elementsToCopy = D3D12MA_MIN(m_Count, newCount); + if (elementsToCopy != 0) + { + memcpy(newArray, m_pArray, elementsToCopy * sizeof(T)); + } + Free(m_AllocationCallbacks, m_pArray); + m_Capacity = newCapacity; + m_pArray = newArray; + } + + m_Count = newCount; +} + +template +void Vector::insert(size_t index, const T& src) +{ + D3D12MA_HEAVY_ASSERT(index <= m_Count); + const size_t oldCount = size(); + resize(oldCount + 1); + if (index < oldCount) + { + memmove(m_pArray + (index + 1), m_pArray + index, (oldCount - index) * sizeof(T)); + } + m_pArray[index] = src; +} + +template +void Vector::remove(size_t index) +{ + D3D12MA_HEAVY_ASSERT(index < m_Count); + const size_t oldCount = size(); + if (index < oldCount - 1) + { + memmove(m_pArray + index, m_pArray + (index + 1), (oldCount - index - 1) * sizeof(T)); + } + resize(oldCount - 1); +} + +template template +size_t Vector::InsertSorted(const T& value, const CmpLess& cmp) +{ + const size_t indexToInsert = BinaryFindFirstNotLess( + m_pArray, + m_pArray + m_Count, + value, + cmp) - m_pArray; + insert(indexToInsert, value); + return indexToInsert; +} + +template template +bool Vector::RemoveSorted(const T& value, const CmpLess& cmp) +{ + const iterator it = BinaryFindFirstNotLess( + m_pArray, + m_pArray + m_Count, + value, + cmp); + if ((it != end()) && !cmp(*it, value) && !cmp(value, *it)) + { + size_t indexToRemove = it - begin(); + remove(indexToRemove); + return true; + } + return false; +} + +template +Vector& Vector::operator=(const Vector& rhs) +{ + if (&rhs != this) + { + resize(rhs.m_Count); + if (m_Count != 0) + { + memcpy(m_pArray, rhs.m_pArray, m_Count * sizeof(T)); + } + } + return *this; +} + +template +T& Vector::operator[](size_t index) +{ + D3D12MA_HEAVY_ASSERT(index < m_Count); + return m_pArray[index]; +} + +template +const T& Vector::operator[](size_t index) const +{ + D3D12MA_HEAVY_ASSERT(index < m_Count); + return m_pArray[index]; +} +#endif // _D3D12MA_VECTOR_FUNCTIONS +#endif // _D3D12MA_VECTOR + +#ifndef _D3D12MA_STRING_BUILDER +class StringBuilder +{ +public: + StringBuilder(const ALLOCATION_CALLBACKS& allocationCallbacks) : m_Data(allocationCallbacks) {} + + size_t GetLength() const { return m_Data.size(); } + LPCWSTR GetData() const { return m_Data.data(); } + + void Add(WCHAR ch) { m_Data.push_back(ch); } + void Add(LPCWSTR str); + void AddNewLine() { Add(L'\n'); } + void AddNumber(UINT num); + void AddNumber(UINT64 num); + void AddPointer(const void* ptr); + +private: + Vector m_Data; +}; + +#ifndef _D3D12MA_STRING_BUILDER_FUNCTIONS +void StringBuilder::Add(LPCWSTR str) +{ + const size_t len = wcslen(str); + if (len > 0) + { + const size_t oldCount = m_Data.size(); + m_Data.resize(oldCount + len); + memcpy(m_Data.data() + oldCount, str, len * sizeof(WCHAR)); + } +} + +void StringBuilder::AddNumber(UINT num) +{ + WCHAR buf[11]; + buf[10] = L'\0'; + WCHAR *p = &buf[10]; + do + { + *--p = L'0' + (num % 10); + num /= 10; + } + while (num); + Add(p); +} + +void StringBuilder::AddNumber(UINT64 num) +{ + WCHAR buf[21]; + buf[20] = L'\0'; + WCHAR *p = &buf[20]; + do + { + *--p = L'0' + (num % 10); + num /= 10; + } + while (num); + Add(p); +} + +void StringBuilder::AddPointer(const void* ptr) +{ + WCHAR buf[21]; + uintptr_t num = (uintptr_t)ptr; + buf[20] = L'\0'; + WCHAR *p = &buf[20]; + do + { + *--p = HexDigitToChar((UINT8)(num & 0xF)); + num >>= 4; + } + while (num); + Add(p); +} + +#endif // _D3D12MA_STRING_BUILDER_FUNCTIONS +#endif // _D3D12MA_STRING_BUILDER + +#ifndef _D3D12MA_JSON_WRITER +/* +Allows to conveniently build a correct JSON document to be written to the +StringBuilder passed to the constructor. +*/ +class JsonWriter +{ +public: + // stringBuilder - string builder to write the document to. Must remain alive for the whole lifetime of this object. + JsonWriter(const ALLOCATION_CALLBACKS& allocationCallbacks, StringBuilder& stringBuilder); + ~JsonWriter(); + + // Begins object by writing "{". + // Inside an object, you must call pairs of WriteString and a value, e.g.: + // j.BeginObject(true); j.WriteString("A"); j.WriteNumber(1); j.WriteString("B"); j.WriteNumber(2); j.EndObject(); + // Will write: { "A": 1, "B": 2 } + void BeginObject(bool singleLine = false); + // Ends object by writing "}". + void EndObject(); + + // Begins array by writing "[". + // Inside an array, you can write a sequence of any values. + void BeginArray(bool singleLine = false); + // Ends array by writing "[". + void EndArray(); + + // Writes a string value inside "". + // pStr can contain any UTF-16 characters, including '"', new line etc. - they will be properly escaped. + void WriteString(LPCWSTR pStr); + + // Begins writing a string value. + // Call BeginString, ContinueString, ContinueString, ..., EndString instead of + // WriteString to conveniently build the string content incrementally, made of + // parts including numbers. + void BeginString(LPCWSTR pStr = NULL); + // Posts next part of an open string. + void ContinueString(LPCWSTR pStr); + // Posts next part of an open string. The number is converted to decimal characters. + void ContinueString(UINT num); + void ContinueString(UINT64 num); + void ContinueString_Pointer(const void* ptr); + // Posts next part of an open string. Pointer value is converted to characters + // using "%p" formatting - shown as hexadecimal number, e.g.: 000000081276Ad00 + // void ContinueString_Pointer(const void* ptr); + // Ends writing a string value by writing '"'. + void EndString(LPCWSTR pStr = NULL); + + // Writes a number value. + void WriteNumber(UINT num); + void WriteNumber(UINT64 num); + // Writes a boolean value - false or true. + void WriteBool(bool b); + // Writes a null value. + void WriteNull(); + + void AddAllocationToObject(const Allocation& alloc); + void AddDetailedStatisticsInfoObject(const DetailedStatistics& stats); + +private: + static const WCHAR* const INDENT; + + enum CollectionType + { + COLLECTION_TYPE_OBJECT, + COLLECTION_TYPE_ARRAY, + }; + struct StackItem + { + CollectionType type; + UINT valueCount; + bool singleLineMode; + }; + + StringBuilder& m_SB; + Vector m_Stack; + bool m_InsideString; + + void BeginValue(bool isString); + void WriteIndent(bool oneLess = false); +}; + +#ifndef _D3D12MA_JSON_WRITER_FUNCTIONS +const WCHAR* const JsonWriter::INDENT = L" "; + +JsonWriter::JsonWriter(const ALLOCATION_CALLBACKS& allocationCallbacks, StringBuilder& stringBuilder) + : m_SB(stringBuilder), + m_Stack(allocationCallbacks), + m_InsideString(false) {} + +JsonWriter::~JsonWriter() +{ + D3D12MA_ASSERT(!m_InsideString); + D3D12MA_ASSERT(m_Stack.empty()); +} + +void JsonWriter::BeginObject(bool singleLine) +{ + D3D12MA_ASSERT(!m_InsideString); + + BeginValue(false); + m_SB.Add(L'{'); + + StackItem stackItem; + stackItem.type = COLLECTION_TYPE_OBJECT; + stackItem.valueCount = 0; + stackItem.singleLineMode = singleLine; + m_Stack.push_back(stackItem); +} + +void JsonWriter::EndObject() +{ + D3D12MA_ASSERT(!m_InsideString); + D3D12MA_ASSERT(!m_Stack.empty() && m_Stack.back().type == COLLECTION_TYPE_OBJECT); + D3D12MA_ASSERT(m_Stack.back().valueCount % 2 == 0); + + WriteIndent(true); + m_SB.Add(L'}'); + + m_Stack.pop_back(); +} + +void JsonWriter::BeginArray(bool singleLine) +{ + D3D12MA_ASSERT(!m_InsideString); + + BeginValue(false); + m_SB.Add(L'['); + + StackItem stackItem; + stackItem.type = COLLECTION_TYPE_ARRAY; + stackItem.valueCount = 0; + stackItem.singleLineMode = singleLine; + m_Stack.push_back(stackItem); +} + +void JsonWriter::EndArray() +{ + D3D12MA_ASSERT(!m_InsideString); + D3D12MA_ASSERT(!m_Stack.empty() && m_Stack.back().type == COLLECTION_TYPE_ARRAY); + + WriteIndent(true); + m_SB.Add(L']'); + + m_Stack.pop_back(); +} + +void JsonWriter::WriteString(LPCWSTR pStr) +{ + BeginString(pStr); + EndString(); +} + +void JsonWriter::BeginString(LPCWSTR pStr) +{ + D3D12MA_ASSERT(!m_InsideString); + + BeginValue(true); + m_InsideString = true; + m_SB.Add(L'"'); + if (pStr != NULL) + { + ContinueString(pStr); + } +} + +void JsonWriter::ContinueString(LPCWSTR pStr) +{ + D3D12MA_ASSERT(m_InsideString); + D3D12MA_ASSERT(pStr); + + for (const WCHAR *p = pStr; *p; ++p) + { + // the strings we encode are assumed to be in UTF-16LE format, the native + // windows wide character Unicode format. In this encoding Unicode code + // points U+0000 to U+D7FF and U+E000 to U+FFFF are encoded in two bytes, + // and everything else takes more than two bytes. We will reject any + // multi wchar character encodings for simplicity. + UINT val = (UINT)*p; + D3D12MA_ASSERT(((val <= 0xD7FF) || (0xE000 <= val && val <= 0xFFFF)) && + "Character not currently supported."); + switch (*p) + { + case L'"': m_SB.Add(L'\\'); m_SB.Add(L'"'); break; + case L'\\': m_SB.Add(L'\\'); m_SB.Add(L'\\'); break; + case L'/': m_SB.Add(L'\\'); m_SB.Add(L'/'); break; + case L'\b': m_SB.Add(L'\\'); m_SB.Add(L'b'); break; + case L'\f': m_SB.Add(L'\\'); m_SB.Add(L'f'); break; + case L'\n': m_SB.Add(L'\\'); m_SB.Add(L'n'); break; + case L'\r': m_SB.Add(L'\\'); m_SB.Add(L'r'); break; + case L'\t': m_SB.Add(L'\\'); m_SB.Add(L't'); break; + default: + // conservatively use encoding \uXXXX for any Unicode character + // requiring more than one byte. + if (32 <= val && val < 256) + m_SB.Add(*p); + else + { + m_SB.Add(L'\\'); + m_SB.Add(L'u'); + for (UINT i = 0; i < 4; ++i) + { + UINT hexDigit = (val & 0xF000) >> 12; + val <<= 4; + if (hexDigit < 10) + m_SB.Add(L'0' + (WCHAR)hexDigit); + else + m_SB.Add(L'A' + (WCHAR)hexDigit); + } + } + break; + } + } +} + +void JsonWriter::ContinueString(UINT num) +{ + D3D12MA_ASSERT(m_InsideString); + m_SB.AddNumber(num); +} + +void JsonWriter::ContinueString(UINT64 num) +{ + D3D12MA_ASSERT(m_InsideString); + m_SB.AddNumber(num); +} + +void JsonWriter::ContinueString_Pointer(const void* ptr) +{ + D3D12MA_ASSERT(m_InsideString); + m_SB.AddPointer(ptr); +} + +void JsonWriter::EndString(LPCWSTR pStr) +{ + D3D12MA_ASSERT(m_InsideString); + + if (pStr) + ContinueString(pStr); + m_SB.Add(L'"'); + m_InsideString = false; +} + +void JsonWriter::WriteNumber(UINT num) +{ + D3D12MA_ASSERT(!m_InsideString); + BeginValue(false); + m_SB.AddNumber(num); +} + +void JsonWriter::WriteNumber(UINT64 num) +{ + D3D12MA_ASSERT(!m_InsideString); + BeginValue(false); + m_SB.AddNumber(num); +} + +void JsonWriter::WriteBool(bool b) +{ + D3D12MA_ASSERT(!m_InsideString); + BeginValue(false); + if (b) + m_SB.Add(L"true"); + else + m_SB.Add(L"false"); +} + +void JsonWriter::WriteNull() +{ + D3D12MA_ASSERT(!m_InsideString); + BeginValue(false); + m_SB.Add(L"null"); +} + +void JsonWriter::AddAllocationToObject(const Allocation& alloc) +{ + WriteString(L"Type"); + switch (alloc.m_PackedData.GetResourceDimension()) { + case D3D12_RESOURCE_DIMENSION_UNKNOWN: + WriteString(L"UNKNOWN"); + break; + case D3D12_RESOURCE_DIMENSION_BUFFER: + WriteString(L"BUFFER"); + break; + case D3D12_RESOURCE_DIMENSION_TEXTURE1D: + WriteString(L"TEXTURE1D"); + break; + case D3D12_RESOURCE_DIMENSION_TEXTURE2D: + WriteString(L"TEXTURE2D"); + break; + case D3D12_RESOURCE_DIMENSION_TEXTURE3D: + WriteString(L"TEXTURE3D"); + break; + default: D3D12MA_ASSERT(0); break; + } + + WriteString(L"Size"); + WriteNumber(alloc.GetSize()); + WriteString(L"Usage"); + WriteNumber((UINT)alloc.m_PackedData.GetResourceFlags()); + + void* privateData = alloc.GetPrivateData(); + if (privateData) + { + WriteString(L"CustomData"); + BeginString(); + ContinueString_Pointer(privateData); + EndString(); + } + + LPCWSTR name = alloc.GetName(); + if (name != NULL) + { + WriteString(L"Name"); + WriteString(name); + } + if (alloc.m_PackedData.GetTextureLayout()) + { + WriteString(L"Layout"); + WriteNumber((UINT)alloc.m_PackedData.GetTextureLayout()); + } +} + +void JsonWriter::AddDetailedStatisticsInfoObject(const DetailedStatistics& stats) +{ + BeginObject(); + + WriteString(L"BlockCount"); + WriteNumber(stats.Stats.BlockCount); + WriteString(L"BlockBytes"); + WriteNumber(stats.Stats.BlockBytes); + WriteString(L"AllocationCount"); + WriteNumber(stats.Stats.AllocationCount); + WriteString(L"AllocationBytes"); + WriteNumber(stats.Stats.AllocationBytes); + WriteString(L"UnusedRangeCount"); + WriteNumber(stats.UnusedRangeCount); + + if (stats.Stats.AllocationCount > 1) + { + WriteString(L"AllocationSizeMin"); + WriteNumber(stats.AllocationSizeMin); + WriteString(L"AllocationSizeMax"); + WriteNumber(stats.AllocationSizeMax); + } + if (stats.UnusedRangeCount > 1) + { + WriteString(L"UnusedRangeSizeMin"); + WriteNumber(stats.UnusedRangeSizeMin); + WriteString(L"UnusedRangeSizeMax"); + WriteNumber(stats.UnusedRangeSizeMax); + } + EndObject(); +} + +void JsonWriter::BeginValue(bool isString) +{ + if (!m_Stack.empty()) + { + StackItem& currItem = m_Stack.back(); + if (currItem.type == COLLECTION_TYPE_OBJECT && currItem.valueCount % 2 == 0) + { + D3D12MA_ASSERT(isString); + } + + if (currItem.type == COLLECTION_TYPE_OBJECT && currItem.valueCount % 2 == 1) + { + m_SB.Add(L':'); m_SB.Add(L' '); + } + else if (currItem.valueCount > 0) + { + m_SB.Add(L','); m_SB.Add(L' '); + WriteIndent(); + } + else + { + WriteIndent(); + } + ++currItem.valueCount; + } +} + +void JsonWriter::WriteIndent(bool oneLess) +{ + if (!m_Stack.empty() && !m_Stack.back().singleLineMode) + { + m_SB.AddNewLine(); + + size_t count = m_Stack.size(); + if (count > 0 && oneLess) + { + --count; + } + for (size_t i = 0; i < count; ++i) + { + m_SB.Add(INDENT); + } + } +} +#endif // _D3D12MA_JSON_WRITER_FUNCTIONS +#endif // _D3D12MA_JSON_WRITER + +#ifndef _D3D12MA_POOL_ALLOCATOR +/* +Allocator for objects of type T using a list of arrays (pools) to speed up +allocation. Number of elements that can be allocated is not bounded because +allocator can create multiple blocks. +T should be POD because constructor and destructor is not called in Alloc or +Free. +*/ +template +class PoolAllocator +{ + D3D12MA_CLASS_NO_COPY(PoolAllocator) +public: + // allocationCallbacks externally owned, must outlive this object. + PoolAllocator(const ALLOCATION_CALLBACKS& allocationCallbacks, UINT firstBlockCapacity); + ~PoolAllocator() { Clear(); } + + void Clear(); + template + T* Alloc(Types... args); + void Free(T* ptr); + +private: + union Item + { + UINT NextFreeIndex; // UINT32_MAX means end of list. + alignas(T) char Value[sizeof(T)]; + }; + + struct ItemBlock + { + Item* pItems; + UINT Capacity; + UINT FirstFreeIndex; + }; + + const ALLOCATION_CALLBACKS& m_AllocationCallbacks; + const UINT m_FirstBlockCapacity; + Vector m_ItemBlocks; + + ItemBlock& CreateNewBlock(); +}; + +#ifndef _D3D12MA_POOL_ALLOCATOR_FUNCTIONS +template +PoolAllocator::PoolAllocator(const ALLOCATION_CALLBACKS& allocationCallbacks, UINT firstBlockCapacity) + : m_AllocationCallbacks(allocationCallbacks), + m_FirstBlockCapacity(firstBlockCapacity), + m_ItemBlocks(allocationCallbacks) +{ + D3D12MA_ASSERT(m_FirstBlockCapacity > 1); +} + +template +void PoolAllocator::Clear() +{ + for(size_t i = m_ItemBlocks.size(); i--; ) + { + D3D12MA_DELETE_ARRAY(m_AllocationCallbacks, m_ItemBlocks[i].pItems, m_ItemBlocks[i].Capacity); + } + m_ItemBlocks.clear(true); +} + +template template +T* PoolAllocator::Alloc(Types... args) +{ + for(size_t i = m_ItemBlocks.size(); i--; ) + { + ItemBlock& block = m_ItemBlocks[i]; + // This block has some free items: Use first one. + if(block.FirstFreeIndex != UINT32_MAX) + { + Item* const pItem = &block.pItems[block.FirstFreeIndex]; + block.FirstFreeIndex = pItem->NextFreeIndex; + T* result = (T*)&pItem->Value; + new(result)T(std::forward(args)...); // Explicit constructor call. + return result; + } + } + + // No block has free item: Create new one and use it. + ItemBlock& newBlock = CreateNewBlock(); + Item* const pItem = &newBlock.pItems[0]; + newBlock.FirstFreeIndex = pItem->NextFreeIndex; + T* result = (T*)pItem->Value; + new(result)T(std::forward(args)...); // Explicit constructor call. + return result; +} + +template +void PoolAllocator::Free(T* ptr) +{ + // Search all memory blocks to find ptr. + for(size_t i = m_ItemBlocks.size(); i--; ) + { + ItemBlock& block = m_ItemBlocks[i]; + + Item* pItemPtr; + memcpy(&pItemPtr, &ptr, sizeof(pItemPtr)); + + // Check if pItemPtr is in address range of this block. + if((pItemPtr >= block.pItems) && (pItemPtr < block.pItems + block.Capacity)) + { + ptr->~T(); // Explicit destructor call. + const UINT index = static_cast(pItemPtr - block.pItems); + pItemPtr->NextFreeIndex = block.FirstFreeIndex; + block.FirstFreeIndex = index; + return; + } + } + D3D12MA_ASSERT(0 && "Pointer doesn't belong to this memory pool."); +} + +template +typename PoolAllocator::ItemBlock& PoolAllocator::CreateNewBlock() +{ + const UINT newBlockCapacity = m_ItemBlocks.empty() ? + m_FirstBlockCapacity : m_ItemBlocks.back().Capacity * 3 / 2; + + const ItemBlock newBlock = { + D3D12MA_NEW_ARRAY(m_AllocationCallbacks, Item, newBlockCapacity), + newBlockCapacity, + 0 }; + + m_ItemBlocks.push_back(newBlock); + + // Setup singly-linked list of all free items in this block. + for(UINT i = 0; i < newBlockCapacity - 1; ++i) + { + newBlock.pItems[i].NextFreeIndex = i + 1; + } + newBlock.pItems[newBlockCapacity - 1].NextFreeIndex = UINT32_MAX; + return m_ItemBlocks.back(); +} +#endif // _D3D12MA_POOL_ALLOCATOR_FUNCTIONS +#endif // _D3D12MA_POOL_ALLOCATOR + +#ifndef _D3D12MA_LIST +/* +Doubly linked list, with elements allocated out of PoolAllocator. +Has custom interface, as well as STL-style interface, including iterator and +const_iterator. +*/ +template +class List +{ + D3D12MA_CLASS_NO_COPY(List) +public: + struct Item + { + Item* pPrev; + Item* pNext; + T Value; + }; + + class reverse_iterator; + class const_reverse_iterator; + class iterator + { + friend class List; + friend class const_iterator; + + public: + iterator() = default; + iterator(const reverse_iterator& src) + : m_pList(src.m_pList), m_pItem(src.m_pItem) {} + + T& operator*() const; + T* operator->() const; + + iterator& operator++(); + iterator& operator--(); + iterator operator++(int); + iterator operator--(int); + + bool operator==(const iterator& rhs) const; + bool operator!=(const iterator& rhs) const; + + private: + List* m_pList = NULL; + Item* m_pItem = NULL; + + iterator(List* pList, Item* pItem) : m_pList(pList), m_pItem(pItem) {} + }; + + class reverse_iterator + { + friend class List; + friend class const_reverse_iterator; + + public: + reverse_iterator() = default; + reverse_iterator(const iterator& src) + : m_pList(src.m_pList), m_pItem(src.m_pItem) {} + + T& operator*() const; + T* operator->() const; + + reverse_iterator& operator++(); + reverse_iterator& operator--(); + reverse_iterator operator++(int); + reverse_iterator operator--(int); + + bool operator==(const reverse_iterator& rhs) const; + bool operator!=(const reverse_iterator& rhs) const; + + private: + List* m_pList = NULL; + Item* m_pItem = NULL; + + reverse_iterator(List* pList, Item* pItem) + : m_pList(pList), m_pItem(pItem) {} + }; + + class const_iterator + { + friend class List; + + public: + const_iterator() = default; + const_iterator(const iterator& src) + : m_pList(src.m_pList), m_pItem(src.m_pItem) {} + const_iterator(const reverse_iterator& src) + : m_pList(src.m_pList), m_pItem(src.m_pItem) {} + const_iterator(const const_reverse_iterator& src) + : m_pList(src.m_pList), m_pItem(src.m_pItem) {} + + iterator dropConst() const; + const T& operator*() const; + const T* operator->() const; + + const_iterator& operator++(); + const_iterator& operator--(); + const_iterator operator++(int); + const_iterator operator--(int); + + bool operator==(const const_iterator& rhs) const; + bool operator!=(const const_iterator& rhs) const; + + private: + const List* m_pList = NULL; + const Item* m_pItem = NULL; + + const_iterator(const List* pList, const Item* pItem) + : m_pList(pList), m_pItem(pItem) {} + }; + + class const_reverse_iterator + { + friend class List; + + public: + const_reverse_iterator() = default; + const_reverse_iterator(const iterator& src) + : m_pList(src.m_pList), m_pItem(src.m_pItem) {} + const_reverse_iterator(const reverse_iterator& src) + : m_pList(src.m_pList), m_pItem(src.m_pItem) {} + const_reverse_iterator(const const_iterator& src) + : m_pList(src.m_pList), m_pItem(src.m_pItem) {} + + reverse_iterator dropConst() const; + const T& operator*() const; + const T* operator->() const; + + const_reverse_iterator& operator++(); + const_reverse_iterator& operator--(); + const_reverse_iterator operator++(int); + const_reverse_iterator operator--(int); + + bool operator==(const const_reverse_iterator& rhs) const; + bool operator!=(const const_reverse_iterator& rhs) const; + + private: + const List* m_pList = NULL; + const Item* m_pItem = NULL; + + const_reverse_iterator(const List* pList, const Item* pItem) + : m_pList(pList), m_pItem(pItem) {} + }; + + // allocationCallbacks externally owned, must outlive this object. + List(const ALLOCATION_CALLBACKS& allocationCallbacks); + // Intentionally not calling Clear, because that would be unnecessary + // computations to return all items to m_ItemAllocator as free. + ~List() = default; + + size_t GetCount() const { return m_Count; } + bool IsEmpty() const { return m_Count == 0; } + + Item* Front() { return m_pFront; } + const Item* Front() const { return m_pFront; } + Item* Back() { return m_pBack; } + const Item* Back() const { return m_pBack; } + + bool empty() const { return IsEmpty(); } + size_t size() const { return GetCount(); } + void push_back(const T& value) { PushBack(value); } + iterator insert(iterator it, const T& value) { return iterator(this, InsertBefore(it.m_pItem, value)); } + void clear() { Clear(); } + void erase(iterator it) { Remove(it.m_pItem); } + + iterator begin() { return iterator(this, Front()); } + iterator end() { return iterator(this, NULL); } + reverse_iterator rbegin() { return reverse_iterator(this, Back()); } + reverse_iterator rend() { return reverse_iterator(this, NULL); } + + const_iterator cbegin() const { return const_iterator(this, Front()); } + const_iterator cend() const { return const_iterator(this, NULL); } + const_iterator begin() const { return cbegin(); } + const_iterator end() const { return cend(); } + + const_reverse_iterator crbegin() const { return const_reverse_iterator(this, Back()); } + const_reverse_iterator crend() const { return const_reverse_iterator(this, NULL); } + const_reverse_iterator rbegin() const { return crbegin(); } + const_reverse_iterator rend() const { return crend(); } + + Item* PushBack(); + Item* PushFront(); + Item* PushBack(const T& value); + Item* PushFront(const T& value); + void PopBack(); + void PopFront(); + + // Item can be null - it means PushBack. + Item* InsertBefore(Item* pItem); + // Item can be null - it means PushFront. + Item* InsertAfter(Item* pItem); + Item* InsertBefore(Item* pItem, const T& value); + Item* InsertAfter(Item* pItem, const T& value); + + void Clear(); + void Remove(Item* pItem); + +private: + const ALLOCATION_CALLBACKS& m_AllocationCallbacks; + PoolAllocator m_ItemAllocator; + Item* m_pFront; + Item* m_pBack; + size_t m_Count; +}; + +#ifndef _D3D12MA_LIST_ITERATOR_FUNCTIONS +template +T& List::iterator::operator*() const +{ + D3D12MA_HEAVY_ASSERT(m_pItem != NULL); + return m_pItem->Value; +} + +template +T* List::iterator::operator->() const +{ + D3D12MA_HEAVY_ASSERT(m_pItem != NULL); + return &m_pItem->Value; +} + +template +typename List::iterator& List::iterator::operator++() +{ + D3D12MA_HEAVY_ASSERT(m_pItem != NULL); + m_pItem = m_pItem->pNext; + return *this; +} + +template +typename List::iterator& List::iterator::operator--() +{ + if (m_pItem != NULL) + { + m_pItem = m_pItem->pPrev; + } + else + { + D3D12MA_HEAVY_ASSERT(!m_pList->IsEmpty()); + m_pItem = m_pList->Back(); + } + return *this; +} + +template +typename List::iterator List::iterator::operator++(int) +{ + iterator result = *this; + ++* this; + return result; +} + +template +typename List::iterator List::iterator::operator--(int) +{ + iterator result = *this; + --* this; + return result; +} + +template +bool List::iterator::operator==(const iterator& rhs) const +{ + D3D12MA_HEAVY_ASSERT(m_pList == rhs.m_pList); + return m_pItem == rhs.m_pItem; +} + +template +bool List::iterator::operator!=(const iterator& rhs) const +{ + D3D12MA_HEAVY_ASSERT(m_pList == rhs.m_pList); + return m_pItem != rhs.m_pItem; +} +#endif // _D3D12MA_LIST_ITERATOR_FUNCTIONS + +#ifndef _D3D12MA_LIST_REVERSE_ITERATOR_FUNCTIONS +template +T& List::reverse_iterator::operator*() const +{ + D3D12MA_HEAVY_ASSERT(m_pItem != NULL); + return m_pItem->Value; +} + +template +T* List::reverse_iterator::operator->() const +{ + D3D12MA_HEAVY_ASSERT(m_pItem != NULL); + return &m_pItem->Value; +} + +template +typename List::reverse_iterator& List::reverse_iterator::operator++() +{ + D3D12MA_HEAVY_ASSERT(m_pItem != NULL); + m_pItem = m_pItem->pPrev; + return *this; +} + +template +typename List::reverse_iterator& List::reverse_iterator::operator--() +{ + if (m_pItem != NULL) + { + m_pItem = m_pItem->pNext; + } + else + { + D3D12MA_HEAVY_ASSERT(!m_pList->IsEmpty()); + m_pItem = m_pList->Front(); + } + return *this; +} + +template +typename List::reverse_iterator List::reverse_iterator::operator++(int) +{ + reverse_iterator result = *this; + ++* this; + return result; +} + +template +typename List::reverse_iterator List::reverse_iterator::operator--(int) +{ + reverse_iterator result = *this; + --* this; + return result; +} + +template +bool List::reverse_iterator::operator==(const reverse_iterator& rhs) const +{ + D3D12MA_HEAVY_ASSERT(m_pList == rhs.m_pList); + return m_pItem == rhs.m_pItem; +} + +template +bool List::reverse_iterator::operator!=(const reverse_iterator& rhs) const +{ + D3D12MA_HEAVY_ASSERT(m_pList == rhs.m_pList); + return m_pItem != rhs.m_pItem; +} +#endif // _D3D12MA_LIST_REVERSE_ITERATOR_FUNCTIONS + +#ifndef _D3D12MA_LIST_CONST_ITERATOR_FUNCTIONS +template +typename List::iterator List::const_iterator::dropConst() const +{ + return iterator(const_cast*>(m_pList), const_cast(m_pItem)); +} + +template +const T& List::const_iterator::operator*() const +{ + D3D12MA_HEAVY_ASSERT(m_pItem != NULL); + return m_pItem->Value; +} + +template +const T* List::const_iterator::operator->() const +{ + D3D12MA_HEAVY_ASSERT(m_pItem != NULL); + return &m_pItem->Value; +} + +template +typename List::const_iterator& List::const_iterator::operator++() +{ + D3D12MA_HEAVY_ASSERT(m_pItem != NULL); + m_pItem = m_pItem->pNext; + return *this; +} + +template +typename List::const_iterator& List::const_iterator::operator--() +{ + if (m_pItem != NULL) + { + m_pItem = m_pItem->pPrev; + } + else + { + D3D12MA_HEAVY_ASSERT(!m_pList->IsEmpty()); + m_pItem = m_pList->Back(); + } + return *this; +} + +template +typename List::const_iterator List::const_iterator::operator++(int) +{ + const_iterator result = *this; + ++* this; + return result; +} + +template +typename List::const_iterator List::const_iterator::operator--(int) +{ + const_iterator result = *this; + --* this; + return result; +} + +template +bool List::const_iterator::operator==(const const_iterator& rhs) const +{ + D3D12MA_HEAVY_ASSERT(m_pList == rhs.m_pList); + return m_pItem == rhs.m_pItem; +} + +template +bool List::const_iterator::operator!=(const const_iterator& rhs) const +{ + D3D12MA_HEAVY_ASSERT(m_pList == rhs.m_pList); + return m_pItem != rhs.m_pItem; +} +#endif // _D3D12MA_LIST_CONST_ITERATOR_FUNCTIONS + +#ifndef _D3D12MA_LIST_CONST_REVERSE_ITERATOR_FUNCTIONS +template +typename List::reverse_iterator List::const_reverse_iterator::dropConst() const +{ + return reverse_iterator(const_cast*>(m_pList), const_cast(m_pItem)); +} + +template +const T& List::const_reverse_iterator::operator*() const +{ + D3D12MA_HEAVY_ASSERT(m_pItem != NULL); + return m_pItem->Value; +} + +template +const T* List::const_reverse_iterator::operator->() const +{ + D3D12MA_HEAVY_ASSERT(m_pItem != NULL); + return &m_pItem->Value; +} + +template +typename List::const_reverse_iterator& List::const_reverse_iterator::operator++() +{ + D3D12MA_HEAVY_ASSERT(m_pItem != NULL); + m_pItem = m_pItem->pPrev; + return *this; +} + +template +typename List::const_reverse_iterator& List::const_reverse_iterator::operator--() +{ + if (m_pItem != NULL) + { + m_pItem = m_pItem->pNext; + } + else + { + D3D12MA_HEAVY_ASSERT(!m_pList->IsEmpty()); + m_pItem = m_pList->Front(); + } + return *this; +} + +template +typename List::const_reverse_iterator List::const_reverse_iterator::operator++(int) +{ + const_reverse_iterator result = *this; + ++* this; + return result; +} + +template +typename List::const_reverse_iterator List::const_reverse_iterator::operator--(int) +{ + const_reverse_iterator result = *this; + --* this; + return result; +} + +template +bool List::const_reverse_iterator::operator==(const const_reverse_iterator& rhs) const +{ + D3D12MA_HEAVY_ASSERT(m_pList == rhs.m_pList); + return m_pItem == rhs.m_pItem; +} + +template +bool List::const_reverse_iterator::operator!=(const const_reverse_iterator& rhs) const +{ + D3D12MA_HEAVY_ASSERT(m_pList == rhs.m_pList); + return m_pItem != rhs.m_pItem; +} +#endif // _D3D12MA_LIST_CONST_REVERSE_ITERATOR_FUNCTIONS + +#ifndef _D3D12MA_LIST_FUNCTIONS +template +List::List(const ALLOCATION_CALLBACKS& allocationCallbacks) + : m_AllocationCallbacks(allocationCallbacks), + m_ItemAllocator(allocationCallbacks, 128), + m_pFront(NULL), + m_pBack(NULL), + m_Count(0) {} + +template +void List::Clear() +{ + if(!IsEmpty()) + { + Item* pItem = m_pBack; + while(pItem != NULL) + { + Item* const pPrevItem = pItem->pPrev; + m_ItemAllocator.Free(pItem); + pItem = pPrevItem; + } + m_pFront = NULL; + m_pBack = NULL; + m_Count = 0; + } +} + +template +typename List::Item* List::PushBack() +{ + Item* const pNewItem = m_ItemAllocator.Alloc(); + pNewItem->pNext = NULL; + if(IsEmpty()) + { + pNewItem->pPrev = NULL; + m_pFront = pNewItem; + m_pBack = pNewItem; + m_Count = 1; + } + else + { + pNewItem->pPrev = m_pBack; + m_pBack->pNext = pNewItem; + m_pBack = pNewItem; + ++m_Count; + } + return pNewItem; +} + +template +typename List::Item* List::PushFront() +{ + Item* const pNewItem = m_ItemAllocator.Alloc(); + pNewItem->pPrev = NULL; + if(IsEmpty()) + { + pNewItem->pNext = NULL; + m_pFront = pNewItem; + m_pBack = pNewItem; + m_Count = 1; + } + else + { + pNewItem->pNext = m_pFront; + m_pFront->pPrev = pNewItem; + m_pFront = pNewItem; + ++m_Count; + } + return pNewItem; +} + +template +typename List::Item* List::PushBack(const T& value) +{ + Item* const pNewItem = PushBack(); + pNewItem->Value = value; + return pNewItem; +} + +template +typename List::Item* List::PushFront(const T& value) +{ + Item* const pNewItem = PushFront(); + pNewItem->Value = value; + return pNewItem; +} + +template +void List::PopBack() +{ + D3D12MA_HEAVY_ASSERT(m_Count > 0); + Item* const pBackItem = m_pBack; + Item* const pPrevItem = pBackItem->pPrev; + if(pPrevItem != NULL) + { + pPrevItem->pNext = NULL; + } + m_pBack = pPrevItem; + m_ItemAllocator.Free(pBackItem); + --m_Count; +} + +template +void List::PopFront() +{ + D3D12MA_HEAVY_ASSERT(m_Count > 0); + Item* const pFrontItem = m_pFront; + Item* const pNextItem = pFrontItem->pNext; + if(pNextItem != NULL) + { + pNextItem->pPrev = NULL; + } + m_pFront = pNextItem; + m_ItemAllocator.Free(pFrontItem); + --m_Count; +} + +template +void List::Remove(Item* pItem) +{ + D3D12MA_HEAVY_ASSERT(pItem != NULL); + D3D12MA_HEAVY_ASSERT(m_Count > 0); + + if(pItem->pPrev != NULL) + { + pItem->pPrev->pNext = pItem->pNext; + } + else + { + D3D12MA_HEAVY_ASSERT(m_pFront == pItem); + m_pFront = pItem->pNext; + } + + if(pItem->pNext != NULL) + { + pItem->pNext->pPrev = pItem->pPrev; + } + else + { + D3D12MA_HEAVY_ASSERT(m_pBack == pItem); + m_pBack = pItem->pPrev; + } + + m_ItemAllocator.Free(pItem); + --m_Count; +} + +template +typename List::Item* List::InsertBefore(Item* pItem) +{ + if(pItem != NULL) + { + Item* const prevItem = pItem->pPrev; + Item* const newItem = m_ItemAllocator.Alloc(); + newItem->pPrev = prevItem; + newItem->pNext = pItem; + pItem->pPrev = newItem; + if(prevItem != NULL) + { + prevItem->pNext = newItem; + } + else + { + D3D12MA_HEAVY_ASSERT(m_pFront == pItem); + m_pFront = newItem; + } + ++m_Count; + return newItem; + } + else + { + return PushBack(); + } +} + +template +typename List::Item* List::InsertAfter(Item* pItem) +{ + if(pItem != NULL) + { + Item* const nextItem = pItem->pNext; + Item* const newItem = m_ItemAllocator.Alloc(); + newItem->pNext = nextItem; + newItem->pPrev = pItem; + pItem->pNext = newItem; + if(nextItem != NULL) + { + nextItem->pPrev = newItem; + } + else + { + D3D12MA_HEAVY_ASSERT(m_pBack == pItem); + m_pBack = newItem; + } + ++m_Count; + return newItem; + } + else + return PushFront(); +} + +template +typename List::Item* List::InsertBefore(Item* pItem, const T& value) +{ + Item* const newItem = InsertBefore(pItem); + newItem->Value = value; + return newItem; +} + +template +typename List::Item* List::InsertAfter(Item* pItem, const T& value) +{ + Item* const newItem = InsertAfter(pItem); + newItem->Value = value; + return newItem; +} +#endif // _D3D12MA_LIST_FUNCTIONS +#endif // _D3D12MA_LIST + +#ifndef _D3D12MA_INTRUSIVE_LINKED_LIST +/* +Expected interface of ItemTypeTraits: +struct MyItemTypeTraits +{ + using ItemType = MyItem; + static ItemType* GetPrev(const ItemType* item) { return item->myPrevPtr; } + static ItemType* GetNext(const ItemType* item) { return item->myNextPtr; } + static ItemType*& AccessPrev(ItemType* item) { return item->myPrevPtr; } + static ItemType*& AccessNext(ItemType* item) { return item->myNextPtr; } +}; +*/ +template +class IntrusiveLinkedList +{ +public: + using ItemType = typename ItemTypeTraits::ItemType; + static ItemType* GetPrev(const ItemType* item) { return ItemTypeTraits::GetPrev(item); } + static ItemType* GetNext(const ItemType* item) { return ItemTypeTraits::GetNext(item); } + + // Movable, not copyable. + IntrusiveLinkedList() = default; + IntrusiveLinkedList(const IntrusiveLinkedList&) = delete; + IntrusiveLinkedList(IntrusiveLinkedList&& src); + IntrusiveLinkedList& operator=(const IntrusiveLinkedList&) = delete; + IntrusiveLinkedList& operator=(IntrusiveLinkedList&& src); + ~IntrusiveLinkedList() { D3D12MA_HEAVY_ASSERT(IsEmpty()); } + + size_t GetCount() const { return m_Count; } + bool IsEmpty() const { return m_Count == 0; } + + ItemType* Front() { return m_Front; } + ItemType* Back() { return m_Back; } + const ItemType* Front() const { return m_Front; } + const ItemType* Back() const { return m_Back; } + + void PushBack(ItemType* item); + void PushFront(ItemType* item); + ItemType* PopBack(); + ItemType* PopFront(); + + // MyItem can be null - it means PushBack. + void InsertBefore(ItemType* existingItem, ItemType* newItem); + // MyItem can be null - it means PushFront. + void InsertAfter(ItemType* existingItem, ItemType* newItem); + + void Remove(ItemType* item); + void RemoveAll(); + +private: + ItemType* m_Front = NULL; + ItemType* m_Back = NULL; + size_t m_Count = 0; +}; + +#ifndef _D3D12MA_INTRUSIVE_LINKED_LIST_FUNCTIONS +template +IntrusiveLinkedList::IntrusiveLinkedList(IntrusiveLinkedList&& src) + : m_Front(src.m_Front), m_Back(src.m_Back), m_Count(src.m_Count) +{ + src.m_Front = src.m_Back = NULL; + src.m_Count = 0; +} + +template +IntrusiveLinkedList& IntrusiveLinkedList::operator=(IntrusiveLinkedList&& src) +{ + if (&src != this) + { + D3D12MA_HEAVY_ASSERT(IsEmpty()); + m_Front = src.m_Front; + m_Back = src.m_Back; + m_Count = src.m_Count; + src.m_Front = src.m_Back = NULL; + src.m_Count = 0; + } + return *this; +} + +template +void IntrusiveLinkedList::PushBack(ItemType* item) +{ + D3D12MA_HEAVY_ASSERT(ItemTypeTraits::GetPrev(item) == NULL && ItemTypeTraits::GetNext(item) == NULL); + if (IsEmpty()) + { + m_Front = item; + m_Back = item; + m_Count = 1; + } + else + { + ItemTypeTraits::AccessPrev(item) = m_Back; + ItemTypeTraits::AccessNext(m_Back) = item; + m_Back = item; + ++m_Count; + } +} + +template +void IntrusiveLinkedList::PushFront(ItemType* item) +{ + D3D12MA_HEAVY_ASSERT(ItemTypeTraits::GetPrev(item) == NULL && ItemTypeTraits::GetNext(item) == NULL); + if (IsEmpty()) + { + m_Front = item; + m_Back = item; + m_Count = 1; + } + else + { + ItemTypeTraits::AccessNext(item) = m_Front; + ItemTypeTraits::AccessPrev(m_Front) = item; + m_Front = item; + ++m_Count; + } +} + +template +typename IntrusiveLinkedList::ItemType* IntrusiveLinkedList::PopBack() +{ + D3D12MA_HEAVY_ASSERT(m_Count > 0); + ItemType* const backItem = m_Back; + ItemType* const prevItem = ItemTypeTraits::GetPrev(backItem); + if (prevItem != NULL) + { + ItemTypeTraits::AccessNext(prevItem) = NULL; + } + m_Back = prevItem; + --m_Count; + ItemTypeTraits::AccessPrev(backItem) = NULL; + ItemTypeTraits::AccessNext(backItem) = NULL; + return backItem; +} + +template +typename IntrusiveLinkedList::ItemType* IntrusiveLinkedList::PopFront() +{ + D3D12MA_HEAVY_ASSERT(m_Count > 0); + ItemType* const frontItem = m_Front; + ItemType* const nextItem = ItemTypeTraits::GetNext(frontItem); + if (nextItem != NULL) + { + ItemTypeTraits::AccessPrev(nextItem) = NULL; + } + m_Front = nextItem; + --m_Count; + ItemTypeTraits::AccessPrev(frontItem) = NULL; + ItemTypeTraits::AccessNext(frontItem) = NULL; + return frontItem; +} + +template +void IntrusiveLinkedList::InsertBefore(ItemType* existingItem, ItemType* newItem) +{ + D3D12MA_HEAVY_ASSERT(newItem != NULL && ItemTypeTraits::GetPrev(newItem) == NULL && ItemTypeTraits::GetNext(newItem) == NULL); + if (existingItem != NULL) + { + ItemType* const prevItem = ItemTypeTraits::GetPrev(existingItem); + ItemTypeTraits::AccessPrev(newItem) = prevItem; + ItemTypeTraits::AccessNext(newItem) = existingItem; + ItemTypeTraits::AccessPrev(existingItem) = newItem; + if (prevItem != NULL) + { + ItemTypeTraits::AccessNext(prevItem) = newItem; + } + else + { + D3D12MA_HEAVY_ASSERT(m_Front == existingItem); + m_Front = newItem; + } + ++m_Count; + } + else + PushBack(newItem); +} + +template +void IntrusiveLinkedList::InsertAfter(ItemType* existingItem, ItemType* newItem) +{ + D3D12MA_HEAVY_ASSERT(newItem != NULL && ItemTypeTraits::GetPrev(newItem) == NULL && ItemTypeTraits::GetNext(newItem) == NULL); + if (existingItem != NULL) + { + ItemType* const nextItem = ItemTypeTraits::GetNext(existingItem); + ItemTypeTraits::AccessNext(newItem) = nextItem; + ItemTypeTraits::AccessPrev(newItem) = existingItem; + ItemTypeTraits::AccessNext(existingItem) = newItem; + if (nextItem != NULL) + { + ItemTypeTraits::AccessPrev(nextItem) = newItem; + } + else + { + D3D12MA_HEAVY_ASSERT(m_Back == existingItem); + m_Back = newItem; + } + ++m_Count; + } + else + return PushFront(newItem); +} + +template +void IntrusiveLinkedList::Remove(ItemType* item) +{ + D3D12MA_HEAVY_ASSERT(item != NULL && m_Count > 0); + if (ItemTypeTraits::GetPrev(item) != NULL) + { + ItemTypeTraits::AccessNext(ItemTypeTraits::AccessPrev(item)) = ItemTypeTraits::GetNext(item); + } + else + { + D3D12MA_HEAVY_ASSERT(m_Front == item); + m_Front = ItemTypeTraits::GetNext(item); + } + + if (ItemTypeTraits::GetNext(item) != NULL) + { + ItemTypeTraits::AccessPrev(ItemTypeTraits::AccessNext(item)) = ItemTypeTraits::GetPrev(item); + } + else + { + D3D12MA_HEAVY_ASSERT(m_Back == item); + m_Back = ItemTypeTraits::GetPrev(item); + } + ItemTypeTraits::AccessPrev(item) = NULL; + ItemTypeTraits::AccessNext(item) = NULL; + --m_Count; +} + +template +void IntrusiveLinkedList::RemoveAll() +{ + if (!IsEmpty()) + { + ItemType* item = m_Back; + while (item != NULL) + { + ItemType* const prevItem = ItemTypeTraits::AccessPrev(item); + ItemTypeTraits::AccessPrev(item) = NULL; + ItemTypeTraits::AccessNext(item) = NULL; + item = prevItem; + } + m_Front = NULL; + m_Back = NULL; + m_Count = 0; + } +} +#endif // _D3D12MA_INTRUSIVE_LINKED_LIST_FUNCTIONS +#endif // _D3D12MA_INTRUSIVE_LINKED_LIST + +#ifndef _D3D12MA_ALLOCATION_OBJECT_ALLOCATOR +/* +Thread-safe wrapper over PoolAllocator free list, for allocation of Allocation objects. +*/ +class AllocationObjectAllocator +{ + D3D12MA_CLASS_NO_COPY(AllocationObjectAllocator); +public: + AllocationObjectAllocator(const ALLOCATION_CALLBACKS& allocationCallbacks) + : m_Allocator(allocationCallbacks, 1024) {} + + template + Allocation* Allocate(Types... args); + void Free(Allocation* alloc); + +private: + D3D12MA_MUTEX m_Mutex; + PoolAllocator m_Allocator; +}; + +#ifndef _D3D12MA_ALLOCATION_OBJECT_ALLOCATOR_FUNCTIONS +template +Allocation* AllocationObjectAllocator::Allocate(Types... args) +{ + MutexLock mutexLock(m_Mutex); + return m_Allocator.Alloc(std::forward(args)...); +} + +void AllocationObjectAllocator::Free(Allocation* alloc) +{ + MutexLock mutexLock(m_Mutex); + m_Allocator.Free(alloc); +} +#endif // _D3D12MA_ALLOCATION_OBJECT_ALLOCATOR_FUNCTIONS +#endif // _D3D12MA_ALLOCATION_OBJECT_ALLOCATOR + +#ifndef _D3D12MA_SUBALLOCATION +/* +Represents a region of NormalBlock that is either assigned and returned as +allocated memory block or free. +*/ +struct Suballocation +{ + UINT64 offset; + UINT64 size; + void* privateData; + SuballocationType type; +}; +using SuballocationList = List; + +// Comparator for offsets. +struct SuballocationOffsetLess +{ + bool operator()(const Suballocation& lhs, const Suballocation& rhs) const + { + return lhs.offset < rhs.offset; + } +}; + +struct SuballocationOffsetGreater +{ + bool operator()(const Suballocation& lhs, const Suballocation& rhs) const + { + return lhs.offset > rhs.offset; + } +}; + +struct SuballocationItemSizeLess +{ + bool operator()(const SuballocationList::iterator lhs, const SuballocationList::iterator rhs) const + { + return lhs->size < rhs->size; + } + bool operator()(const SuballocationList::iterator lhs, UINT64 rhsSize) const + { + return lhs->size < rhsSize; + } +}; +#endif // _D3D12MA_SUBALLOCATION + +#ifndef _D3D12MA_ALLOCATION_REQUEST +/* +Parameters of planned allocation inside a NormalBlock. +*/ +struct AllocationRequest +{ + AllocHandle allocHandle; + UINT64 size; + UINT64 algorithmData; + UINT64 sumFreeSize; // Sum size of free items that overlap with proposed allocation. + UINT64 sumItemSize; // Sum size of items to make lost that overlap with proposed allocation. + SuballocationList::iterator item; + BOOL zeroInitialized = FALSE; // TODO Implement proper handling in TLSF and Linear, using ZeroInitializedRange class. +}; +#endif // _D3D12MA_ALLOCATION_REQUEST + +#ifndef _D3D12MA_ZERO_INITIALIZED_RANGE +/* +Keeps track of the range of bytes that are surely initialized with zeros. +Everything outside of it is considered uninitialized memory that may contain +garbage data. + +The range is left-inclusive. +*/ +class ZeroInitializedRange +{ +public: + void Reset(UINT64 size); + BOOL IsRangeZeroInitialized(UINT64 beg, UINT64 end) const; + void MarkRangeAsUsed(UINT64 usedBeg, UINT64 usedEnd); + +private: + UINT64 m_ZeroBeg = 0, m_ZeroEnd = 0; +}; + +#ifndef _D3D12MA_ZERO_INITIALIZED_RANGE_FUNCTIONS +void ZeroInitializedRange::Reset(UINT64 size) +{ + D3D12MA_ASSERT(size > 0); + m_ZeroBeg = 0; + m_ZeroEnd = size; +} + +BOOL ZeroInitializedRange::IsRangeZeroInitialized(UINT64 beg, UINT64 end) const +{ + D3D12MA_ASSERT(beg < end); + return m_ZeroBeg <= beg && end <= m_ZeroEnd; +} + +void ZeroInitializedRange::MarkRangeAsUsed(UINT64 usedBeg, UINT64 usedEnd) +{ + D3D12MA_ASSERT(usedBeg < usedEnd); + // No new bytes marked. + if (usedEnd <= m_ZeroBeg || m_ZeroEnd <= usedBeg) + { + return; + } + // All bytes marked. + if (usedBeg <= m_ZeroBeg && m_ZeroEnd <= usedEnd) + { + m_ZeroBeg = m_ZeroEnd = 0; + } + // Some bytes marked. + else + { + const UINT64 remainingZeroBefore = usedBeg > m_ZeroBeg ? usedBeg - m_ZeroBeg : 0; + const UINT64 remainingZeroAfter = usedEnd < m_ZeroEnd ? m_ZeroEnd - usedEnd : 0; + D3D12MA_ASSERT(remainingZeroBefore > 0 || remainingZeroAfter > 0); + if (remainingZeroBefore > remainingZeroAfter) + { + m_ZeroEnd = usedBeg; + } + else + { + m_ZeroBeg = usedEnd; + } + } +} +#endif // _D3D12MA_ZERO_INITIALIZED_RANGE_FUNCTIONS +#endif // _D3D12MA_ZERO_INITIALIZED_RANGE + +#ifndef _D3D12MA_BLOCK_METADATA +/* +Data structure used for bookkeeping of allocations and unused ranges of memory +in a single ID3D12Heap memory block. +*/ +class BlockMetadata +{ +public: + BlockMetadata(const ALLOCATION_CALLBACKS* allocationCallbacks, bool isVirtual); + virtual ~BlockMetadata() = default; + + virtual void Init(UINT64 size) { m_Size = size; } + // Validates all data structures inside this object. If not valid, returns false. + virtual bool Validate() const = 0; + UINT64 GetSize() const { return m_Size; } + bool IsVirtual() const { return m_IsVirtual; } + virtual size_t GetAllocationCount() const = 0; + virtual size_t GetFreeRegionsCount() const = 0; + virtual UINT64 GetSumFreeSize() const = 0; + virtual UINT64 GetAllocationOffset(AllocHandle allocHandle) const = 0; + // Returns true if this block is empty - contains only single free suballocation. + virtual bool IsEmpty() const = 0; + + virtual void GetAllocationInfo(AllocHandle allocHandle, VIRTUAL_ALLOCATION_INFO& outInfo) const = 0; + + // Tries to find a place for suballocation with given parameters inside this block. + // If succeeded, fills pAllocationRequest and returns true. + // If failed, returns false. + virtual bool CreateAllocationRequest( + UINT64 allocSize, + UINT64 allocAlignment, + bool upperAddress, + UINT32 strategy, + AllocationRequest* pAllocationRequest) = 0; + + // Makes actual allocation based on request. Request must already be checked and valid. + virtual void Alloc( + const AllocationRequest& request, + UINT64 allocSize, + void* PrivateData) = 0; + + virtual void Free(AllocHandle allocHandle) = 0; + // Frees all allocations. + // Careful! Don't call it if there are Allocation objects owned by pPrivateData of of cleared allocations! + virtual void Clear() = 0; + + virtual AllocHandle GetAllocationListBegin() const = 0; + virtual AllocHandle GetNextAllocation(AllocHandle prevAlloc) const = 0; + virtual UINT64 GetNextFreeRegionSize(AllocHandle alloc) const = 0; + virtual void* GetAllocationPrivateData(AllocHandle allocHandle) const = 0; + virtual void SetAllocationPrivateData(AllocHandle allocHandle, void* privateData) = 0; + + virtual void AddStatistics(Statistics& inoutStats) const = 0; + virtual void AddDetailedStatistics(DetailedStatistics& inoutStats) const = 0; + virtual void WriteAllocationInfoToJson(JsonWriter& json) const = 0; + virtual void DebugLogAllAllocations() const = 0; + +protected: + const ALLOCATION_CALLBACKS* GetAllocs() const { return m_pAllocationCallbacks; } + UINT64 GetDebugMargin() const { return IsVirtual() ? 0 : D3D12MA_DEBUG_MARGIN; } + + void DebugLogAllocation(UINT64 offset, UINT64 size, void* privateData) const; + void PrintDetailedMap_Begin(JsonWriter& json, + UINT64 unusedBytes, + size_t allocationCount, + size_t unusedRangeCount) const; + void PrintDetailedMap_Allocation(JsonWriter& json, + UINT64 offset, UINT64 size, void* privateData) const; + void PrintDetailedMap_UnusedRange(JsonWriter& json, + UINT64 offset, UINT64 size) const; + void PrintDetailedMap_End(JsonWriter& json) const; + +private: + UINT64 m_Size; + bool m_IsVirtual; + const ALLOCATION_CALLBACKS* m_pAllocationCallbacks; + + D3D12MA_CLASS_NO_COPY(BlockMetadata); +}; + +#ifndef _D3D12MA_BLOCK_METADATA_FUNCTIONS +BlockMetadata::BlockMetadata(const ALLOCATION_CALLBACKS* allocationCallbacks, bool isVirtual) + : m_Size(0), + m_IsVirtual(isVirtual), + m_pAllocationCallbacks(allocationCallbacks) +{ + D3D12MA_ASSERT(allocationCallbacks); +} + +void BlockMetadata::DebugLogAllocation(UINT64 offset, UINT64 size, void* privateData) const +{ + if (IsVirtual()) + { + D3D12MA_DEBUG_LOG(L"UNFREED VIRTUAL ALLOCATION; Offset: %llu; Size: %llu; PrivateData: %p", offset, size, privateData); + } + else + { + D3D12MA_ASSERT(privateData != NULL); + Allocation* allocation = reinterpret_cast(privateData); + + privateData = allocation->GetPrivateData(); + LPCWSTR name = allocation->GetName(); + + D3D12MA_DEBUG_LOG(L"UNFREED ALLOCATION; Offset: %llu; Size: %llu; PrivateData: %p; Name: %s", + offset, size, privateData, name ? name : L"D3D12MA_Empty"); + } +} + +void BlockMetadata::PrintDetailedMap_Begin(JsonWriter& json, + UINT64 unusedBytes, size_t allocationCount, size_t unusedRangeCount) const +{ + json.WriteString(L"TotalBytes"); + json.WriteNumber(GetSize()); + + json.WriteString(L"UnusedBytes"); + json.WriteNumber(unusedBytes); + + json.WriteString(L"Allocations"); + json.WriteNumber((UINT64)allocationCount); + + json.WriteString(L"UnusedRanges"); + json.WriteNumber((UINT64)unusedRangeCount); + + json.WriteString(L"Suballocations"); + json.BeginArray(); +} + +void BlockMetadata::PrintDetailedMap_Allocation(JsonWriter& json, + UINT64 offset, UINT64 size, void* privateData) const +{ + json.BeginObject(true); + + json.WriteString(L"Offset"); + json.WriteNumber(offset); + + if (IsVirtual()) + { + json.WriteString(L"Size"); + json.WriteNumber(size); + if (privateData) + { + json.WriteString(L"CustomData"); + json.WriteNumber((uintptr_t)privateData); + } + } + else + { + const Allocation* const alloc = (const Allocation*)privateData; + D3D12MA_ASSERT(alloc); + json.AddAllocationToObject(*alloc); + } + json.EndObject(); +} + +void BlockMetadata::PrintDetailedMap_UnusedRange(JsonWriter& json, + UINT64 offset, UINT64 size) const +{ + json.BeginObject(true); + + json.WriteString(L"Offset"); + json.WriteNumber(offset); + + json.WriteString(L"Type"); + json.WriteString(L"FREE"); + + json.WriteString(L"Size"); + json.WriteNumber(size); + + json.EndObject(); +} + +void BlockMetadata::PrintDetailedMap_End(JsonWriter& json) const +{ + json.EndArray(); +} +#endif // _D3D12MA_BLOCK_METADATA_FUNCTIONS +#endif // _D3D12MA_BLOCK_METADATA + +#if 0 +#ifndef _D3D12MA_BLOCK_METADATA_GENERIC +class BlockMetadata_Generic : public BlockMetadata +{ +public: + BlockMetadata_Generic(const ALLOCATION_CALLBACKS* allocationCallbacks, bool isVirtual); + virtual ~BlockMetadata_Generic() = default; + + size_t GetAllocationCount() const override { return m_Suballocations.size() - m_FreeCount; } + UINT64 GetSumFreeSize() const override { return m_SumFreeSize; } + UINT64 GetAllocationOffset(AllocHandle allocHandle) const override { return (UINT64)allocHandle - 1; } + + void Init(UINT64 size) override; + bool Validate() const override; + bool IsEmpty() const override; + void GetAllocationInfo(AllocHandle allocHandle, VIRTUAL_ALLOCATION_INFO& outInfo) const override; + + bool CreateAllocationRequest( + UINT64 allocSize, + UINT64 allocAlignment, + bool upperAddress, + AllocationRequest* pAllocationRequest) override; + + void Alloc( + const AllocationRequest& request, + UINT64 allocSize, + void* privateData) override; + + void Free(AllocHandle allocHandle) override; + void Clear() override; + + void SetAllocationPrivateData(AllocHandle allocHandle, void* privateData) override; + + void AddStatistics(Statistics& inoutStats) const override; + void AddDetailedStatistics(DetailedStatistics& inoutStats) const override; + void WriteAllocationInfoToJson(JsonWriter& json) const override; + +private: + UINT m_FreeCount; + UINT64 m_SumFreeSize; + SuballocationList m_Suballocations; + // Suballocations that are free and have size greater than certain threshold. + // Sorted by size, ascending. + Vector m_FreeSuballocationsBySize; + ZeroInitializedRange m_ZeroInitializedRange; + + SuballocationList::const_iterator FindAtOffset(UINT64 offset) const; + bool ValidateFreeSuballocationList() const; + + // Checks if requested suballocation with given parameters can be placed in given pFreeSuballocItem. + // If yes, fills pOffset and returns true. If no, returns false. + bool CheckAllocation( + UINT64 allocSize, + UINT64 allocAlignment, + SuballocationList::const_iterator suballocItem, + AllocHandle* pAllocHandle, + UINT64* pSumFreeSize, + UINT64* pSumItemSize, + BOOL *pZeroInitialized) const; + // Given free suballocation, it merges it with following one, which must also be free. + void MergeFreeWithNext(SuballocationList::iterator item); + // Releases given suballocation, making it free. + // Merges it with adjacent free suballocations if applicable. + // Returns iterator to new free suballocation at this place. + SuballocationList::iterator FreeSuballocation(SuballocationList::iterator suballocItem); + // Given free suballocation, it inserts it into sorted list of + // m_FreeSuballocationsBySize if it's suitable. + void RegisterFreeSuballocation(SuballocationList::iterator item); + // Given free suballocation, it removes it from sorted list of + // m_FreeSuballocationsBySize if it's suitable. + void UnregisterFreeSuballocation(SuballocationList::iterator item); + + D3D12MA_CLASS_NO_COPY(BlockMetadata_Generic) +}; + +#ifndef _D3D12MA_BLOCK_METADATA_GENERIC_FUNCTIONS +BlockMetadata_Generic::BlockMetadata_Generic(const ALLOCATION_CALLBACKS* allocationCallbacks, bool isVirtual) + : BlockMetadata(allocationCallbacks, isVirtual), + m_FreeCount(0), + m_SumFreeSize(0), + m_Suballocations(*allocationCallbacks), + m_FreeSuballocationsBySize(*allocationCallbacks) +{ + D3D12MA_ASSERT(allocationCallbacks); +} + +void BlockMetadata_Generic::Init(UINT64 size) +{ + BlockMetadata::Init(size); + m_ZeroInitializedRange.Reset(size); + + m_FreeCount = 1; + m_SumFreeSize = size; + + Suballocation suballoc = {}; + suballoc.offset = 0; + suballoc.size = size; + suballoc.type = SUBALLOCATION_TYPE_FREE; + suballoc.privateData = NULL; + + D3D12MA_ASSERT(size > MIN_FREE_SUBALLOCATION_SIZE_TO_REGISTER); + m_Suballocations.push_back(suballoc); + SuballocationList::iterator suballocItem = m_Suballocations.end(); + --suballocItem; + m_FreeSuballocationsBySize.push_back(suballocItem); +} + +bool BlockMetadata_Generic::Validate() const +{ + D3D12MA_VALIDATE(!m_Suballocations.empty()); + + // Expected offset of new suballocation as calculated from previous ones. + UINT64 calculatedOffset = 0; + // Expected number of free suballocations as calculated from traversing their list. + UINT calculatedFreeCount = 0; + // Expected sum size of free suballocations as calculated from traversing their list. + UINT64 calculatedSumFreeSize = 0; + // Expected number of free suballocations that should be registered in + // m_FreeSuballocationsBySize calculated from traversing their list. + size_t freeSuballocationsToRegister = 0; + // True if previous visited suballocation was free. + bool prevFree = false; + + for (const auto& subAlloc : m_Suballocations) + { + // Actual offset of this suballocation doesn't match expected one. + D3D12MA_VALIDATE(subAlloc.offset == calculatedOffset); + + const bool currFree = (subAlloc.type == SUBALLOCATION_TYPE_FREE); + // Two adjacent free suballocations are invalid. They should be merged. + D3D12MA_VALIDATE(!prevFree || !currFree); + + const Allocation* const alloc = (Allocation*)subAlloc.privateData; + if (!IsVirtual()) + { + D3D12MA_VALIDATE(currFree == (alloc == NULL)); + } + + if (currFree) + { + calculatedSumFreeSize += subAlloc.size; + ++calculatedFreeCount; + if (subAlloc.size >= MIN_FREE_SUBALLOCATION_SIZE_TO_REGISTER) + { + ++freeSuballocationsToRegister; + } + + // Margin required between allocations - every free space must be at least that large. + D3D12MA_VALIDATE(subAlloc.size >= GetDebugMargin()); + } + else + { + if (!IsVirtual()) + { + D3D12MA_VALIDATE(alloc->GetOffset() == subAlloc.offset); + D3D12MA_VALIDATE(alloc->GetSize() == subAlloc.size); + } + + // Margin required between allocations - previous allocation must be free. + D3D12MA_VALIDATE(GetDebugMargin() == 0 || prevFree); + } + + calculatedOffset += subAlloc.size; + prevFree = currFree; + } + + // Number of free suballocations registered in m_FreeSuballocationsBySize doesn't + // match expected one. + D3D12MA_VALIDATE(m_FreeSuballocationsBySize.size() == freeSuballocationsToRegister); + + UINT64 lastSize = 0; + for (size_t i = 0; i < m_FreeSuballocationsBySize.size(); ++i) + { + SuballocationList::iterator suballocItem = m_FreeSuballocationsBySize[i]; + + // Only free suballocations can be registered in m_FreeSuballocationsBySize. + D3D12MA_VALIDATE(suballocItem->type == SUBALLOCATION_TYPE_FREE); + // They must be sorted by size ascending. + D3D12MA_VALIDATE(suballocItem->size >= lastSize); + + lastSize = suballocItem->size; + } + + // Check if totals match calculacted values. + D3D12MA_VALIDATE(ValidateFreeSuballocationList()); + D3D12MA_VALIDATE(calculatedOffset == GetSize()); + D3D12MA_VALIDATE(calculatedSumFreeSize == m_SumFreeSize); + D3D12MA_VALIDATE(calculatedFreeCount == m_FreeCount); + + return true; +} + +bool BlockMetadata_Generic::IsEmpty() const +{ + return (m_Suballocations.size() == 1) && (m_FreeCount == 1); +} + +void BlockMetadata_Generic::GetAllocationInfo(AllocHandle allocHandle, VIRTUAL_ALLOCATION_INFO& outInfo) const +{ + Suballocation& suballoc = *FindAtOffset((UINT64)allocHandle - 1).dropConst(); + outInfo.Offset = suballoc.offset; + outInfo.Size = suballoc.size; + outInfo.pPrivateData = suballoc.privateData; +} + +bool BlockMetadata_Generic::CreateAllocationRequest( + UINT64 allocSize, + UINT64 allocAlignment, + bool upperAddress, + AllocationRequest* pAllocationRequest) +{ + D3D12MA_ASSERT(allocSize > 0); + D3D12MA_ASSERT(!upperAddress && "ALLOCATION_FLAG_UPPER_ADDRESS can be used only with linear algorithm."); + D3D12MA_ASSERT(pAllocationRequest != NULL); + D3D12MA_HEAVY_ASSERT(Validate()); + + // There is not enough total free space in this block to fullfill the request: Early return. + if (m_SumFreeSize < allocSize + GetDebugMargin()) + { + return false; + } + + // New algorithm, efficiently searching freeSuballocationsBySize. + const size_t freeSuballocCount = m_FreeSuballocationsBySize.size(); + if (freeSuballocCount > 0) + { + // Find first free suballocation with size not less than allocSize + GetDebugMargin(). + SuballocationList::iterator* const it = BinaryFindFirstNotLess( + m_FreeSuballocationsBySize.data(), + m_FreeSuballocationsBySize.data() + freeSuballocCount, + allocSize + GetDebugMargin(), + SuballocationItemSizeLess()); + size_t index = it - m_FreeSuballocationsBySize.data(); + for (; index < freeSuballocCount; ++index) + { + if (CheckAllocation( + allocSize, + allocAlignment, + m_FreeSuballocationsBySize[index], + &pAllocationRequest->allocHandle, + &pAllocationRequest->sumFreeSize, + &pAllocationRequest->sumItemSize, + &pAllocationRequest->zeroInitialized)) + { + pAllocationRequest->item = m_FreeSuballocationsBySize[index]; + return true; + } + } + } + + return false; +} + +void BlockMetadata_Generic::Alloc( + const AllocationRequest& request, + UINT64 allocSize, + void* privateData) +{ + D3D12MA_ASSERT(request.item != m_Suballocations.end()); + Suballocation& suballoc = *request.item; + // Given suballocation is a free block. + D3D12MA_ASSERT(suballoc.type == SUBALLOCATION_TYPE_FREE); + // Given offset is inside this suballocation. + UINT64 offset = (UINT64)request.allocHandle - 1; + D3D12MA_ASSERT(offset >= suballoc.offset); + const UINT64 paddingBegin = offset - suballoc.offset; + D3D12MA_ASSERT(suballoc.size >= paddingBegin + allocSize); + const UINT64 paddingEnd = suballoc.size - paddingBegin - allocSize; + + // Unregister this free suballocation from m_FreeSuballocationsBySize and update + // it to become used. + UnregisterFreeSuballocation(request.item); + + suballoc.offset = offset; + suballoc.size = allocSize; + suballoc.type = SUBALLOCATION_TYPE_ALLOCATION; + suballoc.privateData = privateData; + + // If there are any free bytes remaining at the end, insert new free suballocation after current one. + if (paddingEnd) + { + Suballocation paddingSuballoc = {}; + paddingSuballoc.offset = offset + allocSize; + paddingSuballoc.size = paddingEnd; + paddingSuballoc.type = SUBALLOCATION_TYPE_FREE; + SuballocationList::iterator next = request.item; + ++next; + const SuballocationList::iterator paddingEndItem = + m_Suballocations.insert(next, paddingSuballoc); + RegisterFreeSuballocation(paddingEndItem); + } + + // If there are any free bytes remaining at the beginning, insert new free suballocation before current one. + if (paddingBegin) + { + Suballocation paddingSuballoc = {}; + paddingSuballoc.offset = offset - paddingBegin; + paddingSuballoc.size = paddingBegin; + paddingSuballoc.type = SUBALLOCATION_TYPE_FREE; + const SuballocationList::iterator paddingBeginItem = + m_Suballocations.insert(request.item, paddingSuballoc); + RegisterFreeSuballocation(paddingBeginItem); + } + + // Update totals. + m_FreeCount = m_FreeCount - 1; + if (paddingBegin > 0) + { + ++m_FreeCount; + } + if (paddingEnd > 0) + { + ++m_FreeCount; + } + m_SumFreeSize -= allocSize; + + m_ZeroInitializedRange.MarkRangeAsUsed(offset, offset + allocSize); +} + +void BlockMetadata_Generic::Free(AllocHandle allocHandle) +{ + FreeSuballocation(FindAtOffset((UINT64)allocHandle - 1).dropConst()); +} + +void BlockMetadata_Generic::Clear() +{ + m_FreeCount = 1; + m_SumFreeSize = GetSize(); + + m_Suballocations.clear(); + Suballocation suballoc = {}; + suballoc.offset = 0; + suballoc.size = GetSize(); + suballoc.type = SUBALLOCATION_TYPE_FREE; + m_Suballocations.push_back(suballoc); + + m_FreeSuballocationsBySize.clear(); + m_FreeSuballocationsBySize.push_back(m_Suballocations.begin()); +} + +SuballocationList::const_iterator BlockMetadata_Generic::FindAtOffset(UINT64 offset) const +{ + const UINT64 last = m_Suballocations.crbegin()->offset; + if (last == offset) + return m_Suballocations.crbegin(); + const UINT64 first = m_Suballocations.cbegin()->offset; + if (first == offset) + return m_Suballocations.cbegin(); + + const size_t suballocCount = m_Suballocations.size(); + const UINT64 step = (last - first + m_Suballocations.cbegin()->size) / suballocCount; + auto findSuballocation = [&](auto begin, auto end) -> SuballocationList::const_iterator + { + for (auto suballocItem = begin; + suballocItem != end; + ++suballocItem) + { + const Suballocation& suballoc = *suballocItem; + if (suballoc.offset == offset) + return suballocItem; + } + D3D12MA_ASSERT(false && "Not found!"); + return m_Suballocations.end(); + }; + // If requested offset is closer to the end of range, search from the end + if ((offset - first) > suballocCount * step / 2) + { + return findSuballocation(m_Suballocations.crbegin(), m_Suballocations.crend()); + } + return findSuballocation(m_Suballocations.cbegin(), m_Suballocations.cend()); +} + +bool BlockMetadata_Generic::ValidateFreeSuballocationList() const +{ + UINT64 lastSize = 0; + for (size_t i = 0, count = m_FreeSuballocationsBySize.size(); i < count; ++i) + { + const SuballocationList::iterator it = m_FreeSuballocationsBySize[i]; + + D3D12MA_VALIDATE(it->type == SUBALLOCATION_TYPE_FREE); + D3D12MA_VALIDATE(it->size >= MIN_FREE_SUBALLOCATION_SIZE_TO_REGISTER); + D3D12MA_VALIDATE(it->size >= lastSize); + lastSize = it->size; + } + return true; +} + +bool BlockMetadata_Generic::CheckAllocation( + UINT64 allocSize, + UINT64 allocAlignment, + SuballocationList::const_iterator suballocItem, + AllocHandle* pAllocHandle, + UINT64* pSumFreeSize, + UINT64* pSumItemSize, + BOOL* pZeroInitialized) const +{ + D3D12MA_ASSERT(allocSize > 0); + D3D12MA_ASSERT(suballocItem != m_Suballocations.cend()); + D3D12MA_ASSERT(pAllocHandle != NULL && pZeroInitialized != NULL); + + *pSumFreeSize = 0; + *pSumItemSize = 0; + *pZeroInitialized = FALSE; + + const Suballocation& suballoc = *suballocItem; + D3D12MA_ASSERT(suballoc.type == SUBALLOCATION_TYPE_FREE); + + *pSumFreeSize = suballoc.size; + + // Size of this suballocation is too small for this request: Early return. + if (suballoc.size < allocSize) + { + return false; + } + + // Start from offset equal to beginning of this suballocation and debug margin of previous allocation if present. + UINT64 offset = suballoc.offset + (suballocItem == m_Suballocations.cbegin() ? 0 : GetDebugMargin()); + + // Apply alignment. + offset = AlignUp(offset, allocAlignment); + + // Calculate padding at the beginning based on current offset. + const UINT64 paddingBegin = offset - suballoc.offset; + + // Fail if requested size plus margin after is bigger than size of this suballocation. + if (paddingBegin + allocSize + GetDebugMargin() > suballoc.size) + { + return false; + } + + // All tests passed: Success. Offset is already filled. + *pZeroInitialized = m_ZeroInitializedRange.IsRangeZeroInitialized(offset, offset + allocSize); + *pAllocHandle = (AllocHandle)(offset + 1); + return true; +} + +void BlockMetadata_Generic::MergeFreeWithNext(SuballocationList::iterator item) +{ + D3D12MA_ASSERT(item != m_Suballocations.end()); + D3D12MA_ASSERT(item->type == SUBALLOCATION_TYPE_FREE); + + SuballocationList::iterator nextItem = item; + ++nextItem; + D3D12MA_ASSERT(nextItem != m_Suballocations.end()); + D3D12MA_ASSERT(nextItem->type == SUBALLOCATION_TYPE_FREE); + + item->size += nextItem->size; + --m_FreeCount; + m_Suballocations.erase(nextItem); +} + +SuballocationList::iterator BlockMetadata_Generic::FreeSuballocation(SuballocationList::iterator suballocItem) +{ + // Change this suballocation to be marked as free. + Suballocation& suballoc = *suballocItem; + suballoc.type = SUBALLOCATION_TYPE_FREE; + suballoc.privateData = NULL; + + // Update totals. + ++m_FreeCount; + m_SumFreeSize += suballoc.size; + + // Merge with previous and/or next suballocation if it's also free. + bool mergeWithNext = false; + bool mergeWithPrev = false; + + SuballocationList::iterator nextItem = suballocItem; + ++nextItem; + if ((nextItem != m_Suballocations.end()) && (nextItem->type == SUBALLOCATION_TYPE_FREE)) + { + mergeWithNext = true; + } + + SuballocationList::iterator prevItem = suballocItem; + if (suballocItem != m_Suballocations.begin()) + { + --prevItem; + if (prevItem->type == SUBALLOCATION_TYPE_FREE) + { + mergeWithPrev = true; + } + } + + if (mergeWithNext) + { + UnregisterFreeSuballocation(nextItem); + MergeFreeWithNext(suballocItem); + } + + if (mergeWithPrev) + { + UnregisterFreeSuballocation(prevItem); + MergeFreeWithNext(prevItem); + RegisterFreeSuballocation(prevItem); + return prevItem; + } + else + { + RegisterFreeSuballocation(suballocItem); + return suballocItem; + } +} + +void BlockMetadata_Generic::RegisterFreeSuballocation(SuballocationList::iterator item) +{ + D3D12MA_ASSERT(item->type == SUBALLOCATION_TYPE_FREE); + D3D12MA_ASSERT(item->size > 0); + + // You may want to enable this validation at the beginning or at the end of + // this function, depending on what do you want to check. + D3D12MA_HEAVY_ASSERT(ValidateFreeSuballocationList()); + + if (item->size >= MIN_FREE_SUBALLOCATION_SIZE_TO_REGISTER) + { + if (m_FreeSuballocationsBySize.empty()) + { + m_FreeSuballocationsBySize.push_back(item); + } + else + { + m_FreeSuballocationsBySize.InsertSorted(item, SuballocationItemSizeLess()); + } + } + + //D3D12MA_HEAVY_ASSERT(ValidateFreeSuballocationList()); +} + +void BlockMetadata_Generic::UnregisterFreeSuballocation(SuballocationList::iterator item) +{ + D3D12MA_ASSERT(item->type == SUBALLOCATION_TYPE_FREE); + D3D12MA_ASSERT(item->size > 0); + + // You may want to enable this validation at the beginning or at the end of + // this function, depending on what do you want to check. + D3D12MA_HEAVY_ASSERT(ValidateFreeSuballocationList()); + + if (item->size >= MIN_FREE_SUBALLOCATION_SIZE_TO_REGISTER) + { + SuballocationList::iterator* const it = BinaryFindFirstNotLess( + m_FreeSuballocationsBySize.data(), + m_FreeSuballocationsBySize.data() + m_FreeSuballocationsBySize.size(), + item, + SuballocationItemSizeLess()); + for (size_t index = it - m_FreeSuballocationsBySize.data(); + index < m_FreeSuballocationsBySize.size(); + ++index) + { + if (m_FreeSuballocationsBySize[index] == item) + { + m_FreeSuballocationsBySize.remove(index); + return; + } + D3D12MA_ASSERT((m_FreeSuballocationsBySize[index]->size == item->size) && "Not found."); + } + D3D12MA_ASSERT(0 && "Not found."); + } + + //D3D12MA_HEAVY_ASSERT(ValidateFreeSuballocationList()); +} + +void BlockMetadata_Generic::SetAllocationPrivateData(AllocHandle allocHandle, void* privateData) +{ + Suballocation& suballoc = *FindAtOffset((UINT64)allocHandle - 1).dropConst(); + suballoc.privateData = privateData; +} + +void BlockMetadata_Generic::AddStatistics(Statistics& inoutStats) const +{ + inoutStats.BlockCount++; + inoutStats.AllocationCount += (UINT)m_Suballocations.size() - m_FreeCount; + inoutStats.BlockBytes += GetSize(); + inoutStats.AllocationBytes += GetSize() - m_SumFreeSize; +} + +void BlockMetadata_Generic::AddDetailedStatistics(DetailedStatistics& inoutStats) const +{ + inoutStats.Stats.BlockCount++; + inoutStats.Stats.BlockBytes += GetSize(); + + for (const auto& suballoc : m_Suballocations) + { + if (suballoc.type == SUBALLOCATION_TYPE_FREE) + AddDetailedStatisticsUnusedRange(inoutStats, suballoc.size); + else + AddDetailedStatisticsAllocation(inoutStats, suballoc.size); + } +} + +void BlockMetadata_Generic::WriteAllocationInfoToJson(JsonWriter& json) const +{ + PrintDetailedMap_Begin(json, GetSumFreeSize(), GetAllocationCount(), m_FreeCount); + for (const auto& suballoc : m_Suballocations) + { + if (suballoc.type == SUBALLOCATION_TYPE_FREE) + PrintDetailedMap_UnusedRange(json, suballoc.offset, suballoc.size); + else + PrintDetailedMap_Allocation(json, suballoc.offset, suballoc.size, suballoc.privateData); + } + PrintDetailedMap_End(json); +} +#endif // _D3D12MA_BLOCK_METADATA_GENERIC_FUNCTIONS +#endif // _D3D12MA_BLOCK_METADATA_GENERIC +#endif // #if 0 + +#ifndef _D3D12MA_BLOCK_METADATA_LINEAR +class BlockMetadata_Linear : public BlockMetadata +{ +public: + BlockMetadata_Linear(const ALLOCATION_CALLBACKS* allocationCallbacks, bool isVirtual); + virtual ~BlockMetadata_Linear() = default; + + UINT64 GetSumFreeSize() const override { return m_SumFreeSize; } + bool IsEmpty() const override { return GetAllocationCount() == 0; } + UINT64 GetAllocationOffset(AllocHandle allocHandle) const override { return (UINT64)allocHandle - 1; }; + + void Init(UINT64 size) override; + bool Validate() const override; + size_t GetAllocationCount() const override; + size_t GetFreeRegionsCount() const override; + void GetAllocationInfo(AllocHandle allocHandle, VIRTUAL_ALLOCATION_INFO& outInfo) const override; + + bool CreateAllocationRequest( + UINT64 allocSize, + UINT64 allocAlignment, + bool upperAddress, + UINT32 strategy, + AllocationRequest* pAllocationRequest) override; + + void Alloc( + const AllocationRequest& request, + UINT64 allocSize, + void* privateData) override; + + void Free(AllocHandle allocHandle) override; + void Clear() override; + + AllocHandle GetAllocationListBegin() const override; + AllocHandle GetNextAllocation(AllocHandle prevAlloc) const override; + UINT64 GetNextFreeRegionSize(AllocHandle alloc) const override; + void* GetAllocationPrivateData(AllocHandle allocHandle) const override; + void SetAllocationPrivateData(AllocHandle allocHandle, void* privateData) override; + + void AddStatistics(Statistics& inoutStats) const override; + void AddDetailedStatistics(DetailedStatistics& inoutStats) const override; + void WriteAllocationInfoToJson(JsonWriter& json) const override; + void DebugLogAllAllocations() const override; + +private: + /* + There are two suballocation vectors, used in ping-pong way. + The one with index m_1stVectorIndex is called 1st. + The one with index (m_1stVectorIndex ^ 1) is called 2nd. + 2nd can be non-empty only when 1st is not empty. + When 2nd is not empty, m_2ndVectorMode indicates its mode of operation. + */ + typedef Vector SuballocationVectorType; + + enum ALLOC_REQUEST_TYPE + { + ALLOC_REQUEST_UPPER_ADDRESS, + ALLOC_REQUEST_END_OF_1ST, + ALLOC_REQUEST_END_OF_2ND, + }; + + enum SECOND_VECTOR_MODE + { + SECOND_VECTOR_EMPTY, + /* + Suballocations in 2nd vector are created later than the ones in 1st, but they + all have smaller offset. + */ + SECOND_VECTOR_RING_BUFFER, + /* + Suballocations in 2nd vector are upper side of double stack. + They all have offsets higher than those in 1st vector. + Top of this stack means smaller offsets, but higher indices in this vector. + */ + SECOND_VECTOR_DOUBLE_STACK, + }; + + UINT64 m_SumFreeSize; + SuballocationVectorType m_Suballocations0, m_Suballocations1; + UINT32 m_1stVectorIndex; + SECOND_VECTOR_MODE m_2ndVectorMode; + // Number of items in 1st vector with hAllocation = null at the beginning. + size_t m_1stNullItemsBeginCount; + // Number of other items in 1st vector with hAllocation = null somewhere in the middle. + size_t m_1stNullItemsMiddleCount; + // Number of items in 2nd vector with hAllocation = null. + size_t m_2ndNullItemsCount; + + SuballocationVectorType& AccessSuballocations1st() { return m_1stVectorIndex ? m_Suballocations1 : m_Suballocations0; } + SuballocationVectorType& AccessSuballocations2nd() { return m_1stVectorIndex ? m_Suballocations0 : m_Suballocations1; } + const SuballocationVectorType& AccessSuballocations1st() const { return m_1stVectorIndex ? m_Suballocations1 : m_Suballocations0; } + const SuballocationVectorType& AccessSuballocations2nd() const { return m_1stVectorIndex ? m_Suballocations0 : m_Suballocations1; } + + Suballocation& FindSuballocation(UINT64 offset) const; + bool ShouldCompact1st() const; + void CleanupAfterFree(); + + bool CreateAllocationRequest_LowerAddress( + UINT64 allocSize, + UINT64 allocAlignment, + AllocationRequest* pAllocationRequest); + bool CreateAllocationRequest_UpperAddress( + UINT64 allocSize, + UINT64 allocAlignment, + AllocationRequest* pAllocationRequest); + + D3D12MA_CLASS_NO_COPY(BlockMetadata_Linear) +}; + +#ifndef _D3D12MA_BLOCK_METADATA_LINEAR_FUNCTIONS +BlockMetadata_Linear::BlockMetadata_Linear(const ALLOCATION_CALLBACKS* allocationCallbacks, bool isVirtual) + : BlockMetadata(allocationCallbacks, isVirtual), + m_SumFreeSize(0), + m_Suballocations0(*allocationCallbacks), + m_Suballocations1(*allocationCallbacks), + m_1stVectorIndex(0), + m_2ndVectorMode(SECOND_VECTOR_EMPTY), + m_1stNullItemsBeginCount(0), + m_1stNullItemsMiddleCount(0), + m_2ndNullItemsCount(0) +{ + D3D12MA_ASSERT(allocationCallbacks); +} + +void BlockMetadata_Linear::Init(UINT64 size) +{ + BlockMetadata::Init(size); + m_SumFreeSize = size; +} + +bool BlockMetadata_Linear::Validate() const +{ + D3D12MA_VALIDATE(GetSumFreeSize() <= GetSize()); + const SuballocationVectorType& suballocations1st = AccessSuballocations1st(); + const SuballocationVectorType& suballocations2nd = AccessSuballocations2nd(); + + D3D12MA_VALIDATE(suballocations2nd.empty() == (m_2ndVectorMode == SECOND_VECTOR_EMPTY)); + D3D12MA_VALIDATE(!suballocations1st.empty() || + suballocations2nd.empty() || + m_2ndVectorMode != SECOND_VECTOR_RING_BUFFER); + + if (!suballocations1st.empty()) + { + // Null item at the beginning should be accounted into m_1stNullItemsBeginCount. + D3D12MA_VALIDATE(suballocations1st[m_1stNullItemsBeginCount].type != SUBALLOCATION_TYPE_FREE); + // Null item at the end should be just pop_back(). + D3D12MA_VALIDATE(suballocations1st.back().type != SUBALLOCATION_TYPE_FREE); + } + if (!suballocations2nd.empty()) + { + // Null item at the end should be just pop_back(). + D3D12MA_VALIDATE(suballocations2nd.back().type != SUBALLOCATION_TYPE_FREE); + } + + D3D12MA_VALIDATE(m_1stNullItemsBeginCount + m_1stNullItemsMiddleCount <= suballocations1st.size()); + D3D12MA_VALIDATE(m_2ndNullItemsCount <= suballocations2nd.size()); + + UINT64 sumUsedSize = 0; + const size_t suballoc1stCount = suballocations1st.size(); + UINT64 offset = 0; + + if (m_2ndVectorMode == SECOND_VECTOR_RING_BUFFER) + { + const size_t suballoc2ndCount = suballocations2nd.size(); + size_t nullItem2ndCount = 0; + for (size_t i = 0; i < suballoc2ndCount; ++i) + { + const Suballocation& suballoc = suballocations2nd[i]; + const bool currFree = (suballoc.type == SUBALLOCATION_TYPE_FREE); + + const Allocation* alloc = (Allocation*)suballoc.privateData; + if (!IsVirtual()) + { + D3D12MA_VALIDATE(currFree == (alloc == NULL)); + } + D3D12MA_VALIDATE(suballoc.offset >= offset); + + if (!currFree) + { + if (!IsVirtual()) + { + D3D12MA_VALIDATE(GetAllocationOffset(alloc->GetAllocHandle()) == suballoc.offset); + D3D12MA_VALIDATE(alloc->GetSize() == suballoc.size); + } + sumUsedSize += suballoc.size; + } + else + { + ++nullItem2ndCount; + } + + offset = suballoc.offset + suballoc.size + GetDebugMargin(); + } + + D3D12MA_VALIDATE(nullItem2ndCount == m_2ndNullItemsCount); + } + + for (size_t i = 0; i < m_1stNullItemsBeginCount; ++i) + { + const Suballocation& suballoc = suballocations1st[i]; + D3D12MA_VALIDATE(suballoc.type == SUBALLOCATION_TYPE_FREE && + suballoc.privateData == NULL); + } + + size_t nullItem1stCount = m_1stNullItemsBeginCount; + + for (size_t i = m_1stNullItemsBeginCount; i < suballoc1stCount; ++i) + { + const Suballocation& suballoc = suballocations1st[i]; + const bool currFree = (suballoc.type == SUBALLOCATION_TYPE_FREE); + + const Allocation* alloc = (Allocation*)suballoc.privateData; + if (!IsVirtual()) + { + D3D12MA_VALIDATE(currFree == (alloc == NULL)); + } + D3D12MA_VALIDATE(suballoc.offset >= offset); + D3D12MA_VALIDATE(i >= m_1stNullItemsBeginCount || currFree); + + if (!currFree) + { + if (!IsVirtual()) + { + D3D12MA_VALIDATE(GetAllocationOffset(alloc->GetAllocHandle()) == suballoc.offset); + D3D12MA_VALIDATE(alloc->GetSize() == suballoc.size); + } + sumUsedSize += suballoc.size; + } + else + { + ++nullItem1stCount; + } + + offset = suballoc.offset + suballoc.size + GetDebugMargin(); + } + D3D12MA_VALIDATE(nullItem1stCount == m_1stNullItemsBeginCount + m_1stNullItemsMiddleCount); + + if (m_2ndVectorMode == SECOND_VECTOR_DOUBLE_STACK) + { + const size_t suballoc2ndCount = suballocations2nd.size(); + size_t nullItem2ndCount = 0; + for (size_t i = suballoc2ndCount; i--; ) + { + const Suballocation& suballoc = suballocations2nd[i]; + const bool currFree = (suballoc.type == SUBALLOCATION_TYPE_FREE); + + const Allocation* alloc = (Allocation*)suballoc.privateData; + if (!IsVirtual()) + { + D3D12MA_VALIDATE(currFree == (alloc == NULL)); + } + D3D12MA_VALIDATE(suballoc.offset >= offset); + + if (!currFree) + { + if (!IsVirtual()) + { + D3D12MA_VALIDATE(GetAllocationOffset(alloc->GetAllocHandle()) == suballoc.offset); + D3D12MA_VALIDATE(alloc->GetSize() == suballoc.size); + } + sumUsedSize += suballoc.size; + } + else + { + ++nullItem2ndCount; + } + + offset = suballoc.offset + suballoc.size + GetDebugMargin(); + } + + D3D12MA_VALIDATE(nullItem2ndCount == m_2ndNullItemsCount); + } + + D3D12MA_VALIDATE(offset <= GetSize()); + D3D12MA_VALIDATE(m_SumFreeSize == GetSize() - sumUsedSize); + + return true; +} + +size_t BlockMetadata_Linear::GetAllocationCount() const +{ + return AccessSuballocations1st().size() - m_1stNullItemsBeginCount - m_1stNullItemsMiddleCount + + AccessSuballocations2nd().size() - m_2ndNullItemsCount; +} + +size_t BlockMetadata_Linear::GetFreeRegionsCount() const +{ + // Function only used for defragmentation, which is disabled for this algorithm + D3D12MA_ASSERT(0); + return SIZE_MAX; +} + +void BlockMetadata_Linear::GetAllocationInfo(AllocHandle allocHandle, VIRTUAL_ALLOCATION_INFO& outInfo) const +{ + const Suballocation& suballoc = FindSuballocation((UINT64)allocHandle - 1); + outInfo.Offset = suballoc.offset; + outInfo.Size = suballoc.size; + outInfo.pPrivateData = suballoc.privateData; +} + +bool BlockMetadata_Linear::CreateAllocationRequest( + UINT64 allocSize, + UINT64 allocAlignment, + bool upperAddress, + UINT32 strategy, + AllocationRequest* pAllocationRequest) +{ + D3D12MA_ASSERT(allocSize > 0 && "Cannot allocate empty block!"); + D3D12MA_ASSERT(pAllocationRequest != NULL); + D3D12MA_HEAVY_ASSERT(Validate()); + pAllocationRequest->size = allocSize; + return upperAddress ? + CreateAllocationRequest_UpperAddress( + allocSize, allocAlignment, pAllocationRequest) : + CreateAllocationRequest_LowerAddress( + allocSize, allocAlignment, pAllocationRequest); +} + +void BlockMetadata_Linear::Alloc( + const AllocationRequest& request, + UINT64 allocSize, + void* privateData) +{ + UINT64 offset = (UINT64)request.allocHandle - 1; + const Suballocation newSuballoc = { offset, request.size, privateData, SUBALLOCATION_TYPE_ALLOCATION }; + + switch (request.algorithmData) + { + case ALLOC_REQUEST_UPPER_ADDRESS: + { + D3D12MA_ASSERT(m_2ndVectorMode != SECOND_VECTOR_RING_BUFFER && + "CRITICAL ERROR: Trying to use linear allocator as double stack while it was already used as ring buffer."); + SuballocationVectorType& suballocations2nd = AccessSuballocations2nd(); + suballocations2nd.push_back(newSuballoc); + m_2ndVectorMode = SECOND_VECTOR_DOUBLE_STACK; + break; + } + case ALLOC_REQUEST_END_OF_1ST: + { + SuballocationVectorType& suballocations1st = AccessSuballocations1st(); + + D3D12MA_ASSERT(suballocations1st.empty() || + offset >= suballocations1st.back().offset + suballocations1st.back().size); + // Check if it fits before the end of the block. + D3D12MA_ASSERT(offset + request.size <= GetSize()); + + suballocations1st.push_back(newSuballoc); + break; + } + case ALLOC_REQUEST_END_OF_2ND: + { + SuballocationVectorType& suballocations1st = AccessSuballocations1st(); + // New allocation at the end of 2-part ring buffer, so before first allocation from 1st vector. + D3D12MA_ASSERT(!suballocations1st.empty() && + offset + request.size <= suballocations1st[m_1stNullItemsBeginCount].offset); + SuballocationVectorType& suballocations2nd = AccessSuballocations2nd(); + + switch (m_2ndVectorMode) + { + case SECOND_VECTOR_EMPTY: + // First allocation from second part ring buffer. + D3D12MA_ASSERT(suballocations2nd.empty()); + m_2ndVectorMode = SECOND_VECTOR_RING_BUFFER; + break; + case SECOND_VECTOR_RING_BUFFER: + // 2-part ring buffer is already started. + D3D12MA_ASSERT(!suballocations2nd.empty()); + break; + case SECOND_VECTOR_DOUBLE_STACK: + D3D12MA_ASSERT(0 && "CRITICAL ERROR: Trying to use linear allocator as ring buffer while it was already used as double stack."); + break; + default: + D3D12MA_ASSERT(0); + } + + suballocations2nd.push_back(newSuballoc); + break; + } + default: + D3D12MA_ASSERT(0 && "CRITICAL INTERNAL ERROR."); + } + m_SumFreeSize -= newSuballoc.size; +} + +void BlockMetadata_Linear::Free(AllocHandle allocHandle) +{ + SuballocationVectorType& suballocations1st = AccessSuballocations1st(); + SuballocationVectorType& suballocations2nd = AccessSuballocations2nd(); + UINT64 offset = (UINT64)allocHandle - 1; + + if (!suballocations1st.empty()) + { + // First allocation: Mark it as next empty at the beginning. + Suballocation& firstSuballoc = suballocations1st[m_1stNullItemsBeginCount]; + if (firstSuballoc.offset == offset) + { + firstSuballoc.type = SUBALLOCATION_TYPE_FREE; + firstSuballoc.privateData = NULL; + m_SumFreeSize += firstSuballoc.size; + ++m_1stNullItemsBeginCount; + CleanupAfterFree(); + return; + } + } + + // Last allocation in 2-part ring buffer or top of upper stack (same logic). + if (m_2ndVectorMode == SECOND_VECTOR_RING_BUFFER || + m_2ndVectorMode == SECOND_VECTOR_DOUBLE_STACK) + { + Suballocation& lastSuballoc = suballocations2nd.back(); + if (lastSuballoc.offset == offset) + { + m_SumFreeSize += lastSuballoc.size; + suballocations2nd.pop_back(); + CleanupAfterFree(); + return; + } + } + // Last allocation in 1st vector. + else if (m_2ndVectorMode == SECOND_VECTOR_EMPTY) + { + Suballocation& lastSuballoc = suballocations1st.back(); + if (lastSuballoc.offset == offset) + { + m_SumFreeSize += lastSuballoc.size; + suballocations1st.pop_back(); + CleanupAfterFree(); + return; + } + } + + Suballocation refSuballoc; + refSuballoc.offset = offset; + // Rest of members stays uninitialized intentionally for better performance. + + // Item from the middle of 1st vector. + { + const SuballocationVectorType::iterator it = BinaryFindSorted( + suballocations1st.begin() + m_1stNullItemsBeginCount, + suballocations1st.end(), + refSuballoc, + SuballocationOffsetLess()); + if (it != suballocations1st.end()) + { + it->type = SUBALLOCATION_TYPE_FREE; + it->privateData = NULL; + ++m_1stNullItemsMiddleCount; + m_SumFreeSize += it->size; + CleanupAfterFree(); + return; + } + } + + if (m_2ndVectorMode != SECOND_VECTOR_EMPTY) + { + // Item from the middle of 2nd vector. + const SuballocationVectorType::iterator it = m_2ndVectorMode == SECOND_VECTOR_RING_BUFFER ? + BinaryFindSorted(suballocations2nd.begin(), suballocations2nd.end(), refSuballoc, SuballocationOffsetLess()) : + BinaryFindSorted(suballocations2nd.begin(), suballocations2nd.end(), refSuballoc, SuballocationOffsetGreater()); + if (it != suballocations2nd.end()) + { + it->type = SUBALLOCATION_TYPE_FREE; + it->privateData = NULL; + ++m_2ndNullItemsCount; + m_SumFreeSize += it->size; + CleanupAfterFree(); + return; + } + } + + D3D12MA_ASSERT(0 && "Allocation to free not found in linear allocator!"); +} + +void BlockMetadata_Linear::Clear() +{ + m_SumFreeSize = GetSize(); + m_Suballocations0.clear(); + m_Suballocations1.clear(); + // Leaving m_1stVectorIndex unchanged - it doesn't matter. + m_2ndVectorMode = SECOND_VECTOR_EMPTY; + m_1stNullItemsBeginCount = 0; + m_1stNullItemsMiddleCount = 0; + m_2ndNullItemsCount = 0; +} + +AllocHandle BlockMetadata_Linear::GetAllocationListBegin() const +{ + // Function only used for defragmentation, which is disabled for this algorithm + D3D12MA_ASSERT(0); + return (AllocHandle)0; +} + +AllocHandle BlockMetadata_Linear::GetNextAllocation(AllocHandle prevAlloc) const +{ + // Function only used for defragmentation, which is disabled for this algorithm + D3D12MA_ASSERT(0); + return (AllocHandle)0; +} + +UINT64 BlockMetadata_Linear::GetNextFreeRegionSize(AllocHandle alloc) const +{ + // Function only used for defragmentation, which is disabled for this algorithm + D3D12MA_ASSERT(0); + return 0; +} + +void* BlockMetadata_Linear::GetAllocationPrivateData(AllocHandle allocHandle) const +{ + return FindSuballocation((UINT64)allocHandle - 1).privateData; +} + +void BlockMetadata_Linear::SetAllocationPrivateData(AllocHandle allocHandle, void* privateData) +{ + Suballocation& suballoc = FindSuballocation((UINT64)allocHandle - 1); + suballoc.privateData = privateData; +} + +void BlockMetadata_Linear::AddStatistics(Statistics& inoutStats) const +{ + inoutStats.BlockCount++; + inoutStats.AllocationCount += (UINT)GetAllocationCount(); + inoutStats.BlockBytes += GetSize(); + inoutStats.AllocationBytes += GetSize() - m_SumFreeSize; +} + +void BlockMetadata_Linear::AddDetailedStatistics(DetailedStatistics& inoutStats) const +{ + inoutStats.Stats.BlockCount++; + inoutStats.Stats.BlockBytes += GetSize(); + + const UINT64 size = GetSize(); + const SuballocationVectorType& suballocations1st = AccessSuballocations1st(); + const SuballocationVectorType& suballocations2nd = AccessSuballocations2nd(); + const size_t suballoc1stCount = suballocations1st.size(); + const size_t suballoc2ndCount = suballocations2nd.size(); + + UINT64 lastOffset = 0; + if (m_2ndVectorMode == SECOND_VECTOR_RING_BUFFER) + { + const UINT64 freeSpace2ndTo1stEnd = suballocations1st[m_1stNullItemsBeginCount].offset; + size_t nextAlloc2ndIndex = 0; + while (lastOffset < freeSpace2ndTo1stEnd) + { + // Find next non-null allocation or move nextAllocIndex to the end. + while (nextAlloc2ndIndex < suballoc2ndCount && + suballocations2nd[nextAlloc2ndIndex].privateData == NULL) + { + ++nextAlloc2ndIndex; + } + + // Found non-null allocation. + if (nextAlloc2ndIndex < suballoc2ndCount) + { + const Suballocation& suballoc = suballocations2nd[nextAlloc2ndIndex]; + + // 1. Process free space before this allocation. + if (lastOffset < suballoc.offset) + { + // There is free space from lastOffset to suballoc.offset. + const UINT64 unusedRangeSize = suballoc.offset - lastOffset; + AddDetailedStatisticsUnusedRange(inoutStats, unusedRangeSize); + } + + // 2. Process this allocation. + // There is allocation with suballoc.offset, suballoc.size. + AddDetailedStatisticsAllocation(inoutStats, suballoc.size); + + // 3. Prepare for next iteration. + lastOffset = suballoc.offset + suballoc.size; + ++nextAlloc2ndIndex; + } + // We are at the end. + else + { + // There is free space from lastOffset to freeSpace2ndTo1stEnd. + if (lastOffset < freeSpace2ndTo1stEnd) + { + const UINT64 unusedRangeSize = freeSpace2ndTo1stEnd - lastOffset; + AddDetailedStatisticsUnusedRange(inoutStats, unusedRangeSize); + } + + // End of loop. + lastOffset = freeSpace2ndTo1stEnd; + } + } + } + + size_t nextAlloc1stIndex = m_1stNullItemsBeginCount; + const UINT64 freeSpace1stTo2ndEnd = + m_2ndVectorMode == SECOND_VECTOR_DOUBLE_STACK ? suballocations2nd.back().offset : size; + while (lastOffset < freeSpace1stTo2ndEnd) + { + // Find next non-null allocation or move nextAllocIndex to the end. + while (nextAlloc1stIndex < suballoc1stCount && + suballocations1st[nextAlloc1stIndex].privateData == NULL) + { + ++nextAlloc1stIndex; + } + + // Found non-null allocation. + if (nextAlloc1stIndex < suballoc1stCount) + { + const Suballocation& suballoc = suballocations1st[nextAlloc1stIndex]; + + // 1. Process free space before this allocation. + if (lastOffset < suballoc.offset) + { + // There is free space from lastOffset to suballoc.offset. + const UINT64 unusedRangeSize = suballoc.offset - lastOffset; + AddDetailedStatisticsUnusedRange(inoutStats, unusedRangeSize); + } + + // 2. Process this allocation. + // There is allocation with suballoc.offset, suballoc.size. + AddDetailedStatisticsAllocation(inoutStats, suballoc.size); + + // 3. Prepare for next iteration. + lastOffset = suballoc.offset + suballoc.size; + ++nextAlloc1stIndex; + } + // We are at the end. + else + { + // There is free space from lastOffset to freeSpace1stTo2ndEnd. + if (lastOffset < freeSpace1stTo2ndEnd) + { + const UINT64 unusedRangeSize = freeSpace1stTo2ndEnd - lastOffset; + AddDetailedStatisticsUnusedRange(inoutStats, unusedRangeSize); + } + + // End of loop. + lastOffset = freeSpace1stTo2ndEnd; + } + } + + if (m_2ndVectorMode == SECOND_VECTOR_DOUBLE_STACK) + { + size_t nextAlloc2ndIndex = suballocations2nd.size() - 1; + while (lastOffset < size) + { + // Find next non-null allocation or move nextAllocIndex to the end. + while (nextAlloc2ndIndex != SIZE_MAX && + suballocations2nd[nextAlloc2ndIndex].privateData == NULL) + { + --nextAlloc2ndIndex; + } + + // Found non-null allocation. + if (nextAlloc2ndIndex != SIZE_MAX) + { + const Suballocation& suballoc = suballocations2nd[nextAlloc2ndIndex]; + + // 1. Process free space before this allocation. + if (lastOffset < suballoc.offset) + { + // There is free space from lastOffset to suballoc.offset. + const UINT64 unusedRangeSize = suballoc.offset - lastOffset; + AddDetailedStatisticsUnusedRange(inoutStats, unusedRangeSize); + } + + // 2. Process this allocation. + // There is allocation with suballoc.offset, suballoc.size. + AddDetailedStatisticsAllocation(inoutStats, suballoc.size); + + // 3. Prepare for next iteration. + lastOffset = suballoc.offset + suballoc.size; + --nextAlloc2ndIndex; + } + // We are at the end. + else + { + // There is free space from lastOffset to size. + if (lastOffset < size) + { + const UINT64 unusedRangeSize = size - lastOffset; + AddDetailedStatisticsUnusedRange(inoutStats, unusedRangeSize); + } + + // End of loop. + lastOffset = size; + } + } + } +} + +void BlockMetadata_Linear::WriteAllocationInfoToJson(JsonWriter& json) const +{ + const UINT64 size = GetSize(); + const SuballocationVectorType& suballocations1st = AccessSuballocations1st(); + const SuballocationVectorType& suballocations2nd = AccessSuballocations2nd(); + const size_t suballoc1stCount = suballocations1st.size(); + const size_t suballoc2ndCount = suballocations2nd.size(); + + // FIRST PASS + + size_t unusedRangeCount = 0; + UINT64 usedBytes = 0; + + UINT64 lastOffset = 0; + + size_t alloc2ndCount = 0; + if (m_2ndVectorMode == SECOND_VECTOR_RING_BUFFER) + { + const UINT64 freeSpace2ndTo1stEnd = suballocations1st[m_1stNullItemsBeginCount].offset; + size_t nextAlloc2ndIndex = 0; + while (lastOffset < freeSpace2ndTo1stEnd) + { + // Find next non-null allocation or move nextAlloc2ndIndex to the end. + while (nextAlloc2ndIndex < suballoc2ndCount && + suballocations2nd[nextAlloc2ndIndex].privateData == NULL) + { + ++nextAlloc2ndIndex; + } + + // Found non-null allocation. + if (nextAlloc2ndIndex < suballoc2ndCount) + { + const Suballocation& suballoc = suballocations2nd[nextAlloc2ndIndex]; + + // 1. Process free space before this allocation. + if (lastOffset < suballoc.offset) + { + // There is free space from lastOffset to suballoc.offset. + ++unusedRangeCount; + } + + // 2. Process this allocation. + // There is allocation with suballoc.offset, suballoc.size. + ++alloc2ndCount; + usedBytes += suballoc.size; + + // 3. Prepare for next iteration. + lastOffset = suballoc.offset + suballoc.size; + ++nextAlloc2ndIndex; + } + // We are at the end. + else + { + if (lastOffset < freeSpace2ndTo1stEnd) + { + // There is free space from lastOffset to freeSpace2ndTo1stEnd. + ++unusedRangeCount; + } + + // End of loop. + lastOffset = freeSpace2ndTo1stEnd; + } + } + } + + size_t nextAlloc1stIndex = m_1stNullItemsBeginCount; + size_t alloc1stCount = 0; + const UINT64 freeSpace1stTo2ndEnd = + m_2ndVectorMode == SECOND_VECTOR_DOUBLE_STACK ? suballocations2nd.back().offset : size; + while (lastOffset < freeSpace1stTo2ndEnd) + { + // Find next non-null allocation or move nextAllocIndex to the end. + while (nextAlloc1stIndex < suballoc1stCount && + suballocations1st[nextAlloc1stIndex].privateData == NULL) + { + ++nextAlloc1stIndex; + } + + // Found non-null allocation. + if (nextAlloc1stIndex < suballoc1stCount) + { + const Suballocation& suballoc = suballocations1st[nextAlloc1stIndex]; + + // 1. Process free space before this allocation. + if (lastOffset < suballoc.offset) + { + // There is free space from lastOffset to suballoc.offset. + ++unusedRangeCount; + } + + // 2. Process this allocation. + // There is allocation with suballoc.offset, suballoc.size. + ++alloc1stCount; + usedBytes += suballoc.size; + + // 3. Prepare for next iteration. + lastOffset = suballoc.offset + suballoc.size; + ++nextAlloc1stIndex; + } + // We are at the end. + else + { + if (lastOffset < size) + { + // There is free space from lastOffset to freeSpace1stTo2ndEnd. + ++unusedRangeCount; + } + + // End of loop. + lastOffset = freeSpace1stTo2ndEnd; + } + } + + if (m_2ndVectorMode == SECOND_VECTOR_DOUBLE_STACK) + { + size_t nextAlloc2ndIndex = suballocations2nd.size() - 1; + while (lastOffset < size) + { + // Find next non-null allocation or move nextAlloc2ndIndex to the end. + while (nextAlloc2ndIndex != SIZE_MAX && + suballocations2nd[nextAlloc2ndIndex].privateData == NULL) + { + --nextAlloc2ndIndex; + } + + // Found non-null allocation. + if (nextAlloc2ndIndex != SIZE_MAX) + { + const Suballocation& suballoc = suballocations2nd[nextAlloc2ndIndex]; + + // 1. Process free space before this allocation. + if (lastOffset < suballoc.offset) + { + // There is free space from lastOffset to suballoc.offset. + ++unusedRangeCount; + } + + // 2. Process this allocation. + // There is allocation with suballoc.offset, suballoc.size. + ++alloc2ndCount; + usedBytes += suballoc.size; + + // 3. Prepare for next iteration. + lastOffset = suballoc.offset + suballoc.size; + --nextAlloc2ndIndex; + } + // We are at the end. + else + { + if (lastOffset < size) + { + // There is free space from lastOffset to size. + ++unusedRangeCount; + } + + // End of loop. + lastOffset = size; + } + } + } + + const UINT64 unusedBytes = size - usedBytes; + PrintDetailedMap_Begin(json, unusedBytes, alloc1stCount + alloc2ndCount, unusedRangeCount); + + // SECOND PASS + lastOffset = 0; + if (m_2ndVectorMode == SECOND_VECTOR_RING_BUFFER) + { + const UINT64 freeSpace2ndTo1stEnd = suballocations1st[m_1stNullItemsBeginCount].offset; + size_t nextAlloc2ndIndex = 0; + while (lastOffset < freeSpace2ndTo1stEnd) + { + // Find next non-null allocation or move nextAlloc2ndIndex to the end. + while (nextAlloc2ndIndex < suballoc2ndCount && + suballocations2nd[nextAlloc2ndIndex].privateData == NULL) + { + ++nextAlloc2ndIndex; + } + + // Found non-null allocation. + if (nextAlloc2ndIndex < suballoc2ndCount) + { + const Suballocation& suballoc = suballocations2nd[nextAlloc2ndIndex]; + + // 1. Process free space before this allocation. + if (lastOffset < suballoc.offset) + { + // There is free space from lastOffset to suballoc.offset. + const UINT64 unusedRangeSize = suballoc.offset - lastOffset; + PrintDetailedMap_UnusedRange(json, lastOffset, unusedRangeSize); + } + + // 2. Process this allocation. + // There is allocation with suballoc.offset, suballoc.size. + PrintDetailedMap_Allocation(json, suballoc.offset, suballoc.size, suballoc.privateData); + + // 3. Prepare for next iteration. + lastOffset = suballoc.offset + suballoc.size; + ++nextAlloc2ndIndex; + } + // We are at the end. + else + { + if (lastOffset < freeSpace2ndTo1stEnd) + { + // There is free space from lastOffset to freeSpace2ndTo1stEnd. + const UINT64 unusedRangeSize = freeSpace2ndTo1stEnd - lastOffset; + PrintDetailedMap_UnusedRange(json, lastOffset, unusedRangeSize); + } + + // End of loop. + lastOffset = freeSpace2ndTo1stEnd; + } + } + } + + nextAlloc1stIndex = m_1stNullItemsBeginCount; + while (lastOffset < freeSpace1stTo2ndEnd) + { + // Find next non-null allocation or move nextAllocIndex to the end. + while (nextAlloc1stIndex < suballoc1stCount && + suballocations1st[nextAlloc1stIndex].privateData == NULL) + { + ++nextAlloc1stIndex; + } + + // Found non-null allocation. + if (nextAlloc1stIndex < suballoc1stCount) + { + const Suballocation& suballoc = suballocations1st[nextAlloc1stIndex]; + + // 1. Process free space before this allocation. + if (lastOffset < suballoc.offset) + { + // There is free space from lastOffset to suballoc.offset. + const UINT64 unusedRangeSize = suballoc.offset - lastOffset; + PrintDetailedMap_UnusedRange(json, lastOffset, unusedRangeSize); + } + + // 2. Process this allocation. + // There is allocation with suballoc.offset, suballoc.size. + PrintDetailedMap_Allocation(json, suballoc.offset, suballoc.size, suballoc.privateData); + + // 3. Prepare for next iteration. + lastOffset = suballoc.offset + suballoc.size; + ++nextAlloc1stIndex; + } + // We are at the end. + else + { + if (lastOffset < freeSpace1stTo2ndEnd) + { + // There is free space from lastOffset to freeSpace1stTo2ndEnd. + const UINT64 unusedRangeSize = freeSpace1stTo2ndEnd - lastOffset; + PrintDetailedMap_UnusedRange(json, lastOffset, unusedRangeSize); + } + + // End of loop. + lastOffset = freeSpace1stTo2ndEnd; + } + } + + if (m_2ndVectorMode == SECOND_VECTOR_DOUBLE_STACK) + { + size_t nextAlloc2ndIndex = suballocations2nd.size() - 1; + while (lastOffset < size) + { + // Find next non-null allocation or move nextAlloc2ndIndex to the end. + while (nextAlloc2ndIndex != SIZE_MAX && + suballocations2nd[nextAlloc2ndIndex].privateData == NULL) + { + --nextAlloc2ndIndex; + } + + // Found non-null allocation. + if (nextAlloc2ndIndex != SIZE_MAX) + { + const Suballocation& suballoc = suballocations2nd[nextAlloc2ndIndex]; + + // 1. Process free space before this allocation. + if (lastOffset < suballoc.offset) + { + // There is free space from lastOffset to suballoc.offset. + const UINT64 unusedRangeSize = suballoc.offset - lastOffset; + PrintDetailedMap_UnusedRange(json, lastOffset, unusedRangeSize); + } + + // 2. Process this allocation. + // There is allocation with suballoc.offset, suballoc.size. + PrintDetailedMap_Allocation(json, suballoc.offset, suballoc.size, suballoc.privateData); + + // 3. Prepare for next iteration. + lastOffset = suballoc.offset + suballoc.size; + --nextAlloc2ndIndex; + } + // We are at the end. + else + { + if (lastOffset < size) + { + // There is free space from lastOffset to size. + const UINT64 unusedRangeSize = size - lastOffset; + PrintDetailedMap_UnusedRange(json, lastOffset, unusedRangeSize); + } + + // End of loop. + lastOffset = size; + } + } + } + + PrintDetailedMap_End(json); +} + +void BlockMetadata_Linear::DebugLogAllAllocations() const +{ + const SuballocationVectorType& suballocations1st = AccessSuballocations1st(); + for (auto it = suballocations1st.begin() + m_1stNullItemsBeginCount; it != suballocations1st.end(); ++it) + if (it->type != SUBALLOCATION_TYPE_FREE) + DebugLogAllocation(it->offset, it->size, it->privateData); + + const SuballocationVectorType& suballocations2nd = AccessSuballocations2nd(); + for (auto it = suballocations2nd.begin(); it != suballocations2nd.end(); ++it) + if (it->type != SUBALLOCATION_TYPE_FREE) + DebugLogAllocation(it->offset, it->size, it->privateData); +} + +Suballocation& BlockMetadata_Linear::FindSuballocation(UINT64 offset) const +{ + const SuballocationVectorType& suballocations1st = AccessSuballocations1st(); + const SuballocationVectorType& suballocations2nd = AccessSuballocations2nd(); + + Suballocation refSuballoc; + refSuballoc.offset = offset; + // Rest of members stays uninitialized intentionally for better performance. + + // Item from the 1st vector. + { + const SuballocationVectorType::const_iterator it = BinaryFindSorted( + suballocations1st.begin() + m_1stNullItemsBeginCount, + suballocations1st.end(), + refSuballoc, + SuballocationOffsetLess()); + if (it != suballocations1st.end()) + { + return const_cast(*it); + } + } + + if (m_2ndVectorMode != SECOND_VECTOR_EMPTY) + { + // Rest of members stays uninitialized intentionally for better performance. + const SuballocationVectorType::const_iterator it = m_2ndVectorMode == SECOND_VECTOR_RING_BUFFER ? + BinaryFindSorted(suballocations2nd.begin(), suballocations2nd.end(), refSuballoc, SuballocationOffsetLess()) : + BinaryFindSorted(suballocations2nd.begin(), suballocations2nd.end(), refSuballoc, SuballocationOffsetGreater()); + if (it != suballocations2nd.end()) + { + return const_cast(*it); + } + } + + D3D12MA_ASSERT(0 && "Allocation not found in linear allocator!"); + return const_cast(suballocations1st.back()); // Should never occur. +} + +bool BlockMetadata_Linear::ShouldCompact1st() const +{ + const size_t nullItemCount = m_1stNullItemsBeginCount + m_1stNullItemsMiddleCount; + const size_t suballocCount = AccessSuballocations1st().size(); + return suballocCount > 32 && nullItemCount * 2 >= (suballocCount - nullItemCount) * 3; +} + +void BlockMetadata_Linear::CleanupAfterFree() +{ + SuballocationVectorType& suballocations1st = AccessSuballocations1st(); + SuballocationVectorType& suballocations2nd = AccessSuballocations2nd(); + + if (IsEmpty()) + { + suballocations1st.clear(); + suballocations2nd.clear(); + m_1stNullItemsBeginCount = 0; + m_1stNullItemsMiddleCount = 0; + m_2ndNullItemsCount = 0; + m_2ndVectorMode = SECOND_VECTOR_EMPTY; + } + else + { + const size_t suballoc1stCount = suballocations1st.size(); + const size_t nullItem1stCount = m_1stNullItemsBeginCount + m_1stNullItemsMiddleCount; + D3D12MA_ASSERT(nullItem1stCount <= suballoc1stCount); + + // Find more null items at the beginning of 1st vector. + while (m_1stNullItemsBeginCount < suballoc1stCount && + suballocations1st[m_1stNullItemsBeginCount].type == SUBALLOCATION_TYPE_FREE) + { + ++m_1stNullItemsBeginCount; + --m_1stNullItemsMiddleCount; + } + + // Find more null items at the end of 1st vector. + while (m_1stNullItemsMiddleCount > 0 && + suballocations1st.back().type == SUBALLOCATION_TYPE_FREE) + { + --m_1stNullItemsMiddleCount; + suballocations1st.pop_back(); + } + + // Find more null items at the end of 2nd vector. + while (m_2ndNullItemsCount > 0 && + suballocations2nd.back().type == SUBALLOCATION_TYPE_FREE) + { + --m_2ndNullItemsCount; + suballocations2nd.pop_back(); + } + + // Find more null items at the beginning of 2nd vector. + while (m_2ndNullItemsCount > 0 && + suballocations2nd[0].type == SUBALLOCATION_TYPE_FREE) + { + --m_2ndNullItemsCount; + suballocations2nd.remove(0); + } + + if (ShouldCompact1st()) + { + const size_t nonNullItemCount = suballoc1stCount - nullItem1stCount; + size_t srcIndex = m_1stNullItemsBeginCount; + for (size_t dstIndex = 0; dstIndex < nonNullItemCount; ++dstIndex) + { + while (suballocations1st[srcIndex].type == SUBALLOCATION_TYPE_FREE) + { + ++srcIndex; + } + if (dstIndex != srcIndex) + { + suballocations1st[dstIndex] = suballocations1st[srcIndex]; + } + ++srcIndex; + } + suballocations1st.resize(nonNullItemCount); + m_1stNullItemsBeginCount = 0; + m_1stNullItemsMiddleCount = 0; + } + + // 2nd vector became empty. + if (suballocations2nd.empty()) + { + m_2ndVectorMode = SECOND_VECTOR_EMPTY; + } + + // 1st vector became empty. + if (suballocations1st.size() - m_1stNullItemsBeginCount == 0) + { + suballocations1st.clear(); + m_1stNullItemsBeginCount = 0; + + if (!suballocations2nd.empty() && m_2ndVectorMode == SECOND_VECTOR_RING_BUFFER) + { + // Swap 1st with 2nd. Now 2nd is empty. + m_2ndVectorMode = SECOND_VECTOR_EMPTY; + m_1stNullItemsMiddleCount = m_2ndNullItemsCount; + while (m_1stNullItemsBeginCount < suballocations2nd.size() && + suballocations2nd[m_1stNullItemsBeginCount].type == SUBALLOCATION_TYPE_FREE) + { + ++m_1stNullItemsBeginCount; + --m_1stNullItemsMiddleCount; + } + m_2ndNullItemsCount = 0; + m_1stVectorIndex ^= 1; + } + } + } + + D3D12MA_HEAVY_ASSERT(Validate()); +} + +bool BlockMetadata_Linear::CreateAllocationRequest_LowerAddress( + UINT64 allocSize, + UINT64 allocAlignment, + AllocationRequest* pAllocationRequest) +{ + const UINT64 blockSize = GetSize(); + SuballocationVectorType& suballocations1st = AccessSuballocations1st(); + SuballocationVectorType& suballocations2nd = AccessSuballocations2nd(); + + if (m_2ndVectorMode == SECOND_VECTOR_EMPTY || m_2ndVectorMode == SECOND_VECTOR_DOUBLE_STACK) + { + // Try to allocate at the end of 1st vector. + + UINT64 resultBaseOffset = 0; + if (!suballocations1st.empty()) + { + const Suballocation& lastSuballoc = suballocations1st.back(); + resultBaseOffset = lastSuballoc.offset + lastSuballoc.size + GetDebugMargin(); + } + + // Start from offset equal to beginning of free space. + UINT64 resultOffset = resultBaseOffset; + // Apply alignment. + resultOffset = AlignUp(resultOffset, allocAlignment); + + const UINT64 freeSpaceEnd = m_2ndVectorMode == SECOND_VECTOR_DOUBLE_STACK ? + suballocations2nd.back().offset : blockSize; + + // There is enough free space at the end after alignment. + if (resultOffset + allocSize + GetDebugMargin() <= freeSpaceEnd) + { + // All tests passed: Success. + pAllocationRequest->allocHandle = (AllocHandle)(resultOffset + 1); + // pAllocationRequest->item, customData unused. + pAllocationRequest->algorithmData = ALLOC_REQUEST_END_OF_1ST; + return true; + } + } + + // Wrap-around to end of 2nd vector. Try to allocate there, watching for the + // beginning of 1st vector as the end of free space. + if (m_2ndVectorMode == SECOND_VECTOR_EMPTY || m_2ndVectorMode == SECOND_VECTOR_RING_BUFFER) + { + D3D12MA_ASSERT(!suballocations1st.empty()); + + UINT64 resultBaseOffset = 0; + if (!suballocations2nd.empty()) + { + const Suballocation& lastSuballoc = suballocations2nd.back(); + resultBaseOffset = lastSuballoc.offset + lastSuballoc.size + GetDebugMargin(); + } + + // Start from offset equal to beginning of free space. + UINT64 resultOffset = resultBaseOffset; + + // Apply alignment. + resultOffset = AlignUp(resultOffset, allocAlignment); + + size_t index1st = m_1stNullItemsBeginCount; + // There is enough free space at the end after alignment. + if ((index1st == suballocations1st.size() && resultOffset + allocSize + GetDebugMargin() <= blockSize) || + (index1st < suballocations1st.size() && resultOffset + allocSize + GetDebugMargin() <= suballocations1st[index1st].offset)) + { + // All tests passed: Success. + pAllocationRequest->allocHandle = (AllocHandle)(resultOffset + 1); + pAllocationRequest->algorithmData = ALLOC_REQUEST_END_OF_2ND; + // pAllocationRequest->item, customData unused. + return true; + } + } + return false; +} + +bool BlockMetadata_Linear::CreateAllocationRequest_UpperAddress( + UINT64 allocSize, + UINT64 allocAlignment, + AllocationRequest* pAllocationRequest) +{ + const UINT64 blockSize = GetSize(); + SuballocationVectorType& suballocations1st = AccessSuballocations1st(); + SuballocationVectorType& suballocations2nd = AccessSuballocations2nd(); + + if (m_2ndVectorMode == SECOND_VECTOR_RING_BUFFER) + { + D3D12MA_ASSERT(0 && "Trying to use pool with linear algorithm as double stack, while it is already being used as ring buffer."); + return false; + } + + // Try to allocate before 2nd.back(), or end of block if 2nd.empty(). + if (allocSize > blockSize) + { + return false; + } + UINT64 resultBaseOffset = blockSize - allocSize; + if (!suballocations2nd.empty()) + { + const Suballocation& lastSuballoc = suballocations2nd.back(); + resultBaseOffset = lastSuballoc.offset - allocSize; + if (allocSize > lastSuballoc.offset) + { + return false; + } + } + + // Start from offset equal to end of free space. + UINT64 resultOffset = resultBaseOffset; + // Apply debugMargin at the end. + if (GetDebugMargin() > 0) + { + if (resultOffset < GetDebugMargin()) + { + return false; + } + resultOffset -= GetDebugMargin(); + } + + // Apply alignment. + resultOffset = AlignDown(resultOffset, allocAlignment); + // There is enough free space. + const UINT64 endOf1st = !suballocations1st.empty() ? + suballocations1st.back().offset + suballocations1st.back().size : 0; + + if (endOf1st + GetDebugMargin() <= resultOffset) + { + // All tests passed: Success. + pAllocationRequest->allocHandle = (AllocHandle)(resultOffset + 1); + // pAllocationRequest->item unused. + pAllocationRequest->algorithmData = ALLOC_REQUEST_UPPER_ADDRESS; + return true; + } + return false; +} +#endif // _D3D12MA_BLOCK_METADATA_LINEAR_FUNCTIONS +#endif // _D3D12MA_BLOCK_METADATA_LINEAR + +#ifndef _D3D12MA_BLOCK_METADATA_TLSF +class BlockMetadata_TLSF : public BlockMetadata +{ +public: + BlockMetadata_TLSF(const ALLOCATION_CALLBACKS* allocationCallbacks, bool isVirtual); + virtual ~BlockMetadata_TLSF(); + + size_t GetAllocationCount() const override { return m_AllocCount; } + size_t GetFreeRegionsCount() const override { return m_BlocksFreeCount + 1; } + UINT64 GetSumFreeSize() const override { return m_BlocksFreeSize + m_NullBlock->size; } + bool IsEmpty() const override { return m_NullBlock->offset == 0; } + UINT64 GetAllocationOffset(AllocHandle allocHandle) const override { return ((Block*)allocHandle)->offset; }; + + void Init(UINT64 size) override; + bool Validate() const override; + void GetAllocationInfo(AllocHandle allocHandle, VIRTUAL_ALLOCATION_INFO& outInfo) const override; + + bool CreateAllocationRequest( + UINT64 allocSize, + UINT64 allocAlignment, + bool upperAddress, + UINT32 strategy, + AllocationRequest* pAllocationRequest) override; + + void Alloc( + const AllocationRequest& request, + UINT64 allocSize, + void* privateData) override; + + void Free(AllocHandle allocHandle) override; + void Clear() override; + + AllocHandle GetAllocationListBegin() const override; + AllocHandle GetNextAllocation(AllocHandle prevAlloc) const override; + UINT64 GetNextFreeRegionSize(AllocHandle alloc) const override; + void* GetAllocationPrivateData(AllocHandle allocHandle) const override; + void SetAllocationPrivateData(AllocHandle allocHandle, void* privateData) override; + + void AddStatistics(Statistics& inoutStats) const override; + void AddDetailedStatistics(DetailedStatistics& inoutStats) const override; + void WriteAllocationInfoToJson(JsonWriter& json) const override; + void DebugLogAllAllocations() const override; + +private: + // According to original paper it should be preferable 4 or 5: + // M. Masmano, I. Ripoll, A. Crespo, and J. Real "TLSF: a New Dynamic Memory Allocator for Real-Time Systems" + // http://www.gii.upv.es/tlsf/files/ecrts04_tlsf.pdf + static const UINT8 SECOND_LEVEL_INDEX = 5; + static const UINT16 SMALL_BUFFER_SIZE = 256; + static const UINT INITIAL_BLOCK_ALLOC_COUNT = 16; + static const UINT8 MEMORY_CLASS_SHIFT = 7; + static const UINT8 MAX_MEMORY_CLASSES = 65 - MEMORY_CLASS_SHIFT; + + class Block + { + public: + UINT64 offset; + UINT64 size; + Block* prevPhysical; + Block* nextPhysical; + + void MarkFree() { prevFree = NULL; } + void MarkTaken() { prevFree = this; } + bool IsFree() const { return prevFree != this; } + void*& PrivateData() { D3D12MA_HEAVY_ASSERT(!IsFree()); return privateData; } + Block*& PrevFree() { return prevFree; } + Block*& NextFree() { D3D12MA_HEAVY_ASSERT(IsFree()); return nextFree; } + + private: + Block* prevFree; // Address of the same block here indicates that block is taken + union + { + Block* nextFree; + void* privateData; + }; + }; + + size_t m_AllocCount = 0; + // Total number of free blocks besides null block + size_t m_BlocksFreeCount = 0; + // Total size of free blocks excluding null block + UINT64 m_BlocksFreeSize = 0; + UINT32 m_IsFreeBitmap = 0; + UINT8 m_MemoryClasses = 0; + UINT32 m_InnerIsFreeBitmap[MAX_MEMORY_CLASSES]; + UINT32 m_ListsCount = 0; + /* + * 0: 0-3 lists for small buffers + * 1+: 0-(2^SLI-1) lists for normal buffers + */ + Block** m_FreeList = NULL; + PoolAllocator m_BlockAllocator; + Block* m_NullBlock = NULL; + + UINT8 SizeToMemoryClass(UINT64 size) const; + UINT16 SizeToSecondIndex(UINT64 size, UINT8 memoryClass) const; + UINT32 GetListIndex(UINT8 memoryClass, UINT16 secondIndex) const; + UINT32 GetListIndex(UINT64 size) const; + + void RemoveFreeBlock(Block* block); + void InsertFreeBlock(Block* block); + void MergeBlock(Block* block, Block* prev); + + Block* FindFreeBlock(UINT64 size, UINT32& listIndex) const; + bool CheckBlock( + Block& block, + UINT32 listIndex, + UINT64 allocSize, + UINT64 allocAlignment, + AllocationRequest* pAllocationRequest); + + D3D12MA_CLASS_NO_COPY(BlockMetadata_TLSF) +}; + +#ifndef _D3D12MA_BLOCK_METADATA_TLSF_FUNCTIONS +BlockMetadata_TLSF::BlockMetadata_TLSF(const ALLOCATION_CALLBACKS* allocationCallbacks, bool isVirtual) + : BlockMetadata(allocationCallbacks, isVirtual), + m_BlockAllocator(*allocationCallbacks, INITIAL_BLOCK_ALLOC_COUNT) +{ + D3D12MA_ASSERT(allocationCallbacks); +} + +BlockMetadata_TLSF::~BlockMetadata_TLSF() +{ + D3D12MA_DELETE_ARRAY(*GetAllocs(), m_FreeList, m_ListsCount); +} + +void BlockMetadata_TLSF::Init(UINT64 size) +{ + BlockMetadata::Init(size); + + m_NullBlock = m_BlockAllocator.Alloc(); + m_NullBlock->size = size; + m_NullBlock->offset = 0; + m_NullBlock->prevPhysical = NULL; + m_NullBlock->nextPhysical = NULL; + m_NullBlock->MarkFree(); + m_NullBlock->NextFree() = NULL; + m_NullBlock->PrevFree() = NULL; + UINT8 memoryClass = SizeToMemoryClass(size); + UINT16 sli = SizeToSecondIndex(size, memoryClass); + m_ListsCount = (memoryClass == 0 ? 0 : (memoryClass - 1) * (1UL << SECOND_LEVEL_INDEX) + sli) + 1; + if (IsVirtual()) + m_ListsCount += 1UL << SECOND_LEVEL_INDEX; + else + m_ListsCount += 4; + + m_MemoryClasses = memoryClass + 2; + memset(m_InnerIsFreeBitmap, 0, MAX_MEMORY_CLASSES * sizeof(UINT32)); + + m_FreeList = D3D12MA_NEW_ARRAY(*GetAllocs(), Block*, m_ListsCount); + memset(m_FreeList, 0, m_ListsCount * sizeof(Block*)); +} + +bool BlockMetadata_TLSF::Validate() const +{ + D3D12MA_VALIDATE(GetSumFreeSize() <= GetSize()); + + UINT64 calculatedSize = m_NullBlock->size; + UINT64 calculatedFreeSize = m_NullBlock->size; + size_t allocCount = 0; + size_t freeCount = 0; + + // Check integrity of free lists + for (UINT32 list = 0; list < m_ListsCount; ++list) + { + Block* block = m_FreeList[list]; + if (block != NULL) + { + D3D12MA_VALIDATE(block->IsFree()); + D3D12MA_VALIDATE(block->PrevFree() == NULL); + while (block->NextFree()) + { + D3D12MA_VALIDATE(block->NextFree()->IsFree()); + D3D12MA_VALIDATE(block->NextFree()->PrevFree() == block); + block = block->NextFree(); + } + } + } + + D3D12MA_VALIDATE(m_NullBlock->nextPhysical == NULL); + if (m_NullBlock->prevPhysical) + { + D3D12MA_VALIDATE(m_NullBlock->prevPhysical->nextPhysical == m_NullBlock); + } + + // Check all blocks + UINT64 nextOffset = m_NullBlock->offset; + for (Block* prev = m_NullBlock->prevPhysical; prev != NULL; prev = prev->prevPhysical) + { + D3D12MA_VALIDATE(prev->offset + prev->size == nextOffset); + nextOffset = prev->offset; + calculatedSize += prev->size; + + UINT32 listIndex = GetListIndex(prev->size); + if (prev->IsFree()) + { + ++freeCount; + // Check if free block belongs to free list + Block* freeBlock = m_FreeList[listIndex]; + D3D12MA_VALIDATE(freeBlock != NULL); + + bool found = false; + do + { + if (freeBlock == prev) + found = true; + + freeBlock = freeBlock->NextFree(); + } while (!found && freeBlock != NULL); + + D3D12MA_VALIDATE(found); + calculatedFreeSize += prev->size; + } + else + { + ++allocCount; + // Check if taken block is not on a free list + Block* freeBlock = m_FreeList[listIndex]; + while (freeBlock) + { + D3D12MA_VALIDATE(freeBlock != prev); + freeBlock = freeBlock->NextFree(); + } + } + + if (prev->prevPhysical) + { + D3D12MA_VALIDATE(prev->prevPhysical->nextPhysical == prev); + } + } + + D3D12MA_VALIDATE(nextOffset == 0); + D3D12MA_VALIDATE(calculatedSize == GetSize()); + D3D12MA_VALIDATE(calculatedFreeSize == GetSumFreeSize()); + D3D12MA_VALIDATE(allocCount == m_AllocCount); + D3D12MA_VALIDATE(freeCount == m_BlocksFreeCount); + + return true; +} + +void BlockMetadata_TLSF::GetAllocationInfo(AllocHandle allocHandle, VIRTUAL_ALLOCATION_INFO& outInfo) const +{ + Block* block = (Block*)allocHandle; + D3D12MA_ASSERT(!block->IsFree() && "Cannot get allocation info for free block!"); + outInfo.Offset = block->offset; + outInfo.Size = block->size; + outInfo.pPrivateData = block->PrivateData(); +} + +bool BlockMetadata_TLSF::CreateAllocationRequest( + UINT64 allocSize, + UINT64 allocAlignment, + bool upperAddress, + UINT32 strategy, + AllocationRequest* pAllocationRequest) +{ + D3D12MA_ASSERT(allocSize > 0 && "Cannot allocate empty block!"); + D3D12MA_ASSERT(!upperAddress && "ALLOCATION_FLAG_UPPER_ADDRESS can be used only with linear algorithm."); + D3D12MA_ASSERT(pAllocationRequest != NULL); + D3D12MA_HEAVY_ASSERT(Validate()); + + allocSize += GetDebugMargin(); + // Quick check for too small pool + if (allocSize > GetSumFreeSize()) + return false; + + // If no free blocks in pool then check only null block + if (m_BlocksFreeCount == 0) + return CheckBlock(*m_NullBlock, m_ListsCount, allocSize, allocAlignment, pAllocationRequest); + + // Round up to the next block + UINT64 sizeForNextList = allocSize; + UINT16 smallSizeStep = SMALL_BUFFER_SIZE / (IsVirtual() ? 1 << SECOND_LEVEL_INDEX : 4); + if (allocSize > SMALL_BUFFER_SIZE) + { + sizeForNextList += (1ULL << (BitScanMSB(allocSize) - SECOND_LEVEL_INDEX)); + } + else if (allocSize > SMALL_BUFFER_SIZE - smallSizeStep) + sizeForNextList = SMALL_BUFFER_SIZE + 1; + else + sizeForNextList += smallSizeStep; + + UINT32 nextListIndex = 0; + UINT32 prevListIndex = 0; + Block* nextListBlock = NULL; + Block* prevListBlock = NULL; + + // Check blocks according to strategies + if (strategy & ALLOCATION_FLAG_STRATEGY_MIN_TIME) + { + // Quick check for larger block first + nextListBlock = FindFreeBlock(sizeForNextList, nextListIndex); + if (nextListBlock != NULL && CheckBlock(*nextListBlock, nextListIndex, allocSize, allocAlignment, pAllocationRequest)) + return true; + + // If not fitted then null block + if (CheckBlock(*m_NullBlock, m_ListsCount, allocSize, allocAlignment, pAllocationRequest)) + return true; + + // Null block failed, search larger bucket + while (nextListBlock) + { + if (CheckBlock(*nextListBlock, nextListIndex, allocSize, allocAlignment, pAllocationRequest)) + return true; + nextListBlock = nextListBlock->NextFree(); + } + + // Failed again, check best fit bucket + prevListBlock = FindFreeBlock(allocSize, prevListIndex); + while (prevListBlock) + { + if (CheckBlock(*prevListBlock, prevListIndex, allocSize, allocAlignment, pAllocationRequest)) + return true; + prevListBlock = prevListBlock->NextFree(); + } + } + else if (strategy & ALLOCATION_FLAG_STRATEGY_MIN_MEMORY) + { + // Check best fit bucket + prevListBlock = FindFreeBlock(allocSize, prevListIndex); + while (prevListBlock) + { + if (CheckBlock(*prevListBlock, prevListIndex, allocSize, allocAlignment, pAllocationRequest)) + return true; + prevListBlock = prevListBlock->NextFree(); + } + + // If failed check null block + if (CheckBlock(*m_NullBlock, m_ListsCount, allocSize, allocAlignment, pAllocationRequest)) + return true; + + // Check larger bucket + nextListBlock = FindFreeBlock(sizeForNextList, nextListIndex); + while (nextListBlock) + { + if (CheckBlock(*nextListBlock, nextListIndex, allocSize, allocAlignment, pAllocationRequest)) + return true; + nextListBlock = nextListBlock->NextFree(); + } + } + else if (strategy & ALLOCATION_FLAG_STRATEGY_MIN_OFFSET) + { + // Perform search from the start + Vector blockList(m_BlocksFreeCount, *GetAllocs()); + + size_t i = m_BlocksFreeCount; + for (Block* block = m_NullBlock->prevPhysical; block != NULL; block = block->prevPhysical) + { + if (block->IsFree() && block->size >= allocSize) + blockList[--i] = block; + } + + for (; i < m_BlocksFreeCount; ++i) + { + Block& block = *blockList[i]; + if (CheckBlock(block, GetListIndex(block.size), allocSize, allocAlignment, pAllocationRequest)) + return true; + } + + // If failed check null block + if (CheckBlock(*m_NullBlock, m_ListsCount, allocSize, allocAlignment, pAllocationRequest)) + return true; + + // Whole range searched, no more memory + return false; + } + else + { + // Check larger bucket + nextListBlock = FindFreeBlock(sizeForNextList, nextListIndex); + while (nextListBlock) + { + if (CheckBlock(*nextListBlock, nextListIndex, allocSize, allocAlignment, pAllocationRequest)) + return true; + nextListBlock = nextListBlock->NextFree(); + } + + // If failed check null block + if (CheckBlock(*m_NullBlock, m_ListsCount, allocSize, allocAlignment, pAllocationRequest)) + return true; + + // Check best fit bucket + prevListBlock = FindFreeBlock(allocSize, prevListIndex); + while (prevListBlock) + { + if (CheckBlock(*prevListBlock, prevListIndex, allocSize, allocAlignment, pAllocationRequest)) + return true; + prevListBlock = prevListBlock->NextFree(); + } + } + + // Worst case, full search has to be done + while (++nextListIndex < m_ListsCount) + { + nextListBlock = m_FreeList[nextListIndex]; + while (nextListBlock) + { + if (CheckBlock(*nextListBlock, nextListIndex, allocSize, allocAlignment, pAllocationRequest)) + return true; + nextListBlock = nextListBlock->NextFree(); + } + } + + // No more memory sadly + return false; +} + +void BlockMetadata_TLSF::Alloc( + const AllocationRequest& request, + UINT64 allocSize, + void* privateData) +{ + // Get block and pop it from the free list + Block* currentBlock = (Block*)request.allocHandle; + UINT64 offset = request.algorithmData; + D3D12MA_ASSERT(currentBlock != NULL); + D3D12MA_ASSERT(currentBlock->offset <= offset); + + if (currentBlock != m_NullBlock) + RemoveFreeBlock(currentBlock); + + // Append missing alignment to prev block or create new one + UINT64 misssingAlignment = offset - currentBlock->offset; + if (misssingAlignment) + { + Block* prevBlock = currentBlock->prevPhysical; + D3D12MA_ASSERT(prevBlock != NULL && "There should be no missing alignment at offset 0!"); + + if (prevBlock->IsFree() && prevBlock->size != GetDebugMargin()) + { + UINT32 oldList = GetListIndex(prevBlock->size); + prevBlock->size += misssingAlignment; + // Check if new size crosses list bucket + if (oldList != GetListIndex(prevBlock->size)) + { + prevBlock->size -= misssingAlignment; + RemoveFreeBlock(prevBlock); + prevBlock->size += misssingAlignment; + InsertFreeBlock(prevBlock); + } + else + m_BlocksFreeSize += misssingAlignment; + } + else + { + Block* newBlock = m_BlockAllocator.Alloc(); + currentBlock->prevPhysical = newBlock; + prevBlock->nextPhysical = newBlock; + newBlock->prevPhysical = prevBlock; + newBlock->nextPhysical = currentBlock; + newBlock->size = misssingAlignment; + newBlock->offset = currentBlock->offset; + newBlock->MarkTaken(); + + InsertFreeBlock(newBlock); + } + + currentBlock->size -= misssingAlignment; + currentBlock->offset += misssingAlignment; + } + + UINT64 size = request.size + GetDebugMargin(); + if (currentBlock->size == size) + { + if (currentBlock == m_NullBlock) + { + // Setup new null block + m_NullBlock = m_BlockAllocator.Alloc(); + m_NullBlock->size = 0; + m_NullBlock->offset = currentBlock->offset + size; + m_NullBlock->prevPhysical = currentBlock; + m_NullBlock->nextPhysical = NULL; + m_NullBlock->MarkFree(); + m_NullBlock->PrevFree() = NULL; + m_NullBlock->NextFree() = NULL; + currentBlock->nextPhysical = m_NullBlock; + currentBlock->MarkTaken(); + } + } + else + { + D3D12MA_ASSERT(currentBlock->size > size && "Proper block already found, shouldn't find smaller one!"); + + // Create new free block + Block* newBlock = m_BlockAllocator.Alloc(); + newBlock->size = currentBlock->size - size; + newBlock->offset = currentBlock->offset + size; + newBlock->prevPhysical = currentBlock; + newBlock->nextPhysical = currentBlock->nextPhysical; + currentBlock->nextPhysical = newBlock; + currentBlock->size = size; + + if (currentBlock == m_NullBlock) + { + m_NullBlock = newBlock; + m_NullBlock->MarkFree(); + m_NullBlock->NextFree() = NULL; + m_NullBlock->PrevFree() = NULL; + currentBlock->MarkTaken(); + } + else + { + newBlock->nextPhysical->prevPhysical = newBlock; + newBlock->MarkTaken(); + InsertFreeBlock(newBlock); + } + } + currentBlock->PrivateData() = privateData; + + if (GetDebugMargin() > 0) + { + currentBlock->size -= GetDebugMargin(); + Block* newBlock = m_BlockAllocator.Alloc(); + newBlock->size = GetDebugMargin(); + newBlock->offset = currentBlock->offset + currentBlock->size; + newBlock->prevPhysical = currentBlock; + newBlock->nextPhysical = currentBlock->nextPhysical; + newBlock->MarkTaken(); + currentBlock->nextPhysical->prevPhysical = newBlock; + currentBlock->nextPhysical = newBlock; + InsertFreeBlock(newBlock); + } + ++m_AllocCount; +} + +void BlockMetadata_TLSF::Free(AllocHandle allocHandle) +{ + Block* block = (Block*)allocHandle; + Block* next = block->nextPhysical; + D3D12MA_ASSERT(!block->IsFree() && "Block is already free!"); + + --m_AllocCount; + if (GetDebugMargin() > 0) + { + RemoveFreeBlock(next); + MergeBlock(next, block); + block = next; + next = next->nextPhysical; + } + + // Try merging + Block* prev = block->prevPhysical; + if (prev != NULL && prev->IsFree() && prev->size != GetDebugMargin()) + { + RemoveFreeBlock(prev); + MergeBlock(block, prev); + } + + if (!next->IsFree()) + InsertFreeBlock(block); + else if (next == m_NullBlock) + MergeBlock(m_NullBlock, block); + else + { + RemoveFreeBlock(next); + MergeBlock(next, block); + InsertFreeBlock(next); + } +} + +void BlockMetadata_TLSF::Clear() +{ + m_AllocCount = 0; + m_BlocksFreeCount = 0; + m_BlocksFreeSize = 0; + m_IsFreeBitmap = 0; + m_NullBlock->offset = 0; + m_NullBlock->size = GetSize(); + Block* block = m_NullBlock->prevPhysical; + m_NullBlock->prevPhysical = NULL; + while (block) + { + Block* prev = block->prevPhysical; + m_BlockAllocator.Free(block); + block = prev; + } + memset(m_FreeList, 0, m_ListsCount * sizeof(Block*)); + memset(m_InnerIsFreeBitmap, 0, m_MemoryClasses * sizeof(UINT32)); +} + +AllocHandle BlockMetadata_TLSF::GetAllocationListBegin() const +{ + if (m_AllocCount == 0) + return (AllocHandle)0; + + for (Block* block = m_NullBlock->prevPhysical; block; block = block->prevPhysical) + { + if (!block->IsFree()) + return (AllocHandle)block; + } + D3D12MA_ASSERT(false && "If m_AllocCount > 0 then should find any allocation!"); + return (AllocHandle)0; +} + +AllocHandle BlockMetadata_TLSF::GetNextAllocation(AllocHandle prevAlloc) const +{ + Block* startBlock = (Block*)prevAlloc; + D3D12MA_ASSERT(!startBlock->IsFree() && "Incorrect block!"); + + for (Block* block = startBlock->prevPhysical; block; block = block->prevPhysical) + { + if (!block->IsFree()) + return (AllocHandle)block; + } + return (AllocHandle)0; +} + +UINT64 BlockMetadata_TLSF::GetNextFreeRegionSize(AllocHandle alloc) const +{ + Block* block = (Block*)alloc; + D3D12MA_ASSERT(!block->IsFree() && "Incorrect block!"); + + if (block->prevPhysical) + return block->prevPhysical->IsFree() ? block->prevPhysical->size : 0; + return 0; +} + +void* BlockMetadata_TLSF::GetAllocationPrivateData(AllocHandle allocHandle) const +{ + Block* block = (Block*)allocHandle; + D3D12MA_ASSERT(!block->IsFree() && "Cannot get user data for free block!"); + return block->PrivateData(); +} + +void BlockMetadata_TLSF::SetAllocationPrivateData(AllocHandle allocHandle, void* privateData) +{ + Block* block = (Block*)allocHandle; + D3D12MA_ASSERT(!block->IsFree() && "Trying to set user data for not allocated block!"); + block->PrivateData() = privateData; +} + +void BlockMetadata_TLSF::AddStatistics(Statistics& inoutStats) const +{ + inoutStats.BlockCount++; + inoutStats.AllocationCount += static_cast(m_AllocCount); + inoutStats.BlockBytes += GetSize(); + inoutStats.AllocationBytes += GetSize() - GetSumFreeSize(); +} + +void BlockMetadata_TLSF::AddDetailedStatistics(DetailedStatistics& inoutStats) const +{ + inoutStats.Stats.BlockCount++; + inoutStats.Stats.BlockBytes += GetSize(); + + for (Block* block = m_NullBlock->prevPhysical; block != NULL; block = block->prevPhysical) + { + if (block->IsFree()) + AddDetailedStatisticsUnusedRange(inoutStats, block->size); + else + AddDetailedStatisticsAllocation(inoutStats, block->size); + } + + if (m_NullBlock->size > 0) + AddDetailedStatisticsUnusedRange(inoutStats, m_NullBlock->size); +} + +void BlockMetadata_TLSF::WriteAllocationInfoToJson(JsonWriter& json) const +{ + size_t blockCount = m_AllocCount + m_BlocksFreeCount; + Vector blockList(blockCount, *GetAllocs()); + + size_t i = blockCount; + if (m_NullBlock->size > 0) + { + ++blockCount; + blockList.push_back(m_NullBlock); + } + for (Block* block = m_NullBlock->prevPhysical; block != NULL; block = block->prevPhysical) + { + blockList[--i] = block; + } + D3D12MA_ASSERT(i == 0); + + PrintDetailedMap_Begin(json, GetSumFreeSize(), GetAllocationCount(), m_BlocksFreeCount + static_cast(m_NullBlock->size)); + for (; i < blockCount; ++i) + { + Block* block = blockList[i]; + if (block->IsFree()) + PrintDetailedMap_UnusedRange(json, block->offset, block->size); + else + PrintDetailedMap_Allocation(json, block->offset, block->size, block->PrivateData()); + } + PrintDetailedMap_End(json); +} + +void BlockMetadata_TLSF::DebugLogAllAllocations() const +{ + for (Block* block = m_NullBlock->prevPhysical; block != NULL; block = block->prevPhysical) + { + if (!block->IsFree()) + { + DebugLogAllocation(block->offset, block->size, block->PrivateData()); + } + } +} + +UINT8 BlockMetadata_TLSF::SizeToMemoryClass(UINT64 size) const +{ + if (size > SMALL_BUFFER_SIZE) + return BitScanMSB(size) - MEMORY_CLASS_SHIFT; + return 0; +} + +UINT16 BlockMetadata_TLSF::SizeToSecondIndex(UINT64 size, UINT8 memoryClass) const +{ + if (memoryClass == 0) + { + if (IsVirtual()) + return static_cast((size - 1) / 8); + else + return static_cast((size - 1) / 64); + } + return static_cast((size >> (memoryClass + MEMORY_CLASS_SHIFT - SECOND_LEVEL_INDEX)) ^ (1U << SECOND_LEVEL_INDEX)); +} + +UINT32 BlockMetadata_TLSF::GetListIndex(UINT8 memoryClass, UINT16 secondIndex) const +{ + if (memoryClass == 0) + return secondIndex; + + const UINT32 index = static_cast(memoryClass - 1) * (1 << SECOND_LEVEL_INDEX) + secondIndex; + if (IsVirtual()) + return index + (1 << SECOND_LEVEL_INDEX); + else + return index + 4; +} + +UINT32 BlockMetadata_TLSF::GetListIndex(UINT64 size) const +{ + UINT8 memoryClass = SizeToMemoryClass(size); + return GetListIndex(memoryClass, SizeToSecondIndex(size, memoryClass)); +} + +void BlockMetadata_TLSF::RemoveFreeBlock(Block* block) +{ + D3D12MA_ASSERT(block != m_NullBlock); + D3D12MA_ASSERT(block->IsFree()); + + if (block->NextFree() != NULL) + block->NextFree()->PrevFree() = block->PrevFree(); + if (block->PrevFree() != NULL) + block->PrevFree()->NextFree() = block->NextFree(); + else + { + UINT8 memClass = SizeToMemoryClass(block->size); + UINT16 secondIndex = SizeToSecondIndex(block->size, memClass); + UINT32 index = GetListIndex(memClass, secondIndex); + m_FreeList[index] = block->NextFree(); + if (block->NextFree() == NULL) + { + m_InnerIsFreeBitmap[memClass] &= ~(1U << secondIndex); + if (m_InnerIsFreeBitmap[memClass] == 0) + m_IsFreeBitmap &= ~(1UL << memClass); + } + } + block->MarkTaken(); + block->PrivateData() = NULL; + --m_BlocksFreeCount; + m_BlocksFreeSize -= block->size; +} + +void BlockMetadata_TLSF::InsertFreeBlock(Block* block) +{ + D3D12MA_ASSERT(block != m_NullBlock); + D3D12MA_ASSERT(!block->IsFree() && "Cannot insert block twice!"); + + UINT8 memClass = SizeToMemoryClass(block->size); + UINT16 secondIndex = SizeToSecondIndex(block->size, memClass); + UINT32 index = GetListIndex(memClass, secondIndex); + block->PrevFree() = NULL; + block->NextFree() = m_FreeList[index]; + m_FreeList[index] = block; + if (block->NextFree() != NULL) + block->NextFree()->PrevFree() = block; + else + { + m_InnerIsFreeBitmap[memClass] |= 1U << secondIndex; + m_IsFreeBitmap |= 1UL << memClass; + } + ++m_BlocksFreeCount; + m_BlocksFreeSize += block->size; +} + +void BlockMetadata_TLSF::MergeBlock(Block* block, Block* prev) +{ + D3D12MA_ASSERT(block->prevPhysical == prev && "Cannot merge seperate physical regions!"); + D3D12MA_ASSERT(!prev->IsFree() && "Cannot merge block that belongs to free list!"); + + block->offset = prev->offset; + block->size += prev->size; + block->prevPhysical = prev->prevPhysical; + if (block->prevPhysical) + block->prevPhysical->nextPhysical = block; + m_BlockAllocator.Free(prev); +} + +BlockMetadata_TLSF::Block* BlockMetadata_TLSF::FindFreeBlock(UINT64 size, UINT32& listIndex) const +{ + UINT8 memoryClass = SizeToMemoryClass(size); + UINT32 innerFreeMap = m_InnerIsFreeBitmap[memoryClass] & (~0U << SizeToSecondIndex(size, memoryClass)); + if (!innerFreeMap) + { + // Check higher levels for avaiable blocks + UINT32 freeMap = m_IsFreeBitmap & (~0UL << (memoryClass + 1)); + if (!freeMap) + return NULL; // No more memory avaible + + // Find lowest free region + memoryClass = BitScanLSB(freeMap); + innerFreeMap = m_InnerIsFreeBitmap[memoryClass]; + D3D12MA_ASSERT(innerFreeMap != 0); + } + // Find lowest free subregion + listIndex = GetListIndex(memoryClass, BitScanLSB(innerFreeMap)); + return m_FreeList[listIndex]; +} + +bool BlockMetadata_TLSF::CheckBlock( + Block& block, + UINT32 listIndex, + UINT64 allocSize, + UINT64 allocAlignment, + AllocationRequest* pAllocationRequest) +{ + D3D12MA_ASSERT(block.IsFree() && "Block is already taken!"); + + UINT64 alignedOffset = AlignUp(block.offset, allocAlignment); + if (block.size < allocSize + alignedOffset - block.offset) + return false; + + // Alloc successful + pAllocationRequest->allocHandle = (AllocHandle)█ + pAllocationRequest->size = allocSize - GetDebugMargin(); + pAllocationRequest->algorithmData = alignedOffset; + + // Place block at the start of list if it's normal block + if (listIndex != m_ListsCount && block.PrevFree()) + { + block.PrevFree()->NextFree() = block.NextFree(); + if (block.NextFree()) + block.NextFree()->PrevFree() = block.PrevFree(); + block.PrevFree() = NULL; + block.NextFree() = m_FreeList[listIndex]; + m_FreeList[listIndex] = █ + if (block.NextFree()) + block.NextFree()->PrevFree() = █ + } + + return true; +} +#endif // _D3D12MA_BLOCK_METADATA_TLSF_FUNCTIONS +#endif // _D3D12MA_BLOCK_METADATA_TLSF + +#ifndef _D3D12MA_MEMORY_BLOCK +/* +Represents a single block of device memory (heap). +Base class for inheritance. +Thread-safety: This class must be externally synchronized. +*/ +class MemoryBlock +{ +public: + // Creates the ID3D12Heap. + MemoryBlock( + AllocatorPimpl* allocator, + const D3D12_HEAP_PROPERTIES& heapProps, + D3D12_HEAP_FLAGS heapFlags, + UINT64 size, + UINT id); + virtual ~MemoryBlock(); + + const D3D12_HEAP_PROPERTIES& GetHeapProperties() const { return m_HeapProps; } + D3D12_HEAP_FLAGS GetHeapFlags() const { return m_HeapFlags; } + UINT64 GetSize() const { return m_Size; } + UINT GetId() const { return m_Id; } + ID3D12Heap* GetHeap() const { return m_Heap; } + +protected: + AllocatorPimpl* const m_Allocator; + const D3D12_HEAP_PROPERTIES m_HeapProps; + const D3D12_HEAP_FLAGS m_HeapFlags; + const UINT64 m_Size; + const UINT m_Id; + + HRESULT Init(ID3D12ProtectedResourceSession* pProtectedSession, bool denyMsaaTextures); + +private: + ID3D12Heap* m_Heap = NULL; + + D3D12MA_CLASS_NO_COPY(MemoryBlock) +}; +#endif // _D3D12MA_MEMORY_BLOCK + +#ifndef _D3D12MA_NORMAL_BLOCK +/* +Represents a single block of device memory (heap) with all the data about its +regions (aka suballocations, Allocation), assigned and free. +Thread-safety: This class must be externally synchronized. +*/ +class NormalBlock : public MemoryBlock +{ +public: + BlockMetadata* m_pMetadata; + + NormalBlock( + AllocatorPimpl* allocator, + BlockVector* blockVector, + const D3D12_HEAP_PROPERTIES& heapProps, + D3D12_HEAP_FLAGS heapFlags, + UINT64 size, + UINT id); + virtual ~NormalBlock(); + + BlockVector* GetBlockVector() const { return m_BlockVector; } + + // 'algorithm' should be one of the *_ALGORITHM_* flags in enums POOL_FLAGS or VIRTUAL_BLOCK_FLAGS + HRESULT Init(UINT32 algorithm, ID3D12ProtectedResourceSession* pProtectedSession, bool denyMsaaTextures); + + // Validates all data structures inside this object. If not valid, returns false. + bool Validate() const; + +private: + BlockVector* m_BlockVector; + + D3D12MA_CLASS_NO_COPY(NormalBlock) +}; +#endif // _D3D12MA_NORMAL_BLOCK + +#ifndef _D3D12MA_COMMITTED_ALLOCATION_LIST_ITEM_TRAITS +struct CommittedAllocationListItemTraits +{ + using ItemType = Allocation; + + static ItemType* GetPrev(const ItemType* item) + { + D3D12MA_ASSERT(item->m_PackedData.GetType() == Allocation::TYPE_COMMITTED || item->m_PackedData.GetType() == Allocation::TYPE_HEAP); + return item->m_Committed.prev; + } + static ItemType* GetNext(const ItemType* item) + { + D3D12MA_ASSERT(item->m_PackedData.GetType() == Allocation::TYPE_COMMITTED || item->m_PackedData.GetType() == Allocation::TYPE_HEAP); + return item->m_Committed.next; + } + static ItemType*& AccessPrev(ItemType* item) + { + D3D12MA_ASSERT(item->m_PackedData.GetType() == Allocation::TYPE_COMMITTED || item->m_PackedData.GetType() == Allocation::TYPE_HEAP); + return item->m_Committed.prev; + } + static ItemType*& AccessNext(ItemType* item) + { + D3D12MA_ASSERT(item->m_PackedData.GetType() == Allocation::TYPE_COMMITTED || item->m_PackedData.GetType() == Allocation::TYPE_HEAP); + return item->m_Committed.next; + } +}; +#endif // _D3D12MA_COMMITTED_ALLOCATION_LIST_ITEM_TRAITS + +#ifndef _D3D12MA_COMMITTED_ALLOCATION_LIST +/* +Stores linked list of Allocation objects that are of TYPE_COMMITTED or TYPE_HEAP. +Thread-safe, synchronized internally. +*/ +class CommittedAllocationList +{ +public: + CommittedAllocationList() = default; + void Init(bool useMutex, D3D12_HEAP_TYPE heapType, PoolPimpl* pool); + ~CommittedAllocationList(); + + D3D12_HEAP_TYPE GetHeapType() const { return m_HeapType; } + PoolPimpl* GetPool() const { return m_Pool; } + UINT GetMemorySegmentGroup(AllocatorPimpl* allocator) const; + + void AddStatistics(Statistics& inoutStats); + void AddDetailedStatistics(DetailedStatistics& inoutStats); + // Writes JSON array with the list of allocations. + void BuildStatsString(JsonWriter& json); + + void Register(Allocation* alloc); + void Unregister(Allocation* alloc); + +private: + using CommittedAllocationLinkedList = IntrusiveLinkedList; + + bool m_UseMutex = true; + D3D12_HEAP_TYPE m_HeapType = D3D12_HEAP_TYPE_CUSTOM; + PoolPimpl* m_Pool = NULL; + + D3D12MA_RW_MUTEX m_Mutex; + CommittedAllocationLinkedList m_AllocationList; +}; +#endif // _D3D12MA_COMMITTED_ALLOCATION_LIST + +#ifndef _D3D12M_COMMITTED_ALLOCATION_PARAMETERS +struct CommittedAllocationParameters +{ + CommittedAllocationList* m_List = NULL; + D3D12_HEAP_PROPERTIES m_HeapProperties = {}; + D3D12_HEAP_FLAGS m_HeapFlags = D3D12_HEAP_FLAG_NONE; + ID3D12ProtectedResourceSession* m_ProtectedSession = NULL; + bool m_CanAlias = false; + D3D12_RESIDENCY_PRIORITY m_ResidencyPriority = D3D12_RESIDENCY_PRIORITY_NONE; + + bool IsValid() const { return m_List != NULL; } +}; +#endif // _D3D12M_COMMITTED_ALLOCATION_PARAMETERS + +// Simple variant data structure to hold all possible variations of ID3D12Device*::CreateCommittedResource* and ID3D12Device*::CreatePlacedResource* arguments +struct CREATE_RESOURCE_PARAMS +{ + CREATE_RESOURCE_PARAMS() = delete; + CREATE_RESOURCE_PARAMS( + const D3D12_RESOURCE_DESC* pResourceDesc, + D3D12_RESOURCE_STATES InitialResourceState, + const D3D12_CLEAR_VALUE* pOptimizedClearValue) + : Variant(VARIANT_WITH_STATE) + , pResourceDesc(pResourceDesc) + , InitialResourceState(InitialResourceState) + , pOptimizedClearValue(pOptimizedClearValue) + { + } +#ifdef __ID3D12Device8_INTERFACE_DEFINED__ + CREATE_RESOURCE_PARAMS( + const D3D12_RESOURCE_DESC1* pResourceDesc, + D3D12_RESOURCE_STATES InitialResourceState, + const D3D12_CLEAR_VALUE* pOptimizedClearValue) + : Variant(VARIANT_WITH_STATE_AND_DESC1) + , pResourceDesc1(pResourceDesc) + , InitialResourceState(InitialResourceState) + , pOptimizedClearValue(pOptimizedClearValue) + { + } +#endif +#ifdef __ID3D12Device10_INTERFACE_DEFINED__ + CREATE_RESOURCE_PARAMS( + const D3D12_RESOURCE_DESC1* pResourceDesc, + D3D12_BARRIER_LAYOUT InitialLayout, + const D3D12_CLEAR_VALUE* pOptimizedClearValue, + UINT32 NumCastableFormats, + DXGI_FORMAT* pCastableFormats) + : Variant(VARIANT_WITH_LAYOUT) + , pResourceDesc1(pResourceDesc) + , InitialLayout(InitialLayout) + , pOptimizedClearValue(pOptimizedClearValue) + , NumCastableFormats(NumCastableFormats) + , pCastableFormats(pCastableFormats) + { + } +#endif + + enum VARIANT + { + VARIANT_INVALID = 0, + VARIANT_WITH_STATE, + VARIANT_WITH_STATE_AND_DESC1, + VARIANT_WITH_LAYOUT + }; + + VARIANT Variant = VARIANT_INVALID; + + const D3D12_RESOURCE_DESC* GetResourceDesc() const + { + D3D12MA_ASSERT(Variant == VARIANT_WITH_STATE); + return pResourceDesc; + } + const D3D12_RESOURCE_DESC*& AccessResourceDesc() + { + D3D12MA_ASSERT(Variant == VARIANT_WITH_STATE); + return pResourceDesc; + } + const D3D12_RESOURCE_DESC* GetBaseResourceDesc() const + { + // D3D12_RESOURCE_DESC1 can be cast to D3D12_RESOURCE_DESC by discarding the new members at the end. + return pResourceDesc; + } + D3D12_RESOURCE_STATES GetInitialResourceState() const + { + D3D12MA_ASSERT(Variant < VARIANT_WITH_LAYOUT); + return InitialResourceState; + } + const D3D12_CLEAR_VALUE* GetOptimizedClearValue() const + { + return pOptimizedClearValue; + } + +#ifdef __ID3D12Device8_INTERFACE_DEFINED__ + const D3D12_RESOURCE_DESC1* GetResourceDesc1() const + { + D3D12MA_ASSERT(Variant >= VARIANT_WITH_STATE_AND_DESC1); + return pResourceDesc1; + } + const D3D12_RESOURCE_DESC1*& AccessResourceDesc1() + { + D3D12MA_ASSERT(Variant >= VARIANT_WITH_STATE_AND_DESC1); + return pResourceDesc1; + } +#endif + +#ifdef __ID3D12Device10_INTERFACE_DEFINED__ + D3D12_BARRIER_LAYOUT GetInitialLayout() const + { + D3D12MA_ASSERT(Variant >= VARIANT_WITH_LAYOUT); + return InitialLayout; + } + UINT32 GetNumCastableFormats() const + { + D3D12MA_ASSERT(Variant >= VARIANT_WITH_LAYOUT); + return NumCastableFormats; + } + DXGI_FORMAT* GetCastableFormats() const + { + D3D12MA_ASSERT(Variant >= VARIANT_WITH_LAYOUT); + return pCastableFormats; + } +#endif + +private: + union + { + const D3D12_RESOURCE_DESC* pResourceDesc; +#ifdef __ID3D12Device8_INTERFACE_DEFINED__ + const D3D12_RESOURCE_DESC1* pResourceDesc1; +#endif + }; + union + { + D3D12_RESOURCE_STATES InitialResourceState; +#ifdef __ID3D12Device10_INTERFACE_DEFINED__ + D3D12_BARRIER_LAYOUT InitialLayout; +#endif + }; + const D3D12_CLEAR_VALUE* pOptimizedClearValue; +#ifdef __ID3D12Device10_INTERFACE_DEFINED__ + UINT32 NumCastableFormats; + DXGI_FORMAT* pCastableFormats; +#endif +}; + +#ifndef _D3D12MA_BLOCK_VECTOR +/* +Sequence of NormalBlock. Represents memory blocks allocated for a specific +heap type and possibly resource type (if only Tier 1 is supported). + +Synchronized internally with a mutex. +*/ +class BlockVector +{ + friend class DefragmentationContextPimpl; + D3D12MA_CLASS_NO_COPY(BlockVector) +public: + BlockVector( + AllocatorPimpl* hAllocator, + const D3D12_HEAP_PROPERTIES& heapProps, + D3D12_HEAP_FLAGS heapFlags, + UINT64 preferredBlockSize, + size_t minBlockCount, + size_t maxBlockCount, + bool explicitBlockSize, + UINT64 minAllocationAlignment, + UINT32 algorithm, + bool denyMsaaTextures, + ID3D12ProtectedResourceSession* pProtectedSession, + D3D12_RESIDENCY_PRIORITY residencyPriority); + ~BlockVector(); + D3D12_RESIDENCY_PRIORITY GetResidencyPriority() const { return m_ResidencyPriority; } + + const D3D12_HEAP_PROPERTIES& GetHeapProperties() const { return m_HeapProps; } + D3D12_HEAP_FLAGS GetHeapFlags() const { return m_HeapFlags; } + UINT64 GetPreferredBlockSize() const { return m_PreferredBlockSize; } + UINT32 GetAlgorithm() const { return m_Algorithm; } + bool DeniesMsaaTextures() const { return m_DenyMsaaTextures; } + // To be used only while the m_Mutex is locked. Used during defragmentation. + size_t GetBlockCount() const { return m_Blocks.size(); } + // To be used only while the m_Mutex is locked. Used during defragmentation. + NormalBlock* GetBlock(size_t index) const { return m_Blocks[index]; } + D3D12MA_RW_MUTEX& GetMutex() { return m_Mutex; } + + HRESULT CreateMinBlocks(); + bool IsEmpty(); + + HRESULT Allocate( + UINT64 size, + UINT64 alignment, + const ALLOCATION_DESC& allocDesc, + size_t allocationCount, + Allocation** pAllocations); + + void Free(Allocation* hAllocation); + + HRESULT CreateResource( + UINT64 size, + UINT64 alignment, + const ALLOCATION_DESC& allocDesc, + const CREATE_RESOURCE_PARAMS& createParams, + Allocation** ppAllocation, + REFIID riidResource, + void** ppvResource); + + void AddStatistics(Statistics& inoutStats); + void AddDetailedStatistics(DetailedStatistics& inoutStats); + + void WriteBlockInfoToJson(JsonWriter& json); + +private: + AllocatorPimpl* const m_hAllocator; + const D3D12_HEAP_PROPERTIES m_HeapProps; + const D3D12_HEAP_FLAGS m_HeapFlags; + const UINT64 m_PreferredBlockSize; + const size_t m_MinBlockCount; + const size_t m_MaxBlockCount; + const bool m_ExplicitBlockSize; + const UINT64 m_MinAllocationAlignment; + const UINT32 m_Algorithm; + const bool m_DenyMsaaTextures; + ID3D12ProtectedResourceSession* const m_ProtectedSession; + const D3D12_RESIDENCY_PRIORITY m_ResidencyPriority; + /* There can be at most one allocation that is completely empty - a + hysteresis to avoid pessimistic case of alternating creation and destruction + of a ID3D12Heap. */ + bool m_HasEmptyBlock; + D3D12MA_RW_MUTEX m_Mutex; + // Incrementally sorted by sumFreeSize, ascending. + Vector m_Blocks; + UINT m_NextBlockId; + bool m_IncrementalSort = true; + + // Disable incremental sorting when freeing allocations + void SetIncrementalSort(bool val) { m_IncrementalSort = val; } + + UINT64 CalcSumBlockSize() const; + UINT64 CalcMaxBlockSize() const; + + // Finds and removes given block from vector. + void Remove(NormalBlock* pBlock); + + // Performs single step in sorting m_Blocks. They may not be fully sorted + // after this call. + void IncrementallySortBlocks(); + void SortByFreeSize(); + + HRESULT AllocatePage( + UINT64 size, + UINT64 alignment, + const ALLOCATION_DESC& allocDesc, + Allocation** pAllocation); + + HRESULT AllocateFromBlock( + NormalBlock* pBlock, + UINT64 size, + UINT64 alignment, + ALLOCATION_FLAGS allocFlags, + void* pPrivateData, + UINT32 strategy, + Allocation** pAllocation); + + HRESULT CommitAllocationRequest( + AllocationRequest& allocRequest, + NormalBlock* pBlock, + UINT64 size, + UINT64 alignment, + void* pPrivateData, + Allocation** pAllocation); + + HRESULT CreateBlock( + UINT64 blockSize, + size_t* pNewBlockIndex); +}; +#endif // _D3D12MA_BLOCK_VECTOR + +#ifndef _D3D12MA_CURRENT_BUDGET_DATA +class CurrentBudgetData +{ +public: + bool ShouldUpdateBudget() const { return m_OperationsSinceBudgetFetch >= 30; } + + void GetStatistics(Statistics& outStats, UINT group) const; + void GetBudget(bool useMutex, + UINT64* outLocalUsage, UINT64* outLocalBudget, + UINT64* outNonLocalUsage, UINT64* outNonLocalBudget); + +#if D3D12MA_DXGI_1_4 + HRESULT UpdateBudget(IDXGIAdapter3* adapter3, bool useMutex); +#endif + + void AddAllocation(UINT group, UINT64 allocationBytes); + void RemoveAllocation(UINT group, UINT64 allocationBytes); + + void AddBlock(UINT group, UINT64 blockBytes); + void RemoveBlock(UINT group, UINT64 blockBytes); + +private: + D3D12MA_ATOMIC_UINT32 m_BlockCount[DXGI_MEMORY_SEGMENT_GROUP_COUNT] = {}; + D3D12MA_ATOMIC_UINT32 m_AllocationCount[DXGI_MEMORY_SEGMENT_GROUP_COUNT] = {}; + D3D12MA_ATOMIC_UINT64 m_BlockBytes[DXGI_MEMORY_SEGMENT_GROUP_COUNT] = {}; + D3D12MA_ATOMIC_UINT64 m_AllocationBytes[DXGI_MEMORY_SEGMENT_GROUP_COUNT] = {}; + + D3D12MA_ATOMIC_UINT32 m_OperationsSinceBudgetFetch = {0}; + D3D12MA_RW_MUTEX m_BudgetMutex; + UINT64 m_D3D12Usage[DXGI_MEMORY_SEGMENT_GROUP_COUNT] = {}; + UINT64 m_D3D12Budget[DXGI_MEMORY_SEGMENT_GROUP_COUNT] = {}; + UINT64 m_BlockBytesAtD3D12Fetch[DXGI_MEMORY_SEGMENT_GROUP_COUNT] = {}; +}; + +#ifndef _D3D12MA_CURRENT_BUDGET_DATA_FUNCTIONS +void CurrentBudgetData::GetStatistics(Statistics& outStats, UINT group) const +{ + outStats.BlockCount = m_BlockCount[group]; + outStats.AllocationCount = m_AllocationCount[group]; + outStats.BlockBytes = m_BlockBytes[group]; + outStats.AllocationBytes = m_AllocationBytes[group]; +} + +void CurrentBudgetData::GetBudget(bool useMutex, + UINT64* outLocalUsage, UINT64* outLocalBudget, + UINT64* outNonLocalUsage, UINT64* outNonLocalBudget) +{ + MutexLockRead lockRead(m_BudgetMutex, useMutex); + + if (outLocalUsage) + { + const UINT64 D3D12Usage = m_D3D12Usage[DXGI_MEMORY_SEGMENT_GROUP_LOCAL_COPY]; + const UINT64 blockBytes = m_BlockBytes[DXGI_MEMORY_SEGMENT_GROUP_LOCAL_COPY]; + const UINT64 blockBytesAtD3D12Fetch = m_BlockBytesAtD3D12Fetch[DXGI_MEMORY_SEGMENT_GROUP_LOCAL_COPY]; + *outLocalUsage = D3D12Usage + blockBytes > blockBytesAtD3D12Fetch ? + D3D12Usage + blockBytes - blockBytesAtD3D12Fetch : 0; + } + if (outLocalBudget) + *outLocalBudget = m_D3D12Budget[DXGI_MEMORY_SEGMENT_GROUP_LOCAL_COPY]; + + if (outNonLocalUsage) + { + const UINT64 D3D12Usage = m_D3D12Usage[DXGI_MEMORY_SEGMENT_GROUP_NON_LOCAL_COPY]; + const UINT64 blockBytes = m_BlockBytes[DXGI_MEMORY_SEGMENT_GROUP_NON_LOCAL_COPY]; + const UINT64 blockBytesAtD3D12Fetch = m_BlockBytesAtD3D12Fetch[DXGI_MEMORY_SEGMENT_GROUP_NON_LOCAL_COPY]; + *outNonLocalUsage = D3D12Usage + blockBytes > blockBytesAtD3D12Fetch ? + D3D12Usage + blockBytes - blockBytesAtD3D12Fetch : 0; + } + if (outNonLocalBudget) + *outNonLocalBudget = m_D3D12Budget[DXGI_MEMORY_SEGMENT_GROUP_NON_LOCAL_COPY]; +} + +#if D3D12MA_DXGI_1_4 +HRESULT CurrentBudgetData::UpdateBudget(IDXGIAdapter3* adapter3, bool useMutex) +{ + D3D12MA_ASSERT(adapter3); + + DXGI_QUERY_VIDEO_MEMORY_INFO infoLocal = {}; + DXGI_QUERY_VIDEO_MEMORY_INFO infoNonLocal = {}; + const HRESULT hrLocal = adapter3->QueryVideoMemoryInfo(0, DXGI_MEMORY_SEGMENT_GROUP_LOCAL, &infoLocal); + const HRESULT hrNonLocal = adapter3->QueryVideoMemoryInfo(0, DXGI_MEMORY_SEGMENT_GROUP_NON_LOCAL, &infoNonLocal); + + if (SUCCEEDED(hrLocal) || SUCCEEDED(hrNonLocal)) + { + MutexLockWrite lockWrite(m_BudgetMutex, useMutex); + + if (SUCCEEDED(hrLocal)) + { + m_D3D12Usage[0] = infoLocal.CurrentUsage; + m_D3D12Budget[0] = infoLocal.Budget; + } + if (SUCCEEDED(hrNonLocal)) + { + m_D3D12Usage[1] = infoNonLocal.CurrentUsage; + m_D3D12Budget[1] = infoNonLocal.Budget; + } + + m_BlockBytesAtD3D12Fetch[0] = m_BlockBytes[0]; + m_BlockBytesAtD3D12Fetch[1] = m_BlockBytes[1]; + m_OperationsSinceBudgetFetch = 0; + } + + return FAILED(hrLocal) ? hrLocal : hrNonLocal; +} +#endif // #if D3D12MA_DXGI_1_4 + +void CurrentBudgetData::AddAllocation(UINT group, UINT64 allocationBytes) +{ + ++m_AllocationCount[group]; + m_AllocationBytes[group] += allocationBytes; + ++m_OperationsSinceBudgetFetch; +} + +void CurrentBudgetData::RemoveAllocation(UINT group, UINT64 allocationBytes) +{ + D3D12MA_ASSERT(m_AllocationBytes[group] >= allocationBytes); + D3D12MA_ASSERT(m_AllocationCount[group] > 0); + m_AllocationBytes[group] -= allocationBytes; + --m_AllocationCount[group]; + ++m_OperationsSinceBudgetFetch; +} + +void CurrentBudgetData::AddBlock(UINT group, UINT64 blockBytes) +{ + ++m_BlockCount[group]; + m_BlockBytes[group] += blockBytes; + ++m_OperationsSinceBudgetFetch; +} + +void CurrentBudgetData::RemoveBlock(UINT group, UINT64 blockBytes) +{ + D3D12MA_ASSERT(m_BlockBytes[group] >= blockBytes); + D3D12MA_ASSERT(m_BlockCount[group] > 0); + m_BlockBytes[group] -= blockBytes; + --m_BlockCount[group]; + ++m_OperationsSinceBudgetFetch; +} +#endif // _D3D12MA_CURRENT_BUDGET_DATA_FUNCTIONS +#endif // _D3D12MA_CURRENT_BUDGET_DATA + +#ifndef _D3D12MA_DEFRAGMENTATION_CONTEXT_PIMPL +class DefragmentationContextPimpl +{ + D3D12MA_CLASS_NO_COPY(DefragmentationContextPimpl) +public: + DefragmentationContextPimpl( + AllocatorPimpl* hAllocator, + const DEFRAGMENTATION_DESC& desc, + BlockVector* poolVector); + ~DefragmentationContextPimpl(); + + void GetStats(DEFRAGMENTATION_STATS& outStats) { outStats = m_GlobalStats; } + const ALLOCATION_CALLBACKS& GetAllocs() const { return m_Moves.GetAllocs(); } + + HRESULT DefragmentPassBegin(DEFRAGMENTATION_PASS_MOVE_INFO& moveInfo); + HRESULT DefragmentPassEnd(DEFRAGMENTATION_PASS_MOVE_INFO& moveInfo); + +private: + // Max number of allocations to ignore due to size constraints before ending single pass + static const UINT8 MAX_ALLOCS_TO_IGNORE = 16; + enum class CounterStatus { Pass, Ignore, End }; + + struct FragmentedBlock + { + UINT32 data; + NormalBlock* block; + }; + struct StateBalanced + { + UINT64 avgFreeSize = 0; + UINT64 avgAllocSize = UINT64_MAX; + }; + struct MoveAllocationData + { + UINT64 size; + UINT64 alignment; + ALLOCATION_FLAGS flags; + DEFRAGMENTATION_MOVE move = {}; + }; + + const UINT64 m_MaxPassBytes; + const UINT32 m_MaxPassAllocations; + + Vector m_Moves; + + UINT8 m_IgnoredAllocs = 0; + UINT32 m_Algorithm; + UINT32 m_BlockVectorCount; + BlockVector* m_PoolBlockVector; + BlockVector** m_pBlockVectors; + size_t m_ImmovableBlockCount = 0; + DEFRAGMENTATION_STATS m_GlobalStats = { 0 }; + DEFRAGMENTATION_STATS m_PassStats = { 0 }; + void* m_AlgorithmState = NULL; + + static MoveAllocationData GetMoveData(AllocHandle handle, BlockMetadata* metadata); + CounterStatus CheckCounters(UINT64 bytes); + bool IncrementCounters(UINT64 bytes); + bool ReallocWithinBlock(BlockVector& vector, NormalBlock* block); + bool AllocInOtherBlock(size_t start, size_t end, MoveAllocationData& data, BlockVector& vector); + + bool ComputeDefragmentation(BlockVector& vector, size_t index); + bool ComputeDefragmentation_Fast(BlockVector& vector); + bool ComputeDefragmentation_Balanced(BlockVector& vector, size_t index, bool update); + bool ComputeDefragmentation_Full(BlockVector& vector); + + void UpdateVectorStatistics(BlockVector& vector, StateBalanced& state); +}; +#endif // _D3D12MA_DEFRAGMENTATION_CONTEXT_PIMPL + +#ifndef _D3D12MA_POOL_PIMPL +class PoolPimpl +{ + friend class Allocator; + friend struct PoolListItemTraits; +public: + PoolPimpl(AllocatorPimpl* allocator, const POOL_DESC& desc); + ~PoolPimpl(); + + AllocatorPimpl* GetAllocator() const { return m_Allocator; } + const POOL_DESC& GetDesc() const { return m_Desc; } + bool SupportsCommittedAllocations() const { return m_Desc.BlockSize == 0; } + LPCWSTR GetName() const { return m_Name; } + + BlockVector* GetBlockVector() { return m_BlockVector; } + CommittedAllocationList* GetCommittedAllocationList() { return SupportsCommittedAllocations() ? &m_CommittedAllocations : NULL; } + + HRESULT Init(); + void GetStatistics(Statistics& outStats); + void CalculateStatistics(DetailedStatistics& outStats); + void AddDetailedStatistics(DetailedStatistics& inoutStats); + void SetName(LPCWSTR Name); + +private: + AllocatorPimpl* m_Allocator; // Externally owned object. + POOL_DESC m_Desc; + BlockVector* m_BlockVector; // Owned object. + CommittedAllocationList m_CommittedAllocations; + wchar_t* m_Name; + PoolPimpl* m_PrevPool = NULL; + PoolPimpl* m_NextPool = NULL; + + void FreeName(); +}; + +struct PoolListItemTraits +{ + using ItemType = PoolPimpl; + static ItemType* GetPrev(const ItemType* item) { return item->m_PrevPool; } + static ItemType* GetNext(const ItemType* item) { return item->m_NextPool; } + static ItemType*& AccessPrev(ItemType* item) { return item->m_PrevPool; } + static ItemType*& AccessNext(ItemType* item) { return item->m_NextPool; } +}; +#endif // _D3D12MA_POOL_PIMPL + + +#ifndef _D3D12MA_ALLOCATOR_PIMPL +class AllocatorPimpl +{ + friend class Allocator; + friend class Pool; +public: + std::atomic_uint32_t m_RefCount = {1}; + CurrentBudgetData m_Budget; + + AllocatorPimpl(const ALLOCATION_CALLBACKS& allocationCallbacks, const ALLOCATOR_DESC& desc); + ~AllocatorPimpl(); + + ID3D12Device* GetDevice() const { return m_Device; } +#ifdef __ID3D12Device1_INTERFACE_DEFINED__ + ID3D12Device1* GetDevice1() const { return m_Device1; } +#endif +#ifdef __ID3D12Device4_INTERFACE_DEFINED__ + ID3D12Device4* GetDevice4() const { return m_Device4; } +#endif +#ifdef __ID3D12Device8_INTERFACE_DEFINED__ + ID3D12Device8* GetDevice8() const { return m_Device8; } +#endif + // Shortcut for "Allocation Callbacks", because this function is called so often. + const ALLOCATION_CALLBACKS& GetAllocs() const { return m_AllocationCallbacks; } + const D3D12_FEATURE_DATA_D3D12_OPTIONS& GetD3D12Options() const { return m_D3D12Options; } + BOOL IsUMA() const { return m_D3D12Architecture.UMA; } + BOOL IsCacheCoherentUMA() const { return m_D3D12Architecture.CacheCoherentUMA; } + bool SupportsResourceHeapTier2() const { return m_D3D12Options.ResourceHeapTier >= D3D12_RESOURCE_HEAP_TIER_2; } + bool UseMutex() const { return m_UseMutex; } + AllocationObjectAllocator& GetAllocationObjectAllocator() { return m_AllocationObjectAllocator; } + UINT GetCurrentFrameIndex() const { return m_CurrentFrameIndex.load(); } + /* + If SupportsResourceHeapTier2(): + 0: D3D12_HEAP_TYPE_DEFAULT + 1: D3D12_HEAP_TYPE_UPLOAD + 2: D3D12_HEAP_TYPE_READBACK + else: + 0: D3D12_HEAP_TYPE_DEFAULT + buffer + 1: D3D12_HEAP_TYPE_DEFAULT + texture + 2: D3D12_HEAP_TYPE_DEFAULT + texture RT or DS + 3: D3D12_HEAP_TYPE_UPLOAD + buffer + 4: D3D12_HEAP_TYPE_UPLOAD + texture + 5: D3D12_HEAP_TYPE_UPLOAD + texture RT or DS + 6: D3D12_HEAP_TYPE_READBACK + buffer + 7: D3D12_HEAP_TYPE_READBACK + texture + 8: D3D12_HEAP_TYPE_READBACK + texture RT or DS + */ + UINT GetDefaultPoolCount() const { return SupportsResourceHeapTier2() ? 3 : 9; } + BlockVector** GetDefaultPools() { return m_BlockVectors; } + + HRESULT Init(const ALLOCATOR_DESC& desc); + bool HeapFlagsFulfillResourceHeapTier(D3D12_HEAP_FLAGS flags) const; + UINT StandardHeapTypeToMemorySegmentGroup(D3D12_HEAP_TYPE heapType) const; + UINT HeapPropertiesToMemorySegmentGroup(const D3D12_HEAP_PROPERTIES& heapProps) const; + UINT64 GetMemoryCapacity(UINT memorySegmentGroup) const; + + HRESULT CreatePlacedResourceWrap( + ID3D12Heap *pHeap, + UINT64 HeapOffset, + const CREATE_RESOURCE_PARAMS& createParams, + REFIID riidResource, + void** ppvResource); + + HRESULT CreateResource( + const ALLOCATION_DESC* pAllocDesc, + const CREATE_RESOURCE_PARAMS& createParams, + Allocation** ppAllocation, + REFIID riidResource, + void** ppvResource); + + HRESULT CreateAliasingResource( + Allocation* pAllocation, + UINT64 AllocationLocalOffset, + const CREATE_RESOURCE_PARAMS& createParams, + REFIID riidResource, + void** ppvResource); + + HRESULT AllocateMemory( + const ALLOCATION_DESC* pAllocDesc, + const D3D12_RESOURCE_ALLOCATION_INFO* pAllocInfo, + Allocation** ppAllocation); + + // Unregisters allocation from the collection of dedicated allocations. + // Allocation object must be deleted externally afterwards. + void FreeCommittedMemory(Allocation* allocation); + // Unregisters allocation from the collection of placed allocations. + // Allocation object must be deleted externally afterwards. + void FreePlacedMemory(Allocation* allocation); + // Unregisters allocation from the collection of dedicated allocations and destroys associated heap. + // Allocation object must be deleted externally afterwards. + void FreeHeapMemory(Allocation* allocation); + + void SetResidencyPriority(ID3D12Pageable* obj, D3D12_RESIDENCY_PRIORITY priority) const; + + void SetCurrentFrameIndex(UINT frameIndex); + // For more deailed stats use outCutomHeaps to access statistics divided into L0 and L1 group + void CalculateStatistics(TotalStatistics& outStats, DetailedStatistics outCutomHeaps[2] = NULL); + + void GetBudget(Budget* outLocalBudget, Budget* outNonLocalBudget); + void GetBudgetForHeapType(Budget& outBudget, D3D12_HEAP_TYPE heapType); + + void BuildStatsString(WCHAR** ppStatsString, BOOL detailedMap); + void FreeStatsString(WCHAR* pStatsString); + +private: + using PoolList = IntrusiveLinkedList; + + const bool m_UseMutex; + const bool m_AlwaysCommitted; + const bool m_MsaaAlwaysCommitted; + bool m_DefaultPoolsNotZeroed = false; + ID3D12Device* m_Device; // AddRef +#ifdef __ID3D12Device1_INTERFACE_DEFINED__ + ID3D12Device1* m_Device1 = NULL; // AddRef, optional +#endif +#ifdef __ID3D12Device4_INTERFACE_DEFINED__ + ID3D12Device4* m_Device4 = NULL; // AddRef, optional +#endif +#ifdef __ID3D12Device8_INTERFACE_DEFINED__ + ID3D12Device8* m_Device8 = NULL; // AddRef, optional +#endif +#ifdef __ID3D12Device10_INTERFACE_DEFINED__ + ID3D12Device10* m_Device10 = NULL; // AddRef, optional +#endif + IDXGIAdapter* m_Adapter; // AddRef +#if D3D12MA_DXGI_1_4 + IDXGIAdapter3* m_Adapter3 = NULL; // AddRef, optional +#endif + UINT64 m_PreferredBlockSize; + ALLOCATION_CALLBACKS m_AllocationCallbacks; + D3D12MA_ATOMIC_UINT32 m_CurrentFrameIndex; + DXGI_ADAPTER_DESC m_AdapterDesc; + D3D12_FEATURE_DATA_D3D12_OPTIONS m_D3D12Options; + D3D12_FEATURE_DATA_ARCHITECTURE m_D3D12Architecture; + AllocationObjectAllocator m_AllocationObjectAllocator; + + D3D12MA_RW_MUTEX m_PoolsMutex[HEAP_TYPE_COUNT]; + PoolList m_Pools[HEAP_TYPE_COUNT]; + // Default pools. + BlockVector* m_BlockVectors[DEFAULT_POOL_MAX_COUNT]; + CommittedAllocationList m_CommittedAllocations[STANDARD_HEAP_TYPE_COUNT]; + + /* + Heuristics that decides whether a resource should better be placed in its own, + dedicated allocation (committed resource rather than placed resource). + */ + template + static bool PrefersCommittedAllocation(const D3D12_RESOURCE_DESC_T& resourceDesc); + + // Allocates and registers new committed resource with implicit heap, as dedicated allocation. + // Creates and returns Allocation object and optionally D3D12 resource. + HRESULT AllocateCommittedResource( + const CommittedAllocationParameters& committedAllocParams, + UINT64 resourceSize, bool withinBudget, void* pPrivateData, + const CREATE_RESOURCE_PARAMS& createParams, + Allocation** ppAllocation, REFIID riidResource, void** ppvResource); + + // Allocates and registers new heap without any resources placed in it, as dedicated allocation. + // Creates and returns Allocation object. + HRESULT AllocateHeap( + const CommittedAllocationParameters& committedAllocParams, + const D3D12_RESOURCE_ALLOCATION_INFO& allocInfo, bool withinBudget, + void* pPrivateData, Allocation** ppAllocation); + + template + HRESULT CalcAllocationParams(const ALLOCATION_DESC& allocDesc, UINT64 allocSize, + const D3D12_RESOURCE_DESC_T* resDesc, // Optional + BlockVector*& outBlockVector, CommittedAllocationParameters& outCommittedAllocationParams, bool& outPreferCommitted); + + // Returns UINT32_MAX if index cannot be calculcated. + UINT CalcDefaultPoolIndex(const ALLOCATION_DESC& allocDesc, ResourceClass resourceClass) const; + void CalcDefaultPoolParams(D3D12_HEAP_TYPE& outHeapType, D3D12_HEAP_FLAGS& outHeapFlags, UINT index) const; + + // Registers Pool object in m_Pools. + void RegisterPool(Pool* pool, D3D12_HEAP_TYPE heapType); + // Unregisters Pool object from m_Pools. + void UnregisterPool(Pool* pool, D3D12_HEAP_TYPE heapType); + + HRESULT UpdateD3D12Budget(); + + D3D12_RESOURCE_ALLOCATION_INFO GetResourceAllocationInfoNative(const D3D12_RESOURCE_DESC& resourceDesc) const; +#ifdef __ID3D12Device8_INTERFACE_DEFINED__ + D3D12_RESOURCE_ALLOCATION_INFO GetResourceAllocationInfoNative(const D3D12_RESOURCE_DESC1& resourceDesc) const; +#endif + + template + D3D12_RESOURCE_ALLOCATION_INFO GetResourceAllocationInfo(D3D12_RESOURCE_DESC_T& inOutResourceDesc) const; + + bool NewAllocationWithinBudget(D3D12_HEAP_TYPE heapType, UINT64 size); + + // Writes object { } with data of given budget. + static void WriteBudgetToJson(JsonWriter& json, const Budget& budget); +}; + +#ifndef _D3D12MA_ALLOCATOR_PIMPL_FUNCTINOS +AllocatorPimpl::AllocatorPimpl(const ALLOCATION_CALLBACKS& allocationCallbacks, const ALLOCATOR_DESC& desc) + : m_UseMutex((desc.Flags & ALLOCATOR_FLAG_SINGLETHREADED) == 0), + m_AlwaysCommitted((desc.Flags & ALLOCATOR_FLAG_ALWAYS_COMMITTED) != 0), + m_MsaaAlwaysCommitted((desc.Flags & ALLOCATOR_FLAG_MSAA_TEXTURES_ALWAYS_COMMITTED) != 0), + m_Device(desc.pDevice), + m_Adapter(desc.pAdapter), + m_PreferredBlockSize(desc.PreferredBlockSize != 0 ? desc.PreferredBlockSize : D3D12MA_DEFAULT_BLOCK_SIZE), + m_AllocationCallbacks(allocationCallbacks), + m_CurrentFrameIndex(0), + // Below this line don't use allocationCallbacks but m_AllocationCallbacks!!! + m_AllocationObjectAllocator(m_AllocationCallbacks) +{ + // desc.pAllocationCallbacks intentionally ignored here, preprocessed by CreateAllocator. + ZeroMemory(&m_D3D12Options, sizeof(m_D3D12Options)); + ZeroMemory(&m_D3D12Architecture, sizeof(m_D3D12Architecture)); + + ZeroMemory(m_BlockVectors, sizeof(m_BlockVectors)); + + for (UINT i = 0; i < STANDARD_HEAP_TYPE_COUNT; ++i) + { + m_CommittedAllocations[i].Init( + m_UseMutex, + (D3D12_HEAP_TYPE)(D3D12_HEAP_TYPE_DEFAULT + i), + NULL); // pool + } + + m_Device->AddRef(); + m_Adapter->AddRef(); +} + +HRESULT AllocatorPimpl::Init(const ALLOCATOR_DESC& desc) +{ +#if D3D12MA_DXGI_1_4 + desc.pAdapter->QueryInterface(D3D12MA_IID_PPV_ARGS(&m_Adapter3)); +#endif + +#ifdef __ID3D12Device1_INTERFACE_DEFINED__ + m_Device->QueryInterface(D3D12MA_IID_PPV_ARGS(&m_Device1)); +#endif + +#ifdef __ID3D12Device4_INTERFACE_DEFINED__ + m_Device->QueryInterface(D3D12MA_IID_PPV_ARGS(&m_Device4)); +#endif + +#ifdef __ID3D12Device8_INTERFACE_DEFINED__ + m_Device->QueryInterface(D3D12MA_IID_PPV_ARGS(&m_Device8)); + + if((desc.Flags & ALLOCATOR_FLAG_DEFAULT_POOLS_NOT_ZEROED) != 0) + { + D3D12_FEATURE_DATA_D3D12_OPTIONS7 options7 = {}; + if(SUCCEEDED(m_Device->CheckFeatureSupport(D3D12_FEATURE_D3D12_OPTIONS7, &options7, sizeof(options7)))) + { + // DEFAULT_POOLS_NOT_ZEROED both supported and enabled by the user. + m_DefaultPoolsNotZeroed = true; + } + } +#endif + +#ifdef __ID3D12Device10_INTERFACE_DEFINED__ + m_Device->QueryInterface(D3D12MA_IID_PPV_ARGS(&m_Device10)); +#endif + + HRESULT hr = m_Adapter->GetDesc(&m_AdapterDesc); + if (FAILED(hr)) + { + return hr; + } + + hr = m_Device->CheckFeatureSupport(D3D12_FEATURE_D3D12_OPTIONS, &m_D3D12Options, sizeof(m_D3D12Options)); + if (FAILED(hr)) + { + return hr; + } +#ifdef D3D12MA_FORCE_RESOURCE_HEAP_TIER + m_D3D12Options.ResourceHeapTier = (D3D12MA_FORCE_RESOURCE_HEAP_TIER); +#endif + + hr = m_Device->CheckFeatureSupport(D3D12_FEATURE_ARCHITECTURE, &m_D3D12Architecture, sizeof(m_D3D12Architecture)); + if (FAILED(hr)) + { + m_D3D12Architecture.UMA = FALSE; + m_D3D12Architecture.CacheCoherentUMA = FALSE; + } + + D3D12_HEAP_PROPERTIES heapProps = {}; + const UINT defaultPoolCount = GetDefaultPoolCount(); + for (UINT i = 0; i < defaultPoolCount; ++i) + { + D3D12_HEAP_FLAGS heapFlags; + CalcDefaultPoolParams(heapProps.Type, heapFlags, i); + +#if D3D12MA_CREATE_NOT_ZEROED_AVAILABLE + if(m_DefaultPoolsNotZeroed) + { + heapFlags |= D3D12_HEAP_FLAG_CREATE_NOT_ZEROED; + } +#endif + + m_BlockVectors[i] = D3D12MA_NEW(GetAllocs(), BlockVector)( + this, // hAllocator + heapProps, // heapType + heapFlags, // heapFlags + m_PreferredBlockSize, + 0, // minBlockCount + SIZE_MAX, // maxBlockCount + false, // explicitBlockSize + D3D12MA_DEBUG_ALIGNMENT, // minAllocationAlignment + 0, // Default algorithm, + m_MsaaAlwaysCommitted, + NULL, // pProtectedSession + D3D12_RESIDENCY_PRIORITY_NONE); // residencyPriority + // No need to call m_pBlockVectors[i]->CreateMinBlocks here, becase minBlockCount is 0. + } + +#if D3D12MA_DXGI_1_4 + UpdateD3D12Budget(); +#endif + + return S_OK; +} + +AllocatorPimpl::~AllocatorPimpl() +{ +#ifdef __ID3D12Device10_INTERFACE_DEFINED__ + SAFE_RELEASE(m_Device10); +#endif +#ifdef __ID3D12Device8_INTERFACE_DEFINED__ + SAFE_RELEASE(m_Device8); +#endif +#ifdef __ID3D12Device4_INTERFACE_DEFINED__ + SAFE_RELEASE(m_Device4); +#endif +#ifdef __ID3D12Device1_INTERFACE_DEFINED__ + SAFE_RELEASE(m_Device1); +#endif +#if D3D12MA_DXGI_1_4 + SAFE_RELEASE(m_Adapter3); +#endif + SAFE_RELEASE(m_Adapter); + SAFE_RELEASE(m_Device); + + for (UINT i = DEFAULT_POOL_MAX_COUNT; i--; ) + { + D3D12MA_DELETE(GetAllocs(), m_BlockVectors[i]); + } + + for (UINT i = HEAP_TYPE_COUNT; i--; ) + { + if (!m_Pools[i].IsEmpty()) + { + D3D12MA_ASSERT(0 && "Unfreed pools found!"); + } + } +} + +bool AllocatorPimpl::HeapFlagsFulfillResourceHeapTier(D3D12_HEAP_FLAGS flags) const +{ + if (SupportsResourceHeapTier2()) + { + return true; + } + else + { + const bool allowBuffers = (flags & D3D12_HEAP_FLAG_DENY_BUFFERS) == 0; + const bool allowRtDsTextures = (flags & D3D12_HEAP_FLAG_DENY_RT_DS_TEXTURES) == 0; + const bool allowNonRtDsTextures = (flags & D3D12_HEAP_FLAG_DENY_NON_RT_DS_TEXTURES) == 0; + const uint8_t allowedGroupCount = (allowBuffers ? 1 : 0) + (allowRtDsTextures ? 1 : 0) + (allowNonRtDsTextures ? 1 : 0); + return allowedGroupCount == 1; + } +} + +UINT AllocatorPimpl::StandardHeapTypeToMemorySegmentGroup(D3D12_HEAP_TYPE heapType) const +{ + D3D12MA_ASSERT(IsHeapTypeStandard(heapType)); + if (IsUMA()) + return DXGI_MEMORY_SEGMENT_GROUP_LOCAL_COPY; + return heapType == D3D12_HEAP_TYPE_DEFAULT ? + DXGI_MEMORY_SEGMENT_GROUP_LOCAL_COPY : DXGI_MEMORY_SEGMENT_GROUP_NON_LOCAL_COPY; +} + +UINT AllocatorPimpl::HeapPropertiesToMemorySegmentGroup(const D3D12_HEAP_PROPERTIES& heapProps) const +{ + if (IsUMA()) + return DXGI_MEMORY_SEGMENT_GROUP_LOCAL_COPY; + if (heapProps.MemoryPoolPreference == D3D12_MEMORY_POOL_UNKNOWN) + return StandardHeapTypeToMemorySegmentGroup(heapProps.Type); + return heapProps.MemoryPoolPreference == D3D12_MEMORY_POOL_L1 ? + DXGI_MEMORY_SEGMENT_GROUP_LOCAL_COPY : DXGI_MEMORY_SEGMENT_GROUP_NON_LOCAL_COPY; +} + +UINT64 AllocatorPimpl::GetMemoryCapacity(UINT memorySegmentGroup) const +{ + switch (memorySegmentGroup) + { + case DXGI_MEMORY_SEGMENT_GROUP_LOCAL_COPY: + return IsUMA() ? + m_AdapterDesc.DedicatedVideoMemory + m_AdapterDesc.SharedSystemMemory : m_AdapterDesc.DedicatedVideoMemory; + case DXGI_MEMORY_SEGMENT_GROUP_NON_LOCAL_COPY: + return IsUMA() ? 0 : m_AdapterDesc.SharedSystemMemory; + default: + D3D12MA_ASSERT(0); + return UINT64_MAX; + } +} + +HRESULT AllocatorPimpl::CreatePlacedResourceWrap( + ID3D12Heap *pHeap, + UINT64 HeapOffset, + const CREATE_RESOURCE_PARAMS& createParams, + REFIID riidResource, + void** ppvResource) +{ +#ifdef __ID3D12Device10_INTERFACE_DEFINED__ + if (createParams.Variant == CREATE_RESOURCE_PARAMS::VARIANT_WITH_LAYOUT) + { + if (!m_Device10) + { + return E_NOINTERFACE; + } + return m_Device10->CreatePlacedResource2(pHeap, HeapOffset, + createParams.GetResourceDesc1(), createParams.GetInitialLayout(), + createParams.GetOptimizedClearValue(), createParams.GetNumCastableFormats(), + createParams.GetCastableFormats(), riidResource, ppvResource); + } else +#endif +#ifdef __ID3D12Device8_INTERFACE_DEFINED__ + if (createParams.Variant == CREATE_RESOURCE_PARAMS::VARIANT_WITH_STATE_AND_DESC1) + { + if (!m_Device8) + { + return E_NOINTERFACE; + } + return m_Device8->CreatePlacedResource1(pHeap, HeapOffset, + createParams.GetResourceDesc1(), createParams.GetInitialResourceState(), + createParams.GetOptimizedClearValue(), riidResource, ppvResource); + } else +#endif + if (createParams.Variant == CREATE_RESOURCE_PARAMS::VARIANT_WITH_STATE) + { + return m_Device->CreatePlacedResource(pHeap, HeapOffset, + createParams.GetResourceDesc(), createParams.GetInitialResourceState(), + createParams.GetOptimizedClearValue(), riidResource, ppvResource); + } + else + { + D3D12MA_ASSERT(0); + return E_INVALIDARG; + } +} + + +HRESULT AllocatorPimpl::CreateResource( + const ALLOCATION_DESC* pAllocDesc, + const CREATE_RESOURCE_PARAMS& createParams, + Allocation** ppAllocation, + REFIID riidResource, + void** ppvResource) +{ + D3D12MA_ASSERT(pAllocDesc && createParams.GetBaseResourceDesc() && ppAllocation); + + *ppAllocation = NULL; + if (ppvResource) + { + *ppvResource = NULL; + } + + CREATE_RESOURCE_PARAMS finalCreateParams = createParams; + D3D12_RESOURCE_DESC finalResourceDesc; +#ifdef __ID3D12Device8_INTERFACE_DEFINED__ + D3D12_RESOURCE_DESC1 finalResourceDesc1; +#endif + D3D12_RESOURCE_ALLOCATION_INFO resAllocInfo; + if (createParams.Variant == CREATE_RESOURCE_PARAMS::VARIANT_WITH_STATE) + { + finalResourceDesc = *createParams.GetResourceDesc(); + finalCreateParams.AccessResourceDesc() = &finalResourceDesc; + resAllocInfo = GetResourceAllocationInfo(finalResourceDesc); + } +#ifdef __ID3D12Device8_INTERFACE_DEFINED__ + else if (createParams.Variant == CREATE_RESOURCE_PARAMS::VARIANT_WITH_STATE_AND_DESC1) + { + if (!m_Device8) + { + return E_NOINTERFACE; + } + finalResourceDesc1 = *createParams.GetResourceDesc1(); + finalCreateParams.AccessResourceDesc1() = &finalResourceDesc1; + resAllocInfo = GetResourceAllocationInfo(finalResourceDesc1); + } +#endif +#ifdef __ID3D12Device10_INTERFACE_DEFINED__ + else if (createParams.Variant == CREATE_RESOURCE_PARAMS::VARIANT_WITH_LAYOUT) + { + if (!m_Device10) + { + return E_NOINTERFACE; + } + finalResourceDesc1 = *createParams.GetResourceDesc1(); + finalCreateParams.AccessResourceDesc1() = &finalResourceDesc1; + resAllocInfo = GetResourceAllocationInfo(finalResourceDesc1); + } +#endif + else + { + D3D12MA_ASSERT(0); + return E_INVALIDARG; + } + D3D12MA_ASSERT(IsPow2(resAllocInfo.Alignment)); + D3D12MA_ASSERT(resAllocInfo.SizeInBytes > 0); + + BlockVector* blockVector = NULL; + CommittedAllocationParameters committedAllocationParams = {}; + bool preferCommitted = false; + + HRESULT hr; +#ifdef __ID3D12Device8_INTERFACE_DEFINED__ + if (createParams.Variant >= CREATE_RESOURCE_PARAMS::VARIANT_WITH_STATE_AND_DESC1) + { + hr = CalcAllocationParams(*pAllocDesc, resAllocInfo.SizeInBytes, + createParams.GetResourceDesc1(), + blockVector, committedAllocationParams, preferCommitted); + } + else +#endif + { + hr = CalcAllocationParams(*pAllocDesc, resAllocInfo.SizeInBytes, + createParams.GetResourceDesc(), + blockVector, committedAllocationParams, preferCommitted); + } + if (FAILED(hr)) + return hr; + + const bool withinBudget = (pAllocDesc->Flags & ALLOCATION_FLAG_WITHIN_BUDGET) != 0; + hr = E_INVALIDARG; + if (committedAllocationParams.IsValid() && preferCommitted) + { + hr = AllocateCommittedResource(committedAllocationParams, + resAllocInfo.SizeInBytes, withinBudget, pAllocDesc->pPrivateData, + finalCreateParams, ppAllocation, riidResource, ppvResource); + if (SUCCEEDED(hr)) + return hr; + } + if (blockVector != NULL) + { + hr = blockVector->CreateResource(resAllocInfo.SizeInBytes, resAllocInfo.Alignment, + *pAllocDesc, finalCreateParams, + ppAllocation, riidResource, ppvResource); + if (SUCCEEDED(hr)) + return hr; + } + if (committedAllocationParams.IsValid() && !preferCommitted) + { + hr = AllocateCommittedResource(committedAllocationParams, + resAllocInfo.SizeInBytes, withinBudget, pAllocDesc->pPrivateData, + finalCreateParams, ppAllocation, riidResource, ppvResource); + if (SUCCEEDED(hr)) + return hr; + } + return hr; +} + +HRESULT AllocatorPimpl::AllocateMemory( + const ALLOCATION_DESC* pAllocDesc, + const D3D12_RESOURCE_ALLOCATION_INFO* pAllocInfo, + Allocation** ppAllocation) +{ + *ppAllocation = NULL; + + BlockVector* blockVector = NULL; + CommittedAllocationParameters committedAllocationParams = {}; + bool preferCommitted = false; + HRESULT hr = CalcAllocationParams(*pAllocDesc, pAllocInfo->SizeInBytes, + NULL, // pResDesc + blockVector, committedAllocationParams, preferCommitted); + if (FAILED(hr)) + return hr; + + const bool withinBudget = (pAllocDesc->Flags & ALLOCATION_FLAG_WITHIN_BUDGET) != 0; + hr = E_INVALIDARG; + if (committedAllocationParams.IsValid() && preferCommitted) + { + hr = AllocateHeap(committedAllocationParams, *pAllocInfo, withinBudget, pAllocDesc->pPrivateData, ppAllocation); + if (SUCCEEDED(hr)) + return hr; + } + if (blockVector != NULL) + { + hr = blockVector->Allocate(pAllocInfo->SizeInBytes, pAllocInfo->Alignment, + *pAllocDesc, 1, (Allocation**)ppAllocation); + if (SUCCEEDED(hr)) + return hr; + } + if (committedAllocationParams.IsValid() && !preferCommitted) + { + hr = AllocateHeap(committedAllocationParams, *pAllocInfo, withinBudget, pAllocDesc->pPrivateData, ppAllocation); + if (SUCCEEDED(hr)) + return hr; + } + return hr; +} + +HRESULT AllocatorPimpl::CreateAliasingResource( + Allocation* pAllocation, + UINT64 AllocationLocalOffset, + const CREATE_RESOURCE_PARAMS& createParams, + REFIID riidResource, + void** ppvResource) +{ + *ppvResource = NULL; + + CREATE_RESOURCE_PARAMS finalCreateParams = createParams; + D3D12_RESOURCE_DESC finalResourceDesc; +#ifdef __ID3D12Device8_INTERFACE_DEFINED__ + D3D12_RESOURCE_DESC1 finalResourceDesc1; +#endif + D3D12_RESOURCE_ALLOCATION_INFO resAllocInfo; + if (createParams.Variant == CREATE_RESOURCE_PARAMS::VARIANT_WITH_STATE) + { + finalResourceDesc = *createParams.GetResourceDesc(); + finalCreateParams.AccessResourceDesc() = &finalResourceDesc; + resAllocInfo = GetResourceAllocationInfo(finalResourceDesc); + } +#ifdef __ID3D12Device8_INTERFACE_DEFINED__ + else if (createParams.Variant == CREATE_RESOURCE_PARAMS::VARIANT_WITH_STATE_AND_DESC1) + { + if (!m_Device8) + { + return E_NOINTERFACE; + } + finalResourceDesc1 = *createParams.GetResourceDesc1(); + finalCreateParams.AccessResourceDesc1() = &finalResourceDesc1; + resAllocInfo = GetResourceAllocationInfo(finalResourceDesc1); + } +#endif +#ifdef __ID3D12Device10_INTERFACE_DEFINED__ + else if (createParams.Variant == CREATE_RESOURCE_PARAMS::VARIANT_WITH_LAYOUT) + { + if (!m_Device10) + { + return E_NOINTERFACE; + } + finalResourceDesc1 = *createParams.GetResourceDesc1(); + finalCreateParams.AccessResourceDesc1() = &finalResourceDesc1; + resAllocInfo = GetResourceAllocationInfo(finalResourceDesc1); + } +#endif + else + { + D3D12MA_ASSERT(0); + return E_INVALIDARG; + } + D3D12MA_ASSERT(IsPow2(resAllocInfo.Alignment)); + D3D12MA_ASSERT(resAllocInfo.SizeInBytes > 0); + + ID3D12Heap* const existingHeap = pAllocation->GetHeap(); + const UINT64 existingOffset = pAllocation->GetOffset(); + const UINT64 existingSize = pAllocation->GetSize(); + const UINT64 newOffset = existingOffset + AllocationLocalOffset; + + if (existingHeap == NULL || + AllocationLocalOffset + resAllocInfo.SizeInBytes > existingSize || + newOffset % resAllocInfo.Alignment != 0) + { + return E_INVALIDARG; + } + + return CreatePlacedResourceWrap(existingHeap, newOffset, finalCreateParams, riidResource, ppvResource); +} + +void AllocatorPimpl::FreeCommittedMemory(Allocation* allocation) +{ + D3D12MA_ASSERT(allocation && allocation->m_PackedData.GetType() == Allocation::TYPE_COMMITTED); + + CommittedAllocationList* const allocList = allocation->m_Committed.list; + allocList->Unregister(allocation); + + const UINT memSegmentGroup = allocList->GetMemorySegmentGroup(this); + const UINT64 allocSize = allocation->GetSize(); + m_Budget.RemoveAllocation(memSegmentGroup, allocSize); + m_Budget.RemoveBlock(memSegmentGroup, allocSize); +} + +void AllocatorPimpl::FreePlacedMemory(Allocation* allocation) +{ + D3D12MA_ASSERT(allocation && allocation->m_PackedData.GetType() == Allocation::TYPE_PLACED); + + NormalBlock* const block = allocation->m_Placed.block; + D3D12MA_ASSERT(block); + BlockVector* const blockVector = block->GetBlockVector(); + D3D12MA_ASSERT(blockVector); + m_Budget.RemoveAllocation(HeapPropertiesToMemorySegmentGroup(block->GetHeapProperties()), allocation->GetSize()); + blockVector->Free(allocation); +} + +void AllocatorPimpl::FreeHeapMemory(Allocation* allocation) +{ + D3D12MA_ASSERT(allocation && allocation->m_PackedData.GetType() == Allocation::TYPE_HEAP); + + CommittedAllocationList* const allocList = allocation->m_Committed.list; + allocList->Unregister(allocation); + SAFE_RELEASE(allocation->m_Heap.heap); + + const UINT memSegmentGroup = allocList->GetMemorySegmentGroup(this); + const UINT64 allocSize = allocation->GetSize(); + m_Budget.RemoveAllocation(memSegmentGroup, allocSize); + m_Budget.RemoveBlock(memSegmentGroup, allocSize); +} + +void AllocatorPimpl::SetResidencyPriority(ID3D12Pageable* obj, D3D12_RESIDENCY_PRIORITY priority) const +{ +#ifdef __ID3D12Device1_INTERFACE_DEFINED__ + if (priority != D3D12_RESIDENCY_PRIORITY_NONE && m_Device1) + { + // Intentionally ignoring the result. + m_Device1->SetResidencyPriority(1, &obj, &priority); + } +#endif +} + +void AllocatorPimpl::SetCurrentFrameIndex(UINT frameIndex) +{ + m_CurrentFrameIndex.store(frameIndex); + +#if D3D12MA_DXGI_1_4 + UpdateD3D12Budget(); +#endif +} + +void AllocatorPimpl::CalculateStatistics(TotalStatistics& outStats, DetailedStatistics outCutomHeaps[2]) +{ + // Init stats + for (size_t i = 0; i < HEAP_TYPE_COUNT; i++) + ClearDetailedStatistics(outStats.HeapType[i]); + for (size_t i = 0; i < DXGI_MEMORY_SEGMENT_GROUP_COUNT; i++) + ClearDetailedStatistics(outStats.MemorySegmentGroup[i]); + ClearDetailedStatistics(outStats.Total); + if (outCutomHeaps) + { + ClearDetailedStatistics(outCutomHeaps[0]); + ClearDetailedStatistics(outCutomHeaps[1]); + } + + // Process default pools. 3 standard heap types only. Add them to outStats.HeapType[i]. + if (SupportsResourceHeapTier2()) + { + // DEFAULT, UPLOAD, READBACK. + for (size_t heapTypeIndex = 0; heapTypeIndex < STANDARD_HEAP_TYPE_COUNT; ++heapTypeIndex) + { + BlockVector* const pBlockVector = m_BlockVectors[heapTypeIndex]; + D3D12MA_ASSERT(pBlockVector); + pBlockVector->AddDetailedStatistics(outStats.HeapType[heapTypeIndex]); + } + } + else + { + // DEFAULT, UPLOAD, READBACK. + for (size_t heapTypeIndex = 0; heapTypeIndex < STANDARD_HEAP_TYPE_COUNT; ++heapTypeIndex) + { + for (size_t heapSubType = 0; heapSubType < 3; ++heapSubType) + { + BlockVector* const pBlockVector = m_BlockVectors[heapTypeIndex * 3 + heapSubType]; + D3D12MA_ASSERT(pBlockVector); + pBlockVector->AddDetailedStatistics(outStats.HeapType[heapTypeIndex]); + } + } + } + + // Sum them up to memory segment groups. + AddDetailedStatistics( + outStats.MemorySegmentGroup[StandardHeapTypeToMemorySegmentGroup(D3D12_HEAP_TYPE_DEFAULT)], + outStats.HeapType[0]); + AddDetailedStatistics( + outStats.MemorySegmentGroup[StandardHeapTypeToMemorySegmentGroup(D3D12_HEAP_TYPE_UPLOAD)], + outStats.HeapType[1]); + AddDetailedStatistics( + outStats.MemorySegmentGroup[StandardHeapTypeToMemorySegmentGroup(D3D12_HEAP_TYPE_READBACK)], + outStats.HeapType[2]); + + // Process custom pools. + DetailedStatistics tmpStats; + for (size_t heapTypeIndex = 0; heapTypeIndex < HEAP_TYPE_COUNT; ++heapTypeIndex) + { + MutexLockRead lock(m_PoolsMutex[heapTypeIndex], m_UseMutex); + PoolList& poolList = m_Pools[heapTypeIndex]; + for (PoolPimpl* pool = poolList.Front(); pool != NULL; pool = poolList.GetNext(pool)) + { + const D3D12_HEAP_PROPERTIES& poolHeapProps = pool->GetDesc().HeapProperties; + ClearDetailedStatistics(tmpStats); + pool->AddDetailedStatistics(tmpStats); + AddDetailedStatistics( + outStats.HeapType[heapTypeIndex], tmpStats); + + UINT memorySegment = HeapPropertiesToMemorySegmentGroup(poolHeapProps); + AddDetailedStatistics( + outStats.MemorySegmentGroup[memorySegment], tmpStats); + + if (outCutomHeaps) + AddDetailedStatistics(outCutomHeaps[memorySegment], tmpStats); + } + } + + // Process committed allocations. 3 standard heap types only. + for (UINT heapTypeIndex = 0; heapTypeIndex < STANDARD_HEAP_TYPE_COUNT; ++heapTypeIndex) + { + ClearDetailedStatistics(tmpStats); + m_CommittedAllocations[heapTypeIndex].AddDetailedStatistics(tmpStats); + AddDetailedStatistics( + outStats.HeapType[heapTypeIndex], tmpStats); + AddDetailedStatistics( + outStats.MemorySegmentGroup[StandardHeapTypeToMemorySegmentGroup(IndexToHeapType(heapTypeIndex))], tmpStats); + } + + // Sum up memory segment groups to totals. + AddDetailedStatistics(outStats.Total, outStats.MemorySegmentGroup[0]); + AddDetailedStatistics(outStats.Total, outStats.MemorySegmentGroup[1]); + + D3D12MA_ASSERT(outStats.Total.Stats.BlockCount == + outStats.MemorySegmentGroup[0].Stats.BlockCount + outStats.MemorySegmentGroup[1].Stats.BlockCount); + D3D12MA_ASSERT(outStats.Total.Stats.AllocationCount == + outStats.MemorySegmentGroup[0].Stats.AllocationCount + outStats.MemorySegmentGroup[1].Stats.AllocationCount); + D3D12MA_ASSERT(outStats.Total.Stats.BlockBytes == + outStats.MemorySegmentGroup[0].Stats.BlockBytes + outStats.MemorySegmentGroup[1].Stats.BlockBytes); + D3D12MA_ASSERT(outStats.Total.Stats.AllocationBytes == + outStats.MemorySegmentGroup[0].Stats.AllocationBytes + outStats.MemorySegmentGroup[1].Stats.AllocationBytes); + D3D12MA_ASSERT(outStats.Total.UnusedRangeCount == + outStats.MemorySegmentGroup[0].UnusedRangeCount + outStats.MemorySegmentGroup[1].UnusedRangeCount); + + D3D12MA_ASSERT(outStats.Total.Stats.BlockCount == + outStats.HeapType[0].Stats.BlockCount + outStats.HeapType[1].Stats.BlockCount + + outStats.HeapType[2].Stats.BlockCount + outStats.HeapType[3].Stats.BlockCount); + D3D12MA_ASSERT(outStats.Total.Stats.AllocationCount == + outStats.HeapType[0].Stats.AllocationCount + outStats.HeapType[1].Stats.AllocationCount + + outStats.HeapType[2].Stats.AllocationCount + outStats.HeapType[3].Stats.AllocationCount); + D3D12MA_ASSERT(outStats.Total.Stats.BlockBytes == + outStats.HeapType[0].Stats.BlockBytes + outStats.HeapType[1].Stats.BlockBytes + + outStats.HeapType[2].Stats.BlockBytes + outStats.HeapType[3].Stats.BlockBytes); + D3D12MA_ASSERT(outStats.Total.Stats.AllocationBytes == + outStats.HeapType[0].Stats.AllocationBytes + outStats.HeapType[1].Stats.AllocationBytes + + outStats.HeapType[2].Stats.AllocationBytes + outStats.HeapType[3].Stats.AllocationBytes); + D3D12MA_ASSERT(outStats.Total.UnusedRangeCount == + outStats.HeapType[0].UnusedRangeCount + outStats.HeapType[1].UnusedRangeCount + + outStats.HeapType[2].UnusedRangeCount + outStats.HeapType[3].UnusedRangeCount); +} + +void AllocatorPimpl::GetBudget(Budget* outLocalBudget, Budget* outNonLocalBudget) +{ + if (outLocalBudget) + m_Budget.GetStatistics(outLocalBudget->Stats, DXGI_MEMORY_SEGMENT_GROUP_LOCAL_COPY); + if (outNonLocalBudget) + m_Budget.GetStatistics(outNonLocalBudget->Stats, DXGI_MEMORY_SEGMENT_GROUP_NON_LOCAL_COPY); + +#if D3D12MA_DXGI_1_4 + if (m_Adapter3) + { + if (!m_Budget.ShouldUpdateBudget()) + { + m_Budget.GetBudget(m_UseMutex, + outLocalBudget ? &outLocalBudget->UsageBytes : NULL, + outLocalBudget ? &outLocalBudget->BudgetBytes : NULL, + outNonLocalBudget ? &outNonLocalBudget->UsageBytes : NULL, + outNonLocalBudget ? &outNonLocalBudget->BudgetBytes : NULL); + } + else + { + UpdateD3D12Budget(); + GetBudget(outLocalBudget, outNonLocalBudget); // Recursion + } + } + else +#endif + { + if (outLocalBudget) + { + outLocalBudget->UsageBytes = outLocalBudget->Stats.BlockBytes; + outLocalBudget->BudgetBytes = GetMemoryCapacity(DXGI_MEMORY_SEGMENT_GROUP_LOCAL_COPY) * 8 / 10; // 80% heuristics. + } + if (outNonLocalBudget) + { + outNonLocalBudget->UsageBytes = outNonLocalBudget->Stats.BlockBytes; + outNonLocalBudget->BudgetBytes = GetMemoryCapacity(DXGI_MEMORY_SEGMENT_GROUP_NON_LOCAL_COPY) * 8 / 10; // 80% heuristics. + } + } +} + +void AllocatorPimpl::GetBudgetForHeapType(Budget& outBudget, D3D12_HEAP_TYPE heapType) +{ + switch (heapType) + { + case D3D12_HEAP_TYPE_DEFAULT: + GetBudget(&outBudget, NULL); + break; + case D3D12_HEAP_TYPE_UPLOAD: + case D3D12_HEAP_TYPE_READBACK: + GetBudget(NULL, &outBudget); + break; + default: D3D12MA_ASSERT(0); + } +} + +void AllocatorPimpl::BuildStatsString(WCHAR** ppStatsString, BOOL detailedMap) +{ + StringBuilder sb(GetAllocs()); + { + Budget localBudget = {}, nonLocalBudget = {}; + GetBudget(&localBudget, &nonLocalBudget); + + TotalStatistics stats; + DetailedStatistics customHeaps[2]; + CalculateStatistics(stats, customHeaps); + + JsonWriter json(GetAllocs(), sb); + json.BeginObject(); + { + json.WriteString(L"General"); + json.BeginObject(); + { + json.WriteString(L"API"); + json.WriteString(L"Direct3D 12"); + + json.WriteString(L"GPU"); + json.WriteString(m_AdapterDesc.Description); + + json.WriteString(L"DedicatedVideoMemory"); + json.WriteNumber((UINT64)m_AdapterDesc.DedicatedVideoMemory); + json.WriteString(L"DedicatedSystemMemory"); + json.WriteNumber((UINT64)m_AdapterDesc.DedicatedSystemMemory); + json.WriteString(L"SharedSystemMemory"); + json.WriteNumber((UINT64)m_AdapterDesc.SharedSystemMemory); + + json.WriteString(L"ResourceHeapTier"); + json.WriteNumber(static_cast(m_D3D12Options.ResourceHeapTier)); + + json.WriteString(L"ResourceBindingTier"); + json.WriteNumber(static_cast(m_D3D12Options.ResourceBindingTier)); + + json.WriteString(L"TiledResourcesTier"); + json.WriteNumber(static_cast(m_D3D12Options.TiledResourcesTier)); + + json.WriteString(L"TileBasedRenderer"); + json.WriteBool(m_D3D12Architecture.TileBasedRenderer); + + json.WriteString(L"UMA"); + json.WriteBool(m_D3D12Architecture.UMA); + json.WriteString(L"CacheCoherentUMA"); + json.WriteBool(m_D3D12Architecture.CacheCoherentUMA); + } + json.EndObject(); + } + { + json.WriteString(L"Total"); + json.AddDetailedStatisticsInfoObject(stats.Total); + } + { + json.WriteString(L"MemoryInfo"); + json.BeginObject(); + { + json.WriteString(L"L0"); + json.BeginObject(); + { + json.WriteString(L"Budget"); + WriteBudgetToJson(json, IsUMA() ? localBudget : nonLocalBudget); // When UMA device only L0 present as local + + json.WriteString(L"Stats"); + json.AddDetailedStatisticsInfoObject(stats.MemorySegmentGroup[!IsUMA()]); + + json.WriteString(L"MemoryPools"); + json.BeginObject(); + { + if (IsUMA()) + { + json.WriteString(L"DEFAULT"); + json.BeginObject(); + { + json.WriteString(L"Stats"); + json.AddDetailedStatisticsInfoObject(stats.HeapType[0]); + } + json.EndObject(); + } + json.WriteString(L"UPLOAD"); + json.BeginObject(); + { + json.WriteString(L"Stats"); + json.AddDetailedStatisticsInfoObject(stats.HeapType[1]); + } + json.EndObject(); + + json.WriteString(L"READBACK"); + json.BeginObject(); + { + json.WriteString(L"Stats"); + json.AddDetailedStatisticsInfoObject(stats.HeapType[2]); + } + json.EndObject(); + + json.WriteString(L"CUSTOM"); + json.BeginObject(); + { + json.WriteString(L"Stats"); + json.AddDetailedStatisticsInfoObject(customHeaps[!IsUMA()]); + } + json.EndObject(); + } + json.EndObject(); + } + json.EndObject(); + if (!IsUMA()) + { + json.WriteString(L"L1"); + json.BeginObject(); + { + json.WriteString(L"Budget"); + WriteBudgetToJson(json, localBudget); + + json.WriteString(L"Stats"); + json.AddDetailedStatisticsInfoObject(stats.MemorySegmentGroup[0]); + + json.WriteString(L"MemoryPools"); + json.BeginObject(); + { + json.WriteString(L"DEFAULT"); + json.BeginObject(); + { + json.WriteString(L"Stats"); + json.AddDetailedStatisticsInfoObject(stats.HeapType[0]); + } + json.EndObject(); + + json.WriteString(L"CUSTOM"); + json.BeginObject(); + { + json.WriteString(L"Stats"); + json.AddDetailedStatisticsInfoObject(customHeaps[0]); + } + json.EndObject(); + } + json.EndObject(); + } + json.EndObject(); + } + } + json.EndObject(); + } + + if (detailedMap) + { + const auto writeHeapInfo = [&](BlockVector* blockVector, CommittedAllocationList* committedAllocs, bool customHeap) + { + D3D12MA_ASSERT(blockVector); + + D3D12_HEAP_FLAGS flags = blockVector->GetHeapFlags(); + json.WriteString(L"Flags"); + json.BeginArray(true); + { + if (flags & D3D12_HEAP_FLAG_SHARED) + json.WriteString(L"HEAP_FLAG_SHARED"); + if (flags & D3D12_HEAP_FLAG_ALLOW_DISPLAY) + json.WriteString(L"HEAP_FLAG_ALLOW_DISPLAY"); + if (flags & D3D12_HEAP_FLAG_SHARED_CROSS_ADAPTER) + json.WriteString(L"HEAP_FLAG_CROSS_ADAPTER"); +#ifdef __ID3D12Device8_INTERFACE_DEFINED__ + if (flags & D3D12_HEAP_FLAG_HARDWARE_PROTECTED) + json.WriteString(L"HEAP_FLAG_HARDWARE_PROTECTED"); + if (flags & D3D12_HEAP_FLAG_ALLOW_WRITE_WATCH) + json.WriteString(L"HEAP_FLAG_ALLOW_WRITE_WATCH"); + if (flags & D3D12_HEAP_FLAG_ALLOW_SHADER_ATOMICS) + json.WriteString(L"HEAP_FLAG_ALLOW_SHADER_ATOMICS"); +#endif +#ifdef __ID3D12Device8_INTERFACE_DEFINED__ + if (flags & D3D12_HEAP_FLAG_CREATE_NOT_RESIDENT) + json.WriteString(L"HEAP_FLAG_CREATE_NOT_RESIDENT"); + if (flags & D3D12_HEAP_FLAG_CREATE_NOT_ZEROED) + json.WriteString(L"HEAP_FLAG_CREATE_NOT_ZEROED"); +#endif + + if (flags & D3D12_HEAP_FLAG_DENY_BUFFERS) + json.WriteString(L"HEAP_FLAG_DENY_BUFFERS"); + if (flags & D3D12_HEAP_FLAG_DENY_RT_DS_TEXTURES) + json.WriteString(L"HEAP_FLAG_DENY_RT_DS_TEXTURES"); + if (flags & D3D12_HEAP_FLAG_DENY_NON_RT_DS_TEXTURES) + json.WriteString(L"HEAP_FLAG_DENY_NON_RT_DS_TEXTURES"); + + flags &= ~(D3D12_HEAP_FLAG_SHARED + | D3D12_HEAP_FLAG_DENY_BUFFERS + | D3D12_HEAP_FLAG_ALLOW_DISPLAY + | D3D12_HEAP_FLAG_SHARED_CROSS_ADAPTER + | D3D12_HEAP_FLAG_DENY_RT_DS_TEXTURES + | D3D12_HEAP_FLAG_DENY_NON_RT_DS_TEXTURES +#ifdef __ID3D12Device8_INTERFACE_DEFINED__ + | D3D12_HEAP_FLAG_HARDWARE_PROTECTED + | D3D12_HEAP_FLAG_ALLOW_WRITE_WATCH + | D3D12_HEAP_FLAG_ALLOW_SHADER_ATOMICS +#endif + ); +#ifdef __ID3D12Device8_INTERFACE_DEFINED__ + flags &= ~(D3D12_HEAP_FLAG_CREATE_NOT_RESIDENT + | D3D12_HEAP_FLAG_CREATE_NOT_ZEROED); +#endif + if (flags != 0) + json.WriteNumber((UINT)flags); + + if (customHeap) + { + const D3D12_HEAP_PROPERTIES& properties = blockVector->GetHeapProperties(); + switch (properties.MemoryPoolPreference) + { + default: + D3D12MA_ASSERT(0); + case D3D12_MEMORY_POOL_UNKNOWN: + json.WriteString(L"MEMORY_POOL_UNKNOWN"); + break; + case D3D12_MEMORY_POOL_L0: + json.WriteString(L"MEMORY_POOL_L0"); + break; + case D3D12_MEMORY_POOL_L1: + json.WriteString(L"MEMORY_POOL_L1"); + break; + } + switch (properties.CPUPageProperty) + { + default: + D3D12MA_ASSERT(0); + case D3D12_CPU_PAGE_PROPERTY_UNKNOWN: + json.WriteString(L"CPU_PAGE_PROPERTY_UNKNOWN"); + break; + case D3D12_CPU_PAGE_PROPERTY_NOT_AVAILABLE: + json.WriteString(L"CPU_PAGE_PROPERTY_NOT_AVAILABLE"); + break; + case D3D12_CPU_PAGE_PROPERTY_WRITE_COMBINE: + json.WriteString(L"CPU_PAGE_PROPERTY_WRITE_COMBINE"); + break; + case D3D12_CPU_PAGE_PROPERTY_WRITE_BACK: + json.WriteString(L"CPU_PAGE_PROPERTY_WRITE_BACK"); + break; + } + } + } + json.EndArray(); + + json.WriteString(L"PreferredBlockSize"); + json.WriteNumber(blockVector->GetPreferredBlockSize()); + + json.WriteString(L"Blocks"); + blockVector->WriteBlockInfoToJson(json); + + json.WriteString(L"DedicatedAllocations"); + json.BeginArray(); + if (committedAllocs) + committedAllocs->BuildStatsString(json); + json.EndArray(); + }; + + json.WriteString(L"DefaultPools"); + json.BeginObject(); + { + if (SupportsResourceHeapTier2()) + { + for (uint8_t heapType = 0; heapType < STANDARD_HEAP_TYPE_COUNT; ++heapType) + { + json.WriteString(HeapTypeNames[heapType]); + json.BeginObject(); + writeHeapInfo(m_BlockVectors[heapType], m_CommittedAllocations + heapType, false); + json.EndObject(); + } + } + else + { + for (uint8_t heapType = 0; heapType < STANDARD_HEAP_TYPE_COUNT; ++heapType) + { + for (uint8_t heapSubType = 0; heapSubType < 3; ++heapSubType) + { + static const WCHAR* const heapSubTypeName[] = { + L" - Buffers", + L" - Textures", + L" - Textures RT/DS", + }; + json.BeginString(HeapTypeNames[heapType]); + json.EndString(heapSubTypeName[heapSubType]); + + json.BeginObject(); + writeHeapInfo(m_BlockVectors[heapType + heapSubType], m_CommittedAllocations + heapType, false); + json.EndObject(); + } + } + } + } + json.EndObject(); + + json.WriteString(L"CustomPools"); + json.BeginObject(); + for (uint8_t heapTypeIndex = 0; heapTypeIndex < HEAP_TYPE_COUNT; ++heapTypeIndex) + { + MutexLockRead mutex(m_PoolsMutex[heapTypeIndex], m_UseMutex); + auto* item = m_Pools[heapTypeIndex].Front(); + if (item != NULL) + { + size_t index = 0; + json.WriteString(HeapTypeNames[heapTypeIndex]); + json.BeginArray(); + do + { + json.BeginObject(); + json.WriteString(L"Name"); + json.BeginString(); + json.ContinueString(index++); + if (item->GetName()) + { + json.ContinueString(L" - "); + json.ContinueString(item->GetName()); + } + json.EndString(); + + writeHeapInfo(item->GetBlockVector(), item->GetCommittedAllocationList(), heapTypeIndex == 3); + json.EndObject(); + } while ((item = PoolList::GetNext(item)) != NULL); + json.EndArray(); + } + } + json.EndObject(); + } + json.EndObject(); + } + + const size_t length = sb.GetLength(); + WCHAR* result = AllocateArray(GetAllocs(), length + 2); + result[0] = 0xFEFF; + memcpy(result + 1, sb.GetData(), length * sizeof(WCHAR)); + result[length + 1] = L'\0'; + *ppStatsString = result; +} + +void AllocatorPimpl::FreeStatsString(WCHAR* pStatsString) +{ + D3D12MA_ASSERT(pStatsString); + Free(GetAllocs(), pStatsString); +} + +template +bool AllocatorPimpl::PrefersCommittedAllocation(const D3D12_RESOURCE_DESC_T& resourceDesc) +{ + // Intentional. It may change in the future. + return false; +} + +HRESULT AllocatorPimpl::AllocateCommittedResource( + const CommittedAllocationParameters& committedAllocParams, + UINT64 resourceSize, bool withinBudget, void* pPrivateData, + const CREATE_RESOURCE_PARAMS& createParams, + Allocation** ppAllocation, REFIID riidResource, void** ppvResource) +{ + D3D12MA_ASSERT(committedAllocParams.IsValid()); + + HRESULT hr; + ID3D12Resource* res = NULL; + // Allocate aliasing memory with explicit heap + if (committedAllocParams.m_CanAlias) + { + D3D12_RESOURCE_ALLOCATION_INFO heapAllocInfo = {}; + heapAllocInfo.SizeInBytes = resourceSize; + heapAllocInfo.Alignment = HeapFlagsToAlignment(committedAllocParams.m_HeapFlags, m_MsaaAlwaysCommitted); + hr = AllocateHeap(committedAllocParams, heapAllocInfo, withinBudget, pPrivateData, ppAllocation); + if (SUCCEEDED(hr)) + { + hr = CreatePlacedResourceWrap((*ppAllocation)->GetHeap(), 0, + createParams, D3D12MA_IID_PPV_ARGS(&res)); + if (SUCCEEDED(hr)) + { + if (ppvResource != NULL) + hr = res->QueryInterface(riidResource, ppvResource); + if (SUCCEEDED(hr)) + { + (*ppAllocation)->SetResourcePointer(res, createParams.GetBaseResourceDesc()); + return hr; + } + res->Release(); + } + FreeHeapMemory(*ppAllocation); + } + return hr; + } + + if (withinBudget && + !NewAllocationWithinBudget(committedAllocParams.m_HeapProperties.Type, resourceSize)) + { + return E_OUTOFMEMORY; + } + + /* D3D12 ERROR: + * ID3D12Device::CreateCommittedResource: + * When creating a committed resource, D3D12_HEAP_FLAGS must not have either + * D3D12_HEAP_FLAG_DENY_NON_RT_DS_TEXTURES, + * D3D12_HEAP_FLAG_DENY_RT_DS_TEXTURES, + * nor D3D12_HEAP_FLAG_DENY_BUFFERS set. + * These flags will be set automatically to correspond with the committed resource type. + * + * [ STATE_CREATION ERROR #640: CREATERESOURCEANDHEAP_INVALIDHEAPMISCFLAGS] + */ + +#ifdef __ID3D12Device10_INTERFACE_DEFINED__ + if (createParams.Variant == CREATE_RESOURCE_PARAMS::VARIANT_WITH_LAYOUT) + { + if (!m_Device10) + { + return E_NOINTERFACE; + } + hr = m_Device10->CreateCommittedResource3( + &committedAllocParams.m_HeapProperties, + committedAllocParams.m_HeapFlags & ~RESOURCE_CLASS_HEAP_FLAGS, + createParams.GetResourceDesc1(), createParams.GetInitialLayout(), + createParams.GetOptimizedClearValue(), committedAllocParams.m_ProtectedSession, + createParams.GetNumCastableFormats(), createParams.GetCastableFormats(), + D3D12MA_IID_PPV_ARGS(&res)); + } else +#endif +#ifdef __ID3D12Device8_INTERFACE_DEFINED__ + if (createParams.Variant == CREATE_RESOURCE_PARAMS::VARIANT_WITH_STATE_AND_DESC1) + { + if (!m_Device8) + { + return E_NOINTERFACE; + } + hr = m_Device8->CreateCommittedResource2( + &committedAllocParams.m_HeapProperties, + committedAllocParams.m_HeapFlags & ~RESOURCE_CLASS_HEAP_FLAGS, + createParams.GetResourceDesc1(), createParams.GetInitialResourceState(), + createParams.GetOptimizedClearValue(), committedAllocParams.m_ProtectedSession, + D3D12MA_IID_PPV_ARGS(&res)); + } else +#endif + if (createParams.Variant == CREATE_RESOURCE_PARAMS::VARIANT_WITH_STATE) + { +#ifdef __ID3D12Device4_INTERFACE_DEFINED__ + if (m_Device4) + { + hr = m_Device4->CreateCommittedResource1( + &committedAllocParams.m_HeapProperties, + committedAllocParams.m_HeapFlags & ~RESOURCE_CLASS_HEAP_FLAGS, + createParams.GetResourceDesc(), createParams.GetInitialResourceState(), + createParams.GetOptimizedClearValue(), committedAllocParams.m_ProtectedSession, + D3D12MA_IID_PPV_ARGS(&res)); + } + else +#endif + { + if (committedAllocParams.m_ProtectedSession == NULL) + { + hr = m_Device->CreateCommittedResource( + &committedAllocParams.m_HeapProperties, + committedAllocParams.m_HeapFlags & ~RESOURCE_CLASS_HEAP_FLAGS, + createParams.GetResourceDesc(), createParams.GetInitialResourceState(), + createParams.GetOptimizedClearValue(), D3D12MA_IID_PPV_ARGS(&res)); + } + else + hr = E_NOINTERFACE; + } + } + else + { + D3D12MA_ASSERT(0); + return E_INVALIDARG; + } + + if (SUCCEEDED(hr)) + { + SetResidencyPriority(res, committedAllocParams.m_ResidencyPriority); + + if (ppvResource != NULL) + { + hr = res->QueryInterface(riidResource, ppvResource); + } + if (SUCCEEDED(hr)) + { + BOOL wasZeroInitialized = TRUE; +#if D3D12MA_CREATE_NOT_ZEROED_AVAILABLE + if((committedAllocParams.m_HeapFlags & D3D12_HEAP_FLAG_CREATE_NOT_ZEROED) != 0) + { + wasZeroInitialized = FALSE; + } +#endif + + Allocation* alloc = m_AllocationObjectAllocator.Allocate( + this, resourceSize, createParams.GetBaseResourceDesc()->Alignment, wasZeroInitialized); + alloc->InitCommitted(committedAllocParams.m_List); + alloc->SetResourcePointer(res, createParams.GetBaseResourceDesc()); + alloc->SetPrivateData(pPrivateData); + + *ppAllocation = alloc; + + committedAllocParams.m_List->Register(alloc); + + const UINT memSegmentGroup = HeapPropertiesToMemorySegmentGroup(committedAllocParams.m_HeapProperties); + m_Budget.AddBlock(memSegmentGroup, resourceSize); + m_Budget.AddAllocation(memSegmentGroup, resourceSize); + } + else + { + res->Release(); + } + } + return hr; +} + +HRESULT AllocatorPimpl::AllocateHeap( + const CommittedAllocationParameters& committedAllocParams, + const D3D12_RESOURCE_ALLOCATION_INFO& allocInfo, bool withinBudget, + void* pPrivateData, Allocation** ppAllocation) +{ + D3D12MA_ASSERT(committedAllocParams.IsValid()); + + *ppAllocation = nullptr; + + if (withinBudget && + !NewAllocationWithinBudget(committedAllocParams.m_HeapProperties.Type, allocInfo.SizeInBytes)) + { + return E_OUTOFMEMORY; + } + + D3D12_HEAP_DESC heapDesc = {}; + heapDesc.SizeInBytes = allocInfo.SizeInBytes; + heapDesc.Properties = committedAllocParams.m_HeapProperties; + heapDesc.Alignment = allocInfo.Alignment; + heapDesc.Flags = committedAllocParams.m_HeapFlags; + + HRESULT hr; + ID3D12Heap* heap = nullptr; +#ifdef __ID3D12Device4_INTERFACE_DEFINED__ + if (m_Device4) + hr = m_Device4->CreateHeap1(&heapDesc, committedAllocParams.m_ProtectedSession, D3D12MA_IID_PPV_ARGS(&heap)); + else +#endif + { + if (committedAllocParams.m_ProtectedSession == NULL) + hr = m_Device->CreateHeap(&heapDesc, D3D12MA_IID_PPV_ARGS(&heap)); + else + hr = E_NOINTERFACE; + } + + if (SUCCEEDED(hr)) + { + SetResidencyPriority(heap, committedAllocParams.m_ResidencyPriority); + + BOOL wasZeroInitialized = TRUE; +#if D3D12MA_CREATE_NOT_ZEROED_AVAILABLE + if((heapDesc.Flags & D3D12_HEAP_FLAG_CREATE_NOT_ZEROED) != 0) + { + wasZeroInitialized = FALSE; + } +#endif + + (*ppAllocation) = m_AllocationObjectAllocator.Allocate(this, allocInfo.SizeInBytes, allocInfo.Alignment, wasZeroInitialized); + (*ppAllocation)->InitHeap(committedAllocParams.m_List, heap); + (*ppAllocation)->SetPrivateData(pPrivateData); + committedAllocParams.m_List->Register(*ppAllocation); + + const UINT memSegmentGroup = HeapPropertiesToMemorySegmentGroup(committedAllocParams.m_HeapProperties); + m_Budget.AddBlock(memSegmentGroup, allocInfo.SizeInBytes); + m_Budget.AddAllocation(memSegmentGroup, allocInfo.SizeInBytes); + } + return hr; +} + +template +HRESULT AllocatorPimpl::CalcAllocationParams(const ALLOCATION_DESC& allocDesc, UINT64 allocSize, + const D3D12_RESOURCE_DESC_T* resDesc, + BlockVector*& outBlockVector, CommittedAllocationParameters& outCommittedAllocationParams, bool& outPreferCommitted) +{ + outBlockVector = NULL; + outCommittedAllocationParams = CommittedAllocationParameters(); + outPreferCommitted = false; + + bool msaaAlwaysCommitted; + if (allocDesc.CustomPool != NULL) + { + PoolPimpl* const pool = allocDesc.CustomPool->m_Pimpl; + + msaaAlwaysCommitted = pool->GetBlockVector()->DeniesMsaaTextures(); + outBlockVector = pool->GetBlockVector(); + + const auto& desc = pool->GetDesc(); + outCommittedAllocationParams.m_ProtectedSession = desc.pProtectedSession; + outCommittedAllocationParams.m_HeapProperties = desc.HeapProperties; + outCommittedAllocationParams.m_HeapFlags = desc.HeapFlags; + outCommittedAllocationParams.m_List = pool->GetCommittedAllocationList(); + outCommittedAllocationParams.m_ResidencyPriority = pool->GetDesc().ResidencyPriority; + } + else + { + if (!IsHeapTypeStandard(allocDesc.HeapType)) + { + return E_INVALIDARG; + } + msaaAlwaysCommitted = m_MsaaAlwaysCommitted; + + outCommittedAllocationParams.m_HeapProperties = StandardHeapTypeToHeapProperties(allocDesc.HeapType); + outCommittedAllocationParams.m_HeapFlags = allocDesc.ExtraHeapFlags; + outCommittedAllocationParams.m_List = &m_CommittedAllocations[HeapTypeToIndex(allocDesc.HeapType)]; + // outCommittedAllocationParams.m_ResidencyPriority intentionally left with default value. + + const ResourceClass resourceClass = (resDesc != NULL) ? + ResourceDescToResourceClass(*resDesc) : HeapFlagsToResourceClass(allocDesc.ExtraHeapFlags); + const UINT defaultPoolIndex = CalcDefaultPoolIndex(allocDesc, resourceClass); + if (defaultPoolIndex != UINT32_MAX) + { + outBlockVector = m_BlockVectors[defaultPoolIndex]; + const UINT64 preferredBlockSize = outBlockVector->GetPreferredBlockSize(); + if (allocSize > preferredBlockSize) + { + outBlockVector = NULL; + } + else if (allocSize > preferredBlockSize / 2) + { + // Heuristics: Allocate committed memory if requested size if greater than half of preferred block size. + outPreferCommitted = true; + } + } + + const D3D12_HEAP_FLAGS extraHeapFlags = allocDesc.ExtraHeapFlags & ~RESOURCE_CLASS_HEAP_FLAGS; + if (outBlockVector != NULL && extraHeapFlags != 0) + { + outBlockVector = NULL; + } + } + + if ((allocDesc.Flags & ALLOCATION_FLAG_COMMITTED) != 0 || + m_AlwaysCommitted) + { + outBlockVector = NULL; + } + if ((allocDesc.Flags & ALLOCATION_FLAG_NEVER_ALLOCATE) != 0) + { + outCommittedAllocationParams.m_List = NULL; + } + outCommittedAllocationParams.m_CanAlias = allocDesc.Flags & ALLOCATION_FLAG_CAN_ALIAS; + + if (resDesc != NULL) + { + if (resDesc->SampleDesc.Count > 1 && msaaAlwaysCommitted) + outBlockVector = NULL; + if (!outPreferCommitted && PrefersCommittedAllocation(*resDesc)) + outPreferCommitted = true; + } + + return (outBlockVector != NULL || outCommittedAllocationParams.m_List != NULL) ? S_OK : E_INVALIDARG; +} + +UINT AllocatorPimpl::CalcDefaultPoolIndex(const ALLOCATION_DESC& allocDesc, ResourceClass resourceClass) const +{ + D3D12_HEAP_FLAGS extraHeapFlags = allocDesc.ExtraHeapFlags & ~RESOURCE_CLASS_HEAP_FLAGS; + +#if D3D12MA_CREATE_NOT_ZEROED_AVAILABLE + // If allocator was created with ALLOCATOR_FLAG_DEFAULT_POOLS_NOT_ZEROED, also ignore + // D3D12_HEAP_FLAG_CREATE_NOT_ZEROED. + if(m_DefaultPoolsNotZeroed) + { + extraHeapFlags &= ~D3D12_HEAP_FLAG_CREATE_NOT_ZEROED; + } +#endif + + if (extraHeapFlags != 0) + { + return UINT32_MAX; + } + + UINT poolIndex = UINT_MAX; + switch (allocDesc.HeapType) + { + case D3D12_HEAP_TYPE_DEFAULT: poolIndex = 0; break; + case D3D12_HEAP_TYPE_UPLOAD: poolIndex = 1; break; + case D3D12_HEAP_TYPE_READBACK: poolIndex = 2; break; + default: D3D12MA_ASSERT(0); + } + + if (SupportsResourceHeapTier2()) + return poolIndex; + else + { + switch (resourceClass) + { + case ResourceClass::Buffer: + return poolIndex * 3; + case ResourceClass::Non_RT_DS_Texture: + return poolIndex * 3 + 1; + case ResourceClass::RT_DS_Texture: + return poolIndex * 3 + 2; + default: + return UINT32_MAX; + } + } +} + +void AllocatorPimpl::CalcDefaultPoolParams(D3D12_HEAP_TYPE& outHeapType, D3D12_HEAP_FLAGS& outHeapFlags, UINT index) const +{ + outHeapType = D3D12_HEAP_TYPE_DEFAULT; + outHeapFlags = D3D12_HEAP_FLAG_NONE; + + if (!SupportsResourceHeapTier2()) + { + switch (index % 3) + { + case 0: + outHeapFlags = D3D12_HEAP_FLAG_DENY_RT_DS_TEXTURES | D3D12_HEAP_FLAG_DENY_NON_RT_DS_TEXTURES; + break; + case 1: + outHeapFlags = D3D12_HEAP_FLAG_DENY_BUFFERS | D3D12_HEAP_FLAG_DENY_RT_DS_TEXTURES; + break; + case 2: + outHeapFlags = D3D12_HEAP_FLAG_DENY_BUFFERS | D3D12_HEAP_FLAG_DENY_NON_RT_DS_TEXTURES; + break; + } + + index /= 3; + } + + switch (index) + { + case 0: + outHeapType = D3D12_HEAP_TYPE_DEFAULT; + break; + case 1: + outHeapType = D3D12_HEAP_TYPE_UPLOAD; + break; + case 2: + outHeapType = D3D12_HEAP_TYPE_READBACK; + break; + default: + D3D12MA_ASSERT(0); + } +} + +void AllocatorPimpl::RegisterPool(Pool* pool, D3D12_HEAP_TYPE heapType) +{ + const UINT heapTypeIndex = HeapTypeToIndex(heapType); + + MutexLockWrite lock(m_PoolsMutex[heapTypeIndex], m_UseMutex); + m_Pools[heapTypeIndex].PushBack(pool->m_Pimpl); +} + +void AllocatorPimpl::UnregisterPool(Pool* pool, D3D12_HEAP_TYPE heapType) +{ + const UINT heapTypeIndex = HeapTypeToIndex(heapType); + + MutexLockWrite lock(m_PoolsMutex[heapTypeIndex], m_UseMutex); + m_Pools[heapTypeIndex].Remove(pool->m_Pimpl); +} + +HRESULT AllocatorPimpl::UpdateD3D12Budget() +{ +#if D3D12MA_DXGI_1_4 + if (m_Adapter3) + return m_Budget.UpdateBudget(m_Adapter3, m_UseMutex); + else + return E_NOINTERFACE; +#else + return S_OK; +#endif +} + +D3D12_RESOURCE_ALLOCATION_INFO AllocatorPimpl::GetResourceAllocationInfoNative(const D3D12_RESOURCE_DESC& resourceDesc) const +{ + return m_Device->GetResourceAllocationInfo(0, 1, &resourceDesc); +} + +#ifdef __ID3D12Device8_INTERFACE_DEFINED__ +D3D12_RESOURCE_ALLOCATION_INFO AllocatorPimpl::GetResourceAllocationInfoNative(const D3D12_RESOURCE_DESC1& resourceDesc) const +{ + D3D12MA_ASSERT(m_Device8 != NULL); + D3D12_RESOURCE_ALLOCATION_INFO1 info1Unused; + return m_Device8->GetResourceAllocationInfo2(0, 1, &resourceDesc, &info1Unused); +} +#endif // #ifdef __ID3D12Device8_INTERFACE_DEFINED__ + +template +D3D12_RESOURCE_ALLOCATION_INFO AllocatorPimpl::GetResourceAllocationInfo(D3D12_RESOURCE_DESC_T& inOutResourceDesc) const +{ + /* Optional optimization: Microsoft documentation says: + https://docs.microsoft.com/en-us/windows/win32/api/d3d12/nf-d3d12-id3d12device-getresourceallocationinfo + + Your application can forgo using GetResourceAllocationInfo for buffer resources + (D3D12_RESOURCE_DIMENSION_BUFFER). Buffers have the same size on all adapters, + which is merely the smallest multiple of 64KB that's greater or equal to + D3D12_RESOURCE_DESC::Width. + */ + if (inOutResourceDesc.Alignment == 0 && + inOutResourceDesc.Dimension == D3D12_RESOURCE_DIMENSION_BUFFER) + { + return { + AlignUp(inOutResourceDesc.Width, D3D12_DEFAULT_RESOURCE_PLACEMENT_ALIGNMENT), // SizeInBytes + D3D12_DEFAULT_RESOURCE_PLACEMENT_ALIGNMENT }; // Alignment + } + +#if D3D12MA_USE_SMALL_RESOURCE_PLACEMENT_ALIGNMENT + if (inOutResourceDesc.Alignment == 0 && + inOutResourceDesc.Dimension == D3D12_RESOURCE_DIMENSION_TEXTURE2D && + (inOutResourceDesc.Flags & (D3D12_RESOURCE_FLAG_ALLOW_RENDER_TARGET | D3D12_RESOURCE_FLAG_ALLOW_DEPTH_STENCIL)) == 0 +#if D3D12MA_USE_SMALL_RESOURCE_PLACEMENT_ALIGNMENT == 1 + && CanUseSmallAlignment(inOutResourceDesc) +#endif + ) + { + /* + The algorithm here is based on Microsoft sample: "Small Resources Sample" + https://github.com/microsoft/DirectX-Graphics-Samples/tree/master/Samples/Desktop/D3D12SmallResources + */ + const UINT64 smallAlignmentToTry = inOutResourceDesc.SampleDesc.Count > 1 ? + D3D12_SMALL_MSAA_RESOURCE_PLACEMENT_ALIGNMENT : + D3D12_SMALL_RESOURCE_PLACEMENT_ALIGNMENT; + inOutResourceDesc.Alignment = smallAlignmentToTry; + const D3D12_RESOURCE_ALLOCATION_INFO smallAllocInfo = GetResourceAllocationInfoNative(inOutResourceDesc); + // Check if alignment requested has been granted. + if (smallAllocInfo.Alignment == smallAlignmentToTry) + { + return smallAllocInfo; + } + inOutResourceDesc.Alignment = 0; // Restore original + } +#endif // #if D3D12MA_USE_SMALL_RESOURCE_PLACEMENT_ALIGNMENT + + return GetResourceAllocationInfoNative(inOutResourceDesc); +} + +bool AllocatorPimpl::NewAllocationWithinBudget(D3D12_HEAP_TYPE heapType, UINT64 size) +{ + Budget budget = {}; + GetBudgetForHeapType(budget, heapType); + return budget.UsageBytes + size <= budget.BudgetBytes; +} + +void AllocatorPimpl::WriteBudgetToJson(JsonWriter& json, const Budget& budget) +{ + json.BeginObject(); + { + json.WriteString(L"BudgetBytes"); + json.WriteNumber(budget.BudgetBytes); + json.WriteString(L"UsageBytes"); + json.WriteNumber(budget.UsageBytes); + } + json.EndObject(); +} + +#endif // _D3D12MA_ALLOCATOR_PIMPL +#endif // _D3D12MA_ALLOCATOR_PIMPL + +#ifndef _D3D12MA_VIRTUAL_BLOCK_PIMPL +class VirtualBlockPimpl +{ +public: + const ALLOCATION_CALLBACKS m_AllocationCallbacks; + const UINT64 m_Size; + BlockMetadata* m_Metadata; + + VirtualBlockPimpl(const ALLOCATION_CALLBACKS& allocationCallbacks, const VIRTUAL_BLOCK_DESC& desc); + ~VirtualBlockPimpl(); +}; + +#ifndef _D3D12MA_VIRTUAL_BLOCK_PIMPL_FUNCTIONS +VirtualBlockPimpl::VirtualBlockPimpl(const ALLOCATION_CALLBACKS& allocationCallbacks, const VIRTUAL_BLOCK_DESC& desc) + : m_AllocationCallbacks(allocationCallbacks), m_Size(desc.Size) +{ + switch (desc.Flags & VIRTUAL_BLOCK_FLAG_ALGORITHM_MASK) + { + case VIRTUAL_BLOCK_FLAG_ALGORITHM_LINEAR: + m_Metadata = D3D12MA_NEW(allocationCallbacks, BlockMetadata_Linear)(&m_AllocationCallbacks, true); + break; + default: + D3D12MA_ASSERT(0); + case 0: + m_Metadata = D3D12MA_NEW(allocationCallbacks, BlockMetadata_TLSF)(&m_AllocationCallbacks, true); + break; + } + m_Metadata->Init(m_Size); +} + +VirtualBlockPimpl::~VirtualBlockPimpl() +{ + D3D12MA_DELETE(m_AllocationCallbacks, m_Metadata); +} +#endif // _D3D12MA_VIRTUAL_BLOCK_PIMPL_FUNCTIONS +#endif // _D3D12MA_VIRTUAL_BLOCK_PIMPL + + +#ifndef _D3D12MA_MEMORY_BLOCK_FUNCTIONS +MemoryBlock::MemoryBlock( + AllocatorPimpl* allocator, + const D3D12_HEAP_PROPERTIES& heapProps, + D3D12_HEAP_FLAGS heapFlags, + UINT64 size, + UINT id) + : m_Allocator(allocator), + m_HeapProps(heapProps), + m_HeapFlags(heapFlags), + m_Size(size), + m_Id(id) {} + +MemoryBlock::~MemoryBlock() +{ + if (m_Heap) + { + m_Heap->Release(); + m_Allocator->m_Budget.RemoveBlock( + m_Allocator->HeapPropertiesToMemorySegmentGroup(m_HeapProps), m_Size); + } +} + +HRESULT MemoryBlock::Init(ID3D12ProtectedResourceSession* pProtectedSession, bool denyMsaaTextures) +{ + D3D12MA_ASSERT(m_Heap == NULL && m_Size > 0); + + D3D12_HEAP_DESC heapDesc = {}; + heapDesc.SizeInBytes = m_Size; + heapDesc.Properties = m_HeapProps; + heapDesc.Alignment = HeapFlagsToAlignment(m_HeapFlags, denyMsaaTextures); + heapDesc.Flags = m_HeapFlags; + + HRESULT hr; +#ifdef __ID3D12Device4_INTERFACE_DEFINED__ + ID3D12Device4* const device4 = m_Allocator->GetDevice4(); + if (device4) + hr = m_Allocator->GetDevice4()->CreateHeap1(&heapDesc, pProtectedSession, D3D12MA_IID_PPV_ARGS(&m_Heap)); + else +#endif + { + if (pProtectedSession == NULL) + hr = m_Allocator->GetDevice()->CreateHeap(&heapDesc, D3D12MA_IID_PPV_ARGS(&m_Heap)); + else + hr = E_NOINTERFACE; + } + + if (SUCCEEDED(hr)) + { + m_Allocator->m_Budget.AddBlock( + m_Allocator->HeapPropertiesToMemorySegmentGroup(m_HeapProps), m_Size); + } + return hr; +} +#endif // _D3D12MA_MEMORY_BLOCK_FUNCTIONS + +#ifndef _D3D12MA_NORMAL_BLOCK_FUNCTIONS +NormalBlock::NormalBlock( + AllocatorPimpl* allocator, + BlockVector* blockVector, + const D3D12_HEAP_PROPERTIES& heapProps, + D3D12_HEAP_FLAGS heapFlags, + UINT64 size, + UINT id) + : MemoryBlock(allocator, heapProps, heapFlags, size, id), + m_pMetadata(NULL), + m_BlockVector(blockVector) {} + +NormalBlock::~NormalBlock() +{ + if (m_pMetadata != NULL) + { + // Define macro D3D12MA_DEBUG_LOG to receive the list of the unfreed allocations. + if (!m_pMetadata->IsEmpty()) + m_pMetadata->DebugLogAllAllocations(); + + // THIS IS THE MOST IMPORTANT ASSERT IN THE ENTIRE LIBRARY! + // Hitting it means you have some memory leak - unreleased Allocation objects. + D3D12MA_ASSERT(m_pMetadata->IsEmpty() && "Some allocations were not freed before destruction of this memory block!"); + + D3D12MA_DELETE(m_Allocator->GetAllocs(), m_pMetadata); + } +} + +HRESULT NormalBlock::Init(UINT32 algorithm, ID3D12ProtectedResourceSession* pProtectedSession, bool denyMsaaTextures) +{ + HRESULT hr = MemoryBlock::Init(pProtectedSession, denyMsaaTextures); + if (FAILED(hr)) + { + return hr; + } + + switch (algorithm) + { + case POOL_FLAG_ALGORITHM_LINEAR: + m_pMetadata = D3D12MA_NEW(m_Allocator->GetAllocs(), BlockMetadata_Linear)(&m_Allocator->GetAllocs(), false); + break; + default: + D3D12MA_ASSERT(0); + case 0: + m_pMetadata = D3D12MA_NEW(m_Allocator->GetAllocs(), BlockMetadata_TLSF)(&m_Allocator->GetAllocs(), false); + break; + } + m_pMetadata->Init(m_Size); + + return hr; +} + +bool NormalBlock::Validate() const +{ + D3D12MA_VALIDATE(GetHeap() && + m_pMetadata && + m_pMetadata->GetSize() != 0 && + m_pMetadata->GetSize() == GetSize()); + return m_pMetadata->Validate(); +} +#endif // _D3D12MA_NORMAL_BLOCK_FUNCTIONS + +#ifndef _D3D12MA_COMMITTED_ALLOCATION_LIST_FUNCTIONS +void CommittedAllocationList::Init(bool useMutex, D3D12_HEAP_TYPE heapType, PoolPimpl* pool) +{ + m_UseMutex = useMutex; + m_HeapType = heapType; + m_Pool = pool; +} + +CommittedAllocationList::~CommittedAllocationList() +{ + if (!m_AllocationList.IsEmpty()) + { + D3D12MA_ASSERT(0 && "Unfreed committed allocations found!"); + } +} + +UINT CommittedAllocationList::GetMemorySegmentGroup(AllocatorPimpl* allocator) const +{ + if (m_Pool) + return allocator->HeapPropertiesToMemorySegmentGroup(m_Pool->GetDesc().HeapProperties); + else + return allocator->StandardHeapTypeToMemorySegmentGroup(m_HeapType); +} + +void CommittedAllocationList::AddStatistics(Statistics& inoutStats) +{ + MutexLockRead lock(m_Mutex, m_UseMutex); + + for (Allocation* alloc = m_AllocationList.Front(); + alloc != NULL; alloc = m_AllocationList.GetNext(alloc)) + { + const UINT64 size = alloc->GetSize(); + inoutStats.BlockCount++; + inoutStats.AllocationCount++; + inoutStats.BlockBytes += size; + inoutStats.AllocationBytes += size; + } +} + +void CommittedAllocationList::AddDetailedStatistics(DetailedStatistics& inoutStats) +{ + MutexLockRead lock(m_Mutex, m_UseMutex); + + for (Allocation* alloc = m_AllocationList.Front(); + alloc != NULL; alloc = m_AllocationList.GetNext(alloc)) + { + const UINT64 size = alloc->GetSize(); + inoutStats.Stats.BlockCount++; + inoutStats.Stats.BlockBytes += size; + AddDetailedStatisticsAllocation(inoutStats, size); + } +} + +void CommittedAllocationList::BuildStatsString(JsonWriter& json) +{ + MutexLockRead lock(m_Mutex, m_UseMutex); + + for (Allocation* alloc = m_AllocationList.Front(); + alloc != NULL; alloc = m_AllocationList.GetNext(alloc)) + { + json.BeginObject(true); + json.AddAllocationToObject(*alloc); + json.EndObject(); + } +} + +void CommittedAllocationList::Register(Allocation* alloc) +{ + MutexLockWrite lock(m_Mutex, m_UseMutex); + m_AllocationList.PushBack(alloc); +} + +void CommittedAllocationList::Unregister(Allocation* alloc) +{ + MutexLockWrite lock(m_Mutex, m_UseMutex); + m_AllocationList.Remove(alloc); +} +#endif // _D3D12MA_COMMITTED_ALLOCATION_LIST_FUNCTIONS + +#ifndef _D3D12MA_BLOCK_VECTOR_FUNCTIONS +BlockVector::BlockVector( + AllocatorPimpl* hAllocator, + const D3D12_HEAP_PROPERTIES& heapProps, + D3D12_HEAP_FLAGS heapFlags, + UINT64 preferredBlockSize, + size_t minBlockCount, + size_t maxBlockCount, + bool explicitBlockSize, + UINT64 minAllocationAlignment, + UINT32 algorithm, + bool denyMsaaTextures, + ID3D12ProtectedResourceSession* pProtectedSession, + D3D12_RESIDENCY_PRIORITY residencyPriority) + : m_hAllocator(hAllocator), + m_HeapProps(heapProps), + m_HeapFlags(heapFlags), + m_PreferredBlockSize(preferredBlockSize), + m_MinBlockCount(minBlockCount), + m_MaxBlockCount(maxBlockCount), + m_ExplicitBlockSize(explicitBlockSize), + m_MinAllocationAlignment(minAllocationAlignment), + m_Algorithm(algorithm), + m_DenyMsaaTextures(denyMsaaTextures), + m_ProtectedSession(pProtectedSession), + m_ResidencyPriority(residencyPriority), + m_HasEmptyBlock(false), + m_Blocks(hAllocator->GetAllocs()), + m_NextBlockId(0) {} + +BlockVector::~BlockVector() +{ + for (size_t i = m_Blocks.size(); i--; ) + { + D3D12MA_DELETE(m_hAllocator->GetAllocs(), m_Blocks[i]); + } +} + +HRESULT BlockVector::CreateMinBlocks() +{ + for (size_t i = 0; i < m_MinBlockCount; ++i) + { + HRESULT hr = CreateBlock(m_PreferredBlockSize, NULL); + if (FAILED(hr)) + { + return hr; + } + } + return S_OK; +} + +bool BlockVector::IsEmpty() +{ + MutexLockRead lock(m_Mutex, m_hAllocator->UseMutex()); + return m_Blocks.empty(); +} + +HRESULT BlockVector::Allocate( + UINT64 size, + UINT64 alignment, + const ALLOCATION_DESC& allocDesc, + size_t allocationCount, + Allocation** pAllocations) +{ + size_t allocIndex; + HRESULT hr = S_OK; + + { + MutexLockWrite lock(m_Mutex, m_hAllocator->UseMutex()); + for (allocIndex = 0; allocIndex < allocationCount; ++allocIndex) + { + hr = AllocatePage( + size, + alignment, + allocDesc, + pAllocations + allocIndex); + if (FAILED(hr)) + { + break; + } + } + } + + if (FAILED(hr)) + { + // Free all already created allocations. + while (allocIndex--) + { + Free(pAllocations[allocIndex]); + } + ZeroMemory(pAllocations, sizeof(Allocation*) * allocationCount); + } + + return hr; +} + +void BlockVector::Free(Allocation* hAllocation) +{ + NormalBlock* pBlockToDelete = NULL; + + bool budgetExceeded = false; + if (IsHeapTypeStandard(m_HeapProps.Type)) + { + Budget budget = {}; + m_hAllocator->GetBudgetForHeapType(budget, m_HeapProps.Type); + budgetExceeded = budget.UsageBytes >= budget.BudgetBytes; + } + + // Scope for lock. + { + MutexLockWrite lock(m_Mutex, m_hAllocator->UseMutex()); + + NormalBlock* pBlock = hAllocation->m_Placed.block; + + pBlock->m_pMetadata->Free(hAllocation->GetAllocHandle()); + D3D12MA_HEAVY_ASSERT(pBlock->Validate()); + + const size_t blockCount = m_Blocks.size(); + // pBlock became empty after this deallocation. + if (pBlock->m_pMetadata->IsEmpty()) + { + // Already has empty Allocation. We don't want to have two, so delete this one. + if ((m_HasEmptyBlock || budgetExceeded) && + blockCount > m_MinBlockCount) + { + pBlockToDelete = pBlock; + Remove(pBlock); + } + // We now have first empty block. + else + { + m_HasEmptyBlock = true; + } + } + // pBlock didn't become empty, but we have another empty block - find and free that one. + // (This is optional, heuristics.) + else if (m_HasEmptyBlock && blockCount > m_MinBlockCount) + { + NormalBlock* pLastBlock = m_Blocks.back(); + if (pLastBlock->m_pMetadata->IsEmpty()) + { + pBlockToDelete = pLastBlock; + m_Blocks.pop_back(); + m_HasEmptyBlock = false; + } + } + + IncrementallySortBlocks(); + } + + // Destruction of a free Allocation. Deferred until this point, outside of mutex + // lock, for performance reason. + if (pBlockToDelete != NULL) + { + D3D12MA_DELETE(m_hAllocator->GetAllocs(), pBlockToDelete); + } +} + +HRESULT BlockVector::CreateResource( + UINT64 size, + UINT64 alignment, + const ALLOCATION_DESC& allocDesc, + const CREATE_RESOURCE_PARAMS& createParams, + Allocation** ppAllocation, + REFIID riidResource, + void** ppvResource) +{ + HRESULT hr = Allocate(size, alignment, allocDesc, 1, ppAllocation); + if (SUCCEEDED(hr)) + { + ID3D12Resource* res = NULL; + hr = m_hAllocator->CreatePlacedResourceWrap( + (*ppAllocation)->m_Placed.block->GetHeap(), + (*ppAllocation)->GetOffset(), + createParams, + D3D12MA_IID_PPV_ARGS(&res)); + if (SUCCEEDED(hr)) + { + if (ppvResource != NULL) + { + hr = res->QueryInterface(riidResource, ppvResource); + } + if (SUCCEEDED(hr)) + { + (*ppAllocation)->SetResourcePointer(res, createParams.GetBaseResourceDesc()); + } + else + { + res->Release(); + SAFE_RELEASE(*ppAllocation); + } + } + else + { + SAFE_RELEASE(*ppAllocation); + } + } + return hr; +} + +void BlockVector::AddStatistics(Statistics& inoutStats) +{ + MutexLockRead lock(m_Mutex, m_hAllocator->UseMutex()); + + for (size_t i = 0; i < m_Blocks.size(); ++i) + { + const NormalBlock* const pBlock = m_Blocks[i]; + D3D12MA_ASSERT(pBlock); + D3D12MA_HEAVY_ASSERT(pBlock->Validate()); + pBlock->m_pMetadata->AddStatistics(inoutStats); + } +} + +void BlockVector::AddDetailedStatistics(DetailedStatistics& inoutStats) +{ + MutexLockRead lock(m_Mutex, m_hAllocator->UseMutex()); + + for (size_t i = 0; i < m_Blocks.size(); ++i) + { + const NormalBlock* const pBlock = m_Blocks[i]; + D3D12MA_ASSERT(pBlock); + D3D12MA_HEAVY_ASSERT(pBlock->Validate()); + pBlock->m_pMetadata->AddDetailedStatistics(inoutStats); + } +} + +void BlockVector::WriteBlockInfoToJson(JsonWriter& json) +{ + MutexLockRead lock(m_Mutex, m_hAllocator->UseMutex()); + + json.BeginObject(); + + for (size_t i = 0, count = m_Blocks.size(); i < count; ++i) + { + const NormalBlock* const pBlock = m_Blocks[i]; + D3D12MA_ASSERT(pBlock); + D3D12MA_HEAVY_ASSERT(pBlock->Validate()); + json.BeginString(); + json.ContinueString(pBlock->GetId()); + json.EndString(); + + json.BeginObject(); + pBlock->m_pMetadata->WriteAllocationInfoToJson(json); + json.EndObject(); + } + + json.EndObject(); +} + +UINT64 BlockVector::CalcSumBlockSize() const +{ + UINT64 result = 0; + for (size_t i = m_Blocks.size(); i--; ) + { + result += m_Blocks[i]->m_pMetadata->GetSize(); + } + return result; +} + +UINT64 BlockVector::CalcMaxBlockSize() const +{ + UINT64 result = 0; + for (size_t i = m_Blocks.size(); i--; ) + { + result = D3D12MA_MAX(result, m_Blocks[i]->m_pMetadata->GetSize()); + if (result >= m_PreferredBlockSize) + { + break; + } + } + return result; +} + +void BlockVector::Remove(NormalBlock* pBlock) +{ + for (size_t blockIndex = 0; blockIndex < m_Blocks.size(); ++blockIndex) + { + if (m_Blocks[blockIndex] == pBlock) + { + m_Blocks.remove(blockIndex); + return; + } + } + D3D12MA_ASSERT(0); +} + +void BlockVector::IncrementallySortBlocks() +{ + if (!m_IncrementalSort) + return; + // Bubble sort only until first swap. + for (size_t i = 1; i < m_Blocks.size(); ++i) + { + if (m_Blocks[i - 1]->m_pMetadata->GetSumFreeSize() > m_Blocks[i]->m_pMetadata->GetSumFreeSize()) + { + D3D12MA_SWAP(m_Blocks[i - 1], m_Blocks[i]); + return; + } + } +} + +void BlockVector::SortByFreeSize() +{ + D3D12MA_SORT(m_Blocks.begin(), m_Blocks.end(), + [](auto* b1, auto* b2) + { + return b1->m_pMetadata->GetSumFreeSize() < b2->m_pMetadata->GetSumFreeSize(); + }); +} + +HRESULT BlockVector::AllocatePage( + UINT64 size, + UINT64 alignment, + const ALLOCATION_DESC& allocDesc, + Allocation** pAllocation) +{ + // Early reject: requested allocation size is larger that maximum block size for this block vector. + if (size + D3D12MA_DEBUG_MARGIN > m_PreferredBlockSize) + { + return E_OUTOFMEMORY; + } + + UINT64 freeMemory = UINT64_MAX; + if (IsHeapTypeStandard(m_HeapProps.Type)) + { + Budget budget = {}; + m_hAllocator->GetBudgetForHeapType(budget, m_HeapProps.Type); + freeMemory = (budget.UsageBytes < budget.BudgetBytes) ? (budget.BudgetBytes - budget.UsageBytes) : 0; + } + + const bool canCreateNewBlock = + ((allocDesc.Flags & ALLOCATION_FLAG_NEVER_ALLOCATE) == 0) && + (m_Blocks.size() < m_MaxBlockCount) && + // Even if we don't have to stay within budget with this allocation, when the + // budget would be exceeded, we don't want to allocate new blocks, but always + // create resources as committed. + freeMemory >= size; + + // 1. Search existing allocations + { + // Forward order in m_Blocks - prefer blocks with smallest amount of free space. + for (size_t blockIndex = 0; blockIndex < m_Blocks.size(); ++blockIndex) + { + NormalBlock* const pCurrBlock = m_Blocks[blockIndex]; + D3D12MA_ASSERT(pCurrBlock); + HRESULT hr = AllocateFromBlock( + pCurrBlock, + size, + alignment, + allocDesc.Flags, + allocDesc.pPrivateData, + allocDesc.Flags & ALLOCATION_FLAG_STRATEGY_MASK, + pAllocation); + if (SUCCEEDED(hr)) + { + return hr; + } + } + } + + // 2. Try to create new block. + if (canCreateNewBlock) + { + // Calculate optimal size for new block. + UINT64 newBlockSize = m_PreferredBlockSize; + UINT newBlockSizeShift = 0; + + if (!m_ExplicitBlockSize) + { + // Allocate 1/8, 1/4, 1/2 as first blocks. + const UINT64 maxExistingBlockSize = CalcMaxBlockSize(); + for (UINT i = 0; i < NEW_BLOCK_SIZE_SHIFT_MAX; ++i) + { + const UINT64 smallerNewBlockSize = newBlockSize / 2; + if (smallerNewBlockSize > maxExistingBlockSize && smallerNewBlockSize >= size * 2) + { + newBlockSize = smallerNewBlockSize; + ++newBlockSizeShift; + } + else + { + break; + } + } + } + + size_t newBlockIndex = 0; + HRESULT hr = newBlockSize <= freeMemory ? + CreateBlock(newBlockSize, &newBlockIndex) : E_OUTOFMEMORY; + // Allocation of this size failed? Try 1/2, 1/4, 1/8 of m_PreferredBlockSize. + if (!m_ExplicitBlockSize) + { + while (FAILED(hr) && newBlockSizeShift < NEW_BLOCK_SIZE_SHIFT_MAX) + { + const UINT64 smallerNewBlockSize = newBlockSize / 2; + if (smallerNewBlockSize >= size) + { + newBlockSize = smallerNewBlockSize; + ++newBlockSizeShift; + hr = newBlockSize <= freeMemory ? + CreateBlock(newBlockSize, &newBlockIndex) : E_OUTOFMEMORY; + } + else + { + break; + } + } + } + + if (SUCCEEDED(hr)) + { + NormalBlock* const pBlock = m_Blocks[newBlockIndex]; + D3D12MA_ASSERT(pBlock->m_pMetadata->GetSize() >= size); + + hr = AllocateFromBlock( + pBlock, + size, + alignment, + allocDesc.Flags, + allocDesc.pPrivateData, + allocDesc.Flags & ALLOCATION_FLAG_STRATEGY_MASK, + pAllocation); + if (SUCCEEDED(hr)) + { + return hr; + } + else + { + // Allocation from new block failed, possibly due to D3D12MA_DEBUG_MARGIN or alignment. + return E_OUTOFMEMORY; + } + } + } + + return E_OUTOFMEMORY; +} + +HRESULT BlockVector::AllocateFromBlock( + NormalBlock* pBlock, + UINT64 size, + UINT64 alignment, + ALLOCATION_FLAGS allocFlags, + void* pPrivateData, + UINT32 strategy, + Allocation** pAllocation) +{ + alignment = D3D12MA_MAX(alignment, m_MinAllocationAlignment); + + AllocationRequest currRequest = {}; + if (pBlock->m_pMetadata->CreateAllocationRequest( + size, + alignment, + allocFlags & ALLOCATION_FLAG_UPPER_ADDRESS, + strategy, + &currRequest)) + { + return CommitAllocationRequest(currRequest, pBlock, size, alignment, pPrivateData, pAllocation); + } + return E_OUTOFMEMORY; +} + +HRESULT BlockVector::CommitAllocationRequest( + AllocationRequest& allocRequest, + NormalBlock* pBlock, + UINT64 size, + UINT64 alignment, + void* pPrivateData, + Allocation** pAllocation) +{ + // We no longer have an empty Allocation. + if (pBlock->m_pMetadata->IsEmpty()) + m_HasEmptyBlock = false; + + *pAllocation = m_hAllocator->GetAllocationObjectAllocator().Allocate(m_hAllocator, size, alignment, allocRequest.zeroInitialized); + pBlock->m_pMetadata->Alloc(allocRequest, size, *pAllocation); + + (*pAllocation)->InitPlaced(allocRequest.allocHandle, pBlock); + (*pAllocation)->SetPrivateData(pPrivateData); + + D3D12MA_HEAVY_ASSERT(pBlock->Validate()); + m_hAllocator->m_Budget.AddAllocation(m_hAllocator->HeapPropertiesToMemorySegmentGroup(m_HeapProps), size); + + return S_OK; +} + +HRESULT BlockVector::CreateBlock( + UINT64 blockSize, + size_t* pNewBlockIndex) +{ + NormalBlock* const pBlock = D3D12MA_NEW(m_hAllocator->GetAllocs(), NormalBlock)( + m_hAllocator, + this, + m_HeapProps, + m_HeapFlags, + blockSize, + m_NextBlockId++); + HRESULT hr = pBlock->Init(m_Algorithm, m_ProtectedSession, m_DenyMsaaTextures); + if (FAILED(hr)) + { + D3D12MA_DELETE(m_hAllocator->GetAllocs(), pBlock); + return hr; + } + + m_hAllocator->SetResidencyPriority(pBlock->GetHeap(), m_ResidencyPriority); + + m_Blocks.push_back(pBlock); + if (pNewBlockIndex != NULL) + { + *pNewBlockIndex = m_Blocks.size() - 1; + } + + return hr; +} +#endif // _D3D12MA_BLOCK_VECTOR_FUNCTIONS + +#ifndef _D3D12MA_DEFRAGMENTATION_CONTEXT_PIMPL_FUNCTIONS +DefragmentationContextPimpl::DefragmentationContextPimpl( + AllocatorPimpl* hAllocator, + const DEFRAGMENTATION_DESC& desc, + BlockVector* poolVector) + : m_MaxPassBytes(desc.MaxBytesPerPass == 0 ? UINT64_MAX : desc.MaxBytesPerPass), + m_MaxPassAllocations(desc.MaxAllocationsPerPass == 0 ? UINT32_MAX : desc.MaxAllocationsPerPass), + m_Moves(hAllocator->GetAllocs()) +{ + m_Algorithm = desc.Flags & DEFRAGMENTATION_FLAG_ALGORITHM_MASK; + + if (poolVector != NULL) + { + m_BlockVectorCount = 1; + m_PoolBlockVector = poolVector; + m_pBlockVectors = &m_PoolBlockVector; + m_PoolBlockVector->SetIncrementalSort(false); + m_PoolBlockVector->SortByFreeSize(); + } + else + { + m_BlockVectorCount = hAllocator->GetDefaultPoolCount(); + m_PoolBlockVector = NULL; + m_pBlockVectors = hAllocator->GetDefaultPools(); + for (UINT32 i = 0; i < m_BlockVectorCount; ++i) + { + BlockVector* vector = m_pBlockVectors[i]; + if (vector != NULL) + { + vector->SetIncrementalSort(false); + vector->SortByFreeSize(); + } + } + } + + switch (m_Algorithm) + { + case 0: // Default algorithm + m_Algorithm = DEFRAGMENTATION_FLAG_ALGORITHM_BALANCED; + case DEFRAGMENTATION_FLAG_ALGORITHM_BALANCED: + { + m_AlgorithmState = D3D12MA_NEW_ARRAY(hAllocator->GetAllocs(), StateBalanced, m_BlockVectorCount); + break; + } + } +} + +DefragmentationContextPimpl::~DefragmentationContextPimpl() +{ + if (m_PoolBlockVector != NULL) + m_PoolBlockVector->SetIncrementalSort(true); + else + { + for (UINT32 i = 0; i < m_BlockVectorCount; ++i) + { + BlockVector* vector = m_pBlockVectors[i]; + if (vector != NULL) + vector->SetIncrementalSort(true); + } + } + + if (m_AlgorithmState) + { + switch (m_Algorithm) + { + case DEFRAGMENTATION_FLAG_ALGORITHM_BALANCED: + D3D12MA_DELETE_ARRAY(m_Moves.GetAllocs(), reinterpret_cast(m_AlgorithmState), m_BlockVectorCount); + break; + default: + D3D12MA_ASSERT(0); + } + } +} + +HRESULT DefragmentationContextPimpl::DefragmentPassBegin(DEFRAGMENTATION_PASS_MOVE_INFO& moveInfo) +{ + if (m_PoolBlockVector != NULL) + { + MutexLockWrite lock(m_PoolBlockVector->GetMutex(), m_PoolBlockVector->m_hAllocator->UseMutex()); + + if (m_PoolBlockVector->GetBlockCount() > 1) + ComputeDefragmentation(*m_PoolBlockVector, 0); + else if (m_PoolBlockVector->GetBlockCount() == 1) + ReallocWithinBlock(*m_PoolBlockVector, m_PoolBlockVector->GetBlock(0)); + + // Setup index into block vector + for (size_t i = 0; i < m_Moves.size(); ++i) + m_Moves[i].pDstTmpAllocation->SetPrivateData(0); + } + else + { + for (UINT32 i = 0; i < m_BlockVectorCount; ++i) + { + if (m_pBlockVectors[i] != NULL) + { + MutexLockWrite lock(m_pBlockVectors[i]->GetMutex(), m_pBlockVectors[i]->m_hAllocator->UseMutex()); + + bool end = false; + size_t movesOffset = m_Moves.size(); + if (m_pBlockVectors[i]->GetBlockCount() > 1) + { + end = ComputeDefragmentation(*m_pBlockVectors[i], i); + } + else if (m_pBlockVectors[i]->GetBlockCount() == 1) + { + end = ReallocWithinBlock(*m_pBlockVectors[i], m_pBlockVectors[i]->GetBlock(0)); + } + + // Setup index into block vector + for (; movesOffset < m_Moves.size(); ++movesOffset) + m_Moves[movesOffset].pDstTmpAllocation->SetPrivateData(reinterpret_cast(static_cast(i))); + + if (end) + break; + } + } + } + + moveInfo.MoveCount = static_cast(m_Moves.size()); + if (moveInfo.MoveCount > 0) + { + moveInfo.pMoves = m_Moves.data(); + return S_FALSE; + } + + moveInfo.pMoves = NULL; + return S_OK; +} + +HRESULT DefragmentationContextPimpl::DefragmentPassEnd(DEFRAGMENTATION_PASS_MOVE_INFO& moveInfo) +{ + D3D12MA_ASSERT(moveInfo.MoveCount > 0 ? moveInfo.pMoves != NULL : true); + + HRESULT result = S_OK; + Vector immovableBlocks(m_Moves.GetAllocs()); + + for (uint32_t i = 0; i < moveInfo.MoveCount; ++i) + { + DEFRAGMENTATION_MOVE& move = moveInfo.pMoves[i]; + size_t prevCount = 0, currentCount = 0; + UINT64 freedBlockSize = 0; + + UINT32 vectorIndex; + BlockVector* vector; + if (m_PoolBlockVector != NULL) + { + vectorIndex = 0; + vector = m_PoolBlockVector; + } + else + { + vectorIndex = static_cast(reinterpret_cast(move.pDstTmpAllocation->GetPrivateData())); + vector = m_pBlockVectors[vectorIndex]; + D3D12MA_ASSERT(vector != NULL); + } + + switch (move.Operation) + { + case DEFRAGMENTATION_MOVE_OPERATION_COPY: + { + move.pSrcAllocation->SwapBlockAllocation(move.pDstTmpAllocation); + + // Scope for locks, Free have it's own lock + { + MutexLockRead lock(vector->GetMutex(), vector->m_hAllocator->UseMutex()); + prevCount = vector->GetBlockCount(); + freedBlockSize = move.pDstTmpAllocation->GetBlock()->m_pMetadata->GetSize(); + } + move.pDstTmpAllocation->Release(); + { + MutexLockRead lock(vector->GetMutex(), vector->m_hAllocator->UseMutex()); + currentCount = vector->GetBlockCount(); + } + + result = S_FALSE; + break; + } + case DEFRAGMENTATION_MOVE_OPERATION_IGNORE: + { + m_PassStats.BytesMoved -= move.pSrcAllocation->GetSize(); + --m_PassStats.AllocationsMoved; + move.pDstTmpAllocation->Release(); + + NormalBlock* newBlock = move.pSrcAllocation->GetBlock(); + bool notPresent = true; + for (const FragmentedBlock& block : immovableBlocks) + { + if (block.block == newBlock) + { + notPresent = false; + break; + } + } + if (notPresent) + immovableBlocks.push_back({ vectorIndex, newBlock }); + break; + } + case DEFRAGMENTATION_MOVE_OPERATION_DESTROY: + { + m_PassStats.BytesMoved -= move.pSrcAllocation->GetSize(); + --m_PassStats.AllocationsMoved; + // Scope for locks, Free have it's own lock + { + MutexLockRead lock(vector->GetMutex(), vector->m_hAllocator->UseMutex()); + prevCount = vector->GetBlockCount(); + freedBlockSize = move.pSrcAllocation->GetBlock()->m_pMetadata->GetSize(); + } + move.pSrcAllocation->Release(); + { + MutexLockRead lock(vector->GetMutex(), vector->m_hAllocator->UseMutex()); + currentCount = vector->GetBlockCount(); + } + freedBlockSize *= prevCount - currentCount; + + UINT64 dstBlockSize; + { + MutexLockRead lock(vector->GetMutex(), vector->m_hAllocator->UseMutex()); + dstBlockSize = move.pDstTmpAllocation->GetBlock()->m_pMetadata->GetSize(); + } + move.pDstTmpAllocation->Release(); + { + MutexLockRead lock(vector->GetMutex(), vector->m_hAllocator->UseMutex()); + freedBlockSize += dstBlockSize * (currentCount - vector->GetBlockCount()); + currentCount = vector->GetBlockCount(); + } + + result = S_FALSE; + break; + } + default: + D3D12MA_ASSERT(0); + } + + if (prevCount > currentCount) + { + size_t freedBlocks = prevCount - currentCount; + m_PassStats.HeapsFreed += static_cast(freedBlocks); + m_PassStats.BytesFreed += freedBlockSize; + } + } + moveInfo.MoveCount = 0; + moveInfo.pMoves = NULL; + m_Moves.clear(); + + // Update stats + m_GlobalStats.AllocationsMoved += m_PassStats.AllocationsMoved; + m_GlobalStats.BytesFreed += m_PassStats.BytesFreed; + m_GlobalStats.BytesMoved += m_PassStats.BytesMoved; + m_GlobalStats.HeapsFreed += m_PassStats.HeapsFreed; + m_PassStats = { 0 }; + + // Move blocks with immovable allocations according to algorithm + if (immovableBlocks.size() > 0) + { + // Move to the begining + for (const FragmentedBlock& block : immovableBlocks) + { + BlockVector* vector = m_pBlockVectors[block.data]; + MutexLockWrite lock(vector->GetMutex(), vector->m_hAllocator->UseMutex()); + + for (size_t i = m_ImmovableBlockCount; i < vector->GetBlockCount(); ++i) + { + if (vector->GetBlock(i) == block.block) + { + D3D12MA_SWAP(vector->m_Blocks[i], vector->m_Blocks[m_ImmovableBlockCount++]); + break; + } + } + } + } + return result; +} + +bool DefragmentationContextPimpl::ComputeDefragmentation(BlockVector& vector, size_t index) +{ + switch (m_Algorithm) + { + case DEFRAGMENTATION_FLAG_ALGORITHM_FAST: + return ComputeDefragmentation_Fast(vector); + default: + D3D12MA_ASSERT(0); + case DEFRAGMENTATION_FLAG_ALGORITHM_BALANCED: + return ComputeDefragmentation_Balanced(vector, index, true); + case DEFRAGMENTATION_FLAG_ALGORITHM_FULL: + return ComputeDefragmentation_Full(vector); + } +} + +DefragmentationContextPimpl::MoveAllocationData DefragmentationContextPimpl::GetMoveData( + AllocHandle handle, BlockMetadata* metadata) +{ + MoveAllocationData moveData; + moveData.move.pSrcAllocation = (Allocation*)metadata->GetAllocationPrivateData(handle); + moveData.size = moveData.move.pSrcAllocation->GetSize(); + moveData.alignment = moveData.move.pSrcAllocation->GetAlignment(); + moveData.flags = ALLOCATION_FLAG_NONE; + + return moveData; +} + +DefragmentationContextPimpl::CounterStatus DefragmentationContextPimpl::CheckCounters(UINT64 bytes) +{ + // Ignore allocation if will exceed max size for copy + if (m_PassStats.BytesMoved + bytes > m_MaxPassBytes) + { + if (++m_IgnoredAllocs < MAX_ALLOCS_TO_IGNORE) + return CounterStatus::Ignore; + else + return CounterStatus::End; + } + return CounterStatus::Pass; +} + +bool DefragmentationContextPimpl::IncrementCounters(UINT64 bytes) +{ + m_PassStats.BytesMoved += bytes; + // Early return when max found + if (++m_PassStats.AllocationsMoved >= m_MaxPassAllocations || m_PassStats.BytesMoved >= m_MaxPassBytes) + { + D3D12MA_ASSERT((m_PassStats.AllocationsMoved == m_MaxPassAllocations || + m_PassStats.BytesMoved == m_MaxPassBytes) && "Exceeded maximal pass threshold!"); + return true; + } + return false; +} + +bool DefragmentationContextPimpl::ReallocWithinBlock(BlockVector& vector, NormalBlock* block) +{ + BlockMetadata* metadata = block->m_pMetadata; + + for (AllocHandle handle = metadata->GetAllocationListBegin(); + handle != (AllocHandle)0; + handle = metadata->GetNextAllocation(handle)) + { + MoveAllocationData moveData = GetMoveData(handle, metadata); + // Ignore newly created allocations by defragmentation algorithm + if (moveData.move.pSrcAllocation->GetPrivateData() == this) + continue; + switch (CheckCounters(moveData.move.pSrcAllocation->GetSize())) + { + case CounterStatus::Ignore: + continue; + case CounterStatus::End: + return true; + default: + D3D12MA_ASSERT(0); + case CounterStatus::Pass: + break; + } + + UINT64 offset = moveData.move.pSrcAllocation->GetOffset(); + if (offset != 0 && metadata->GetSumFreeSize() >= moveData.size) + { + AllocationRequest request = {}; + if (metadata->CreateAllocationRequest( + moveData.size, + moveData.alignment, + false, + ALLOCATION_FLAG_STRATEGY_MIN_OFFSET, + &request)) + { + if (metadata->GetAllocationOffset(request.allocHandle) < offset) + { + if (SUCCEEDED(vector.CommitAllocationRequest( + request, + block, + moveData.size, + moveData.alignment, + this, + &moveData.move.pDstTmpAllocation))) + { + m_Moves.push_back(moveData.move); + if (IncrementCounters(moveData.size)) + return true; + } + } + } + } + } + return false; +} + +bool DefragmentationContextPimpl::AllocInOtherBlock(size_t start, size_t end, MoveAllocationData& data, BlockVector& vector) +{ + for (; start < end; ++start) + { + NormalBlock* dstBlock = vector.GetBlock(start); + if (dstBlock->m_pMetadata->GetSumFreeSize() >= data.size) + { + if (SUCCEEDED(vector.AllocateFromBlock(dstBlock, + data.size, + data.alignment, + data.flags, + this, + 0, + &data.move.pDstTmpAllocation))) + { + m_Moves.push_back(data.move); + if (IncrementCounters(data.size)) + return true; + break; + } + } + } + return false; +} + +bool DefragmentationContextPimpl::ComputeDefragmentation_Fast(BlockVector& vector) +{ + // Move only between blocks + + // Go through allocations in last blocks and try to fit them inside first ones + for (size_t i = vector.GetBlockCount() - 1; i > m_ImmovableBlockCount; --i) + { + BlockMetadata* metadata = vector.GetBlock(i)->m_pMetadata; + + for (AllocHandle handle = metadata->GetAllocationListBegin(); + handle != (AllocHandle)0; + handle = metadata->GetNextAllocation(handle)) + { + MoveAllocationData moveData = GetMoveData(handle, metadata); + // Ignore newly created allocations by defragmentation algorithm + if (moveData.move.pSrcAllocation->GetPrivateData() == this) + continue; + switch (CheckCounters(moveData.move.pSrcAllocation->GetSize())) + { + case CounterStatus::Ignore: + continue; + case CounterStatus::End: + return true; + default: + D3D12MA_ASSERT(0); + case CounterStatus::Pass: + break; + } + + // Check all previous blocks for free space + if (AllocInOtherBlock(0, i, moveData, vector)) + return true; + } + } + return false; +} + +bool DefragmentationContextPimpl::ComputeDefragmentation_Balanced(BlockVector& vector, size_t index, bool update) +{ + // Go over every allocation and try to fit it in previous blocks at lowest offsets, + // if not possible: realloc within single block to minimize offset (exclude offset == 0), + // but only if there are noticable gaps between them (some heuristic, ex. average size of allocation in block) + D3D12MA_ASSERT(m_AlgorithmState != NULL); + + StateBalanced& vectorState = reinterpret_cast(m_AlgorithmState)[index]; + if (update && vectorState.avgAllocSize == UINT64_MAX) + UpdateVectorStatistics(vector, vectorState); + + const size_t startMoveCount = m_Moves.size(); + UINT64 minimalFreeRegion = vectorState.avgFreeSize / 2; + for (size_t i = vector.GetBlockCount() - 1; i > m_ImmovableBlockCount; --i) + { + NormalBlock* block = vector.GetBlock(i); + BlockMetadata* metadata = block->m_pMetadata; + UINT64 prevFreeRegionSize = 0; + + for (AllocHandle handle = metadata->GetAllocationListBegin(); + handle != (AllocHandle)0; + handle = metadata->GetNextAllocation(handle)) + { + MoveAllocationData moveData = GetMoveData(handle, metadata); + // Ignore newly created allocations by defragmentation algorithm + if (moveData.move.pSrcAllocation->GetPrivateData() == this) + continue; + switch (CheckCounters(moveData.move.pSrcAllocation->GetSize())) + { + case CounterStatus::Ignore: + continue; + case CounterStatus::End: + return true; + default: + D3D12MA_ASSERT(0); + case CounterStatus::Pass: + break; + } + + // Check all previous blocks for free space + const size_t prevMoveCount = m_Moves.size(); + if (AllocInOtherBlock(0, i, moveData, vector)) + return true; + + UINT64 nextFreeRegionSize = metadata->GetNextFreeRegionSize(handle); + // If no room found then realloc within block for lower offset + UINT64 offset = moveData.move.pSrcAllocation->GetOffset(); + if (prevMoveCount == m_Moves.size() && offset != 0 && metadata->GetSumFreeSize() >= moveData.size) + { + // Check if realloc will make sense + if (prevFreeRegionSize >= minimalFreeRegion || + nextFreeRegionSize >= minimalFreeRegion || + moveData.size <= vectorState.avgFreeSize || + moveData.size <= vectorState.avgAllocSize) + { + AllocationRequest request = {}; + if (metadata->CreateAllocationRequest( + moveData.size, + moveData.alignment, + false, + ALLOCATION_FLAG_STRATEGY_MIN_OFFSET, + &request)) + { + if (metadata->GetAllocationOffset(request.allocHandle) < offset) + { + if (SUCCEEDED(vector.CommitAllocationRequest( + request, + block, + moveData.size, + moveData.alignment, + this, + &moveData.move.pDstTmpAllocation))) + { + m_Moves.push_back(moveData.move); + if (IncrementCounters(moveData.size)) + return true; + } + } + } + } + } + prevFreeRegionSize = nextFreeRegionSize; + } + } + + // No moves perfomed, update statistics to current vector state + if (startMoveCount == m_Moves.size() && !update) + { + vectorState.avgAllocSize = UINT64_MAX; + return ComputeDefragmentation_Balanced(vector, index, false); + } + return false; +} + +bool DefragmentationContextPimpl::ComputeDefragmentation_Full(BlockVector& vector) +{ + // Go over every allocation and try to fit it in previous blocks at lowest offsets, + // if not possible: realloc within single block to minimize offset (exclude offset == 0) + + for (size_t i = vector.GetBlockCount() - 1; i > m_ImmovableBlockCount; --i) + { + NormalBlock* block = vector.GetBlock(i); + BlockMetadata* metadata = block->m_pMetadata; + + for (AllocHandle handle = metadata->GetAllocationListBegin(); + handle != (AllocHandle)0; + handle = metadata->GetNextAllocation(handle)) + { + MoveAllocationData moveData = GetMoveData(handle, metadata); + // Ignore newly created allocations by defragmentation algorithm + if (moveData.move.pSrcAllocation->GetPrivateData() == this) + continue; + switch (CheckCounters(moveData.move.pSrcAllocation->GetSize())) + { + case CounterStatus::Ignore: + continue; + case CounterStatus::End: + return true; + default: + D3D12MA_ASSERT(0); + case CounterStatus::Pass: + break; + } + + // Check all previous blocks for free space + const size_t prevMoveCount = m_Moves.size(); + if (AllocInOtherBlock(0, i, moveData, vector)) + return true; + + // If no room found then realloc within block for lower offset + UINT64 offset = moveData.move.pSrcAllocation->GetOffset(); + if (prevMoveCount == m_Moves.size() && offset != 0 && metadata->GetSumFreeSize() >= moveData.size) + { + AllocationRequest request = {}; + if (metadata->CreateAllocationRequest( + moveData.size, + moveData.alignment, + false, + ALLOCATION_FLAG_STRATEGY_MIN_OFFSET, + &request)) + { + if (metadata->GetAllocationOffset(request.allocHandle) < offset) + { + if (SUCCEEDED(vector.CommitAllocationRequest( + request, + block, + moveData.size, + moveData.alignment, + this, + &moveData.move.pDstTmpAllocation))) + { + m_Moves.push_back(moveData.move); + if (IncrementCounters(moveData.size)) + return true; + } + } + } + } + } + } + return false; +} + +void DefragmentationContextPimpl::UpdateVectorStatistics(BlockVector& vector, StateBalanced& state) +{ + size_t allocCount = 0; + size_t freeCount = 0; + state.avgFreeSize = 0; + state.avgAllocSize = 0; + + for (size_t i = 0; i < vector.GetBlockCount(); ++i) + { + BlockMetadata* metadata = vector.GetBlock(i)->m_pMetadata; + + allocCount += metadata->GetAllocationCount(); + freeCount += metadata->GetFreeRegionsCount(); + state.avgFreeSize += metadata->GetSumFreeSize(); + state.avgAllocSize += metadata->GetSize(); + } + + state.avgAllocSize = (state.avgAllocSize - state.avgFreeSize) / allocCount; + state.avgFreeSize /= freeCount; +} +#endif // _D3D12MA_DEFRAGMENTATION_CONTEXT_PIMPL_FUNCTIONS + +#ifndef _D3D12MA_POOL_PIMPL_FUNCTIONS +PoolPimpl::PoolPimpl(AllocatorPimpl* allocator, const POOL_DESC& desc) + : m_Allocator(allocator), + m_Desc(desc), + m_BlockVector(NULL), + m_Name(NULL) +{ + const bool explicitBlockSize = desc.BlockSize != 0; + const UINT64 preferredBlockSize = explicitBlockSize ? desc.BlockSize : D3D12MA_DEFAULT_BLOCK_SIZE; + UINT maxBlockCount = desc.MaxBlockCount != 0 ? desc.MaxBlockCount : UINT_MAX; + +#ifndef __ID3D12Device4_INTERFACE_DEFINED__ + D3D12MA_ASSERT(m_Desc.pProtectedSession == NULL); +#endif + + m_BlockVector = D3D12MA_NEW(allocator->GetAllocs(), BlockVector)( + allocator, desc.HeapProperties, desc.HeapFlags, + preferredBlockSize, + desc.MinBlockCount, maxBlockCount, + explicitBlockSize, + D3D12MA_MAX(desc.MinAllocationAlignment, (UINT64)D3D12MA_DEBUG_ALIGNMENT), + (desc.Flags & POOL_FLAG_ALGORITHM_MASK) != 0, + (desc.Flags & POOL_FLAG_MSAA_TEXTURES_ALWAYS_COMMITTED) != 0, + desc.pProtectedSession, + desc.ResidencyPriority); +} + +PoolPimpl::~PoolPimpl() +{ + D3D12MA_ASSERT(m_PrevPool == NULL && m_NextPool == NULL); + FreeName(); + D3D12MA_DELETE(m_Allocator->GetAllocs(), m_BlockVector); +} + +HRESULT PoolPimpl::Init() +{ + m_CommittedAllocations.Init(m_Allocator->UseMutex(), m_Desc.HeapProperties.Type, this); + return m_BlockVector->CreateMinBlocks(); +} + +void PoolPimpl::GetStatistics(Statistics& outStats) +{ + ClearStatistics(outStats); + m_BlockVector->AddStatistics(outStats); + m_CommittedAllocations.AddStatistics(outStats); +} + +void PoolPimpl::CalculateStatistics(DetailedStatistics& outStats) +{ + ClearDetailedStatistics(outStats); + AddDetailedStatistics(outStats); +} + +void PoolPimpl::AddDetailedStatistics(DetailedStatistics& inoutStats) +{ + m_BlockVector->AddDetailedStatistics(inoutStats); + m_CommittedAllocations.AddDetailedStatistics(inoutStats); +} + +void PoolPimpl::SetName(LPCWSTR Name) +{ + FreeName(); + + if (Name) + { + const size_t nameCharCount = wcslen(Name) + 1; + m_Name = D3D12MA_NEW_ARRAY(m_Allocator->GetAllocs(), WCHAR, nameCharCount); + memcpy(m_Name, Name, nameCharCount * sizeof(WCHAR)); + } +} + +void PoolPimpl::FreeName() +{ + if (m_Name) + { + const size_t nameCharCount = wcslen(m_Name) + 1; + D3D12MA_DELETE_ARRAY(m_Allocator->GetAllocs(), m_Name, nameCharCount); + m_Name = NULL; + } +} +#endif // _D3D12MA_POOL_PIMPL_FUNCTIONS + + +#ifndef _D3D12MA_PUBLIC_INTERFACE +HRESULT CreateAllocator(const ALLOCATOR_DESC* pDesc, Allocator** ppAllocator) +{ + if (!pDesc || !ppAllocator || !pDesc->pDevice || !pDesc->pAdapter || + !(pDesc->PreferredBlockSize == 0 || (pDesc->PreferredBlockSize >= 16 && pDesc->PreferredBlockSize < 0x10000000000ull))) + { + D3D12MA_ASSERT(0 && "Invalid arguments passed to CreateAllocator."); + return E_INVALIDARG; + } + + D3D12MA_DEBUG_GLOBAL_MUTEX_LOCK + + ALLOCATION_CALLBACKS allocationCallbacks; + SetupAllocationCallbacks(allocationCallbacks, pDesc->pAllocationCallbacks); + + *ppAllocator = D3D12MA_NEW(allocationCallbacks, Allocator)(allocationCallbacks, *pDesc); + HRESULT hr = (*ppAllocator)->m_Pimpl->Init(*pDesc); + if (FAILED(hr)) + { + D3D12MA_DELETE(allocationCallbacks, *ppAllocator); + *ppAllocator = NULL; + } + return hr; +} + +HRESULT CreateVirtualBlock(const VIRTUAL_BLOCK_DESC* pDesc, VirtualBlock** ppVirtualBlock) +{ + if (!pDesc || !ppVirtualBlock) + { + D3D12MA_ASSERT(0 && "Invalid arguments passed to CreateVirtualBlock."); + return E_INVALIDARG; + } + + D3D12MA_DEBUG_GLOBAL_MUTEX_LOCK + + ALLOCATION_CALLBACKS allocationCallbacks; + SetupAllocationCallbacks(allocationCallbacks, pDesc->pAllocationCallbacks); + + *ppVirtualBlock = D3D12MA_NEW(allocationCallbacks, VirtualBlock)(allocationCallbacks, *pDesc); + return S_OK; +} + +#ifndef _D3D12MA_IUNKNOWN_IMPL_FUNCTIONS +HRESULT STDMETHODCALLTYPE IUnknownImpl::QueryInterface(REFIID riid, void** ppvObject) +{ + if (ppvObject == NULL) + return E_POINTER; + if (riid == IID_IUnknown) + { + ++m_RefCount; + *ppvObject = this; + return S_OK; + } + *ppvObject = NULL; + return E_NOINTERFACE; +} + +ULONG STDMETHODCALLTYPE IUnknownImpl::AddRef() +{ + return ++m_RefCount; +} + +ULONG STDMETHODCALLTYPE IUnknownImpl::Release() +{ + D3D12MA_DEBUG_GLOBAL_MUTEX_LOCK + + const uint32_t newRefCount = --m_RefCount; + if (newRefCount == 0) + ReleaseThis(); + return newRefCount; +} +#endif // _D3D12MA_IUNKNOWN_IMPL_FUNCTIONS + +#ifndef _D3D12MA_ALLOCATION_FUNCTIONS +void Allocation::PackedData::SetType(Type type) +{ + const UINT u = (UINT)type; + D3D12MA_ASSERT(u < (1u << 2)); + m_Type = u; +} + +void Allocation::PackedData::SetResourceDimension(D3D12_RESOURCE_DIMENSION resourceDimension) +{ + const UINT u = (UINT)resourceDimension; + D3D12MA_ASSERT(u < (1u << 3)); + m_ResourceDimension = u; +} + +void Allocation::PackedData::SetResourceFlags(D3D12_RESOURCE_FLAGS resourceFlags) +{ + const UINT u = (UINT)resourceFlags; + D3D12MA_ASSERT(u < (1u << 24)); + m_ResourceFlags = u; +} + +void Allocation::PackedData::SetTextureLayout(D3D12_TEXTURE_LAYOUT textureLayout) +{ + const UINT u = (UINT)textureLayout; + D3D12MA_ASSERT(u < (1u << 9)); + m_TextureLayout = u; +} + +UINT64 Allocation::GetOffset() const +{ + switch (m_PackedData.GetType()) + { + case TYPE_COMMITTED: + case TYPE_HEAP: + return 0; + case TYPE_PLACED: + return m_Placed.block->m_pMetadata->GetAllocationOffset(m_Placed.allocHandle); + default: + D3D12MA_ASSERT(0); + return 0; + } +} + +void Allocation::SetResource(ID3D12Resource* pResource) +{ + if (pResource != m_Resource) + { + if (m_Resource) + m_Resource->Release(); + m_Resource = pResource; + if (m_Resource) + m_Resource->AddRef(); + } +} + +ID3D12Heap* Allocation::GetHeap() const +{ + switch (m_PackedData.GetType()) + { + case TYPE_COMMITTED: + return NULL; + case TYPE_PLACED: + return m_Placed.block->GetHeap(); + case TYPE_HEAP: + return m_Heap.heap; + default: + D3D12MA_ASSERT(0); + return 0; + } +} + +void Allocation::SetName(LPCWSTR Name) +{ + FreeName(); + + if (Name) + { + const size_t nameCharCount = wcslen(Name) + 1; + m_Name = D3D12MA_NEW_ARRAY(m_Allocator->GetAllocs(), WCHAR, nameCharCount); + memcpy(m_Name, Name, nameCharCount * sizeof(WCHAR)); + } +} + +void Allocation::ReleaseThis() +{ + if (this == NULL) + { + return; + } + + SAFE_RELEASE(m_Resource); + + switch (m_PackedData.GetType()) + { + case TYPE_COMMITTED: + m_Allocator->FreeCommittedMemory(this); + break; + case TYPE_PLACED: + m_Allocator->FreePlacedMemory(this); + break; + case TYPE_HEAP: + m_Allocator->FreeHeapMemory(this); + break; + } + + FreeName(); + + m_Allocator->GetAllocationObjectAllocator().Free(this); +} + +Allocation::Allocation(AllocatorPimpl* allocator, UINT64 size, UINT64 alignment, BOOL wasZeroInitialized) + : m_Allocator{ allocator }, + m_Size{ size }, + m_Alignment{ alignment }, + m_Resource{ NULL }, + m_pPrivateData{ NULL }, + m_Name{ NULL } +{ + D3D12MA_ASSERT(allocator); + + m_PackedData.SetType(TYPE_COUNT); + m_PackedData.SetResourceDimension(D3D12_RESOURCE_DIMENSION_UNKNOWN); + m_PackedData.SetResourceFlags(D3D12_RESOURCE_FLAG_NONE); + m_PackedData.SetTextureLayout(D3D12_TEXTURE_LAYOUT_UNKNOWN); + m_PackedData.SetWasZeroInitialized(wasZeroInitialized); +} + +void Allocation::InitCommitted(CommittedAllocationList* list) +{ + m_PackedData.SetType(TYPE_COMMITTED); + m_Committed.list = list; + m_Committed.prev = NULL; + m_Committed.next = NULL; +} + +void Allocation::InitPlaced(AllocHandle allocHandle, NormalBlock* block) +{ + m_PackedData.SetType(TYPE_PLACED); + m_Placed.allocHandle = allocHandle; + m_Placed.block = block; +} + +void Allocation::InitHeap(CommittedAllocationList* list, ID3D12Heap* heap) +{ + m_PackedData.SetType(TYPE_HEAP); + m_Heap.list = list; + m_Committed.prev = NULL; + m_Committed.next = NULL; + m_Heap.heap = heap; +} + +void Allocation::SwapBlockAllocation(Allocation* allocation) +{ + D3D12MA_ASSERT(allocation != NULL); + D3D12MA_ASSERT(m_PackedData.GetType() == TYPE_PLACED); + D3D12MA_ASSERT(allocation->m_PackedData.GetType() == TYPE_PLACED); + + D3D12MA_SWAP(m_Resource, allocation->m_Resource); + m_PackedData.SetWasZeroInitialized(allocation->m_PackedData.WasZeroInitialized()); + m_Placed.block->m_pMetadata->SetAllocationPrivateData(m_Placed.allocHandle, allocation); + D3D12MA_SWAP(m_Placed, allocation->m_Placed); + m_Placed.block->m_pMetadata->SetAllocationPrivateData(m_Placed.allocHandle, this); +} + +AllocHandle Allocation::GetAllocHandle() const +{ + switch (m_PackedData.GetType()) + { + case TYPE_COMMITTED: + case TYPE_HEAP: + return (AllocHandle)0; + case TYPE_PLACED: + return m_Placed.allocHandle; + default: + D3D12MA_ASSERT(0); + return (AllocHandle)0; + } +} + +NormalBlock* Allocation::GetBlock() +{ + switch (m_PackedData.GetType()) + { + case TYPE_COMMITTED: + case TYPE_HEAP: + return NULL; + case TYPE_PLACED: + return m_Placed.block; + default: + D3D12MA_ASSERT(0); + return NULL; + } +} + +template +void Allocation::SetResourcePointer(ID3D12Resource* resource, const D3D12_RESOURCE_DESC_T* pResourceDesc) +{ + D3D12MA_ASSERT(m_Resource == NULL && pResourceDesc); + m_Resource = resource; + m_PackedData.SetResourceDimension(pResourceDesc->Dimension); + m_PackedData.SetResourceFlags(pResourceDesc->Flags); + m_PackedData.SetTextureLayout(pResourceDesc->Layout); +} + +void Allocation::FreeName() +{ + if (m_Name) + { + const size_t nameCharCount = wcslen(m_Name) + 1; + D3D12MA_DELETE_ARRAY(m_Allocator->GetAllocs(), m_Name, nameCharCount); + m_Name = NULL; + } +} +#endif // _D3D12MA_ALLOCATION_FUNCTIONS + +#ifndef _D3D12MA_DEFRAGMENTATION_CONTEXT_FUNCTIONS +HRESULT DefragmentationContext::BeginPass(DEFRAGMENTATION_PASS_MOVE_INFO* pPassInfo) +{ + D3D12MA_ASSERT(pPassInfo); + return m_Pimpl->DefragmentPassBegin(*pPassInfo); +} + +HRESULT DefragmentationContext::EndPass(DEFRAGMENTATION_PASS_MOVE_INFO* pPassInfo) +{ + D3D12MA_ASSERT(pPassInfo); + return m_Pimpl->DefragmentPassEnd(*pPassInfo); +} + +void DefragmentationContext::GetStats(DEFRAGMENTATION_STATS* pStats) +{ + D3D12MA_ASSERT(pStats); + m_Pimpl->GetStats(*pStats); +} + +void DefragmentationContext::ReleaseThis() +{ + if (this == NULL) + { + return; + } + + D3D12MA_DELETE(m_Pimpl->GetAllocs(), this); +} + +DefragmentationContext::DefragmentationContext(AllocatorPimpl* allocator, + const DEFRAGMENTATION_DESC& desc, + BlockVector* poolVector) + : m_Pimpl(D3D12MA_NEW(allocator->GetAllocs(), DefragmentationContextPimpl)(allocator, desc, poolVector)) {} + +DefragmentationContext::~DefragmentationContext() +{ + D3D12MA_DELETE(m_Pimpl->GetAllocs(), m_Pimpl); +} +#endif // _D3D12MA_DEFRAGMENTATION_CONTEXT_FUNCTIONS + +#ifndef _D3D12MA_POOL_FUNCTIONS +POOL_DESC Pool::GetDesc() const +{ + return m_Pimpl->GetDesc(); +} + +void Pool::GetStatistics(Statistics* pStats) +{ + D3D12MA_ASSERT(pStats); + D3D12MA_DEBUG_GLOBAL_MUTEX_LOCK + m_Pimpl->GetStatistics(*pStats); +} + +void Pool::CalculateStatistics(DetailedStatistics* pStats) +{ + D3D12MA_ASSERT(pStats); + D3D12MA_DEBUG_GLOBAL_MUTEX_LOCK + m_Pimpl->CalculateStatistics(*pStats); +} + +void Pool::SetName(LPCWSTR Name) +{ + D3D12MA_DEBUG_GLOBAL_MUTEX_LOCK + m_Pimpl->SetName(Name); +} + +LPCWSTR Pool::GetName() const +{ + return m_Pimpl->GetName(); +} + +HRESULT Pool::BeginDefragmentation(const DEFRAGMENTATION_DESC* pDesc, DefragmentationContext** ppContext) +{ + D3D12MA_ASSERT(pDesc && ppContext); + + // Check for support + if (m_Pimpl->GetBlockVector()->GetAlgorithm() & POOL_FLAG_ALGORITHM_LINEAR) + return E_NOINTERFACE; + + AllocatorPimpl* allocator = m_Pimpl->GetAllocator(); + *ppContext = D3D12MA_NEW(allocator->GetAllocs(), DefragmentationContext)(allocator, *pDesc, m_Pimpl->GetBlockVector()); + return S_OK; +} + +void Pool::ReleaseThis() +{ + if (this == NULL) + { + return; + } + + D3D12MA_DELETE(m_Pimpl->GetAllocator()->GetAllocs(), this); +} + +Pool::Pool(Allocator* allocator, const POOL_DESC& desc) + : m_Pimpl(D3D12MA_NEW(allocator->m_Pimpl->GetAllocs(), PoolPimpl)(allocator->m_Pimpl, desc)) {} + +Pool::~Pool() +{ + m_Pimpl->GetAllocator()->UnregisterPool(this, m_Pimpl->GetDesc().HeapProperties.Type); + + D3D12MA_DELETE(m_Pimpl->GetAllocator()->GetAllocs(), m_Pimpl); +} +#endif // _D3D12MA_POOL_FUNCTIONS + +#ifndef _D3D12MA_ALLOCATOR_FUNCTIONS +const D3D12_FEATURE_DATA_D3D12_OPTIONS& Allocator::GetD3D12Options() const +{ + return m_Pimpl->GetD3D12Options(); +} + +BOOL Allocator::IsUMA() const +{ + return m_Pimpl->IsUMA(); +} + +BOOL Allocator::IsCacheCoherentUMA() const +{ + return m_Pimpl->IsCacheCoherentUMA(); +} + +UINT64 Allocator::GetMemoryCapacity(UINT memorySegmentGroup) const +{ + return m_Pimpl->GetMemoryCapacity(memorySegmentGroup); +} + +HRESULT Allocator::CreateResource( + const ALLOCATION_DESC* pAllocDesc, + const D3D12_RESOURCE_DESC* pResourceDesc, + D3D12_RESOURCE_STATES InitialResourceState, + const D3D12_CLEAR_VALUE* pOptimizedClearValue, + Allocation** ppAllocation, + REFIID riidResource, + void** ppvResource) +{ + if (!pAllocDesc || !pResourceDesc || !ppAllocation) + { + D3D12MA_ASSERT(0 && "Invalid arguments passed to Allocator::CreateResource."); + return E_INVALIDARG; + } + D3D12MA_DEBUG_GLOBAL_MUTEX_LOCK + return m_Pimpl->CreateResource( + pAllocDesc, + CREATE_RESOURCE_PARAMS(pResourceDesc, InitialResourceState, pOptimizedClearValue), + ppAllocation, + riidResource, + ppvResource); +} + +#ifdef __ID3D12Device8_INTERFACE_DEFINED__ +HRESULT Allocator::CreateResource2( + const ALLOCATION_DESC* pAllocDesc, + const D3D12_RESOURCE_DESC1* pResourceDesc, + D3D12_RESOURCE_STATES InitialResourceState, + const D3D12_CLEAR_VALUE* pOptimizedClearValue, + Allocation** ppAllocation, + REFIID riidResource, + void** ppvResource) +{ + if (!pAllocDesc || !pResourceDesc || !ppAllocation) + { + D3D12MA_ASSERT(0 && "Invalid arguments passed to Allocator::CreateResource2."); + return E_INVALIDARG; + } + D3D12MA_DEBUG_GLOBAL_MUTEX_LOCK + return m_Pimpl->CreateResource( + pAllocDesc, + CREATE_RESOURCE_PARAMS(pResourceDesc, InitialResourceState, pOptimizedClearValue), + ppAllocation, + riidResource, + ppvResource); +} +#endif // #ifdef __ID3D12Device8_INTERFACE_DEFINED__ + +#ifdef __ID3D12Device10_INTERFACE_DEFINED__ +HRESULT Allocator::CreateResource3( + const ALLOCATION_DESC* pAllocDesc, + const D3D12_RESOURCE_DESC1* pResourceDesc, + D3D12_BARRIER_LAYOUT InitialLayout, + const D3D12_CLEAR_VALUE* pOptimizedClearValue, + UINT32 NumCastableFormats, + DXGI_FORMAT* pCastableFormats, + Allocation** ppAllocation, + REFIID riidResource, + void** ppvResource) +{ + if (!pAllocDesc || !pResourceDesc || !ppAllocation) + { + D3D12MA_ASSERT(0 && "Invalid arguments passed to Allocator::CreateResource3."); + return E_INVALIDARG; + } + D3D12MA_DEBUG_GLOBAL_MUTEX_LOCK + return m_Pimpl->CreateResource( + pAllocDesc, + CREATE_RESOURCE_PARAMS(pResourceDesc, InitialLayout, pOptimizedClearValue, NumCastableFormats, pCastableFormats), + ppAllocation, + riidResource, + ppvResource); +} +#endif // #ifdef __ID3D12Device10_INTERFACE_DEFINED__ + +HRESULT Allocator::AllocateMemory( + const ALLOCATION_DESC* pAllocDesc, + const D3D12_RESOURCE_ALLOCATION_INFO* pAllocInfo, + Allocation** ppAllocation) +{ + if (!ValidateAllocateMemoryParameters(pAllocDesc, pAllocInfo, ppAllocation)) + { + D3D12MA_ASSERT(0 && "Invalid arguments passed to Allocator::AllocateMemory."); + return E_INVALIDARG; + } + D3D12MA_DEBUG_GLOBAL_MUTEX_LOCK + return m_Pimpl->AllocateMemory(pAllocDesc, pAllocInfo, ppAllocation); +} + +HRESULT Allocator::CreateAliasingResource( + Allocation* pAllocation, + UINT64 AllocationLocalOffset, + const D3D12_RESOURCE_DESC* pResourceDesc, + D3D12_RESOURCE_STATES InitialResourceState, + const D3D12_CLEAR_VALUE* pOptimizedClearValue, + REFIID riidResource, + void** ppvResource) +{ + if (!pAllocation || !pResourceDesc || !ppvResource) + { + D3D12MA_ASSERT(0 && "Invalid arguments passed to Allocator::CreateAliasingResource."); + return E_INVALIDARG; + } + D3D12MA_DEBUG_GLOBAL_MUTEX_LOCK + return m_Pimpl->CreateAliasingResource( + pAllocation, + AllocationLocalOffset, + CREATE_RESOURCE_PARAMS(pResourceDesc, InitialResourceState, pOptimizedClearValue), + riidResource, + ppvResource); +} + +#ifdef __ID3D12Device8_INTERFACE_DEFINED__ +HRESULT Allocator::CreateAliasingResource1( + Allocation* pAllocation, + UINT64 AllocationLocalOffset, + const D3D12_RESOURCE_DESC1* pResourceDesc, + D3D12_RESOURCE_STATES InitialResourceState, + const D3D12_CLEAR_VALUE* pOptimizedClearValue, + REFIID riidResource, + void** ppvResource) +{ + if (!pAllocation || !pResourceDesc || !ppvResource) + { + D3D12MA_ASSERT(0 && "Invalid arguments passed to Allocator::CreateAliasingResource."); + return E_INVALIDARG; + } + D3D12MA_DEBUG_GLOBAL_MUTEX_LOCK + return m_Pimpl->CreateAliasingResource( + pAllocation, + AllocationLocalOffset, + CREATE_RESOURCE_PARAMS(pResourceDesc, InitialResourceState, pOptimizedClearValue), + riidResource, + ppvResource); +} +#endif // #ifdef __ID3D12Device8_INTERFACE_DEFINED__ + +#ifdef __ID3D12Device10_INTERFACE_DEFINED__ +HRESULT Allocator::CreateAliasingResource2( + Allocation* pAllocation, + UINT64 AllocationLocalOffset, + const D3D12_RESOURCE_DESC1* pResourceDesc, + D3D12_BARRIER_LAYOUT InitialLayout, + const D3D12_CLEAR_VALUE* pOptimizedClearValue, + UINT32 NumCastableFormats, + DXGI_FORMAT* pCastableFormats, + REFIID riidResource, + void** ppvResource) +{ + if (!pAllocation || !pResourceDesc || !ppvResource) + { + D3D12MA_ASSERT(0 && "Invalid arguments passed to Allocator::CreateAliasingResource."); + return E_INVALIDARG; + } + D3D12MA_DEBUG_GLOBAL_MUTEX_LOCK + return m_Pimpl->CreateAliasingResource( + pAllocation, + AllocationLocalOffset, + CREATE_RESOURCE_PARAMS(pResourceDesc, InitialLayout, pOptimizedClearValue, NumCastableFormats, pCastableFormats), + riidResource, + ppvResource); +} +#endif // #ifdef __ID3D12Device10_INTERFACE_DEFINED__ + +HRESULT Allocator::CreatePool( + const POOL_DESC* pPoolDesc, + Pool** ppPool) +{ + if (!pPoolDesc || !ppPool || + (pPoolDesc->MaxBlockCount > 0 && pPoolDesc->MaxBlockCount < pPoolDesc->MinBlockCount) || + (pPoolDesc->MinAllocationAlignment > 0 && !IsPow2(pPoolDesc->MinAllocationAlignment))) + { + D3D12MA_ASSERT(0 && "Invalid arguments passed to Allocator::CreatePool."); + return E_INVALIDARG; + } + if (!m_Pimpl->HeapFlagsFulfillResourceHeapTier(pPoolDesc->HeapFlags)) + { + D3D12MA_ASSERT(0 && "Invalid pPoolDesc->HeapFlags passed to Allocator::CreatePool. Did you forget to handle ResourceHeapTier=1?"); + return E_INVALIDARG; + } + D3D12MA_DEBUG_GLOBAL_MUTEX_LOCK + * ppPool = D3D12MA_NEW(m_Pimpl->GetAllocs(), Pool)(this, *pPoolDesc); + HRESULT hr = (*ppPool)->m_Pimpl->Init(); + if (SUCCEEDED(hr)) + { + m_Pimpl->RegisterPool(*ppPool, pPoolDesc->HeapProperties.Type); + } + else + { + D3D12MA_DELETE(m_Pimpl->GetAllocs(), *ppPool); + *ppPool = NULL; + } + return hr; +} + +void Allocator::SetCurrentFrameIndex(UINT frameIndex) +{ + D3D12MA_DEBUG_GLOBAL_MUTEX_LOCK + m_Pimpl->SetCurrentFrameIndex(frameIndex); +} + +void Allocator::GetBudget(Budget* pLocalBudget, Budget* pNonLocalBudget) +{ + if (pLocalBudget == NULL && pNonLocalBudget == NULL) + { + return; + } + D3D12MA_DEBUG_GLOBAL_MUTEX_LOCK + m_Pimpl->GetBudget(pLocalBudget, pNonLocalBudget); +} + +void Allocator::CalculateStatistics(TotalStatistics* pStats) +{ + D3D12MA_ASSERT(pStats); + D3D12MA_DEBUG_GLOBAL_MUTEX_LOCK + m_Pimpl->CalculateStatistics(*pStats); +} + +void Allocator::BuildStatsString(WCHAR** ppStatsString, BOOL DetailedMap) const +{ + D3D12MA_ASSERT(ppStatsString); + D3D12MA_DEBUG_GLOBAL_MUTEX_LOCK + m_Pimpl->BuildStatsString(ppStatsString, DetailedMap); +} + +void Allocator::FreeStatsString(WCHAR* pStatsString) const +{ + if (pStatsString != NULL) + { + D3D12MA_DEBUG_GLOBAL_MUTEX_LOCK + m_Pimpl->FreeStatsString(pStatsString); + } +} + +void Allocator::BeginDefragmentation(const DEFRAGMENTATION_DESC* pDesc, DefragmentationContext** ppContext) +{ + D3D12MA_ASSERT(pDesc && ppContext); + + *ppContext = D3D12MA_NEW(m_Pimpl->GetAllocs(), DefragmentationContext)(m_Pimpl, *pDesc, NULL); +} + +void Allocator::ReleaseThis() +{ + // Copy is needed because otherwise we would call destructor and invalidate the structure with callbacks before using it to free memory. + const ALLOCATION_CALLBACKS allocationCallbacksCopy = m_Pimpl->GetAllocs(); + D3D12MA_DELETE(allocationCallbacksCopy, this); +} + +Allocator::Allocator(const ALLOCATION_CALLBACKS& allocationCallbacks, const ALLOCATOR_DESC& desc) + : m_Pimpl(D3D12MA_NEW(allocationCallbacks, AllocatorPimpl)(allocationCallbacks, desc)) {} + +Allocator::~Allocator() +{ + D3D12MA_DELETE(m_Pimpl->GetAllocs(), m_Pimpl); +} +#endif // _D3D12MA_ALLOCATOR_FUNCTIONS + +#ifndef _D3D12MA_VIRTUAL_BLOCK_FUNCTIONS +BOOL VirtualBlock::IsEmpty() const +{ + D3D12MA_DEBUG_GLOBAL_MUTEX_LOCK + return m_Pimpl->m_Metadata->IsEmpty() ? TRUE : FALSE; +} + +void VirtualBlock::GetAllocationInfo(VirtualAllocation allocation, VIRTUAL_ALLOCATION_INFO* pInfo) const +{ + D3D12MA_ASSERT(allocation.AllocHandle != (AllocHandle)0 && pInfo); + + D3D12MA_DEBUG_GLOBAL_MUTEX_LOCK + m_Pimpl->m_Metadata->GetAllocationInfo(allocation.AllocHandle, *pInfo); +} + +HRESULT VirtualBlock::Allocate(const VIRTUAL_ALLOCATION_DESC* pDesc, VirtualAllocation* pAllocation, UINT64* pOffset) +{ + if (!pDesc || !pAllocation || pDesc->Size == 0 || !IsPow2(pDesc->Alignment)) + { + D3D12MA_ASSERT(0 && "Invalid arguments passed to VirtualBlock::Allocate."); + return E_INVALIDARG; + } + + D3D12MA_DEBUG_GLOBAL_MUTEX_LOCK + + const UINT64 alignment = pDesc->Alignment != 0 ? pDesc->Alignment : 1; + AllocationRequest allocRequest = {}; + if (m_Pimpl->m_Metadata->CreateAllocationRequest( + pDesc->Size, + alignment, + pDesc->Flags & VIRTUAL_ALLOCATION_FLAG_UPPER_ADDRESS, + pDesc->Flags & VIRTUAL_ALLOCATION_FLAG_STRATEGY_MASK, + &allocRequest)) + { + m_Pimpl->m_Metadata->Alloc(allocRequest, pDesc->Size, pDesc->pPrivateData); + D3D12MA_HEAVY_ASSERT(m_Pimpl->m_Metadata->Validate()); + pAllocation->AllocHandle = allocRequest.allocHandle; + + if (pOffset) + *pOffset = m_Pimpl->m_Metadata->GetAllocationOffset(allocRequest.allocHandle); + return S_OK; + } + + pAllocation->AllocHandle = (AllocHandle)0; + if (pOffset) + *pOffset = UINT64_MAX; + + return E_OUTOFMEMORY; +} + +void VirtualBlock::FreeAllocation(VirtualAllocation allocation) +{ + if (allocation.AllocHandle == (AllocHandle)0) + return; + + D3D12MA_DEBUG_GLOBAL_MUTEX_LOCK + + m_Pimpl->m_Metadata->Free(allocation.AllocHandle); + D3D12MA_HEAVY_ASSERT(m_Pimpl->m_Metadata->Validate()); +} + +void VirtualBlock::Clear() +{ + D3D12MA_DEBUG_GLOBAL_MUTEX_LOCK + + m_Pimpl->m_Metadata->Clear(); + D3D12MA_HEAVY_ASSERT(m_Pimpl->m_Metadata->Validate()); +} + +void VirtualBlock::SetAllocationPrivateData(VirtualAllocation allocation, void* pPrivateData) +{ + D3D12MA_ASSERT(allocation.AllocHandle != (AllocHandle)0); + + D3D12MA_DEBUG_GLOBAL_MUTEX_LOCK + m_Pimpl->m_Metadata->SetAllocationPrivateData(allocation.AllocHandle, pPrivateData); +} + +void VirtualBlock::GetStatistics(Statistics* pStats) const +{ + D3D12MA_ASSERT(pStats); + D3D12MA_DEBUG_GLOBAL_MUTEX_LOCK + D3D12MA_HEAVY_ASSERT(m_Pimpl->m_Metadata->Validate()); + ClearStatistics(*pStats); + m_Pimpl->m_Metadata->AddStatistics(*pStats); +} + +void VirtualBlock::CalculateStatistics(DetailedStatistics* pStats) const +{ + D3D12MA_ASSERT(pStats); + D3D12MA_DEBUG_GLOBAL_MUTEX_LOCK + D3D12MA_HEAVY_ASSERT(m_Pimpl->m_Metadata->Validate()); + ClearDetailedStatistics(*pStats); + m_Pimpl->m_Metadata->AddDetailedStatistics(*pStats); +} + +void VirtualBlock::BuildStatsString(WCHAR** ppStatsString) const +{ + D3D12MA_ASSERT(ppStatsString); + + D3D12MA_DEBUG_GLOBAL_MUTEX_LOCK + + StringBuilder sb(m_Pimpl->m_AllocationCallbacks); + { + JsonWriter json(m_Pimpl->m_AllocationCallbacks, sb); + D3D12MA_HEAVY_ASSERT(m_Pimpl->m_Metadata->Validate()); + json.BeginObject(); + m_Pimpl->m_Metadata->WriteAllocationInfoToJson(json); + json.EndObject(); + } // Scope for JsonWriter + + const size_t length = sb.GetLength(); + WCHAR* result = AllocateArray(m_Pimpl->m_AllocationCallbacks, length + 1); + memcpy(result, sb.GetData(), length * sizeof(WCHAR)); + result[length] = L'\0'; + *ppStatsString = result; +} + +void VirtualBlock::FreeStatsString(WCHAR* pStatsString) const +{ + if (pStatsString != NULL) + { + D3D12MA_DEBUG_GLOBAL_MUTEX_LOCK + D3D12MA::Free(m_Pimpl->m_AllocationCallbacks, pStatsString); + } +} + +void VirtualBlock::ReleaseThis() +{ + // Copy is needed because otherwise we would call destructor and invalidate the structure with callbacks before using it to free memory. + const ALLOCATION_CALLBACKS allocationCallbacksCopy = m_Pimpl->m_AllocationCallbacks; + D3D12MA_DELETE(allocationCallbacksCopy, this); +} + +VirtualBlock::VirtualBlock(const ALLOCATION_CALLBACKS& allocationCallbacks, const VIRTUAL_BLOCK_DESC& desc) + : m_Pimpl(D3D12MA_NEW(allocationCallbacks, VirtualBlockPimpl)(allocationCallbacks, desc)) {} + +VirtualBlock::~VirtualBlock() +{ + // THIS IS AN IMPORTANT ASSERT! + // Hitting it means you have some memory leak - unreleased allocations in this virtual block. + D3D12MA_ASSERT(m_Pimpl->m_Metadata->IsEmpty() && "Some allocations were not freed before destruction of this virtual block!"); + + D3D12MA_DELETE(m_Pimpl->m_AllocationCallbacks, m_Pimpl); +} +#endif // _D3D12MA_VIRTUAL_BLOCK_FUNCTIONS +#endif // _D3D12MA_PUBLIC_INTERFACE +} // namespace D3D12MA + +#if defined(__clang__) || defined(__GNUC__) +#pragma GCC diagnostic pop +#endif diff --git a/src/3rdparty/D3D12MemoryAllocator/D3D12MemAlloc.h b/src/3rdparty/D3D12MemoryAllocator/D3D12MemAlloc.h new file mode 100644 index 00000000..d80dcb1e --- /dev/null +++ b/src/3rdparty/D3D12MemoryAllocator/D3D12MemAlloc.h @@ -0,0 +1,2632 @@ +// +// Copyright (c) 2019-2022 Advanced Micro Devices, Inc. All rights reserved. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. +// + +#pragma once + +/** \mainpage D3D12 Memory Allocator + +Version 2.1.0-development (2022-12-15) + +Copyright (c) 2019-2022 Advanced Micro Devices, Inc. All rights reserved. \n +License: MIT + +Documentation of all members: D3D12MemAlloc.h + +\section main_table_of_contents Table of contents + +- \subpage quick_start + - [Project setup](@ref quick_start_project_setup) + - [Creating resources](@ref quick_start_creating_resources) + - [Resource reference counting](@ref quick_start_resource_reference_counting) + - [Mapping memory](@ref quick_start_mapping_memory) +- \subpage custom_pools +- \subpage defragmentation +- \subpage statistics +- \subpage resource_aliasing +- \subpage linear_algorithm +- \subpage virtual_allocator +- \subpage configuration + - [Custom CPU memory allocator](@ref custom_memory_allocator) + - [Debug margins](@ref debug_margins) +- \subpage general_considerations + - [Thread safety](@ref general_considerations_thread_safety) + - [Versioning and compatibility](@ref general_considerations_versioning_and_compatibility) + - [Features not supported](@ref general_considerations_features_not_supported) + +\section main_see_also See also + +- [Product page on GPUOpen](https://gpuopen.com/gaming-product/d3d12-memory-allocator/) +- [Source repository on GitHub](https://github.com/GPUOpen-LibrariesAndSDKs/D3D12MemoryAllocator) +*/ + +// If using this library on a platform different than Windows PC or want to use different version of DXGI, +// you should include D3D12-compatible headers before this library on your own and define this macro. +#ifndef D3D12MA_D3D12_HEADERS_ALREADY_INCLUDED + #include + #include +#endif + +// Define this macro to 0 to disable usage of DXGI 1.4 (needed for IDXGIAdapter3 and query for memory budget). +#ifndef D3D12MA_DXGI_1_4 + #ifdef __IDXGIAdapter3_INTERFACE_DEFINED__ + #define D3D12MA_DXGI_1_4 1 + #else + #define D3D12MA_DXGI_1_4 0 + #endif +#endif + +/* +When defined to value other than 0, the library will try to use +D3D12_SMALL_RESOURCE_PLACEMENT_ALIGNMENT or D3D12_SMALL_MSAA_RESOURCE_PLACEMENT_ALIGNMENT +for created textures when possible, which can save memory because some small textures +may get their alignment 4K and their size a multiply of 4K instead of 64K. + +#define D3D12MA_USE_SMALL_RESOURCE_PLACEMENT_ALIGNMENT 0 + Disables small texture alignment. +#define D3D12MA_USE_SMALL_RESOURCE_PLACEMENT_ALIGNMENT 1 + Enables conservative algorithm that will use small alignment only for some textures + that are surely known to support it. +#define D3D12MA_USE_SMALL_RESOURCE_PLACEMENT_ALIGNMENT 2 + Enables query for small alignment to D3D12 (based on Microsoft sample) which will + enable small alignment for more textures, but will also generate D3D Debug Layer + error #721 on call to ID3D12Device::GetResourceAllocationInfo, which you should just + ignore. +*/ +#ifndef D3D12MA_USE_SMALL_RESOURCE_PLACEMENT_ALIGNMENT + #define D3D12MA_USE_SMALL_RESOURCE_PLACEMENT_ALIGNMENT 1 +#endif + +/// \cond INTERNAL + +#define D3D12MA_CLASS_NO_COPY(className) \ + private: \ + className(const className&) = delete; \ + className(className&&) = delete; \ + className& operator=(const className&) = delete; \ + className& operator=(className&&) = delete; + +// To be used with MAKE_HRESULT to define custom error codes. +#define FACILITY_D3D12MA 3542 + +/* +If providing your own implementation, you need to implement a subset of std::atomic. +*/ +#if !defined(D3D12MA_ATOMIC_UINT32) || !defined(D3D12MA_ATOMIC_UINT64) + #include +#endif + +#ifndef D3D12MA_ATOMIC_UINT32 + #define D3D12MA_ATOMIC_UINT32 std::atomic +#endif + +#ifndef D3D12MA_ATOMIC_UINT64 + #define D3D12MA_ATOMIC_UINT64 std::atomic +#endif + +#ifdef D3D12MA_EXPORTS + #define D3D12MA_API __declspec(dllexport) +#elif defined(D3D12MA_IMPORTS) + #define D3D12MA_API __declspec(dllimport) +#else + #define D3D12MA_API +#endif + +// Forward declaration if ID3D12ProtectedResourceSession is not defined inside the headers (older SDK, pre ID3D12Device4) +struct ID3D12ProtectedResourceSession; + +// Define this enum even if SDK doesn't provide it, to simplify the API. +#ifndef __ID3D12Device1_INTERFACE_DEFINED__ +typedef enum D3D12_RESIDENCY_PRIORITY +{ + D3D12_RESIDENCY_PRIORITY_MINIMUM = 0x28000000, + D3D12_RESIDENCY_PRIORITY_LOW = 0x50000000, + D3D12_RESIDENCY_PRIORITY_NORMAL = 0x78000000, + D3D12_RESIDENCY_PRIORITY_HIGH = 0xa0010000, + D3D12_RESIDENCY_PRIORITY_MAXIMUM = 0xc8000000 +} D3D12_RESIDENCY_PRIORITY; +#endif + +namespace D3D12MA +{ +class D3D12MA_API IUnknownImpl : public IUnknown +{ +public: + virtual ~IUnknownImpl() = default; + HRESULT STDMETHODCALLTYPE QueryInterface(REFIID riid, void** ppvObject) override; + ULONG STDMETHODCALLTYPE AddRef() override; + ULONG STDMETHODCALLTYPE Release() override; +protected: + virtual void ReleaseThis() { delete this; } +private: + D3D12MA_ATOMIC_UINT32 m_RefCount = {1}; +}; +} // namespace D3D12MA + +/// \endcond + +namespace D3D12MA +{ + +/// \cond INTERNAL +class DefragmentationContextPimpl; +class AllocatorPimpl; +class PoolPimpl; +class NormalBlock; +class BlockVector; +class CommittedAllocationList; +class JsonWriter; +class VirtualBlockPimpl; +/// \endcond + +class Pool; +class Allocator; +struct Statistics; +struct DetailedStatistics; +struct TotalStatistics; + +/// \brief Unique identifier of single allocation done inside the memory heap. +typedef UINT64 AllocHandle; + +/// Pointer to custom callback function that allocates CPU memory. +using ALLOCATE_FUNC_PTR = void* (*)(size_t Size, size_t Alignment, void* pPrivateData); +/** +\brief Pointer to custom callback function that deallocates CPU memory. + +`pMemory = null` should be accepted and ignored. +*/ +using FREE_FUNC_PTR = void (*)(void* pMemory, void* pPrivateData); + +/// Custom callbacks to CPU memory allocation functions. +struct ALLOCATION_CALLBACKS +{ + /// %Allocation function. + ALLOCATE_FUNC_PTR pAllocate; + /// Dellocation function. + FREE_FUNC_PTR pFree; + /// Custom data that will be passed to allocation and deallocation functions as `pUserData` parameter. + void* pPrivateData; +}; + + +/// \brief Bit flags to be used with ALLOCATION_DESC::Flags. +enum ALLOCATION_FLAGS +{ + /// Zero + ALLOCATION_FLAG_NONE = 0, + + /** + Set this flag if the allocation should have its own dedicated memory allocation (committed resource with implicit heap). + + Use it for special, big resources, like fullscreen textures used as render targets. + + - When used with functions like D3D12MA::Allocator::CreateResource, it will use `ID3D12Device::CreateCommittedResource`, + so the created allocation will contain a resource (D3D12MA::Allocation::GetResource() `!= NULL`) but will not have + a heap (D3D12MA::Allocation::GetHeap() `== NULL`), as the heap is implicit. + - When used with raw memory allocation like D3D12MA::Allocator::AllocateMemory, it will use `ID3D12Device::CreateHeap`, + so the created allocation will contain a heap (D3D12MA::Allocation::GetHeap() `!= NULL`) and its offset will always be 0. + */ + ALLOCATION_FLAG_COMMITTED = 0x1, + + /** + Set this flag to only try to allocate from existing memory heaps and never create new such heap. + + If new allocation cannot be placed in any of the existing heaps, allocation + fails with `E_OUTOFMEMORY` error. + + You should not use D3D12MA::ALLOCATION_FLAG_COMMITTED and + D3D12MA::ALLOCATION_FLAG_NEVER_ALLOCATE at the same time. It makes no sense. + */ + ALLOCATION_FLAG_NEVER_ALLOCATE = 0x2, + + /** Create allocation only if additional memory required for it, if any, won't exceed + memory budget. Otherwise return `E_OUTOFMEMORY`. + */ + ALLOCATION_FLAG_WITHIN_BUDGET = 0x4, + + /** Allocation will be created from upper stack in a double stack pool. + + This flag is only allowed for custom pools created with #POOL_FLAG_ALGORITHM_LINEAR flag. + */ + ALLOCATION_FLAG_UPPER_ADDRESS = 0x8, + + /** Set this flag if the allocated memory will have aliasing resources. + + Use this when calling D3D12MA::Allocator::CreateResource() and similar to + guarantee creation of explicit heap for desired allocation and prevent it from using `CreateCommittedResource`, + so that new allocation object will always have `allocation->GetHeap() != NULL`. + */ + ALLOCATION_FLAG_CAN_ALIAS = 0x10, + + /** Allocation strategy that chooses smallest possible free range for the allocation + to minimize memory usage and fragmentation, possibly at the expense of allocation time. + */ + ALLOCATION_FLAG_STRATEGY_MIN_MEMORY = 0x00010000, + + /** Allocation strategy that chooses first suitable free range for the allocation - + not necessarily in terms of the smallest offset but the one that is easiest and fastest to find + to minimize allocation time, possibly at the expense of allocation quality. + */ + ALLOCATION_FLAG_STRATEGY_MIN_TIME = 0x00020000, + + /** Allocation strategy that chooses always the lowest offset in available space. + This is not the most efficient strategy but achieves highly packed data. + Used internally by defragmentation, not recomended in typical usage. + */ + ALLOCATION_FLAG_STRATEGY_MIN_OFFSET = 0x0004000, + + /// Alias to #ALLOCATION_FLAG_STRATEGY_MIN_MEMORY. + ALLOCATION_FLAG_STRATEGY_BEST_FIT = ALLOCATION_FLAG_STRATEGY_MIN_MEMORY, + /// Alias to #ALLOCATION_FLAG_STRATEGY_MIN_TIME. + ALLOCATION_FLAG_STRATEGY_FIRST_FIT = ALLOCATION_FLAG_STRATEGY_MIN_TIME, + + /// A bit mask to extract only `STRATEGY` bits from entire set of flags. + ALLOCATION_FLAG_STRATEGY_MASK = + ALLOCATION_FLAG_STRATEGY_MIN_MEMORY | + ALLOCATION_FLAG_STRATEGY_MIN_TIME | + ALLOCATION_FLAG_STRATEGY_MIN_OFFSET, +}; + +/// \brief Parameters of created D3D12MA::Allocation object. To be used with Allocator::CreateResource. +struct ALLOCATION_DESC +{ + /// Flags. + ALLOCATION_FLAGS Flags; + /** \brief The type of memory heap where the new allocation should be placed. + + It must be one of: `D3D12_HEAP_TYPE_DEFAULT`, `D3D12_HEAP_TYPE_UPLOAD`, `D3D12_HEAP_TYPE_READBACK`. + + When D3D12MA::ALLOCATION_DESC::CustomPool != NULL this member is ignored. + */ + D3D12_HEAP_TYPE HeapType; + /** \brief Additional heap flags to be used when allocating memory. + + In most cases it can be 0. + + - If you use D3D12MA::Allocator::CreateResource(), you don't need to care. + Necessary flag `D3D12_HEAP_FLAG_ALLOW_ONLY_BUFFERS`, `D3D12_HEAP_FLAG_ALLOW_ONLY_NON_RT_DS_TEXTURES`, + or `D3D12_HEAP_FLAG_ALLOW_ONLY_RT_DS_TEXTURES` is added automatically. + - If you use D3D12MA::Allocator::AllocateMemory(), you should specify one of those `ALLOW_ONLY` flags. + Except when you validate that D3D12MA::Allocator::GetD3D12Options()`.ResourceHeapTier == D3D12_RESOURCE_HEAP_TIER_1` - + then you can leave it 0. + - You can specify additional flags if needed. Then the memory will always be allocated as + separate block using `D3D12Device::CreateCommittedResource` or `CreateHeap`, not as part of an existing larget block. + + When D3D12MA::ALLOCATION_DESC::CustomPool != NULL this member is ignored. + */ + D3D12_HEAP_FLAGS ExtraHeapFlags; + /** \brief Custom pool to place the new resource in. Optional. + + When not NULL, the resource will be created inside specified custom pool. + */ + Pool* CustomPool; + /// Custom general-purpose pointer that will be stored in D3D12MA::Allocation. + void* pPrivateData; +}; + +/** \brief Calculated statistics of memory usage e.g. in a specific memory heap type, +memory segment group, custom pool, or total. + +These are fast to calculate. +See functions: D3D12MA::Allocator::GetBudget(), D3D12MA::Pool::GetStatistics(). +*/ +struct Statistics +{ + /** \brief Number of D3D12 memory blocks allocated - `ID3D12Heap` objects and committed resources. + */ + UINT BlockCount; + /** \brief Number of D3D12MA::Allocation objects allocated. + + Committed allocations have their own blocks, so each one adds 1 to `AllocationCount` as well as `BlockCount`. + */ + UINT AllocationCount; + /** \brief Number of bytes allocated in memory blocks. + */ + UINT64 BlockBytes; + /** \brief Total number of bytes occupied by all D3D12MA::Allocation objects. + + Always less or equal than `BlockBytes`. + Difference `(BlockBytes - AllocationBytes)` is the amount of memory allocated from D3D12 + but unused by any D3D12MA::Allocation. + */ + UINT64 AllocationBytes; +}; + +/** \brief More detailed statistics than D3D12MA::Statistics. + +These are slower to calculate. Use for debugging purposes. +See functions: D3D12MA::Allocator::CalculateStatistics(), D3D12MA::Pool::CalculateStatistics(). + +Averages are not provided because they can be easily calculated as: + +\code +UINT64 AllocationSizeAvg = DetailedStats.Statistics.AllocationBytes / detailedStats.Statistics.AllocationCount; +UINT64 UnusedBytes = DetailedStats.Statistics.BlockBytes - DetailedStats.Statistics.AllocationBytes; +UINT64 UnusedRangeSizeAvg = UnusedBytes / DetailedStats.UnusedRangeCount; +\endcode +*/ +struct DetailedStatistics +{ + /// Basic statistics. + Statistics Stats; + /// Number of free ranges of memory between allocations. + UINT UnusedRangeCount; + /// Smallest allocation size. `UINT64_MAX` if there are 0 allocations. + UINT64 AllocationSizeMin; + /// Largest allocation size. 0 if there are 0 allocations. + UINT64 AllocationSizeMax; + /// Smallest empty range size. `UINT64_MAX` if there are 0 empty ranges. + UINT64 UnusedRangeSizeMin; + /// Largest empty range size. 0 if there are 0 empty ranges. + UINT64 UnusedRangeSizeMax; +}; + +/** \brief General statistics from current state of the allocator - +total memory usage across all memory heaps and segments. + +These are slower to calculate. Use for debugging purposes. +See function D3D12MA::Allocator::CalculateStatistics(). +*/ +struct TotalStatistics +{ + /** \brief One element for each type of heap located at the following indices: + + - 0 = `D3D12_HEAP_TYPE_DEFAULT` + - 1 = `D3D12_HEAP_TYPE_UPLOAD` + - 2 = `D3D12_HEAP_TYPE_READBACK` + - 3 = `D3D12_HEAP_TYPE_CUSTOM` + */ + DetailedStatistics HeapType[4]; + /** \brief One element for each memory segment group located at the following indices: + + - 0 = `DXGI_MEMORY_SEGMENT_GROUP_LOCAL` + - 1 = `DXGI_MEMORY_SEGMENT_GROUP_NON_LOCAL` + + Meaning of these segment groups is: + + - When `IsUMA() == FALSE` (discrete graphics card): + - `DXGI_MEMORY_SEGMENT_GROUP_LOCAL` (index 0) represents GPU memory + (resources allocated in `D3D12_HEAP_TYPE_DEFAULT` or `D3D12_MEMORY_POOL_L1`). + - `DXGI_MEMORY_SEGMENT_GROUP_NON_LOCAL` (index 1) represents system memory + (resources allocated in `D3D12_HEAP_TYPE_UPLOAD`, `D3D12_HEAP_TYPE_READBACK`, or `D3D12_MEMORY_POOL_L0`). + - When `IsUMA() == TRUE` (integrated graphics chip): + - `DXGI_MEMORY_SEGMENT_GROUP_LOCAL` = (index 0) represents memory shared for all the resources. + - `DXGI_MEMORY_SEGMENT_GROUP_NON_LOCAL` = (index 1) is unused and always 0. + */ + DetailedStatistics MemorySegmentGroup[2]; + /// Total statistics from all memory allocated from D3D12. + DetailedStatistics Total; +}; + +/** \brief %Statistics of current memory usage and available budget for a specific memory segment group. + +These are fast to calculate. See function D3D12MA::Allocator::GetBudget(). +*/ +struct Budget +{ + /** \brief %Statistics fetched from the library. + */ + Statistics Stats; + /** \brief Estimated current memory usage of the program. + + Fetched from system using `IDXGIAdapter3::QueryVideoMemoryInfo` if possible. + + It might be different than `BlockBytes` (usually higher) due to additional implicit objects + also occupying the memory, like swapchain, pipeline state objects, descriptor heaps, command lists, or + heaps and resources allocated outside of this library, if any. + */ + UINT64 UsageBytes; + /** \brief Estimated amount of memory available to the program. + + Fetched from system using `IDXGIAdapter3::QueryVideoMemoryInfo` if possible. + + It might be different (most probably smaller) than memory capacity returned + by D3D12MA::Allocator::GetMemoryCapacity() due to factors + external to the program, decided by the operating system. + Difference `BudgetBytes - UsageBytes` is the amount of additional memory that can probably + be allocated without problems. Exceeding the budget may result in various problems. + */ + UINT64 BudgetBytes; +}; + + +/// \brief Represents single memory allocation done inside VirtualBlock. +struct D3D12MA_API VirtualAllocation +{ + /// \brief Unique idenitfier of current allocation. 0 means null/invalid. + AllocHandle AllocHandle; +}; + +/** \brief Represents single memory allocation. + +It may be either implicit memory heap dedicated to a single resource or a +specific region of a bigger heap plus unique offset. + +To create such object, fill structure D3D12MA::ALLOCATION_DESC and call function +Allocator::CreateResource. + +The object remembers size and some other information. +To retrieve this information, use methods of this class. + +The object also remembers `ID3D12Resource` and "owns" a reference to it, +so it calls `%Release()` on the resource when destroyed. +*/ +class D3D12MA_API Allocation : public IUnknownImpl +{ +public: + /** \brief Returns offset in bytes from the start of memory heap. + + You usually don't need to use this offset. If you create a buffer or a texture together with the allocation using function + D3D12MA::Allocator::CreateResource, functions that operate on that resource refer to the beginning of the resource, + not entire memory heap. + + If the Allocation represents committed resource with implicit heap, returns 0. + */ + UINT64 GetOffset() const; + + /// Returns alignment that resource was created with. + UINT64 GetAlignment() const { return m_Alignment; } + + /** \brief Returns size in bytes of the allocation. + + - If you created a buffer or a texture together with the allocation using function D3D12MA::Allocator::CreateResource, + this is the size of the resource returned by `ID3D12Device::GetResourceAllocationInfo`. + - For allocations made out of bigger memory blocks, this also is the size of the memory region assigned exclusively to this allocation. + - For resources created as committed, this value may not be accurate. DirectX implementation may optimize memory usage internally + so that you may even observe regions of `ID3D12Resource::GetGPUVirtualAddress()` + Allocation::GetSize() to overlap in memory and still work correctly. + */ + UINT64 GetSize() const { return m_Size; } + + /** \brief Returns D3D12 resource associated with this object. + + Calling this method doesn't increment resource's reference counter. + */ + ID3D12Resource* GetResource() const { return m_Resource; } + + /// Releases the resource currently pointed by the allocation (if any), sets it to new one, incrementing its reference counter (if not null). + void SetResource(ID3D12Resource* pResource); + + /** \brief Returns memory heap that the resource is created in. + + If the Allocation represents committed resource with implicit heap, returns NULL. + */ + ID3D12Heap* GetHeap() const; + + /// Changes custom pointer for an allocation to a new value. + void SetPrivateData(void* pPrivateData) { m_pPrivateData = pPrivateData; } + + /// Get custom pointer associated with the allocation. + void* GetPrivateData() const { return m_pPrivateData; } + + /** \brief Associates a name with the allocation object. This name is for use in debug diagnostics and tools. + + Internal copy of the string is made, so the memory pointed by the argument can be + changed of freed immediately after this call. + + `Name` can be null. + */ + void SetName(LPCWSTR Name); + + /** \brief Returns the name associated with the allocation object. + + Returned string points to an internal copy. + + If no name was associated with the allocation, returns null. + */ + LPCWSTR GetName() const { return m_Name; } + + /** \brief Returns `TRUE` if the memory of the allocation was filled with zeros when the allocation was created. + + Returns `TRUE` only if the allocator is sure that the entire memory where the + allocation was created was filled with zeros at the moment the allocation was made. + + Returns `FALSE` if the memory could potentially contain garbage data. + If it's a render-target or depth-stencil texture, it then needs proper + initialization with `ClearRenderTargetView`, `ClearDepthStencilView`, `DiscardResource`, + or a copy operation, as described on page + "ID3D12Device::CreatePlacedResource method - Notes on the required resource initialization" in Microsoft documentation. + Please note that rendering a fullscreen triangle or quad to the texture as + a render target is not a proper way of initialization! + + See also articles: + + - "Coming to DirectX 12: More control over memory allocation" on DirectX Developer Blog + - ["Initializing DX12 Textures After Allocation and Aliasing"](https://asawicki.info/news_1724_initializing_dx12_textures_after_allocation_and_aliasing). + */ + BOOL WasZeroInitialized() const { return m_PackedData.WasZeroInitialized(); } + +protected: + void ReleaseThis() override; + +private: + friend class AllocatorPimpl; + friend class BlockVector; + friend class CommittedAllocationList; + friend class JsonWriter; + friend class BlockMetadata_Linear; + friend class DefragmentationContextPimpl; + friend struct CommittedAllocationListItemTraits; + template friend void D3D12MA_DELETE(const ALLOCATION_CALLBACKS&, T*); + template friend class PoolAllocator; + + enum Type + { + TYPE_COMMITTED, + TYPE_PLACED, + TYPE_HEAP, + TYPE_COUNT + }; + + AllocatorPimpl* m_Allocator; + UINT64 m_Size; + UINT64 m_Alignment; + ID3D12Resource* m_Resource; + void* m_pPrivateData; + wchar_t* m_Name; + + union + { + struct + { + CommittedAllocationList* list; + Allocation* prev; + Allocation* next; + } m_Committed; + + struct + { + AllocHandle allocHandle; + NormalBlock* block; + } m_Placed; + + struct + { + // Beginning must be compatible with m_Committed. + CommittedAllocationList* list; + Allocation* prev; + Allocation* next; + ID3D12Heap* heap; + } m_Heap; + }; + + struct PackedData + { + public: + PackedData() : + m_Type(0), m_ResourceDimension(0), m_ResourceFlags(0), m_TextureLayout(0), m_WasZeroInitialized(0) { } + + Type GetType() const { return (Type)m_Type; } + D3D12_RESOURCE_DIMENSION GetResourceDimension() const { return (D3D12_RESOURCE_DIMENSION)m_ResourceDimension; } + D3D12_RESOURCE_FLAGS GetResourceFlags() const { return (D3D12_RESOURCE_FLAGS)m_ResourceFlags; } + D3D12_TEXTURE_LAYOUT GetTextureLayout() const { return (D3D12_TEXTURE_LAYOUT)m_TextureLayout; } + BOOL WasZeroInitialized() const { return (BOOL)m_WasZeroInitialized; } + + void SetType(Type type); + void SetResourceDimension(D3D12_RESOURCE_DIMENSION resourceDimension); + void SetResourceFlags(D3D12_RESOURCE_FLAGS resourceFlags); + void SetTextureLayout(D3D12_TEXTURE_LAYOUT textureLayout); + void SetWasZeroInitialized(BOOL wasZeroInitialized) { m_WasZeroInitialized = wasZeroInitialized ? 1 : 0; } + + private: + UINT m_Type : 2; // enum Type + UINT m_ResourceDimension : 3; // enum D3D12_RESOURCE_DIMENSION + UINT m_ResourceFlags : 24; // flags D3D12_RESOURCE_FLAGS + UINT m_TextureLayout : 9; // enum D3D12_TEXTURE_LAYOUT + UINT m_WasZeroInitialized : 1; // BOOL + } m_PackedData; + + Allocation(AllocatorPimpl* allocator, UINT64 size, UINT64 alignment, BOOL wasZeroInitialized); + // Nothing here, everything already done in Release. + virtual ~Allocation() = default; + + void InitCommitted(CommittedAllocationList* list); + void InitPlaced(AllocHandle allocHandle, NormalBlock* block); + void InitHeap(CommittedAllocationList* list, ID3D12Heap* heap); + void SwapBlockAllocation(Allocation* allocation); + // If the Allocation represents committed resource with implicit heap, returns UINT64_MAX. + AllocHandle GetAllocHandle() const; + NormalBlock* GetBlock(); + template + void SetResourcePointer(ID3D12Resource* resource, const D3D12_RESOURCE_DESC_T* pResourceDesc); + void FreeName(); + + D3D12MA_CLASS_NO_COPY(Allocation) +}; + + +/// Flags to be passed as DEFRAGMENTATION_DESC::Flags. +enum DEFRAGMENTATION_FLAGS +{ + /** Use simple but fast algorithm for defragmentation. + May not achieve best results but will require least time to compute and least allocations to copy. + */ + DEFRAGMENTATION_FLAG_ALGORITHM_FAST = 0x1, + /** Default defragmentation algorithm, applied also when no `ALGORITHM` flag is specified. + Offers a balance between defragmentation quality and the amount of allocations and bytes that need to be moved. + */ + DEFRAGMENTATION_FLAG_ALGORITHM_BALANCED = 0x2, + /** Perform full defragmentation of memory. + Can result in notably more time to compute and allocations to copy, but will achieve best memory packing. + */ + DEFRAGMENTATION_FLAG_ALGORITHM_FULL = 0x4, + + /// A bit mask to extract only `ALGORITHM` bits from entire set of flags. + DEFRAGMENTATION_FLAG_ALGORITHM_MASK = + DEFRAGMENTATION_FLAG_ALGORITHM_FAST | + DEFRAGMENTATION_FLAG_ALGORITHM_BALANCED | + DEFRAGMENTATION_FLAG_ALGORITHM_FULL +}; + +/** \brief Parameters for defragmentation. + +To be used with functions Allocator::BeginDefragmentation() and Pool::BeginDefragmentation(). +*/ +struct DEFRAGMENTATION_DESC +{ + /// Flags. + DEFRAGMENTATION_FLAGS Flags; + /** \brief Maximum numbers of bytes that can be copied during single pass, while moving allocations to different places. + + 0 means no limit. + */ + UINT64 MaxBytesPerPass; + /** \brief Maximum number of allocations that can be moved during single pass to a different place. + + 0 means no limit. + */ + UINT32 MaxAllocationsPerPass; +}; + +/// Operation performed on single defragmentation move. +enum DEFRAGMENTATION_MOVE_OPERATION +{ + /** Resource has been recreated at `pDstTmpAllocation`, data has been copied, old resource has been destroyed. + `pSrcAllocation` will be changed to point to the new place. This is the default value set by DefragmentationContext::BeginPass(). + */ + DEFRAGMENTATION_MOVE_OPERATION_COPY = 0, + /// Set this value if you cannot move the allocation. New place reserved at `pDstTmpAllocation` will be freed. `pSrcAllocation` will remain unchanged. + DEFRAGMENTATION_MOVE_OPERATION_IGNORE = 1, + /// Set this value if you decide to abandon the allocation and you destroyed the resource. New place reserved `pDstTmpAllocation` will be freed, along with `pSrcAllocation`. + DEFRAGMENTATION_MOVE_OPERATION_DESTROY = 2, +}; + +/// Single move of an allocation to be done for defragmentation. +struct DEFRAGMENTATION_MOVE +{ + /** \brief Operation to be performed on the allocation by DefragmentationContext::EndPass(). + Default value is #DEFRAGMENTATION_MOVE_OPERATION_COPY. You can modify it. + */ + DEFRAGMENTATION_MOVE_OPERATION Operation; + /// %Allocation that should be moved. + Allocation* pSrcAllocation; + /** \brief Temporary allocation pointing to destination memory that will replace `pSrcAllocation`. + + Use it to retrieve new `ID3D12Heap` and offset to create new `ID3D12Resource` and then store it here via Allocation::SetResource(). + + \warning Do not store this allocation in your data structures! It exists only temporarily, for the duration of the defragmentation pass, + to be used for storing newly created resource. DefragmentationContext::EndPass() will destroy it and make `pSrcAllocation` point to this memory. + */ + Allocation* pDstTmpAllocation; +}; + +/** \brief Parameters for incremental defragmentation steps. + +To be used with function DefragmentationContext::BeginPass(). +*/ +struct DEFRAGMENTATION_PASS_MOVE_INFO +{ + /// Number of elements in the `pMoves` array. + UINT32 MoveCount; + /** \brief Array of moves to be performed by the user in the current defragmentation pass. + + Pointer to an array of `MoveCount` elements, owned by %D3D12MA, created in DefragmentationContext::BeginPass(), destroyed in DefragmentationContext::EndPass(). + + For each element, you should: + + 1. Create a new resource in the place pointed by `pMoves[i].pDstTmpAllocation->GetHeap()` + `pMoves[i].pDstTmpAllocation->GetOffset()`. + 2. Store new resource in `pMoves[i].pDstTmpAllocation` by using Allocation::SetResource(). It will later replace old resource from `pMoves[i].pSrcAllocation`. + 3. Copy data from the `pMoves[i].pSrcAllocation` e.g. using `D3D12GraphicsCommandList::CopyResource`. + 4. Make sure these commands finished executing on the GPU. + + Only then you can finish defragmentation pass by calling DefragmentationContext::EndPass(). + After this call, the allocation will point to the new place in memory. + + Alternatively, if you cannot move specific allocation, + you can set DEFRAGMENTATION_MOVE::Operation to D3D12MA::DEFRAGMENTATION_MOVE_OPERATION_IGNORE. + + Alternatively, if you decide you want to completely remove the allocation, + set DEFRAGMENTATION_MOVE::Operation to D3D12MA::DEFRAGMENTATION_MOVE_OPERATION_DESTROY. + Then, after DefragmentationContext::EndPass() the allocation will be released. + */ + DEFRAGMENTATION_MOVE* pMoves; +}; + +/// %Statistics returned for defragmentation process by function DefragmentationContext::GetStats(). +struct DEFRAGMENTATION_STATS +{ + /// Total number of bytes that have been copied while moving allocations to different places. + UINT64 BytesMoved; + /// Total number of bytes that have been released to the system by freeing empty heaps. + UINT64 BytesFreed; + /// Number of allocations that have been moved to different places. + UINT32 AllocationsMoved; + /// Number of empty `ID3D12Heap` objects that have been released to the system. + UINT32 HeapsFreed; +}; + +/** \brief Represents defragmentation process in progress. + +You can create this object using Allocator::BeginDefragmentation (for default pools) or +Pool::BeginDefragmentation (for a custom pool). +*/ +class D3D12MA_API DefragmentationContext : public IUnknownImpl +{ +public: + /** \brief Starts single defragmentation pass. + + \param[out] pPassInfo Computed informations for current pass. + \returns + - `S_OK` if no more moves are possible. Then you can omit call to DefragmentationContext::EndPass() and simply end whole defragmentation. + - `S_FALSE` if there are pending moves returned in `pPassInfo`. You need to perform them, call DefragmentationContext::EndPass(), + and then preferably try another pass with DefragmentationContext::BeginPass(). + */ + HRESULT BeginPass(DEFRAGMENTATION_PASS_MOVE_INFO* pPassInfo); + /** \brief Ends single defragmentation pass. + + \param pPassInfo Computed informations for current pass filled by DefragmentationContext::BeginPass() and possibly modified by you. + \return Returns `S_OK` if no more moves are possible or `S_FALSE` if more defragmentations are possible. + + Ends incremental defragmentation pass and commits all defragmentation moves from `pPassInfo`. + After this call: + + - %Allocation at `pPassInfo[i].pSrcAllocation` that had `pPassInfo[i].Operation ==` #DEFRAGMENTATION_MOVE_OPERATION_COPY + (which is the default) will be pointing to the new destination place. + - %Allocation at `pPassInfo[i].pSrcAllocation` that had `pPassInfo[i].operation ==` #DEFRAGMENTATION_MOVE_OPERATION_DESTROY + will be released. + + If no more moves are possible you can end whole defragmentation. + */ + HRESULT EndPass(DEFRAGMENTATION_PASS_MOVE_INFO* pPassInfo); + /** \brief Returns statistics of the defragmentation performed so far. + */ + void GetStats(DEFRAGMENTATION_STATS* pStats); + +protected: + void ReleaseThis() override; + +private: + friend class Pool; + friend class Allocator; + template friend void D3D12MA_DELETE(const ALLOCATION_CALLBACKS&, T*); + + DefragmentationContextPimpl* m_Pimpl; + + DefragmentationContext(AllocatorPimpl* allocator, + const DEFRAGMENTATION_DESC& desc, + BlockVector* poolVector); + ~DefragmentationContext(); + + D3D12MA_CLASS_NO_COPY(DefragmentationContext) +}; + +/// \brief Bit flags to be used with POOL_DESC::Flags. +enum POOL_FLAGS +{ + /// Zero + POOL_FLAG_NONE = 0, + + /** \brief Enables alternative, linear allocation algorithm in this pool. + + Specify this flag to enable linear allocation algorithm, which always creates + new allocations after last one and doesn't reuse space from allocations freed in + between. It trades memory consumption for simplified algorithm and data + structure, which has better performance and uses less memory for metadata. + + By using this flag, you can achieve behavior of free-at-once, stack, + ring buffer, and double stack. + For details, see documentation chapter \ref linear_algorithm. + */ + POOL_FLAG_ALGORITHM_LINEAR = 0x1, + + /** \brief Optimization, allocate MSAA textures as committed resources always. + + Specify this flag to create MSAA textures with implicit heaps, as if they were created + with flag D3D12MA::ALLOCATION_FLAG_COMMITTED. Usage of this flags enables pool to create its heaps + on smaller alignment not suitable for MSAA textures. + */ + POOL_FLAG_MSAA_TEXTURES_ALWAYS_COMMITTED = 0x2, + + // Bit mask to extract only `ALGORITHM` bits from entire set of flags. + POOL_FLAG_ALGORITHM_MASK = POOL_FLAG_ALGORITHM_LINEAR +}; + +/// \brief Parameters of created D3D12MA::Pool object. To be used with D3D12MA::Allocator::CreatePool. +struct POOL_DESC +{ + /// Flags. + POOL_FLAGS Flags; + /** \brief The parameters of memory heap where allocations of this pool should be placed. + + In the simplest case, just fill it with zeros and set `Type` to one of: `D3D12_HEAP_TYPE_DEFAULT`, + `D3D12_HEAP_TYPE_UPLOAD`, `D3D12_HEAP_TYPE_READBACK`. Additional parameters can be used e.g. to utilize UMA. + */ + D3D12_HEAP_PROPERTIES HeapProperties; + /** \brief Heap flags to be used when allocating heaps of this pool. + + It should contain one of these values, depending on type of resources you are going to create in this heap: + `D3D12_HEAP_FLAG_ALLOW_ONLY_BUFFERS`, + `D3D12_HEAP_FLAG_ALLOW_ONLY_NON_RT_DS_TEXTURES`, + `D3D12_HEAP_FLAG_ALLOW_ONLY_RT_DS_TEXTURES`. + Except if ResourceHeapTier = 2, then it may be `D3D12_HEAP_FLAG_ALLOW_ALL_BUFFERS_AND_TEXTURES` = 0. + + You can specify additional flags if needed. + */ + D3D12_HEAP_FLAGS HeapFlags; + /** \brief Size of a single heap (memory block) to be allocated as part of this pool, in bytes. Optional. + + Specify nonzero to set explicit, constant size of memory blocks used by this pool. + Leave 0 to use default and let the library manage block sizes automatically. + Then sizes of particular blocks may vary. + */ + UINT64 BlockSize; + /** \brief Minimum number of heaps (memory blocks) to be always allocated in this pool, even if they stay empty. Optional. + + Set to 0 to have no preallocated blocks and allow the pool be completely empty. + */ + UINT MinBlockCount; + /** \brief Maximum number of heaps (memory blocks) that can be allocated in this pool. Optional. + + Set to 0 to use default, which is `UINT64_MAX`, which means no limit. + + Set to same value as D3D12MA::POOL_DESC::MinBlockCount to have fixed amount of memory allocated + throughout whole lifetime of this pool. + */ + UINT MaxBlockCount; + /** \brief Additional minimum alignment to be used for all allocations created from this pool. Can be 0. + + Leave 0 (default) not to impose any additional alignment. If not 0, it must be a power of two. + */ + UINT64 MinAllocationAlignment; + /** \brief Additional parameter allowing pool to create resources with passed protected session. + + If not null then all the heaps and committed resources will be created with this parameter. + Valid only if ID3D12Device4 interface is present in current Windows SDK! + */ + ID3D12ProtectedResourceSession* pProtectedSession; + /** \brief Residency priority to be set for all allocations made in this pool. Optional. + + Set this parameter to one of the possible enum values e.g. `D3D12_RESIDENCY_PRIORITY_HIGH` + to apply specific residency priority to all allocations made in this pool: + `ID3D12Heap` memory blocks used to sub-allocate for placed resources, as well as + committed resources or heaps created when D3D12MA::ALLOCATION_FLAG_COMMITTED is used. + This can increase/decrease chance that the memory will be pushed out from VRAM + to system RAM when the system runs out of memory, which is invisible to the developer + using D3D12 API while it can degrade performance. + + Priority is set using function `ID3D12Device1::SetResidencyPriority`. + It is performed only when `ID3D12Device1` interface is defined and successfully obtained. + Otherwise, this parameter is ignored. + + This parameter is optional. If you set it to `D3D12_RESIDENCY_PRIORITY(0)`, + residency priority will not be set for allocations made in this pool. + + There is no equivalent parameter for allocations made in default pools. + If you want to set residency priority for such allocation, you need to do it manually: + allocate with D3D12MA::ALLOCATION_FLAG_COMMITTED and call + `ID3D12Device1::SetResidencyPriority`, passing `allocation->GetResource()`. + */ + D3D12_RESIDENCY_PRIORITY ResidencyPriority; +}; + +/** \brief Custom memory pool + +Represents a separate set of heaps (memory blocks) that can be used to create +D3D12MA::Allocation-s and resources in it. Usually there is no need to create custom +pools - creating resources in default pool is sufficient. + +To create custom pool, fill D3D12MA::POOL_DESC and call D3D12MA::Allocator::CreatePool. +*/ +class D3D12MA_API Pool : public IUnknownImpl +{ +public: + /** \brief Returns copy of parameters of the pool. + + These are the same parameters as passed to D3D12MA::Allocator::CreatePool. + */ + POOL_DESC GetDesc() const; + + /** \brief Retrieves basic statistics of the custom pool that are fast to calculate. + + \param[out] pStats %Statistics of the current pool. + */ + void GetStatistics(Statistics* pStats); + + /** \brief Retrieves detailed statistics of the custom pool that are slower to calculate. + + \param[out] pStats %Statistics of the current pool. + */ + void CalculateStatistics(DetailedStatistics* pStats); + + /** \brief Associates a name with the pool. This name is for use in debug diagnostics and tools. + + Internal copy of the string is made, so the memory pointed by the argument can be + changed of freed immediately after this call. + + `Name` can be NULL. + */ + void SetName(LPCWSTR Name); + + /** \brief Returns the name associated with the pool object. + + Returned string points to an internal copy. + + If no name was associated with the allocation, returns NULL. + */ + LPCWSTR GetName() const; + + /** \brief Begins defragmentation process of the current pool. + + \param pDesc Structure filled with parameters of defragmentation. + \param[out] ppContext Context object that will manage defragmentation. + \returns + - `S_OK` if defragmentation can begin. + - `E_NOINTERFACE` if defragmentation is not supported. + + For more information about defragmentation, see documentation chapter: + [Defragmentation](@ref defragmentation). + */ + HRESULT BeginDefragmentation(const DEFRAGMENTATION_DESC* pDesc, DefragmentationContext** ppContext); + +protected: + void ReleaseThis() override; + +private: + friend class Allocator; + friend class AllocatorPimpl; + template friend void D3D12MA_DELETE(const ALLOCATION_CALLBACKS&, T*); + + PoolPimpl* m_Pimpl; + + Pool(Allocator* allocator, const POOL_DESC &desc); + ~Pool(); + + D3D12MA_CLASS_NO_COPY(Pool) +}; + + +/// \brief Bit flags to be used with ALLOCATOR_DESC::Flags. +enum ALLOCATOR_FLAGS +{ + /// Zero + ALLOCATOR_FLAG_NONE = 0, + + /** + Allocator and all objects created from it will not be synchronized internally, + so you must guarantee they are used from only one thread at a time or + synchronized by you. + + Using this flag may increase performance because internal mutexes are not used. + */ + ALLOCATOR_FLAG_SINGLETHREADED = 0x1, + + /** + Every allocation will have its own memory block. + To be used for debugging purposes. + */ + ALLOCATOR_FLAG_ALWAYS_COMMITTED = 0x2, + + /** + Heaps created for the default pools will be created with flag `D3D12_HEAP_FLAG_CREATE_NOT_ZEROED`, + allowing for their memory to be not zeroed by the system if possible, + which can speed up allocation. + + Only affects default pools. + To use the flag with @ref custom_pools, you need to add it manually: + + \code + poolDesc.heapFlags |= D3D12_HEAP_FLAG_CREATE_NOT_ZEROED; + \endcode + + Only avaiable if `ID3D12Device8` is present. Otherwise, the flag is ignored. + */ + ALLOCATOR_FLAG_DEFAULT_POOLS_NOT_ZEROED = 0x4, + + /** \brief Optimization, allocate MSAA textures as committed resources always. + + Specify this flag to create MSAA textures with implicit heaps, as if they were created + with flag D3D12MA::ALLOCATION_FLAG_COMMITTED. Usage of this flags enables all default pools + to create its heaps on smaller alignment not suitable for MSAA textures. + */ + ALLOCATOR_FLAG_MSAA_TEXTURES_ALWAYS_COMMITTED = 0x8, +}; + +/// \brief Parameters of created Allocator object. To be used with CreateAllocator(). +struct ALLOCATOR_DESC +{ + /// Flags. + ALLOCATOR_FLAGS Flags; + + /** Direct3D device object that the allocator should be attached to. + + Allocator is doing `AddRef`/`Release` on this object. + */ + ID3D12Device* pDevice; + + /** \brief Preferred size of a single `ID3D12Heap` block to be allocated. + + Set to 0 to use default, which is currently 64 MiB. + */ + UINT64 PreferredBlockSize; + + /** \brief Custom CPU memory allocation callbacks. Optional. + + Optional, can be null. When specified, will be used for all CPU-side memory allocations. + */ + const ALLOCATION_CALLBACKS* pAllocationCallbacks; + + /** DXGI Adapter object that you use for D3D12 and this allocator. + + Allocator is doing `AddRef`/`Release` on this object. + */ + IDXGIAdapter* pAdapter; +}; + +/** +\brief Represents main object of this library initialized for particular `ID3D12Device`. + +Fill structure D3D12MA::ALLOCATOR_DESC and call function CreateAllocator() to create it. +Call method `Release()` to destroy it. + +It is recommended to create just one object of this type per `ID3D12Device` object, +right after Direct3D 12 is initialized and keep it alive until before Direct3D device is destroyed. +*/ +class D3D12MA_API Allocator : public IUnknownImpl +{ +public: + /// Returns cached options retrieved from D3D12 device. + const D3D12_FEATURE_DATA_D3D12_OPTIONS& GetD3D12Options() const; + /** \brief Returns true if `D3D12_FEATURE_DATA_ARCHITECTURE1::UMA` was found to be true. + + For more information about how to use it, see articles in Microsoft Docs articles: + + - "UMA Optimizations: CPU Accessible Textures and Standard Swizzle" + - "D3D12_FEATURE_DATA_ARCHITECTURE structure (d3d12.h)" + - "ID3D12Device::GetCustomHeapProperties method (d3d12.h)" + */ + BOOL IsUMA() const; + /** \brief Returns true if `D3D12_FEATURE_DATA_ARCHITECTURE1::CacheCoherentUMA` was found to be true. + + For more information about how to use it, see articles in Microsoft Docs articles: + + - "UMA Optimizations: CPU Accessible Textures and Standard Swizzle" + - "D3D12_FEATURE_DATA_ARCHITECTURE structure (d3d12.h)" + - "ID3D12Device::GetCustomHeapProperties method (d3d12.h)" + */ + BOOL IsCacheCoherentUMA() const; + /** \brief Returns total amount of memory of specific segment group, in bytes. + + \param memorySegmentGroup use `DXGI_MEMORY_SEGMENT_GROUP_LOCAL` or DXGI_MEMORY_SEGMENT_GROUP_NON_LOCAL`. + + This information is taken from `DXGI_ADAPTER_DESC`. + It is not recommended to use this number. + You should preferably call GetBudget() and limit memory usage to D3D12MA::Budget::BudgetBytes instead. + + - When IsUMA() `== FALSE` (discrete graphics card): + - `GetMemoryCapacity(DXGI_MEMORY_SEGMENT_GROUP_LOCAL)` returns the size of the video memory. + - `GetMemoryCapacity(DXGI_MEMORY_SEGMENT_GROUP_NON_LOCAL)` returns the size of the system memory available for D3D12 resources. + - When IsUMA() `== TRUE` (integrated graphics chip): + - `GetMemoryCapacity(DXGI_MEMORY_SEGMENT_GROUP_LOCAL)` returns the size of the shared memory available for all D3D12 resources. + All memory is considered "local". + - `GetMemoryCapacity(DXGI_MEMORY_SEGMENT_GROUP_NON_LOCAL)` is not applicable and returns 0. + */ + UINT64 GetMemoryCapacity(UINT memorySegmentGroup) const; + + /** \brief Allocates memory and creates a D3D12 resource (buffer or texture). This is the main allocation function. + + The function is similar to `ID3D12Device::CreateCommittedResource`, but it may + really call `ID3D12Device::CreatePlacedResource` to assign part of a larger, + existing memory heap to the new resource, which is the main purpose of this + whole library. + + If `ppvResource` is null, you receive only `ppAllocation` object from this function. + It holds pointer to `ID3D12Resource` that can be queried using function D3D12MA::Allocation::GetResource(). + Reference count of the resource object is 1. + It is automatically destroyed when you destroy the allocation object. + + If `ppvResource` is not null, you receive pointer to the resource next to allocation object. + Reference count of the resource object is then increased by calling `QueryInterface`, so you need to manually `Release` it + along with the allocation. + + \param pAllocDesc Parameters of the allocation. + \param pResourceDesc Description of created resource. + \param InitialResourceState Initial resource state. + \param pOptimizedClearValue Optional. Either null or optimized clear value. + \param[out] ppAllocation Filled with pointer to new allocation object created. + \param riidResource IID of a resource to be returned via `ppvResource`. + \param[out] ppvResource Optional. If not null, filled with pointer to new resouce created. + + \note This function creates a new resource. Sub-allocation of parts of one large buffer, + although recommended as a good practice, is out of scope of this library and could be implemented + by the user as a higher-level logic on top of it, e.g. using the \ref virtual_allocator feature. + */ + HRESULT CreateResource( + const ALLOCATION_DESC* pAllocDesc, + const D3D12_RESOURCE_DESC* pResourceDesc, + D3D12_RESOURCE_STATES InitialResourceState, + const D3D12_CLEAR_VALUE *pOptimizedClearValue, + Allocation** ppAllocation, + REFIID riidResource, + void** ppvResource); + +#ifdef __ID3D12Device8_INTERFACE_DEFINED__ + /** \brief Similar to Allocator::CreateResource, but supports new structure `D3D12_RESOURCE_DESC1`. + + It internally uses `ID3D12Device8::CreateCommittedResource2` or `ID3D12Device8::CreatePlacedResource1`. + + To work correctly, `ID3D12Device8` interface must be available in the current system. Otherwise, `E_NOINTERFACE` is returned. + */ + HRESULT CreateResource2( + const ALLOCATION_DESC* pAllocDesc, + const D3D12_RESOURCE_DESC1* pResourceDesc, + D3D12_RESOURCE_STATES InitialResourceState, + const D3D12_CLEAR_VALUE *pOptimizedClearValue, + Allocation** ppAllocation, + REFIID riidResource, + void** ppvResource); +#endif // #ifdef __ID3D12Device8_INTERFACE_DEFINED__ + +#ifdef __ID3D12Device10_INTERFACE_DEFINED__ + /** \brief Similar to Allocator::CreateResource2, but there are initial layout instead of state and + castable formats list + + It internally uses `ID3D12Device10::CreateCommittedResource3` or `ID3D12Device10::CreatePlacedResource2`. + + To work correctly, `ID3D12Device10` interface must be available in the current system. Otherwise, `E_NOINTERFACE` is returned. + */ + HRESULT CreateResource3(const ALLOCATION_DESC* pAllocDesc, + const D3D12_RESOURCE_DESC1* pResourceDesc, + D3D12_BARRIER_LAYOUT InitialLayout, + const D3D12_CLEAR_VALUE* pOptimizedClearValue, + UINT32 NumCastableFormats, + DXGI_FORMAT* pCastableFormats, + Allocation** ppAllocation, + REFIID riidResource, + void** ppvResource); +#endif // #ifdef __ID3D12Device10_INTERFACE_DEFINED__ + + /** \brief Allocates memory without creating any resource placed in it. + + This function is similar to `ID3D12Device::CreateHeap`, but it may really assign + part of a larger, existing heap to the allocation. + + `pAllocDesc->heapFlags` should contain one of these values, depending on type of resources you are going to create in this memory: + `D3D12_HEAP_FLAG_ALLOW_ONLY_BUFFERS`, + `D3D12_HEAP_FLAG_ALLOW_ONLY_NON_RT_DS_TEXTURES`, + `D3D12_HEAP_FLAG_ALLOW_ONLY_RT_DS_TEXTURES`. + Except if you validate that ResourceHeapTier = 2 - then `heapFlags` + may be `D3D12_HEAP_FLAG_ALLOW_ALL_BUFFERS_AND_TEXTURES` = 0. + Additional flags in `heapFlags` are allowed as well. + + `pAllocInfo->SizeInBytes` must be multiply of 64KB. + `pAllocInfo->Alignment` must be one of the legal values as described in documentation of `D3D12_HEAP_DESC`. + + If you use D3D12MA::ALLOCATION_FLAG_COMMITTED you will get a separate memory block - + a heap that always has offset 0. + */ + HRESULT AllocateMemory( + const ALLOCATION_DESC* pAllocDesc, + const D3D12_RESOURCE_ALLOCATION_INFO* pAllocInfo, + Allocation** ppAllocation); + + /** \brief Creates a new resource in place of an existing allocation. This is useful for memory aliasing. + + \param pAllocation Existing allocation indicating the memory where the new resource should be created. + It can be created using D3D12MA::Allocator::CreateResource and already have a resource bound to it, + or can be a raw memory allocated with D3D12MA::Allocator::AllocateMemory. + It must not be created as committed so that `ID3D12Heap` is available and not implicit. + \param AllocationLocalOffset Additional offset in bytes to be applied when allocating the resource. + Local from the start of `pAllocation`, not the beginning of the whole `ID3D12Heap`! + If the new resource should start from the beginning of the `pAllocation` it should be 0. + \param pResourceDesc Description of the new resource to be created. + \param InitialResourceState + \param pOptimizedClearValue + \param riidResource + \param[out] ppvResource Returns pointer to the new resource. + The resource is not bound with `pAllocation`. + This pointer must not be null - you must get the resource pointer and `Release` it when no longer needed. + + Memory requirements of the new resource are checked for validation. + If its size exceeds the end of `pAllocation` or required alignment is not fulfilled + considering `pAllocation->GetOffset() + AllocationLocalOffset`, the function + returns `E_INVALIDARG`. + */ + HRESULT CreateAliasingResource( + Allocation* pAllocation, + UINT64 AllocationLocalOffset, + const D3D12_RESOURCE_DESC* pResourceDesc, + D3D12_RESOURCE_STATES InitialResourceState, + const D3D12_CLEAR_VALUE *pOptimizedClearValue, + REFIID riidResource, + void** ppvResource); + +#ifdef __ID3D12Device8_INTERFACE_DEFINED__ + /** \brief Similar to Allocator::CreateAliasingResource, but supports new structure `D3D12_RESOURCE_DESC1`. + + It internally uses `ID3D12Device8::CreatePlacedResource1`. + + To work correctly, `ID3D12Device8` interface must be available in the current system. Otherwise, `E_NOINTERFACE` is returned. + */ + HRESULT CreateAliasingResource1(Allocation* pAllocation, + UINT64 AllocationLocalOffset, + const D3D12_RESOURCE_DESC1* pResourceDesc, + D3D12_RESOURCE_STATES InitialResourceState, + const D3D12_CLEAR_VALUE* pOptimizedClearValue, + REFIID riidResource, + void** ppvResource); +#endif // #ifdef __ID3D12Device8_INTERFACE_DEFINED__ + +#ifdef __ID3D12Device10_INTERFACE_DEFINED__ + /** \brief Similar to Allocator::CreateAliasingResource1, but there are initial layout instead of state and + castable formats list + + It internally uses `ID3D12Device10::CreatePlacedResource2`. + + To work correctly, `ID3D12Device10` interface must be available in the current system. Otherwise, `E_NOINTERFACE` is returned. + */ + HRESULT CreateAliasingResource2(Allocation* pAllocation, + UINT64 AllocationLocalOffset, + const D3D12_RESOURCE_DESC1* pResourceDesc, + D3D12_BARRIER_LAYOUT InitialLayout, + const D3D12_CLEAR_VALUE* pOptimizedClearValue, + UINT32 NumCastableFormats, + DXGI_FORMAT* pCastableFormats, + REFIID riidResource, + void** ppvResource); +#endif // #ifdef __ID3D12Device10_INTERFACE_DEFINED__ + + /** \brief Creates custom pool. + */ + HRESULT CreatePool( + const POOL_DESC* pPoolDesc, + Pool** ppPool); + + /** \brief Sets the index of the current frame. + + This function is used to set the frame index in the allocator when a new game frame begins. + */ + void SetCurrentFrameIndex(UINT frameIndex); + + /** \brief Retrieves information about current memory usage and budget. + + \param[out] pLocalBudget Optional, can be null. + \param[out] pNonLocalBudget Optional, can be null. + + - When IsUMA() `== FALSE` (discrete graphics card): + - `pLocalBudget` returns the budget of the video memory. + - `pNonLocalBudget` returns the budget of the system memory available for D3D12 resources. + - When IsUMA() `== TRUE` (integrated graphics chip): + - `pLocalBudget` returns the budget of the shared memory available for all D3D12 resources. + All memory is considered "local". + - `pNonLocalBudget` is not applicable and returns zeros. + + This function is called "get" not "calculate" because it is very fast, suitable to be called + every frame or every allocation. For more detailed statistics use CalculateStatistics(). + + Note that when using allocator from multiple threads, returned information may immediately + become outdated. + */ + void GetBudget(Budget* pLocalBudget, Budget* pNonLocalBudget); + + /** \brief Retrieves statistics from current state of the allocator. + + This function is called "calculate" not "get" because it has to traverse all + internal data structures, so it may be quite slow. Use it for debugging purposes. + For faster but more brief statistics suitable to be called every frame or every allocation, + use GetBudget(). + + Note that when using allocator from multiple threads, returned information may immediately + become outdated. + */ + void CalculateStatistics(TotalStatistics* pStats); + + /** \brief Builds and returns statistics as a string in JSON format. + * + @param[out] ppStatsString Must be freed using Allocator::FreeStatsString. + @param DetailedMap `TRUE` to include full list of allocations (can make the string quite long), `FALSE` to only return statistics. + */ + void BuildStatsString(WCHAR** ppStatsString, BOOL DetailedMap) const; + + /// Frees memory of a string returned from Allocator::BuildStatsString. + void FreeStatsString(WCHAR* pStatsString) const; + + /** \brief Begins defragmentation process of the default pools. + + \param pDesc Structure filled with parameters of defragmentation. + \param[out] ppContext Context object that will manage defragmentation. + + For more information about defragmentation, see documentation chapter: + [Defragmentation](@ref defragmentation). + */ + void BeginDefragmentation(const DEFRAGMENTATION_DESC* pDesc, DefragmentationContext** ppContext); + +protected: + void ReleaseThis() override; + +private: + friend D3D12MA_API HRESULT CreateAllocator(const ALLOCATOR_DESC*, Allocator**); + template friend void D3D12MA_DELETE(const ALLOCATION_CALLBACKS&, T*); + friend class DefragmentationContext; + friend class Pool; + + Allocator(const ALLOCATION_CALLBACKS& allocationCallbacks, const ALLOCATOR_DESC& desc); + ~Allocator(); + + AllocatorPimpl* m_Pimpl; + + D3D12MA_CLASS_NO_COPY(Allocator) +}; + + +/// \brief Bit flags to be used with VIRTUAL_BLOCK_DESC::Flags. +enum VIRTUAL_BLOCK_FLAGS +{ + /// Zero + VIRTUAL_BLOCK_FLAG_NONE = 0, + + /** \brief Enables alternative, linear allocation algorithm in this virtual block. + + Specify this flag to enable linear allocation algorithm, which always creates + new allocations after last one and doesn't reuse space from allocations freed in + between. It trades memory consumption for simplified algorithm and data + structure, which has better performance and uses less memory for metadata. + + By using this flag, you can achieve behavior of free-at-once, stack, + ring buffer, and double stack. + For details, see documentation chapter \ref linear_algorithm. + */ + VIRTUAL_BLOCK_FLAG_ALGORITHM_LINEAR = POOL_FLAG_ALGORITHM_LINEAR, + + // Bit mask to extract only `ALGORITHM` bits from entire set of flags. + VIRTUAL_BLOCK_FLAG_ALGORITHM_MASK = POOL_FLAG_ALGORITHM_MASK +}; + +/// Parameters of created D3D12MA::VirtualBlock object to be passed to CreateVirtualBlock(). +struct VIRTUAL_BLOCK_DESC +{ + /// Flags. + VIRTUAL_BLOCK_FLAGS Flags; + /** \brief Total size of the block. + + Sizes can be expressed in bytes or any units you want as long as you are consistent in using them. + For example, if you allocate from some array of structures, 1 can mean single instance of entire structure. + */ + UINT64 Size; + /** \brief Custom CPU memory allocation callbacks. Optional. + + Optional, can be null. When specified, will be used for all CPU-side memory allocations. + */ + const ALLOCATION_CALLBACKS* pAllocationCallbacks; +}; + +/// \brief Bit flags to be used with VIRTUAL_ALLOCATION_DESC::Flags. +enum VIRTUAL_ALLOCATION_FLAGS +{ + /// Zero + VIRTUAL_ALLOCATION_FLAG_NONE = 0, + + /** \brief Allocation will be created from upper stack in a double stack pool. + + This flag is only allowed for virtual blocks created with #VIRTUAL_BLOCK_FLAG_ALGORITHM_LINEAR flag. + */ + VIRTUAL_ALLOCATION_FLAG_UPPER_ADDRESS = ALLOCATION_FLAG_UPPER_ADDRESS, + + /// Allocation strategy that tries to minimize memory usage. + VIRTUAL_ALLOCATION_FLAG_STRATEGY_MIN_MEMORY = ALLOCATION_FLAG_STRATEGY_MIN_MEMORY, + /// Allocation strategy that tries to minimize allocation time. + VIRTUAL_ALLOCATION_FLAG_STRATEGY_MIN_TIME = ALLOCATION_FLAG_STRATEGY_MIN_TIME, + /** \brief Allocation strategy that chooses always the lowest offset in available space. + This is not the most efficient strategy but achieves highly packed data. + */ + VIRTUAL_ALLOCATION_FLAG_STRATEGY_MIN_OFFSET = ALLOCATION_FLAG_STRATEGY_MIN_OFFSET, + /** \brief A bit mask to extract only `STRATEGY` bits from entire set of flags. + + These strategy flags are binary compatible with equivalent flags in #ALLOCATION_FLAGS. + */ + VIRTUAL_ALLOCATION_FLAG_STRATEGY_MASK = ALLOCATION_FLAG_STRATEGY_MASK, +}; + +/// Parameters of created virtual allocation to be passed to VirtualBlock::Allocate(). +struct VIRTUAL_ALLOCATION_DESC +{ + /// Flags. + VIRTUAL_ALLOCATION_FLAGS Flags; + /** \brief Size of the allocation. + + Cannot be zero. + */ + UINT64 Size; + /** \brief Required alignment of the allocation. + + Must be power of two. Special value 0 has the same meaning as 1 - means no special alignment is required, so allocation can start at any offset. + */ + UINT64 Alignment; + /** \brief Custom pointer to be associated with the allocation. + + It can be fetched or changed later. + */ + void* pPrivateData; +}; + +/// Parameters of an existing virtual allocation, returned by VirtualBlock::GetAllocationInfo(). +struct VIRTUAL_ALLOCATION_INFO +{ + /// \brief Offset of the allocation. + UINT64 Offset; + /** \brief Size of the allocation. + + Same value as passed in VIRTUAL_ALLOCATION_DESC::Size. + */ + UINT64 Size; + /** \brief Custom pointer associated with the allocation. + + Same value as passed in VIRTUAL_ALLOCATION_DESC::pPrivateData or VirtualBlock::SetAllocationPrivateData(). + */ + void* pPrivateData; +}; + +/** \brief Represents pure allocation algorithm and a data structure with allocations in some memory block, without actually allocating any GPU memory. + +This class allows to use the core algorithm of the library custom allocations e.g. CPU memory or +sub-allocation regions inside a single GPU buffer. + +To create this object, fill in D3D12MA::VIRTUAL_BLOCK_DESC and call CreateVirtualBlock(). +To destroy it, call its method `VirtualBlock::Release()`. +You need to free all the allocations within this block or call Clear() before destroying it. + +This object is not thread-safe - should not be used from multiple threads simultaneously, must be synchronized externally. +*/ +class D3D12MA_API VirtualBlock : public IUnknownImpl +{ +public: + /** \brief Returns true if the block is empty - contains 0 allocations. + */ + BOOL IsEmpty() const; + /** \brief Returns information about an allocation - its offset, size and custom pointer. + */ + void GetAllocationInfo(VirtualAllocation allocation, VIRTUAL_ALLOCATION_INFO* pInfo) const; + + /** \brief Creates new allocation. + \param pDesc + \param[out] pAllocation Unique indentifier of the new allocation within single block. + \param[out] pOffset Returned offset of the new allocation. Optional, can be null. + \return `S_OK` if allocation succeeded, `E_OUTOFMEMORY` if it failed. + + If the allocation failed, `pAllocation->AllocHandle` is set to 0 and `pOffset`, if not null, is set to `UINT64_MAX`. + */ + HRESULT Allocate(const VIRTUAL_ALLOCATION_DESC* pDesc, VirtualAllocation* pAllocation, UINT64* pOffset); + /** \brief Frees the allocation. + + Calling this function with `allocation.AllocHandle == 0` is correct and does nothing. + */ + void FreeAllocation(VirtualAllocation allocation); + /** \brief Frees all the allocations. + */ + void Clear(); + /** \brief Changes custom pointer for an allocation to a new value. + */ + void SetAllocationPrivateData(VirtualAllocation allocation, void* pPrivateData); + /** \brief Retrieves basic statistics of the virtual block that are fast to calculate. + + \param[out] pStats %Statistics of the virtual block. + */ + void GetStatistics(Statistics* pStats) const; + /** \brief Retrieves detailed statistics of the virtual block that are slower to calculate. + + \param[out] pStats %Statistics of the virtual block. + */ + void CalculateStatistics(DetailedStatistics* pStats) const; + + /** \brief Builds and returns statistics as a string in JSON format, including the list of allocations with their parameters. + @param[out] ppStatsString Must be freed using VirtualBlock::FreeStatsString. + */ + void BuildStatsString(WCHAR** ppStatsString) const; + + /** \brief Frees memory of a string returned from VirtualBlock::BuildStatsString. + */ + void FreeStatsString(WCHAR* pStatsString) const; + +protected: + void ReleaseThis() override; + +private: + friend D3D12MA_API HRESULT CreateVirtualBlock(const VIRTUAL_BLOCK_DESC*, VirtualBlock**); + template friend void D3D12MA_DELETE(const ALLOCATION_CALLBACKS&, T*); + + VirtualBlockPimpl* m_Pimpl; + + VirtualBlock(const ALLOCATION_CALLBACKS& allocationCallbacks, const VIRTUAL_BLOCK_DESC& desc); + ~VirtualBlock(); + + D3D12MA_CLASS_NO_COPY(VirtualBlock) +}; + + +/** \brief Creates new main D3D12MA::Allocator object and returns it through `ppAllocator`. + +You normally only need to call it once and keep a single Allocator object for your `ID3D12Device`. +*/ +D3D12MA_API HRESULT CreateAllocator(const ALLOCATOR_DESC* pDesc, Allocator** ppAllocator); + +/** \brief Creates new D3D12MA::VirtualBlock object and returns it through `ppVirtualBlock`. + +Note you don't need to create D3D12MA::Allocator to use virtual blocks. +*/ +D3D12MA_API HRESULT CreateVirtualBlock(const VIRTUAL_BLOCK_DESC* pDesc, VirtualBlock** ppVirtualBlock); + +} // namespace D3D12MA + +/// \cond INTERNAL +DEFINE_ENUM_FLAG_OPERATORS(D3D12MA::ALLOCATION_FLAGS); +DEFINE_ENUM_FLAG_OPERATORS(D3D12MA::DEFRAGMENTATION_FLAGS); +DEFINE_ENUM_FLAG_OPERATORS(D3D12MA::ALLOCATOR_FLAGS); +DEFINE_ENUM_FLAG_OPERATORS(D3D12MA::POOL_FLAGS); +DEFINE_ENUM_FLAG_OPERATORS(D3D12MA::VIRTUAL_BLOCK_FLAGS); +DEFINE_ENUM_FLAG_OPERATORS(D3D12MA::VIRTUAL_ALLOCATION_FLAGS); +/// \endcond + +/** +\page quick_start Quick start + +\section quick_start_project_setup Project setup and initialization + +This is a small, standalone C++ library. It consists of a pair of 2 files: +"D3D12MemAlloc.h" header file with public interface and "D3D12MemAlloc.cpp" with +internal implementation. The only external dependencies are WinAPI, Direct3D 12, +and parts of C/C++ standard library (but STL containers, exceptions, or RTTI are +not used). + +The library is developed and tested using Microsoft Visual Studio 2019, but it +should work with other compilers as well. It is designed for 64-bit code. + +To use the library in your project: + +(1.) Copy files `D3D12MemAlloc.cpp`, `%D3D12MemAlloc.h` to your project. + +(2.) Make `D3D12MemAlloc.cpp` compiling as part of the project, as C++ code. + +(3.) Include library header in each CPP file that needs to use the library. + +\code +#include "D3D12MemAlloc.h" +\endcode + +(4.) Right after you created `ID3D12Device`, fill D3D12MA::ALLOCATOR_DESC +structure and call function D3D12MA::CreateAllocator to create the main +D3D12MA::Allocator object. + +Please note that all symbols of the library are declared inside #D3D12MA namespace. + +\code +IDXGIAdapter* adapter = (...) +ID3D12Device* device = (...) + +D3D12MA::ALLOCATOR_DESC allocatorDesc = {}; +allocatorDesc.pDevice = device; +allocatorDesc.pAdapter = adapter; + +D3D12MA::Allocator* allocator; +HRESULT hr = D3D12MA::CreateAllocator(&allocatorDesc, &allocator); +\endcode + +(5.) Right before destroying the D3D12 device, destroy the allocator object. + +Objects of this library must be destroyed by calling `Release` method. +They are somewhat compatible with COM: they implement `IUnknown` interface with its virtual methods: `AddRef`, `Release`, `QueryInterface`, +and they are reference-counted internally. +You can use smart pointers designed for COM with objects of this library - e.g. `CComPtr` or `Microsoft::WRL::ComPtr`. +The reference counter is thread-safe. +`QueryInterface` method supports only `IUnknown`, as classes of this library don't define their own GUIDs. + +\code +allocator->Release(); +\endcode + + +\section quick_start_creating_resources Creating resources + +To use the library for creating resources (textures and buffers), call method +D3D12MA::Allocator::CreateResource in the place where you would previously call +`ID3D12Device::CreateCommittedResource`. + +The function has similar syntax, but it expects structure D3D12MA::ALLOCATION_DESC +to be passed along with `D3D12_RESOURCE_DESC` and other parameters for created +resource. This structure describes parameters of the desired memory allocation, +including choice of `D3D12_HEAP_TYPE`. + +The function returns a new object of type D3D12MA::Allocation. +It represents allocated memory and can be queried for size, offset, `ID3D12Heap`. +It also holds a reference to the `ID3D12Resource`, which can be accessed by calling D3D12MA::Allocation::GetResource(). + +\code +D3D12_RESOURCE_DESC resourceDesc = {}; +resourceDesc.Dimension = D3D12_RESOURCE_DIMENSION_TEXTURE2D; +resourceDesc.Alignment = 0; +resourceDesc.Width = 1024; +resourceDesc.Height = 1024; +resourceDesc.DepthOrArraySize = 1; +resourceDesc.MipLevels = 1; +resourceDesc.Format = DXGI_FORMAT_R8G8B8A8_UNORM; +resourceDesc.SampleDesc.Count = 1; +resourceDesc.SampleDesc.Quality = 0; +resourceDesc.Layout = D3D12_TEXTURE_LAYOUT_UNKNOWN; +resourceDesc.Flags = D3D12_RESOURCE_FLAG_NONE; + +D3D12MA::ALLOCATION_DESC allocationDesc = {}; +allocationDesc.HeapType = D3D12_HEAP_TYPE_DEFAULT; + +D3D12MA::Allocation* allocation; +HRESULT hr = allocator->CreateResource( + &allocationDesc, + &resourceDesc, + D3D12_RESOURCE_STATE_COPY_DEST, + NULL, + &allocation, + IID_NULL, NULL); + +// Use allocation->GetResource()... +\endcode + +You need to release the allocation object when no longer needed. +This will also release the D3D12 resource. + +\code +allocation->Release(); +\endcode + +The advantage of using the allocator instead of creating committed resource, and +the main purpose of this library, is that it can decide to allocate bigger memory +heap internally using `ID3D12Device::CreateHeap` and place multiple resources in +it, at different offsets, using `ID3D12Device::CreatePlacedResource`. The library +manages its own collection of allocated memory blocks (heaps) and remembers which +parts of them are occupied and which parts are free to be used for new resources. + +It is important to remember that resources created as placed don't have their memory +initialized to zeros, but may contain garbage data, so they need to be fully initialized +before usage, e.g. using Clear (`ClearRenderTargetView`), Discard (`DiscardResource`), +or copy (`CopyResource`). + +The library also automatically handles resource heap tier. +When `D3D12_FEATURE_DATA_D3D12_OPTIONS::ResourceHeapTier` equals `D3D12_RESOURCE_HEAP_TIER_1`, +resources of 3 types: buffers, textures that are render targets or depth-stencil, +and other textures must be kept in separate heaps. When `D3D12_RESOURCE_HEAP_TIER_2`, +they can be kept together. By using this library, you don't need to handle this +manually. + + +\section quick_start_resource_reference_counting Resource reference counting + +`ID3D12Resource` and other interfaces of Direct3D 12 use COM, so they are reference-counted. +Objects of this library are reference-counted as well. +An object of type D3D12MA::Allocation remembers the resource (buffer or texture) +that was created together with this memory allocation +and holds a reference to the `ID3D12Resource` object. +(Note this is a difference to Vulkan Memory Allocator, where a `VmaAllocation` object has no connection +with the buffer or image that was created with it.) +Thus, it is important to manage the resource reference counter properly. + +The simplest use case is shown in the code snippet above. +When only D3D12MA::Allocation object is obtained from a function call like D3D12MA::Allocator::CreateResource, +it remembers the `ID3D12Resource` that was created with it and holds a reference to it. +The resource can be obtained by calling `allocation->GetResource()`, which doesn't increment the resource +reference counter. +Calling `allocation->Release()` will decrease the resource reference counter, which is = 1 in this case, +so the resource will be released. + +Second option is to retrieve a pointer to the resource along with D3D12MA::Allocation. +Last parameters of the resource creation function can be used for this purpose. + +\code +D3D12MA::Allocation* allocation; +ID3D12Resource* resource; +HRESULT hr = allocator->CreateResource( + &allocationDesc, + &resourceDesc, + D3D12_RESOURCE_STATE_COPY_DEST, + NULL, + &allocation, + IID_PPV_ARGS(&resource)); + +// Use resource... +\endcode + +In this case, returned pointer `resource` is equal to `allocation->GetResource()`, +but the creation function additionally increases resource reference counter for the purpose of returning it from this call +(it actually calls `QueryInterface` internally), so the resource will have the counter = 2. +The resource then need to be released along with the allocation, in this particular order, +to make sure the resource is destroyed before its memory heap can potentially be freed. + +\code +resource->Release(); +allocation->Release(); +\endcode + +More advanced use cases are possible when we consider that an D3D12MA::Allocation object can just hold +a reference to any resource. +It can be changed by calling D3D12MA::Allocation::SetResource. This function +releases the old resource and calls `AddRef` on the new one. + +Special care must be taken when performing defragmentation. +The new resource created at the destination place should be set as `pass.pMoves[i].pDstTmpAllocation->SetResource(newRes)`, +but it is moved to the source allocation at end of the defragmentation pass, +while the old resource accessible through `pass.pMoves[i].pSrcAllocation->GetResource()` is then released. +For more information, see documentation chapter \ref defragmentation. + + +\section quick_start_mapping_memory Mapping memory + +The process of getting regular CPU-side pointer to the memory of a resource in +Direct3D is called "mapping". There are rules and restrictions to this process, +as described in D3D12 documentation of `ID3D12Resource::Map` method. + +Mapping happens on the level of particular resources, not entire memory heaps, +and so it is out of scope of this library. Just as the documentation of the `Map` function says: + +- Returned pointer refers to data of particular subresource, not entire memory heap. +- You can map same resource multiple times. It is ref-counted internally. +- Mapping is thread-safe. +- Unmapping is not required before resource destruction. +- Unmapping may not be required before using written data - some heap types on + some platforms support resources persistently mapped. + +When using this library, you can map and use your resources normally without +considering whether they are created as committed resources or placed resources in one large heap. + +Example for buffer created and filled in `UPLOAD` heap type: + +\code +const UINT64 bufSize = 65536; +const float* bufData = (...); + +D3D12_RESOURCE_DESC resourceDesc = {}; +resourceDesc.Dimension = D3D12_RESOURCE_DIMENSION_BUFFER; +resourceDesc.Alignment = 0; +resourceDesc.Width = bufSize; +resourceDesc.Height = 1; +resourceDesc.DepthOrArraySize = 1; +resourceDesc.MipLevels = 1; +resourceDesc.Format = DXGI_FORMAT_UNKNOWN; +resourceDesc.SampleDesc.Count = 1; +resourceDesc.SampleDesc.Quality = 0; +resourceDesc.Layout = D3D12_TEXTURE_LAYOUT_ROW_MAJOR; +resourceDesc.Flags = D3D12_RESOURCE_FLAG_NONE; + +D3D12MA::ALLOCATION_DESC allocationDesc = {}; +allocationDesc.HeapType = D3D12_HEAP_TYPE_UPLOAD; + +D3D12Resource* resource; +D3D12MA::Allocation* allocation; +HRESULT hr = allocator->CreateResource( + &allocationDesc, + &resourceDesc, + D3D12_RESOURCE_STATE_GENERIC_READ, + NULL, + &allocation, + IID_PPV_ARGS(&resource)); + +void* mappedPtr; +hr = resource->Map(0, NULL, &mappedPtr); + +memcpy(mappedPtr, bufData, bufSize); + +resource->Unmap(0, NULL); +\endcode + + +\page custom_pools Custom memory pools + +A "pool" is a collection of memory blocks that share certain properties. +Allocator creates 3 default pools: for `D3D12_HEAP_TYPE_DEFAULT`, `UPLOAD`, `READBACK`. +A default pool automatically grows in size. Size of allocated blocks is also variable and managed automatically. +Typical allocations are created in these pools. You can also create custom pools. + +\section custom_pools_usage Usage + +To create a custom pool, fill in structure D3D12MA::POOL_DESC and call function D3D12MA::Allocator::CreatePool +to obtain object D3D12MA::Pool. Example: + +\code +POOL_DESC poolDesc = {}; +poolDesc.HeapProperties.Type = D3D12_HEAP_TYPE_DEFAULT; + +Pool* pool; +HRESULT hr = allocator->CreatePool(&poolDesc, &pool); +\endcode + +To allocate resources out of a custom pool, only set member D3D12MA::ALLOCATION_DESC::CustomPool. +Example: + +\code +ALLOCATION_DESC allocDesc = {}; +allocDesc.CustomPool = pool; + +D3D12_RESOURCE_DESC resDesc = ... +Allocation* alloc; +hr = allocator->CreateResource(&allocDesc, &resDesc, + D3D12_RESOURCE_STATE_GENERIC_READ, NULL, &alloc, IID_NULL, NULL); +\endcode + +All allocations must be released before releasing the pool. +The pool must be released before relasing the allocator. + +\code +alloc->Release(); +pool->Release(); +\endcode + +\section custom_pools_features_and_benefits Features and benefits + +While it is recommended to use default pools whenever possible for simplicity and to give the allocator +more opportunities for internal optimizations, custom pools may be useful in following cases: + +- To keep some resources separate from others in memory. +- To keep track of memory usage of just a specific group of resources. %Statistics can be queried using + D3D12MA::Pool::CalculateStatistics. +- To use specific size of a memory block (`ID3D12Heap`). To set it, use member D3D12MA::POOL_DESC::BlockSize. + When set to 0, the library uses automatically determined, variable block sizes. +- To reserve some minimum amount of memory allocated. To use it, set member D3D12MA::POOL_DESC::MinBlockCount. +- To limit maximum amount of memory allocated. To use it, set member D3D12MA::POOL_DESC::MaxBlockCount. +- To use extended parameters of the D3D12 memory allocation. While resources created from default pools + can only specify `D3D12_HEAP_TYPE_DEFAULT`, `UPLOAD`, `READBACK`, a custom pool may use non-standard + `D3D12_HEAP_PROPERTIES` (member D3D12MA::POOL_DESC::HeapProperties) and `D3D12_HEAP_FLAGS` + (D3D12MA::POOL_DESC::HeapFlags), which is useful e.g. for cross-adapter sharing or UMA + (see also D3D12MA::Allocator::IsUMA). + +New versions of this library support creating **committed allocations in custom pools**. +It is supported only when D3D12MA::POOL_DESC::BlockSize = 0. +To use this feature, set D3D12MA::ALLOCATION_DESC::CustomPool to the pointer to your custom pool and +D3D12MA::ALLOCATION_DESC::Flags to D3D12MA::ALLOCATION_FLAG_COMMITTED. Example: + +\code +ALLOCATION_DESC allocDesc = {}; +allocDesc.CustomPool = pool; +allocDesc.Flags = ALLOCATION_FLAG_COMMITTED; + +D3D12_RESOURCE_DESC resDesc = ... +Allocation* alloc; +ID3D12Resource* res; +hr = allocator->CreateResource(&allocDesc, &resDesc, + D3D12_RESOURCE_STATE_GENERIC_READ, NULL, &alloc, IID_PPV_ARGS(&res)); +\endcode + +This feature may seem unnecessary, but creating committed allocations from custom pools may be useful +in some cases, e.g. to have separate memory usage statistics for some group of resources or to use +extended allocation parameters, like custom `D3D12_HEAP_PROPERTIES`, which are available only in custom pools. + + +\page defragmentation Defragmentation + +Interleaved allocations and deallocations of many objects of varying size can +cause fragmentation over time, which can lead to a situation where the library is unable +to find a continuous range of free memory for a new allocation despite there is +enough free space, just scattered across many small free ranges between existing +allocations. + +To mitigate this problem, you can use defragmentation feature. +It doesn't happen automatically though and needs your cooperation, +because %D3D12MA is a low level library that only allocates memory. +It cannot recreate buffers and textures in a new place as it doesn't remember the contents of `D3D12_RESOURCE_DESC` structure. +It cannot copy their contents as it doesn't record any commands to a command list. + +Example: + +\code +D3D12MA::DEFRAGMENTATION_DESC defragDesc = {}; +defragDesc.Flags = D3D12MA::DEFRAGMENTATION_FLAG_ALGORITHM_FAST; + +D3D12MA::DefragmentationContext* defragCtx; +allocator->BeginDefragmentation(&defragDesc, &defragCtx); + +for(;;) +{ + D3D12MA::DEFRAGMENTATION_PASS_MOVE_INFO pass; + HRESULT hr = defragCtx->BeginPass(&pass); + if(hr == S_OK) + break; + else if(hr != S_FALSE) + // Handle error... + + for(UINT i = 0; i < pass.MoveCount; ++i) + { + // Inspect pass.pMoves[i].pSrcAllocation, identify what buffer/texture it represents. + MyEngineResourceData* resData = (MyEngineResourceData*)pMoves[i].pSrcAllocation->GetPrivateData(); + + // Recreate this buffer/texture as placed at pass.pMoves[i].pDstTmpAllocation. + D3D12_RESOURCE_DESC resDesc = ... + ID3D12Resource* newRes; + hr = device->CreatePlacedResource( + pass.pMoves[i].pDstTmpAllocation->GetHeap(), + pass.pMoves[i].pDstTmpAllocation->GetOffset(), &resDesc, + D3D12_RESOURCE_STATE_COPY_DEST, NULL, IID_PPV_ARGS(&newRes)); + // Check hr... + + // Store new resource in the pDstTmpAllocation. + pass.pMoves[i].pDstTmpAllocation->SetResource(newRes); + + // Copy its content to the new place. + cmdList->CopyResource( + pass.pMoves[i].pDstTmpAllocation->GetResource(), + pass.pMoves[i].pSrcAllocation->GetResource()); + } + + // Make sure the copy commands finished executing. + cmdQueue->ExecuteCommandLists(...); + // ... + WaitForSingleObject(fenceEvent, INFINITE); + + // Update appropriate descriptors to point to the new places... + + hr = defragCtx->EndPass(&pass); + if(hr == S_OK) + break; + else if(hr != S_FALSE) + // Handle error... +} + +defragCtx->Release(); +\endcode + +Although functions like D3D12MA::Allocator::CreateResource() +create an allocation and a buffer/texture at once, these are just a shortcut for +allocating memory and creating a placed resource. +Defragmentation works on memory allocations only. You must handle the rest manually. +Defragmentation is an iterative process that should repreat "passes" as long as related functions +return `S_FALSE` not `S_OK`. +In each pass: + +1. D3D12MA::DefragmentationContext::BeginPass() function call: + - Calculates and returns the list of allocations to be moved in this pass. + Note this can be a time-consuming process. + - Reserves destination memory for them by creating temporary destination allocations + that you can query for their `ID3D12Heap` + offset using methods like D3D12MA::Allocation::GetHeap(). +2. Inside the pass, **you should**: + - Inspect the returned list of allocations to be moved. + - Create new buffers/textures as placed at the returned destination temporary allocations. + - Copy data from source to destination resources if necessary. + - Store the pointer to the new resource in the temporary destination allocation. +3. D3D12MA::DefragmentationContext::EndPass() function call: + - Frees the source memory reserved for the allocations that are moved. + - Modifies source D3D12MA::Allocation objects that are moved to point to the destination reserved memory + and destination resource, while source resource is released. + - Frees `ID3D12Heap` blocks that became empty. + +Defragmentation algorithm tries to move all suitable allocations. +You can, however, refuse to move some of them inside a defragmentation pass, by setting +`pass.pMoves[i].Operation` to D3D12MA::DEFRAGMENTATION_MOVE_OPERATION_IGNORE. +This is not recommended and may result in suboptimal packing of the allocations after defragmentation. +If you cannot ensure any allocation can be moved, it is better to keep movable allocations separate in a custom pool. + +Inside a pass, for each allocation that should be moved: + +- You should copy its data from the source to the destination place by calling e.g. `CopyResource()`. + - You need to make sure these commands finished executing before the source buffers/textures are released by D3D12MA::DefragmentationContext::EndPass(). +- If a resource doesn't contain any meaningful data, e.g. it is a transient render-target texture to be cleared, + filled, and used temporarily in each rendering frame, you can just recreate this texture + without copying its data. +- If the resource is in `D3D12_HEAP_TYPE_READBACK` memory, you can copy its data on the CPU + using `memcpy()`. +- If you cannot move the allocation, you can set `pass.pMoves[i].Operation` to D3D12MA::DEFRAGMENTATION_MOVE_OPERATION_IGNORE. + This will cancel the move. + - D3D12MA::DefragmentationContext::EndPass() will then free the destination memory + not the source memory of the allocation, leaving it unchanged. +- If you decide the allocation is unimportant and can be destroyed instead of moved (e.g. it wasn't used for long time), + you can set `pass.pMoves[i].Operation` to D3D12MA::DEFRAGMENTATION_MOVE_OPERATION_DESTROY. + - D3D12MA::DefragmentationContext::EndPass() will then free both source and destination memory, and will destroy the source D3D12MA::Allocation object. + +You can defragment a specific custom pool by calling D3D12MA::Pool::BeginDefragmentation +or all the default pools by calling D3D12MA::Allocator::BeginDefragmentation (like in the example above). + +Defragmentation is always performed in each pool separately. +Allocations are never moved between different heap types. +The size of the destination memory reserved for a moved allocation is the same as the original one. +Alignment of an allocation as it was determined using `GetResourceAllocationInfo()` is also respected after defragmentation. +Buffers/textures should be recreated with the same `D3D12_RESOURCE_DESC` parameters as the original ones. + +You can perform the defragmentation incrementally to limit the number of allocations and bytes to be moved +in each pass, e.g. to call it in sync with render frames and not to experience too big hitches. +See members: D3D12MA::DEFRAGMENTATION_DESC::MaxBytesPerPass, D3D12MA::DEFRAGMENTATION_DESC::MaxAllocationsPerPass. + +Thread safety: +It is safe to perform the defragmentation asynchronously to render frames and other Direct3D 12 and %D3D12MA +usage, possibly from multiple threads, with the exception that allocations +returned in D3D12MA::DEFRAGMENTATION_PASS_MOVE_INFO::pMoves shouldn't be released until the defragmentation pass is ended. +During the call to D3D12MA::DefragmentationContext::BeginPass(), any operations on the memory pool +affected by the defragmentation are blocked by a mutex. + +What it means in practice is that you shouldn't free any allocations from the defragmented pool +since the moment a call to `BeginPass` begins. Otherwise, a thread performing the `allocation->Release()` +would block for the time `BeginPass` executes and then free the allocation when it finishes, while the allocation +could have ended up on the list of allocations to move. +A solution to freeing allocations during defragmentation is to find such allocation on the list +`pass.pMoves[i]` and set its operation to D3D12MA::DEFRAGMENTATION_MOVE_OPERATION_DESTROY instead of +calling `allocation->Release()`, or simply deferring the release to the time after defragmentation finished. + +Mapping is out of scope of this library and so it is not preserved after an allocation is moved during defragmentation. +You need to map the new resource yourself if needed. + +\note Defragmentation is not supported in custom pools created with D3D12MA::POOL_FLAG_ALGORITHM_LINEAR. + + +\page statistics Statistics + +This library contains several functions that return information about its internal state, +especially the amount of memory allocated from D3D12. + +\section statistics_numeric_statistics Numeric statistics + +If you need to obtain basic statistics about memory usage per memory segment group, together with current budget, +you can call function D3D12MA::Allocator::GetBudget() and inspect structure D3D12MA::Budget. +This is useful to keep track of memory usage and stay withing budget. +Example: + +\code +D3D12MA::Budget localBudget; +allocator->GetBudget(&localBudget, NULL); + +printf("My GPU memory currently has %u allocations taking %llu B,\n", + localBudget.Statistics.AllocationCount, + localBudget.Statistics.AllocationBytes); +printf("allocated out of %u D3D12 memory heaps taking %llu B,\n", + localBudget.Statistics.BlockCount, + localBudget.Statistics.BlockBytes); +printf("D3D12 reports total usage %llu B with budget %llu B.\n", + localBudget.UsageBytes, + localBudget.BudgetBytes); +\endcode + +You can query for more detailed statistics per heap type, memory segment group, and totals, +including minimum and maximum allocation size and unused range size, +by calling function D3D12MA::Allocator::CalculateStatistics() and inspecting structure D3D12MA::TotalStatistics. +This function is slower though, as it has to traverse all the internal data structures, +so it should be used only for debugging purposes. + +You can query for statistics of a custom pool using function D3D12MA::Pool::GetStatistics() +or D3D12MA::Pool::CalculateStatistics(). + +You can query for information about a specific allocation using functions of the D3D12MA::Allocation class, +e.g. `GetSize()`, `GetOffset()`, `GetHeap()`. + +\section statistics_json_dump JSON dump + +You can dump internal state of the allocator to a string in JSON format using function D3D12MA::Allocator::BuildStatsString(). +The result is guaranteed to be correct JSON. +It uses Windows Unicode (UTF-16) encoding. +Any strings provided by user (see D3D12MA::Allocation::SetName()) +are copied as-is and properly escaped for JSON. +It must be freed using function D3D12MA::Allocator::FreeStatsString(). + +The format of this JSON string is not part of official documentation of the library, +but it will not change in backward-incompatible way without increasing library major version number +and appropriate mention in changelog. + +The JSON string contains all the data that can be obtained using D3D12MA::Allocator::CalculateStatistics(). +It can also contain detailed map of allocated memory blocks and their regions - +free and occupied by allocations. +This allows e.g. to visualize the memory or assess fragmentation. + + +\page resource_aliasing Resource aliasing (overlap) + +New explicit graphics APIs (Vulkan and Direct3D 12), thanks to manual memory +management, give an opportunity to alias (overlap) multiple resources in the +same region of memory - a feature not available in the old APIs (Direct3D 11, OpenGL). +It can be useful to save video memory, but it must be used with caution. + +For example, if you know the flow of your whole render frame in advance, you +are going to use some intermediate textures or buffers only during a small range of render passes, +and you know these ranges don't overlap in time, you can create these resources in +the same place in memory, even if they have completely different parameters (width, height, format etc.). + +![Resource aliasing (overlap)](../gfx/Aliasing.png) + +Such scenario is possible using D3D12MA, but you need to create your resources +using special function D3D12MA::Allocator::CreateAliasingResource. +Before that, you need to allocate memory with parameters calculated using formula: + +- allocation size = max(size of each resource) +- allocation alignment = max(alignment of each resource) + +Following example shows two different textures created in the same place in memory, +allocated to fit largest of them. + +\code +D3D12_RESOURCE_DESC resDesc1 = {}; +resDesc1.Dimension = D3D12_RESOURCE_DIMENSION_TEXTURE2D; +resDesc1.Alignment = 0; +resDesc1.Width = 1920; +resDesc1.Height = 1080; +resDesc1.DepthOrArraySize = 1; +resDesc1.MipLevels = 1; +resDesc1.Format = DXGI_FORMAT_R8G8B8A8_UNORM; +resDesc1.SampleDesc.Count = 1; +resDesc1.SampleDesc.Quality = 0; +resDesc1.Layout = D3D12_TEXTURE_LAYOUT_UNKNOWN; +resDesc1.Flags = D3D12_RESOURCE_FLAG_ALLOW_RENDER_TARGET | D3D12_RESOURCE_FLAG_ALLOW_UNORDERED_ACCESS; + +D3D12_RESOURCE_DESC resDesc2 = {}; +resDesc2.Dimension = D3D12_RESOURCE_DIMENSION_TEXTURE2D; +resDesc2.Alignment = 0; +resDesc2.Width = 1024; +resDesc2.Height = 1024; +resDesc2.DepthOrArraySize = 1; +resDesc2.MipLevels = 0; +resDesc2.Format = DXGI_FORMAT_R8G8B8A8_UNORM; +resDesc2.SampleDesc.Count = 1; +resDesc2.SampleDesc.Quality = 0; +resDesc2.Layout = D3D12_TEXTURE_LAYOUT_UNKNOWN; +resDesc2.Flags = D3D12_RESOURCE_FLAG_ALLOW_RENDER_TARGET; + +const D3D12_RESOURCE_ALLOCATION_INFO allocInfo1 = + device->GetResourceAllocationInfo(0, 1, &resDesc1); +const D3D12_RESOURCE_ALLOCATION_INFO allocInfo2 = + device->GetResourceAllocationInfo(0, 1, &resDesc2); + +D3D12_RESOURCE_ALLOCATION_INFO finalAllocInfo = {}; +finalAllocInfo.Alignment = std::max(allocInfo1.Alignment, allocInfo2.Alignment); +finalAllocInfo.SizeInBytes = std::max(allocInfo1.SizeInBytes, allocInfo2.SizeInBytes); + +D3D12MA::ALLOCATION_DESC allocDesc = {}; +allocDesc.HeapType = D3D12_HEAP_TYPE_DEFAULT; +allocDesc.ExtraHeapFlags = D3D12_HEAP_FLAG_ALLOW_ONLY_RT_DS_TEXTURES; + +D3D12MA::Allocation* alloc; +hr = allocator->AllocateMemory(&allocDesc, &finalAllocInfo, &alloc); +assert(alloc != NULL && alloc->GetHeap() != NULL); + +ID3D12Resource* res1; +hr = allocator->CreateAliasingResource( + alloc, + 0, // AllocationLocalOffset + &resDesc1, + D3D12_RESOURCE_STATE_COMMON, + NULL, // pOptimizedClearValue + IID_PPV_ARGS(&res1)); + +ID3D12Resource* res2; +hr = allocator->CreateAliasingResource( + alloc, + 0, // AllocationLocalOffset + &resDesc2, + D3D12_RESOURCE_STATE_COMMON, + NULL, // pOptimizedClearValue + IID_PPV_ARGS(&res2)); + +// You can use res1 and res2, but not at the same time! + +res2->Release(); +res1->Release(); +alloc->Release(); +\endcode + +Remember that using resouces that alias in memory requires proper synchronization. +You need to issue a special barrier of type `D3D12_RESOURCE_BARRIER_TYPE_ALIASING`. +You also need to treat a resource after aliasing as uninitialized - containing garbage data. +For example, if you use `res1` and then want to use `res2`, you need to first initialize `res2` +using either Clear, Discard, or Copy to the entire resource. + +Additional considerations: + +- D3D12 also allows to interpret contents of memory between aliasing resources consistently in some cases, + which is called "data inheritance". For details, see + Microsoft documentation chapter "Memory Aliasing and Data Inheritance". +- You can create more complex layout where different textures and buffers are bound + at different offsets inside one large allocation. For example, one can imagine + a big texture used in some render passes, aliasing with a set of many small buffers + used in some further passes. To bind a resource at non-zero offset of an allocation, + call D3D12MA::Allocator::CreateAliasingResource with appropriate value of `AllocationLocalOffset` parameter. +- Resources of the three categories: buffers, textures with `RENDER_TARGET` or `DEPTH_STENCIL` flags, and all other textures, + can be placed in the same memory only when `allocator->GetD3D12Options().ResourceHeapTier >= D3D12_RESOURCE_HEAP_TIER_2`. + Otherwise they must be placed in different memory heap types, and thus aliasing them is not possible. + + +\page linear_algorithm Linear allocation algorithm + +Each D3D12 memory block managed by this library has accompanying metadata that +keeps track of used and unused regions. By default, the metadata structure and +algorithm tries to find best place for new allocations among free regions to +optimize memory usage. This way you can allocate and free objects in any order. + +![Default allocation algorithm](../gfx/Linear_allocator_1_algo_default.png) + +Sometimes there is a need to use simpler, linear allocation algorithm. You can +create custom pool that uses such algorithm by adding flag +D3D12MA::POOL_FLAG_ALGORITHM_LINEAR to D3D12MA::POOL_DESC::Flags while creating +D3D12MA::Pool object. Then an alternative metadata management is used. It always +creates new allocations after last one and doesn't reuse free regions after +allocations freed in the middle. It results in better allocation performance and +less memory consumed by metadata. + +![Linear allocation algorithm](../gfx/Linear_allocator_2_algo_linear.png) + +With this one flag, you can create a custom pool that can be used in many ways: +free-at-once, stack, double stack, and ring buffer. See below for details. +You don't need to specify explicitly which of these options you are going to use - it is detected automatically. + +\section linear_algorithm_free_at_once Free-at-once + +In a pool that uses linear algorithm, you still need to free all the allocations +individually by calling `allocation->Release()`. You can free +them in any order. New allocations are always made after last one - free space +in the middle is not reused. However, when you release all the allocation and +the pool becomes empty, allocation starts from the beginning again. This way you +can use linear algorithm to speed up creation of allocations that you are going +to release all at once. + +![Free-at-once](../gfx/Linear_allocator_3_free_at_once.png) + +This mode is also available for pools created with D3D12MA::POOL_DESC::MaxBlockCount +value that allows multiple memory blocks. + +\section linear_algorithm_stack Stack + +When you free an allocation that was created last, its space can be reused. +Thanks to this, if you always release allocations in the order opposite to their +creation (LIFO - Last In First Out), you can achieve behavior of a stack. + +![Stack](../gfx/Linear_allocator_4_stack.png) + +This mode is also available for pools created with D3D12MA::POOL_DESC::MaxBlockCount +value that allows multiple memory blocks. + +\section linear_algorithm_double_stack Double stack + +The space reserved by a custom pool with linear algorithm may be used by two +stacks: + +- First, default one, growing up from offset 0. +- Second, "upper" one, growing down from the end towards lower offsets. + +To make allocation from the upper stack, add flag D3D12MA::ALLOCATION_FLAG_UPPER_ADDRESS +to D3D12MA::ALLOCATION_DESC::Flags. + +![Double stack](../gfx/Linear_allocator_7_double_stack.png) + +Double stack is available only in pools with one memory block - +D3D12MA::POOL_DESC::MaxBlockCount must be 1. Otherwise behavior is undefined. + +When the two stacks' ends meet so there is not enough space between them for a +new allocation, such allocation fails with usual `E_OUTOFMEMORY` error. + +\section linear_algorithm_ring_buffer Ring buffer + +When you free some allocations from the beginning and there is not enough free space +for a new one at the end of a pool, allocator's "cursor" wraps around to the +beginning and starts allocation there. Thanks to this, if you always release +allocations in the same order as you created them (FIFO - First In First Out), +you can achieve behavior of a ring buffer / queue. + +![Ring buffer](../gfx/Linear_allocator_5_ring_buffer.png) + +Ring buffer is available only in pools with one memory block - +D3D12MA::POOL_DESC::MaxBlockCount must be 1. Otherwise behavior is undefined. + +\section linear_algorithm_additional_considerations Additional considerations + +Linear algorithm can also be used with \ref virtual_allocator. +See flag D3D12MA::VIRTUAL_BLOCK_FLAG_ALGORITHM_LINEAR. + + +\page virtual_allocator Virtual allocator + +As an extra feature, the core allocation algorithm of the library is exposed through a simple and convenient API of "virtual allocator". +It doesn't allocate any real GPU memory. It just keeps track of used and free regions of a "virtual block". +You can use it to allocate your own memory or other objects, even completely unrelated to D3D12. +A common use case is sub-allocation of pieces of one large GPU buffer. + +\section virtual_allocator_creating_virtual_block Creating virtual block + +To use this functionality, there is no main "allocator" object. +You don't need to have D3D12MA::Allocator object created. +All you need to do is to create a separate D3D12MA::VirtualBlock object for each block of memory you want to be managed by the allocator: + +-# Fill in D3D12MA::ALLOCATOR_DESC structure. +-# Call D3D12MA::CreateVirtualBlock. Get new D3D12MA::VirtualBlock object. + +Example: + +\code +D3D12MA::VIRTUAL_BLOCK_DESC blockDesc = {}; +blockDesc.Size = 1048576; // 1 MB + +D3D12MA::VirtualBlock *block; +HRESULT hr = CreateVirtualBlock(&blockDesc, &block); +\endcode + +\section virtual_allocator_making_virtual_allocations Making virtual allocations + +D3D12MA::VirtualBlock object contains internal data structure that keeps track of free and occupied regions +using the same code as the main D3D12 memory allocator. +A single allocation is identified by a lightweight structure D3D12MA::VirtualAllocation. +You will also likely want to know the offset at which the allocation was made in the block. + +In order to make an allocation: + +-# Fill in D3D12MA::VIRTUAL_ALLOCATION_DESC structure. +-# Call D3D12MA::VirtualBlock::Allocate. Get new D3D12MA::VirtualAllocation value that identifies the allocation. + +Example: + +\code +D3D12MA::VIRTUAL_ALLOCATION_DESC allocDesc = {}; +allocDesc.Size = 4096; // 4 KB + +D3D12MA::VirtualAllocation alloc; +UINT64 allocOffset; +hr = block->Allocate(&allocDesc, &alloc, &allocOffset); +if(SUCCEEDED(hr)) +{ + // Use the 4 KB of your memory starting at allocOffset. +} +else +{ + // Allocation failed - no space for it could be found. Handle this error! +} +\endcode + +\section virtual_allocator_deallocation Deallocation + +When no longer needed, an allocation can be freed by calling D3D12MA::VirtualBlock::FreeAllocation. + +When whole block is no longer needed, the block object can be released by calling `block->Release()`. +All allocations must be freed before the block is destroyed, which is checked internally by an assert. +However, if you don't want to call `block->FreeAllocation` for each allocation, you can use D3D12MA::VirtualBlock::Clear to free them all at once - +a feature not available in normal D3D12 memory allocator. + +Example: + +\code +block->FreeAllocation(alloc); +block->Release(); +\endcode + +\section virtual_allocator_allocation_parameters Allocation parameters + +You can attach a custom pointer to each allocation by using D3D12MA::VirtualBlock::SetAllocationPrivateData. +Its default value is `NULL`. +It can be used to store any data that needs to be associated with that allocation - e.g. an index, a handle, or a pointer to some +larger data structure containing more information. Example: + +\code +struct CustomAllocData +{ + std::string m_AllocName; +}; +CustomAllocData* allocData = new CustomAllocData(); +allocData->m_AllocName = "My allocation 1"; +block->SetAllocationPrivateData(alloc, allocData); +\endcode + +The pointer can later be fetched, along with allocation offset and size, by passing the allocation handle to function +D3D12MA::VirtualBlock::GetAllocationInfo and inspecting returned structure D3D12MA::VIRTUAL_ALLOCATION_INFO. +If you allocated a new object to be used as the custom pointer, don't forget to delete that object before freeing the allocation! +Example: + +\code +VIRTUAL_ALLOCATION_INFO allocInfo; +block->GetAllocationInfo(alloc, &allocInfo); +delete (CustomAllocData*)allocInfo.pPrivateData; + +block->FreeAllocation(alloc); +\endcode + +\section virtual_allocator_alignment_and_units Alignment and units + +It feels natural to express sizes and offsets in bytes. +If an offset of an allocation needs to be aligned to a multiply of some number (e.g. 4 bytes), you can fill optional member +D3D12MA::VIRTUAL_ALLOCATION_DESC::Alignment to request it. Example: + +\code +D3D12MA::VIRTUAL_ALLOCATION_DESC allocDesc = {}; +allocDesc.Size = 4096; // 4 KB +allocDesc.Alignment = 4; // Returned offset must be a multiply of 4 B + +D3D12MA::VirtualAllocation alloc; +UINT64 allocOffset; +hr = block->Allocate(&allocDesc, &alloc, &allocOffset); +\endcode + +Alignments of different allocations made from one block may vary. +However, if all alignments and sizes are always multiply of some size e.g. 4 B or `sizeof(MyDataStruct)`, +you can express all sizes, alignments, and offsets in multiples of that size instead of individual bytes. +It might be more convenient, but you need to make sure to use this new unit consistently in all the places: + +- D3D12MA::VIRTUAL_BLOCK_DESC::Size +- D3D12MA::VIRTUAL_ALLOCATION_DESC::Size and D3D12MA::VIRTUAL_ALLOCATION_DESC::Alignment +- Using offset returned by D3D12MA::VirtualBlock::Allocate and D3D12MA::VIRTUAL_ALLOCATION_INFO::Offset + +\section virtual_allocator_statistics Statistics + +You can obtain brief statistics of a virtual block using D3D12MA::VirtualBlock::GetStatistics(). +The function fills structure D3D12MA::Statistics - same as used by the normal D3D12 memory allocator. +Example: + +\code +D3D12MA::Statistics stats; +block->GetStatistics(&stats); +printf("My virtual block has %llu bytes used by %u virtual allocations\n", + stats.AllocationBytes, stats.AllocationCount); +\endcode + +More detailed statistics can be obtained using function D3D12MA::VirtualBlock::CalculateStatistics(), +but they are slower to calculate. + +You can also request a full list of allocations and free regions as a string in JSON format by calling +D3D12MA::VirtualBlock::BuildStatsString. +Returned string must be later freed using D3D12MA::VirtualBlock::FreeStatsString. +The format of this string may differ from the one returned by the main D3D12 allocator, but it is similar. + +\section virtual_allocator_additional_considerations Additional considerations + +Alternative, linear algorithm can be used with virtual allocator - see flag +D3D12MA::VIRTUAL_BLOCK_FLAG_ALGORITHM_LINEAR and documentation: \ref linear_algorithm. + +Note that the "virtual allocator" functionality is implemented on a level of individual memory blocks. +Keeping track of a whole collection of blocks, allocating new ones when out of free space, +deleting empty ones, and deciding which one to try first for a new allocation must be implemented by the user. + + +\page configuration Configuration + +Please check file `D3D12MemAlloc.cpp` lines between "Configuration Begin" and +"Configuration End" to find macros that you can define to change the behavior of +the library, primarily for debugging purposes. + +\section custom_memory_allocator Custom CPU memory allocator + +If you use custom allocator for CPU memory rather than default C++ operator `new` +and `delete` or `malloc` and `free` functions, you can make this library using +your allocator as well by filling structure D3D12MA::ALLOCATION_CALLBACKS and +passing it as optional member D3D12MA::ALLOCATOR_DESC::pAllocationCallbacks. +Functions pointed there will be used by the library to make any CPU-side +allocations. Example: + +\code +#include + +void* CustomAllocate(size_t Size, size_t Alignment, void* pPrivateData) +{ + void* memory = _aligned_malloc(Size, Alignment); + // Your extra bookkeeping here... + return memory; +} + +void CustomFree(void* pMemory, void* pPrivateData) +{ + // Your extra bookkeeping here... + _aligned_free(pMemory); +} + +(...) + +D3D12MA::ALLOCATION_CALLBACKS allocationCallbacks = {}; +allocationCallbacks.pAllocate = &CustomAllocate; +allocationCallbacks.pFree = &CustomFree; + +D3D12MA::ALLOCATOR_DESC allocatorDesc = {}; +allocatorDesc.pDevice = device; +allocatorDesc.pAdapter = adapter; +allocatorDesc.pAllocationCallbacks = &allocationCallbacks; + +D3D12MA::Allocator* allocator; +HRESULT hr = D3D12MA::CreateAllocator(&allocatorDesc, &allocator); +\endcode + + +\section debug_margins Debug margins + +By default, allocations are laid out in memory blocks next to each other if possible +(considering required alignment returned by `ID3D12Device::GetResourceAllocationInfo`). + +![Allocations without margin](../gfx/Margins_1.png) + +Define macro `D3D12MA_DEBUG_MARGIN` to some non-zero value (e.g. 16) inside "D3D12MemAlloc.cpp" +to enforce specified number of bytes as a margin after every allocation. + +![Allocations with margin](../gfx/Margins_2.png) + +If your bug goes away after enabling margins, it means it may be caused by memory +being overwritten outside of allocation boundaries. It is not 100% certain though. +Change in application behavior may also be caused by different order and distribution +of allocations across memory blocks after margins are applied. + +Margins work with all memory heap types. + +Margin is applied only to placed allocations made out of memory heaps and not to committed +allocations, which have their own, implicit memory heap of specific size. +It is thus not applied to allocations made using D3D12MA::ALLOCATION_FLAG_COMMITTED flag +or those automatically decided to put into committed allocations, e.g. due to its large size. + +Margins appear in [JSON dump](@ref statistics_json_dump) as part of free space. + +Note that enabling margins increases memory usage and fragmentation. + +Margins do not apply to \ref virtual_allocator. + + +\page general_considerations General considerations + +\section general_considerations_thread_safety Thread safety + +- The library has no global state, so separate D3D12MA::Allocator objects can be used independently. + In typical applications there should be no need to create multiple such objects though - one per `ID3D12Device` is enough. +- All calls to methods of D3D12MA::Allocator class are safe to be made from multiple + threads simultaneously because they are synchronized internally when needed. +- When the allocator is created with D3D12MA::ALLOCATOR_FLAG_SINGLETHREADED, + calls to methods of D3D12MA::Allocator class must be made from a single thread or synchronized by the user. + Using this flag may improve performance. +- D3D12MA::VirtualBlock is not safe to be used from multiple threads simultaneously. + +\section general_considerations_versioning_and_compatibility Versioning and compatibility + +The library uses [**Semantic Versioning**](https://semver.org/), +which means version numbers follow convention: Major.Minor.Patch (e.g. 2.3.0), where: + +- Incremented Patch version means a release is backward- and forward-compatible, + introducing only some internal improvements, bug fixes, optimizations etc. + or changes that are out of scope of the official API described in this documentation. +- Incremented Minor version means a release is backward-compatible, + so existing code that uses the library should continue to work, while some new + symbols could have been added: new structures, functions, new values in existing + enums and bit flags, new structure members, but not new function parameters. +- Incrementing Major version means a release could break some backward compatibility. + +All changes between official releases are documented in file "CHANGELOG.md". + +\warning Backward compatiblity is considered on the level of C++ source code, not binary linkage. +Adding new members to existing structures is treated as backward compatible if initializing +the new members to binary zero results in the old behavior. +You should always fully initialize all library structures to zeros and not rely on their +exact binary size. + +\section general_considerations_features_not_supported Features not supported + +Features deliberately excluded from the scope of this library: + +- **Descriptor allocation.** Although also called "heaps", objects that represent + descriptors are separate part of the D3D12 API from buffers and textures. + You can still use \ref virtual_allocator to manage descriptors and their ranges inside a descriptor heap. +- **Support for reserved (tiled) resources.** We don't recommend using them. +- Support for `ID3D12Device::Evict` and `MakeResident`. We don't recommend using them. + You can call them on the D3D12 objects manually. + Plese keep in mind, however, that eviction happens on the level of entire `ID3D12Heap` memory blocks + and not individual buffers or textures which may be placed inside them. +- **Handling CPU memory allocation failures.** When dynamically creating small C++ + objects in CPU memory (not the GPU memory), allocation failures are not + handled gracefully, because that would complicate code significantly and + is usually not needed in desktop PC applications anyway. + Success of an allocation is just checked with an assert. +- **Code free of any compiler warnings.** + There are many preprocessor macros that make some variables unused, function parameters unreferenced, + or conditional expressions constant in some configurations. + The code of this library should not be bigger or more complicated just to silence these warnings. + It is recommended to disable such warnings instead. +- This is a C++ library. **Bindings or ports to any other programming languages** are welcome as external projects but + are not going to be included into this repository. +*/ diff --git a/src/3rdparty/D3D12MemoryAllocator/LICENSE.txt b/src/3rdparty/D3D12MemoryAllocator/LICENSE.txt new file mode 100644 index 00000000..bc2ab4dc --- /dev/null +++ b/src/3rdparty/D3D12MemoryAllocator/LICENSE.txt @@ -0,0 +1,19 @@ +Copyright (c) 2019-2022 Advanced Micro Devices, Inc. All rights reserved. + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. diff --git a/src/3rdparty/D3D12MemoryAllocator/patches/0001-Eliminate-warnings-in-D3D12MA.patch b/src/3rdparty/D3D12MemoryAllocator/patches/0001-Eliminate-warnings-in-D3D12MA.patch new file mode 100644 index 00000000..5004c09d --- /dev/null +++ b/src/3rdparty/D3D12MemoryAllocator/patches/0001-Eliminate-warnings-in-D3D12MA.patch @@ -0,0 +1,91 @@ +From d83bc556c26b13e1a243c71628f75ef624de05bf Mon Sep 17 00:00:00 2001 +From: Laszlo Agocs +Date: Sat, 21 Jan 2023 20:07:00 +0100 +Subject: [PATCH] Eliminate warnings in D3D12MA + +Change-Id: If703c50cc1239248b94967edb4047868aaf07f1a +--- + .../D3D12MemoryAllocator/D3D12MemAlloc.cpp | 23 ++++++++++++++++++- + .../D3D12MemoryAllocator/D3D12MemAlloc.h | 6 ++--- + 2 files changed, 25 insertions(+), 4 deletions(-) + +diff --git a/src/3rdparty/D3D12MemoryAllocator/D3D12MemAlloc.cpp b/src/3rdparty/D3D12MemoryAllocator/D3D12MemAlloc.cpp +index fe1856927f..f041ec13d8 100644 +--- a/src/3rdparty/D3D12MemoryAllocator/D3D12MemAlloc.cpp ++++ b/src/3rdparty/D3D12MemoryAllocator/D3D12MemAlloc.cpp +@@ -132,6 +132,18 @@ especially to test compatibility with D3D12_RESOURCE_HEAP_TIER_1 on modern GPUs. + #define D3D12MA_CREATE_NOT_ZEROED_AVAILABLE 1 + #endif + ++#if defined(__clang__) || defined(__GNUC__) ++#pragma GCC diagnostic push ++#pragma GCC diagnostic ignored "-Wunused-parameter" ++#pragma GCC diagnostic ignored "-Wunused-variable" ++#pragma GCC diagnostic ignored "-Wsign-compare" ++#pragma GCC diagnostic ignored "-Wmissing-field-initializers" ++#pragma GCC diagnostic ignored "-Wswitch" ++#pragma GCC diagnostic ignored "-Wimplicit-fallthrough" ++#pragma GCC diagnostic ignored "-Wunused-function" ++#pragma GCC diagnostic ignored "-Wnonnull-compare" ++#endif ++ + namespace D3D12MA + { + static constexpr UINT HEAP_TYPE_COUNT = 4; +@@ -7581,12 +7593,14 @@ void AllocatorPimpl::BuildStatsString(WCHAR** ppStatsString, BOOL detailedMap) + json.WriteString(L"HEAP_FLAG_ALLOW_DISPLAY"); + if (flags & D3D12_HEAP_FLAG_SHARED_CROSS_ADAPTER) + json.WriteString(L"HEAP_FLAG_CROSS_ADAPTER"); ++#ifdef __ID3D12Device8_INTERFACE_DEFINED__ + if (flags & D3D12_HEAP_FLAG_HARDWARE_PROTECTED) + json.WriteString(L"HEAP_FLAG_HARDWARE_PROTECTED"); + if (flags & D3D12_HEAP_FLAG_ALLOW_WRITE_WATCH) + json.WriteString(L"HEAP_FLAG_ALLOW_WRITE_WATCH"); + if (flags & D3D12_HEAP_FLAG_ALLOW_SHADER_ATOMICS) + json.WriteString(L"HEAP_FLAG_ALLOW_SHADER_ATOMICS"); ++#endif + #ifdef __ID3D12Device8_INTERFACE_DEFINED__ + if (flags & D3D12_HEAP_FLAG_CREATE_NOT_RESIDENT) + json.WriteString(L"HEAP_FLAG_CREATE_NOT_RESIDENT"); +@@ -7607,9 +7621,12 @@ void AllocatorPimpl::BuildStatsString(WCHAR** ppStatsString, BOOL detailedMap) + | D3D12_HEAP_FLAG_SHARED_CROSS_ADAPTER + | D3D12_HEAP_FLAG_DENY_RT_DS_TEXTURES + | D3D12_HEAP_FLAG_DENY_NON_RT_DS_TEXTURES ++#ifdef __ID3D12Device8_INTERFACE_DEFINED__ + | D3D12_HEAP_FLAG_HARDWARE_PROTECTED + | D3D12_HEAP_FLAG_ALLOW_WRITE_WATCH +- | D3D12_HEAP_FLAG_ALLOW_SHADER_ATOMICS); ++ | D3D12_HEAP_FLAG_ALLOW_SHADER_ATOMICS ++#endif ++ ); + #ifdef __ID3D12Device8_INTERFACE_DEFINED__ + flags &= ~(D3D12_HEAP_FLAG_CREATE_NOT_RESIDENT + | D3D12_HEAP_FLAG_CREATE_NOT_ZEROED); +@@ -10539,3 +10556,7 @@ VirtualBlock::~VirtualBlock() + #endif // _D3D12MA_VIRTUAL_BLOCK_FUNCTIONS + #endif // _D3D12MA_PUBLIC_INTERFACE + } // namespace D3D12MA ++ ++#if defined(__clang__) || defined(__GNUC__) ++#pragma GCC diagnostic pop ++#endif +diff --git a/src/3rdparty/D3D12MemoryAllocator/D3D12MemAlloc.h b/src/3rdparty/D3D12MemoryAllocator/D3D12MemAlloc.h +index 4ab7be318e..d80dcb1e89 100644 +--- a/src/3rdparty/D3D12MemoryAllocator/D3D12MemAlloc.h ++++ b/src/3rdparty/D3D12MemoryAllocator/D3D12MemAlloc.h +@@ -151,9 +151,9 @@ class D3D12MA_API IUnknownImpl : public IUnknown + { + public: + virtual ~IUnknownImpl() = default; +- virtual HRESULT STDMETHODCALLTYPE QueryInterface(REFIID riid, void** ppvObject); +- virtual ULONG STDMETHODCALLTYPE AddRef(); +- virtual ULONG STDMETHODCALLTYPE Release(); ++ HRESULT STDMETHODCALLTYPE QueryInterface(REFIID riid, void** ppvObject) override; ++ ULONG STDMETHODCALLTYPE AddRef() override; ++ ULONG STDMETHODCALLTYPE Release() override; + protected: + virtual void ReleaseThis() { delete this; } + private: +-- +2.33.0.windows.2 + diff --git a/src/3rdparty/D3D12MemoryAllocator/qt_attribution.json b/src/3rdparty/D3D12MemoryAllocator/qt_attribution.json new file mode 100644 index 00000000..3bb1be82 --- /dev/null +++ b/src/3rdparty/D3D12MemoryAllocator/qt_attribution.json @@ -0,0 +1,16 @@ +[ + { + "Id": "D3D12MemoryAllocator", + "Name": "D3D12 Memory Allocator", + "QDocModule": "qtgui", + "Description": "D3D12 Memory Allocator", + "QtUsage": "Memory management for the D3D12 backend of QRhi.", + + "Homepage": "https://github.com/GPUOpen-LibrariesAndSDKs/D3D12MemoryAllocator", + "Version": "f128d39b7a95b4235bd228d231646278dc6c24b2", + "License": "MIT License", + "LicenseId": "MIT", + "LicenseFile": "LICENSE.txt", + "Copyright": "Copyright (c) 2019-2022 Advanced Micro Devices, Inc. All rights reserved." + } +] diff --git a/src/3rdparty/gradle/gradle.properties b/src/3rdparty/gradle/gradle.properties index 263d7023..4fe1674a 100644 --- a/src/3rdparty/gradle/gradle.properties +++ b/src/3rdparty/gradle/gradle.properties @@ -3,7 +3,7 @@ # http://www.gradle.org/docs/current/userguide/build_environment.html # Specifies the JVM arguments used for the daemon process. # The setting is particularly useful for tweaking memory settings. -org.gradle.jvmargs=-Xmx2500m -XX:MaxMetaspaceSize=512m -XX:+HeapDumpOnOutOfMemoryError -Dfile.encoding=UTF-8 +org.gradle.jvmargs=-Xmx2500m -XX:MaxMetaspaceSize=768m -XX:+HeapDumpOnOutOfMemoryError -Dfile.encoding=UTF-8 # Enable building projects in parallel org.gradle.parallel=true @@ -12,3 +12,7 @@ org.gradle.parallel=true # build with the same inputs. However, over time, the cache size will # grow. Uncomment the following line to enable it. #org.gradle.caching=true +#org.gradle.configuration-cache=true + +# Allow AndroidX usage +android.useAndroidX=true diff --git a/src/3rdparty/gradle/gradle/wrapper/gradle-wrapper.jar b/src/3rdparty/gradle/gradle/wrapper/gradle-wrapper.jar index 41d9927a..7f93135c 100644 Binary files a/src/3rdparty/gradle/gradle/wrapper/gradle-wrapper.jar and b/src/3rdparty/gradle/gradle/wrapper/gradle-wrapper.jar differ diff --git a/src/3rdparty/gradle/gradle/wrapper/gradle-wrapper.properties b/src/3rdparty/gradle/gradle/wrapper/gradle-wrapper.properties index da1db5f0..ac72c34e 100644 --- a/src/3rdparty/gradle/gradle/wrapper/gradle-wrapper.properties +++ b/src/3rdparty/gradle/gradle/wrapper/gradle-wrapper.properties @@ -1,5 +1,7 @@ distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-8.0-bin.zip +distributionUrl=https\://services.gradle.org/distributions/gradle-8.3-bin.zip +networkTimeout=10000 +validateDistributionUrl=true zipStoreBase=GRADLE_USER_HOME zipStorePath=wrapper/dists diff --git a/src/3rdparty/gradle/gradlew b/src/3rdparty/gradle/gradlew index 005bcde0..c22a5176 100644 --- a/src/3rdparty/gradle/gradlew +++ b/src/3rdparty/gradle/gradlew @@ -55,7 +55,7 @@ # Darwin, MinGW, and NonStop. # # (3) This script is generated from the Groovy template -# https://github.com/gradle/gradle/blob/master/subprojects/plugins/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt +# https://github.com/gradle/gradle/blob/HEAD/subprojects/plugins/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt # within the Gradle project. # # You can find Gradle at https://github.com/gradle/gradle/. @@ -80,13 +80,11 @@ do esac done -APP_HOME=$( cd "${APP_HOME:-./}" && pwd -P ) || exit - -APP_NAME="Gradle" +# This is normally unused +# shellcheck disable=SC2034 APP_BASE_NAME=${0##*/} - -# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. -DEFAULT_JVM_OPTS='-Dfile.encoding=UTF-8 "-Xmx64m" "-Xms64m"' +# Discard cd standard output in case $CDPATH is set (https://github.com/gradle/gradle/issues/25036) +APP_HOME=$( cd "${APP_HOME:-./}" > /dev/null && pwd -P ) || exit # Use the maximum available, or set MAX_FD != -1 to use that value. MAX_FD=maximum @@ -133,22 +131,29 @@ location of your Java installation." fi else JAVACMD=java - which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. + if ! command -v java >/dev/null 2>&1 + then + die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. Please set the JAVA_HOME variable in your environment to match the location of your Java installation." + fi fi # Increase the maximum file descriptors if we can. if ! "$cygwin" && ! "$darwin" && ! "$nonstop" ; then case $MAX_FD in #( max*) + # In POSIX sh, ulimit -H is undefined. That's why the result is checked to see if it worked. + # shellcheck disable=SC3045 MAX_FD=$( ulimit -H -n ) || warn "Could not query maximum file descriptor limit" esac case $MAX_FD in #( '' | soft) :;; #( *) + # In POSIX sh, ulimit -n is undefined. That's why the result is checked to see if it worked. + # shellcheck disable=SC3045 ulimit -n "$MAX_FD" || warn "Could not set maximum file descriptor limit to $MAX_FD" esac @@ -193,6 +198,10 @@ if "$cygwin" || "$msys" ; then done fi + +# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. +DEFAULT_JVM_OPTS='-Dfile.encoding=UTF-8 "-Xmx64m" "-Xms64m"' + # Collect all arguments for the java command; # * $DEFAULT_JVM_OPTS, $JAVA_OPTS, and $GRADLE_OPTS can contain fragments of # shell script including quotes and variable substitutions, so put them in @@ -205,6 +214,12 @@ set -- \ org.gradle.wrapper.GradleWrapperMain \ "$@" +# Stop when "xargs" is not available. +if ! command -v xargs >/dev/null 2>&1 +then + die "xargs is not available" +fi + # Use "xargs" to parse quoted args. # # With -n1 it outputs one arg per line, with the quotes and backslashes removed. diff --git a/src/3rdparty/gradle/gradlew.bat b/src/3rdparty/gradle/gradlew.bat index 6a68175e..ea603b41 100644 --- a/src/3rdparty/gradle/gradlew.bat +++ b/src/3rdparty/gradle/gradlew.bat @@ -14,7 +14,7 @@ @rem limitations under the License. @rem -@if "%DEBUG%" == "" @echo off +@if "%DEBUG%"=="" @echo off @rem ########################################################################## @rem @rem Gradle startup script for Windows @@ -25,7 +25,8 @@ if "%OS%"=="Windows_NT" setlocal set DIRNAME=%~dp0 -if "%DIRNAME%" == "" set DIRNAME=. +if "%DIRNAME%"=="" set DIRNAME=. +@rem This is normally unused set APP_BASE_NAME=%~n0 set APP_HOME=%DIRNAME% @@ -40,7 +41,7 @@ if defined JAVA_HOME goto findJavaFromJavaHome set JAVA_EXE=java.exe %JAVA_EXE% -version >NUL 2>&1 -if "%ERRORLEVEL%" == "0" goto execute +if %ERRORLEVEL% equ 0 goto execute echo. echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. @@ -75,13 +76,15 @@ set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar :end @rem End local scope for the variables with windows NT shell -if "%ERRORLEVEL%"=="0" goto mainEnd +if %ERRORLEVEL% equ 0 goto mainEnd :fail rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of rem the _cmd.exe /c_ return code! -if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1 -exit /b 1 +set EXIT_CODE=%ERRORLEVEL% +if %EXIT_CODE% equ 0 set EXIT_CODE=1 +if not ""=="%GRADLE_EXIT_CONSOLE%" exit %EXIT_CODE% +exit /b %EXIT_CODE% :mainEnd if "%OS%"=="Windows_NT" endlocal diff --git a/src/3rdparty/gradle/qt_attribution.json b/src/3rdparty/gradle/qt_attribution.json index 5f5d434b..8e759c88 100644 --- a/src/3rdparty/gradle/qt_attribution.json +++ b/src/3rdparty/gradle/qt_attribution.json @@ -4,8 +4,8 @@ "QDocModule": "qtcore", "QtParts": ["tools"], "Homepage": "https://gradle.org", - "Version": "8.0", - "DownloadLocation": "https://github.com/gradle/gradle/releases/tag/v8.0", + "Version": "8.3", + "DownloadLocation": "https://github.com/gradle/gradle/releases/tag/v8.3.0", "QtUsage": "Needed to create Android packages", "License": "Apache License 2.0", "LicenseId": "Apache-2.0", diff --git a/src/android/jar/CMakeLists.txt b/src/android/jar/CMakeLists.txt index 81eafc87..3e05d7a3 100644 --- a/src/android/jar/CMakeLists.txt +++ b/src/android/jar/CMakeLists.txt @@ -30,6 +30,7 @@ qt_internal_add_jar(Qt${QtBase_VERSION_MAJOR}Android ) qt_path_join(destination ${INSTALL_DATADIR} "jar") + install_jar(Qt${QtBase_VERSION_MAJOR}Android DESTINATION ${destination} COMPONENT Devel diff --git a/src/android/jar/src/org/qtproject/qt/android/QtMessageDialogHelper.java b/src/android/jar/src/org/qtproject/qt/android/QtMessageDialogHelper.java index 58bcbf06..26696577 100644 --- a/src/android/jar/src/org/qtproject/qt/android/QtMessageDialogHelper.java +++ b/src/android/jar/src/org/qtproject/qt/android/QtMessageDialogHelper.java @@ -59,15 +59,15 @@ public class QtMessageDialogHelper } - public void setIcon(int icon) + public void setStandardIcon(int icon) { - m_icon = icon; + m_standardIcon = icon; } private Drawable getIconDrawable() { - if (m_icon == 0) + if (m_standardIcon == 0) return null; try { @@ -80,7 +80,7 @@ public class QtMessageDialogHelper } // Information, Warning, Critical, Question - switch (m_icon) + switch (m_standardIcon) { case 1: // Information try { @@ -336,7 +336,7 @@ public class QtMessageDialogHelper public void reset() { - m_icon = 0; + m_standardIcon = 0; m_title = null; m_text = null; m_informativeText = null; @@ -347,7 +347,7 @@ public class QtMessageDialogHelper } private Activity m_activity; - private int m_icon = 0; + private int m_standardIcon = 0; private Spanned m_title, m_text, m_informativeText, m_detailedText; private ArrayList m_buttonsList; private AlertDialog m_dialog; diff --git a/src/android/templates/AndroidManifest.xml b/src/android/templates/AndroidManifest.xml index d7438317..8f918637 100644 --- a/src/android/templates/AndroidManifest.xml +++ b/src/android/templates/AndroidManifest.xml @@ -43,5 +43,15 @@ android:name="android.app.extract_android_style" android:value="minimal" /> + + + + diff --git a/src/android/templates/CMakeLists.txt b/src/android/templates/CMakeLists.txt index 0d710f72..94f3243c 100644 --- a/src/android/templates/CMakeLists.txt +++ b/src/android/templates/CMakeLists.txt @@ -14,6 +14,7 @@ add_custom_target(Qt${QtBase_VERSION_MAJOR}AndroidTemplates SOURCES ${template_files} "${CMAKE_CURRENT_SOURCE_DIR}/res/values/libs.xml" + "${CMAKE_CURRENT_SOURCE_DIR}/res/xml/qtprovider_paths.xml" ) qt_path_join(destination ${QT_INSTALL_DIR} ${INSTALL_DATADIR} "src/android/templates") diff --git a/src/android/templates/build.gradle b/src/android/templates/build.gradle index 6671957f..f94ffbde 100644 --- a/src/android/templates/build.gradle +++ b/src/android/templates/build.gradle @@ -18,6 +18,7 @@ apply plugin: 'com.android.application' dependencies { implementation fileTree(dir: 'libs', include: ['*.jar', '*.aar']) + implementation 'androidx.core:core:1.10.1' } android { diff --git a/src/android/templates/res/xml/qtprovider_paths.xml b/src/android/templates/res/xml/qtprovider_paths.xml new file mode 100644 index 00000000..ae5b4b60 --- /dev/null +++ b/src/android/templates/res/xml/qtprovider_paths.xml @@ -0,0 +1,4 @@ + + + + diff --git a/src/assets/icons/CMakeLists.txt b/src/assets/icons/CMakeLists.txt index e9f611b6..83506bd0 100644 --- a/src/assets/icons/CMakeLists.txt +++ b/src/assets/icons/CMakeLists.txt @@ -8,6 +8,7 @@ qt_internal_add_module(ExampleIconsPrivate ) set(icons_resource_files + index.theme 16x16/document-new.png 16x16/document-open.png 16x16/document-print.png @@ -165,7 +166,7 @@ set(icons_resource_files qt_internal_add_resource(ExampleIconsPrivate "example_icons" PREFIX - "/qt-project.org/examples/icons/" + "/qt-project.org/icons/example_icons" FILES ${icons_resource_files} ) diff --git a/src/assets/icons/README b/src/assets/icons/README index 46c1522e..26d94e9f 100644 --- a/src/assets/icons/README +++ b/src/assets/icons/README @@ -17,7 +17,13 @@ Setting up a project for using Example icon library ) ... -2. Create image resource in your application code: +2. Load the theme ... - bool success = img->load(":/qt-project.org/examples/icons/32x32/document-new.png"); + QIcon::setThemeSearchPaths(QIcon::themeSearchPaths() << u":/qt-project.org/icons"_s); + QIcon::setFallbackThemeName(u"example_icons"_s); + ... + +3. Use the icons + ... + const QIcon openIcon = QIcon::fromTheme("document-open"); ... diff --git a/src/assets/icons/index.theme b/src/assets/icons/index.theme new file mode 100644 index 00000000..e389719e --- /dev/null +++ b/src/assets/icons/index.theme @@ -0,0 +1,46 @@ +[Icon Theme] +Name=example_icons + +Directories=16x16,16x16@2,32x32,32x32@2,128x128,128x128@2,256x256,256x256@2,scalable + +[16x16] +Size=16 +Type=Fixed + +[16x16@2] +Size=16 +Scale=2 +Type=Fixed + +[32x32] +Size=32 +Type=Fixed + +[32x32@2] +Size=32 +Scale=2 +Type=Fixed + +[128x128] +Size=128 +Type=Fixed + +[128x128@2] +Size=128 +Scale=2 +Type=Fixed + +[256x256] +Size=256 +Type=Fixed + +[256x256@2] +Size=256 +Scale=2 +Type=Fixed + +[scalable] +Size=512 +Type=Scalable +MinSize=16 +MaxSize=512 diff --git a/src/corelib/CMakeLists.txt b/src/corelib/CMakeLists.txt index be7681bf..d42b51d5 100644 --- a/src/corelib/CMakeLists.txt +++ b/src/corelib/CMakeLists.txt @@ -81,9 +81,11 @@ qt_internal_add_module(Core global/qsysinfo.cpp global/qsysinfo.h global/qsystemdetection.h global/qtclasshelpermacros.h + global/qtconfiginclude.h global/qtconfigmacros.h global/qtdeprecationmarkers.h global/qtenvironmentvariables.cpp global/qtenvironmentvariables.h + global/qtenvironmentvariables_p.h global/qtnoop.h global/qtpreprocessorsupport.h global/qtrace_p.h @@ -97,13 +99,19 @@ qt_internal_add_module(Core ${core_version_tagging_files} global/qvolatile_p.h global/q20algorithm.h + global/q20chrono.h global/q20functional.h global/q20iterator.h global/q20memory.h global/q20type_traits.h global/q23functional.h + global/q23utility.cpp # remove once we have a user that tests this + global/q23utility.h global/qxpfunctional.h global/qxptype_traits.h + ipc/qsharedmemory.cpp ipc/qsharedmemory.h ipc/qsharedmemory_p.h + ipc/qsystemsemaphore.cpp ipc/qsystemsemaphore.h ipc/qsystemsemaphore_p.h + ipc/qtipccommon.cpp ipc/qtipccommon.h ipc/qtipccommon_p.h io/qabstractfileengine.cpp io/qabstractfileengine_p.h io/qbuffer.cpp io/qbuffer.h io/qdataurl.cpp io/qdataurl_p.h @@ -138,6 +146,7 @@ qt_internal_add_module(Core io/qurlidna.cpp io/qurlquery.cpp io/qurlquery.h io/qurlrecode.cpp + io/qzipreader_p.h io/qzipwriter_p.h io/qzip.cpp kernel/qabstracteventdispatcher.cpp kernel/qabstracteventdispatcher.h kernel/qabstracteventdispatcher_p.h kernel/qabstractnativeeventfilter.cpp kernel/qabstractnativeeventfilter.h kernel/qapplicationstatic.h @@ -148,7 +157,7 @@ qt_internal_add_module(Core kernel/qcoreapplication_platform.h kernel/qcorecmdlineargs_p.h kernel/qcoreevent.cpp kernel/qcoreevent.h - kernel/qdeadlinetimer.cpp kernel/qdeadlinetimer.h kernel/qdeadlinetimer_p.h + kernel/qdeadlinetimer.cpp kernel/qdeadlinetimer.h kernel/qelapsedtimer.cpp kernel/qelapsedtimer.h kernel/qeventloop.cpp kernel/qeventloop.h kernel/qeventloop_p.h kernel/qfunctions_p.h @@ -170,15 +179,13 @@ qt_internal_add_module(Core kernel/qproperty.cpp kernel/qproperty.h kernel/qproperty_p.h kernel/qpropertyprivate.h kernel/qsequentialiterable.cpp kernel/qsequentialiterable.h - kernel/qsharedmemory.cpp kernel/qsharedmemory.h kernel/qsharedmemory_p.h kernel/qsignalmapper.cpp kernel/qsignalmapper.h kernel/qsocketnotifier.cpp kernel/qsocketnotifier.h kernel/qsystemerror.cpp kernel/qsystemerror_p.h - kernel/qsystemsemaphore.cpp kernel/qsystemsemaphore.h kernel/qsystemsemaphore_p.h kernel/qtestsupport_core.cpp kernel/qtestsupport_core.h kernel/qtimer.cpp kernel/qtimer.h kernel/qtimer_p.h kernel/qtranslator.cpp kernel/qtranslator.h kernel/qtranslator_p.h - kernel/qvariant.cpp kernel/qvariant.h + kernel/qvariant.cpp kernel/qvariant.h kernel/qvariant_p.h kernel/qvariantmap.h kernel/qvarianthash.h kernel/qvariantlist.h plugin/qfactoryinterface.cpp plugin/qfactoryinterface.h plugin/qfactoryloader.cpp plugin/qfactoryloader_p.h @@ -201,9 +208,6 @@ qt_internal_add_module(Core serialization/qjsonvalue.cpp serialization/qjsonvalue.h serialization/qjsonwriter.cpp serialization/qjsonwriter_p.h serialization/qtextstream.cpp serialization/qtextstream.h serialization/qtextstream_p.h - serialization/qxmlstream.cpp serialization/qxmlstream.h serialization/qxmlstream_p.h - serialization/qxmlstreamgrammar.cpp serialization/qxmlstreamgrammar_p.h - serialization/qxmlstreamparser_p.h serialization/qxmlutils.cpp serialization/qxmlutils_p.h text/qanystringview.h text/qbytearray.cpp text/qbytearray.h text/qbytearray_p.h @@ -216,6 +220,7 @@ qt_internal_add_module(Core text/qcollator.cpp text/qcollator.h text/qcollator_p.h text/qdoublescanprint_p.h text/qlatin1stringmatcher.cpp text/qlatin1stringmatcher.h + text/qlatin1stringview.h text/qlocale.cpp text/qlocale.h text/qlocale_p.h text/qlocale_data_p.h text/qlocale_tools.cpp text/qlocale_tools_p.h @@ -274,6 +279,7 @@ qt_internal_add_module(Core tools/qduplicatetracker_p.h tools/qflatmap_p.h tools/qfreelist.cpp tools/qfreelist_p.h + tools/qfunctionaltools_impl.h tools/qhashfunctions.h tools/qiterator.h tools/qline.cpp tools/qline.h @@ -298,6 +304,7 @@ qt_internal_add_module(Core tools/qsharedpointer.cpp tools/qsharedpointer.h tools/qsharedpointer_impl.h tools/qsize.cpp tools/qsize.h + tools/qspan_p.h tools/qstack.h tools/qtaggedpointer.h tools/qtools_p.h @@ -341,6 +348,8 @@ qt_internal_add_module(Core "${CMAKE_CURRENT_SOURCE_DIR}/Qt6CoreDeploySupport.cmake" "${config_build_dir}/QtInstallPaths.cmake" ${corelib_extra_cmake_files} + POLICIES + QTP0002 ) _qt_internal_setup_deploy_support() @@ -455,9 +464,6 @@ qt_internal_extend_target(Core ) qt_internal_extend_target(Core CONDITION ANDROID - SOURCES - kernel/qsharedmemory_android.cpp - kernel/qsystemsemaphore_android.cpp DEFINES LIBS_SUFFIX="_${ANDROID_ABI}.so" ) @@ -467,12 +473,11 @@ qt_internal_extend_target(Core CONDITION MSVC AND (TEST_architecture_arch STREQU "/BASE:0x67000000" ) -# QtCore can't be compiled with -Wl,-no-undefined because it uses the -# "environ" variable and FreeBSD does not include a weak symbol for it -# in libc. -qt_internal_extend_target(Core CONDITION FREEBSD - LINK_OPTIONS - "LINKER:--warn-unresolved-symbols" +qt_internal_extend_target(Core CONDITION QT_FEATURE_xmlstream + SOURCES + serialization/qxmlstream.cpp serialization/qxmlstream.h serialization/qxmlstream_p.h + serialization/qxmlstreamgrammar.cpp serialization/qxmlstreamgrammar_p.h + serialization/qxmlstreamparser_p.h ) qt_internal_extend_target(Core CONDITION QT_FEATURE_animation @@ -510,11 +515,10 @@ qt_internal_extend_target(Core CONDITION WIN32 io/qwindowspipewriter.cpp io/qwindowspipewriter_p.h io/qntdll_p.h kernel/qcoreapplication_win.cpp - kernel/qelapsedtimer_win.cpp kernel/qeventdispatcher_win.cpp kernel/qeventdispatcher_win_p.h kernel/qfunctions_win.cpp kernel/qfunctions_win_p.h kernel/qfunctions_winrt_p.h - kernel/qsharedmemory_win.cpp - kernel/qsystemsemaphore_win.cpp + ipc/qsharedmemory_win.cpp + ipc/qsystemsemaphore_win.cpp kernel/qwineventnotifier.cpp kernel/qwineventnotifier.h kernel/qwineventnotifier_p.h kernel/qwinregistry.cpp kernel/qwinregistry_p.h plugin/qsystemlibrary.cpp plugin/qsystemlibrary_p.h @@ -571,7 +575,6 @@ qt_internal_extend_target(Core CONDITION APPLE kernel/qcore_foundation.mm kernel/qcore_mac.mm kernel/qcore_mac_p.h kernel/qcoreapplication_mac.cpp - kernel/qelapsedtimer_mac.cpp kernel/qeventdispatcher_cf.mm kernel/qeventdispatcher_cf_p.h LIBRARIES ${FWCoreFoundation} @@ -667,10 +670,6 @@ qt_internal_extend_target(Core CONDITION QT_FEATURE_thread qt_internal_extend_target(Core CONDITION QT_FEATURE_thread AND UNIX SOURCES thread/qwaitcondition_unix.cpp - NO_UNITY_BUILD_SOURCES - thread/qwaitcondition_unix.cpp - # Temporary exclusion until this, https://codereview.qt-project.org/c/qt/qtbase/+/472013, - # or this, https://codereview.qt-project.org/c/qt/qtbase/+/471184 addresses the issue. ) qt_internal_extend_target(Core CONDITION APPLE AND QT_FEATURE_thread @@ -817,7 +816,7 @@ qt_internal_extend_target(Core CONDITION QT_FEATURE_regularexpression WrapPCRE2::WrapPCRE2 ) -qt_internal_extend_target(Core CONDITION QT_FEATURE_openssl_linked AND QT_FEATURE_opensslv30 +qt_internal_extend_target(Core CONDITION QT_FEATURE_openssl_hash LIBRARIES WrapOpenSSL::WrapOpenSSL ) @@ -971,11 +970,6 @@ qt_internal_extend_target(Core CONDITION APPLE AND NOT MACOS ${FWMobileCoreServices} ) -qt_internal_extend_target(Core CONDITION UNIX AND NOT APPLE - SOURCES - kernel/qelapsedtimer_unix.cpp -) - qt_internal_extend_target(Core CONDITION ANDROID SOURCES io/qstandardpaths_android.cpp @@ -1098,19 +1092,30 @@ qt_internal_extend_target(Core CONDITION QT_FEATURE_glib AND UNIX GLIB2::GLIB2 ) -qt_internal_extend_target(Core CONDITION QT_FEATURE_clock_gettime AND UNIX +qt_internal_extend_target(Core CONDITION QT_FEATURE_clock_gettime LIBRARIES WrapRt::WrapRt ) -qt_internal_extend_target(Core CONDITION UNIX AND NOT ANDROID +qt_internal_extend_target(Core CONDITION QT_FEATURE_posix_shm AND UNIX SOURCES - kernel/qsharedmemory_posix.cpp - kernel/qsharedmemory_systemv.cpp - kernel/qsharedmemory_unix.cpp - kernel/qsystemsemaphore_posix.cpp - kernel/qsystemsemaphore_systemv.cpp - kernel/qsystemsemaphore_unix.cpp + ipc/qsharedmemory_posix.cpp + LIBRARIES + WrapRt::WrapRt +) +qt_internal_extend_target(Core CONDITION QT_FEATURE_sysv_shm + SOURCES + ipc/qsharedmemory_systemv.cpp +) +qt_internal_extend_target(Core CONDITION QT_FEATURE_posix_sem + SOURCES + ipc/qsystemsemaphore_posix.cpp + LIBRARIES + WrapRt::WrapRt +) +qt_internal_extend_target(Core CONDITION QT_FEATURE_sysv_sem + SOURCES + ipc/qsystemsemaphore_systemv.cpp ) qt_internal_extend_target(Core CONDITION VXWORKS @@ -1305,10 +1310,11 @@ qt_internal_extend_target(Core CONDITION QT_FEATURE_ctf AND QT_FEATURE_library tracing ) +# These files are included by qmutex.cpp set_source_files_properties( thread/qmutex_mac.cpp thread/qmutex_unix.cpp - PROPERTIES HEADER_FILE_ONLY ON) # special case: These files are included by qmutex.cpp! + PROPERTIES HEADER_FILE_ONLY ON) # Remove QT_NO_CAST_TO_ASCII to ensure that the symbols are included in the library. if(WIN32) diff --git a/src/corelib/Qt6AndroidMacros.cmake b/src/corelib/Qt6AndroidMacros.cmake index 370041fe..c2cf7787 100644 --- a/src/corelib/Qt6AndroidMacros.cmake +++ b/src/corelib/Qt6AndroidMacros.cmake @@ -899,6 +899,38 @@ endfunction() # properties. function(_qt_internal_android_format_deployment_paths target) if(QT_BUILD_STANDALONE_TESTS OR QT_BUILDING_QT) + set(android_deployment_paths_policy NEW) + else() + set(policy_path_properties + QT_QML_IMPORT_PATH + QT_QML_ROOT_PATH + QT_ANDROID_PACKAGE_SOURCE_DIR + QT_ANDROID_EXTRA_PLUGINS + QT_ANDROID_EXTRA_LIBS + ) + + # Check if any of paths contains the value and stop the evaluation if all properties are + # empty or -NOTFOUND + set(has_android_paths FALSE) + foreach(prop_name IN LISTS policy_path_properties) + get_target_property(prop_value ${target} ${prop_name}) + if(prop_value) + set(has_android_paths TRUE) + break() + endif() + endforeach() + if(NOT has_android_paths) + return() + endif() + + __qt_internal_setup_policy(QTP0002 "6.6.0" + "Target properties that specify android-specific paths may contain generator\ + expressions but they must evaluate to valid JSON strings.\ + Check https://doc.qt.io/qt-6/qt-cmake-policy-qtp0002.html for policy details." + ) + qt6_policy(GET QTP0002 android_deployment_paths_policy) + endif() + if(android_deployment_paths_policy STREQUAL "NEW") # When building standalone tests or Qt itself we obligate developers to not use # windows paths when setting QT_* properties below, so their values are used as is when # generating deployment settings. diff --git a/src/corelib/Qt6CTestMacros.cmake b/src/corelib/Qt6CTestMacros.cmake index fb2ee6d3..a27581dd 100644 --- a/src/corelib/Qt6CTestMacros.cmake +++ b/src/corelib/Qt6CTestMacros.cmake @@ -260,6 +260,9 @@ endfunction() # so the argument containing list will look as following: # -DLIST_ARGUMENT=item1[[;]]item2[[;]]...itemN. macro(_qt_internal_test_expect_pass _dir) + if(WASM) + return() + endif() set(_test_option_args SIMULATE_IN_SOURCE NO_CLEAN_STEP diff --git a/src/corelib/Qt6CoreMacros.cmake b/src/corelib/Qt6CoreMacros.cmake index bde53f4b..349bb4b4 100644 --- a/src/corelib/Qt6CoreMacros.cmake +++ b/src/corelib/Qt6CoreMacros.cmake @@ -93,24 +93,32 @@ function(_qt_internal_create_moc_command infile outfile moc_flags moc_options set(extra_output_files "${outfile}.json") set(${out_json_file} "${extra_output_files}" PARENT_SCOPE) endif() - string (REPLACE ";" "\n" _moc_parameters "${_moc_parameters}") if(moc_target) set(_moc_parameters_file ${_moc_parameters_file}$<$>:_$>) set(targetincludes "$") set(targetdefines "$") - set(targetincludes "$<$:-I$,\n-I>\n>") - set(targetdefines "$<$:-D$,\n-D>\n>") + set(targetincludes "$<$:-I$>") + set(targetdefines "$<$:-D$>") + string(REPLACE ">" "$" _moc_escaped_parameters "${_moc_parameters}") + string(REPLACE "," "$" _moc_escaped_parameters "${_moc_escaped_parameters}") + + set(concatenated "$<$:${targetincludes};>$<$:${targetdefines};>$<$:${_moc_escaped_parameters};>") + + set(concatenated "$,EXCLUDE,^-[DI]$>") + set(concatenated "$") file (GENERATE OUTPUT ${_moc_parameters_file} - CONTENT "${targetdefines}${targetincludes}${_moc_parameters}\n" + CONTENT "${concatenated}" ) + set(concatenated) set(targetincludes) set(targetdefines) else() + string (REPLACE ";" "\n" _moc_parameters "${_moc_parameters}") file(WRITE ${_moc_parameters_file} "${_moc_parameters}\n") endif() @@ -121,8 +129,12 @@ function(_qt_internal_create_moc_command infile outfile moc_flags moc_options ${_moc_working_dir} VERBATIM) set_source_files_properties(${infile} PROPERTIES SKIP_AUTOMOC ON) - set_source_files_properties(${outfile} PROPERTIES SKIP_AUTOMOC ON) - set_source_files_properties(${outfile} PROPERTIES SKIP_AUTOUIC ON) + set_source_files_properties(${outfile} PROPERTIES SKIP_AUTOMOC ON + SKIP_AUTOUIC ON + ) + if(CMAKE_VERSION VERSION_GREATER_EQUAL "3.27") + set_source_files_properties(${outfile} PROPERTIES SKIP_LINTING ON) + endif() endfunction() function(qt6_generate_moc infile outfile ) @@ -434,6 +446,9 @@ function(qt6_add_big_resources outfiles ) _qt6_parse_qrc_file(${infile} _out_depends _rc_depends) set_source_files_properties(${infile} PROPERTIES SKIP_AUTOGEN ON) + if(CMAKE_VERSION VERSION_GREATER_EQUAL "3.27") + set_source_files_properties(${tmpoutfile} PROPERTIES SKIP_LINTING ON) + endif() add_custom_command(OUTPUT ${tmpoutfile} COMMAND ${QT_CMAKE_EXPORT_NAMESPACE}::rcc ${rcc_options} --name ${outfilename} --pass 1 --output ${tmpoutfile} ${infile} DEPENDS ${infile} ${_rc_depends} "${out_depends}" ${QT_CMAKE_EXPORT_NAMESPACE}::rcc @@ -660,6 +675,26 @@ function(_qt_internal_finalize_executable target) endif() endfunction() +function(_cat IN_FILE OUT_FILE) + file(READ ${IN_FILE} CONTENTS) + file(APPEND ${OUT_FILE} "${CONTENTS}\n") +endfunction() + +function(_qt_internal_finalize_batch name) + find_package(Qt6 ${PROJECT_VERSION} CONFIG REQUIRED COMPONENTS Core) + + set(generated_blacklist_file "${CMAKE_CURRENT_BINARY_DIR}/BLACKLIST") + get_target_property(blacklist_files "${name}" _qt_blacklist_files) + file(WRITE "${generated_blacklist_file}" "") + foreach(blacklist_file ${blacklist_files}) + _cat("${blacklist_file}" "${generated_blacklist_file}") + endforeach() + qt_internal_add_resource(${name} "batch_blacklist" + PREFIX "/" + FILES "${CMAKE_CURRENT_BINARY_DIR}/BLACKLIST" + BASE ${CMAKE_CURRENT_BINARY_DIR}) +endfunction() + # If a task needs to run before any targets are finalized in the current directory # scope, call this function and pass the ID of that task as the argument. function(_qt_internal_delay_finalization_until_after defer_id) @@ -667,6 +702,7 @@ function(_qt_internal_delay_finalization_until_after defer_id) endfunction() function(qt6_finalize_target target) + set_property(TARGET ${target} PROPERTY _qt_expects_finalization FALSE) if(CMAKE_VERSION VERSION_GREATER_EQUAL "3.19") cmake_language(DEFER GET_CALL_IDS ids_queued) get_directory_property(wait_for_ids qt_internal_finalizers_wait_for_ids) @@ -1025,9 +1061,23 @@ function(qt6_extract_metatypes target) add_dependencies(${target}_automoc_json_extraction ${target}_autogen) _qt_internal_assign_to_internal_targets_folder(${target}_automoc_json_extraction) else() - set(cmake_autogen_timestamp_file - "${target_autogen_build_dir}/timestamp" - ) + set(use_better_automoc_graph FALSE) + if(${CMAKE_VERSION} VERSION_GREATER_EQUAL "3.28.0" + AND CMAKE_CROSS_CONFIGS) + if(DEFINED QT_USE_BETTER_AUTOMOC_GRAPH) + set(use_better_automoc_graph ${QT_USE_BETTER_AUTOMOC_GRAPH}) + else() + set(use_better_automoc_graph TRUE) + endif() + endif() + + if(use_better_automoc_graph) + set(cmake_autogen_timestamp_file + "${target_autogen_build_dir}/timestamp_$") + else() + set(cmake_autogen_timestamp_file + "${target_autogen_build_dir}/timestamp") + endif() add_custom_command(OUTPUT ${type_list_file} DEPENDS ${QT_CMAKE_EXPORT_NAMESPACE}::cmake_automoc_parser @@ -1485,6 +1535,13 @@ function(__qt_get_relative_resource_path_for_file output_alias file) get_property(alias SOURCE ${file} PROPERTY QT_RESOURCE_ALIAS) if (NOT alias) set(alias "${file}") + if(IS_ABSOLUTE "${file}") + message(FATAL_ERROR + "The source file '${file}' was specified with an absolute path and is used in a Qt " + "resource. Please set the QT_RESOURCE_ALIAS property on that source file to a " + "relative path to make the file properly accessible via the resource system." + ) + endif() endif() set(${output_alias} ${alias} PARENT_SCOPE) endfunction() @@ -1887,6 +1944,7 @@ function(_qt_internal_process_resource target resourceName) return() endif() set(generatedResourceFile "${CMAKE_CURRENT_BINARY_DIR}/.rcc/${resourceName}.qrc") + _qt_internal_expose_source_file_to_ide(${target} ${generatedResourceFile}) # Generate .qrc file: @@ -1907,10 +1965,15 @@ function(_qt_internal_process_resource target resourceName) set(file "${CMAKE_CURRENT_SOURCE_DIR}/${file}") endif() + get_property(is_empty SOURCE ${file} PROPERTY QT_DISCARD_FILE_CONTENTS) + ### FIXME: escape file paths to be XML conform # ... - string(APPEND qrcContents " ") - string(APPEND qrcContents "${file}\n") + string(APPEND qrcContents " ${file}\n") list(APPEND files "${file}") set(scope_args) @@ -2017,6 +2080,9 @@ function(_qt_internal_process_resource target resourceName) SKIP_UNITY_BUILD_INCLUSION TRUE SKIP_PRECOMPILE_HEADERS TRUE ) + if(CMAKE_VERSION VERSION_GREATER_EQUAL "3.27") + set_source_files_properties(${generatedOutfile} ${scope_args} PROPERTIES SKIP_LINTING ON) + endif() get_target_property(target_source_dir ${target} SOURCE_DIR) if(NOT target_source_dir STREQUAL CMAKE_CURRENT_SOURCE_DIR) @@ -2804,8 +2870,8 @@ macro(qt6_standard_project_setup) endif() endforeach() - # Enable folder support for IDEs. A future CMake version might enable this by default. - # See CMake issue #21695. + # Enable folder support for IDEs. CMake >= 3.26 enables USE_FOLDERS by default but this is + # guarded by CMake policy CMP0143. get_property(__qt_use_folders GLOBAL PROPERTY USE_FOLDERS) if(__qt_use_folders OR "${__qt_use_folders}" STREQUAL "") set_property(GLOBAL PROPERTY USE_FOLDERS ON) diff --git a/src/corelib/Qt6WasmMacros.cmake b/src/corelib/Qt6WasmMacros.cmake index 5cb8b73a..3ec4ed58 100644 --- a/src/corelib/Qt6WasmMacros.cmake +++ b/src/corelib/Qt6WasmMacros.cmake @@ -23,6 +23,14 @@ function(_qt_internal_wasm_add_target_helpers target) set(APPNAME ${_target_output_name}) + # Shared library builds preload plugins and qml imports by default. + # The json files are generated by scripts in qtbase/util/wasm/preload + if (QT_FEATURE_shared) + set(PRELOAD "preload: ['qt_plugins.json', 'qt_qml_imports.json'],") + else() + set(PRELOAD "") + endif() + get_target_property(target_output_directory ${target} RUNTIME_OUTPUT_DIRECTORY) get_target_property(is_test ${target} _qt_is_test_executable) @@ -87,7 +95,7 @@ endfunction() function(_qt_internal_add_wasm_extra_exported_methods target) get_target_property(wasm_extra_exported_methods "${target}" QT_WASM_EXTRA_EXPORTED_METHODS) - set(wasm_default_exported_methods "UTF16ToString,stringToUTF16,JSEvents,specialHTMLTargets") + set(wasm_default_exported_methods "UTF16ToString,stringToUTF16,JSEvents,specialHTMLTargets,FS") if(NOT wasm_extra_exported_methods) set(wasm_extra_exported_methods ${QT_WASM_EXTRA_EXPORTED_METHODS}) diff --git a/src/corelib/animation/qabstractanimation.cpp b/src/corelib/animation/qabstractanimation.cpp index df1557b1..c32053db 100644 --- a/src/corelib/animation/qabstractanimation.cpp +++ b/src/corelib/animation/qabstractanimation.cpp @@ -1121,7 +1121,7 @@ void QAbstractAnimation::setDirection(Direction direction) return; } - Qt::beginPropertyUpdateGroup(); + const QScopedPropertyUpdateGroup guard; const int oldCurrentLoop = d->currentLoop; if (state() == Stopped) { if (direction == Backward) { @@ -1148,7 +1148,6 @@ void QAbstractAnimation::setDirection(Direction direction) if (d->currentLoop != oldCurrentLoop) d->currentLoop.notify(); d->direction.notify(); - Qt::endPropertyUpdateGroup(); } QBindable QAbstractAnimation::bindableDirection() diff --git a/src/corelib/compat/removed_api.cpp b/src/corelib/compat/removed_api.cpp index 7acbae8a..dde262e7 100644 --- a/src/corelib/compat/removed_api.cpp +++ b/src/corelib/compat/removed_api.cpp @@ -303,6 +303,15 @@ int QMetaType::idHelper() const return registerHelper(d_ptr); } +#if QT_CONFIG(sharedmemory) +#include "qsharedmemory.h" + +void QSharedMemory::setNativeKey(const QString &key) +{ + setNativeKey(key, QNativeIpcKey::legacyDefaultTypeForOs()); +} +#endif + #include "qvariant.h" // these implementations aren't as efficient as they used to be prior to @@ -474,3 +483,135 @@ void QXmlStreamWriter::writeStartElement(const QString &namespaceUri, const QStr // order sections alphabetically to reduce chances of merge conflicts #endif // QT_CORE_REMOVED_SINCE(6, 5) + +#if QT_CORE_REMOVED_SINCE(6, 6) + +#include "qmessageauthenticationcode.h" + +QMessageAuthenticationCode::QMessageAuthenticationCode(QCryptographicHash::Algorithm method, + const QByteArray &key) + : QMessageAuthenticationCode(method, qToByteArrayViewIgnoringNull(key)) {} + +void QMessageAuthenticationCode::setKey(const QByteArray &key) +{ + setKey(qToByteArrayViewIgnoringNull(key)); +} + +void QMessageAuthenticationCode::addData(const QByteArray &data) +{ + addData(qToByteArrayViewIgnoringNull(data)); +} + +QByteArray QMessageAuthenticationCode::hash(const QByteArray &msg, const QByteArray &key, + QCryptographicHash::Algorithm method) +{ + return hash(qToByteArrayViewIgnoringNull(msg), + qToByteArrayViewIgnoringNull(key), method); +} + +#include "qobject.h" // inlined API + +#include "qrunnable.h" + +QRunnable *QRunnable::create(std::function functionToRun) +{ + return QRunnable::create>(std::move(functionToRun)); +} + +#include "qstring.h" + +qsizetype QString::toUcs4_helper(const ushort *uc, qsizetype length, uint *out) +{ + return toUcs4_helper(reinterpret_cast(uc), length, + reinterpret_cast(out)); +} + +#if QT_CONFIG(thread) +#include "qreadwritelock.h" + +bool QReadWriteLock::tryLockForRead() +{ + return tryLockForRead(0); +} + +bool QReadWriteLock::tryLockForWrite() +{ + return tryLockForWrite(0); +} + +#include "qthreadpool.h" +#include "private/qthreadpool_p.h" + +void QThreadPool::start(std::function functionToRun, int priority) +{ + if (!functionToRun) + return; + start(QRunnable::create(std::move(functionToRun)), priority); +} + +bool QThreadPool::tryStart(std::function functionToRun) +{ + if (!functionToRun) + return false; + + Q_D(QThreadPool); + QMutexLocker locker(&d->mutex); + if (!d->allThreads.isEmpty() && d->areAllThreadsActive()) + return false; + + QRunnable *runnable = QRunnable::create(std::move(functionToRun)); + if (d->tryStart(runnable)) + return true; + delete runnable; + return false; +} + +void QThreadPool::startOnReservedThread(std::function functionToRun) +{ + if (!functionToRun) + return releaseThread(); + + startOnReservedThread(QRunnable::create(std::move(functionToRun))); +} + +#endif // QT_CONFIG(thread) + +#include "qxmlstream.h" + +QStringView QXmlStreamAttributes::value(const QString &namespaceUri, const QString &name) const +{ + return value(qToAnyStringViewIgnoringNull(namespaceUri), qToAnyStringViewIgnoringNull(name)); +} + +QStringView QXmlStreamAttributes::value(const QString &namespaceUri, QLatin1StringView name) const +{ + return value(qToAnyStringViewIgnoringNull(namespaceUri), QAnyStringView(name)); +} + +QStringView QXmlStreamAttributes::value(QLatin1StringView namespaceUri, QLatin1StringView name) const +{ + return value(QAnyStringView(namespaceUri), QAnyStringView(name)); +} + +QStringView QXmlStreamAttributes::value(const QString &qualifiedName) const +{ + return value(qToAnyStringViewIgnoringNull(qualifiedName)); +} + +QStringView QXmlStreamAttributes::value(QLatin1StringView qualifiedName) const +{ + return value(QAnyStringView(qualifiedName)); +} + +// inlined API +#if QT_CONFIG(thread) +#include "qmutex.h" +#include "qreadwritelock.h" +#include "qsemaphore.h" +#endif + +// #include "qotherheader.h" +// // implement removed functions from qotherheader.h +// order sections alphabetically to reduce chances of merge conflicts + +#endif // QT_CORE_REMOVED_SINCE(6, 6) diff --git a/src/corelib/configure.cmake b/src/corelib/configure.cmake index 7d52f48f..6f4cb49e 100644 --- a/src/corelib/configure.cmake +++ b/src/corelib/configure.cmake @@ -98,6 +98,18 @@ clock_gettime(CLOCK_MONOTONIC, &ts); } ") +# close_range +qt_config_compile_test(close_range + LABEL "close_range()" + CODE +"#include + +int main() +{ + return close_range(3, 1024, 0) != 0; +} +") + # cloexec qt_config_compile_test(cloexec LABEL "O_CLOEXEC" @@ -125,7 +137,6 @@ int pipes[2]; } ") -# special case begin # cxx11_future if (UNIX AND NOT ANDROID AND NOT QNX AND NOT INTEGRITY) set(cxx11_future_TEST_LIBRARIES pthread) @@ -146,7 +157,6 @@ std::future f = std::async([]() { return 42; }); return 0; } ") -# special case end # cxx11_random qt_config_compile_test(cxx11_random @@ -297,49 +307,71 @@ inotify_rm_watch(0, 1); } ") -# ipc_sysv -qt_config_compile_test(ipc_sysv - LABEL "SysV IPC" +qt_config_compile_test(sysv_shm + LABEL "System V/XSI shared memory" CODE "#include #include -#include #include #include int main(void) { - /* BEGIN TEST: */ -key_t unix_key = ftok(\"test\", 'Q'); -semctl(semget(unix_key, 1, 0666 | IPC_CREAT | IPC_EXCL), 0, IPC_RMID, 0); -shmget(unix_key, 0, 0666 | IPC_CREAT | IPC_EXCL); -shmctl(0, 0, (struct shmid_ds *)(0)); - /* END TEST: */ + key_t unix_key = ftok(\"test\", 'Q'); + shmget(unix_key, 0, 0666 | IPC_CREAT | IPC_EXCL); + shmctl(0, 0, (struct shmid_ds *)(0)); + return 0; +} +") + +qt_config_compile_test(sysv_sem + LABEL "System V/XSI semaphores" + CODE +"#include +#include +#include +#include + +int main(void) +{ + key_t unix_key = ftok(\"test\", 'Q'); + semctl(semget(unix_key, 1, 0666 | IPC_CREAT | IPC_EXCL), 0, IPC_RMID, 0); return 0; } ") -# ipc_posix if (LINUX) - set(ipc_posix_TEST_LIBRARIES pthread rt) + set(ipc_posix_TEST_LIBRARIES pthread WrapRt::WrapRt) endif() -qt_config_compile_test(ipc_posix - LABEL "POSIX IPC" +qt_config_compile_test(posix_shm + LABEL "POSIX shared memory" LIBRARIES "${ipc_posix_TEST_LIBRARIES}" CODE "#include #include +#include + +int main(void) +{ + shm_open(\"test\", O_RDWR | O_CREAT | O_EXCL, 0666); + shm_unlink(\"test\"); + return 0; +} +") + +qt_config_compile_test(posix_sem + LABEL "POSIX semaphores" + LIBRARIES + "${ipc_posix_TEST_LIBRARIES}" + CODE +"#include #include #include int main(void) { - /* BEGIN TEST: */ -sem_close(sem_open(\"test\", O_CREAT | O_EXCL, 0666, 0)); -shm_open(\"test\", O_RDWR | O_CREAT | O_EXCL, 0666); -shm_unlink(\"test\"); - /* END TEST: */ + sem_close(sem_open(\"test\", O_CREAT | O_EXCL, 0666, 0)); return 0; } ") @@ -531,7 +563,12 @@ qt_feature("clock-monotonic" PUBLIC CONDITION QT_FEATURE_clock_gettime AND TEST_clock_monotonic ) qt_feature_definition("clock-monotonic" "QT_NO_CLOCK_MONOTONIC" NEGATE VALUE "1") -qt_feature("doubleconversion" PUBLIC PRIVATE +qt_feature("close_range" PRIVATE + LABEL "close_range()" + CONDITION QT_FEATURE_process AND TEST_close_range + AUTODETECT UNIX +) +qt_feature("doubleconversion" PRIVATE LABEL "DoubleConversion" ) qt_feature_definition("doubleconversion" "QT_NO_DOUBLECONVERSION" NEGATE VALUE "1") @@ -592,9 +629,11 @@ qt_feature("inotify" PUBLIC PRIVATE ) qt_feature_definition("inotify" "QT_NO_INOTIFY" NEGATE VALUE "1") qt_feature("ipc_posix" - LABEL "Using POSIX IPC" - AUTODETECT NOT WIN32 AND ( ( APPLE AND QT_FEATURE_appstore_compliant ) OR NOT TEST_ipc_sysv ) - CONDITION TEST_ipc_posix + LABEL "Defaulting legacy IPC to POSIX" + CONDITION TEST_posix_shm AND TEST_posix_sem AND ( + FEATURE_ipc_posix OR (APPLE AND QT_FEATURE_appstore_compliant) + OR NOT TEST_sysv_shm OR NOT TEST_sysv_sem + ) ) qt_feature_definition("ipc_posix" "QT_POSIX_IPC") qt_feature("journald" PRIVATE @@ -662,6 +701,14 @@ qt_feature("poll_select" PRIVATE EMIT_IF NOT WIN32 ) qt_feature_definition("poll_select" "QT_NO_NATIVE_POLL") +qt_feature("posix_sem" PRIVATE + LABEL "POSIX semaphores" + CONDITION TEST_posix_sem +) +qt_feature("posix_shm" PRIVATE + LABEL "POSIX shared memory" + CONDITION TEST_posix_shm AND UNIX +) qt_feature("qqnx_pps" PRIVATE LABEL "PPS" CONDITION PPS_FOUND @@ -684,6 +731,14 @@ qt_feature("syslog" PRIVATE AUTODETECT OFF CONDITION TEST_syslog ) +qt_feature("sysv_sem" PRIVATE + LABEL "System V / XSI semaphores" + CONDITION TEST_sysv_sem +) +qt_feature("sysv_shm" PRIVATE + LABEL "System V / XSI shared memory" + CONDITION TEST_sysv_shm +) qt_feature("threadsafe-cloexec" LABEL "Threadsafe pipe creation" CONDITION TEST_cloexec @@ -705,7 +760,7 @@ qt_feature("sharedmemory" PUBLIC SECTION "Kernel" LABEL "QSharedMemory" PURPOSE "Provides access to a shared memory segment." - CONDITION ( ANDROID OR WIN32 OR ( NOT VXWORKS AND ( TEST_ipc_sysv OR TEST_ipc_posix ) ) ) + CONDITION WIN32 OR TEST_sysv_shm OR TEST_posix_shm ) qt_feature_definition("sharedmemory" "QT_NO_SHAREDMEMORY" NEGATE VALUE "1") qt_feature("shortcut" PUBLIC @@ -718,7 +773,7 @@ qt_feature("systemsemaphore" PUBLIC SECTION "Kernel" LABEL "QSystemSemaphore" PURPOSE "Provides a general counting system semaphore." - CONDITION ( NOT INTEGRITY AND NOT VXWORKS AND NOT rtems ) AND ( ANDROID OR WIN32 OR TEST_ipc_sysv OR TEST_ipc_posix ) + CONDITION WIN32 OR TEST_sysv_sem OR TEST_posix_sem ) qt_feature_definition("systemsemaphore" "QT_NO_SYSTEMSEMAPHORE" NEGATE VALUE "1") qt_feature("xmlstream" PUBLIC @@ -729,23 +784,21 @@ qt_feature("xmlstream" PUBLIC qt_feature("cpp-winrt" PRIVATE PUBLIC LABEL "cpp/winrt base" PURPOSE "basic cpp/winrt language projection support" + AUTODETECT WIN32 CONDITION WIN32 AND TEST_cpp_winrt ) -qt_feature_definition("xmlstream" "QT_NO_XMLSTREAM" NEGATE VALUE "1") qt_feature("xmlstreamreader" PUBLIC SECTION "Kernel" LABEL "QXmlStreamReader" PURPOSE "Provides a well-formed XML parser with a simple streaming API." CONDITION QT_FEATURE_xmlstream ) -qt_feature_definition("xmlstreamreader" "QT_NO_XMLSTREAMREADER" NEGATE VALUE "1") qt_feature("xmlstreamwriter" PUBLIC SECTION "Kernel" LABEL "QXmlStreamWriter" PURPOSE "Provides a XML writer with a simple streaming API." CONDITION QT_FEATURE_xmlstream ) -qt_feature_definition("xmlstreamwriter" "QT_NO_XMLSTREAMWRITER" NEGATE VALUE "1") qt_feature("textdate" PUBLIC SECTION "Data structures" LABEL "Text Date" @@ -963,22 +1016,31 @@ qt_feature("permissions" PUBLIC SECTION "Utilities" LABEL "Application permissions" PURPOSE "Provides support for requesting user permission to access restricted data or APIs" - CONDITION APPLE OR ANDROID OR WASM ) +qt_feature("openssl-hash" PRIVATE + LABEL "OpenSSL based cryptographic hash" + AUTODETECT OFF + CONDITION QT_FEATURE_openssl_linked AND QT_FEATURE_opensslv30 + PURPOSE "Uses OpenSSL based implementation of cryptographic hash algorithms." +) + qt_configure_add_summary_section(NAME "Qt Core") qt_configure_add_summary_entry(ARGS "backtrace") qt_configure_add_summary_entry(ARGS "doubleconversion") qt_configure_add_summary_entry(ARGS "system-doubleconversion") +qt_configure_add_summary_entry(ARGS "forkfd_pidfd" CONDITION LINUX) qt_configure_add_summary_entry(ARGS "glib") qt_configure_add_summary_entry(ARGS "icu") qt_configure_add_summary_entry(ARGS "system-libb2") qt_configure_add_summary_entry(ARGS "mimetype-database") -qt_configure_add_summary_entry(ARGS "cpp-winrt") +qt_configure_add_summary_entry(ARGS "permissions") +qt_configure_add_summary_entry(ARGS "ipc_posix" CONDITION UNIX) qt_configure_add_summary_entry( TYPE "firstAvailableFeature" ARGS "etw lttng ctf" MESSAGE "Tracing backend" ) +qt_configure_add_summary_entry(ARGS "openssl-hash") qt_configure_add_summary_section(NAME "Logging backends") qt_configure_add_summary_entry(ARGS "journald") qt_configure_add_summary_entry(ARGS "syslog") @@ -990,11 +1052,6 @@ qt_configure_add_summary_entry( ) qt_configure_add_summary_entry(ARGS "pcre2") qt_configure_add_summary_entry(ARGS "system-pcre2") -qt_configure_add_summary_entry( - ARGS "forkfd_pidfd" - CONDITION LINUX -) -qt_configure_add_summary_entry(ARGS "permissions") qt_configure_end_summary_section() # end of "Qt Core" section qt_configure_add_report_entry( TYPE NOTE diff --git a/src/corelib/doc/snippets/code/doc_src_qiterator.cpp b/src/corelib/doc/snippets/code/doc_src_qiterator.cpp index f033a7e1..0d921b87 100644 --- a/src/corelib/doc/snippets/code/doc_src_qiterator.cpp +++ b/src/corelib/doc/snippets/code/doc_src_qiterator.cpp @@ -25,15 +25,6 @@ while (i.hasNext()) float f = i.next(); //! [6] - -//! [7] -QSetIterator i(set); -i.toBack(); -while (i.hasPrevious()) - QString s = i.previous(); -//! [7] - - //! [8] QList list; ... diff --git a/src/corelib/doc/snippets/code/doc_src_resources.qdoc b/src/corelib/doc/snippets/code/doc_src_resources.qdoc index a2e515ce..f5a12ee8 100644 --- a/src/corelib/doc/snippets/code/doc_src_resources.qdoc +++ b/src/corelib/doc/snippets/code/doc_src_resources.qdoc @@ -26,3 +26,7 @@ //! [3] rcc -binary myresource.qrc -o myresource.rcc //! [3] + +//! [4] +Button.qml +//! [4] diff --git a/src/corelib/doc/snippets/code/src_corelib_io_qsettings.cpp b/src/corelib/doc/snippets/code/src_corelib_io_qsettings.cpp index 9ec7d1fb..5774add2 100644 --- a/src/corelib/doc/snippets/code/src_corelib_io_qsettings.cpp +++ b/src/corelib/doc/snippets/code/src_corelib_io_qsettings.cpp @@ -45,7 +45,7 @@ settings.value("HKEY_CURRENT_USER\\MySoft\\Star Runner\\Galaxy\\Default"); // re //! [7] -#ifdef Q_OS_MAC +#ifdef Q_OS_DARWIN QSettings settings("grenoullelogique.fr", "Squash"); #else QSettings settings("Grenoulle Logique", "Squash"); diff --git a/src/corelib/doc/snippets/code/src_corelib_kernel_qsystemsemaphore.cpp b/src/corelib/doc/snippets/code/src_corelib_kernel_qsystemsemaphore.cpp index d1afba27..45da88c7 100644 --- a/src/corelib/doc/snippets/code/src_corelib_kernel_qsystemsemaphore.cpp +++ b/src/corelib/doc/snippets/code/src_corelib_kernel_qsystemsemaphore.cpp @@ -2,7 +2,7 @@ // SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause //! [0] -QSystemSemaphore sem("market", 3, QSystemSemaphore::Create); +QSystemSemaphore sem(QSystemSemaphore::platformSafeKey("market"), 3, QSystemSemaphore::Create); // resources available == 3 sem.acquire(); // resources available == 2 sem.acquire(); // resources available == 1 @@ -13,7 +13,7 @@ sem.release(2); // resources available == 3 //! [1] -QSystemSemaphore sem("market", 5, QSystemSemaphore::Create); +QSystemSemaphore sem(QSystemSemaphore::platformSafeKey("market"), 5, QSystemSemaphore::Create); for (int i = 0; i < 5; ++i) // acquire all 5 resources sem.acquire(); sem.release(5); // release the 5 resources diff --git a/src/corelib/doc/snippets/code/src_corelib_thread_qfuture.cpp b/src/corelib/doc/snippets/code/src_corelib_thread_qfuture.cpp index 9a6181e0..500d7cc7 100644 --- a/src/corelib/doc/snippets/code/src_corelib_thread_qfuture.cpp +++ b/src/corelib/doc/snippets/code/src_corelib_thread_qfuture.cpp @@ -239,9 +239,9 @@ auto future = QtConcurrent::run([] { //! [20] QObject *context = ...; -auto future = cachedResultsReady ? QtFuture::makeReadyFuture(results) - : QtConcurrent::run([] { /* compute results */}); -auto continuation = future.then(context, [] (Results results) { +auto future = cachedResultsReady ? QtFuture::makeReadyValueFuture(result) + : QtConcurrent::run([] { /* compute result */}); +auto continuation = future.then(context, [] (Result result) { // Runs in the context's thread }).then([] { // May or may not run in the context's thread @@ -399,3 +399,44 @@ p.start(); p.addResult(42); p.finish(); //! [31] + +//! [32] +const std::vector values{1, 2, 3}; +auto f = QtFuture::makeReadyRangeFuture(values); +//! [32] + +//! [33] +auto f = QtFuture::makeReadyRangeFuture({1, 2, 3}); +//! [33] + +//! [34] +const int count = f.resultCount(); // count == 3 +const auto results = f.results(); // results == { 1, 2, 3 } +//! [34] + +//! [35] +auto f = QtFuture::makeReadyValueFuture(std::make_unique(42)); +... +const int result = *f.takeResult(); // result == 42 +//! [35] + +//! [36] +auto f = QtFuture::makeReadyVoidFuture(); +... +const bool started = f.isStarted(); // started == true +const bool running = f.isRunning(); // running == false +const bool finished = f.isFinished(); // finished == true +//! [36] + +//! [37] +QObject *context = ...; +auto future = ...; +auto continuation = future.then(context, [context](Result result) { + // ... + }).onCanceled([context = QPointer(context)] { + if (!context) + return; // context was destroyed already + // handle cancellation + }); + +//! [37] diff --git a/src/corelib/doc/snippets/code/src_corelib_thread_qsemaphore.cpp b/src/corelib/doc/snippets/code/src_corelib_thread_qsemaphore.cpp index a46e10c7..d7c9b900 100644 --- a/src/corelib/doc/snippets/code/src_corelib_thread_qsemaphore.cpp +++ b/src/corelib/doc/snippets/code/src_corelib_thread_qsemaphore.cpp @@ -35,6 +35,12 @@ sem.tryAcquire(250, 1000); // sem.available() == 5, waits 1000 milliseconds a sem.tryAcquire(3, 30000); // sem.available() == 2, returns true without waiting //! [3] +//! [tryAcquire-QDeadlineTimer] +QSemaphore sem(5); // sem.available() == 5 +sem.tryAcquire(250, QDeadlineTimer(1000)); // sem.available() == 5, waits 1000 milliseconds and returns false +sem.tryAcquire(3, QDeadlineTimer(30s)); // sem.available() == 2, returns true without waiting +//! [tryAcquire-QDeadlineTimer] + //! [4] // ... do something that may throw or return early sem.release(); diff --git a/src/corelib/doc/snippets/ntfsp.cpp b/src/corelib/doc/snippets/ntfsp.cpp index b4d59e6a..18f9bd0c 100644 --- a/src/corelib/doc/snippets/ntfsp.cpp +++ b/src/corelib/doc/snippets/ntfsp.cpp @@ -11,3 +11,18 @@ qt_ntfs_permission_lookup++; // turn checking on qt_ntfs_permission_lookup--; // turn it off again //! [1] +//! [raii] +void complexFunction() +{ + QNtfsPermissionCheckGuard permissionGuard; // check is enabled + + // do complex things here that need permission check enabled + +} // as the guard goes out of scope the check is disabled +//! [raii] + +//! [free-funcs] +qAreNtfsPermissionChecksEnabled(); // check status +qEnableNtfsPermissionChecks(); // turn checking on +qDisableNtfsPermissionChecks(); // turn it off again +//! [free-funcs] diff --git a/src/corelib/doc/snippets/qstring/stringbuilder.cpp b/src/corelib/doc/snippets/qstring/stringbuilder.cpp index e9eac1f5..a48f9858 100644 --- a/src/corelib/doc/snippets/qstring/stringbuilder.cpp +++ b/src/corelib/doc/snippets/qstring/stringbuilder.cpp @@ -1,6 +1,8 @@ // Copyright (C) 2016 The Qt Company Ltd. // SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause +#include + using namespace Qt::StringLiterals; //! [0] @@ -21,3 +23,24 @@ using namespace Qt::StringLiterals; QLatin1StringView world("world"); QString message = hello % el % world % QChar('!'); //! [5] + +//! [6] + QString str("QStringBuilder"); + + // "s" type is deduced as QStringBuilder + auto s = "Like hot glue, " % str % " concatenates strings"; + + // Similarly the return type of this lambda is deduced as QStringBuilder + auto concatenateStr = []() { + return "Like hot glue, " % str % " concatenates strings"; + }; +//! [6] + +//! [7] + QString s = "Like hot glue, " % str % " concatenates strings"; + + // With a lambda, specify a trailing return type + auto concatenateStr = []() -> QString { + return "Like hot glue, " % str % " concatenates strings"; + }; +//! [7] diff --git a/src/corelib/doc/src/cbor.qdoc b/src/corelib/doc/src/cbor.qdoc index 1deeb19b..1358480b 100644 --- a/src/corelib/doc/src/cbor.qdoc +++ b/src/corelib/doc/src/cbor.qdoc @@ -6,6 +6,7 @@ \title CBOR Support in Qt \ingroup qt-basic-concepts \brief An overview of CBOR support in Qt. + \ingroup explanations-dataprocessingandio \ingroup frameworks-technologies diff --git a/src/corelib/doc/src/cmake/cmake-configure-variables.qdoc b/src/corelib/doc/src/cmake/cmake-configure-variables.qdoc index 86fcb794..a777dd7d 100644 --- a/src/corelib/doc/src/cmake/cmake-configure-variables.qdoc +++ b/src/corelib/doc/src/cmake/cmake-configure-variables.qdoc @@ -344,7 +344,7 @@ Set \c QT_NO_COLLECT_BUILD_TREE_APK_DEPS to \c TRUE to disable this behavior. When using CMake version 3.21 or later, the build system collects the locations of imported shared library targets that might be relevant for deployment. -The collected targets are those that are reachable from the the directory scope +The collected targets are those that are reachable from the directory scope of the currently processed executable target. That includes the target's source directory scope and its parents. The collected locations are passed to \l androiddeployqt for deployment consideration when diff --git a/src/corelib/doc/src/cmake/cmake-properties.qdoc b/src/corelib/doc/src/cmake/cmake-properties.qdoc index a933c242..fb167a7b 100644 --- a/src/corelib/doc/src/cmake/cmake-properties.qdoc +++ b/src/corelib/doc/src/cmake/cmake-properties.qdoc @@ -444,6 +444,33 @@ the property value overrides the runtime path where the resource file is found. \sa{The Qt Resource System} */ +/*! +\page cmake-source-file-property-qt-discard-file-contents.html +\ingroup cmake-source-file-properties-qtcore + +\title QT_DISCARD_FILE_CONTENTS +\target cmake-source-file-property-QT_DISCARD_FILE_CONTENTS + +\summary {Specifies that the given files should be empty in the resource file system} + +\cmakepropertysince 6.6 +\preliminarycmakeproperty + +When using the target-based variant of \l{qt6_add_resources}{qt_add_resources} +or \l{qt_add_qml_module}, setting this property to \c TRUE causes the file +contents to be omitted when creating the resource file system. The file name is +retained. + +This is useful if you want to strip QML source code from the binary. + +\note If you omit the QML source code from the binary, the QML engine has to +rely on the compilation units created by \l{qmlcachegen} or \l{qmlsc}. +Those are tied to the specific version of Qt they were built with. If you change +the version of Qt your application uses, they can't be loaded anymore. + +\sa{The Qt Resource System} +*/ + /*! \page cmake-target-property-qt-wasm-pthread-pool-size.html \ingroup cmake-properties-qtcore diff --git a/src/corelib/doc/src/cmake/policy/qtp0002.qdoc b/src/corelib/doc/src/cmake/policy/qtp0002.qdoc new file mode 100644 index 00000000..a40344a1 --- /dev/null +++ b/src/corelib/doc/src/cmake/policy/qtp0002.qdoc @@ -0,0 +1,63 @@ +// Copyright (C) 2023 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GFDL-1.3-no-invariants-only + +/*! +\page qt-cmake-policy-qtp0002.html +\ingroup qt-cmake-policies + +\title QTP0002 +\keyword qt_cmake_policy_qtp0002 + +\summary {Target properties that specify Android-specific paths may contain generator expressions.} + +This policy was introduced in Qt 6.6. It changes the processing of target +properties that specify Android-specific paths: +\list + \li \l QT_QML_IMPORT_PATH + \li \l QT_QML_ROOT_PATH + \li \l QT_ANDROID_PACKAGE_SOURCE_DIR + \li \l QT_ANDROID_EXTRA_PLUGINS + \li \l QT_ANDROID_EXTRA_LIBS +\endlist + +The \c OLD behavior of this policy doesn't allow generator expressions in the +target properties that specify Android-specific paths but implicitly converts +the specified paths to valid JSON strings. + +The \c NEW behavior of this policy allows using generator expressions in the +target properties that specify Android-specific paths, but they must evaluate to +valid JSON strings. + +The following value of the \l QT_ANDROID_EXTRA_PLUGINS property is converted to +a valid JSON string if you set the policy to OLD, but leads to an error if the +policy is set to NEW: +\badcode +set_target_properties( + QT_ANDROID_EXTRA_PLUGINS "\\path\\to\\MyPlugin.so" +) +\endcode +If the policy is set to NEW for the above example, the resulting JSON string in +the deployment settings file will contain escaped symbols instead of path +separators. + +Generator expressions are only supported if the policy is set to NEW, so the +OLD behavior generates a malformed deployment settings file with the following +code: +\badcode +set_target_properties( + QT_ANDROID_EXTRA_PLUGINS "$" +) +\endcode + +This property value works as expected with both OLD and NEW policy values: +\badcode +set_target_properties( + QT_ANDROID_EXTRA_PLUGINS "/path/to/MyPlugin.so" +) +\endcode + +\qtpolicydeprecatedbehavior + +\sa qt_policy, {Qt CMake policies} + +*/ diff --git a/src/corelib/doc/src/cmake/qt_policy.qdoc b/src/corelib/doc/src/cmake/qt_policy.qdoc index 7c44b4d8..6deb7a72 100644 --- a/src/corelib/doc/src/cmake/qt_policy.qdoc +++ b/src/corelib/doc/src/cmake/qt_policy.qdoc @@ -58,8 +58,7 @@ You can set \c behavior to one of the following options: \li \c{OLD} to explicitly opt-out of it \endlist -\note The \c{OLD} behavior of a policy is deprecated, and may -be removed in the future. +\qtpolicydeprecatedbehavior \sa qt_standard_project_setup diff --git a/src/corelib/doc/src/containers.qdoc b/src/corelib/doc/src/containers.qdoc index 4a7d5dc1..df828307 100644 --- a/src/corelib/doc/src/containers.qdoc +++ b/src/corelib/doc/src/containers.qdoc @@ -353,7 +353,7 @@ \section3 Java-Style Iterators \l{java-style-iterators}{Java-Style iterators} were introduced in Qt 4. Their API is modelled on Java's iterator classes. - New code should should prefer \l{STL-Style Iterators}. + New code should prefer \l{STL-Style Iterators}. \section1 Qt containers compared with std containers diff --git a/src/corelib/doc/src/includes/android-content-uri-limitations.qdocinc b/src/corelib/doc/src/includes/android-content-uri-limitations.qdocinc index f0808640..0521aff6 100644 --- a/src/corelib/doc/src/includes/android-content-uri-limitations.qdocinc +++ b/src/corelib/doc/src/includes/android-content-uri-limitations.qdocinc @@ -3,7 +3,7 @@ On Android, some limitations apply when dealing with \list \li Access permissions might be needed by prompting the user through the \l QFileDialog which implements - \l {Access documents and other files from shared storage}{Android's native file picker}. + \l {Android: Access documents and other files from shared storage}{Android's native file picker}. \li Aim to follow the \l {Android: Scoped storage}{Scoped storage} guidelines, such as using app specific directories instead of other public external directories. For more information, also see diff --git a/src/corelib/doc/src/includes/qstring.qdocinc b/src/corelib/doc/src/includes/qstring.qdocinc index a3c8810a..dade4f0a 100644 --- a/src/corelib/doc/src/includes/qstring.qdocinc +++ b/src/corelib/doc/src/includes/qstring.qdocinc @@ -22,3 +22,15 @@ Returns -1 if \a \2 is not found. Returns the index position of the last occurrence of the \1 \a \2 in this string, searching backward from index position \a from. //! [qstring-last-index-of] + +//! [qstring-local-8-bit-equivalent] +On Unix systems this is equivalent to \1(). +Note that on Apple systems this function does not take +\c\l{https://developer.apple.com/documentation/foundation/nsstring/1410091-defaultcstringencoding?language=objc} +{NSString.defaultCStringEncoding} or +\c\l{https://developer.apple.com/documentation/corefoundation/1541720-cfstringgetsystemencoding?language=objc} +{CFStringGetSystemEncoding()} into account, as these functions +typically return the legacy "Western (Mac OS Roman)" encoding, +which should not be used on modern Apple operating systems. +On Windows the system's current code page is used. +//! [qstring-local-8-bit-equivalent] diff --git a/src/corelib/doc/src/ipc.qdoc b/src/corelib/doc/src/ipc.qdoc new file mode 100644 index 00000000..e60dae82 --- /dev/null +++ b/src/corelib/doc/src/ipc.qdoc @@ -0,0 +1,489 @@ +// Copyright (C) 2022 Intel Corporation. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GFDL-1.3-no-invariants-only + +/*! + \page ipc.html + \title Inter-Process Communication + \ingroup groups + \ingroup frameworks-technologies + \keyword ipc + \ingroup explanations-networkingandconnectivity + + \brief An overview of Qt's inter-process communication functionality + + Qt supports many ways of communicating with other processes running in the + same system or in different systems. There are basically three types of + inter-process communication mechanisms: + + \list 1 + \li Synchronization primitives + \li Exchanging of arbitrary byte-level data + \li Passing structured messages + \endlist + + \section1 Synchronization primitives + + Qt only provides one class for explicit inter-process synchronization: + \l{QSystemSemaphore}. A QSystemSemaphore is like a \l{QSemaphore} that is + accessible by multiple processes in the same system. It is globally + identified by a "key", which in Qt is represented by the \l{QNativeIpcKey} + class. Additionally, depending on the OS, Qt may support multiple different + backends for sharing memory; see the \l{Native IPC Keys} documentation for + more information and limitations. + + It is possible to use regular thread-synchronization primitives such as + mutexes, wait conditions, and read-write locks, located in memory that is + shared between processes. Qt does not provide any class to support this, + but applications can use low-level operations on certain operating systems. + + Other Qt classes may be used to provide higher-level locking, like + \l{QLockFile}, or by acquiring a unique, system-wide resource. Such + techniques include \l{QTcpServer}{TCP} or \l{QUdpSocket}{UDP} ports or + well-known names in \l{QDBusConnection::registerService}{D-Bus}. + + \section1 Byte-level data sharing + + Using byte-level data, applications can implement any communication + protocol they may choose. Sharing of byte data can be stream-oriented + (serialized) or can allow random access (a similar condition to + QFileDevice::isSequential()). + + For serial communication, Qt provides a number of different classes and + even full modules: + \list + \li Pipes and FIFOs: \l QFile + \li Child processes: \l QProcess + \li Sockets: \l QTcpSocket, \l QUdpSocket (in \l{Qt Network}) + \li HTTP(S): \l QNetworkAccessManager (in \l{Qt Network}) and + \l QHttpServer (in \l{Qt HTTP Server}) + \li CoAP(S): \l QCoapClient (in \l{Qt CoAP}) + \endlist + + For random-access data sharing within the same system, Qt provides + \l{QSharedMemory}. See the \l{Shared Memory} documentation for detailed + information. + + \section1 Structured message passing + + Qt also provides a number of techniques to exchange structured messages + with other processes. Applications can build on top of the byte-level + solutions above, such as by using \l QJsonDocument or \l QXmlStreamReader / + \l QXmlStreamWriter over HTTP to perform JSONRPC or XMLRPC, respectively, + or \l QCborValue with QtCoAP. + + Dedicated Qt modules for structured messages and remote procedure-calling + include: + \list + \li \l{Qt D-Bus} + \li \l{Qt Remote Objects} + \li \l{Qt WebSockets} + \endlist +*/ + +/*! + \page shared-memory.html + \title Shared Memory + \keyword ipc + \keyword shared memory + + \brief Overview of the techniques for sharing memory between processes + + Qt provides two techniques to share memory with other processes in the same + system: \l{QSharedMemory} and memory-mapped files using \l{QFile}. Memory + that is shared with other processes is often referred to as a "segment", + and although it may have been implemented as specific segments on + processors with segmented memory models in the past, this is not the case + in any modern operating system. Shared memory segments are simply regions + of memory that the operating system will ensure are available to all + processes participating. + + \note The address at which the segment is located in memory will almost + always be different for each process that is participating in the sharing. + Therefore, applications must take care to share only position-independent + data, such as primitive C++ types or arrays of such types. + + \section1 Sharing memory using QSharedMemory + + QSharedMemory provides a simple API to create a shared memory segment of a + given size or attach to one that was created by another process. + Additionally, it provides a pair of methods to \l{QSharedMemory::}{lock} + and \l{QSharedMemory::}{unlock} the whole segment, using an internal + \l{QSystemSemaphore}. + + Shared memory segments and system semaphores are globally identified in the + system through a "key", which in Qt is represented by the \l{QNativeIpcKey} + class. Additionally, depending on the OS, Qt may support multiple different + backends for sharing memory; see the \l{Native IPC Keys} documentation for + more information and limitations. + + QSharedMemory is designed to share memory only within the same privilege + level (that is, not with untrusted other processes, such as those started + by other users). For backends that support it, QSharedMemory will create + segments such that only processes with the same privilege level can attach. + + \section1 Sharing memory via memory-mapped files + + Most files can be mapped to memory using QFile::map() and, if the + \l{QFileDevice::MapPrivateOption}{MapPrivateOption} option is not specified, + any writes to the mapped segment will be observed by all other processes + that have mapped the same file. Exceptions to files that can be mapped to + memory include remote files found in network shares or those located in + certain filesystems. Even if the operating system does allow mapping remote + files to memory, I/O operations on the file will likely be cached and + delayed, thus making true memory sharing impossible. + + This solution has the major advantages of being independent of any backend + API and of being simpler to interoperate with from non-Qt applications. + Since \l{QTemporaryFile} is a \l{QFile}, applications can use that class to + achieve clean-up semantics and to create unique shared memory segments too. + + To achieve locking of the shared memory segment, applications will need to + deploy their own mechanisms. One way may be to use \l QLockFile. Another + and less costly solution is to use QBasicAtomicInteger or \c{std::atomic} in + a pre-determined offset in the segment itself. Higher-level locking + primitives may be available on some operating systems; for example, on + Linux, applications can set the "pshared" flag in the mutex attribute + passed to \c{pthread_mutex_create()} to indicate that the mutex resides in + a shared memory segment. + + Be aware that the operating system will likely attempt to commit to + permanent storage any writes made to the shared memory. This may be desired + or it may be a performance penalty if the file itself was meant to be + temporary. In that case, applications should locate a RAM-backed + filesystem, such as \c{tmpfs} on Linux (see + QStorageInfo::fileSystemType()), or pass a flag to the native file-opening + function to inform the OS to avoid committing the contents to storage. + + It is possible to use file-backed shared memory to communicate with + untrusted processes, in which case the application should exercise great + care. The files may be truncated/shrunk and cause applications accessing + memory beyond the file's size to crash. + + \section2 Linux hints on memory-mapped files + + On modern Linux systems, while the \c{/tmp} directory is often a \c{tmpfs} + mount point, that is not a requirement. However, the \c{/dev/shm} directory + is required to be a \c{tmpfs} and exists for the very purpose of sharing + memory. Do note that it is world-readable and writable (like \c{/tmp} and + \c{/var/tmp}), so applications must be careful of the contents revealed + there. Another alternative is to use the XDG Runtime Directory (see + QStandardPaths::writableLocation() and \l{QStandardPaths::RuntimeLocation}), + which on Linux systems using systemd is a user-specific \c{tmpfs}. + + An even more secure solution is to create a "memfd" using \c{memfd_create(2)} + and use interprocess communication to pass the file descriptor, like + \l{QDBusUnixFileDescriptor} or by letting the child process of a \l{QProcess} + inherit it. "memfds" can also be sealed against being shrunk, so they are + safe to be used when communicating with processes with a different privilege + level. + + \section2 FreeBSD hints on memory-mapped files + + FreeBSD also has \c{memfd_create(2)} and can pass file descriptors to other + processes using the same techniques as Linux. It does not have temporary + filesystems mounted by default. + + \section2 Windows hints on memory-mapped files + + On Windows, the application can request the operating system avoid saving + the file's contents on permanent storage. This request is performed by + passing the \c{FILE_ATTRIBUTE_TEMPORARY} flag in the + \c{dwFlagsAndAttributes} parameter to the \c{CreateFile} Win32 function, + the \c{_O_SHORT_LIVED} flag to \c{_open()} low-level function, or by + including the modifier "T" to the + \c{fopen()} C runtime function. + + There's also a flag to inform the operating system to delete the file when + the last handle to it is closed (\c{FILE_FLAG_DELETE_ON_CLOSE}, + \c{_O_TEMPORARY}, and the "D" modifier), but do note that all processes + attempting to open the file must agree on using this flag or not using it. A + mismatch will likely cause a sharing violation and failure to open the file. +*/ + +/*! + \page native-ipc-keys.html + \title Native IPC Keys + \keyword ipc + \keyword shared memory + \keyword system semaphore + + \brief An overview of keys for QSharedMemory and QSystemSemaphore + + The \l QSharedMemory and \l QSystemSemaphore classes identify their + resource using a system-wide identifier known as a "key". The low-level key + value as well as the key type are encapsulated in Qt using the \l + QNativeIpcKey class. That class also provides the proper means of + exchanging the key with other processes, by way of + QNativeIpcKey::toString() and QNativeIpcKey::fromString(). + + Qt currently supports three distinct backends for those two classes, which + match the values available in the \l{QNativeIpcKey::Type} enumeration. + \list + \li POSIX Realtime extensions (IEEE 1003.1b, POSIX.1b) + \li X/Open System Interfaces (XSI) or System V (SVr4), though also now part of POSIX + \li Windows primitives + \endlist + + As the name indicates, the Windows primitives are only available on the + Windows operating system, where they are the default backend. The other two + are usually both available on Unix operating systems. The following table + provides an overview of typical availability since Qt 6.6: + + \table + \header \li Operating system \li POSIX \li System V \li Windows + \row \li Android \li \li \li + \row \li INTEGRITY \li \li \li + \row \li QNX \li Yes \li \li + \row \li \macos \li Yes \li Usually (1) \li + \row \li Other Apple OSes \li Yes \li \li + \row \li Other Unix systems \li Yes \li Yes \li + \row \li Windows \li Rarely (2) \li \li Yes + \endtable + + \note 1 Sandboxed \macos applications, which include all applications + distributed via the Apple App Store, may not use System V objects. + + \note 2 Some GCC-compatible C runtimes on Windows provide POSIX-compatible + shared memory support, but this is rare. It is always absent with the + Microsoft compiler. + + To determine whether a given key type is supported, applications should + call QSharedMemory::isKeyTypeSupported() and + QSystemSemaphore::isKeyTypeSupported(). + + QNativeIpcKey also provides support for compatibility with Qt applications + prior to its introduction. The following sections detail the limitations of + the backends, the contents of the string keys themselves, and + compatibility. + + \section1 Cross-platform safe key format + + QNativeIpcKey::setNativeKey() and QNativeIpcKey::nativeKey() handle the + low-level native key, which may be used with the native APIs and shared + with other, non-Qt processes (see below for the API). This format is not + usually cross-platform, so both QSharedMemory and QSystemSemaphore provide + a function to translate a cross-platform identifier string to the native + key: QSharedMemory::platformSafeKey() and + QSystemSemaphore::platformSafeKey(). + + The length of the cross-platform key on most platforms is the same as that + of a file name, but is severely limited on Apple platforms to only 30 + usable bytes (be mindful of UTF-8 encoding if using characters outside the + US-ASCII range). The format of the key is also similar to that of a file + path component, meaning it should not contain any characters not allowed in + file names, in particular those that separate path components (slash and + backslash), with the exception of sandboxed applications on Apple operating + systems. The following are good examples of cross-platform keys: "myapp", + "org.example.myapp", "org.example.myapp-12345". + + \b{Apple sandbox limitations:} if the application is running inside of a + sandbox in an Apple operating system, the key must be in a very specific + format: \c {/}. Sandboxing + is implied for all applications distributed through the Apple App Store. + See Apple's documentation + \l{https://developer.apple.com/library/archive/documentation/Security/Conceptual/AppSandboxDesignGuide/AppSandboxInDepth/AppSandboxInDepth.html#//apple_ref/doc/uid/TP40011183-CH3-SW24} + {here} and \l{https://developer.apple.com/documentation/bundleresources/entitlements/com_apple_security_application-groups} + {here} for more information, including how to obtain the application's group identifier. + + \section1 Native key format + + This section details the format of the native keys of the supported + backends. + + \section3 POSIX Realtime + Native keys resemble file names and may contain any character that file + names do, except for a slash. POSIX requires the first character in the key + name to be a slash and leaves undetermined whether any additional slashes + are permitted. On most operating systems, the key length is the same as a + file name, but it is limited to 32 characters on Apple operating systems + (this includes the first slash and the terminating null, so only 30 usable + characters are possible). + + The following are good examples of native POSIX keys: "/myapp", + "/org.example.myapp", "/org.example.myapp-12345". + + QSharedMemory::platformSafeKey() and QSystemSemaphore::platformSafeKey() + simply prepend the slash. On Apple operating systems, they also truncate + the result to the available size. + + \section3 Windows + Windows key types are NT + \l{https://learn.microsoft.com/en-us/windows/win32/sync/object-namespaces}{kernel + object names} and may be up to \c{MAX_PATH} (260) characters in length. + They look like relative paths (that is, they don't start with a backslash + or a drive letter), but unlike file names on Windows, they are + case-sensitive. + + The following are good examples of native Windows keys: "myapp", + "org.example.myapp", "org.example.myapp-12345". + + QSharedMemory::platformSafeKey() and QSystemSemaphore::platformSafeKey() + insert a prefix to disambiguate shared memory and system semaphores, + respectively. + + \section3 X/Open System Interfaces (XSI) / System V + System V keys take the form of the name of a file in the system, and thus + have the exact same limitations as file paths do. Both QSharedMemory and + QSystemSemaphore will create this file if it does not exist when creating + the object. If auto-removal is disabled, it may also be shared between + QSharedMemory and QSystemSemaphore without conflict and can be any extant + file (for example, it can be the process executable itself, see + QCoreApplication::applicationFilePath()). The path should be an absolute + one to avoid mistakes due to different current directories. + + QSharedMemory::platformSafeKey() and QSystemSemaphore::platformSafeKey() + always return an absolute path. If the input was already absolute, they + will return their input unchanged. Otherwise, they will prepend a suitable + path where the application usually has permission to create files in. + + \section1 Ownership + + Shared memory and system semaphore objects need to be created before use, + which is accomplished with QSharedMemory::create() or by passing + QSystemSemaphore::Create to the constructor, respectively. + + On Unix systems, the Qt classes that created the object will be responsible + for cleaning up the object in question. Therefore, if the application with + that C++ object exits uncleanly (a crash, qFatal(), etc.), the object may + be left behind. If that happens, applications may fail to create the + object again and should instead attach to an existing one. For example, for + QSharedMemory: + + \code + if (!shm.create(4096) && shm.error() == QSharedMemory::AlreadyExists) + shm.attach(); + \endcode + + Re-attaching to a QSystemSemaphore is probably unwise, as the token counter + in it is probably in an unknown state and therefore may lead to deadlocks. + + \section3 POSIX Realtime + POSIX Realtime object ownership is patterned after files, in the sense that + they exist independent of any process using them or not. Qt is unable to + determine if the object is still in use, so auto-removal will remove it + even then, which will make attaching to the same object impossible but + otherwise not affecting existing attachments. + + Prior to Qt 6.6, Qt never cleaned up POSIX Realtime objects, except on QNX. + + \section3 X/Open System Interfaces (XSI) / System V + There are two resources managed by the Qt classes: the file the key refers + to and the object itself. QSharedMemory manages the object cooperatively: + the last attachment is responsible for removing the object itself and then + removing the key file. QSystemSemaphore will remove the object if and only + if it was passed QSystemSemaphore::Create; additionally, if it created the + key file, it will remove that too. + + Since Qt 6.6, it is possible to ask either class not to clean up. + + \section3 Windows + The operating system owns the object and will clean up after the last + handle to the object is closed. + + \section1 Interoperability with old Qt applications + + The QNativeIpcKey class was introduced in Qt 6.6. Prior to this version, + QSharedMemory and QSystemSemaphore backends were determined at the time of + Qt's own build. For Windows systems, it was always the Windows backend. For + Unix systems, it defaulted to the System V backend if the configuration + script determined it was available. If it was not available, it fell back + to the POSIX one (since Qt 4.8). The POSIX backend could be explicitly + selected using the \c{-feature-ipc_posix} option to the Qt configure + script; if it was enabled, the \c{QT_POSIX_IPC} macro would be defined. + + Qt 6.6 retains the configure script option but it no longer controls the + availability of the backends. Instead, it changes what + QNativeIpcKey::legacyDefaultTypeForOs() will return. Applications that need + to retain compatibility must use this key type exclusively to guarantee + interoperability. + + The API in both QSharedMemory and QSystemSemaphore had the concept of a + cross-platform key, which is now deprecated in favor of using + QSharedMemory::legacyNativeKey() and QSystemSemaphore::legacyNativeKey(). + Those two functions produce the same native key as the deprecated functions + did in prior versions. If the old code was for example: + + \code + QSharedMemory shm("org.example.myapplication"); + QSystemSemaphore sem("org.example.myapplication"); + \endcode + + It can be updated to be: + \code + QSharedMemory shm(QSharedMemory::legacyNativeKey("org.example.myapplication")); + QSystemSemaphore sem(QSystemSemaphore::legacyNativeKey("org.example.myapplication")); + \endcode + + If the two applications exchanged native keys, there is no need to update + code such as: + + \code + QSharedMemory shm; + shm.setNativeKey(key); + \endcode + + Though if the older application did accept a native key, the new one may + opt to use \c{platformSafeKey()} with a second argument of + QNativeIpcKey::legacyDefaultTypeForOs(). + + \section3 X/Open System Interfaces (XSI) / System V + Never use existing files for QSharedMemory keys, as the old Qt application + may attempt to remove it. Instead, let QSharedMemory create it. + + \section1 Interoperability with non-Qt applications + + Interoperability with non-Qt applications is possible, with some limitations: + \list + \li Creation of shared memory segments must not race + \li QSharedMemory support for locking the segment is unavailable + \endlist + + Communication with non-Qt applications must always be through the native + key. + + QSharedMemory always maps the entire segment to memory. The non-Qt + application may choose to only map a subset of it to memory, with no ill + effects. + + \section3 POSIX Realtime + POSIX shared memory can be opened using + \l{https://pubs.opengroup.org/onlinepubs/9699919799/functions/shm_open.html}{shm_open()} + and POSIX system semaphores can be opened using + \l{https://pubs.opengroup.org/onlinepubs/9699919799/functions/sem_open.html}{sem_open()}. + + Both of those functions take a \c name parameter that is the result of + QNativeIpcKey::nativeKey(), encoded for file names using + QFile::encodeName() / QFile::decodeName(). + + \section3 Windows + Windows shared memory objects can be opened using + \l{https://learn.microsoft.com/en-us/windows/win32/api/memoryapi/nf-memoryapi-createfilemappingw}{CreateFileMappingW} + and Windows system semaphore objects can be opened using + \l{https://learn.microsoft.com/en-us/windows/win32/api/synchapi/nf-synchapi-createsemaphorew}{CreateSemaphoreW}. + Despite the name of both functions starting with "Create", they are able + to attach to existing objects. + + The \c lpName parameter to those functions is the result of + QNativeIpcKey::nativeKey(), without transformation. + + If the foreign application uses the non-Unicode version of those functions + (ending in "A"), the name can be converted to and from 8-bit using QString. + + \section3 X/Open System Interfaces (XSI) / System V + System V shared memory can be obtained using + \l{https://pubs.opengroup.org/onlinepubs/9699919799/functions/shmget.html}{shmget()} + and System V system semaphores can be obtained using + \l{https://pubs.opengroup.org/onlinepubs/9699919799/functions/semget.html}{semget()}. + + The \c{key} parameter to either of those functions is the result of the + \l{https://pubs.opengroup.org/onlinepubs/9699919799/functions/ftok.html}{ftok()} + function when passed the file name obtained from QNativeIpcKey::nativeKey() + with an \c id of 81 or 0x51 (the ASCII capital letter 'Q'). + + System V semaphore objects may contain multiple semaphores, but + QSystemSemaphore only uses the first one (number 0 for \c{sem_num}). + + Both QSharedMemory and QSystemSemaphore default to removing the object + using the \c{IPC_RMID} operation to \c{shmctl()} and \c{semctl()} + respectively if they are the last attachment. +*/ diff --git a/src/corelib/doc/src/json.qdoc b/src/corelib/doc/src/json.qdoc index 3097075e..27959d47 100644 --- a/src/corelib/doc/src/json.qdoc +++ b/src/corelib/doc/src/json.qdoc @@ -6,7 +6,7 @@ \title JSON Support in Qt \ingroup qt-basic-concepts \brief An overview of JSON support in Qt. - + \ingroup explanations-dataprocessingandio \ingroup frameworks-technologies \keyword JSON diff --git a/src/corelib/doc/src/objectmodel/metaobjects.qdoc b/src/corelib/doc/src/objectmodel/metaobjects.qdoc index 62226ca4..3d768544 100644 --- a/src/corelib/doc/src/objectmodel/metaobjects.qdoc +++ b/src/corelib/doc/src/objectmodel/metaobjects.qdoc @@ -5,7 +5,7 @@ \page metaobjects.html \title The Meta-Object System \brief An overview of Qt's meta-object system and introspection capabilities. - + \ingroup explanations-basics \ingroup qt-basic-concepts \keyword meta-object \keyword Meta-Object System diff --git a/src/corelib/doc/src/objectmodel/properties.qdoc b/src/corelib/doc/src/objectmodel/properties.qdoc index eef2df70..93624574 100644 --- a/src/corelib/doc/src/objectmodel/properties.qdoc +++ b/src/corelib/doc/src/objectmodel/properties.qdoc @@ -5,7 +5,7 @@ \page properties.html \title The Property System \brief An overview of Qt's property system. - + \ingroup explanations-basics \ingroup qt-basic-concepts \keyword Qt's Property System diff --git a/src/corelib/doc/src/objectmodel/signalsandslots.qdoc b/src/corelib/doc/src/objectmodel/signalsandslots.qdoc index 836fa07a..f0eeb200 100644 --- a/src/corelib/doc/src/objectmodel/signalsandslots.qdoc +++ b/src/corelib/doc/src/objectmodel/signalsandslots.qdoc @@ -8,7 +8,7 @@ \ingroup qt-basic-concepts \brief An overview of Qt's signals and slots inter-object communication mechanism. - + \ingroup explanations-basics Signals and slots are used for communication between objects. The signals and slots mechanism is a central feature of Qt and probably the part that differs most from the features provided by diff --git a/src/corelib/doc/src/qtcore-index.qdoc b/src/corelib/doc/src/qtcore-index.qdoc index 520dd0b7..38452e1b 100644 --- a/src/corelib/doc/src/qtcore-index.qdoc +++ b/src/corelib/doc/src/qtcore-index.qdoc @@ -76,6 +76,7 @@ \li \l{The Animation Framework} \li \l{JSON Support in Qt} \li \l{CBOR Support in Qt} + \li \l{Inter-Process Communication} \li \l{How to Create Qt Plugins} \li \l{The Event System} \li \l{Application Permissions} diff --git a/src/corelib/doc/src/resource-system.qdoc b/src/corelib/doc/src/resource-system.qdoc index 27993a4d..0ee706ed 100644 --- a/src/corelib/doc/src/resource-system.qdoc +++ b/src/corelib/doc/src/resource-system.qdoc @@ -160,6 +160,25 @@ The file is from the application then only accessible as \c :/cut-img.png or \c{qrc:/cut-img.png}. + \section2 Discarding the file contents + + Sometimes you want to add a file node to the resource file system but + don't actually want to add the file contents. \c .qrc files allow this + by setting the \c empty attribute to \c{true}. + + \snippet code/doc_src_resources.qdoc 4 + + The resulting file is then still accessible from the application, but + its contents are empty. + + This is useful to strip QML source code from an application binary. + + \note If you omit the QML source code from the binary, the QML engine has to + rely on the compilation units created by \l{qmlcachegen} or \l{qmlsc}. + Those are tied to the specific version of Qt they were built with. If you + change the version of Qt your application uses, they can't be loaded + anymore. + \section2 Language Selectors Some resources need to change based on the user's locale, such as diff --git a/src/corelib/global/q20chrono.h b/src/corelib/global/q20chrono.h new file mode 100644 index 00000000..b296b4e9 --- /dev/null +++ b/src/corelib/global/q20chrono.h @@ -0,0 +1,62 @@ +// Copyright (C) 2023 Ahmad Samir +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only + +#ifndef Q20CHRONO_H +#define Q20CHRONO_H + +#include + +#include + +// +// W A R N I N G +// ------------- +// +// This file is not part of the Qt API. Types and functions defined in this +// file can reliably be replaced by their std counterparts, once available. +// You may use these definitions in your own code, but be aware that we +// will remove them once Qt depends on the C++ version that supports +// them in namespace std. There will be NO deprecation warning, the +// definitions will JUST go away. +// +// If you can't agree to these terms, don't use these definitions! +// +// We mean it. +// + +QT_BEGIN_NAMESPACE + +namespace q20 { +namespace chrono { + +#if defined(__GLIBCXX__) +// https://gcc.gnu.org/git/?p=gcc.git;a=blob;f=libstdc%2B%2B-v3/include/bits/chrono.h +using IntRep = int64_t; +#else +// https://github.com/llvm/llvm-project/blob/main/libcxx/include/__chrono/duration.h +// https://github.com/microsoft/STL/blob/main/stl/inc/__msvc_chrono.hpp +using IntRep = int; +#endif + +#if __cpp_lib_chrono >= 201907L +using std::chrono::days; +using std::chrono::weeks; +using std::chrono::years; +using std::chrono::months; + +static_assert(std::is_same_v); +static_assert(std::is_same_v); +static_assert(std::is_same_v); +static_assert(std::is_same_v); +#else // __cpp_lib_chrono >= 201907L +using days = std::chrono::duration>; +using weeks = std::chrono::duration, days::period>>; +using years = std::chrono::duration, days::period>>; +using months = std::chrono::duration>>; +#endif // __cpp_lib_chrono >= 201907L +} // namespace chrono +} // namespace q20 + +QT_END_NAMESPACE + +#endif /* Q20CHRONO_H */ diff --git a/src/corelib/global/q20iterator.h b/src/corelib/global/q20iterator.h index 23b6406b..9ed4d699 100644 --- a/src/corelib/global/q20iterator.h +++ b/src/corelib/global/q20iterator.h @@ -39,6 +39,16 @@ namespace q20 { #endif } // namespace q20 +// like q20::iter_reference_t +namespace q20 { +#ifdef __cpp_lib_ranges + using std::iter_reference_t; +#else + template // unconstrained (constraint requires concepts) + using iter_reference_t = decltype(*std::declval()); +#endif // __cpp_lib_ranges +} // namespace q20 + QT_END_NAMESPACE #endif /* Q20ITERATOR_H */ diff --git a/src/corelib/global/q20memory.h b/src/corelib/global/q20memory.h index bb337a9f..008f4dde 100644 --- a/src/corelib/global/q20memory.h +++ b/src/corelib/global/q20memory.h @@ -1,4 +1,5 @@ // Copyright (C) 2023 The Qt Company Ltd. +// Copyright (C) 2020 Klarälvdalens Datakonsult AB, a KDAB Group company, info@kdab.com, author Marc Mutz // SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only #ifndef Q20MEMORY_H @@ -7,9 +8,9 @@ #include #include -#include #include +#include // // W A R N I N G @@ -45,6 +46,48 @@ T *construct_at(T *ptr, Args && ... args) #endif // __cpp_lib_constexpr_dynamic_alloc } // namespace q20 + +namespace q20 { +// like std::to_address +#ifdef __cpp_lib_to_address +using std::to_address; +#else +// http://eel.is/c++draft/pointer.conversion +template +constexpr T *to_address(T *p) noexcept { + // http://eel.is/c++draft/pointer.conversion#1: + // Mandates: T is not a function type. + static_assert(!std::is_function_v, "to_address must not be used on function types"); + return p; +} + +template , bool> = true> +constexpr auto to_address(const Ptr &ptr) noexcept; // fwd declared + +namespace detail { + // http://eel.is/c++draft/pointer.conversion#3 + template + struct to_address_helper { + static auto get(const Ptr &ptr) noexcept + { return q20::to_address(ptr.operator->()); } + }; + template + struct to_address_helper::to_address(std::declval())) + >> + { + static auto get(const Ptr &ptr) noexcept + { return std::pointer_traits::to_address(ptr); } + }; +} // namespace detail + +template , bool>> +constexpr auto to_address(const Ptr &ptr) noexcept +{ return detail::to_address_helper::get(ptr); } + +#endif // __cpp_lib_to_address +} // namespace q20 + QT_END_NAMESPACE #endif /* Q20MEMORY_H */ diff --git a/src/corelib/global/q20type_traits.h b/src/corelib/global/q20type_traits.h index 2e812ed3..80ac1fa4 100644 --- a/src/corelib/global/q20type_traits.h +++ b/src/corelib/global/q20type_traits.h @@ -38,6 +38,19 @@ using remove_cvref_t = std::remove_cv_t>; #endif // __cpp_lib_remove_cvref } +namespace q20 { +// like std::type_identity(_t) +#ifdef __cpp_lib_type_identity +using std::type_identity; +using std::type_identity_t; +#else +template +struct type_identity { using type = T; }; +template +using type_identity_t = typename type_identity::type; +#endif // __cpp_lib_type_identity +} + QT_END_NAMESPACE #endif /* Q20TYPE_TRAITS_H */ diff --git a/src/corelib/global/q23utility.cpp b/src/corelib/global/q23utility.cpp new file mode 100644 index 00000000..9c5365c5 --- /dev/null +++ b/src/corelib/global/q23utility.cpp @@ -0,0 +1,25 @@ +// 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 + +QT_BEGIN_NAMESPACE + +#define CHECK2(cvref_in, cvref_out) \ + static_assert(std::is_same_v< \ + decltype(q23::forward_like(std::declval())), \ + long cvref_out \ + >, "oops: cvref '" #cvref_in "' doesn't work") \ + /* end */ +#define CHECK(cvref) CHECK2(cvref, cvref) +CHECK2(/**/, &&); +CHECK(&); +CHECK(&&); +CHECK2(const, const &&); +CHECK(const &); +CHECK(const &&); +// volatile is not supported +#undef CHECK +#undef CHECK2 + +QT_END_NAMESPACE diff --git a/src/corelib/global/q23utility.h b/src/corelib/global/q23utility.h new file mode 100644 index 00000000..9ae5389b --- /dev/null +++ b/src/corelib/global/q23utility.h @@ -0,0 +1,75 @@ +// 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 Q23UTILITY_H +#define Q23UTILITY_H + +#include + +#include + +// +// W A R N I N G +// ------------- +// +// This file is not part of the Qt API. Types and functions defined in this +// file can reliably be replaced by their std counterparts, once available. +// You may use these definitions in your own code, but be aware that we +// will remove them once Qt depends on the C++ version that supports +// them in namespace std. There will be NO deprecation warning, the +// definitions will JUST go away. +// +// If you can't agree to these terms, don't use these definitions! +// +// We mean it. +// + +QT_BEGIN_NAMESPACE + +namespace q23 { +// like std::forward_like +#ifdef __cpp_lib_forward_like +using std::forward_like; +#else + +namespace _detail { + +// [forward]/6.1 COPY_CONST +template +using copy_const_t = std::conditional_t< + std::is_const_v, const B, + /* else */ B + >; + +// [forward]/6.2 OVERRIDE_REF +template +using override_ref_t = std::conditional_t< + std::is_rvalue_reference_v, std::remove_reference_t&&, + /* else */ B& + >; + +// [forward]/6.3 "V" +template +using forward_like_ret_t = override_ref_t< + T&&, + copy_const_t< + std::remove_reference_t, + std::remove_reference_t + > + >; + +} // namespace detail + +// http://eel.is/c++draft/forward#lib:forward_like +template +[[nodiscard]] constexpr auto forward_like(U &&x) noexcept + -> _detail::forward_like_ret_t +{ + using V = _detail::forward_like_ret_t; + return static_cast(x); +} +#endif // __cpp_lib_forward_like +} // namespace q23 + +QT_END_NAMESPACE + +#endif /* Q23UTILITY_H */ diff --git a/src/corelib/global/qassert.cpp b/src/corelib/global/qassert.cpp index 4165429d..462f1412 100644 --- a/src/corelib/global/qassert.cpp +++ b/src/corelib/global/qassert.cpp @@ -139,29 +139,20 @@ void qBadAlloc() /*! \macro void Q_ASSUME(bool expr) + \deprecated \relates \since 5.0 - Causes the compiler to assume that \a expr is \c true. This macro is useful - for improving code generation, by providing the compiler with hints about - conditions that it would not otherwise know about. However, there is no - guarantee that the compiler will actually use those hints. + Causes the compiler to assume that \a expr is \c true. - This macro could be considered a "lighter" version of \l{Q_ASSERT()}. While - Q_ASSERT will abort the program's execution if the condition is \c false, - Q_ASSUME will tell the compiler not to generate code for those conditions. - Therefore, it is important that the assumptions always hold, otherwise - undefined behavior may occur. + This macro is known to produce worse code than when no assumption was + inserted in the code, with some compiler versions. The arguments passed to + it are always evaluated, even in release mode, with some compilers and not + others, so application code needs to be aware of those possible differences + in behavior. - If \a expr is a constantly \c false condition, Q_ASSUME will tell the compiler - that the current code execution cannot be reached. That is, Q_ASSUME(false) - is equivalent to Q_UNREACHABLE(). - - In debug builds the condition is enforced by an assert to facilitate debugging. - - \note Q_LIKELY() tells the compiler that the expression is likely, but not - the only possibility. Q_ASSUME tells the compiler that it is the only - possibility. + Do not use it in new code. It is retained as-is for compatibility with old + code and will likely be removed in the next major version Qt. \sa Q_ASSERT(), Q_UNREACHABLE(), Q_LIKELY() */ @@ -199,7 +190,7 @@ void qBadAlloc() compilers that need them, without causing warnings for compilers that complain about its presence. - \sa Q_ASSERT(), Q_ASSUME(), qFatal(), Q_UNREACHABLE_RETURN() + \sa Q_ASSERT(), qFatal(), Q_UNREACHABLE_RETURN() */ /*! diff --git a/src/corelib/global/qassert.h b/src/corelib/global/qassert.h index 28c6b6c8..862a707e 100644 --- a/src/corelib/global/qassert.h +++ b/src/corelib/global/qassert.h @@ -7,6 +7,7 @@ #include #include #include +#include #if 0 #pragma qt_class(QtAssert) @@ -17,7 +18,7 @@ QT_BEGIN_NAMESPACE #if defined(__cplusplus) -#ifndef Q_CC_MSVC +#if !defined(Q_CC_MSVC_ONLY) Q_NORETURN #endif Q_DECL_COLD_FUNCTION @@ -31,7 +32,7 @@ Q_CORE_EXPORT void qt_assert(const char *assertion, const char *file, int line) # endif #endif -#ifndef Q_CC_MSVC +#if !defined(Q_CC_MSVC_ONLY) Q_NORETURN #endif Q_DECL_COLD_FUNCTION diff --git a/src/corelib/global/qcompare_impl.h b/src/corelib/global/qcompare_impl.h index 2556f4af..c52417fc 100644 --- a/src/corelib/global/qcompare_impl.h +++ b/src/corelib/global/qcompare_impl.h @@ -1,15 +1,16 @@ // Copyright (C) 2020 Klarälvdalens Datakonsult AB, a KDAB Group company, info@kdab.com, author Giuseppe D'Angelo // SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only +#ifndef QCOMPARE_IMPL_H +#define QCOMPARE_IMPL_H + #if 0 #pragma qt_sync_skip_header_check #pragma qt_sync_stop_processing #endif -#ifndef QCOMPARE_IMPL_H -#define QCOMPARE_IMPL_H - -#include +#include +#include QT_BEGIN_NAMESPACE diff --git a/src/corelib/global/qcompilerdetection.h b/src/corelib/global/qcompilerdetection.h index 70fa7f6c..5525be28 100644 --- a/src/corelib/global/qcompilerdetection.h +++ b/src/corelib/global/qcompilerdetection.h @@ -2,9 +2,7 @@ // Copyright (C) 2016 Intel Corporation. // SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only -#ifndef QGLOBAL_H -# include -#endif +#include #if 0 #pragma qt_class(QtCompilerDetection) @@ -179,7 +177,7 @@ # ifdef Q_OS_WIN # define Q_DECL_EXPORT __declspec(dllexport) # define Q_DECL_IMPORT __declspec(dllimport) -# elif defined(QT_VISIBILITY_AVAILABLE) +# else # define Q_DECL_EXPORT_OVERRIDABLE __attribute__((visibility("default"), weak)) # ifdef QT_USE_PROTECTED_VISIBILITY # define Q_DECL_EXPORT __attribute__((visibility("protected"))) @@ -513,8 +511,7 @@ * For library features, we assume is present (this header includes it). * * For a full listing of feature test macros, see - * https://isocpp.org/std/standing-documents/sd-6-sg10-feature-test-recommendations (by macro) - * https://en.cppreference.com/w/User:D41D8CD98F/feature_testing_macros (by C++ version) + * https://en.cppreference.com/w/cpp/feature_test * * C++ extensions: * Q_COMPILER_RESTRICTED_VLA variable-length arrays, prior to __cpp_runtime_arrays @@ -548,7 +545,8 @@ # endif /* C++11 features, see http://clang.llvm.org/cxx_status.html */ -# if __cplusplus >= 201103L || defined(__GXX_EXPERIMENTAL_CXX0X__) +# if (defined(__cplusplus) && __cplusplus >= 201103L) \ + || defined(__GXX_EXPERIMENTAL_CXX0X__) /* Detect C++ features using __has_feature(), see http://clang.llvm.org/docs/LanguageExtensions.html#cxx11 */ # if __has_feature(cxx_alignas) # define Q_COMPILER_ALIGNAS @@ -646,10 +644,10 @@ # if Q_CC_CLANG >= 209 /* since clang 2.9 */ # define Q_COMPILER_EXTERN_TEMPLATES # endif -# endif +# endif // (defined(__cplusplus) && __cplusplus >= 201103L) || defined(__GXX_EXPERIMENTAL_CXX0X__) /* C++1y features, deprecated macros. Do not update this list. */ -# if __cplusplus > 201103L +# if defined(__cplusplus) && __cplusplus > 201103L //# if __has_feature(cxx_binary_literals) //# define Q_COMPILER_BINARY_LITERALS // see above //# endif @@ -671,7 +669,7 @@ # if __has_feature(cxx_runtime_array) # define Q_COMPILER_VLA # endif -# endif +# endif // if defined(__cplusplus) && __cplusplus > 201103L # if defined(__STDC_VERSION__) # if __has_feature(c_static_assert) @@ -895,7 +893,7 @@ # endif // !_HAS_CONSTEXPR # endif // !__GLIBCXX__ && !_LIBCPP_VERSION # endif // Q_OS_QNX -# if defined(Q_CC_CLANG) && defined(Q_OS_MAC) +# if defined(Q_CC_CLANG) && defined(Q_OS_DARWIN) # if defined(__GNUC_LIBSTD__) && ((__GNUC_LIBSTD__-0) * 100 + __GNUC_LIBSTD_MINOR__-0 <= 402) // Apple has not updated libstdc++ since 2007, which means it does not have // or std::move. Let's disable these features @@ -956,6 +954,13 @@ # define Q_REQUIRED_RESULT [[nodiscard]] #endif +#if __has_cpp_attribute(nodiscard) >= 201907L /* used for both P1771 and P1301... */ +// [[nodiscard]] constructor (P1771) +# ifndef Q_NODISCARD_CTOR +# define Q_NODISCARD_CTOR [[nodiscard]] +# endif +#endif + #if __has_cpp_attribute(maybe_unused) # undef Q_DECL_UNUSED # define Q_DECL_UNUSED [[maybe_unused]] @@ -1005,6 +1010,9 @@ #ifndef Q_REQUIRED_RESULT # define Q_REQUIRED_RESULT #endif +#ifndef Q_NODISCARD_CTOR +# define Q_NODISCARD_CTOR +#endif #ifndef Q_DECL_DEPRECATED # define Q_DECL_DEPRECATED #endif @@ -1368,6 +1376,20 @@ QT_WARNING_DISABLE_MSVC(4530) /* C++ exception handler used, but unwind semantic # endif #endif +#if defined(__cplusplus) && __cplusplus >= 202002L // P0846 doesn't have a feature macro :/ +# define QT_COMPILER_HAS_P0846 +#endif + +#ifdef QT_COMPILER_HAS_P0846 +# define QT_ENABLE_P0846_SEMANTICS_FOR(func) +#else + class QT_CLASS_JUST_FOR_P0846_SIMULATION; +# define QT_ENABLE_P0846_SEMANTICS_FOR(func) \ + template \ + void func (QT_CLASS_JUST_FOR_P0846_SIMULATION *); \ + /* end */ +#endif // !P0846 + #endif // __cplusplus #endif // QCOMPILERDETECTION_H diff --git a/src/corelib/global/qcompilerdetection.qdoc b/src/corelib/global/qcompilerdetection.qdoc index 377323a5..5074d8f6 100644 --- a/src/corelib/global/qcompilerdetection.qdoc +++ b/src/corelib/global/qcompilerdetection.qdoc @@ -175,7 +175,7 @@ \since 5.8 Can be used in switch statements at the end of case block to tell the compiler - and other developers that that the lack of a break statement is intentional. + and other developers that the lack of a break statement is intentional. This is useful since a missing break statement is often a bug, and some compilers can be configured to emit warnings when one is not found. @@ -197,7 +197,7 @@ \snippet code/src_corelib_global_qglobal.cpp qlikely - \sa Q_UNLIKELY(), Q_ASSUME() + \sa Q_UNLIKELY() */ /*! @@ -392,3 +392,21 @@ If this macro is used outside a function, the behavior is undefined. */ + +/*! + \macro Q_NODISCARD_CTOR + \relates + \since 6.6 + + \brief Expands to \c{[[nodiscard]]} on compilers that accept it on constructors. + + Otherwise it expands to nothing. + + Constructors marked as Q_NODISCARD_CTOR cause a compiler warning if a call + site doesn't use the resulting object. + + This macro is exists solely to prevent warnings on compilers that don't + implement the feature. If your supported platforms all allow \c{[[nodiscard]]} + on constructors, we strongly recommend you use the C++ attribute directly instead + of this macro. +*/ diff --git a/src/corelib/global/qconfig-bootstrapped.h b/src/corelib/global/qconfig-bootstrapped.h index 78d622f5..ace4c51d 100644 --- a/src/corelib/global/qconfig-bootstrapped.h +++ b/src/corelib/global/qconfig-bootstrapped.h @@ -63,6 +63,7 @@ #define QT_FEATURE_jalalicalendar -1 #define QT_FEATURE_journald -1 #define QT_FEATURE_futimens -1 +#undef QT_FEATURE_future #define QT_FEATURE_future -1 #define QT_FEATURE_itemmodel -1 #define QT_FEATURE_library -1 @@ -81,7 +82,6 @@ # define QT_FEATURE_renameat2 -1 #endif #define QT_FEATURE_shortcut -1 -#define QT_FEATURE_signaling_nan -1 #define QT_FEATURE_slog2 -1 #ifdef __GLIBC_PREREQ # define QT_FEATURE_statx (__GLIBC_PREREQ(2, 28) ? 1 : -1) @@ -92,6 +92,7 @@ #define QT_NO_SYSTEMLOCALE #define QT_FEATURE_temporaryfile 1 #define QT_FEATURE_textdate 1 +#undef QT_FEATURE_thread #define QT_FEATURE_thread -1 #define QT_FEATURE_timezone -1 #define QT_FEATURE_topleveldomain -1 diff --git a/src/corelib/global/qcontainerinfo.h b/src/corelib/global/qcontainerinfo.h index e2787707..14468510 100644 --- a/src/corelib/global/qcontainerinfo.h +++ b/src/corelib/global/qcontainerinfo.h @@ -50,6 +50,11 @@ inline constexpr bool has_size_v = false; template inline constexpr bool has_size_v> = true; +template +inline constexpr bool has_reserve_v = false; +template +inline constexpr bool has_reserve_v> = true; + template inline constexpr bool has_clear_v = false; template diff --git a/src/corelib/global/qendian.h b/src/corelib/global/qendian.h index 9cd0044c..60c4a7b1 100644 --- a/src/corelib/global/qendian.h +++ b/src/corelib/global/qendian.h @@ -105,6 +105,23 @@ inline constexpr T qbswap(T source) return T(qbswap_helper(typename QIntegerForSizeof::Unsigned(source))); } +#ifdef QT_SUPPORTS_INT128 +// extra definitions for q(u)int128, in case std::is_integral_v<~~> == false +inline constexpr quint128 qbswap(quint128 source) +{ + quint128 result = {}; + result = qbswap_helper(quint64(source)); + result <<= 64; + result |= qbswap_helper(quint64(source >> 64)); + return result; +} + +inline constexpr quint128 qbswap(qint128 source) +{ + return qint128(qbswap(quint128(source))); +} +#endif + // floating specializations template Float qbswapFloatHelper(Float source) diff --git a/src/corelib/global/qexceptionhandling.h b/src/corelib/global/qexceptionhandling.h index aca8de90..76c6185c 100644 --- a/src/corelib/global/qexceptionhandling.h +++ b/src/corelib/global/qexceptionhandling.h @@ -6,6 +6,7 @@ #include #include +#include #if 0 #pragma qt_class(QtExceptionHandling) diff --git a/src/corelib/global/qflags.h b/src/corelib/global/qflags.h index 26a8e5c9..cc028e00 100644 --- a/src/corelib/global/qflags.h +++ b/src/corelib/global/qflags.h @@ -1,12 +1,12 @@ // 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 -#include - #ifndef QFLAGS_H #define QFLAGS_H +#include +#include + #include QT_BEGIN_NAMESPACE diff --git a/src/corelib/global/qfloat16.h b/src/corelib/global/qfloat16.h index 06542c4b..15dab5e7 100644 --- a/src/corelib/global/qfloat16.h +++ b/src/corelib/global/qfloat16.h @@ -87,7 +87,7 @@ public: inline operator float() const noexcept; #endif template && !std::is_same_v>> - explicit qfloat16(T value) noexcept : qfloat16(NearestFloat(value)) {} + constexpr explicit qfloat16(T value) noexcept : qfloat16(NearestFloat(value)) {} // Support for qIs{Inf,NaN,Finite}: bool isInf() const noexcept { return (b16 & 0x7fff) == 0x7c00; } diff --git a/src/corelib/global/qglobal.h b/src/corelib/global/qglobal.h index 3a9d40a0..1009057b 100644 --- a/src/corelib/global/qglobal.h +++ b/src/corelib/global/qglobal.h @@ -27,20 +27,21 @@ #include -#include - #include #include #include -#include -#include -#include - +#ifndef __ASSEMBLER__ +# include +# include +# include +#endif /* !__ASSEMBLER__ */ #include #if defined(__cplusplus) +#include + // We need to keep QTypeInfo, QSysInfo, QFlags, qDebug & family in qglobal.h for compatibility with Qt 4. // Be careful when changing the order of these files. #include diff --git a/src/corelib/global/qglobal_p.h b/src/corelib/global/qglobal_p.h index d6e84acd..31f67510 100644 --- a/src/corelib/global/qglobal_p.h +++ b/src/corelib/global/qglobal_p.h @@ -52,21 +52,9 @@ #endif #if defined(__cplusplus) -#ifdef Q_CC_MINGW -# include // Define _POSIX_THREAD_SAFE_FUNCTIONS to obtain localtime_r() -#endif -#include - QT_BEGIN_NAMESPACE -// These behave as if they consult the environment, so need to share its locking: -Q_CORE_EXPORT void qTzSet(); -Q_CORE_EXPORT time_t qMkTime(struct tm *when); - -#if !defined(Q_CC_MSVC) -Q_NORETURN -#endif -Q_CORE_EXPORT void qAbort(); +Q_NORETURN Q_CORE_EXPORT void qAbort(); QT_END_NAMESPACE @@ -142,4 +130,3 @@ QT_END_NAMESPACE #endif // defined(__cplusplus) #endif // QGLOBAL_P_H - diff --git a/src/corelib/global/qglobalstatic.h b/src/corelib/global/qglobalstatic.h index 1c615aef..7ed85ba4 100644 --- a/src/corelib/global/qglobalstatic.h +++ b/src/corelib/global/qglobalstatic.h @@ -1,13 +1,12 @@ // Copyright (C) 2021 Intel Corporation. // SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only -#include - #ifndef QGLOBALSTATIC_H #define QGLOBALSTATIC_H #include #include +#include #include // for bootstrapped (no thread) builds #include @@ -75,13 +74,13 @@ template struct QGlobalStatic } Type *operator->() { - Q_ASSERT_X(!isDestroyed(), "Q_GLOBAL_STATIC", + Q_ASSERT_X(!isDestroyed(), Q_FUNC_INFO, "The global static was used after being destroyed"); return instance(); } Type &operator*() { - Q_ASSERT_X(!isDestroyed(), "Q_GLOBAL_STATIC", + Q_ASSERT_X(!isDestroyed(), Q_FUNC_INFO, "The global static was used after being destroyed"); return *instance(); } diff --git a/src/corelib/global/qlibraryinfo.cpp b/src/corelib/global/qlibraryinfo.cpp index 469e2b42..6dab173e 100644 --- a/src/corelib/global/qlibraryinfo.cpp +++ b/src/corelib/global/qlibraryinfo.cpp @@ -540,8 +540,8 @@ QString QLibraryInfoPrivate::path(QLibraryInfo::LibraryPath p, UsageMode usageMo ret = v.toString(); } - int startIndex = 0; - forever { + qsizetype startIndex = 0; + while (true) { startIndex = ret.indexOf(u'$', startIndex); if (startIndex < 0) break; @@ -551,7 +551,7 @@ QString QLibraryInfoPrivate::path(QLibraryInfo::LibraryPath p, UsageMode usageMo startIndex++; continue; } - int endIndex = ret.indexOf(u')', startIndex + 2); + qsizetype endIndex = ret.indexOf(u')', startIndex + 2); if (endIndex < 0) break; auto envVarName = QStringView{ret}.mid(startIndex + 2, endIndex - startIndex - 2); diff --git a/src/corelib/global/qlogging.cpp b/src/corelib/global/qlogging.cpp index 7e708c9c..a63149f0 100644 --- a/src/corelib/global/qlogging.cpp +++ b/src/corelib/global/qlogging.cpp @@ -163,10 +163,11 @@ Q_TRACE_POINT(qtcore, qt_message_print, int type, const char *category, const ch \snippet code/src_corelib_global_qglobal.cpp 4 */ -#if !defined(Q_CC_MSVC) +template +#if !defined(Q_CC_MSVC_ONLY) Q_NORETURN #endif -static void qt_message_fatal(QtMsgType, const QMessageLogContext &context, const QString &message); +static void qt_message_fatal(QtMsgType, const QMessageLogContext &context, String &&message); static void qt_message_print(QtMsgType, const QMessageLogContext &context, const QString &message); static void qt_message_print(const QString &message); @@ -347,7 +348,7 @@ using namespace QtPrivate; \sa QMessageLogContext, qDebug(), qInfo(), qWarning(), qCritical(), qFatal() */ -#if defined(Q_CC_MSVC) && defined(QT_DEBUG) && defined(_DEBUG) && defined(_CRT_ERROR) +#if defined(Q_CC_MSVC_ONLY) && defined(QT_DEBUG) && defined(_DEBUG) && defined(_CRT_ERROR) static inline void convert_to_wchar_t_elided(wchar_t *d, size_t space, const char *s) noexcept { size_t len = qstrlen(s); @@ -368,11 +369,13 @@ static inline void convert_to_wchar_t_elided(wchar_t *d, size_t space, const cha \internal */ Q_NEVER_INLINE -static QString qt_message(QtMsgType msgType, const QMessageLogContext &context, const char *msg, va_list ap) +static void qt_message(QtMsgType msgType, const QMessageLogContext &context, const char *msg, va_list ap) { QString buf = QString::vasprintf(msg, ap); qt_message_print(msgType, context, buf); - return buf; + + if (isFatal(msgType)) + qt_message_fatal(msgType, context, buf); } #undef qDebug @@ -386,11 +389,8 @@ void QMessageLogger::debug(const char *msg, ...) const { va_list ap; va_start(ap, msg); // use variable arg list - const QString message = qt_message(QtDebugMsg, context, msg, ap); + qt_message(QtDebugMsg, context, msg, ap); va_end(ap); - - if (isFatal(QtDebugMsg)) - qt_message_fatal(QtDebugMsg, context, message); } @@ -406,11 +406,8 @@ void QMessageLogger::info(const char *msg, ...) const { va_list ap; va_start(ap, msg); // use variable arg list - const QString message = qt_message(QtInfoMsg, context, msg, ap); + qt_message(QtInfoMsg, context, msg, ap); va_end(ap); - - if (isFatal(QtInfoMsg)) - qt_message_fatal(QtInfoMsg, context, message); } /*! @@ -445,11 +442,8 @@ void QMessageLogger::debug(const QLoggingCategory &cat, const char *msg, ...) co va_list ap; va_start(ap, msg); // use variable arg list - const QString message = qt_message(QtDebugMsg, ctxt, msg, ap); + qt_message(QtDebugMsg, ctxt, msg, ap); va_end(ap); - - if (isFatal(QtDebugMsg)) - qt_message_fatal(QtDebugMsg, ctxt, message); } /*! @@ -472,11 +466,8 @@ void QMessageLogger::debug(QMessageLogger::CategoryFunction catFunc, va_list ap; va_start(ap, msg); // use variable arg list - const QString message = qt_message(QtDebugMsg, ctxt, msg, ap); + qt_message(QtDebugMsg, ctxt, msg, ap); va_end(ap); - - if (isFatal(QtDebugMsg)) - qt_message_fatal(QtDebugMsg, ctxt, message); } #ifndef QT_NO_DEBUG_STREAM @@ -556,11 +547,8 @@ void QMessageLogger::info(const QLoggingCategory &cat, const char *msg, ...) con va_list ap; va_start(ap, msg); // use variable arg list - const QString message = qt_message(QtInfoMsg, ctxt, msg, ap); + qt_message(QtInfoMsg, ctxt, msg, ap); va_end(ap); - - if (isFatal(QtInfoMsg)) - qt_message_fatal(QtInfoMsg, ctxt, message); } /*! @@ -583,11 +571,8 @@ void QMessageLogger::info(QMessageLogger::CategoryFunction catFunc, va_list ap; va_start(ap, msg); // use variable arg list - const QString message = qt_message(QtInfoMsg, ctxt, msg, ap); + qt_message(QtInfoMsg, ctxt, msg, ap); va_end(ap); - - if (isFatal(QtInfoMsg)) - qt_message_fatal(QtInfoMsg, ctxt, message); } #ifndef QT_NO_DEBUG_STREAM @@ -649,11 +634,8 @@ void QMessageLogger::warning(const char *msg, ...) const { va_list ap; va_start(ap, msg); // use variable arg list - const QString message = qt_message(QtWarningMsg, context, msg, ap); + qt_message(QtWarningMsg, context, msg, ap); va_end(ap); - - if (isFatal(QtWarningMsg)) - qt_message_fatal(QtWarningMsg, context, message); } /*! @@ -674,11 +656,8 @@ void QMessageLogger::warning(const QLoggingCategory &cat, const char *msg, ...) va_list ap; va_start(ap, msg); // use variable arg list - const QString message = qt_message(QtWarningMsg, ctxt, msg, ap); + qt_message(QtWarningMsg, ctxt, msg, ap); va_end(ap); - - if (isFatal(QtWarningMsg)) - qt_message_fatal(QtWarningMsg, ctxt, message); } /*! @@ -701,11 +680,8 @@ void QMessageLogger::warning(QMessageLogger::CategoryFunction catFunc, va_list ap; va_start(ap, msg); // use variable arg list - const QString message = qt_message(QtWarningMsg, ctxt, msg, ap); + qt_message(QtWarningMsg, ctxt, msg, ap); va_end(ap); - - if (isFatal(QtWarningMsg)) - qt_message_fatal(QtWarningMsg, ctxt, message); } #ifndef QT_NO_DEBUG_STREAM @@ -765,11 +741,8 @@ void QMessageLogger::critical(const char *msg, ...) const { va_list ap; va_start(ap, msg); // use variable arg list - const QString message = qt_message(QtCriticalMsg, context, msg, ap); + qt_message(QtCriticalMsg, context, msg, ap); va_end(ap); - - if (isFatal(QtCriticalMsg)) - qt_message_fatal(QtCriticalMsg, context, message); } /*! @@ -790,11 +763,8 @@ void QMessageLogger::critical(const QLoggingCategory &cat, const char *msg, ...) va_list ap; va_start(ap, msg); // use variable arg list - const QString message = qt_message(QtCriticalMsg, ctxt, msg, ap); + qt_message(QtCriticalMsg, ctxt, msg, ap); va_end(ap); - - if (isFatal(QtCriticalMsg)) - qt_message_fatal(QtCriticalMsg, ctxt, message); } /*! @@ -817,11 +787,8 @@ void QMessageLogger::critical(QMessageLogger::CategoryFunction catFunc, va_list ap; va_start(ap, msg); // use variable arg list - const QString message = qt_message(QtCriticalMsg, ctxt, msg, ap); + qt_message(QtCriticalMsg, ctxt, msg, ap); va_end(ap); - - if (isFatal(QtCriticalMsg)) - qt_message_fatal(QtCriticalMsg, ctxt, message); } #ifndef QT_NO_DEBUG_STREAM @@ -885,14 +852,14 @@ void QMessageLogger::fatal(const QLoggingCategory &cat, const char *msg, ...) co ctxt.copyContextFrom(context); ctxt.category = cat.categoryName(); - QString message; - va_list ap; va_start(ap, msg); // use variable arg list - QT_TERMINATE_ON_EXCEPTION(message = qt_message(QtFatalMsg, ctxt, msg, ap)); + QT_TERMINATE_ON_EXCEPTION(qt_message(QtFatalMsg, ctxt, msg, ap)); va_end(ap); - qt_message_fatal(QtCriticalMsg, ctxt, message); +#ifndef Q_CC_MSVC_ONLY + Q_UNREACHABLE(); +#endif } /*! @@ -911,14 +878,14 @@ void QMessageLogger::fatal(QMessageLogger::CategoryFunction catFunc, ctxt.copyContextFrom(context); ctxt.category = cat.categoryName(); - QString message; - va_list ap; va_start(ap, msg); // use variable arg list - QT_TERMINATE_ON_EXCEPTION(message = qt_message(QtFatalMsg, ctxt, msg, ap)); + QT_TERMINATE_ON_EXCEPTION(qt_message(QtFatalMsg, ctxt, msg, ap)); va_end(ap); - qt_message_fatal(QtFatalMsg, ctxt, message); +#ifndef Q_CC_MSVC_ONLY + Q_UNREACHABLE(); +#endif } /*! @@ -929,14 +896,14 @@ void QMessageLogger::fatal(QMessageLogger::CategoryFunction catFunc, */ void QMessageLogger::fatal(const char *msg, ...) const noexcept { - QString message; - va_list ap; va_start(ap, msg); // use variable arg list - QT_TERMINATE_ON_EXCEPTION(message = qt_message(QtFatalMsg, context, msg, ap)); + QT_TERMINATE_ON_EXCEPTION(qt_message(QtFatalMsg, context, msg, ap)); va_end(ap); - qt_message_fatal(QtFatalMsg, context, message); +#ifndef Q_CC_MSVC_ONLY + Q_UNREACHABLE(); +#endif } #ifndef QT_NO_DEBUG_STREAM @@ -1638,12 +1605,12 @@ QString qFormatLogMessage(QtMsgType type, const QMessageLogContext &context, con QString timeFormat = pattern->timeArgs.at(timeArgsIdx); timeArgsIdx++; if (timeFormat == "process"_L1) { - quint64 ms = pattern->timer.elapsed(); - message.append(QString::asprintf("%6d.%03d", uint(ms / 1000), uint(ms % 1000))); + quint64 ms = pattern->timer.elapsed(); + message.append(QString::asprintf("%6d.%03d", uint(ms / 1000), uint(ms % 1000))); } else if (timeFormat == "boot"_L1) { // just print the milliseconds since the elapsed timer reference // like the Linux kernel does - uint ms = QDeadlineTimer::current().deadline(); + qint64 ms = QDeadlineTimer::current().deadline(); message.append(QString::asprintf("%6d.%03d", uint(ms / 1000), uint(ms % 1000))); #if QT_CONFIG(datestring) } else if (timeFormat.isEmpty()) { @@ -2032,9 +1999,10 @@ static void qt_message_print(const QString &message) fflush(stderr); } -static void qt_message_fatal(QtMsgType, const QMessageLogContext &context, const QString &message) +template +static void qt_message_fatal(QtMsgType, const QMessageLogContext &context, String &&message) { -#if defined(Q_CC_MSVC) && defined(QT_DEBUG) && defined(_DEBUG) && defined(_CRT_ERROR) +#if defined(Q_CC_MSVC_ONLY) && defined(QT_DEBUG) && defined(_DEBUG) && defined(_CRT_ERROR) wchar_t contextFileL[256]; // we probably should let the compiler do this for us, by declaring QMessageLogContext::file to // be const wchar_t * in the first place, but the #ifdefery above is very complex and we @@ -2053,13 +2021,15 @@ static void qt_message_fatal(QtMsgType, const QMessageLogContext &context, const _CrtDbgBreak(); #else Q_UNUSED(context); - Q_UNUSED(message); #endif + if constexpr (std::is_class_v && !std::is_const_v) + message.clear(); + else + Q_UNUSED(message); qAbort(); } - /*! \internal */ diff --git a/src/corelib/global/qlogging.h b/src/corelib/global/qlogging.h index ea1c68b1..44821b44 100644 --- a/src/corelib/global/qlogging.h +++ b/src/corelib/global/qlogging.h @@ -1,11 +1,14 @@ // 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 - #ifndef QLOGGING_H #define QLOGGING_H +#include +#include +#include +#include + #if 0 // header is automatically included in qglobal.h #pragma qt_no_master_include @@ -22,6 +25,7 @@ QT_BEGIN_NAMESPACE class QDebug; class QNoDebug; + enum QtMsgType { QtDebugMsg, QtWarningMsg, @@ -54,7 +58,7 @@ private: class QLoggingCategory; -#ifdef Q_CC_MSVC +#if defined(Q_CC_MSVC_ONLY) # define QT_MESSAGE_LOGGER_NORETURN #else # define QT_MESSAGE_LOGGER_NORETURN Q_NORETURN diff --git a/src/corelib/global/qnamespace.qdoc b/src/corelib/global/qnamespace.qdoc index 78c69176..d5731d66 100644 --- a/src/corelib/global/qnamespace.qdoc +++ b/src/corelib/global/qnamespace.qdoc @@ -94,9 +94,11 @@ \value AA_DontShowShortcutsInContextMenus Actions with the Shortcut property won't be shown in any shortcut menus unless specifically set by the QAction::shortcutVisibleInContextMenu property. This value was added - in Qt 5.10, and defaults to the preference reported by the implementation - of QPlatformIntegration::styleHint. To override the platform integration, - set this attribute after QCoreApplication has been instantiated. + in Qt 5.10, and is by default based on the value reported by + QStyleHints::showShortcutsInContextMenus(). To override the default + behavior, set the style hint before QCoreApplication has been + instantiated, or set this attribute after QCoreApplication has + been instantiated. \value AA_NativeWindows Ensures that widgets have native windows. diff --git a/src/corelib/global/qnumeric.h b/src/corelib/global/qnumeric.h index 7779c035..2238c13d 100644 --- a/src/corelib/global/qnumeric.h +++ b/src/corelib/global/qnumeric.h @@ -8,7 +8,9 @@ #pragma qt_class(QtNumeric) #endif -#include +#include +#include +#include #include #include diff --git a/src/corelib/global/qnumeric_p.h b/src/corelib/global/qnumeric_p.h index 9765ce4a..40c45999 100644 --- a/src/corelib/global/qnumeric_p.h +++ b/src/corelib/global/qnumeric_p.h @@ -180,7 +180,7 @@ static inline bool convertDoubleTo(double v, T *value, bool allow_precision_upgr // correct, but Clang, ICC and MSVC don't realize that it's a constant and // the math call stays in the compiled code. -#ifdef Q_PROCESSOR_X86_64 +#if defined(Q_PROCESSOR_X86_64) && defined(__SSE2__) // Of course, UB doesn't apply if we use intrinsics, in which case we are // allowed to dpeend on exactly the processor's behavior. This // implementation uses the truncating conversions from Scalar Double to diff --git a/src/corelib/global/qsimd.h b/src/corelib/global/qsimd.h index 6ed7821e..4ef925ca 100644 --- a/src/corelib/global/qsimd.h +++ b/src/corelib/global/qsimd.h @@ -55,7 +55,7 @@ #if defined(Q_PROCESSOR_X86) && defined(Q_CC_MSVC) // MSVC doesn't define __SSE2__, so do it ourselves -# if (defined(_M_X64) || _M_IX86_FP >= 2) +# if (defined(_M_X64) || _M_IX86_FP >= 2) && defined(QT_COMPILER_SUPPORTS_SSE2) # define __SSE__ 1 # define __SSE2__ 1 # endif diff --git a/src/corelib/global/qsimd_p.h b/src/corelib/global/qsimd_p.h index 9f1833c9..5d53ec7d 100644 --- a/src/corelib/global/qsimd_p.h +++ b/src/corelib/global/qsimd_p.h @@ -218,31 +218,29 @@ asm( // x86-64 sub-architecture version 3 // // The Intel Core 4th generation was codenamed "Haswell" and introduced AVX2, -// BMI1, BMI2, FMA, LZCNT, MOVBE. This feature set was chosen as the version 3 -// of the x86-64 ISA (x86-64-v3) and is supported by GCC and Clang. On systems -// with the GNU libc, libraries with this feature can be installed on a -// "glibc-hwcaps/x86-64-v3" subdir. macOS's fat binaries support the "x86_64h" -// sub-architecture too. - -# if defined(__AVX2__) -// List of features present with -march=x86-64-v3 and not architecturally -// implied by __AVX2__ -# define ARCH_HASWELL_MACROS \ - (__AVX2__ && __BMI__ && __BMI2__ && __F16C__ && __FMA__ && __LZCNT__ && __POPCNT__) -# if ARCH_HASWELL_MACROS == 0 +// BMI1, BMI2, FMA, LZCNT, MOVBE, which makes it a good divider for a +// sub-target for us. The first AMD processor with AVX2 support (Zen) has the +// same features, but had already introduced BMI1 in the previous generation. +// This feature set was chosen as the version 3 of the x86-64 ISA (x86-64-v3) +// and is supported by GCC and Clang. +// +// macOS's fat binaries support the "x86_64h" sub-architecture and the GNU libc +// ELF loader also supports a "haswell/" subdir (e.g., /usr/lib/haswell). +# define ARCH_HASWELL_MACROS (__AVX2__ + __FMA__) +# if ARCH_HASWELL_MACROS != 0 +# if ARCH_HASWELL_MACROS != 2 # error "Please enable all x86-64-v3 extensions; you probably want to use -march=haswell or -march=x86-64-v3 instead of -mavx2" # endif static_assert(ARCH_HASWELL_MACROS, "Undeclared identifiers indicate which features are missing."); # define __haswell__ 1 -# undef ARCH_HASWELL_MACROS # endif +# undef ARCH_HASWELL_MACROS // x86-64 sub-architecture version 4 // // Similar to the above, x86-64-v4 matches the AVX512 variant of the Intel Core // 6th generation (codename "Skylake"). AMD Zen4 is the their first processor -// with AVX512 support and it includes all of these too. The GNU libc subdir for -// this is "glibc-hwcaps/x86-64-v4". +// with AVX512 support and it includes all of these too. // # define ARCH_SKX_MACROS (__AVX512F__ + __AVX512BW__ + __AVX512CD__ + __AVX512DQ__ + __AVX512VL__) # if ARCH_SKX_MACROS != 0 diff --git a/src/corelib/global/qsimd_x86.cpp b/src/corelib/global/qsimd_x86.cpp index f1a08e05..9a3bd80b 100644 --- a/src/corelib/global/qsimd_x86.cpp +++ b/src/corelib/global/qsimd_x86.cpp @@ -1,8 +1,8 @@ // Copyright (C) 2022 Intel Corporation. // SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only - // This is a generated file. DO NOT EDIT. // Please see util/x86simdgen/README.md + #include "qsimd_x86_p.h" static const char features_string[] = @@ -30,24 +30,28 @@ static const char features_string[] = " avx512bw\0" " avx512vl\0" " avx512vbmi\0" + " waitpkg\0" " avx512vbmi2\0" " shstk\0" " gfni\0" " vaes\0" - " avx512vnni\0" " avx512bitalg\0" " avx512vpopcntdq\0" " hybrid\0" " ibt\0" " avx512fp16\0" + " raoint\0" + " cmpccxadd\0" + " avxifma\0" + " lam\0" "\0"; static const uint16_t features_indices[] = { 0, 6, 12, 19, 24, 32, 40, 47, 55, 60, 65, 71, 78, 83, 89, 95, 104, 114, 122, 134, 144, 149, 159, 169, - 181, 194, 201, 207, 213, 225, 239, 256, - 264, 269, + 181, 190, 203, 210, 216, 222, 236, 253, + 261, 266, 278, 286, 297, 306, }; enum X86CpuidLeaves { @@ -57,6 +61,7 @@ enum X86CpuidLeaves { Leaf07_00ECX, Leaf07_00EDX, Leaf07_01EAX, + Leaf07_01EDX, Leaf13_01EAX, Leaf80000001hECX, Leaf80000008hEBX, @@ -88,16 +93,20 @@ static const uint16_t x86_locators[] = { Leaf07_00EBX*32 + 30, // avx512bw Leaf07_00EBX*32 + 31, // avx512vl Leaf07_00ECX*32 + 1, // avx512vbmi + Leaf07_00ECX*32 + 5, // waitpkg Leaf07_00ECX*32 + 6, // avx512vbmi2 Leaf07_00ECX*32 + 7, // shstk Leaf07_00ECX*32 + 8, // gfni Leaf07_00ECX*32 + 9, // vaes - Leaf07_00ECX*32 + 11, // avx512vnni Leaf07_00ECX*32 + 12, // avx512bitalg Leaf07_00ECX*32 + 14, // avx512vpopcntdq Leaf07_00EDX*32 + 15, // hybrid Leaf07_00EDX*32 + 20, // ibt Leaf07_00EDX*32 + 23, // avx512fp16 + Leaf07_01EAX*32 + 3, // raoint + Leaf07_01EAX*32 + 6, // cmpccxadd + Leaf07_01EAX*32 + 23, // avxifma + Leaf07_01EAX*32 + 26, // lam }; struct X86Architecture @@ -107,25 +116,31 @@ struct X86Architecture }; static const struct X86Architecture x86_architectures[] = { - { cpu_sapphirerapids, "Sapphire Rapids" }, - { cpu_tigerlake, "Tiger Lake" }, - { cpu_icelake_server, "Ice Lake (Server)" }, - { cpu_icelake_client, "Ice Lake (Client)" }, - { cpu_alderlake, "Alder Lake" }, - { cpu_cannonlake, "Cannon Lake" }, - { cpu_cooperlake, "Cooper Lake" }, - { cpu_cascadelake, "Cascade Lake" }, - { cpu_skylake_avx512, "Skylake (Avx512)" }, - { cpu_skylake, "Skylake" }, - { cpu_tremont, "Tremont" }, - { cpu_broadwell, "Broadwell" }, - { cpu_haswell, "Haswell" }, - { cpu_goldmont, "Goldmont" }, - { cpu_ivybridge, "Ivy Bridge" }, - { cpu_silvermont, "Silvermont" }, - { cpu_sandybridge, "Sandy Bridge" }, - { cpu_westmere, "Westmere" }, { cpu_core2, "Core2" }, + { cpu_westmere, "Westmere" }, + { cpu_sandybridge, "Sandy Bridge" }, + { cpu_silvermont, "Silvermont" }, + { cpu_ivybridge, "Ivy Bridge" }, + { cpu_goldmont, "Goldmont" }, + { cpu_haswell, "Haswell" }, + { cpu_broadwell, "Broadwell" }, + { cpu_tremont, "Tremont" }, + { cpu_skylake, "Skylake" }, + { cpu_skylake_avx512, "Skylake (Avx512)" }, + { cpu_cascadelake, "Cascade Lake" }, + { cpu_cooperlake, "Cooper Lake" }, + { cpu_cannonlake, "Cannon Lake" }, + { cpu_gracemont, "Gracemont" }, + { cpu_icelake_client, "Ice Lake (Client)" }, + { cpu_icelake_server, "Ice Lake (Server)" }, + { cpu_crestmont, "Crestmont" }, + { cpu_tigerlake, "Tiger Lake" }, + { cpu_clearwaterforest, "Clearwater Forest" }, + { cpu_grandridge, "Grand Ridge" }, + { cpu_raptorcove, "Raptor Cove" }, + { cpu_redwoodcove, "Redwood Cove" }, + { cpu_emeraldrapids, "Emerald Rapids" }, + { cpu_graniterapids, "Granite Rapids" }, }; enum XSaveBits { @@ -168,10 +183,10 @@ static const uint64_t XSaveReq_AvxState = 0 | cpu_feature_avx512vbmi | cpu_feature_avx512vbmi2 | cpu_feature_vaes - | cpu_feature_avx512vnni | cpu_feature_avx512bitalg | cpu_feature_avx512vpopcntdq - | cpu_feature_avx512fp16; + | cpu_feature_avx512fp16 + | cpu_feature_avxifma; // List of features requiring XSave_Avx512State static const uint64_t XSaveReq_Avx512State = 0 @@ -183,7 +198,6 @@ static const uint64_t XSaveReq_Avx512State = 0 | cpu_feature_avx512vl | cpu_feature_avx512vbmi | cpu_feature_avx512vbmi2 - | cpu_feature_avx512vnni | cpu_feature_avx512bitalg | cpu_feature_avx512vpopcntdq | cpu_feature_avx512fp16; diff --git a/src/corelib/global/qsimd_x86_p.h b/src/corelib/global/qsimd_x86_p.h index 3e7427b0..1ec89d0c 100644 --- a/src/corelib/global/qsimd_x86_p.h +++ b/src/corelib/global/qsimd_x86_p.h @@ -1,5 +1,7 @@ // Copyright (C) 2022 Intel Corporation. // SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only +// This is a generated file. DO NOT EDIT. +// Please see util/x86simdgen/README.md // // W A R N I N G @@ -50,11 +52,11 @@ // in CPUID Leaf 7, Sub-leaf 0, ECX: #define cpu_feature_avx512vbmi (UINT64_C(1) << 23) -#define cpu_feature_avx512vbmi2 (UINT64_C(1) << 24) -#define cpu_feature_shstk (UINT64_C(1) << 25) -#define cpu_feature_gfni (UINT64_C(1) << 26) -#define cpu_feature_vaes (UINT64_C(1) << 27) -#define cpu_feature_avx512vnni (UINT64_C(1) << 28) +#define cpu_feature_waitpkg (UINT64_C(1) << 24) +#define cpu_feature_avx512vbmi2 (UINT64_C(1) << 25) +#define cpu_feature_shstk (UINT64_C(1) << 26) +#define cpu_feature_gfni (UINT64_C(1) << 27) +#define cpu_feature_vaes (UINT64_C(1) << 28) #define cpu_feature_avx512bitalg (UINT64_C(1) << 29) #define cpu_feature_avx512vpopcntdq (UINT64_C(1) << 30) @@ -63,6 +65,12 @@ #define cpu_feature_ibt (UINT64_C(1) << 32) #define cpu_feature_avx512fp16 (UINT64_C(1) << 33) +// in CPUID Leaf 7, Sub-leaf 1, EAX: +#define cpu_feature_raoint (UINT64_C(1) << 34) +#define cpu_feature_cmpccxadd (UINT64_C(1) << 35) +#define cpu_feature_avxifma (UINT64_C(1) << 36) +#define cpu_feature_lam (UINT64_C(1) << 37) + // CPU architectures #define cpu_x86_64 (0 \ | cpu_feature_sse2) @@ -89,42 +97,65 @@ | cpu_feature_rdseed) #define cpu_bdx (cpu_bdw) #define cpu_skl (cpu_bdw) -#define cpu_adl (cpu_skl \ - | cpu_feature_gfni \ - | cpu_feature_vaes \ - | cpu_feature_shstk \ - | cpu_feature_ibt) #define cpu_skx (cpu_skl \ | cpu_feature_avx512f \ | cpu_feature_avx512dq \ | cpu_feature_avx512cd \ | cpu_feature_avx512bw \ | cpu_feature_avx512vl) -#define cpu_clx (cpu_skx \ - | cpu_feature_avx512vnni) +#define cpu_clx (cpu_skx) #define cpu_cpx (cpu_clx) -#define cpu_cnl (cpu_skx \ +#define cpu_plc (cpu_skx \ | cpu_feature_avx512ifma \ | cpu_feature_avx512vbmi) -#define cpu_icl (cpu_cnl \ +#define cpu_snc (cpu_plc \ | cpu_feature_avx512vbmi2 \ | cpu_feature_gfni \ | cpu_feature_vaes \ - | cpu_feature_avx512vnni \ | cpu_feature_avx512bitalg \ | cpu_feature_avx512vpopcntdq) -#define cpu_icx (cpu_icl) -#define cpu_tgl (cpu_icl \ +#define cpu_wlc (cpu_snc \ | cpu_feature_shstk \ | cpu_feature_ibt) -#define cpu_spr (cpu_tgl) +#define cpu_glc (cpu_wlc \ + | cpu_feature_waitpkg) +#define cpu_rpc (cpu_glc) +#define cpu_rwc (cpu_rpc) #define cpu_slm (cpu_wsm \ | cpu_feature_rdrnd \ | cpu_feature_movbe) #define cpu_glm (cpu_slm \ | cpu_feature_rdseed) #define cpu_tnt (cpu_glm \ - | cpu_feature_gfni) + | cpu_feature_gfni \ + | cpu_feature_waitpkg) +#define cpu_grt (cpu_skl \ + | cpu_feature_gfni \ + | cpu_feature_vaes \ + | cpu_feature_shstk \ + | cpu_feature_ibt \ + | cpu_feature_waitpkg) +#define cpu_cmt (cpu_grt \ + | cpu_feature_cmpccxadd \ + | cpu_feature_avxifma) +#define cpu_cnl (cpu_plc) +#define cpu_icl (cpu_snc) +#define cpu_tgl (cpu_wlc) +#define cpu_adl (cpu_grt) +#define cpu_rpl (cpu_grt) +#define cpu_mtl (cpu_cmt) +#define cpu_arl (cpu_cmt) +#define cpu_lnl (cpu_cmt) +#define cpu_icx (cpu_snc) +#define cpu_spr (cpu_glc) +#define cpu_emr (cpu_spr) +#define cpu_gnr (cpu_glc) +#define cpu_srf (cpu_cmt \ + | cpu_feature_cmpccxadd \ + | cpu_feature_avxifma) +#define cpu_grr (cpu_srf \ + | cpu_feature_raoint) +#define cpu_cwf (cpu_srf) #define cpu_nehalem (cpu_nhm) #define cpu_westmere (cpu_wsm) #define cpu_sandybridge (cpu_snb) @@ -135,15 +166,32 @@ #define cpu_skylake_avx512 (cpu_skx) #define cpu_cascadelake (cpu_clx) #define cpu_cooperlake (cpu_cpx) +#define cpu_palmcove (cpu_plc) #define cpu_cannonlake (cpu_cnl) +#define cpu_sunnycove (cpu_snc) #define cpu_icelake_client (cpu_icl) #define cpu_icelake_server (cpu_icx) -#define cpu_alderlake (cpu_adl) -#define cpu_sapphirerapids (cpu_spr) +#define cpu_willowcove (cpu_wlc) #define cpu_tigerlake (cpu_tgl) +#define cpu_goldencove (cpu_glc) +#define cpu_alderlake (cpu_adl) +#define cpu_raptorcove (cpu_rpc) +#define cpu_raptorlake (cpu_rpl) +#define cpu_redwoodcove (cpu_rwc) +#define cpu_meteorlake (cpu_mtl) +#define cpu_arrowlake (cpu_arl) +#define cpu_lunarlake (cpu_lnl) +#define cpu_sapphirerapids (cpu_spr) +#define cpu_emeraldrapids (cpu_emr) +#define cpu_graniterapids (cpu_gnr) #define cpu_silvermont (cpu_slm) #define cpu_goldmont (cpu_glm) #define cpu_tremont (cpu_tnt) +#define cpu_gracemont (cpu_grt) +#define cpu_crestmont (cpu_cmt) +#define cpu_grandridge (cpu_grr) +#define cpu_sierraforest (cpu_srf) +#define cpu_clearwaterforest (cpu_cwf) // __attribute__ target strings for GCC and Clang #define QT_FUNCTION_TARGET_STRING_SSE2 "sse2" @@ -170,16 +218,20 @@ #define QT_FUNCTION_TARGET_STRING_AVX512BW "avx512bw,avx512f" #define QT_FUNCTION_TARGET_STRING_AVX512VL "avx512vl,avx512f" #define QT_FUNCTION_TARGET_STRING_AVX512VBMI "avx512vbmi,avx512f" +#define QT_FUNCTION_TARGET_STRING_WAITPKG "waitpkg" #define QT_FUNCTION_TARGET_STRING_AVX512VBMI2 "avx512vbmi2,avx512f" #define QT_FUNCTION_TARGET_STRING_SHSTK "shstk" #define QT_FUNCTION_TARGET_STRING_GFNI "gfni" #define QT_FUNCTION_TARGET_STRING_VAES "vaes,avx2,avx,aes" -#define QT_FUNCTION_TARGET_STRING_AVX512VNNI "avx512vnni,avx512f" #define QT_FUNCTION_TARGET_STRING_AVX512BITALG "avx512bitalg,avx512f" #define QT_FUNCTION_TARGET_STRING_AVX512VPOPCNTDQ "avx512vpopcntdq,avx512f" #define QT_FUNCTION_TARGET_STRING_HYBRID "hybrid" #define QT_FUNCTION_TARGET_STRING_IBT "ibt" #define QT_FUNCTION_TARGET_STRING_AVX512FP16 "avx512fp16,avx512f,f16c" +#define QT_FUNCTION_TARGET_STRING_RAOINT "raoint" +#define QT_FUNCTION_TARGET_STRING_CMPCCXADD "cmpccxadd" +#define QT_FUNCTION_TARGET_STRING_AVXIFMA "avxifma,avx" +#define QT_FUNCTION_TARGET_STRING_LAM "lam" #define QT_FUNCTION_TARGET_STRING_ARCH_X86_64 "sse2" #define QT_FUNCTION_TARGET_STRING_ARCH_CORE2 QT_FUNCTION_TARGET_STRING_ARCH_X86_64 ",sse3,ssse3,cx16" #define QT_FUNCTION_TARGET_STRING_ARCH_NHM QT_FUNCTION_TARGET_STRING_ARCH_CORE2 ",sse4.1,sse4.2,popcnt" @@ -190,18 +242,35 @@ #define QT_FUNCTION_TARGET_STRING_ARCH_BDW QT_FUNCTION_TARGET_STRING_ARCH_HSW ",adx,rdseed" #define QT_FUNCTION_TARGET_STRING_ARCH_BDX QT_FUNCTION_TARGET_STRING_ARCH_BDW #define QT_FUNCTION_TARGET_STRING_ARCH_SKL QT_FUNCTION_TARGET_STRING_ARCH_BDW ",xsavec,xsaves" -#define QT_FUNCTION_TARGET_STRING_ARCH_ADL QT_FUNCTION_TARGET_STRING_ARCH_SKL ",avxvnni,gfni,vaes,vpclmulqdq,serialize,shstk,cldemote,movdiri,movdir64b,ibt,waitpkg,keylocker" #define QT_FUNCTION_TARGET_STRING_ARCH_SKX QT_FUNCTION_TARGET_STRING_ARCH_SKL ",avx512f,avx512dq,avx512cd,avx512bw,avx512vl" #define QT_FUNCTION_TARGET_STRING_ARCH_CLX QT_FUNCTION_TARGET_STRING_ARCH_SKX ",avx512vnni" #define QT_FUNCTION_TARGET_STRING_ARCH_CPX QT_FUNCTION_TARGET_STRING_ARCH_CLX ",avx512bf16" -#define QT_FUNCTION_TARGET_STRING_ARCH_CNL QT_FUNCTION_TARGET_STRING_ARCH_SKX ",avx512ifma,avx512vbmi" -#define QT_FUNCTION_TARGET_STRING_ARCH_ICL QT_FUNCTION_TARGET_STRING_ARCH_CNL ",avx512vbmi2,gfni,vaes,vpclmulqdq,avx512vnni,avx512bitalg,avx512vpopcntdq" -#define QT_FUNCTION_TARGET_STRING_ARCH_ICX QT_FUNCTION_TARGET_STRING_ARCH_ICL ",pconfig" -#define QT_FUNCTION_TARGET_STRING_ARCH_TGL QT_FUNCTION_TARGET_STRING_ARCH_ICL ",avx512vp2intersect,shstk,,movdiri,movdir64b,ibt,keylocker" -#define QT_FUNCTION_TARGET_STRING_ARCH_SPR QT_FUNCTION_TARGET_STRING_ARCH_TGL ",avx512bf16,amxtile,amxbf16,amxint8,avxvnni,cldemote,pconfig,waitpkg,serialize,tsxldtrk,uintr" +#define QT_FUNCTION_TARGET_STRING_ARCH_PLC QT_FUNCTION_TARGET_STRING_ARCH_SKX ",avx512ifma,avx512vbmi" +#define QT_FUNCTION_TARGET_STRING_ARCH_SNC QT_FUNCTION_TARGET_STRING_ARCH_PLC ",avx512vbmi2,gfni,vaes,vpclmulqdq,avx512vnni,avx512bitalg,avx512vpopcntdq" +#define QT_FUNCTION_TARGET_STRING_ARCH_WLC QT_FUNCTION_TARGET_STRING_ARCH_SNC ",shstk,movdiri,movdir64b,ibt,keylocker" +#define QT_FUNCTION_TARGET_STRING_ARCH_GLC QT_FUNCTION_TARGET_STRING_ARCH_WLC ",avx512bf16,avxvnni,cldemote,waitpkg,serialize,uintr" +#define QT_FUNCTION_TARGET_STRING_ARCH_RPC QT_FUNCTION_TARGET_STRING_ARCH_GLC +#define QT_FUNCTION_TARGET_STRING_ARCH_RWC QT_FUNCTION_TARGET_STRING_ARCH_RPC ",prefetchiti" #define QT_FUNCTION_TARGET_STRING_ARCH_SLM QT_FUNCTION_TARGET_STRING_ARCH_WSM ",rdrnd,movbe" #define QT_FUNCTION_TARGET_STRING_ARCH_GLM QT_FUNCTION_TARGET_STRING_ARCH_SLM ",fsgsbase,rdseed,lzcnt,xsavec,xsaves" #define QT_FUNCTION_TARGET_STRING_ARCH_TNT QT_FUNCTION_TARGET_STRING_ARCH_GLM ",clwb,gfni,cldemote,waitpkg,movdiri,movdir64b" +#define QT_FUNCTION_TARGET_STRING_ARCH_GRT QT_FUNCTION_TARGET_STRING_ARCH_SKL ",avxvnni,gfni,vaes,vpclmulqdq,serialize,shstk,cldemote,movdiri,movdir64b,ibt,waitpkg,keylocker" +#define QT_FUNCTION_TARGET_STRING_ARCH_CMT QT_FUNCTION_TARGET_STRING_ARCH_GRT ",cmpccxadd,avxifma,avxneconvert,avxvnniint8" +#define QT_FUNCTION_TARGET_STRING_ARCH_CNL QT_FUNCTION_TARGET_STRING_ARCH_PLC +#define QT_FUNCTION_TARGET_STRING_ARCH_ICL QT_FUNCTION_TARGET_STRING_ARCH_SNC +#define QT_FUNCTION_TARGET_STRING_ARCH_TGL QT_FUNCTION_TARGET_STRING_ARCH_WLC +#define QT_FUNCTION_TARGET_STRING_ARCH_ADL QT_FUNCTION_TARGET_STRING_ARCH_GRT +#define QT_FUNCTION_TARGET_STRING_ARCH_RPL QT_FUNCTION_TARGET_STRING_ARCH_GRT +#define QT_FUNCTION_TARGET_STRING_ARCH_MTL QT_FUNCTION_TARGET_STRING_ARCH_CMT +#define QT_FUNCTION_TARGET_STRING_ARCH_ARL QT_FUNCTION_TARGET_STRING_ARCH_CMT +#define QT_FUNCTION_TARGET_STRING_ARCH_LNL QT_FUNCTION_TARGET_STRING_ARCH_CMT +#define QT_FUNCTION_TARGET_STRING_ARCH_ICX QT_FUNCTION_TARGET_STRING_ARCH_SNC ",pconfig" +#define QT_FUNCTION_TARGET_STRING_ARCH_SPR QT_FUNCTION_TARGET_STRING_ARCH_GLC ",pconfig,amx-tile,amx-bf16,amx-int8" +#define QT_FUNCTION_TARGET_STRING_ARCH_EMR QT_FUNCTION_TARGET_STRING_ARCH_SPR +#define QT_FUNCTION_TARGET_STRING_ARCH_GNR QT_FUNCTION_TARGET_STRING_ARCH_GLC ",pconfig,amx-tile,amx-bf16,amx-int8,amx-fp16,amx-complex" +#define QT_FUNCTION_TARGET_STRING_ARCH_SRF QT_FUNCTION_TARGET_STRING_ARCH_CMT ",cmpccxadd,avxifma,avxneconvert,avxvnniint8" +#define QT_FUNCTION_TARGET_STRING_ARCH_GRR QT_FUNCTION_TARGET_STRING_ARCH_SRF ",raoint" +#define QT_FUNCTION_TARGET_STRING_ARCH_CWF QT_FUNCTION_TARGET_STRING_ARCH_SRF #define QT_FUNCTION_TARGET_STRING_ARCH_NEHALEM QT_FUNCTION_TARGET_STRING_ARCH_NHM #define QT_FUNCTION_TARGET_STRING_ARCH_WESTMERE QT_FUNCTION_TARGET_STRING_ARCH_WSM #define QT_FUNCTION_TARGET_STRING_ARCH_SANDYBRIDGE QT_FUNCTION_TARGET_STRING_ARCH_SNB @@ -212,15 +281,32 @@ #define QT_FUNCTION_TARGET_STRING_ARCH_SKYLAKE_AVX512 QT_FUNCTION_TARGET_STRING_ARCH_SKX #define QT_FUNCTION_TARGET_STRING_ARCH_CASCADELAKE QT_FUNCTION_TARGET_STRING_ARCH_CLX #define QT_FUNCTION_TARGET_STRING_ARCH_COOPERLAKE QT_FUNCTION_TARGET_STRING_ARCH_CPX +#define QT_FUNCTION_TARGET_STRING_ARCH_PALMCOVE QT_FUNCTION_TARGET_STRING_ARCH_PLC #define QT_FUNCTION_TARGET_STRING_ARCH_CANNONLAKE QT_FUNCTION_TARGET_STRING_ARCH_CNL +#define QT_FUNCTION_TARGET_STRING_ARCH_SUNNYCOVE QT_FUNCTION_TARGET_STRING_ARCH_SNC #define QT_FUNCTION_TARGET_STRING_ARCH_ICELAKE_CLIENT QT_FUNCTION_TARGET_STRING_ARCH_ICL #define QT_FUNCTION_TARGET_STRING_ARCH_ICELAKE_SERVER QT_FUNCTION_TARGET_STRING_ARCH_ICX -#define QT_FUNCTION_TARGET_STRING_ARCH_ALDERLAKE QT_FUNCTION_TARGET_STRING_ARCH_ADL -#define QT_FUNCTION_TARGET_STRING_ARCH_SAPPHIRERAPIDS QT_FUNCTION_TARGET_STRING_ARCH_SPR +#define QT_FUNCTION_TARGET_STRING_ARCH_WILLOWCOVE QT_FUNCTION_TARGET_STRING_ARCH_WLC #define QT_FUNCTION_TARGET_STRING_ARCH_TIGERLAKE QT_FUNCTION_TARGET_STRING_ARCH_TGL +#define QT_FUNCTION_TARGET_STRING_ARCH_GOLDENCOVE QT_FUNCTION_TARGET_STRING_ARCH_GLC +#define QT_FUNCTION_TARGET_STRING_ARCH_ALDERLAKE QT_FUNCTION_TARGET_STRING_ARCH_ADL +#define QT_FUNCTION_TARGET_STRING_ARCH_RAPTORCOVE QT_FUNCTION_TARGET_STRING_ARCH_RPC +#define QT_FUNCTION_TARGET_STRING_ARCH_RAPTORLAKE QT_FUNCTION_TARGET_STRING_ARCH_RPL +#define QT_FUNCTION_TARGET_STRING_ARCH_REDWOODCOVE QT_FUNCTION_TARGET_STRING_ARCH_RWC +#define QT_FUNCTION_TARGET_STRING_ARCH_METEORLAKE QT_FUNCTION_TARGET_STRING_ARCH_MTL +#define QT_FUNCTION_TARGET_STRING_ARCH_ARROWLAKE QT_FUNCTION_TARGET_STRING_ARCH_ARL +#define QT_FUNCTION_TARGET_STRING_ARCH_LUNARLAKE QT_FUNCTION_TARGET_STRING_ARCH_LNL +#define QT_FUNCTION_TARGET_STRING_ARCH_SAPPHIRERAPIDS QT_FUNCTION_TARGET_STRING_ARCH_SPR +#define QT_FUNCTION_TARGET_STRING_ARCH_EMERALDRAPIDS QT_FUNCTION_TARGET_STRING_ARCH_EMR +#define QT_FUNCTION_TARGET_STRING_ARCH_GRANITERAPIDS QT_FUNCTION_TARGET_STRING_ARCH_GNR #define QT_FUNCTION_TARGET_STRING_ARCH_SILVERMONT QT_FUNCTION_TARGET_STRING_ARCH_SLM #define QT_FUNCTION_TARGET_STRING_ARCH_GOLDMONT QT_FUNCTION_TARGET_STRING_ARCH_GLM #define QT_FUNCTION_TARGET_STRING_ARCH_TREMONT QT_FUNCTION_TARGET_STRING_ARCH_TNT +#define QT_FUNCTION_TARGET_STRING_ARCH_GRACEMONT QT_FUNCTION_TARGET_STRING_ARCH_GRT +#define QT_FUNCTION_TARGET_STRING_ARCH_CRESTMONT QT_FUNCTION_TARGET_STRING_ARCH_CMT +#define QT_FUNCTION_TARGET_STRING_ARCH_GRANDRIDGE QT_FUNCTION_TARGET_STRING_ARCH_GRR +#define QT_FUNCTION_TARGET_STRING_ARCH_SIERRAFOREST QT_FUNCTION_TARGET_STRING_ARCH_SRF +#define QT_FUNCTION_TARGET_STRING_ARCH_CLEARWATERFOREST QT_FUNCTION_TARGET_STRING_ARCH_CWF static const uint64_t _compilerCpuFeatures = 0 #ifdef __SSE2__ @@ -295,6 +381,9 @@ static const uint64_t _compilerCpuFeatures = 0 #ifdef __AVX512VBMI__ | cpu_feature_avx512vbmi #endif +#ifdef __WAITPKG__ + | cpu_feature_waitpkg +#endif #ifdef __AVX512VBMI2__ | cpu_feature_avx512vbmi2 #endif @@ -307,9 +396,6 @@ static const uint64_t _compilerCpuFeatures = 0 #ifdef __VAES__ | cpu_feature_vaes #endif -#ifdef __AVX512VNNI__ - | cpu_feature_avx512vnni -#endif #ifdef __AVX512BITALG__ | cpu_feature_avx512bitalg #endif @@ -324,6 +410,18 @@ static const uint64_t _compilerCpuFeatures = 0 #endif #ifdef __AVX512FP16__ | cpu_feature_avx512fp16 +#endif +#ifdef __RAOINT__ + | cpu_feature_raoint +#endif +#ifdef __CMPCCXADD__ + | cpu_feature_cmpccxadd +#endif +#ifdef __AVXIFMA__ + | cpu_feature_avxifma +#endif +#ifdef __LAM__ + | cpu_feature_lam #endif ; @@ -353,16 +451,20 @@ enum X86CpuFeatures : uint64_t { CpuFeatureAVX512BW = cpu_feature_avx512bw, ///< AVX512 Byte & Word CpuFeatureAVX512VL = cpu_feature_avx512vl, ///< AVX512 Vector Length CpuFeatureAVX512VBMI = cpu_feature_avx512vbmi, ///< AVX512 Vector Byte Manipulation Instructions + CpuFeatureWAITPKG = cpu_feature_waitpkg, ///< User-Level Monitor / Wait CpuFeatureAVX512VBMI2 = cpu_feature_avx512vbmi2, ///< AVX512 Vector Byte Manipulation Instructions 2 CpuFeatureSHSTK = cpu_feature_shstk, ///< Control Flow Enforcement Technology Shadow Stack CpuFeatureGFNI = cpu_feature_gfni, ///< Galois Field new instructions CpuFeatureVAES = cpu_feature_vaes, ///< 256- and 512-bit AES - CpuFeatureAVX512VNNI = cpu_feature_avx512vnni, ///< AVX512 Vector Neural Network Instructions CpuFeatureAVX512BITALG = cpu_feature_avx512bitalg, ///< AVX512 Bit Algorithms CpuFeatureAVX512VPOPCNTDQ = cpu_feature_avx512vpopcntdq, ///< AVX512 Population Count CpuFeatureHYBRID = cpu_feature_hybrid, ///< Hybrid processor CpuFeatureIBT = cpu_feature_ibt, ///< Control Flow Enforcement Technology Indirect Branch Tracking CpuFeatureAVX512FP16 = cpu_feature_avx512fp16, ///< AVX512 16-bit Floating Point + CpuFeatureRAOINT = cpu_feature_raoint, ///< Remote Atomic Operations, Integer + CpuFeatureCMPCCXADD = cpu_feature_cmpccxadd, ///< CMPccXADD instructions + CpuFeatureAVXIFMA = cpu_feature_avxifma, ///< AVX-IFMA instructions + CpuFeatureLAM = cpu_feature_lam, ///< Linear Address Masking }; // enum X86CpuFeatures enum X86CpuArchitectures : uint64_t { @@ -372,22 +474,39 @@ enum X86CpuArchitectures : uint64_t { CpuArchWSM = cpu_wsm, CpuArchSNB = cpu_snb, CpuArchIVB = cpu_ivb, - CpuArchHSW = cpu_hsw, + CpuArchHSW = cpu_hsw, ///< hle,rtm CpuArchBDW = cpu_bdw, CpuArchBDX = cpu_bdx, CpuArchSKL = cpu_skl, - CpuArchADL = cpu_adl, - CpuArchSKX = cpu_skx, + CpuArchSKX = cpu_skx, ///< clwb CpuArchCLX = cpu_clx, CpuArchCPX = cpu_cpx, - CpuArchCNL = cpu_cnl, - CpuArchICL = cpu_icl, - CpuArchICX = cpu_icx, - CpuArchTGL = cpu_tgl, - CpuArchSPR = cpu_spr, + CpuArchPLC = cpu_plc, ///< sha + CpuArchSNC = cpu_snc, ///< fsrm,rdpid + CpuArchWLC = cpu_wlc, ///< avx512vp2intersect + CpuArchGLC = cpu_glc, ///< tsxldtrk + CpuArchRPC = cpu_rpc, + CpuArchRWC = cpu_rwc, CpuArchSLM = cpu_slm, CpuArchGLM = cpu_glm, CpuArchTNT = cpu_tnt, + CpuArchGRT = cpu_grt, ///< rdpid + CpuArchCMT = cpu_cmt, + CpuArchCNL = cpu_cnl, + CpuArchICL = cpu_icl, + CpuArchTGL = cpu_tgl, + CpuArchADL = cpu_adl, + CpuArchRPL = cpu_rpl, + CpuArchMTL = cpu_mtl, + CpuArchARL = cpu_arl, + CpuArchLNL = cpu_lnl, + CpuArchICX = cpu_icx, + CpuArchSPR = cpu_spr, + CpuArchEMR = cpu_emr, + CpuArchGNR = cpu_gnr, + CpuArchSRF = cpu_srf, + CpuArchGRR = cpu_grr, + CpuArchCWF = cpu_cwf, CpuArchNehalem = cpu_nehalem, ///< Intel Core i3/i5/i7 CpuArchWestmere = cpu_westmere, ///< Intel Core i3/i5/i7 CpuArchSandyBridge = cpu_sandybridge, ///< Second Generation Intel Core i3/i5/i7 @@ -398,15 +517,32 @@ enum X86CpuArchitectures : uint64_t { CpuArchSkylakeAvx512 = cpu_skylake_avx512, ///< Intel Xeon Scalable CpuArchCascadeLake = cpu_cascadelake, ///< Second Generation Intel Xeon Scalable CpuArchCooperLake = cpu_cooperlake, ///< Third Generation Intel Xeon Scalable + CpuArchPalmCove = cpu_palmcove, CpuArchCannonLake = cpu_cannonlake, ///< Intel Core i3-8121U + CpuArchSunnyCove = cpu_sunnycove, CpuArchIceLakeClient = cpu_icelake_client, ///< Tenth Generation Intel Core i3/i5/i7 CpuArchIceLakeServer = cpu_icelake_server, ///< Third Generation Intel Xeon Scalable - CpuArchAlderLake = cpu_alderlake, - CpuArchSapphireRapids = cpu_sapphirerapids, + CpuArchWillowCove = cpu_willowcove, CpuArchTigerLake = cpu_tigerlake, ///< Eleventh Generation Intel Core i3/i5/i7 + CpuArchGoldenCove = cpu_goldencove, + CpuArchAlderLake = cpu_alderlake, ///< Twelfth Generation Intel Core + CpuArchRaptorCove = cpu_raptorcove, + CpuArchRaptorLake = cpu_raptorlake, ///< Thirteenth Generation Intel Core + CpuArchRedwoodCove = cpu_redwoodcove, + CpuArchMeteorLake = cpu_meteorlake, + CpuArchArrowLake = cpu_arrowlake, + CpuArchLunarLake = cpu_lunarlake, + CpuArchSapphireRapids = cpu_sapphirerapids, ///< Fourth Generation Intel Xeon Scalable + CpuArchEmeraldRapids = cpu_emeraldrapids, ///< Fifth Generation Intel Xeon Scalable + CpuArchGraniteRapids = cpu_graniterapids, CpuArchSilvermont = cpu_silvermont, CpuArchGoldmont = cpu_goldmont, CpuArchTremont = cpu_tremont, + CpuArchGracemont = cpu_gracemont, + CpuArchCrestmont = cpu_crestmont, + CpuArchGrandRidge = cpu_grandridge, + CpuArchSierraForest = cpu_sierraforest, + CpuArchClearwaterForest = cpu_clearwaterforest, }; // enum X86cpuArchitectures #endif /* C++11 */ diff --git a/src/corelib/global/qsysinfo.cpp b/src/corelib/global/qsysinfo.cpp index 3654b709..20919d08 100644 --- a/src/corelib/global/qsysinfo.cpp +++ b/src/corelib/global/qsysinfo.cpp @@ -352,8 +352,7 @@ static QByteArray getEtcFileFirstLine(const char *fileName) return QByteArray(); const char *ptr = buffer.constData(); - int eol = buffer.indexOf("\n"); - return QByteArray(ptr, eol).trimmed(); + return QByteArray(ptr, buffer.indexOf("\n")).trimmed(); } static bool readEtcRedHatRelease(QUnixOSVersion &v) @@ -368,9 +367,9 @@ static bool readEtcRedHatRelease(QUnixOSVersion &v) v.prettyName = QString::fromLatin1(line); const char keyword[] = "release "; - int releaseIndex = line.indexOf(keyword); + const qsizetype releaseIndex = line.indexOf(keyword); v.productType = QString::fromLatin1(line.mid(0, releaseIndex)).remove(u' '); - int spaceIndex = line.indexOf(' ', releaseIndex + strlen(keyword)); + const qsizetype spaceIndex = line.indexOf(' ', releaseIndex + strlen(keyword)); v.productVersion = QString::fromLatin1(line.mid(releaseIndex + strlen(keyword), spaceIndex > -1 ? spaceIndex - releaseIndex - int(strlen(keyword)) : -1)); return true; @@ -788,6 +787,8 @@ QString QSysInfo::productType() return QStringLiteral("macos"); #elif defined(Q_OS_DARWIN) return QStringLiteral("darwin"); +#elif defined(Q_OS_WASM) + return QStringLiteral("wasm"); #elif defined(USE_ETC_OS_RELEASE) // Q_OS_UNIX QUnixOSVersion unixOsVersion; diff --git a/src/corelib/global/qsysinfo.h b/src/corelib/global/qsysinfo.h index 5dff3ee4..01f63132 100644 --- a/src/corelib/global/qsysinfo.h +++ b/src/corelib/global/qsysinfo.h @@ -2,21 +2,22 @@ // Copyright (C) 2016 Intel Corporation. // SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only -#include - #ifndef QSYSINFO_H #define QSYSINFO_H +#include +#include +#include + QT_BEGIN_NAMESPACE /* System information */ -#ifdef Q_QDOC -class QByteArray; -#endif class QString; +class QByteArray; + class Q_CORE_EXPORT QSysInfo { public: diff --git a/src/corelib/global/qsystemdetection.h b/src/corelib/global/qsystemdetection.h index 3a992f2a..ca94d552 100644 --- a/src/corelib/global/qsystemdetection.h +++ b/src/corelib/global/qsystemdetection.h @@ -2,10 +2,6 @@ // Copyright (C) 2019 Intel Corporation. // SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only -#ifndef QGLOBAL_H -# include -#endif - #if 0 #pragma qt_class(QtSystemDetection) #pragma qt_sync_skip_header_check @@ -54,14 +50,10 @@ #if defined(__APPLE__) && (defined(__GNUC__) || defined(__xlC__) || defined(__xlc__)) # include +# define Q_OS_APPLE # if defined(TARGET_OS_MAC) && TARGET_OS_MAC # define Q_OS_DARWIN # define Q_OS_BSD4 -# ifdef __LP64__ -# define Q_OS_DARWIN64 -# else -# define Q_OS_DARWIN32 -# endif # if defined(TARGET_OS_IPHONE) && TARGET_OS_IPHONE # define QT_PLATFORM_UIKIT # if defined(TARGET_OS_WATCH) && TARGET_OS_WATCH @@ -157,92 +149,32 @@ // Compatibility synonyms #ifdef Q_OS_DARWIN -#define Q_OS_MAC -#endif -#ifdef Q_OS_DARWIN32 -#define Q_OS_MAC32 -#endif -#ifdef Q_OS_DARWIN64 -#define Q_OS_MAC64 -#endif -#ifdef Q_OS_MACOS -#define Q_OS_MACX -#define Q_OS_OSX +# pragma clang diagnostic push +# pragma clang diagnostic ignored "-Wunknown-pragmas" +# define Q_OS_MAC // FIXME: Deprecate +# ifdef __LP64__ +# define Q_OS_DARWIN64 +# pragma clang deprecated(Q_OS_DARWIN64, "use Q_OS_DARWIN and QT_POINTER_SIZE/Q_PROCESSOR_* instead") +# define Q_OS_MAC64 +# pragma clang deprecated(Q_OS_MAC64, "use Q_OS_DARWIN and QT_POINTER_SIZE/Q_PROCESSOR_* instead") +# else +# define Q_OS_DARWIN32 +# pragma clang deprecated(Q_OS_DARWIN32, "use Q_OS_DARWIN and QT_POINTER_SIZE/Q_PROCESSOR_* instead") +# define Q_OS_MAC32 +# pragma clang deprecated(Q_OS_MAC32, "use Q_OS_DARWIN and QT_POINTER_SIZE/Q_PROCESSOR_* instead") +# endif +# ifdef Q_OS_MACOS +# define Q_OS_MACX +# pragma clang deprecated(Q_OS_MACX, "use Q_OS_MACOS instead") +# define Q_OS_OSX +# pragma clang deprecated(Q_OS_OSX, "use Q_OS_MACOS instead") +# endif +# pragma clang diagnostic pop #endif #ifdef Q_OS_DARWIN # include # include -# -# ifdef Q_OS_MACOS -# if !defined(__MAC_OS_X_VERSION_MIN_REQUIRED) || __MAC_OS_X_VERSION_MIN_REQUIRED < __MAC_10_6 -# undef __MAC_OS_X_VERSION_MIN_REQUIRED -# define __MAC_OS_X_VERSION_MIN_REQUIRED __MAC_10_6 -# endif -# if !defined(MAC_OS_X_VERSION_MIN_REQUIRED) || MAC_OS_X_VERSION_MIN_REQUIRED < MAC_OS_X_VERSION_10_6 -# undef MAC_OS_X_VERSION_MIN_REQUIRED -# define MAC_OS_X_VERSION_MIN_REQUIRED MAC_OS_X_VERSION_10_6 -# endif -# endif -# -# // Numerical checks are preferred to named checks, but to be safe -# // we define the missing version names in case Qt uses them. -# -# if !defined(__MAC_10_11) -# define __MAC_10_11 101100 -# endif -# if !defined(__MAC_10_12) -# define __MAC_10_12 101200 -# endif -# if !defined(__MAC_10_13) -# define __MAC_10_13 101300 -# endif -# if !defined(__MAC_10_14) -# define __MAC_10_14 101400 -# endif -# if !defined(__MAC_10_15) -# define __MAC_10_15 101500 -# endif -# if !defined(__MAC_10_16) -# define __MAC_10_16 101600 -# endif -# if !defined(MAC_OS_X_VERSION_10_11) -# define MAC_OS_X_VERSION_10_11 __MAC_10_11 -# endif -# if !defined(MAC_OS_X_VERSION_10_12) -# define MAC_OS_X_VERSION_10_12 __MAC_10_12 -# endif -# if !defined(MAC_OS_X_VERSION_10_13) -# define MAC_OS_X_VERSION_10_13 __MAC_10_13 -# endif -# if !defined(MAC_OS_X_VERSION_10_14) -# define MAC_OS_X_VERSION_10_14 __MAC_10_14 -# endif -# if !defined(MAC_OS_X_VERSION_10_15) -# define MAC_OS_X_VERSION_10_15 __MAC_10_15 -# endif -# if !defined(MAC_OS_X_VERSION_10_16) -# define MAC_OS_X_VERSION_10_16 __MAC_10_16 -# endif -# -# if !defined(__IPHONE_10_0) -# define __IPHONE_10_0 100000 -# endif -# if !defined(__IPHONE_10_1) -# define __IPHONE_10_1 100100 -# endif -# if !defined(__IPHONE_10_2) -# define __IPHONE_10_2 100200 -# endif -# if !defined(__IPHONE_10_3) -# define __IPHONE_10_3 100300 -# endif -# if !defined(__IPHONE_11_0) -# define __IPHONE_11_0 110000 -# endif -# if !defined(__IPHONE_12_0) -# define __IPHONE_12_0 120000 -# endif # define QT_DARWIN_PLATFORM_SDK_EQUAL_OR_ABOVE(macos, ios, tvos, watchos) \ ((defined(__MAC_OS_X_VERSION_MAX_ALLOWED) && macos != __MAC_NA && __MAC_OS_X_VERSION_MAX_ALLOWED >= macos) || \ @@ -278,13 +210,7 @@ # define QT_WATCHOS_DEPLOYMENT_TARGET_BELOW(watchos) \ QT_DARWIN_DEPLOYMENT_TARGET_BELOW(__MAC_NA, __IPHONE_NA, __TVOS_NA, watchos) -// Compatibility synonyms, do not use -# define QT_MAC_PLATFORM_SDK_EQUAL_OR_ABOVE(osx, ios) QT_MACOS_IOS_PLATFORM_SDK_EQUAL_OR_ABOVE(osx, ios) -# define QT_MAC_DEPLOYMENT_TARGET_BELOW(osx, ios) QT_MACOS_IOS_DEPLOYMENT_TARGET_BELOW(osx, ios) -# define QT_OSX_PLATFORM_SDK_EQUAL_OR_ABOVE(osx) QT_MACOS_PLATFORM_SDK_EQUAL_OR_ABOVE(osx) -# define QT_OSX_DEPLOYMENT_TARGET_BELOW(osx) QT_MACOS_DEPLOYMENT_TARGET_BELOW(osx) - -#else +#else // !Q_OS_DARWIN #define QT_DARWIN_PLATFORM_SDK_EQUAL_OR_ABOVE(macos, ios, tvos, watchos) (0) #define QT_MACOS_IOS_PLATFORM_SDK_EQUAL_OR_ABOVE(macos, ios) (0) @@ -293,9 +219,6 @@ #define QT_TVOS_PLATFORM_SDK_EQUAL_OR_ABOVE(tvos) (0) #define QT_WATCHOS_PLATFORM_SDK_EQUAL_OR_ABOVE(watchos) (0) -#define QT_MAC_PLATFORM_SDK_EQUAL_OR_ABOVE(osx, ios) (0) -#define QT_OSX_PLATFORM_SDK_EQUAL_OR_ABOVE(osx) (0) - #endif // Q_OS_DARWIN #ifdef __LSB_VERSION__ diff --git a/src/corelib/global/qsystemdetection.qdoc b/src/corelib/global/qsystemdetection.qdoc index 210f865f..11750e8c 100644 --- a/src/corelib/global/qsystemdetection.qdoc +++ b/src/corelib/global/qsystemdetection.qdoc @@ -23,6 +23,16 @@ \relates Defined on Darwin-based operating systems such as \macos, iOS, watchOS, and tvOS. + + \note Unless you are dealing with code specific to the Darwin kernel, + prefer Q_OS_APPLE to refer to the family of Apple operating systems. +*/ + +/*! + \macro Q_OS_APPLE + \relates + + Defined on Apple operating systems such as \macos, iOS, watchOS, and tvOS. */ /*! diff --git a/src/corelib/global/qt_pch.h b/src/corelib/global/qt_pch.h index 207a30a5..3f224cda 100644 --- a/src/corelib/global/qt_pch.h +++ b/src/corelib/global/qt_pch.h @@ -14,36 +14,31 @@ // for rand_s, _CRT_RAND_S must be #defined before #including stdlib.h. // put it at the beginning so some indirect inclusion doesn't break it #ifndef _CRT_RAND_S -#define _CRT_RAND_S +# define _CRT_RAND_S #endif #include #include #ifdef Q_OS_WIN -# ifdef Q_CC_MINGW +# ifdef Q_CC_MINGW // must be included before any other header pulls in . -# include // Define _POSIX_THREAD_SAFE_FUNCTIONS to obtain localtime_r() -# endif -# define _POSIX_ -# include -# undef _POSIX_ -# if defined(Q_CC_CLANG) && defined(Q_CC_MSVC) -// See https://bugs.llvm.org/show_bug.cgi?id=41226 -# include -__declspec(selectany) auto *__wmemchr_symbol_loader_value = wmemchr(L"", L'0', 0); -# endif -# endif -# include -# include -# include -# include -# include /* All moc generated code has this include */ -# include -# if QT_CONFIG(regularexpression) -# include -# endif -# include -# include -# include -# include -# include +# include // Define _POSIX_THREAD_SAFE_FUNCTIONS to obtain localtime_r() +# endif // Q_CC_MINGW +# define _POSIX_ +# include +# undef _POSIX_ +#endif // Q_OS_WIN +#include +#include +#include +#include +#include /* All moc generated code has this include */ +#include +#if QT_CONFIG(regularexpression) +# include +#endif +#include +#include +#include +#include +#include #endif diff --git a/src/corelib/global/qtconfiginclude.h b/src/corelib/global/qtconfiginclude.h new file mode 100644 index 00000000..8b22a47a --- /dev/null +++ b/src/corelib/global/qtconfiginclude.h @@ -0,0 +1,22 @@ +// Copyright (C) 2022 Intel Corporation +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only + +#ifndef QTCONFIGINCLUDE_H +#define QTCONFIGINCLUDE_H + +#if 0 +# pragma qt_sync_stop_processing +#endif + +#include + +#ifdef QT_BOOTSTRAPPED +// qconfig-bootstrapped.h is not supposed to be a part of the synced header files. So we find it by +// the include path specified for Bootstrap library in the source tree instead of the build tree as +// it's done for regular header files. +#include "qconfig-bootstrapped.h" +#else +#include +#endif + +#endif // QTCONFIGINCLUDE_H diff --git a/src/corelib/global/qtconfigmacros.h b/src/corelib/global/qtconfigmacros.h index 7d42b13c..a89894ff 100644 --- a/src/corelib/global/qtconfigmacros.h +++ b/src/corelib/global/qtconfigmacros.h @@ -7,15 +7,10 @@ #if 0 # pragma qt_sync_stop_processing #endif -#ifdef QT_BOOTSTRAPPED -// qconfig-bootstrapped.h is not supposed to be a part of the synced header files. So we find it by -// the include path specified for Bootstrap library in the source tree instead of the build tree as -// it's done for regular header files. -#include "qconfig-bootstrapped.h" -#else -#include -#include -#endif + +#include + +#include /* The Qt modules' export macros. @@ -65,7 +60,7 @@ 1: The feature is available */ #define QT_CONFIG(feature) (1/QT_FEATURE_##feature == 1) -#define QT_REQUIRE_CONFIG(feature) Q_STATIC_ASSERT_X(QT_FEATURE_##feature == 1, "Required feature " #feature " for file " __FILE__ " not available.") +#define QT_REQUIRE_CONFIG(feature) static_assert(QT_FEATURE_##feature == 1, "Required feature " #feature " for file " __FILE__ " not available.") /* moc compats (signals/slots) */ #ifndef QT_MOC_COMPAT diff --git a/src/corelib/global/qtdeprecationmarkers.h b/src/corelib/global/qtdeprecationmarkers.h index fb97487b..a480a18e 100644 --- a/src/corelib/global/qtdeprecationmarkers.h +++ b/src/corelib/global/qtdeprecationmarkers.h @@ -203,6 +203,14 @@ QT_BEGIN_NAMESPACE # define QT_DEPRECATED_VERSION_6_9 #endif +#if QT_WARN_DEPRECATED_UP_TO >= QT_VERSION_CHECK(6, 10, 0) +# define QT_DEPRECATED_VERSION_X_6_10(text) QT_DEPRECATED_X(text) +# define QT_DEPRECATED_VERSION_6_10 QT_DEPRECATED +#else +# define QT_DEPRECATED_VERSION_X_6_10(text) +# define QT_DEPRECATED_VERSION_6_10 +#endif + #define QT_DEPRECATED_VERSION_X_5(minor, text) QT_DEPRECATED_VERSION_X_5_##minor(text) #define QT_DEPRECATED_VERSION_X(major, minor, text) QT_DEPRECATED_VERSION_X_##major##_##minor(text) @@ -299,6 +307,12 @@ QT_BEGIN_NAMESPACE # define QT_IF_DEPRECATED_SINCE_6_9(whenTrue, whenFalse) whenTrue #endif +#if QT_DEPRECATED_SINCE(6, 10) +# define QT_IF_DEPRECATED_SINCE_6_10(whenTrue, whenFalse) whenFalse +#else +# define QT_IF_DEPRECATED_SINCE_6_10(whenTrue, whenFalse) whenTrue +#endif + #ifdef __cplusplus // A tag to help mark stuff deprecated (cf. QStringViewLiteral) namespace QtPrivate { diff --git a/src/corelib/global/qtenvironmentvariables.cpp b/src/corelib/global/qtenvironmentvariables.cpp index 4046beae..47fc8f7e 100644 --- a/src/corelib/global/qtenvironmentvariables.cpp +++ b/src/corelib/global/qtenvironmentvariables.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 "qtenvironmentvariables.h" +#include "qtenvironmentvariables_p.h" #include #include @@ -330,9 +331,11 @@ bool qunsetenv(const char *varName) #endif } -/* - Wraps tzset(), which accesses the environment, so should only be called while - we hold the lock on the environment mutex. +/* Various time-related APIs that need to consult system settings also need + protection with the same lock as the environment, since those system settings + include part of the environment (principally TZ). + + First, tzset(), which POSIX explicitly says accesses the environment. */ void qTzSet() { @@ -344,9 +347,8 @@ void qTzSet() #endif // Q_OS_WIN } -/* - Wrap mktime(), which is specified to behave as if it called tzset(), hence - shares its implicit environment-dependence. +/* Wrap mktime(), which is specified to behave as if it called tzset(), hence + shares its implicit environment-dependence. */ time_t qMkTime(struct tm *when) { @@ -354,4 +356,70 @@ time_t qMkTime(struct tm *when) return mktime(when); } +/* For localtime(), POSIX mandates that it behave as if it called tzset(). + For the alternatives to it, we need (if only for compatibility) to do the + same explicitly, which should ensure a re-parse of timezone info. +*/ +bool qLocalTime(time_t utc, struct tm *local) +{ + const auto locker = qt_scoped_lock(environmentMutex); +#if defined(Q_OS_WIN) + // The doc of localtime_s() says that it corrects for the same things + // _tzset() sets the globals for, but QTBUG-109974 reveals a need for an + // explicit call, all the same. + _tzset(); + return !localtime_s(local, &utc); +#elif QT_CONFIG(thread) && defined(_POSIX_THREAD_SAFE_FUNCTIONS) + // Use the reentrant version of localtime() where available, as it is + // thread-safe and doesn't use a shared static data area. + // As localtime_r() is not specified to work as if it called tzset(), + // make an explicit call. + tzset(); + if (tm *res = localtime_r(&utc, local)) { + Q_ASSERT(res == local); + Q_UNUSED(res); + return true; + } + return false; +#else + // POSIX mandates that localtime() behaves as if it called tzset(). + // Returns shared static data which may be overwritten at any time (albeit + // our lock probably keeps it safe). So copy the result promptly: + if (tm *res = localtime(&utc)) { + *local = *res; + return true; + } + return false; +#endif +} + +/* Access to the tzname[] global in one thread is UB if any other is calling + tzset() or anything that behaves as if it called tzset(). So also lock this + access to prevent such collisions. + + Parameter dstIndex must be 1 for DST or 0 for standard time. + Returns the relevant form of the name of local-time's zone. +*/ +QString qTzName(int dstIndex) +{ + char name[512]; + bool ok; +#if defined(Q_CC_MSVC) + size_t s = 0; + { + const auto locker = qt_scoped_lock(environmentMutex); + ok = _get_tzname(&s, name, 512, dstIndex) != 0; + } +#else + { + const auto locker = qt_scoped_lock(environmentMutex); + const char *const src = tzname[dstIndex]; + ok = src != nullptr; + if (ok) + memcpy(name, src, std::min(sizeof(name), strlen(src) + 1)); + } +#endif // Q_OS_WIN + return ok ? QString::fromLocal8Bit(name) : QString(); +} + QT_END_NAMESPACE diff --git a/src/corelib/global/qtenvironmentvariables_p.h b/src/corelib/global/qtenvironmentvariables_p.h new file mode 100644 index 00000000..0c81d36d --- /dev/null +++ b/src/corelib/global/qtenvironmentvariables_p.h @@ -0,0 +1,39 @@ +// Copyright (C) 2017 The Qt Company Ltd. +// Copyright (C) 2015 Intel Corporation. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only + +#ifndef QTENVIRONMENTVARIABLES_P_H +#define QTENVIRONMENTVARIABLES_P_H +// Nothing but (tests and) ../time/qlocaltime.cpp should access this. +#if defined(__cplusplus) + +// +// 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 "qglobal_p.h" + +#ifdef Q_CC_MINGW +# include // Define _POSIX_THREAD_SAFE_FUNCTIONS to obtain localtime_r() +#endif +#include + +QT_BEGIN_NAMESPACE + +// These (behave as if they) consult the environment, so need to share its locking: +Q_CORE_EXPORT void qTzSet(); +Q_CORE_EXPORT time_t qMkTime(struct tm *when); +Q_CORE_EXPORT bool qLocalTime(time_t utc, struct tm *local); +Q_CORE_EXPORT QString qTzName(int dstIndex); + +QT_END_NAMESPACE + +#endif // defined(__cplusplus) +#endif // QTENVIRONMENTVARIABLES_P_H diff --git a/src/corelib/global/qttypetraits.h b/src/corelib/global/qttypetraits.h index 7e991ab8..49f2728e 100644 --- a/src/corelib/global/qttypetraits.h +++ b/src/corelib/global/qttypetraits.h @@ -5,6 +5,7 @@ #define QTTYPETRAITS_H #include +#include #include #include @@ -24,16 +25,21 @@ constexpr std::underlying_type_t qToUnderlying(Enum e) noexcept } #ifndef QT_NO_AS_CONST +#if QT_DEPRECATED_SINCE(6, 6) // this adds const to non-const objects (like std::as_const) template +QT_DEPRECATED_VERSION_X_6_6("Use std::as_const() instead.") constexpr typename std::add_const::type &qAsConst(T &t) noexcept { return t; } // prevent rvalue arguments: template void qAsConst(const T &&) = delete; +#endif // QT_DEPRECATED_SINCE(6, 6) #endif // QT_NO_AS_CONST +#ifndef QT_NO_QEXCHANGE + // like std::exchange template constexpr T qExchange(T &t, U &&newValue) @@ -45,6 +51,8 @@ noexcept(std::conjunction_v, return old; } +#endif // QT_NO_QEXCHANGE + namespace QtPrivate { // helper to be used to trigger a "dependent static_assert(false)" // (for instance, in a final `else` branch of a `if constexpr`.) diff --git a/src/corelib/global/qttypetraits.qdoc b/src/corelib/global/qttypetraits.qdoc index 082b6255..44c9ad68 100644 --- a/src/corelib/global/qttypetraits.qdoc +++ b/src/corelib/global/qttypetraits.qdoc @@ -15,6 +15,8 @@ \relates \since 5.7 + \deprecated [6.6] Use std::as_const() instead. + Returns \a t cast to \c{const T}. This function is a Qt implementation of C++17's std::as_const(), @@ -52,6 +54,8 @@ \since 5.7 \overload + \deprecated [6.6] + This overload is deleted to prevent a dangling reference in code like \snippet code/src_corelib_global_qglobal.cpp as-const-4 */ diff --git a/src/corelib/global/qtversionchecks.h b/src/corelib/global/qtversionchecks.h index d3b7a7b0..7b5d99af 100644 --- a/src/corelib/global/qtversionchecks.h +++ b/src/corelib/global/qtversionchecks.h @@ -9,15 +9,7 @@ #pragma qt_sync_stop_processing #endif -#ifdef QT_BOOTSTRAPPED -// qconfig-bootstrapped.h is not supposed to be a part of the synced header files. So we find it by -// the include path specified for Bootstrap library in the source tree instead of the build tree as -// it's done for regular header files. -#include "qconfig-bootstrapped.h" -#else -#include -#include -#endif +#include /* QT_VERSION is (major << 16) | (minor << 8) | patch. @@ -36,7 +28,7 @@ void QT7_ONLY(Q_CORE_EXPORT) void operate(); } */ -#if QT_VERSION_MAJOR == 7 +#if QT_VERSION_MAJOR == 7 || defined(QT_BOOTSTRAPPED) # define QT7_ONLY(...) __VA_ARGS__ # define QT6_ONLY(...) #elif QT_VERSION_MAJOR == 6 diff --git a/src/corelib/global/qtypeinfo.h b/src/corelib/global/qtypeinfo.h index f493dce1..f89bf726 100644 --- a/src/corelib/global/qtypeinfo.h +++ b/src/corelib/global/qtypeinfo.h @@ -2,15 +2,16 @@ // Copyright (C) 2016 Intel Corporation. // SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only -#include +#ifndef QTYPEINFO_H +#define QTYPEINFO_H + +#include #include + #include #include #include -#ifndef QTYPEINFO_H -#define QTYPEINFO_H - QT_BEGIN_NAMESPACE class QDebug; @@ -45,8 +46,8 @@ class QTypeInfo { public: enum { - isPointer = std::is_pointer_v, - isIntegral = std::is_integral_v, + isPointer [[deprecated("Use std::is_pointer instead")]] = std::is_pointer_v, + isIntegral [[deprecated("Use std::is_integral instead")]] = std::is_integral_v, isComplex = !std::is_trivial_v, isRelocatable = QtPrivate::qIsRelocatable, isValueInitializationBitwiseZero = QtPrivate::qIsValueInitializationBitwiseZero, @@ -58,8 +59,8 @@ class QTypeInfo { public: enum { - isPointer = false, - isIntegral = false, + isPointer [[deprecated("Use std::is_pointer instead")]] = false, + isIntegral [[deprecated("Use std::is_integral instead")]] = false, isComplex = false, isRelocatable = false, isValueInitializationBitwiseZero = false, @@ -93,8 +94,8 @@ class QTypeInfoMerger public: static constexpr bool isComplex = ((QTypeInfo::isComplex) || ...); static constexpr bool isRelocatable = ((QTypeInfo::isRelocatable) && ...); - static constexpr bool isPointer = false; - static constexpr bool isIntegral = false; + [[deprecated("Use std::is_pointer instead")]] static constexpr bool isPointer = false; + [[deprecated("Use std::is_integral instead")]] static constexpr bool isIntegral = false; static constexpr bool isValueInitializationBitwiseZero = false; }; @@ -110,8 +111,8 @@ class QTypeInfo> \ { \ public: \ enum { \ - isPointer = false, \ - isIntegral = false, \ + isPointer [[deprecated("Use std::is_pointer instead")]] = false, \ + isIntegral [[deprecated("Use std::is_integral instead")]] = false, \ isComplex = true, \ isRelocatable = true, \ isValueInitializationBitwiseZero = false, \ @@ -153,8 +154,8 @@ public: \ enum { \ isComplex = (((FLAGS) & Q_PRIMITIVE_TYPE) == 0) && !std::is_trivial_v, \ isRelocatable = !isComplex || ((FLAGS) & Q_RELOCATABLE_TYPE) || QtPrivate::qIsRelocatable, \ - isPointer = std::is_pointer_v< TYPE >, \ - isIntegral = std::is_integral< TYPE >::value, \ + isPointer [[deprecated("Use std::is_pointer instead")]] = std::is_pointer_v< TYPE >, \ + isIntegral [[deprecated("Use std::is_integral instead")]] = std::is_integral< TYPE >::value, \ isValueInitializationBitwiseZero = QtPrivate::qIsValueInitializationBitwiseZero, \ }; \ } diff --git a/src/corelib/global/qtypes.cpp b/src/corelib/global/qtypes.cpp index 5c3b963a..19a7541e 100644 --- a/src/corelib/global/qtypes.cpp +++ b/src/corelib/global/qtypes.cpp @@ -144,6 +144,43 @@ QT_BEGIN_NAMESPACE \sa Q_UINT64_C(), qint64, qulonglong */ +/*! + \typedef qint128 + \relates + \since 6.6 + + Typedef for \c{__int128} on platforms that support it (Qt defines the macro + \l QT_SUPPORTS_INT128 if this is the case). + + Literals of this type can be created using the Q_INT128_C() macro. + + \sa Q_INT128_C(), Q_INT128_MIN, Q_INT128_MAX, quint128, QT_SUPPORTS_INT128 +*/ + +/*! + \typedef quint128 + \relates + \since 6.6 + + Typedef for \c{unsigned __int128} on platforms that support it (Qt defines + the macro \l QT_SUPPORTS_INT128 if this is the case). + + Literals of this type can be created using the Q_UINT128_C() macro. + + \sa Q_UINT128_C(), Q_UINT128_MAX, qint128, QT_SUPPORTS_INT128 +*/ + +/*! + \macro QT_SUPPORTS_INT128 + \relates + \since 6.6 + + Qt defines this macro as well as the \l qint128 and \l quint128 types if + the platform has support for 128-bit integer types. + + \sa qint128, quint128, Q_INT128_C(), Q_UINT128_C(), Q_INT128_MIN, Q_INT128_MAX, Q_UINT128_MAX +*/ + /*! \typedef qintptr \relates @@ -327,7 +364,7 @@ QT_BEGIN_NAMESPACE \snippet code/src_corelib_global_qglobal.cpp 8 - \sa qint64, Q_UINT64_C() + \sa qint64, Q_UINT64_C(), Q_INT128_C() */ /*! \macro quint64 Q_UINT64_C(literal) @@ -340,7 +377,79 @@ QT_BEGIN_NAMESPACE \snippet code/src_corelib_global_qglobal.cpp 9 - \sa quint64, Q_INT64_C() + \sa quint64, Q_INT64_C(), Q_UINT128_C() +*/ + +/*! + \macro qint128 Q_INT128_C(literal) + \relates + \since 6.6 + + Wraps the signed 128-bit integer \a literal in a + platform-independent way. + + \note Unlike Q_INT64_C(), this macro is only available in C++, not in C. + This is because compilers do not provide these literals as built-ins and C + does not have support for user-defined literals. + + \sa qint128, Q_UINT128_C(), Q_INT128_MIN, Q_INT128_MAX, Q_INT64_C(), QT_SUPPORTS_INT128 +*/ + +/*! + \macro quint128 Q_UINT128_C(literal) + \relates + \since 6.6 + + Wraps the unsigned 128-bit integer \a literal in a + platform-independent way. + + \note Unlike Q_UINT64_C(), this macro is only available in C++, not in C. + This is because compilers do not provide these literals as built-ins and C + does not have support for user-defined literals. + + \sa quint128, Q_INT128_C(), Q_UINT128_MAX, Q_UINT64_C(), QT_SUPPORTS_INT128 +*/ + +/*! + \macro Q_UINT128_MAX + \relates + \since 6.6 + + This macro expands to a compile-time constant representing the + maximum value representable in a \l quint128. + + This macro is available in both C++ and C modes. + + The minimum of \l quint128 is 0 (zero), so a \c{Q_UINT128_MIN} is neither + needed nor provided. + + \sa Q_INT128_MAX, quint128, Q_UINT128_C, QT_SUPPORTS_INT128 +*/ + +/*! + \macro Q_INT128_MIN + \relates + \since 6.6 + + This macro expands to a compile-time constant representing the + minimum value representable in a \l qint128. + + This macro is available in both C++ and C modes. + + \sa Q_INT128_MAX, qint128, Q_INT128_C, QT_SUPPORTS_INT128 +*/ + +/*! + \macro Q_INT128_MAX + \relates + \since 6.6 + + This macro expands to a compile-time constant representing the + maximum value representable in a \l qint128. + + This macro is available in both C++ and C modes. + + \sa Q_INT128_MIN, Q_UINT128_MAX, qint128, Q_INT128_C, QT_SUPPORTS_INT128 */ // Statically check assumptions about the environment we're running @@ -399,5 +508,18 @@ static_assert(sizeof(qint8) == 1, "Internal error, qint8 is misdefined"); static_assert(sizeof(qint16)== 2, "Internal error, qint16 is misdefined"); static_assert(sizeof(qint32) == 4, "Internal error, qint32 is misdefined"); static_assert(sizeof(qint64) == 8, "Internal error, qint64 is misdefined"); +#ifdef QT_SUPPORTS_INT128 +static_assert(sizeof(qint128) == 16, "Internal error, qint128 is misdefined"); +#endif + +#ifdef QT_SUPPORTS_INT128 +// check that numeric_limits works: +// This fails here for GCC 9, but succeeds on Clang and GCC >= 11 +// However, all tests in tst_qglobal::int128Literals() pass for GCC 9, too, +// so just suppress the check for older GCC: +# if !defined(Q_CC_GNU_ONLY) || Q_CC_GNU >= 1100 +static_assert(std::numeric_limits::max() == Q_UINT128_MAX); +# endif +#endif QT_END_NAMESPACE diff --git a/src/corelib/global/qtypes.h b/src/corelib/global/qtypes.h index f7757b43..41e60257 100644 --- a/src/corelib/global/qtypes.h +++ b/src/corelib/global/qtypes.h @@ -7,10 +7,13 @@ #include #include +#include #ifdef __cplusplus # include # include +#else +# include #endif #if 0 @@ -19,8 +22,6 @@ #pragma qt_sync_stop_processing #endif -#ifndef __ASSEMBLER__ - /* Useful type definitions for Qt */ @@ -58,9 +59,104 @@ typedef unsigned long long quint64; /* 64 bit unsigned */ typedef qint64 qlonglong; typedef quint64 qulonglong; +#if defined(__SIZEOF_INT128__) && !defined(QT_NO_INT128) +# define QT_SUPPORTS_INT128 __SIZEOF_INT128__ +#else +# undef QT_SUPPORTS_INT128 +#endif + +#if defined(QT_SUPPORTS_INT128) +__extension__ typedef __int128_t qint128; +__extension__ typedef __uint128_t quint128; + +// limits: +# ifdef __cplusplus /* need to avoid c-style-casts in C++ mode */ +# define QT_C_STYLE_CAST(type, x) static_cast(x) +# else /* but C doesn't have constructor-style casts */ +# define QT_C_STYLE_CAST(type, x) ((type)x) +# endif +# ifndef Q_UINT128_MAX /* allow qcompilerdetection.h/user override */ +# define Q_UINT128_MAX QT_C_STYLE_CAST(quint128, -1) +# endif +# define Q_INT128_MAX QT_C_STYLE_CAST(qint128, (Q_UINT128_MAX / 2)) +# define Q_INT128_MIN (-Q_INT128_MAX - 1) + +# ifdef __cplusplus + namespace QtPrivate::NumberLiterals { + namespace detail { + template + constexpr quint128 construct() { return accu; } + + template + constexpr quint128 construct() + { + if constexpr (C != '\'') { // ignore digit separators + const int digitValue = '0' <= C && C <= '9' ? C - '0' : + 'a' <= C && C <= 'z' ? C - 'a' + 10 : + 'A' <= C && C <= 'Z' ? C - 'A' + 10 : + /* else */ -1 ; + static_assert(digitValue >= 0 && digitValue < base, + "Invalid character"); + // accu * base + digitValue <= MAX, but without overflow: + static_assert(accu <= (Q_UINT128_MAX - digitValue) / base, + "Overflow occurred"); + return construct(); + } else { + return construct(); + } + } + + template + constexpr quint128 parse0xb() + { + constexpr quint128 accu = 0; + if constexpr (C == 'x' || C == 'X') + return construct(); // base 16, skip 'x' + else if constexpr (C == 'b' || C == 'B') + return construct(); // base 2, skip 'b' + else + return construct(); // base 8, include C + } + + template + constexpr quint128 parse0() + { + if constexpr (sizeof...(Cs) == 0) // this was just a literal 0 + return 0; + else + return parse0xb(); + } + + template + constexpr quint128 parse() + { + if constexpr (C == '0') + return parse0(); // base 2, 8, or 16 (or just a literal 0), skip '0' + else + return construct<0, 10, C, Cs...>(); // initial accu 0, base 10, include C + } + } // namespace detail + template + constexpr quint128 operator""_quint128() noexcept + { return QtPrivate::NumberLiterals::detail::parse(); } + template + constexpr qint128 operator""_qint128() noexcept + { return qint128(QtPrivate::NumberLiterals::detail::parse()); } + + #ifndef Q_UINT128_C // allow qcompilerdetection.h/user override + # define Q_UINT128_C(c) ([]{ using namespace QtPrivate::NumberLiterals; return c ## _quint128; }()) + #endif + #ifndef Q_INT128_C // allow qcompilerdetection.h/user override + # define Q_INT128_C(c) ([]{ using namespace QtPrivate::NumberLiterals; return c ## _qint128; }()) + #endif + + } // namespace QtPrivate::NumberLiterals +# endif // __cplusplus +#endif // QT_SUPPORTS_INT128 + #ifndef __cplusplus // In C++ mode, we define below using QIntegerForSize template -Q_STATIC_ASSERT_X(sizeof(ptrdiff_t) == sizeof(size_t), "Weird ptrdiff_t and size_t definitions"); +static_assert(sizeof(ptrdiff_t) == sizeof(size_t), "Weird ptrdiff_t and size_t definitions"); typedef ptrdiff_t qptrdiff; typedef ptrdiff_t qsizetype; typedef ptrdiff_t qintptr; @@ -102,8 +198,8 @@ template <> struct QIntegerForSize<1> { typedef quint8 Unsigned; typedef qin template <> struct QIntegerForSize<2> { typedef quint16 Unsigned; typedef qint16 Signed; }; template <> struct QIntegerForSize<4> { typedef quint32 Unsigned; typedef qint32 Signed; }; template <> struct QIntegerForSize<8> { typedef quint64 Unsigned; typedef qint64 Signed; }; -#if defined(Q_CC_GNU) && defined(__SIZEOF_INT128__) -template <> struct QIntegerForSize<16> { __extension__ typedef unsigned __int128 Unsigned; __extension__ typedef __int128 Signed; }; +#if defined(QT_SUPPORTS_INT128) +template <> struct QIntegerForSize<16> { typedef quint128 Unsigned; typedef qint128 Signed; }; #endif template struct QIntegerForSizeof: QIntegerForSize { }; typedef QIntegerForSize::Signed qregisterint; @@ -158,6 +254,4 @@ using qsizetype = QIntegerForSizeof::Signed; QT_END_NAMESPACE -#endif // __ASSEMBLER__ - #endif // QTYPES_H diff --git a/src/corelib/global/qversiontagging.h b/src/corelib/global/qversiontagging.h index e64cae1d..73faf5b6 100644 --- a/src/corelib/global/qversiontagging.h +++ b/src/corelib/global/qversiontagging.h @@ -1,12 +1,14 @@ // Copyright (C) 2022 Intel Corporation. // SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only -// qglobal.h includes this header, so keep it outside of our include guards -#include - #if !defined(QVERSIONTAGGING_H) #define QVERSIONTAGGING_H +#include +#include +#include +#include + QT_BEGIN_NAMESPACE /* @@ -86,7 +88,7 @@ struct QVersionTag // Calling convention on other architectures does not prepend a _ # define QT_MANGLE_IMPORT_PREFIX __imp_ # endif -# ifdef Q_CC_MSVC +# if defined(Q_CC_MSVC_ONLY) # pragma section(".qtversion",read,shared) # define QT_VERSION_TAG_SECTION __declspec(allocate(".qtversion")) # define QT_VERSION_TAG_ATTRIBUTE __declspec(selectany) extern const diff --git a/src/corelib/io/qabstractfileengine.cpp b/src/corelib/io/qabstractfileengine.cpp index 401eae8a..25dfe79d 100644 --- a/src/corelib/io/qabstractfileengine.cpp +++ b/src/corelib/io/qabstractfileengine.cpp @@ -225,6 +225,8 @@ QAbstractFileEngine *QAbstractFileEngine::create(const QString &fileName) the base name). \value AbsoluteLinkTarget The full file name of the file that this file is a link to. (This will be empty if this file is not a link.) + \value RawLinkPath The raw link path of the file that this file is a + link to. (This will be empty if this file is not a link.) \value CanonicalName Often very similar to AbsoluteLinkTarget. Will return the true path to the file. \value CanonicalPathName Same as CanonicalName, excluding the base name. \value BundleName Returns the name of the bundle implies BundleType is set. diff --git a/src/corelib/io/qabstractfileengine_p.h b/src/corelib/io/qabstractfileengine_p.h index 04ad7827..28f07bf8 100644 --- a/src/corelib/io/qabstractfileengine_p.h +++ b/src/corelib/io/qabstractfileengine_p.h @@ -73,6 +73,7 @@ public: CanonicalPathName, BundleName, JunctionName, + RawLinkPath, NFileNames // Must be last. }; enum FileOwner { diff --git a/src/corelib/io/qdebug.cpp b/src/corelib/io/qdebug.cpp index dd8661cc..240cc2ea 100644 --- a/src/corelib/io/qdebug.cpp +++ b/src/corelib/io/qdebug.cpp @@ -15,6 +15,8 @@ #include #include +#include + QT_BEGIN_NAMESPACE using namespace QtMiscUtils; @@ -345,6 +347,95 @@ void QDebug::putByteArray(const char *begin, size_t length, Latin1Content conten } } +static QByteArray timeUnit(qint64 num, qint64 den) +{ + using namespace std::chrono; + using namespace q20::chrono; + + if (num == 1 && den > 1) { + // sub-multiple of seconds + char prefix = '\0'; + auto tryprefix = [&](auto d, char c) { + static_assert(decltype(d)::num == 1, "not an SI prefix"); + if (den == decltype(d)::den) + prefix = c; + }; + + // "u" should be "µ", but debugging output is not always UTF-8-safe + tryprefix(std::milli{}, 'm'); + tryprefix(std::micro{}, 'u'); + tryprefix(std::nano{}, 'n'); + tryprefix(std::pico{}, 'p'); + tryprefix(std::femto{}, 'f'); + tryprefix(std::atto{}, 'a'); + // uncommon ones later + tryprefix(std::centi{}, 'c'); + tryprefix(std::deci{}, 'd'); + if (prefix) { + char unit[3] = { prefix, 's' }; + return QByteArray(unit, sizeof(unit) - 1); + } + } + + const char *unit = nullptr; + if (num > 1 && den == 1) { + // multiple of seconds - but we don't use SI prefixes + auto tryunit = [&](auto d, const char *name) { + static_assert(decltype(d)::period::den == 1, "not a multiple of a second"); + if (unit || num % decltype(d)::period::num) + return; + unit = name; + num /= decltype(d)::period::num; + }; + tryunit(years{}, "yr"); + tryunit(weeks{}, "wk"); + tryunit(days{}, "d"); + tryunit(hours{}, "h"); + tryunit(minutes{}, "min"); + } + if (!unit) + unit = "s"; + + if (num == 1 && den == 1) + return unit; + if (Q_UNLIKELY(num < 1 || den < 1)) + return QString::asprintf("", num, den).toLatin1(); + + // uncommon units: will return something like "[2/3]s" + // strlen("[/]min") = 6 + char buf[2 * (std::numeric_limits::digits10 + 2) + 10]; + size_t len = 0; + auto appendChar = [&](char c) { + Q_ASSERT(len < sizeof(buf)); + buf[len++] = c; + }; + auto appendNumber = [&](qint64 value) { + if (value >= 10'000 && (value % 1000) == 0) + len += qsnprintf(buf + len, sizeof(buf) - len, "%.6g", double(value)); // "1e+06" + else + len += qsnprintf(buf + len, sizeof(buf) - len, "%lld", value); + }; + appendChar('['); + appendNumber(num); + if (den != 1) { + appendChar('/'); + appendNumber(den); + } + appendChar(']'); + memcpy(buf + len, unit, strlen(unit)); + return QByteArray(buf, len + strlen(unit)); +} + +/*! + \since 6.6 + \internal + Helper to the std::chrono::duration debug streaming output. + */ +void QDebug::putTimeUnit(qint64 num, qint64 den) +{ + stream->ts << timeUnit(num, den); // ### optimize +} + /*! \fn QDebug::swap(QDebug &other) \since 5.0 @@ -776,6 +867,18 @@ QDebug &QDebug::resetFormat() \endlist */ +/*! + \since 6.6 + \fn template QDebug &QDebug::operator<<(std::chrono::duration duration) + + Prints the time duration \a duration to the stream and returns a reference + to the stream. The printed string is the numeric representation of the + period followed by the time unit, similar to what the C++ Standard Library + would produce with \c{std::ostream}. + + The unit is not localized. +*/ + /*! \fn template QString QDebug::toString(T &&object) \since 6.0 @@ -1023,7 +1126,6 @@ void qt_QMetaEnum_flagDebugOperator(QDebug &debug, size_t sizeofT, int value) #ifndef QT_NO_QOBJECT /*! - \fn QDebug qt_QMetaEnum_debugOperator(QDebug &, int value, const QMetaObject *, const char *name) \internal Formats the given enum \a value for debug output. @@ -1070,7 +1172,7 @@ QDebug qt_QMetaEnum_debugOperator(QDebug &dbg, qint64 value, const QMetaObject * dbg << scope << u"::"; } - const char *key = me.valueToKey(value); + const char *key = me.valueToKey(static_cast(value)); const bool scoped = me.isScoped() || verbosity & 1; if (scoped || !key) dbg << me.enumName() << (!key ? u"(" : u"::"); @@ -1139,7 +1241,7 @@ QDebug qt_QMetaEnum_flagDebugOperator(QDebug &debug, quint64 value, const QMetaO debug << '('; } - debug << me.valueToKeys(value); + debug << me.valueToKeys(static_cast(value)); if (enumScope) debug << ')'; diff --git a/src/corelib/io/qdebug.h b/src/corelib/io/qdebug.h index a55a6ad1..8469a4a8 100644 --- a/src/corelib/io/qdebug.h +++ b/src/corelib/io/qdebug.h @@ -16,6 +16,7 @@ #include // all these have already been included by various headers above, but don't rely on indirect includes: +#include #include #include #include @@ -67,6 +68,7 @@ class QT6_ONLY(Q_CORE_EXPORT) QDebug : public QIODeviceBase QT7_ONLY(Q_CORE_EXPORT) void putUcs4(uint ucs4); QT7_ONLY(Q_CORE_EXPORT) void putString(const QChar *begin, size_t length); QT7_ONLY(Q_CORE_EXPORT) void putByteArray(const char *begin, size_t length, Latin1Content content); + QT7_ONLY(Q_CORE_EXPORT) void putTimeUnit(qint64 num, qint64 den); public: explicit QDebug(QIODevice *device) : stream(new Stream(device)) {} explicit QDebug(QString *string) : stream(new Stream(string)) {} @@ -189,6 +191,14 @@ public: { return *this << QString::fromUcs4(s.data(), s.size()); } #endif // !Q_QDOC + template + QDebug &operator<<(std::chrono::duration duration) + { + stream->ts << duration.count(); + putTimeUnit(Period::num, Period::den); + return maybeSpace(); + } + template static QString toString(T &&object) { @@ -202,10 +212,12 @@ public: Q_DECLARE_SHARED(QDebug) class QDebugStateSaverPrivate; -class Q_CORE_EXPORT QDebugStateSaver +class QDebugStateSaver { public: + Q_NODISCARD_CTOR Q_CORE_EXPORT QDebugStateSaver(QDebug &dbg); + Q_CORE_EXPORT ~QDebugStateSaver(); private: Q_DISABLE_COPY(QDebugStateSaver) @@ -528,7 +540,7 @@ inline QDebug operator<<(QDebug debug, QKeyCombination combination) return debug; } -#ifdef Q_OS_MAC +#ifdef Q_OS_DARWIN // We provide QDebug stream operators for commonly used Core Foundation // and Core Graphics types, as well as NSObject. Additional CF/CG types @@ -607,7 +619,7 @@ QT_FOR_EACH_MUTABLE_CORE_GRAPHICS_TYPE(QT_FORWARD_DECLARE_QDEBUG_OPERATOR_FOR_CF #undef QT_FORWARD_DECLARE_CG_TYPE #undef QT_FORWARD_DECLARE_MUTABLE_CG_TYPE -#endif // Q_OS_MAC +#endif // Q_OS_DARWIN QT_END_NAMESPACE diff --git a/src/corelib/io/qdir.cpp b/src/corelib/io/qdir.cpp index b7ee8284..3ad804b9 100644 --- a/src/corelib/io/qdir.cpp +++ b/src/corelib/io/qdir.cpp @@ -22,7 +22,9 @@ #include #ifndef QT_BOOTSTRAPPED -# include "qreadwritelock.h" +# include +# include "qreadwritelock.h" +# include "qmutex.h" #endif #include @@ -77,12 +79,9 @@ static qsizetype rootLength(QStringView name, bool allowUncPaths) } //************* QDirPrivate -QDirPrivate::QDirPrivate(const QString &path, const QStringList &nameFilters_, QDir::SortFlags sort_, QDir::Filters filters_) - : QSharedData() - , fileListsInitialized(false) - , nameFilters(nameFilters_) - , sort(sort_) - , filters(filters_) +QDirPrivate::QDirPrivate(const QString &path, const QStringList &nameFilters_, + QDir::SortFlags sort_, QDir::Filters filters_) + : QSharedData(), nameFilters(nameFilters_), sort(sort_), filters(filters_) { setPath(path.isEmpty() ? QString::fromLatin1(".") : path); @@ -93,22 +92,31 @@ QDirPrivate::QDirPrivate(const QString &path, const QStringList &nameFilters_, Q } QDirPrivate::QDirPrivate(const QDirPrivate ©) - : QSharedData(copy) - , fileListsInitialized(false) - , nameFilters(copy.nameFilters) - , sort(copy.sort) - , filters(copy.filters) - , dirEntry(copy.dirEntry) - , metaData(copy.metaData) + : QSharedData(copy), + // mutex is not copied + nameFilters(copy.nameFilters), + sort(copy.sort), + filters(copy.filters), + // fileEngine is not copied + dirEntry(copy.dirEntry) { + QMutexLocker locker(©.fileCache.mutex); + fileCache.fileListsInitialized = copy.fileCache.fileListsInitialized.load(); + fileCache.files = copy.fileCache.files; + fileCache.fileInfos = copy.fileCache.fileInfos; + fileCache.absoluteDirEntry = copy.fileCache.absoluteDirEntry; + fileCache.metaData = copy.fileCache.metaData; } bool QDirPrivate::exists() const { if (!fileEngine) { - QFileSystemEngine::fillMetaData(dirEntry, metaData, - QFileSystemMetaData::ExistsAttribute | QFileSystemMetaData::DirectoryType); // always stat - return metaData.exists() && metaData.isDirectory(); + QMutexLocker locker(&fileCache.mutex); + QFileSystemEngine::fillMetaData( + dirEntry, fileCache.metaData, + QFileSystemMetaData::ExistsAttribute + | QFileSystemMetaData::DirectoryType); // always stat + return fileCache.metaData.exists() && fileCache.metaData.isDirectory(); } const QAbstractFileEngine::FileFlags info = fileEngine->fileFlags(QAbstractFileEngine::DirectoryType @@ -151,56 +159,89 @@ inline void QDirPrivate::setPath(const QString &path) ) { p.truncate(p.size() - 1); } - dirEntry = QFileSystemEntry(p, QFileSystemEntry::FromInternalPath()); - metaData.clear(); - initFileEngine(); - clearFileLists(); - absoluteDirEntry = QFileSystemEntry(); + clearCache(IncludingMetaData); + fileCache.absoluteDirEntry = QFileSystemEntry(); } -inline void QDirPrivate::clearFileLists() +inline QString QDirPrivate::resolveAbsoluteEntry() const { - fileListsInitialized = false; - files.clear(); - fileInfos.clear(); -} + QMutexLocker locker(&fileCache.mutex); + if (!fileCache.absoluteDirEntry.isEmpty()) + return fileCache.absoluteDirEntry.filePath(); -inline void QDirPrivate::resolveAbsoluteEntry() const -{ - if (!absoluteDirEntry.isEmpty() || dirEntry.isEmpty()) - return; + if (dirEntry.isEmpty()) + return dirEntry.filePath(); QString absoluteName; if (!fileEngine) { if (!dirEntry.isRelative() && dirEntry.isClean()) { - absoluteDirEntry = dirEntry; - return; + fileCache.absoluteDirEntry = dirEntry; + return dirEntry.filePath(); } absoluteName = QFileSystemEngine::absoluteName(dirEntry).filePath(); } else { absoluteName = fileEngine->fileName(QAbstractFileEngine::AbsoluteName); } - - absoluteDirEntry = QFileSystemEntry(QDir::cleanPath(absoluteName), QFileSystemEntry::FromInternalPath()); + auto absoluteFileSystemEntry = + QFileSystemEntry(QDir::cleanPath(absoluteName), QFileSystemEntry::FromInternalPath()); + fileCache.absoluteDirEntry = absoluteFileSystemEntry; + return absoluteFileSystemEntry.filePath(); } /* For sorting */ struct QDirSortItem { + QDirSortItem() = default; + QDirSortItem(const QFileInfo &fi, QDir::SortFlags sort) + : item(fi) + { + // A dir e.g. "dirA.bar" doesn't have actually have an extension/suffix, when + // sorting by type such "suffix" should be ignored but that would complicate + // the code and uses can change the behavior by setting DirsFirst/DirsLast + if (sort.testAnyFlag(QDir::Type)) + suffix_cache = item.suffix(); + } + mutable QString filename_cache; - mutable QString suffix_cache; + QString suffix_cache; QFileInfo item; }; - class QDirSortItemComparator { QDir::SortFlags qt_cmp_si_sort_flags; + +#ifndef QT_BOOTSTRAPPED + QCollator *collator = nullptr; +#endif public: - QDirSortItemComparator(QDir::SortFlags flags) : qt_cmp_si_sort_flags(flags) {} +#ifndef QT_BOOTSTRAPPED + QDirSortItemComparator(QDir::SortFlags flags, QCollator *coll = nullptr) + : qt_cmp_si_sort_flags(flags), collator(coll) + { + Q_ASSERT(!qt_cmp_si_sort_flags.testAnyFlag(QDir::LocaleAware) || collator); + + if (collator && qt_cmp_si_sort_flags.testAnyFlag(QDir::IgnoreCase)) + collator->setCaseSensitivity(Qt::CaseInsensitive); + } +#else + QDirSortItemComparator(QDir::SortFlags flags) + : qt_cmp_si_sort_flags(flags) + { + } +#endif bool operator()(const QDirSortItem &, const QDirSortItem &) const; + + int compareStrings(const QString &a, const QString &b, Qt::CaseSensitivity cs) const + { +#ifndef QT_BOOTSTRAPPED + if (collator) + return collator->compare(a, b); +#endif + return a.compare(b, cs); + } }; bool QDirSortItemComparator::operator()(const QDirSortItem &n1, const QDirSortItem &n2) const @@ -213,43 +254,25 @@ bool QDirSortItemComparator::operator()(const QDirSortItem &n1, const QDirSortIt if ((qt_cmp_si_sort_flags & QDir::DirsLast) && (f1->item.isDir() != f2->item.isDir())) return !f1->item.isDir(); + const bool ic = qt_cmp_si_sort_flags.testAnyFlag(QDir::IgnoreCase); + const auto qtcase = ic ? Qt::CaseInsensitive : Qt::CaseSensitive; + qint64 r = 0; int sortBy = ((qt_cmp_si_sort_flags & QDir::SortByMask) | (qt_cmp_si_sort_flags & QDir::Type)).toInt(); switch (sortBy) { case QDir::Time: { - QDateTime firstModified = f1->item.lastModified(); - QDateTime secondModified = f2->item.lastModified(); - - // QDateTime by default will do all sorts of conversions on these to - // find timezones, which is incredibly expensive. As we aren't - // presenting these to the user, we don't care (at all) about the - // local timezone, so force them to UTC to avoid that conversion. - firstModified.setTimeZone(QTimeZone::UTC); - secondModified.setTimeZone(QTimeZone::UTC); - + const QDateTime firstModified = f1->item.lastModified(QTimeZone::UTC); + const QDateTime secondModified = f2->item.lastModified(QTimeZone::UTC); r = firstModified.msecsTo(secondModified); break; } case QDir::Size: r = f2->item.size() - f1->item.size(); break; - case QDir::Type: - { - bool ic = qt_cmp_si_sort_flags.testAnyFlag(QDir::IgnoreCase); - - if (f1->suffix_cache.isNull()) - f1->suffix_cache = ic ? f1->item.suffix().toLower() - : f1->item.suffix(); - if (f2->suffix_cache.isNull()) - f2->suffix_cache = ic ? f2->item.suffix().toLower() - : f2->item.suffix(); - - r = qt_cmp_si_sort_flags & QDir::LocaleAware - ? f1->suffix_cache.localeAwareCompare(f2->suffix_cache) - : f1->suffix_cache.compare(f2->suffix_cache); - } + case QDir::Type: + r = compareStrings(f1->suffix_cache, f2->suffix_cache, qtcase); break; default: ; @@ -257,18 +280,13 @@ bool QDirSortItemComparator::operator()(const QDirSortItem &n1, const QDirSortIt if (r == 0 && sortBy != QDir::Unsorted) { // Still not sorted - sort by name - bool ic = qt_cmp_si_sort_flags.testAnyFlag(QDir::IgnoreCase); if (f1->filename_cache.isNull()) - f1->filename_cache = ic ? f1->item.fileName().toLower() - : f1->item.fileName(); + f1->filename_cache = f1->item.fileName(); if (f2->filename_cache.isNull()) - f2->filename_cache = ic ? f2->item.fileName().toLower() - : f2->item.fileName(); + f2->filename_cache = f2->item.fileName(); - r = qt_cmp_si_sort_flags & QDir::LocaleAware - ? f1->filename_cache.localeAwareCompare(f2->filename_cache) - : f1->filename_cache.compare(f2->filename_cache); + r = compareStrings(f1->filename_cache, f2->filename_cache, qtcase); } if (qt_cmp_si_sort_flags & QDir::Reversed) return r > 0; @@ -278,48 +296,75 @@ bool QDirSortItemComparator::operator()(const QDirSortItem &n1, const QDirSortIt inline void QDirPrivate::sortFileList(QDir::SortFlags sort, const QFileInfoList &l, QStringList *names, QFileInfoList *infos) { - // names and infos are always empty lists or 0 here - qsizetype n = l.size(); - if (n > 0) { - if (n == 1 || (sort & QDir::SortByMask) == QDir::Unsorted) { - if (infos) - *infos = l; - if (names) { - for (const QFileInfo &fi : l) - names->append(fi.fileName()); - } + Q_ASSERT(names || infos); + Q_ASSERT(!infos || infos->isEmpty()); + Q_ASSERT(!names || names->isEmpty()); + + const qsizetype n = l.size(); + if (n == 0) + return; + + if (n == 1 || (sort & QDir::SortByMask) == QDir::Unsorted) { + if (infos) + *infos = l; + + if (names) { + for (const QFileInfo &fi : l) + names->append(fi.fileName()); + } + } else { + QScopedArrayPointer si(new QDirSortItem[n]); + for (qsizetype i = 0; i < n; ++i) + si[i] = QDirSortItem{l.at(i), sort}; + +#ifndef QT_BOOTSTRAPPED + if (sort.testAnyFlag(QDir::LocaleAware)) { + QCollator coll; + std::sort(si.data(), si.data() + n, QDirSortItemComparator(sort, &coll)); } else { - QScopedArrayPointer si(new QDirSortItem[n]); - for (qsizetype i = 0; i < n; ++i) - si[i].item = l.at(i); std::sort(si.data(), si.data() + n, QDirSortItemComparator(sort)); - // put them back in the list(s) - if (infos) { - for (qsizetype i = 0; i < n; ++i) - infos->append(si[i].item); - } + } +#else + std::sort(si.data(), si.data() + n, QDirSortItemComparator(sort)); +#endif // QT_BOOTSTRAPPED + + // put them back in the list(s) + for (qsizetype i = 0; i < n; ++i) { + auto &fileInfo = si[i].item; + if (infos) + infos->append(fileInfo); if (names) { - for (qsizetype i = 0; i < n; ++i) - names->append(si[i].item.fileName()); + const bool cached = !si[i].filename_cache.isNull(); + names->append(cached ? si[i].filename_cache : fileInfo.fileName()); } } } } + inline void QDirPrivate::initFileLists(const QDir &dir) const { - if (!fileListsInitialized) { + QMutexLocker locker(&fileCache.mutex); + if (!fileCache.fileListsInitialized) { QFileInfoList l; QDirIterator it(dir); while (it.hasNext()) l.append(it.nextFileInfo()); - sortFileList(sort, l, &files, &fileInfos); - fileListsInitialized = true; + + sortFileList(sort, l, &fileCache.files, &fileCache.fileInfos); + fileCache.fileListsInitialized = true; } } -inline void QDirPrivate::initFileEngine() +inline void QDirPrivate::clearCache(MetaDataClearing mode) { - fileEngine.reset(QFileSystemEngine::resolveEntryAndCreateLegacyEngine(dirEntry, metaData)); + QMutexLocker locker(&fileCache.mutex); + if (mode == IncludingMetaData) + fileCache.metaData.clear(); + fileCache.fileListsInitialized = false; + fileCache.files.clear(); + fileCache.fileInfos.clear(); + fileEngine.reset( + QFileSystemEngine::resolveEntryAndCreateLegacyEngine(dirEntry, fileCache.metaData)); } /*! @@ -622,10 +667,8 @@ QString QDir::path() const QString QDir::absolutePath() const { Q_D(const QDir); - if (!d->fileEngine) { - d->resolveAbsoluteEntry(); - return d->absoluteDirEntry.filePath(); - } + if (!d->fileEngine) + return d->resolveAbsoluteEntry(); return d->fileEngine->fileName(QAbstractFileEngine::AbsoluteName); } @@ -650,7 +693,9 @@ QString QDir::canonicalPath() const { Q_D(const QDir); if (!d->fileEngine) { - QFileSystemEntry answer = QFileSystemEngine::canonicalName(d->dirEntry, d->metaData); + QMutexLocker locker(&d->fileCache.mutex); + QFileSystemEntry answer = + QFileSystemEngine::canonicalName(d->dirEntry, d->fileCache.metaData); return answer.filePath(); } return d->fileEngine->fileName(QAbstractFileEngine::CanonicalName); @@ -767,8 +812,7 @@ QString QDir::absoluteFilePath(const QString &fileName) const return fileName; Q_D(const QDir); - d->resolveAbsoluteEntry(); - const QString absoluteDirPath = d->absoluteDirEntry.filePath(); + QString absoluteDirPath = d->resolveAbsoluteEntry(); if (fileName.isEmpty()) return absoluteDirPath; #ifdef Q_OS_WIN @@ -1027,9 +1071,7 @@ QStringList QDir::nameFilters() const void QDir::setNameFilters(const QStringList &nameFilters) { Q_D(QDir); - d->initFileEngine(); - d->clearFileLists(); - + d->clearCache(QDirPrivate::KeepMetaData); d->nameFilters = nameFilters; } @@ -1208,9 +1250,7 @@ QDir::Filters QDir::filter() const void QDir::setFilter(Filters filters) { Q_D(QDir); - d->initFileEngine(); - d->clearFileLists(); - + d->clearCache(QDirPrivate::KeepMetaData); d->filters = filters; } @@ -1265,9 +1305,7 @@ QDir::SortFlags QDir::sorting() const void QDir::setSorting(SortFlags sort) { Q_D(QDir); - d->initFileEngine(); - d->clearFileLists(); - + d->clearCache(QDirPrivate::KeepMetaData); d->sort = sort; } @@ -1285,7 +1323,7 @@ qsizetype QDir::count(QT6_IMPL_NEW_OVERLOAD) const { Q_D(const QDir); d->initFileLists(*this); - return d->files.size(); + return d->fileCache.files.size(); } /*! @@ -1301,7 +1339,7 @@ QString QDir::operator[](qsizetype pos) const { Q_D(const QDir); d->initFileLists(*this); - return d->files[pos]; + return d->fileCache.files[pos]; } /*! @@ -1378,17 +1416,29 @@ QStringList QDir::entryList(const QStringList &nameFilters, Filters filters, if (sort == NoSort) sort = d->sort; + const bool needsSorting = (sort & QDir::SortByMask) != QDir::Unsorted; + if (filters == d->filters && sort == d->sort && nameFilters == d->nameFilters) { - d->initFileLists(*this); - return d->files; + // Don't fill a QFileInfo cache if we just need names + if (needsSorting || d->fileCache.fileListsInitialized) { + d->initFileLists(*this); + return d->fileCache.files; + } } - QFileInfoList l; QDirIterator it(d->dirEntry.filePath(), nameFilters, filters); - while (it.hasNext()) - l.append(it.nextFileInfo()); QStringList ret; - d->sortFileList(sort, l, &ret, nullptr); + if (needsSorting) { + QFileInfoList l; + while (it.hasNext()) + l.append(it.nextFileInfo()); + d->sortFileList(sort, l, &ret, nullptr); + } else { + while (it.hasNext()) { + it.next(); + ret.append(it.fileName()); + } + } return ret; } @@ -1420,7 +1470,7 @@ QFileInfoList QDir::entryInfoList(const QStringList &nameFilters, Filters filter if (filters == d->filters && sort == d->sort && nameFilters == d->nameFilters) { d->initFileLists(*this); - return d->fileInfos; + return d->fileCache.fileInfos; } QFileInfoList l; @@ -1635,10 +1685,12 @@ bool QDir::isReadable() const Q_D(const QDir); if (!d->fileEngine) { - if (!d->metaData.hasFlags(QFileSystemMetaData::UserReadPermission)) - QFileSystemEngine::fillMetaData(d->dirEntry, d->metaData, QFileSystemMetaData::UserReadPermission); - - return d->metaData.permissions().testAnyFlag(QFile::ReadUser); + QMutexLocker locker(&d->fileCache.mutex); + if (!d->fileCache.metaData.hasFlags(QFileSystemMetaData::UserReadPermission)) { + QFileSystemEngine::fillMetaData(d->dirEntry, d->fileCache.metaData, + QFileSystemMetaData::UserReadPermission); + } + return d->fileCache.metaData.permissions().testAnyFlag(QFile::ReadUser); } const QAbstractFileEngine::FileFlags info = @@ -1745,9 +1797,9 @@ bool QDir::makeAbsolute() dir.reset(new QDirPrivate(*d_ptr.constData())); dir->setPath(absolutePath); } else { // native FS - d->resolveAbsoluteEntry(); + QString absoluteFilePath = d->resolveAbsoluteEntry(); dir.reset(new QDirPrivate(*d_ptr.constData())); - dir->setPath(d->absoluteDirEntry.filePath()); + dir->setPath(absoluteFilePath); } d_ptr = dir.release(); // actually detach return true; @@ -1798,9 +1850,9 @@ bool QDir::operator==(const QDir &dir) const if (dir.exists()) return false; //can't be equal if only one exists // Neither exists, compare absolute paths rather than canonical (which would be empty strings) - d->resolveAbsoluteEntry(); - other->resolveAbsoluteEntry(); - return d->absoluteDirEntry.filePath().compare(other->absoluteDirEntry.filePath(), sensitive) == 0; + QString thisFilePath = d->resolveAbsoluteEntry(); + QString otherFilePath = other->resolveAbsoluteEntry(); + return thisFilePath.compare(otherFilePath, sensitive) == 0; } } return false; @@ -2365,9 +2417,7 @@ bool QDir::isRelativePath(const QString &path) void QDir::refresh() const { QDirPrivate *d = const_cast(this)->d_func(); - d->metaData.clear(); - d->initFileEngine(); - d->clearFileLists(); + d->clearCache(QDirPrivate::IncludingMetaData); } /*! diff --git a/src/corelib/io/qdir_p.h b/src/corelib/io/qdir_p.h index 9f6be858..7dce69c1 100644 --- a/src/corelib/io/qdir_p.h +++ b/src/corelib/io/qdir_p.h @@ -18,6 +18,8 @@ #include "qfilesystementry_p.h" #include "qfilesystemmetadata_p.h" +#include + #include QT_BEGIN_NAMESPACE @@ -37,11 +39,10 @@ public: QDir::SortFlags sort_ = QDir::SortFlags(QDir::Name | QDir::IgnoreCase), QDir::Filters filters_ = QDir::AllEntries); - explicit QDirPrivate(const QDirPrivate ©); + explicit QDirPrivate(const QDirPrivate ©); // Copies everything except mutex and fileEngine bool exists() const; - void initFileEngine(); void initFileLists(const QDir &dir) const; static void sortFileList(QDir::SortFlags, const QFileInfoList &, QStringList *, QFileInfoList *); @@ -52,13 +53,10 @@ public: void setPath(const QString &path); - void clearFileLists(); + enum MetaDataClearing { KeepMetaData, IncludingMetaData }; + void clearCache(MetaDataClearing mode); - void resolveAbsoluteEntry() const; - - mutable bool fileListsInitialized; - mutable QStringList files; - mutable QFileInfoList fileInfos; + QString resolveAbsoluteEntry() const; QStringList nameFilters; QDir::SortFlags sort; @@ -67,8 +65,17 @@ public: std::unique_ptr fileEngine; QFileSystemEntry dirEntry; - mutable QFileSystemEntry absoluteDirEntry; - mutable QFileSystemMetaData metaData; + + struct FileCache + { + QMutex mutex; + QStringList files; + QFileInfoList fileInfos; + std::atomic fileListsInitialized = false; + QFileSystemEntry absoluteDirEntry; + QFileSystemMetaData metaData; + }; + mutable FileCache fileCache; }; Q_DECLARE_OPERATORS_FOR_FLAGS(QDirPrivate::PathNormalizations) diff --git a/src/corelib/io/qfile.h b/src/corelib/io/qfile.h index 3ac67954..839a2b51 100644 --- a/src/corelib/io/qfile.h +++ b/src/corelib/io/qfile.h @@ -26,6 +26,34 @@ namespace std { QT_BEGIN_NAMESPACE +#ifdef Q_OS_WIN + +#if QT_DEPRECATED_SINCE(6,6) +QT_DEPRECATED_VERSION_X_6_6("Use QNtfsPermissionCheckGuard RAII class instead.") +Q_CORE_EXPORT extern int qt_ntfs_permission_lookup; // defined in qfilesystemengine_win.cpp +#endif + +Q_CORE_EXPORT bool qEnableNtfsPermissionChecks() noexcept; +Q_CORE_EXPORT bool qDisableNtfsPermissionChecks() noexcept; +Q_CORE_EXPORT bool qAreNtfsPermissionChecksEnabled() noexcept; + +class QNtfsPermissionCheckGuard +{ + Q_DISABLE_COPY_MOVE(QNtfsPermissionCheckGuard) +public: + Q_NODISCARD_CTOR + QNtfsPermissionCheckGuard() + { + qEnableNtfsPermissionChecks(); + } + + ~QNtfsPermissionCheckGuard() + { + qDisableNtfsPermissionChecks(); + } +}; +#endif // Q_OS_WIN + #if QT_CONFIG(cxx17_filesystem) namespace QtPrivate { inline QString fromFilesystemPath(const std::filesystem::path &path) diff --git a/src/corelib/io/qfiledevice.cpp b/src/corelib/io/qfiledevice.cpp index 6c790e9d..092b09ae 100644 --- a/src/corelib/io/qfiledevice.cpp +++ b/src/corelib/io/qfiledevice.cpp @@ -114,6 +114,20 @@ void QFileDevicePrivate::setError(QFileDevice::FileError err, int errNum) to increment or decrement \c qt_ntfs_permission_lookup before any threads other than the main thread have started or after every thread other than the main thread has ended. + + \note From Qt 6.6 the variable \c qt_ntfs_permission_lookup is + deprecated. Please use the following alternatives. + + The safe and easy way to manage permission checks is to use the RAII class + \c QNtfsPermissionCheckGuard. + + \snippet ntfsp.cpp raii + + If you need more fine-grained control, it is possible to manage the permission + with the following functions instead: + + \snippet ntfsp.cpp free-funcs + */ //************* QFileDevice diff --git a/src/corelib/io/qfileinfo.cpp b/src/corelib/io/qfileinfo.cpp index 1fbd1e57..ee0b2bcc 100644 --- a/src/corelib/io/qfileinfo.cpp +++ b/src/corelib/io/qfileinfo.cpp @@ -38,6 +38,9 @@ QString QFileInfoPrivate::getFileName(QAbstractFileEngine::FileName name) const case QAbstractFileEngine::AbsoluteLinkTarget: ret = QFileSystemEngine::getLinkTarget(fileEntry, metaData).filePath(); break; + case QAbstractFileEngine::RawLinkPath: + ret = QFileSystemEngine::getRawLinkPath(fileEntry, metaData).filePath(); + break; case QAbstractFileEngine::JunctionName: ret = QFileSystemEngine::getJunctionTarget(fileEntry, metaData).filePath(); break; @@ -223,6 +226,25 @@ QDateTime &QFileInfoPrivate::getFileTime(QAbstractFileEngine::FileTime request) isSymLink(). The symLinkTarget() function provides the name of the file the symlink points to. + Elements of the file's name can be extracted with path() and + fileName(). The fileName()'s parts can be extracted with + baseName(), suffix() or completeSuffix(). QFileInfo objects to + directories created by Qt classes will not have a trailing file + separator. If you wish to use trailing separators in your own file + info objects, just append one to the file name given to the constructors + or setFile(). + + Date and time related information are returned by birthTime(), fileTime(), + lastModified(), lastRead(), and metadataChangeTime(). + Information about + access permissions can be obtained with isReadable(), isWritable(), and + isExecutable(). Ownership information can be obtained with + owner(), ownerId(), group(), and groupId(). You can also examine + permissions and ownership in a single statement using the permission() + function. + + \section1 Symbolic Links and Shortcuts + On Unix (including \macos and iOS), the property getter functions in this class return the properties such as times and size of the target file, not the symlink, because Unix handles symlinks transparently. Opening a symlink @@ -238,23 +260,9 @@ QDateTime &QFileInfoPrivate::getFileTime(QAbstractFileEngine::FileTime request) \snippet code/src_corelib_io_qfileinfo.cpp 1 - Elements of the file's name can be extracted with path() and - fileName(). The fileName()'s parts can be extracted with - baseName(), suffix() or completeSuffix(). QFileInfo objects to - directories created by Qt classes will not have a trailing file - separator. If you wish to use trailing separators in your own file - info objects, just append one to the file name given to the constructors - or setFile(). + \section1 NTFS permissions - The file's dates are returned by birthTime(), lastModified(), lastRead() and - fileTime(). Information about the file's access permissions is - obtained with isReadable(), isWritable() and isExecutable(). The - file's ownership is available from owner(), ownerId(), group() and - groupId(). You can examine a file's permissions and ownership in a - single statement using the permission() function. - - \target NTFS permissions - \note On NTFS file systems, ownership and permissions checking is + On NTFS file systems, ownership and permissions checking is disabled by default for performance reasons. To enable it, include the following line: @@ -270,7 +278,20 @@ QDateTime &QFileInfoPrivate::getFileTime(QAbstractFileEngine::FileTime request) threads other than the main thread have started or after every thread other than the main thread has ended. - \section1 Performance Issues + \note From Qt 6.6 the variable \c qt_ntfs_permission_lookup is + deprecated. Please use the following alternatives. + + The safe and easy way to manage permission checks is to use the RAII class + \c QNtfsPermissionCheckGuard. + + \snippet ntfsp.cpp raii + + If you need more fine-grained control, it is possible to manage the permission + with the following functions instead: + + \snippet ntfsp.cpp free-funcs + + \section1 Performance Considerations Some of QFileInfo's functions query the file system, but for performance reasons, some functions only operate on the @@ -279,10 +300,8 @@ QDateTime &QFileInfoPrivate::getFileTime(QAbstractFileEngine::FileTime request) The path() function, however, can work on the file name directly, and so it is faster. - \note To speed up performance, QFileInfo caches information about - the file. - - Because files can be changed by other users or programs, or + To speed up performance, QFileInfo also caches information about + the file. Because files can be changed by other users or programs, or even by other parts of the same program, there is a function that refreshes the file information: refresh(). If you want to switch off a QFileInfo's caching and force it to access the file system @@ -290,6 +309,12 @@ QDateTime &QFileInfoPrivate::getFileTime(QAbstractFileEngine::FileTime request) If you want to make sure that all information is read from the file system, use stat(). + \l{birthTime()}, \l{fileTime()}, \l{lastModified()}, \l{lastRead()}, + and \l{metadataChangeTime()} return times in \e{local time} by default. + Since native file system API typically uses UTC, this requires a conversion. + If you don't actually need the local time, you can avoid this by requesting + the time in QTimeZone::UTC directly. + \section1 Platform Specific Issues \include android-content-uri-limitations.qdocinc @@ -1001,7 +1026,8 @@ bool QFileInfo::isNativePath() const /*! Returns \c true if this object points to a file or to a symbolic link to a file. Returns \c false if the - object points to something which isn't a file, such as a directory. + object points to something that is not a file (such as a directory) + or that does not exist. If the file is a symlink, this function returns true if the target is a regular file (not the symlink). @@ -1019,7 +1045,9 @@ bool QFileInfo::isFile() const /*! Returns \c true if this object points to a directory or to a symbolic - link to a directory; otherwise returns \c false. + link to a directory. Returns \c false if the + object points to something that is not a directory (such as a file) + or that does not exist. If the file is a symlink, this function returns true if the target is a directory (not the symlink). @@ -1235,6 +1263,25 @@ QString QFileInfo::symLinkTarget() const return d->getFileName(QAbstractFileEngine::AbsoluteLinkTarget); } +/*! + \since 6.6 + Read the path the symlink references. + + Returns the raw path referenced by the symbolic link, without resolving a relative + path relative to the directory containing the symbolic link. The returned string will + only be an absolute path if the symbolic link actually references it as such. Returns + an empty string if the object is not a symbolic link. + + \sa symLinkTarget(), exists(), isSymLink(), isDir(), isFile() +*/ +QString QFileInfo::readSymLink() const +{ + Q_D(const QFileInfo); + if (d->isDefaultConstructed) + return {}; + return d->getFileName(QAbstractFileEngine::RawLinkPath); +} + /*! \since 6.2 @@ -1421,8 +1468,26 @@ qint64 QFileInfo::size() const /*! \fn QDateTime QFileInfo::birthTime() const + + Returns the date and time when the file was created (born), in local time. + + If the file birth time is not available, this function returns an invalid QDateTime. + + If the file is a symlink, the time of the target file is returned (not the symlink). + + This function overloads QFileInfo::birthTime(const QTimeZone &tz), and + returns the same as \c{birthTime(QTimeZone::LocalTime)}. + \since 5.10 - Returns the date and time when the file was created / born. + \sa lastModified(), lastRead(), metadataChangeTime(), fileTime() +*/ + +/*! + \fn QDateTime QFileInfo::birthTime(const QTimeZone &tz) const + + Returns the date and time when the file was created (born). + + \include qfileinfo.cpp file-times-in-time-zone If the file birth time is not available, this function returns an invalid QDateTime. @@ -1430,60 +1495,151 @@ qint64 QFileInfo::size() const If the file is a symlink, the time of the target file is returned (not the symlink). - \sa lastModified(), lastRead(), metadataChangeTime() + \since 6.6 + \sa lastModified(const QTimeZone &), lastRead(const QTimeZone &), metadataChangeTime(const QTimeZone &), fileTime(QFile::FileTime, const QTimeZone &) */ /*! \fn QDateTime QFileInfo::metadataChangeTime() const - \since 5.10 - Returns the date and time when the file metadata was changed. A metadata - change occurs when the file is created, but it also occurs whenever the - user writes or sets inode information (for example, changing the file - permissions). + + Returns the date and time when the file's metadata was last changed, + in local time. + + A metadata change occurs when the file is first created, but it also + occurs whenever the user writes or sets inode information (for example, + changing the file permissions). If the file is a symlink, the time of the target file is returned (not the symlink). - \sa lastModified(), lastRead() + This function overloads QFileInfo::metadataChangeTime(const QTimeZone &tz), + and returns the same as \c{metadataChangeTime(QTimeZone::LocalTime)}. + + \since 5.10 + \sa birthTime(), lastModified(), lastRead(), fileTime() +*/ + +/*! + \fn QDateTime QFileInfo::metadataChangeTime(const QTimeZone &tz) const + + Returns the date and time when the file's metadata was last changed. + A metadata change occurs when the file is first created, but it also + occurs whenever the user writes or sets inode information (for example, + changing the file permissions). + + \include qfileinfo.cpp file-times-in-time-zone + + If the file is a symlink, the time of the target file is returned + (not the symlink). + + \since 6.6 + \sa birthTime(const QTimeZone &), lastModified(const QTimeZone &), lastRead(const QTimeZone &), + fileTime(QFile::FileTime time, const QTimeZone &) */ /*! \fn QDateTime QFileInfo::lastModified() const - Returns the date and local time when the file was last modified. + Returns the date and time when the file was last modified. If the file is a symlink, the time of the target file is returned (not the symlink). + This function overloads \l{QFileInfo::lastModified(const QTimeZone &)}, + and returns the same as \c{lastModified(QTimeZone::LocalTime)}. + \sa birthTime(), lastRead(), metadataChangeTime(), fileTime() */ /*! - \fn QDateTime QFileInfo::lastRead() const + \fn QDateTime QFileInfo::lastModified(const QTimeZone &tz) const - Returns the date and local time when the file was last read (accessed). + Returns the date and time when the file was last modified. - On platforms where this information is not available, returns the - same as lastModified(). + \include qfileinfo.cpp file-times-in-time-zone If the file is a symlink, the time of the target file is returned (not the symlink). + \since 6.6 + \sa birthTime(const QTimeZone &), lastRead(const QTimeZone &), metadataChangeTime(const QTimeZone &), fileTime(QFile::FileTime, const QTimeZone &) +*/ + +/*! + \fn QDateTime QFileInfo::lastRead() const + + Returns the date and time when the file was last read (accessed). + + On platforms where this information is not available, returns the same + time as lastModified(). + + If the file is a symlink, the time of the target file is returned + (not the symlink). + + This function overloads \l{QFileInfo::lastRead(const QTimeZone &)}, + and returns the same as \c{lastRead(QTimeZone::LocalTime)}. + \sa birthTime(), lastModified(), metadataChangeTime(), fileTime() */ /*! - \since 5.10 + \fn QDateTime QFileInfo::lastRead(const QTimeZone &tz) const - Returns the file time specified by \a time. If the time cannot be - determined, an invalid date time is returned. + Returns the date and time when the file was last read (accessed). + + \include qfileinfo.cpp file-times-in-time-zone + + On platforms where this information is not available, returns the same + time as lastModified(). If the file is a symlink, the time of the target file is returned (not the symlink). - \sa QFile::FileTime, QDateTime::isValid() + \since 6.6 + \sa birthTime(const QTimeZone &), lastModified(const QTimeZone &), metadataChangeTime(const QTimeZone &), fileTime(QFile::FileTime, const QTimeZone &) */ -QDateTime QFileInfo::fileTime(QFile::FileTime time) const + +#if QT_VERSION < QT_VERSION_CHECK(7, 0, 0) && !defined(QT_BOOTSTRAPPED) +/*! + Returns the file time specified by \a time. + + If the time cannot be determined, an invalid date time is returned. + + If the file is a symlink, the time of the target file is returned + (not the symlink). + + This function overloads + \l{QFileInfo::fileTime(QFile::FileTime, const QTimeZone &)}, + and returns the same as \c{fileTime(time, QTimeZone::LocalTime)}. + + \since 5.10 + \sa birthTime(), lastModified(), lastRead(), metadataChangeTime() +*/ +QDateTime QFileInfo::fileTime(QFile::FileTime time) const { + return fileTime(time, QTimeZone::LocalTime); +} +#endif + +/*! + Returns the file time specified by \a time. + +//! [file-times-in-time-zone] + The returned time is in the time zone specified by \a tz. For example, + you can use QTimeZone::LocalTime or QTimeZone::UTC to get the time in + the Local time zone or UTC, respectively. Since native file system API + typically uses UTC, using QTimeZone::UTC is often faster, as it does not + require any conversions. +//! [file-times-in-time-zone] + + If the time cannot be determined, an invalid date time is returned. + + If the file is a symlink, the time of the target file is returned + (not the symlink). + + \since 6.6 + \sa birthTime(const QTimeZone &), lastModified(const QTimeZone &), lastRead(const QTimeZone &), metadataChangeTime(const QTimeZone &), QDateTime::isValid() +*/ +QDateTime QFileInfo::fileTime(QFile::FileTime time, const QTimeZone &tz) const { static_assert(int(QFile::FileAccessTime) == int(QAbstractFileEngine::AccessTime)); static_assert(int(QFile::FileBirthTime) == int(QAbstractFileEngine::BirthTime)); @@ -1508,10 +1664,10 @@ QDateTime QFileInfo::fileTime(QFile::FileTime time) const break; } - return d->checkAttribute( - flag, - [=]() { return d->metaData.fileTime(fetime).toLocalTime(); }, - [=]() { return d->getFileTime(fetime).toLocalTime(); }); + auto fsLambda = [d, fetime]() { return d->metaData.fileTime(fetime); }; + auto engineLambda = [d, fetime]() { return d->getFileTime(fetime); }; + const QDateTime dt = d->checkAttribute(flag, fsLambda, engineLambda); + return dt.toTimeZone(tz); } /*! @@ -1663,6 +1819,13 @@ QDebug operator<<(QDebug dbg, const QFileInfo &fi) Returns symLinkTarget() as a \c{std::filesystem::path}. \sa symLinkTarget() */ +/*! + \fn std::filesystem::path QFileInfo::filesystemReadSymLink() const + \since 6.6 + + Returns readSymLink() as a \c{std::filesystem::path}. + \sa readSymLink() +*/ /*! \fn std::filesystem::path QFileInfo::filesystemJunctionTarget() const \since 6.2 diff --git a/src/corelib/io/qfileinfo.h b/src/corelib/io/qfileinfo.h index d0ec231f..15a119cb 100644 --- a/src/corelib/io/qfileinfo.h +++ b/src/corelib/io/qfileinfo.h @@ -9,6 +9,7 @@ #include #include #include +#include QT_BEGIN_NAMESPACE @@ -126,12 +127,16 @@ public: bool isBundle() const; QString symLinkTarget() const; + QString readSymLink() const; QString junctionTarget() const; #if QT_CONFIG(cxx17_filesystem) || defined(Q_QDOC) std::filesystem::path filesystemSymLinkTarget() const { return QtPrivate::toFilesystemPath(symLinkTarget()); } + std::filesystem::path filesystemReadSymLink() const + { return QtPrivate::toFilesystemPath(readSymLink()); } + std::filesystem::path filesystemJunctionTarget() const { return QtPrivate::toFilesystemPath(junctionTarget()); } #endif // QT_CONFIG(cxx17_filesystem) @@ -152,6 +157,12 @@ public: QDateTime lastRead() const { return fileTime(QFile::FileAccessTime); } QDateTime fileTime(QFile::FileTime time) const; + QDateTime birthTime(const QTimeZone &tz) const { return fileTime(QFile::FileBirthTime, tz); } + QDateTime metadataChangeTime(const QTimeZone &tz) const { return fileTime(QFile::FileMetadataChangeTime, tz); } + QDateTime lastModified(const QTimeZone &tz) const { return fileTime(QFile::FileModificationTime, tz); } + QDateTime lastRead(const QTimeZone &tz) const { return fileTime(QFile::FileAccessTime, tz); } + QDateTime fileTime(QFile::FileTime time, const QTimeZone &tz) const; + bool caching() const; void setCaching(bool on); void stat(); diff --git a/src/corelib/io/qfilesystemengine_p.h b/src/corelib/io/qfilesystemengine_p.h index c54efa7f..c1dddb2e 100644 --- a/src/corelib/io/qfilesystemengine_p.h +++ b/src/corelib/io/qfilesystemengine_p.h @@ -67,6 +67,8 @@ public: } static QFileSystemEntry getLinkTarget(const QFileSystemEntry &link, QFileSystemMetaData &data); + static QFileSystemEntry getRawLinkPath(const QFileSystemEntry &link, + QFileSystemMetaData &data); static QFileSystemEntry getJunctionTarget(const QFileSystemEntry &link, QFileSystemMetaData &data); static QFileSystemEntry canonicalName(const QFileSystemEntry &entry, QFileSystemMetaData &data); static QFileSystemEntry absoluteName(const QFileSystemEntry &entry); diff --git a/src/corelib/io/qfilesystemengine_unix.cpp b/src/corelib/io/qfilesystemengine_unix.cpp index 277e10d8..d94cd452 100644 --- a/src/corelib/io/qfilesystemengine_unix.cpp +++ b/src/corelib/io/qfilesystemengine_unix.cpp @@ -23,6 +23,7 @@ #include #include +#include #include // for std::unique_ptr #if __has_include() @@ -32,7 +33,7 @@ # define _PATH_TMP "/tmp" #endif -#if defined(Q_OS_MAC) +#if defined(Q_OS_DARWIN) # include # include #endif @@ -160,16 +161,39 @@ static bool isPackage(const QFileSystemMetaData &data, const QFileSystemEntry &e namespace { namespace GetFileTimes { -qint64 timespecToMSecs(const timespec &spec) +qint64 time_t_toMsecs(time_t t) { - return (qint64(spec.tv_sec) * 1000) + (spec.tv_nsec / 1000000); + using namespace std::chrono; + return milliseconds{seconds{t}}.count(); } // fallback set -[[maybe_unused]] qint64 atime(const QT_STATBUF &statBuffer, ulong) { return qint64(statBuffer.st_atime) * 1000; } -[[maybe_unused]] qint64 birthtime(const QT_STATBUF &, ulong) { return Q_INT64_C(0); } -[[maybe_unused]] qint64 ctime(const QT_STATBUF &statBuffer, ulong) { return qint64(statBuffer.st_ctime) * 1000; } -[[maybe_unused]] qint64 mtime(const QT_STATBUF &statBuffer, ulong) { return qint64(statBuffer.st_mtime) * 1000; } +[[maybe_unused]] qint64 atime(const QT_STATBUF &statBuffer, ulong) +{ + return time_t_toMsecs(statBuffer.st_atime); +} +[[maybe_unused]] qint64 birthtime(const QT_STATBUF &, ulong) +{ + return Q_INT64_C(0); +} +[[maybe_unused]] qint64 ctime(const QT_STATBUF &statBuffer, ulong) +{ + return time_t_toMsecs(statBuffer.st_ctime); +} +[[maybe_unused]] qint64 mtime(const QT_STATBUF &statBuffer, ulong) +{ + return time_t_toMsecs(statBuffer.st_mtime); +} + +// T is either a stat.timespec or statx.statx_timestamp, +// both have tv_sec and tv_nsec members +template +qint64 timespecToMSecs(const T &spec) +{ + using namespace std::chrono; + const nanoseconds nsecs = seconds{spec.tv_sec} + nanoseconds{spec.tv_nsec}; + return duration_cast(nsecs).count(); +} // Xtim, POSIX.1-2008 template @@ -304,17 +328,12 @@ inline void QFileSystemMetaData::fillFromStatxBuf(const struct statx &statxBuffe size_ = qint64(statxBuffer.stx_size); // Times - auto toMSecs = [](struct statx_timestamp ts) - { - return qint64(ts.tv_sec) * 1000 + (ts.tv_nsec / 1000000); - }; - accessTime_ = toMSecs(statxBuffer.stx_atime); - metadataChangeTime_ = toMSecs(statxBuffer.stx_ctime); - modificationTime_ = toMSecs(statxBuffer.stx_mtime); - if (statxBuffer.stx_mask & STATX_BTIME) - birthTime_ = toMSecs(statxBuffer.stx_btime); - else - birthTime_ = 0; + using namespace GetFileTimes; + accessTime_ = timespecToMSecs(statxBuffer.stx_atime); + metadataChangeTime_ = timespecToMSecs(statxBuffer.stx_ctime); + modificationTime_ = timespecToMSecs(statxBuffer.stx_mtime); + const bool birthMask = statxBuffer.stx_mask & STATX_BTIME; + birthTime_ = birthMask ? timespecToMSecs(statxBuffer.stx_btime) : 0; userId_ = statxBuffer.stx_uid; groupId_ = statxBuffer.stx_gid; @@ -610,12 +629,22 @@ QFileSystemEntry QFileSystemEngine::getLinkTarget(const QFileSystemEntry &link, return QFileSystemEntry(); } +//static +QFileSystemEntry QFileSystemEngine::getRawLinkPath(const QFileSystemEntry &link, + QFileSystemMetaData &data) +{ + Q_UNUSED(data) + const QByteArray path = qt_readlink(link.nativeFilePath().constData()); + const QString ret = QFile::decodeName(path); + return QFileSystemEntry(ret); +} + //static QFileSystemEntry QFileSystemEngine::canonicalName(const QFileSystemEntry &entry, QFileSystemMetaData &data) { Q_CHECK_FILE_NAME(entry, entry); -#if !defined(Q_OS_MAC) && !defined(Q_OS_QNX) && !defined(Q_OS_ANDROID) && !defined(Q_OS_HAIKU) && _POSIX_VERSION < 200809L +#if !defined(Q_OS_DARWIN) && !defined(Q_OS_QNX) && !defined(Q_OS_ANDROID) && !defined(Q_OS_HAIKU) && _POSIX_VERSION < 200809L // realpath(X,0) is not supported Q_UNUSED(data); return QFileSystemEntry(slowCanonicalized(absoluteName(entry).filePath())); @@ -735,7 +764,7 @@ QByteArray QFileSystemEngine::id(int fd) QString QFileSystemEngine::resolveUserName(uint userId) { #if QT_CONFIG(thread) && defined(_POSIX_THREAD_SAFE_FUNCTIONS) && !defined(Q_OS_OPENBSD) - int size_max = sysconf(_SC_GETPW_R_SIZE_MAX); + long size_max = sysconf(_SC_GETPW_R_SIZE_MAX); if (size_max == -1) size_max = 1024; QVarLengthArray buf(size_max); @@ -761,7 +790,7 @@ QString QFileSystemEngine::resolveUserName(uint userId) QString QFileSystemEngine::resolveGroupName(uint groupId) { #if QT_CONFIG(thread) && defined(_POSIX_THREAD_SAFE_FUNCTIONS) && !defined(Q_OS_OPENBSD) - int size_max = sysconf(_SC_GETPW_R_SIZE_MAX); + long size_max = sysconf(_SC_GETPW_R_SIZE_MAX); if (size_max == -1) size_max = 1024; QVarLengthArray buf(size_max); @@ -777,7 +806,7 @@ QString QFileSystemEngine::resolveGroupName(uint groupId) struct group entry; // Some large systems have more members than the POSIX max size // Loop over by doubling the buffer size (upper limit 250k) - for (unsigned size = size_max; size < 256000; size += size) + for (long size = size_max; size < 256000; size += size) { buf.resize(size); // ERANGE indicates that the buffer was too small @@ -1075,7 +1104,7 @@ static bool createDirectoryWithParents(const QByteArray &nativeName, mode_t mode return false; // mkdir failed because the parent dir doesn't exist, so try to create it - int slash = nativeName.lastIndexOf('/'); + qsizetype slash = nativeName.lastIndexOf('/'); if (slash < 1) return false; @@ -1118,7 +1147,7 @@ bool QFileSystemEngine::removeDirectory(const QFileSystemEntry &entry, bool remo if (removeEmptyParents) { QString dirName = QDir::cleanPath(entry.filePath()); - for (int oldslash = 0, slash=dirName.size(); slash > 0; oldslash = slash) { + for (qsizetype oldslash = 0, slash=dirName.size(); slash > 0; oldslash = slash) { const QByteArray chunk = QFile::encodeName(dirName.left(slash)); QT_STATBUF st; if (QT_STAT(chunk.constData(), &st) != -1) { @@ -1523,19 +1552,13 @@ bool QFileSystemEngine::setFileTime(int fd, const QDateTime &newDate, QAbstractF } #if QT_CONFIG(futimens) - struct timespec ts[2]; + // UTIME_OMIT: leave file timestamp unchanged + struct timespec ts[2] = {{0, UTIME_OMIT}, {0, UTIME_OMIT}}; - ts[0].tv_sec = ts[1].tv_sec = 0; - ts[0].tv_nsec = ts[1].tv_nsec = UTIME_OMIT; - - const qint64 msecs = newDate.toMSecsSinceEpoch(); - - if (time == QAbstractFileEngine::AccessTime) { - ts[0].tv_sec = msecs / 1000; - ts[0].tv_nsec = (msecs % 1000) * 1000000; - } else if (time == QAbstractFileEngine::ModificationTime) { - ts[1].tv_sec = msecs / 1000; - ts[1].tv_nsec = (msecs % 1000) * 1000000; + if (time == QAbstractFileEngine::AccessTime || time == QAbstractFileEngine::ModificationTime) { + const int idx = time == QAbstractFileEngine::AccessTime ? 0 : 1; + const std::chrono::milliseconds msecs{newDate.toMSecsSinceEpoch()}; + ts[idx] = durationToTimespec(msecs); } if (futimens(fd, ts) == -1) { diff --git a/src/corelib/io/qfilesystemengine_win.cpp b/src/corelib/io/qfilesystemengine_win.cpp index 6d085d98..49f375c4 100644 --- a/src/corelib/io/qfilesystemengine_win.cpp +++ b/src/corelib/io/qfilesystemengine_win.cpp @@ -381,8 +381,54 @@ constexpr QFileDevice::Permissions toSpecificPermissions(PermissionTag tag, } // anonymous namespace #endif // QT_CONFIG(fslibs) +#if QT_DEPRECATED_SINCE(6,6) +int qt_ntfs_permission_lookup = 0; +#endif -Q_CORE_EXPORT int qt_ntfs_permission_lookup = 0; +static QBasicAtomicInt qt_ntfs_permission_lookup_v2 = Q_BASIC_ATOMIC_INITIALIZER(0); + +QT_WARNING_PUSH +QT_WARNING_DISABLE_DEPRECATED + +/*! + \internal + + Returns true if the check was previously enabled. +*/ + +bool qEnableNtfsPermissionChecks() noexcept +{ + return qt_ntfs_permission_lookup_v2.fetchAndAddRelaxed(1) +QT_IF_DEPRECATED_SINCE(6, 6, /*nothing*/, + qt_ntfs_permission_lookup) + != 0; +} + +/*! + \internal + + Returns true if the check is disabled, i.e. there are no more users. +*/ + +bool qDisableNtfsPermissionChecks() noexcept +{ + return qt_ntfs_permission_lookup_v2.fetchAndSubRelaxed(1) +QT_IF_DEPRECATED_SINCE(6, 6, /*nothing*/, + qt_ntfs_permission_lookup) + == 1; +} + +/*! + \internal + + Returns true if the check is enabled. +*/ + +bool qAreNtfsPermissionChecksEnabled() noexcept +{ + return qt_ntfs_permission_lookup_v2.loadRelaxed() +QT_IF_DEPRECATED_SINCE(6, 6, /*nothing*/, + qt_ntfs_permission_lookup) + ; +} +QT_WARNING_POP /*! \class QNativeFilePermissions @@ -855,6 +901,18 @@ void QFileSystemEngine::clearWinStatData(QFileSystemMetaData &data) //static QFileSystemEntry QFileSystemEngine::getLinkTarget(const QFileSystemEntry &link, QFileSystemMetaData &data) +{ + QFileSystemEntry ret = getRawLinkPath(link, data); + if (!ret.isEmpty() && ret.isRelative()) { + QString target = absoluteName(link).path() + u'/' + ret.filePath(); + ret = QFileSystemEntry(QDir::cleanPath(target)); + } + return ret; +} + +//static +QFileSystemEntry QFileSystemEngine::getRawLinkPath(const QFileSystemEntry &link, + QFileSystemMetaData &data) { Q_CHECK_FILE_NAME(link, link); @@ -866,12 +924,7 @@ QFileSystemEntry QFileSystemEngine::getLinkTarget(const QFileSystemEntry &link, target = readLink(link); else if (data.isLink()) target = readSymLink(link); - QFileSystemEntry ret(target); - if (!target.isEmpty() && ret.isRelative()) { - target.prepend(absoluteName(link).path() + u'/'); - ret = QFileSystemEntry(QDir::cleanPath(target)); - } - return ret; + return QFileSystemEntry(target); } //static @@ -1071,8 +1124,7 @@ QString QFileSystemEngine::owner(const QFileSystemEntry &entry, QAbstractFileEng { QString name; #if QT_CONFIG(fslibs) - extern int qt_ntfs_permission_lookup; - if (qt_ntfs_permission_lookup > 0) { + if (qAreNtfsPermissionChecksEnabled()) { initGlobalSid(); { PSID pOwner = 0; @@ -1126,7 +1178,7 @@ bool QFileSystemEngine::fillPermissions(const QFileSystemEntry &entry, QFileSyst QFileSystemMetaData::MetaDataFlags what) { #if QT_CONFIG(fslibs) - if (qt_ntfs_permission_lookup > 0) { + if (qAreNtfsPermissionChecksEnabled()) { initGlobalSid(); QString fname = entry.nativeFilePath(); diff --git a/src/corelib/io/qfilesystemwatcher_inotify.cpp b/src/corelib/io/qfilesystemwatcher_inotify.cpp index 3b53b490..a37eecfe 100644 --- a/src/corelib/io/qfilesystemwatcher_inotify.cpp +++ b/src/corelib/io/qfilesystemwatcher_inotify.cpp @@ -334,7 +334,7 @@ void QInotifyFileSystemWatcherEngine::readFromInotify() return; QVarLengthArray buffer(buffSize); - buffSize = read(inotifyFd, buffer.data(), buffSize); + buffSize = int(read(inotifyFd, buffer.data(), buffSize)); char *at = buffer.data(); char * const end = at + buffSize; diff --git a/src/corelib/io/qfilesystemwatcher_polling.cpp b/src/corelib/io/qfilesystemwatcher_polling.cpp index 03425ac2..a74205b7 100644 --- a/src/corelib/io/qfilesystemwatcher_polling.cpp +++ b/src/corelib/io/qfilesystemwatcher_polling.cpp @@ -2,16 +2,21 @@ // SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only #include "qfilesystemwatcher_polling_p.h" + #include #include +#include + +using namespace std::chrono_literals; + QT_BEGIN_NAMESPACE +static constexpr auto PollingInterval = 1s; + QPollingFileSystemWatcherEngine::QPollingFileSystemWatcherEngine(QObject *parent) - : QFileSystemWatcherEngine(parent), - timer(this) + : QFileSystemWatcherEngine(parent) { - connect(&timer, SIGNAL(timeout()), SLOT(timeout())); } QStringList QPollingFileSystemWatcherEngine::addPaths(const QStringList &paths, @@ -43,7 +48,7 @@ QStringList QPollingFileSystemWatcherEngine::addPaths(const QStringList &paths, if ((!this->files.isEmpty() || !this->directories.isEmpty()) && !timer.isActive()) { - timer.start(PollingInterval); + timer.start(PollingInterval, this); } return unhandled; @@ -72,8 +77,11 @@ QStringList QPollingFileSystemWatcherEngine::removePaths(const QStringList &path return unhandled; } -void QPollingFileSystemWatcherEngine::timeout() +void QPollingFileSystemWatcherEngine::timerEvent(QTimerEvent *e) { + if (e->timerId() != timer.timerId()) + return QFileSystemWatcherEngine::timerEvent(e); + for (auto it = files.begin(), end = files.end(); it != end; /*erasing*/) { QString path = it.key(); QFileInfo fi(path); diff --git a/src/corelib/io/qfilesystemwatcher_polling_p.h b/src/corelib/io/qfilesystemwatcher_polling_p.h index a12ff4b5..b65ff055 100644 --- a/src/corelib/io/qfilesystemwatcher_polling_p.h +++ b/src/corelib/io/qfilesystemwatcher_polling_p.h @@ -15,11 +15,11 @@ // We mean it. // +#include #include #include #include #include -#include #include #include "qfilesystemwatcher_p.h" @@ -27,8 +27,6 @@ QT_REQUIRE_CONFIG(filesystemwatcher); QT_BEGIN_NAMESPACE -enum { PollingInterval = 1000 }; - class QPollingFileSystemWatcherEngine : public QFileSystemWatcherEngine { Q_OBJECT @@ -46,7 +44,7 @@ class QPollingFileSystemWatcherEngine : public QFileSystemWatcherEngine : ownerId(fileInfo.ownerId()), groupId(fileInfo.groupId()), permissions(fileInfo.permissions()), - lastModified(fileInfo.lastModified()) + lastModified(fileInfo.lastModified(QTimeZone::UTC)) { if (fileInfo.isDir()) { entries = fileInfo.absoluteDir().entryList(QDir::AllEntries); @@ -65,7 +63,7 @@ class QPollingFileSystemWatcherEngine : public QFileSystemWatcherEngine return (ownerId != fileInfo.ownerId() || groupId != fileInfo.groupId() || permissions != fileInfo.permissions() - || lastModified != fileInfo.lastModified()); + || lastModified != fileInfo.lastModified(QTimeZone::UTC)); } }; @@ -77,11 +75,11 @@ public: QStringList addPaths(const QStringList &paths, QStringList *files, QStringList *directories) override; QStringList removePaths(const QStringList &paths, QStringList *files, QStringList *directories) override; -private Q_SLOTS: - void timeout(); +private: + void timerEvent(QTimerEvent *) final; private: - QTimer timer; + QBasicTimer timer; }; QT_END_NAMESPACE diff --git a/src/corelib/io/qfsfileengine.cpp b/src/corelib/io/qfsfileengine.cpp index ffc4878e..0ff7da1a 100644 --- a/src/corelib/io/qfsfileengine.cpp +++ b/src/corelib/io/qfsfileengine.cpp @@ -18,7 +18,7 @@ #endif #include #include -#if defined(Q_OS_MAC) +#if defined(Q_OS_DARWIN) # include #endif diff --git a/src/corelib/io/qfsfileengine_unix.cpp b/src/corelib/io/qfsfileengine_unix.cpp index fb08385c..217474cb 100644 --- a/src/corelib/io/qfsfileengine_unix.cpp +++ b/src/corelib/io/qfsfileengine_unix.cpp @@ -450,6 +450,12 @@ QString QFSFileEngine::fileName(FileName file) const return entry.filePath(); } return QString(); + case RawLinkPath: + if (d->isSymlink()) { + QFileSystemEntry entry = QFileSystemEngine::getRawLinkPath(d->fileEntry, d->metaData); + return entry.filePath(); + } + return QString(); case JunctionName: return QString(); case DefaultName: diff --git a/src/corelib/io/qfsfileengine_win.cpp b/src/corelib/io/qfsfileengine_win.cpp index 1030c559..daa43262 100644 --- a/src/corelib/io/qfsfileengine_win.cpp +++ b/src/corelib/io/qfsfileengine_win.cpp @@ -619,6 +619,8 @@ QString QFSFileEngine::fileName(FileName file) const } case AbsoluteLinkTarget: return QFileSystemEngine::getLinkTarget(d->fileEntry, d->metaData).filePath(); + case RawLinkPath: + return QFileSystemEngine::getRawLinkPath(d->fileEntry, d->metaData).filePath(); case BundleName: return QString(); case JunctionName: diff --git a/src/corelib/io/qiodevice.cpp b/src/corelib/io/qiodevice.cpp index 29752c8e..81c2bc16 100644 --- a/src/corelib/io/qiodevice.cpp +++ b/src/corelib/io/qiodevice.cpp @@ -1500,7 +1500,7 @@ qint64 QIODevice::readLineData(char *data, qint64 maxSize) Q_D(QIODevice); qint64 readSoFar = 0; char c; - int lastReadReturn = 0; + qint64 lastReadReturn = 0; d->baseReadLineDataCalled = true; while (readSoFar < maxSize && (lastReadReturn = read(&c, 1)) == 1) { diff --git a/src/corelib/io/qipaddress.cpp b/src/corelib/io/qipaddress.cpp index 9956f037..c2b274f8 100644 --- a/src/corelib/io/qipaddress.cpp +++ b/src/corelib/io/qipaddress.cpp @@ -61,7 +61,7 @@ static bool parseIp4Internal(IPv4Address &address, const char *ptr, bool acceptL return false; auto [ll, used] = qstrntoull(ptr, stop - ptr, 0); - quint32 x = ll; + const quint32 x = quint32(ll); if (used <= 0 || ll != x) return false; diff --git a/src/corelib/io/qlockfile.cpp b/src/corelib/io/qlockfile.cpp index 522bb081..0eb6acb6 100644 --- a/src/corelib/io/qlockfile.cpp +++ b/src/corelib/io/qlockfile.cpp @@ -60,7 +60,7 @@ static QString machineName() When protecting for a short-term operation, it is acceptable to call lock() and wait until any running operation finishes. When protecting a resource over a long time, however, the application should always - call setStaleLockTime(0) and then tryLock() with a short timeout, in order to + call setStaleLockTime(0ms) and then tryLock() with a short timeout, in order to warn the user that the resource is locked. If the process holding the lock crashes, the lock file stays on disk and can prevent @@ -138,20 +138,24 @@ QString QLockFile::fileName() const meanwhile, so one way to detect a stale lock file is by the fact that it has been around for a long time. + This is an overloaded function, equivalent to calling: + \code + setStaleLockTime(std::chrono::milliseconds{staleLockTime}); + \endcode + \sa staleLockTime() */ void QLockFile::setStaleLockTime(int staleLockTime) { - Q_D(QLockFile); - d->staleLockTime = staleLockTime; + setStaleLockTime(std::chrono::milliseconds{staleLockTime}); } -/*! \fn void QLockFile::setStaleLockTime(std::chrono::milliseconds value) - \overload +/*! \since 6.2 - Sets the interval after which a lock file is considered stale to \a value. - The default value is 30 seconds. + Sets the interval after which a lock file is considered stale to \a staleLockTime. + The default value is 30s. + If your application typically keeps the file locked for more than 30 seconds (for instance while saving megabytes of data for 2 minutes), you should set a bigger value using setStaleLockTime(). @@ -164,6 +168,11 @@ void QLockFile::setStaleLockTime(int staleLockTime) \sa staleLockTime() */ +void QLockFile::setStaleLockTime(std::chrono::milliseconds staleLockTime) +{ + Q_D(QLockFile); + d->staleLockTime = staleLockTime; +} /*! Returns the time in milliseconds after which @@ -173,8 +182,7 @@ void QLockFile::setStaleLockTime(int staleLockTime) */ int QLockFile::staleLockTime() const { - Q_D(const QLockFile); - return d->staleLockTime; + return int(staleLockTimeAsDuration().count()); } /*! \fn std::chrono::milliseconds QLockFile::staleLockTimeAsDuration() const @@ -186,6 +194,11 @@ int QLockFile::staleLockTime() const \sa setStaleLockTime() */ +std::chrono::milliseconds QLockFile::staleLockTimeAsDuration() const +{ + Q_D(const QLockFile); + return d->staleLockTime; +} /*! Returns \c true if the lock was acquired by this QLockFile instance, @@ -216,7 +229,7 @@ bool QLockFile::isLocked() const */ bool QLockFile::lock() { - return tryLock(-1); + return tryLock(std::chrono::milliseconds::max()); } /*! @@ -241,48 +254,10 @@ bool QLockFile::lock() */ bool QLockFile::tryLock(int timeout) { - Q_D(QLockFile); - QDeadlineTimer timer(qMax(timeout, -1)); // QDT only takes -1 as "forever" - int sleepTime = 100; - forever { - d->lockError = d->tryLock_sys(); - switch (d->lockError) { - case NoError: - d->isLocked = true; - return true; - case PermissionError: - case UnknownError: - return false; - case LockFailedError: - if (!d->isLocked && d->isApparentlyStale()) { - if (Q_UNLIKELY(QFileInfo(d->fileName).lastModified() > QDateTime::currentDateTimeUtc())) - qInfo("QLockFile: Lock file '%ls' has a modification time in the future", qUtf16Printable(d->fileName)); - // Stale lock from another thread/process - // Ensure two processes don't remove it at the same time - QLockFile rmlock(d->fileName + ".rmlock"_L1); - if (rmlock.tryLock()) { - if (d->isApparentlyStale() && d->removeStaleLock()) - continue; - } - } - break; - } - - int remainingTime = timer.remainingTime(); - if (remainingTime == 0) - return false; - else if (uint(sleepTime) > uint(remainingTime)) - sleepTime = remainingTime; - - QThread::msleep(sleepTime); - if (sleepTime < 5 * 1000) - sleepTime *= 2; - } - // not reached - return false; + return tryLock(std::chrono::milliseconds{ timeout }); } -/*! \fn bool QLockFile::tryLock(std::chrono::milliseconds timeout) +/*! \overload \since 6.2 @@ -300,6 +275,54 @@ bool QLockFile::tryLock(int timeout) \sa lock(), unlock() */ +bool QLockFile::tryLock(std::chrono::milliseconds timeout) +{ + using namespace std::chrono_literals; + using Msec = std::chrono::milliseconds; + + Q_D(QLockFile); + + QDeadlineTimer timer(timeout < 0ms ? Msec::max() : timeout); + + Msec sleepTime = 100ms; + while (true) { + d->lockError = d->tryLock_sys(); + switch (d->lockError) { + case NoError: + d->isLocked = true; + return true; + case PermissionError: + case UnknownError: + return false; + case LockFailedError: + if (!d->isLocked && d->isApparentlyStale()) { + if (Q_UNLIKELY(QFileInfo(d->fileName).lastModified(QTimeZone::UTC) > QDateTime::currentDateTimeUtc())) + qInfo("QLockFile: Lock file '%ls' has a modification time in the future", qUtf16Printable(d->fileName)); + // Stale lock from another thread/process + // Ensure two processes don't remove it at the same time + QLockFile rmlock(d->fileName + ".rmlock"_L1); + if (rmlock.tryLock()) { + if (d->isApparentlyStale() && d->removeStaleLock()) + continue; + } + } + break; + } + + auto remainingTime = std::chrono::duration_cast(timer.remainingTimeAsDuration()); + if (remainingTime == 0ms) + return false; + + if (sleepTime > remainingTime) + sleepTime = remainingTime; + + QThread::sleep(sleepTime); + if (sleepTime < 5s) + sleepTime *= 2; + } + // not reached + return false; +} /*! \fn void QLockFile::unlock() @@ -413,8 +436,10 @@ bool QLockFilePrivate::isApparentlyStale() const } } - const qint64 age = QFileInfo(fileName).lastModified().msecsTo(QDateTime::currentDateTimeUtc()); - return staleLockTime > 0 && qAbs(age) > staleLockTime; + const QDateTime lastMod = QFileInfo(fileName).lastModified(QTimeZone::UTC); + using namespace std::chrono; + const milliseconds age{lastMod.msecsTo(QDateTime::currentDateTimeUtc())}; + return staleLockTime > 0ms && abs(age) > staleLockTime; } /*! diff --git a/src/corelib/io/qlockfile.h b/src/corelib/io/qlockfile.h index b63194dc..af481ab5 100644 --- a/src/corelib/io/qlockfile.h +++ b/src/corelib/io/qlockfile.h @@ -22,20 +22,16 @@ public: QString fileName() const; bool lock(); - bool tryLock(int timeout = 0); + bool tryLock(int timeout); void unlock(); void setStaleLockTime(int); int staleLockTime() const; - bool tryLock(std::chrono::milliseconds timeout) { return tryLock(int(timeout.count())); } + bool tryLock(std::chrono::milliseconds timeout = std::chrono::milliseconds::zero()); - void setStaleLockTime(std::chrono::milliseconds value) { setStaleLockTime(int(value.count())); } - - std::chrono::milliseconds staleLockTimeAsDuration() const - { - return std::chrono::milliseconds(staleLockTime()); - } + void setStaleLockTime(std::chrono::milliseconds value); + std::chrono::milliseconds staleLockTimeAsDuration() const; bool isLocked() const; bool getLockInfo(qint64 *pid, QString *hostname, QString *appname) const; diff --git a/src/corelib/io/qlockfile_p.h b/src/corelib/io/qlockfile_p.h index 42aa5ecc..299b13b2 100644 --- a/src/corelib/io/qlockfile_p.h +++ b/src/corelib/io/qlockfile_p.h @@ -32,15 +32,7 @@ class QLockFilePrivate { public: QLockFilePrivate(const QString &fn) - : fileName(fn), -#ifdef Q_OS_WIN - fileHandle(INVALID_HANDLE_VALUE), -#else - fileHandle(-1), -#endif - staleLockTime(30 * 1000), // 30 seconds - lockError(QLockFile::NoError), - isLocked(false) + : fileName(fn) { } QLockFile::LockError tryLock_sys(); @@ -55,14 +47,16 @@ public: static bool isProcessRunning(qint64 pid, const QString &appname); QString fileName; + #ifdef Q_OS_WIN - Qt::HANDLE fileHandle; + Qt::HANDLE fileHandle = INVALID_HANDLE_VALUE; #else - int fileHandle; + int fileHandle = -1; #endif - int staleLockTime; // "int milliseconds" is big enough for 24 days - QLockFile::LockError lockError; - bool isLocked; + + std::chrono::milliseconds staleLockTime = std::chrono::seconds{30}; + QLockFile::LockError lockError = QLockFile::NoError; + bool isLocked = false; static int getLockFileHandle(QLockFile *f) { diff --git a/src/corelib/io/qlockfile_unix.cpp b/src/corelib/io/qlockfile_unix.cpp index 34aa3b65..47aff8b9 100644 --- a/src/corelib/io/qlockfile_unix.cpp +++ b/src/corelib/io/qlockfile_unix.cpp @@ -170,7 +170,7 @@ bool QLockFilePrivate::removeStaleLock() bool QLockFilePrivate::isProcessRunning(qint64 pid, const QString &appname) { - if (::kill(pid, 0) == -1 && errno == ESRCH) + if (::kill(pid_t(pid), 0) == -1 && errno == ESRCH) return false; // PID doesn't exist anymore const QString processName = processNameByPid(pid); diff --git a/src/corelib/io/qloggingregistry.cpp b/src/corelib/io/qloggingregistry.cpp index 98057222..3c835cce 100644 --- a/src/corelib/io/qloggingregistry.cpp +++ b/src/corelib/io/qloggingregistry.cpp @@ -67,7 +67,7 @@ int QLoggingRule::pass(QLatin1StringView cat, QtMsgType msgType) const return 0; } - const int idx = cat.indexOf(category); + const qsizetype idx = cat.indexOf(category); if (idx >= 0) { if (flags == MidFilter) { // matches somewhere @@ -194,7 +194,7 @@ void QLoggingSettingsParser::parseNextLine(QStringView line) } if (m_inRulesSection) { - int equalPos = line.indexOf(u'='); + const qsizetype equalPos = line.indexOf(u'='); if (equalPos != -1) { if (line.lastIndexOf(u'=') == equalPos) { const auto key = line.left(equalPos).trimmed(); diff --git a/src/corelib/io/qprocess.cpp b/src/corelib/io/qprocess.cpp index 67366a23..a7107507 100644 --- a/src/corelib/io/qprocess.cpp +++ b/src/corelib/io/qprocess.cpp @@ -72,7 +72,7 @@ QProcessEnvironment QProcessEnvironmentPrivate::fromList(const QStringList &list QStringList::ConstIterator it = list.constBegin(), end = list.constEnd(); for ( ; it != end; ++it) { - int pos = it->indexOf(u'=', 1); + const qsizetype pos = it->indexOf(u'=', 1); if (pos < 1) continue; @@ -804,6 +804,76 @@ void QProcessPrivate::Channel::clear() \sa QProcess::CreateProcessArgumentModifier */ +/*! + \class QProcess::UnixProcessParameters + \inmodule QtCore + \note This struct is only available on Unix platforms + \since 6.6 + + This struct can be used to pass extra, Unix-specific configuration for the + child process using QProcess::setUnixProcessParameters(). + + Its members are: + \list + \li UnixProcessParameters::flags Flags, see QProcess::UnixProcessFlags + \li UnixProcessParameters::lowestFileDescriptorToClose The lowest file + descriptor to close. + \endlist + + When the QProcess::UnixProcessFlags::CloseFileDescriptors flag is set in + the \c flags field, QProcess closes the application's open file descriptors + before executing the child process. The descriptors 0, 1, and 2 (that is, + \c stdin, \c stdout, and \c stderr) are left alone, along with the ones + numbered lower than the value of the \c lowestFileDescriptorToClose field. + + All of the settings above can also be manually achieved by calling the + respective POSIX function from a handler set with + QProcess::setChildProcessModifier(). This structure allows QProcess to deal + with any platform-specific differences, benefit from certain optimizations, + and reduces code duplication. Moreover, if any of those functions fail, + QProcess will enter QProcess::FailedToStart state, while the child process + modifier callback is not allowed to fail. + + \sa QProcess::setUnixProcessParameters(), QProcess::setChildProcessModifier() +*/ + +/*! + \enum QProcess::UnixProcessFlag + \since 6.6 + + These flags can be used in the \c flags field of \l UnixProcessParameters. + + \value CloseFileDescriptors Close all file descriptors above the threshold + defined by \c lowestFileDescriptorToClose, preventing any currently + open descriptor in the parent process from accidentally leaking to the + child. The \c stdin, \c stdout, and \c stderr file descriptors are + never closed. + + \value IgnoreSigPipe Always sets the \c SIGPIPE signal to ignored + (\c SIG_IGN), even if the \c ResetSignalHandlers flag was set. By + default, if the child attempts to write to its standard output or + standard error after the respective channel was closed with + QProcess::closeReadChannel(), it would get the \c SIGPIPE signal and + terminate immediately; with this flag, the write operation fails + without a signal and the child may continue executing. + + \value ResetSignalHandlers Resets all Unix signal handlers back to their + default state (that is, pass \c SIG_DFL to \c{signal(2)}). This flag + is useful to ensure any ignored (\c SIG_IGN) signal does not affect + the child's behavior. + + \value UseVFork Requests that QProcess use \c{vfork(2)} to start the child + process. Use this flag to indicate that the callback function set + with setChildProcessModifier() is safe to execute in the child side of + a \c{vfork(2)}; that is, the callback does not modify any non-local + variables (directly or through any function it calls), nor attempts + to communicate with the parent process. It is implementation-defined + if QProcess will actually use \c{vfork(2)} and if \c{vfork(2)} is + different from standard \c{fork(2)}. + + \sa setUnixProcessParameters(), unixProcessParameters() +*/ + /*! \fn void QProcess::errorOccurred(QProcess::ProcessError error) \since 5.6 @@ -1553,7 +1623,7 @@ void QProcess::setCreateProcessArgumentsModifier(CreateProcessArgumentModifier m \note This function is only available on Unix platforms. - \sa setChildProcessModifier() + \sa setChildProcessModifier(), unixProcessParameters() */ std::function QProcess::childProcessModifier() const { @@ -1567,12 +1637,9 @@ std::function QProcess::childProcessModifier() const Sets the \a modifier function for the child process, for Unix systems (including \macos; for Windows, see setCreateProcessArgumentsModifier()). The function contained by the \a modifier argument will be invoked in the - child process after \c{fork()} or \c{vfork()} is completed and QProcess has set up the - standard file descriptors for the child process, but before \c{execve()}, - inside start(). The modifier is useful to change certain properties of the - child process, such as setting up additional file descriptors or closing - others, changing the nice level, disconnecting from the controlling TTY, - etc. + child process after \c{fork()} or \c{vfork()} is completed and QProcess has + set up the standard file descriptors for the child process, but before + \c{execve()}, inside start(). The following shows an example of setting up a child process to run without privileges: @@ -1582,22 +1649,32 @@ std::function QProcess::childProcessModifier() const If the modifier function needs to exit the process, remember to use \c{_exit()}, not \c{exit()}. + Certain properties of the child process, such as closing all extraneous + file descriptors or disconnecting from the controlling TTY, can be more + readily achieved by using setUnixProcessParameters(), which can detect + failure and report a \l{QProcess::}{FailedToStart} condition. The modifier + is useful to change certain uncommon properties of the child process, such + as setting up additional file descriptors. If both a child process modifier + and Unix process parameters are set, the modifier is run before these + parameters are applied. + \note In multithreaded applications, this function must be careful not to call any functions that may lock mutexes that may have been in use in other threads (in general, using only functions defined by POSIX as "async-signal-safe" is advised). Most of the Qt API is unsafe inside this callback, including qDebug(), and may lead to deadlocks. - \note On some systems (notably, Linux), QProcess will use \c{vfork()} - semantics to start the child process, so this function must obey even - stricter constraints. First, because it is still sharing memory with the - parent process, it must not write to any non-local variable and must obey - proper ordering semantics when reading from them, to avoid data races. - Second, even more library functions may misbehave; therefore, this function - should only make use of low-level system calls, such as \c{read()}, + \note If the UnixProcessParameters::UseVFork flag is set via + setUnixProcessParameters(), QProcess may use \c{vfork()} semantics to + start the child process, so this function must obey even stricter + constraints. First, because it is still sharing memory with the parent + process, it must not write to any non-local variable and must obey proper + ordering semantics when reading from them, to avoid data races. Second, + even more library functions may misbehave; therefore, this function should + only make use of low-level system calls, such as \c{read()}, \c{write()}, \c{setsid()}, \c{nice()}, and similar. - \sa childProcessModifier() + \sa childProcessModifier(), setUnixProcessParameters() */ void QProcess::setChildProcessModifier(const std::function &modifier) { @@ -1606,6 +1683,67 @@ void QProcess::setChildProcessModifier(const std::function &modifier d->unixExtras.reset(new QProcessPrivate::UnixExtras); d->unixExtras->childProcessModifier = modifier; } + +/*! + \since 6.6 + Returns the \l UnixProcessParameters object describing extra flags and + settings that will be applied to the child process on Unix systems. The + default settings correspond to a default-constructed UnixProcessParameters. + + \note This function is only available on Unix platforms. + + \sa childProcessModifier() +*/ +auto QProcess::unixProcessParameters() const noexcept -> UnixProcessParameters +{ + Q_D(const QProcess); + return d->unixExtras ? d->unixExtras->processParameters : UnixProcessParameters{}; +} + +/*! + \since 6.6 + Sets the extra settings and parameters for the child process on Unix + systems to be \a params. This function can be used to ask QProcess to + modify the child process before launching the target executable. + + This function can be used to change certain properties of the child + process, such as closing all extraneous file descriptors, changing the nice + level of the child, or disconnecting from the controlling TTY. For more + fine-grained control of the child process or to modify it in other ways, + use the setChildProcessModifier() function. If both a child process + modifier and Unix process parameters are set, the modifier is run before + these parameters are applied. + + \note This function is only available on Unix platforms. + + \sa unixProcessParameters(), setChildProcessModifier() +*/ +void QProcess::setUnixProcessParameters(const UnixProcessParameters ¶ms) +{ + Q_D(QProcess); + if (!d->unixExtras) + d->unixExtras.reset(new QProcessPrivate::UnixExtras); + d->unixExtras->processParameters = params; +} + +/*! + \since 6.6 + \overload + + Sets the extra settings for the child process on Unix systems to \a + flagsOnly. This is the same as the overload with just the \c flags field + set. + \note This function is only available on Unix platforms. + + \sa unixProcessParameters(), setChildProcessModifier() +*/ +void QProcess::setUnixProcessParameters(UnixProcessFlags flagsOnly) +{ + Q_D(QProcess); + if (!d->unixExtras) + d->unixExtras.reset(new QProcessPrivate::UnixExtras); + d->unixExtras->processParameters = { flagsOnly }; +} #endif /*! diff --git a/src/corelib/io/qprocess.h b/src/corelib/io/qprocess.h index 4fa1037b..bfffd9b9 100644 --- a/src/corelib/io/qprocess.h +++ b/src/corelib/io/qprocess.h @@ -1,4 +1,5 @@ // Copyright (C) 2016 The Qt Company Ltd. +// Copyright (C) 2023 Intel Corporation. // SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only #ifndef QPROCESS_H @@ -173,6 +174,25 @@ public: #if defined(Q_OS_UNIX) || defined(Q_QDOC) std::function childProcessModifier() const; void setChildProcessModifier(const std::function &modifier); + + enum class UnixProcessFlag : quint32 { + ResetSignalHandlers = 0x0001, // like POSIX_SPAWN_SETSIGDEF + IgnoreSigPipe = 0x0002, + // some room if we want to add IgnoreSigHup or so + CloseFileDescriptors = 0x0010, + UseVFork = 0x0020, // like POSIX_SPAWN_USEVFORK + }; + Q_DECLARE_FLAGS(UnixProcessFlags, UnixProcessFlag) + struct UnixProcessParameters + { + UnixProcessFlags flags = {}; + int lowestFileDescriptorToClose = 0; + + quint32 _reserved[6] {}; + }; + UnixProcessParameters unixProcessParameters() const noexcept; + void setUnixProcessParameters(const UnixProcessParameters ¶ms); + void setUnixProcessParameters(UnixProcessFlags flagsOnly); #endif QString workingDirectory() const; @@ -256,6 +276,10 @@ private: Q_PRIVATE_SLOT(d_func(), void _q_processDied()) }; +#ifdef Q_OS_UNIX +Q_DECLARE_OPERATORS_FOR_FLAGS(QProcess::UnixProcessFlags) +#endif + #endif // QT_CONFIG(process) QT_END_NAMESPACE diff --git a/src/corelib/io/qprocess_p.h b/src/corelib/io/qprocess_p.h index e42b5733..3b862bc5 100644 --- a/src/corelib/io/qprocess_p.h +++ b/src/corelib/io/qprocess_p.h @@ -280,9 +280,9 @@ public: QWinEventNotifier *processFinishedNotifier = nullptr; Q_PROCESS_INFORMATION *pid = nullptr; #else - struct UnixExtras { std::function childProcessModifier; + QProcess::UnixProcessParameters processParameters; }; std::unique_ptr unixExtras; QSocketNotifier *stateNotifier = nullptr; @@ -304,7 +304,7 @@ public: void startProcess(); #if defined(Q_OS_UNIX) void commitChannels() const; - void execChild(int workingDirectory, char **argv, char **envp) const; + void execChild(int workingDirectory, char **argv, char **envp) const noexcept; #endif bool processStarted(QString *errorMessage = nullptr); void processFinished(); diff --git a/src/corelib/io/qprocess_unix.cpp b/src/corelib/io/qprocess_unix.cpp index 6d0b26d5..2dc27f20 100644 --- a/src/corelib/io/qprocess_unix.cpp +++ b/src/corelib/io/qprocess_unix.cpp @@ -14,7 +14,7 @@ #include "private/qcore_unix_p.h" #include "private/qlocking_p.h" -#ifdef Q_OS_MAC +#ifdef Q_OS_DARWIN #include #endif @@ -36,20 +36,54 @@ #include #include #include +#include +#include + +#if __has_include() +// FreeBSD's is in +# include +#endif #if QT_CONFIG(process) #include #endif +#ifndef O_PATH +# define O_PATH 0 +#endif + +#ifdef Q_OS_FREEBSD +__attribute__((weak)) +#endif +extern char **environ; + QT_BEGIN_NAMESPACE using namespace Qt::StringLiterals; -#if !defined(Q_OS_DARWIN) +namespace { +struct PThreadCancelGuard +{ +#if defined(PTHREAD_CANCEL_DISABLE) + int oldstate; + PThreadCancelGuard() noexcept(false) + { + pthread_setcancelstate(PTHREAD_CANCEL_DISABLE, &oldstate); + } + ~PThreadCancelGuard() noexcept(false) + { + reenable(); + } + void reenable() noexcept(false) + { + // this doesn't touch errno + pthread_setcancelstate(oldstate, nullptr); + } +#endif +}; +} -QT_BEGIN_INCLUDE_NAMESPACE -extern char **environ; -QT_END_INCLUDE_NAMESPACE +#if !defined(Q_OS_DARWIN) QProcessEnvironment QProcessEnvironment::systemEnvironment() { @@ -72,6 +106,70 @@ QProcessEnvironment QProcessEnvironment::systemEnvironment() #if QT_CONFIG(process) +namespace QtVforkSafe { +// Certain libc functions we need to call in the child process scenario aren't +// safe under vfork() because they do more than just place the system call to +// the kernel and set errno on return. For those, we'll create a function +// pointer like: +// static constexpr auto foobar = __libc_foobar; +// while for all other OSes, it'll be +// using ::foobar; +// allowing the code for the child side of the vfork to simply use +// QtVforkSafe::foobar(args); +// +// Currently known issues are: +// +// - FreeBSD's libthr sigaction() wrapper locks a rwlock +// https://github.com/freebsd/freebsd-src/blob/8dad5ece49479ba6cdcd5bb4c2799bbd61add3e6/lib/libthr/thread/thr_sig.c#L575-L641 +// - MUSL's sigaction() locks a mutex if the signal is SIGABR +// https://github.com/bminor/musl/blob/718f363bc2067b6487900eddc9180c84e7739f80/src/signal/sigaction.c#L63-L85 +// +// All other functions called in the child side are vfork-safe, provided that +// PThread cancellation is disabled and Unix signals are blocked. +#if defined(__MUSL__) +# define LIBC_PREFIX __libc_ +#elif defined(Q_OS_FREEBSD) +// will cause QtCore to link to ELF version "FBSDprivate_1.0" +# define LIBC_PREFIX _ +#endif + +#ifdef LIBC_PREFIX +# define CONCAT(x, y) CONCAT2(x, y) +# define CONCAT2(x, y) x ## y +# define DECLARE_FUNCTIONS(NAME) \ + extern decltype(::NAME) CONCAT(LIBC_PREFIX, NAME); \ + static constexpr auto NAME = std::addressof(CONCAT(LIBC_PREFIX, NAME)); +#else // LIBC_PREFIX +# define DECLARE_FUNCTIONS(NAME) using ::NAME; +#endif // LIBC_PREFIX + +extern "C" { +DECLARE_FUNCTIONS(sigaction) +} + +#undef LIBC_PREFIX +#undef DECLARE_FUNCTIONS + +// similar to qt_ignore_sigpipe() in qcore_unix_p.h, but vfork-safe +static void change_sigpipe(decltype(SIG_DFL) new_handler) +{ + struct sigaction sa; + memset(&sa, 0, sizeof(sa)); + sa.sa_handler = new_handler; + sigaction(SIGPIPE, &sa, nullptr); +} +} // namespace QtVforkSafe + +static int opendirfd(QByteArray encodedName) +{ + // We append "/." to the name to ensure that the directory is actually + // traversable (i.e., has the +x bit set). This avoids later problems + // with fchdir(). + if (encodedName != "/" && !encodedName.endsWith("/.")) + encodedName += "/."; + return qt_safe_open(encodedName, QT_OPEN_RDONLY | O_DIRECTORY | O_PATH); +} + namespace { struct AutoPipe { @@ -95,8 +193,8 @@ struct AutoPipe struct ChildError { - qint64 code; - char function[8]; + int code; + char function[12]; }; // Used for argv and envp arguments to execve() @@ -412,6 +510,35 @@ static QString resolveExecutable(const QString &program) return program; } +static int useForkFlags(const QProcessPrivate::UnixExtras *unixExtras) +{ +#if defined(__SANITIZE_ADDRESS__) || __has_feature(address_sanitizer) + // ASan writes to global memory, so we mustn't use vfork(). + return FFD_USE_FORK; +#endif +#if defined(Q_OS_LINUX) && !QT_CONFIG(forkfd_pidfd) + // some broken environments are known to have problems with the new Linux + // API, so we have a way for users to opt-out during configure time (see + // QTBUG-86285) + return FFD_USE_FORK; +#endif +#if defined(Q_OS_DARWIN) + // Using vfork() for startDetached() is causing problems. We don't know + // why: without the tools to investigate why it happens, we didn't bother. + return FFD_USE_FORK; +#endif + + if (!unixExtras || !unixExtras->childProcessModifier) + return 0; // no modifier was supplied + + // if a modifier was supplied, use fork() unless the user opts in to + // vfork() + auto flags = unixExtras->processParameters.flags; + if (flags.testFlag(QProcess::UnixProcessFlag::UseVFork)) + return 0; + return FFD_USE_FORK; +} + void QProcessPrivate::startProcess() { Q_Q(QProcess); @@ -443,7 +570,7 @@ void QProcessPrivate::startProcess() int workingDirFd = -1; if (!workingDirectory.isEmpty()) { - workingDirFd = qt_safe_open(QFile::encodeName(workingDirectory), QT_OPEN_RDONLY | O_DIRECTORY); + workingDirFd = opendirfd(QFile::encodeName(workingDirectory)); if (workingDirFd == -1) { setErrorAndEmit(QProcess::FailedToStart, "chdir: "_L1 + qt_error_string()); cleanup(); @@ -458,6 +585,11 @@ void QProcessPrivate::startProcess() const CharPointerList argv(resolveExecutable(program), arguments); const CharPointerList envp(environment.d.constData()); + // Disable PThread cancellation from this point on: we mustn't have it + // enabled when the child starts running nor while our state could get + // corrupted if we abruptly exited this function. + [[maybe_unused]] PThreadCancelGuard cancelGuard; + // Start the child. auto execChild1 = [this, workingDirFd, &argv, &envp]() { execChild(workingDirFd, argv.pointers.get(), envp.pointers.get()); @@ -467,19 +599,7 @@ void QProcessPrivate::startProcess() return -1; }; - int ffdflags = FFD_CLOEXEC; - -#if defined(Q_OS_LINUX) && !QT_CONFIG(forkfd_pidfd) - // QTBUG-86285 - ffdflags |= FFD_USE_FORK; -#endif -#if defined(__SANITIZE_ADDRESS__) || __has_feature(address_sanitizer) - // ASan writes to global memory, so we mustn't use vfork(). - ffdflags |= FFD_USE_FORK; -#endif - if (unixExtras && unixExtras->childProcessModifier) - ffdflags |= FFD_USE_FORK; - + int ffdflags = FFD_CLOEXEC | useForkFlags(unixExtras.get()); forkfd = ::vforkfd(ffdflags, &pid, execChild2, &execChild1); int lastForkErrno = errno; @@ -529,14 +649,110 @@ void QProcessPrivate::startProcess() ::fcntl(stderrChannel.pipe[0], F_SETFL, ::fcntl(stderrChannel.pipe[0], F_GETFL) | O_NONBLOCK); } +// we need an errno number to use to indicate the child process modifier threw, +// something the regular operations shouldn't set. +static constexpr int FakeErrnoForThrow = +#ifdef ECANCELED + ECANCELED +#else + ESHUTDOWN +#endif + ; + +// See IMPORTANT notice below +static void applyProcessParameters(const QProcess::UnixProcessParameters ¶ms) +{ + // Apply Unix signal handler parameters. + // We don't expect signal() to fail, so we ignore its return value + bool ignore_sigpipe = params.flags.testFlag(QProcess::UnixProcessFlag::IgnoreSigPipe); + if (ignore_sigpipe) + QtVforkSafe::change_sigpipe(SIG_IGN); + if (params.flags.testFlag(QProcess::UnixProcessFlag::ResetSignalHandlers)) { + struct sigaction sa = {}; + sa.sa_handler = SIG_DFL; + for (int sig = 1; sig < NSIG; ++sig) { + if (!ignore_sigpipe || sig != SIGPIPE) + QtVforkSafe::sigaction(sig, &sa, nullptr); + } + + // and unmask all signals + sigset_t set; + sigemptyset(&set); + sigprocmask(SIG_SETMASK, &set, nullptr); + } + + // Close all file descriptors above stderr. + // This isn't expected to fail, so we ignore close()'s return value. + if (params.flags.testFlag(QProcess::UnixProcessFlag::CloseFileDescriptors)) { + int r = -1; + int fd = qMax(STDERR_FILENO + 1, params.lowestFileDescriptorToClose); +#if QT_CONFIG(close_range) + // On FreeBSD, this probably won't fail. + // On Linux, this will fail with ENOSYS before kernel 5.9. + r = close_range(fd, INT_MAX, 0); +#endif + if (r == -1) { + // We *could* read /dev/fd to find out what file descriptors are + // open, but we won't. We CANNOT use opendir() here because it + // allocates memory. Using getdents(2) plus either strtoul() or + // std::from_chars() would be acceptable. + int max_fd = INT_MAX; + if (struct rlimit limit; getrlimit(RLIMIT_NOFILE, &limit) == 0) + max_fd = limit.rlim_cur; + for ( ; fd < max_fd; ++fd) + close(fd); + } + } +} + +// the noexcept here adds an extra layer of protection +static const char *callChildProcessModifier(const QProcessPrivate::UnixExtras *unixExtras) noexcept +{ + QT_TRY { + if (unixExtras->childProcessModifier) + unixExtras->childProcessModifier(); + } QT_CATCH (...) { + errno = FakeErrnoForThrow; + return "throw"; + } + return nullptr; +} + +// this function doesn't return if the execution succeeds +static const char *doExecChild(char **argv, char **envp, int workingDirFd, + const QProcessPrivate::UnixExtras *unixExtras) noexcept +{ + // enter the working directory + if (workingDirFd != -1 && fchdir(workingDirFd) == -1) + return "fchdir"; + + if (unixExtras) { + // FIRST we call the user modifier function, before we dropping + // privileges or closing non-standard file descriptors + if (const char *what = callChildProcessModifier(unixExtras)) + return what; + + // then we apply our other user-provided parameters + applyProcessParameters(unixExtras->processParameters); + } + + // execute the process + if (!envp) + qt_safe_execv(argv[0], argv); + else + qt_safe_execve(argv[0], argv, envp); + return "execve"; +} + + // IMPORTANT: // // This function is called in a vfork() context on some OSes (notably, Linux // with forkfd), so it MUST NOT modify any non-local variable because it's // still sharing memory with the parent process. -void QProcessPrivate::execChild(int workingDir, char **argv, char **envp) const +void QProcessPrivate::execChild(int workingDir, char **argv, char **envp) const noexcept { - ::signal(SIGPIPE, SIG_DFL); // reset the signal that we ignored + QtVforkSafe::change_sigpipe(SIG_DFL); // reset the signal that we ignored ChildError error = { 0, {} }; // force zeroing of function[8] @@ -546,34 +762,12 @@ void QProcessPrivate::execChild(int workingDir, char **argv, char **envp) const // make sure this fd is closed if execv() succeeds qt_safe_close(childStartedPipe[0]); - // enter the working directory - if (workingDir != -1 && fchdir(workingDir) == -1) { - // failed, stop the process - strcpy(error.function, "fchdir"); - goto report_errno; - } - - if (unixExtras) { - if (unixExtras->childProcessModifier) - unixExtras->childProcessModifier(); - } - - // execute the process - if (!envp) { - qt_safe_execv(argv[0], argv); - strcpy(error.function, "execvp"); - } else { -#if defined (QPROCESS_DEBUG) - fprintf(stderr, "QProcessPrivate::execChild() starting %s\n", argv[0]); -#endif - qt_safe_execve(argv[0], argv, envp); - strcpy(error.function, "execve"); - } + const char *what = doExecChild(argv, envp, workingDir, unixExtras.get()); + strcpy(error.function, what); // notify failure // don't use strerror or any other routines that may allocate memory, since // some buggy libc versions can deadlock on locked mutexes. -report_errno: error.code = errno; qt_safe_write(childStartedPipe[1], &error, sizeof(error)); } @@ -583,7 +777,7 @@ bool QProcessPrivate::processStarted(QString *errorMessage) Q_Q(QProcess); ChildError buf; - int ret = qt_safe_read(childStartedPipe[0], &buf, sizeof(buf)); + ssize_t ret = qt_safe_read(childStartedPipe[0], &buf, sizeof(buf)); if (stateNotifier) { stateNotifier->setEnabled(false); @@ -612,8 +806,12 @@ bool QProcessPrivate::processStarted(QString *errorMessage) } // did we read an error message? - if (errorMessage) - *errorMessage = QLatin1StringView(buf.function) + ": "_L1 + qt_error_string(buf.code); + if (errorMessage) { + if (buf.code == FakeErrnoForThrow) + *errorMessage = QProcess::tr("childProcessModifier() function threw an exception"); + else + *errorMessage = QLatin1StringView(buf.function) + ": "_L1 + qt_error_string(buf.code); + } return false; } @@ -945,7 +1143,7 @@ bool QProcessPrivate::startDetached(qint64 *pid) int workingDirFd = -1; if (!workingDirectory.isEmpty()) { - workingDirFd = qt_safe_open(QFile::encodeName(workingDirectory), QT_OPEN_RDONLY | O_DIRECTORY); + workingDirFd = opendirfd(QFile::encodeName(workingDirectory)); if (workingDirFd == -1) { setErrorAndEmit(QProcess::FailedToStart, "chdir: "_L1 + qt_error_string(errno)); return false; @@ -955,9 +1153,17 @@ bool QProcessPrivate::startDetached(qint64 *pid) const CharPointerList argv(resolveExecutable(program), arguments); const CharPointerList envp(environment.d.constData()); - pid_t childPid = fork(); + // see startProcess() for more information + [[maybe_unused]] PThreadCancelGuard cancelGuard; + + auto doFork = [this]() { + if (useForkFlags(unixExtras.get())) + return fork; + QT_IGNORE_DEPRECATIONS(return vfork;) + }(); + pid_t childPid = doFork(); if (childPid == 0) { - ::signal(SIGPIPE, SIG_DFL); // reset the signal that we ignored + QtVforkSafe::change_sigpipe(SIG_DFL); // reset the signal that we ignored ::setsid(); qt_safe_close(startedPipe[0]); @@ -970,20 +1176,13 @@ bool QProcessPrivate::startDetached(qint64 *pid) ::_exit(1); }; - if (workingDirFd != -1 && fchdir(workingDirFd) == -1) - reportFailed("fchdir: "); - - pid_t doubleForkPid = fork(); + pid_t doubleForkPid = doFork(); if (doubleForkPid == 0) { // Render channels configuration. commitChannels(); - if (envp.pointers) - qt_safe_execve(argv.pointers[0], argv.pointers.get(), envp.pointers.get()); - else - qt_safe_execv(argv.pointers[0], argv.pointers.get()); - - reportFailed("execv: "); + reportFailed(doExecChild(argv.pointers.get(), envp.pointers.get(), workingDirFd, + unixExtras.get())); } else if (doubleForkPid == -1) { reportFailed("fork: "); } diff --git a/src/corelib/io/qresource.cpp b/src/corelib/io/qresource.cpp index c8dc09db..56394911 100644 --- a/src/corelib/io/qresource.cpp +++ b/src/corelib/io/qresource.cpp @@ -82,7 +82,7 @@ public: inline QStringView next() { - int start = m_pos; + const qsizetype start = m_pos; while (m_pos < m_len && m_data[m_pos] != m_splitChar) ++m_pos; return QStringView(m_data + start, m_pos - start); @@ -1478,14 +1478,14 @@ QString QResourceFileEngine::fileName(FileName file) const { Q_D(const QResourceFileEngine); if (file == BaseName) { - int slash = d->resource.fileName().lastIndexOf(u'/'); + const qsizetype slash = d->resource.fileName().lastIndexOf(u'/'); if (slash == -1) return d->resource.fileName(); return d->resource.fileName().mid(slash + 1); } else if (file == PathName || file == AbsolutePathName) { const QString path = (file == AbsolutePathName) ? d->resource.absoluteFilePath() : d->resource.fileName(); - const int slash = path.lastIndexOf(u'/'); + const qsizetype slash = path.lastIndexOf(u'/'); if (slash == -1) return ":"_L1; else if (slash <= 1) @@ -1495,7 +1495,7 @@ QString QResourceFileEngine::fileName(FileName file) const } else if (file == CanonicalName || file == CanonicalPathName) { const QString absoluteFilePath = d->resource.absoluteFilePath(); if (file == CanonicalPathName) { - const int slash = absoluteFilePath.lastIndexOf(u'/'); + const qsizetype slash = absoluteFilePath.lastIndexOf(u'/'); if (slash != -1) return absoluteFilePath.left(slash); } diff --git a/src/corelib/io/qsettings.cpp b/src/corelib/io/qsettings.cpp index 5cf535ea..d9e7df1b 100644 --- a/src/corelib/io/qsettings.cpp +++ b/src/corelib/io/qsettings.cpp @@ -44,7 +44,7 @@ # include #endif -#if defined(Q_OS_UNIX) && !defined(Q_OS_MAC) && !defined(Q_OS_ANDROID) +#if defined(Q_OS_UNIX) && !defined(Q_OS_DARWIN) && !defined(Q_OS_ANDROID) #define Q_XDG_PLATFORM #endif @@ -212,9 +212,7 @@ namespace { } QChar *write(QChar *out, QLatin1StringView v) { - for (char ch : v) - *out++ = QLatin1Char(ch); - return out; + return QLatin1::convertToUnicode(out, v); } QChar *write(QChar *out, QStringView v) { @@ -272,7 +270,7 @@ QString QSettingsPrivate::normalizedKey(QAnyStringView key) // see also qsettings_win.cpp and qsettings_mac.cpp -#if !defined(Q_OS_WIN) && !defined(Q_OS_MAC) && !defined(Q_OS_WASM) +#if !defined(Q_OS_WIN) && !defined(Q_OS_DARWIN) && !defined(Q_OS_WASM) QSettingsPrivate *QSettingsPrivate::create(QSettings::Format format, QSettings::Scope scope, const QString &organization, const QString &application) { @@ -560,7 +558,7 @@ bool QSettingsPrivate::iniUnescapedKey(QByteArrayView key, QString &result) } int numDigits = 2; - int firstDigitPos = i + 1; + qsizetype firstDigitPos = i + 1; ch = decoded.at(i + 1).unicode(); if (ch == 'U') { @@ -753,8 +751,8 @@ StNormal: ch = str.at(i); if (isHexDigit(ch)) goto StHexEscape; - } else if (isOctalDigit(ch)) { - escapeVal = ch - '0'; + } else if (const int o = fromOct(ch); o != -1) { + escapeVal = o; goto StOctEscape; } else if (ch == '\n' || ch == '\r') { if (i < str.size()) { @@ -816,11 +814,9 @@ StHexEscape: } ch = str.at(i); - if (ch >= 'a') - ch -= 'a' - 'A'; - if ((ch >= '0' && ch <= '9') || (ch >= 'A' && ch <= 'F')) { + if (const int h = fromHex(ch); h != -1) { escapeVal <<= 4; - escapeVal += QtMiscUtils::fromHex(ch); + escapeVal += h; ++i; goto StHexEscape; } else { @@ -835,9 +831,9 @@ StOctEscape: } ch = str.at(i); - if (ch >= '0' && ch <= '7') { + if (const int o = fromOct(ch); o != -1) { escapeVal <<= 3; - escapeVal += ch - '0'; + escapeVal += o; ++i; goto StOctEscape; } else { @@ -885,7 +881,7 @@ void QConfFileSettingsPrivate::initFormat() extension = (format == QSettings::NativeFormat) ? ".conf"_L1 : ".ini"_L1; readFunc = nullptr; writeFunc = nullptr; -#if defined(Q_OS_MAC) +#if defined(Q_OS_DARWIN) caseSensitivity = (format == QSettings::NativeFormat) ? Qt::CaseSensitive : IniCaseSensitivity; #else caseSensitivity = IniCaseSensitivity; @@ -1019,7 +1015,7 @@ static std::unique_lock initDefaultPaths(std::unique_lockinsert(pathHashKey(QSettings::IniFormat, QSettings::UserScope), Path(userPath, false)); pathHash->insert(pathHashKey(QSettings::IniFormat, QSettings::SystemScope), Path(systemPath, false)); -#ifndef Q_OS_MAC +#ifndef Q_OS_DARWIN pathHash->insert(pathHashKey(QSettings::NativeFormat, QSettings::UserScope), Path(userPath, false)); pathHash->insert(pathHashKey(QSettings::NativeFormat, QSettings::SystemScope), Path(systemPath, false)); #endif @@ -1326,13 +1322,13 @@ void QConfFileSettingsPrivate::syncConfFile(QConfFile *confFile) { bool readOnly = confFile->addedKeys.isEmpty() && confFile->removedKeys.isEmpty(); + QFileInfo fileInfo(confFile->name); /* We can often optimize the read-only case, if the file on disk hasn't changed. */ if (readOnly && confFile->size > 0) { - QFileInfo fileInfo(confFile->name); - if (confFile->size == fileInfo.size() && confFile->timeStamp == fileInfo.lastModified()) + if (confFile->size == fileInfo.size() && confFile->timeStamp == fileInfo.lastModified(QTimeZone::UTC)) return; } @@ -1368,13 +1364,13 @@ void QConfFileSettingsPrivate::syncConfFile(QConfFile *confFile) We hold the lock. Let's reread the file if it has changed since last time we read it. */ - QFileInfo fileInfo(confFile->name); + fileInfo.refresh(); bool mustReadFile = true; bool createFile = !fileInfo.exists(); if (!readOnly) mustReadFile = (confFile->size != fileInfo.size() - || (confFile->size != 0 && confFile->timeStamp != fileInfo.lastModified())); + || (confFile->size != 0 && confFile->timeStamp != fileInfo.lastModified(QTimeZone::UTC))); if (mustReadFile) { confFile->unparsedIniSections.clear(); @@ -1392,7 +1388,7 @@ void QConfFileSettingsPrivate::syncConfFile(QConfFile *confFile) */ if (file.isReadable() && file.size() != 0) { bool ok = false; -#ifdef Q_OS_MAC +#ifdef Q_OS_DARWIN if (format == QSettings::NativeFormat) { QByteArray data = file.readAll(); ok = readPlistFile(data, &confFile->originalKeys); @@ -1420,7 +1416,7 @@ void QConfFileSettingsPrivate::syncConfFile(QConfFile *confFile) } confFile->size = fileInfo.size(); - confFile->timeStamp = fileInfo.lastModified(); + confFile->timeStamp = fileInfo.lastModified(QTimeZone::UTC); } /* @@ -1448,7 +1444,7 @@ void QConfFileSettingsPrivate::syncConfFile(QConfFile *confFile) return; } -#ifdef Q_OS_MAC +#ifdef Q_OS_DARWIN if (format == QSettings::NativeFormat) { ok = writePlistFile(sf, mergedKeys); } else @@ -1477,9 +1473,9 @@ void QConfFileSettingsPrivate::syncConfFile(QConfFile *confFile) confFile->addedKeys.clear(); confFile->removedKeys.clear(); - QFileInfo fileInfo(confFile->name); + fileInfo.refresh(); confFile->size = fileInfo.size(); - confFile->timeStamp = fileInfo.lastModified(); + confFile->timeStamp = fileInfo.lastModified(QTimeZone::UTC); // If we have created the file, apply the file perms if (createFile) { @@ -2344,7 +2340,7 @@ void QConfFileSettingsPrivate::ensureSectionParsed(QConfFile *confFile, \endlist - \sa QVariant, QSessionManager, {Settings Editor Example}, {Qt Widgets - Application Example} + \sa QVariant, QSessionManager, {Settings Editor Example} */ /*! \enum QSettings::Status diff --git a/src/corelib/io/qsettings_p.h b/src/corelib/io/qsettings_p.h index 24298202..d79c10e6 100644 --- a/src/corelib/io/qsettings_p.h +++ b/src/corelib/io/qsettings_p.h @@ -248,7 +248,7 @@ private: virtual void initAccess(); void syncConfFile(QConfFile *confFile); bool writeIniFile(QIODevice &device, const ParsedSettingsMap &map); -#ifdef Q_OS_MAC +#ifdef Q_OS_DARWIN bool readPlistFile(const QByteArray &data, ParsedSettingsMap *map) const; bool writePlistFile(QIODevice &file, const ParsedSettingsMap &map) const; #endif diff --git a/src/corelib/io/qstandardpaths.cpp b/src/corelib/io/qstandardpaths.cpp index a20c50e0..9a14ae07 100644 --- a/src/corelib/io/qstandardpaths.cpp +++ b/src/corelib/io/qstandardpaths.cpp @@ -523,7 +523,7 @@ QString QStandardPaths::findExecutable(const QString &executableName, const QStr \include standardpath/functiondocs.qdocinc displayName */ -#if !defined(Q_OS_MAC) && !defined(QT_BOOTSTRAPPED) +#if !defined(Q_OS_DARWIN) && !defined(QT_BOOTSTRAPPED) QString QStandardPaths::displayName(StandardLocation type) { switch (type) { diff --git a/src/corelib/io/qstandardpaths_unix.cpp b/src/corelib/io/qstandardpaths_unix.cpp index 30c4c3ad..64d8a3fa 100644 --- a/src/corelib/io/qstandardpaths_unix.cpp +++ b/src/corelib/io/qstandardpaths_unix.cpp @@ -357,9 +357,9 @@ static QStringList dirsList(const QString &xdgEnvVar) if (dir.startsWith(u'/')) dirs.push_back(QDir::cleanPath(dir.toString())); - // Remove duplicates from the list, there's no use for duplicated - // paths in XDG_DATA_DIRS - if it's not found in the given - // directory the first time, it won't be there the second time. + // Remove duplicates from the list, there's no use for duplicated paths + // in XDG_* env vars - if whatever is being looked for is not found in + // the given directory the first time, it won't be there the second time. // Plus duplicate paths causes problems for example for mimetypes, // where duplicate paths here lead to duplicated mime types returned // for a file, eg "text/plain,text/plain" instead of "text/plain" diff --git a/src/corelib/io/qstandardpaths_win.cpp b/src/corelib/io/qstandardpaths_win.cpp index 9e2607c3..13b8fe22 100644 --- a/src/corelib/io/qstandardpaths_win.cpp +++ b/src/corelib/io/qstandardpaths_win.cpp @@ -12,7 +12,6 @@ #include #include -#include #include #include @@ -61,26 +60,20 @@ static inline void appendTestMode(QString &path) static bool isProcessLowIntegrity() { - if (!IsWindows8OrGreater()) - return false; - // same as GetCurrentProcessToken() const auto process_token = HANDLE(quintptr(-4)); QVarLengthArray token_info_buf(256); auto* token_info = reinterpret_cast(token_info_buf.data()); DWORD token_info_length = token_info_buf.size(); - if (!GetTokenInformation(process_token, TokenIntegrityLevel, token_info, token_info_length, &token_info_length) - && GetLastError() == ERROR_INSUFFICIENT_BUFFER) { + if (!GetTokenInformation(process_token, TokenIntegrityLevel, token_info, token_info_length, &token_info_length)) { // grow buffer and retry GetTokenInformation token_info_buf.resize(token_info_length); token_info = reinterpret_cast(token_info_buf.data()); if (!GetTokenInformation(process_token, TokenIntegrityLevel, token_info, token_info_length, &token_info_length)) return false; // assume "normal" process } - else - return false; - + // The GetSidSubAuthorityCount return-code is undefined on failure, so // there's no point in checking before dereferencing DWORD integrity_level = *GetSidSubAuthority(token_info->Label.Sid, *GetSidSubAuthorityCount(token_info->Label.Sid) - 1); diff --git a/src/corelib/io/qstorageinfo.cpp b/src/corelib/io/qstorageinfo.cpp index 25b11103..2a12c2cd 100644 --- a/src/corelib/io/qstorageinfo.cpp +++ b/src/corelib/io/qstorageinfo.cpp @@ -241,9 +241,10 @@ QByteArray QStorageInfo::device() const Returns the subvolume name for this volume. Some filesystem types allow multiple subvolumes inside one device, which - may be mounted in different paths. If the subvolume could be detected, it - is returned here. The format of the subvolume name is specific to each - filesystem type. + may be mounted in different paths (e.g. 'bind' mounts on Unix, or Btrfs + filesystem subvolumes). If the subvolume could be detected, its name is + returned by this function. The format of the subvolume name is specific + to each filesystem type. If this volume was not mounted from a subvolume of a larger filesystem or if the subvolume could not be detected, this function returns an empty byte diff --git a/src/corelib/io/qstorageinfo_mac.cpp b/src/corelib/io/qstorageinfo_mac.cpp index 690e7212..38f86c3f 100644 --- a/src/corelib/io/qstorageinfo_mac.cpp +++ b/src/corelib/io/qstorageinfo_mac.cpp @@ -43,9 +43,9 @@ void QStorageInfoPrivate::retrievePosixInfo() QT_STATFSBUF statfs_buf; int result = QT_STATFS(QFile::encodeName(rootPath).constData(), &statfs_buf); if (result == 0) { - device = QByteArray(statfs_buf.f_mntfromname); + device.assign(statfs_buf.f_mntfromname); readOnly = (statfs_buf.f_flags & MNT_RDONLY) != 0; - fileSystemType = QByteArray(statfs_buf.f_fstypename); + fileSystemType.assign(statfs_buf.f_fstypename); blockSize = statfs_buf.f_bsize; } } diff --git a/src/corelib/io/qstorageinfo_p.h b/src/corelib/io/qstorageinfo_p.h index d6d0ab28..af323b4d 100644 --- a/src/corelib/io/qstorageinfo_p.h +++ b/src/corelib/io/qstorageinfo_p.h @@ -40,7 +40,7 @@ protected: void retrieveDiskFreeSpace(); bool queryStorageProperty(); void queryFileFsSectorSizeInformation(); -#elif defined(Q_OS_MAC) +#elif defined(Q_OS_DARWIN) void retrievePosixInfo(); void retrieveUrlProperties(bool initRootPath = false); void retrieveLabel(); @@ -58,7 +58,7 @@ public: qint64 bytesTotal; qint64 bytesFree; qint64 bytesAvailable; - int blockSize; + ulong blockSize; bool readOnly; bool ready; diff --git a/src/corelib/io/qstorageinfo_unix.cpp b/src/corelib/io/qstorageinfo_unix.cpp index 207e93fa..20bddd87 100644 --- a/src/corelib/io/qstorageinfo_unix.cpp +++ b/src/corelib/io/qstorageinfo_unix.cpp @@ -463,8 +463,7 @@ inline bool QStorageIterator::next() r = qstrntoll(ptr, stop - ptr, 10); if (!r.ok()) return false; - int parent_id = r.result; - Q_UNUSED(parent_id); + // parent_id = r.result; // member currently not in use ptr += r.used; r = qstrntoll(ptr, stop - ptr, 10); diff --git a/src/corelib/io/qtemporaryfile.cpp b/src/corelib/io/qtemporaryfile.cpp index 86ad1439..80ea57fb 100644 --- a/src/corelib/io/qtemporaryfile.cpp +++ b/src/corelib/io/qtemporaryfile.cpp @@ -45,8 +45,8 @@ QTemporaryFileName::QTemporaryFileName(const QString &templateName) { // Ensure there is a placeholder mask QString qfilename = QDir::fromNativeSeparators(templateName); - uint phPos = qfilename.size(); - uint phLength = 0; + qsizetype phPos = qfilename.size(); + qsizetype phLength = 0; while (phPos != 0) { --phPos; @@ -163,7 +163,7 @@ QFileSystemEntry::NativePath QTemporaryFileName::generateNext() \internal Generates a unique file path from the template \a templ and creates a new - file based based on those parameters: the \c templ.length characters in \c + file based on those parameters: the \c templ.length characters in \c templ.path starting at \c templ.pos will be replaced by a random sequence of characters. \a mode specifies the file mode bits (not used on Windows). @@ -406,7 +406,7 @@ bool QTemporaryFileEngine::close() QString QTemporaryFileEngine::fileName(QAbstractFileEngine::FileName file) const { if (isUnnamedFile()) { - if (file == AbsoluteLinkTarget) { + if (file == AbsoluteLinkTarget || file == RawLinkPath) { // we know our file isn't (won't be) a symlink return QString(); } diff --git a/src/corelib/io/qurl.cpp b/src/corelib/io/qurl.cpp index 95f688ac..db2afb43 100644 --- a/src/corelib/io/qurl.cpp +++ b/src/corelib/io/qurl.cpp @@ -409,9 +409,6 @@ QT_BEGIN_NAMESPACE using namespace Qt::StringLiterals; using namespace QtMiscUtils; -// in qstring.cpp: -void qt_from_latin1(char16_t *dst, const char *str, size_t size) noexcept; - inline static bool isHex(char c) { c |= 0x20; diff --git a/src/corelib/io/qurl_p.h b/src/corelib/io/qurl_p.h index 43dcdf82..9562d199 100644 --- a/src/corelib/io/qurl_p.h +++ b/src/corelib/io/qurl_p.h @@ -29,8 +29,8 @@ extern Q_AUTOTEST_EXPORT qsizetype qt_urlRecode(QString &appendTo, QStringView u // in qurlidna.cpp enum AceLeadingDot { AllowLeadingDot, ForbidLeadingDot }; enum AceOperation { ToAceOnly, NormalizeAce }; -extern QString qt_ACE_do(const QString &domain, AceOperation op, AceLeadingDot dot, - QUrl::AceProcessingOptions options); +QString Q_CORE_EXPORT qt_ACE_do(const QString &domain, AceOperation op, AceLeadingDot dot, + QUrl::AceProcessingOptions options = {}); extern Q_AUTOTEST_EXPORT void qt_punycodeEncoder(QStringView in, QString *output); extern Q_AUTOTEST_EXPORT QString qt_punycodeDecoder(const QString &pc); diff --git a/src/corelib/io/qurlidna.cpp b/src/corelib/io/qurlidna.cpp index aed54450..bb14e1cd 100644 --- a/src/corelib/io/qurlidna.cpp +++ b/src/corelib/io/qurlidna.cpp @@ -327,12 +327,12 @@ static bool lessThan(const QChar *a, int l, const char *c) return false; while (*c) { - if (uc == e || *uc != *c) + if (uc == e || *uc != static_cast(*c)) break; ++uc; ++c; } - return (uc == e ? *c : *uc < *c); + return uc == e ? *c : (*uc < static_cast(*c)); } static bool equal(const QChar *a, int l, const char *b) diff --git a/src/gui/text/qzip.cpp b/src/corelib/io/qzip.cpp similarity index 99% rename from src/gui/text/qzip.cpp rename to src/corelib/io/qzip.cpp index 7fd96363..d4976691 100644 --- a/src/gui/text/qzip.cpp +++ b/src/corelib/io/qzip.cpp @@ -1,12 +1,9 @@ // 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 - -#ifndef QT_NO_TEXTODFWRITER - #include "qzipreader_p.h" #include "qzipwriter_p.h" + #include #include #include @@ -1348,5 +1345,3 @@ void QZipWriter::close() } QT_END_NAMESPACE - -#endif // QT_NO_TEXTODFWRITER diff --git a/src/gui/text/qzipreader_p.h b/src/corelib/io/qzipreader_p.h similarity index 92% rename from src/gui/text/qzipreader_p.h rename to src/corelib/io/qzipreader_p.h index 2e8d6bc9..e6ddd0dc 100644 --- a/src/gui/text/qzipreader_p.h +++ b/src/corelib/io/qzipreader_p.h @@ -4,10 +4,7 @@ #ifndef QZIPREADER_H #define QZIPREADER_H -#include -#include - -#ifndef QT_NO_TEXTODFWRITER +#include // // W A R N I N G @@ -28,7 +25,7 @@ QT_BEGIN_NAMESPACE class QZipReaderPrivate; -class Q_GUI_EXPORT QZipReader +class Q_CORE_EXPORT QZipReader { public: explicit QZipReader(const QString &fileName, QIODevice::OpenMode mode = QIODevice::ReadOnly ); @@ -87,5 +84,4 @@ Q_DECLARE_TYPEINFO(QZipReader::Status, Q_PRIMITIVE_TYPE); QT_END_NAMESPACE -#endif // QT_NO_TEXTODFWRITER #endif // QZIPREADER_H diff --git a/src/gui/text/qzipwriter_p.h b/src/corelib/io/qzipwriter_p.h similarity index 92% rename from src/gui/text/qzipwriter_p.h rename to src/corelib/io/qzipwriter_p.h index 6c1ef5d8..770e6118 100644 --- a/src/gui/text/qzipwriter_p.h +++ b/src/corelib/io/qzipwriter_p.h @@ -3,9 +3,7 @@ #ifndef QZIPWRITER_H #define QZIPWRITER_H -#include - -#ifndef QT_NO_TEXTODFWRITER +#include // // W A R N I N G @@ -25,8 +23,7 @@ QT_BEGIN_NAMESPACE class QZipWriterPrivate; - -class Q_GUI_EXPORT QZipWriter +class Q_CORE_EXPORT QZipWriter { public: explicit QZipWriter(const QString &fileName, QIODevice::OpenMode mode = (QIODevice::WriteOnly | QIODevice::Truncate) ); @@ -77,5 +74,4 @@ private: QT_END_NAMESPACE -#endif // QT_NO_TEXTODFWRITER #endif // QZIPWRITER_H diff --git a/src/corelib/ipc/qsharedmemory.cpp b/src/corelib/ipc/qsharedmemory.cpp new file mode 100644 index 00000000..8dcdc43e --- /dev/null +++ b/src/corelib/ipc/qsharedmemory.cpp @@ -0,0 +1,697 @@ +// 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 "qsharedmemory.h" +#include "qsharedmemory_p.h" + +#include "qtipccommon_p.h" +#include "qsystemsemaphore.h" + +#include +#include +#ifdef Q_OS_WIN +# include +#endif + +#ifndef MAX_PATH +# define MAX_PATH PATH_MAX +#endif + +QT_BEGIN_NAMESPACE + +#if QT_CONFIG(sharedmemory) + +using namespace QtIpcCommon; +using namespace Qt::StringLiterals; + +QSharedMemoryPrivate::~QSharedMemoryPrivate() +{ + destructBackend(); +} + +inline void QSharedMemoryPrivate::constructBackend() +{ + using namespace q20; + visit([](auto p) { construct_at(p); }); +} + +inline void QSharedMemoryPrivate::destructBackend() +{ + visit([](auto p) { std::destroy_at(p); }); +} + +#if QT_CONFIG(systemsemaphore) +inline QNativeIpcKey QSharedMemoryPrivate::semaphoreNativeKey() const +{ + if (isIpcSupported(IpcType::SharedMemory, QNativeIpcKey::Type::Windows) + && nativeKey.type() == QNativeIpcKey::Type::Windows) { + // native keys are plain kernel object names, limited to MAX_PATH + auto suffix = "_sem"_L1; + QString semkey = nativeKey.nativeKey(); + semkey.truncate(MAX_PATH - suffix.size() - 1); + semkey += suffix; + return { semkey, QNativeIpcKey::Type::Windows }; + } + + // System V and POSIX keys appear to operate in different namespaces, so we + // can just use the same native key + return nativeKey; +} +#endif + +/*! + \class QSharedMemory + \inmodule QtCore + \since 4.4 + + \brief The QSharedMemory class provides access to a shared memory segment. + + QSharedMemory provides access to a \l{Shared Memory}{shared memory segment} + by multiple threads and processes. Shared memory segments are identified by a + key, represented by \l QNativeIpcKey. A key can be created in a + cross-platform manner by using platformSafeKey(). + + One QSharedMemory object must create() the segment and this call specifies + the size of the segment. All other processes simply attach() to the segment + that must already exist. After either operation is successful, the + application may call data() to obtain a pointer to the data. + + To support non-atomic operations, QSharedMemory provides API to gain + exclusive access: you may lock the shared memory with lock() before reading + from or writing to the shared memory, but remember to release the lock with + unlock() after you are done. + + By default, QSharedMemory automatically destroys the shared memory segment + when the last instance of QSharedMemory is \l{detach()}{detached} from the + segment, and no references to the segment remain. + + For details on the key types, platform-specific limitations, and + interoperability with older or non-Qt applications, see the \l{Native IPC + Keys} documentation. That includes important information for sandboxed + applications on Apple platforms, including all apps obtained via the Apple + App Store. + + \sa {Inter-Process Communication}, QSystemSemaphore + */ + +/*! + \overload QSharedMemory() + + Constructs a shared memory object with the given \a parent. The shared memory + object's key is not set by the constructor, so the shared memory object does + not have an underlying shared memory segment attached. The key must be set + with setNativeKey() before create() or attach() can be used. + + \sa setNativeKey() + */ + +QSharedMemory::QSharedMemory(QObject *parent) + : QSharedMemory(QNativeIpcKey(), parent) +{ +} + +/*! + \overload + + Constructs a shared memory object with the given \a parent and with + its key set to \a key. Because its key is set, its create() and + attach() functions can be called. + + \sa setNativeKey(), create(), attach() + */ +QSharedMemory::QSharedMemory(const QNativeIpcKey &key, QObject *parent) + : QObject(*new QSharedMemoryPrivate(key.type()), parent) +{ + setNativeKey(key); +} + +#if QT_DEPRECATED_SINCE(6, 10) +/*! + \deprecated + + Constructs a shared memory object with the given \a parent and with + the legacy key set to \a key. Because its key is set, its create() and + attach() functions can be called. + + Legacy keys are deprecated. See \l{Native IPC Keys} for more information. + + \sa setKey(), create(), attach() + */ +QSharedMemory::QSharedMemory(const QString &key, QObject *parent) + : QSharedMemory(legacyNativeKey(key), parent) +{ +} +#endif + +/*! + The destructor clears the key, which forces the shared memory object + to \l {detach()} {detach} from its underlying shared memory + segment. If this shared memory object is the last one connected to + the shared memory segment, the detach() operation destroys the + shared memory segment. + + \sa detach(), isAttached() + */ +QSharedMemory::~QSharedMemory() +{ + Q_D(QSharedMemory); + if (isAttached()) + detach(); + d->cleanHandle(); +} + +#if QT_DEPRECATED_SINCE(6, 10) +/*! + \deprecated + \overload + + Sets the legacy \a key for this shared memory object. If \a key is the same + as the current key, the function returns without doing anything. Otherwise, + if the shared memory object is attached to an underlying shared memory + segment, it will \l {detach()} {detach} from it before setting the new key. + This function does not do an attach(). + + You can call key() to retrieve the legacy key. This function is mostly the + same as: + + \code + shm.setNativeKey(QSharedMemory::legacyNativeKey(key)); + \endcode + + except that it enables obtaining the legacy key using key(). + + \sa key(), nativeKey(), isAttached() +*/ +void QSharedMemory::setKey(const QString &key) +{ + setNativeKey(legacyNativeKey(key)); +} +#endif + +/*! + \since 4.8 + \fn void QSharedMemory::setNativeKey(const QString &key, QNativeIpcKey::Type type) + + Sets the native, platform specific, \a key for this shared memory object of + type \a type (the type parameter has been available since Qt 6.6). If \a key + is the same as the current native key, the function returns without doing + anything. Otherwise, if the shared memory object is attached to an underlying + shared memory segment, it will \l {detach()} {detach} from it before setting + the new key. This function does not do an attach(). + + This function is useful if the native key was shared from another process, + though the application must take care to ensure the key type matches what the + other process expects. See \l{Native IPC Keys} for more information. + + Portable native keys can be obtained using platformSafeKey(). + + You can call nativeKey() to retrieve the native key. + + \sa nativeKey(), nativeIpcKey(), isAttached() +*/ + +/*! + \since 6.6 + + Sets the native, platform specific, \a key for this shared memory object. If + \a key is the same as the current native key, the function returns without + doing anything. Otherwise, if the shared memory object is attached to an + underlying shared memory segment, it will \l {detach()} {detach} from it + before setting the new key. This function does not do an attach(). + + This function is useful if the native key was shared from another process. + See \l{Native IPC Keys} for more information. + + Portable native keys can be obtained using platformSafeKey(). + + You can call nativeKey() to retrieve the native key. + + \sa nativeKey(), nativeIpcKey(), isAttached() +*/ +void QSharedMemory::setNativeKey(const QNativeIpcKey &key) +{ + Q_D(QSharedMemory); + if (key == d->nativeKey && key.isEmpty()) + return; + if (!isKeyTypeSupported(key.type())) { + d->setError(KeyError, tr("%1: unsupported key type") + .arg("QSharedMemory::setNativeKey"_L1)); + return; + } + + if (isAttached()) + detach(); + d->cleanHandle(); + if (key.type() == d->nativeKey.type()) { + // we can reuse the backend + d->nativeKey = key; + } else { + // we must recreate the backend + d->destructBackend(); + d->nativeKey = key; + d->constructBackend(); + } +} + +bool QSharedMemoryPrivate::initKey(SemaphoreAccessMode mode) +{ + if (!cleanHandle()) + return false; +#if QT_CONFIG(systemsemaphore) + systemSemaphore.setNativeKey(semaphoreNativeKey(), 1, mode); + if (systemSemaphore.error() != QSystemSemaphore::NoError) { + QString function = "QSharedMemoryPrivate::initKey"_L1; + errorString = QSharedMemory::tr("%1: unable to set key on lock (%2)") + .arg(function, systemSemaphore.errorString()); + switch(systemSemaphore.error()) { + case QSystemSemaphore::PermissionDenied: + error = QSharedMemory::PermissionDenied; + break; + case QSystemSemaphore::KeyError: + error = QSharedMemory::KeyError; + break; + case QSystemSemaphore::AlreadyExists: + error = QSharedMemory::AlreadyExists; + break; + case QSystemSemaphore::NotFound: + error = QSharedMemory::NotFound; + break; + case QSystemSemaphore::OutOfResources: + error = QSharedMemory::OutOfResources; + break; + case QSystemSemaphore::UnknownError: + default: + error = QSharedMemory::UnknownError; + break; + } + return false; + } +#else + Q_UNUSED(mode); +#endif + errorString = QString(); + error = QSharedMemory::NoError; + return true; +} + +#if QT_DEPRECATED_SINCE(6, 10) +/*! + \deprecated + Returns the legacy key assigned with setKey() to this shared memory, or a null key + if no key has been assigned, or if the segment is using a nativeKey(). The + key is the identifier used by Qt applications to identify the shared memory + segment. + + You can find the native, platform specific, key used by the operating system + by calling nativeKey(). + + \sa setKey(), setNativeKey() + */ +QString QSharedMemory::key() const +{ + Q_D(const QSharedMemory); + return QNativeIpcKeyPrivate::legacyKey(d->nativeKey); +} +#endif + +/*! + \since 4.8 + + Returns the native, platform specific, key for this shared memory object. The + native key is the identifier used by the operating system to identify the + shared memory segment. + + You can use the native key to access shared memory segments that have not + been created by Qt, or to grant shared memory access to non-Qt applications. + See \l{Native IPC Keys} for more information. + + \sa setNativeKey(), nativeIpcKey() +*/ +QString QSharedMemory::nativeKey() const +{ + Q_D(const QSharedMemory); + return d->nativeKey.nativeKey(); +} + +/*! + \since 6.6 + + Returns the key type for this shared memory object. The key type complements + the nativeKey() as the identifier used by the operating system to identify + the shared memory segment. + + You can use the native key to access shared memory segments that have not + been created by Qt, or to grant shared memory access to non-Qt applications. + See \l{Native IPC Keys} for more information. + + \sa nativeKey(), setNativeKey() +*/ +QNativeIpcKey QSharedMemory::nativeIpcKey() const +{ + Q_D(const QSharedMemory); + return d->nativeKey; +} + +/*! + Creates a shared memory segment of \a size bytes with the key passed to the + constructor or set with setNativeKey(), then attaches to + the new shared memory segment with the given access \a mode and returns + \tt true. If a shared memory segment identified by the key already exists, + the attach operation is not performed and \tt false is returned. When the + return value is \tt false, call error() to determine which error occurred. + + \sa error() + */ +bool QSharedMemory::create(qsizetype size, AccessMode mode) +{ + Q_D(QSharedMemory); + QLatin1StringView function = "QSharedMemory::create"_L1; + +#if QT_CONFIG(systemsemaphore) + if (!d->initKey(QSystemSemaphore::Create)) + return false; + QSharedMemoryLocker lock(this); + if (!d->nativeKey.isEmpty() && !d->tryLocker(&lock, function)) + return false; +#else + if (!d->initKey({})) + return false; +#endif + + if (size <= 0) { + d->error = QSharedMemory::InvalidSize; + d->errorString = + QSharedMemory::tr("%1: create size is less then 0").arg(function); + return false; + } + + if (!d->create(size)) + return false; + + return d->attach(mode); +} + +/*! + Returns the size of the attached shared memory segment. If no shared + memory segment is attached, 0 is returned. + + \note The size of the segment may be larger than the requested size that was + passed to create(). + + \sa create(), attach() + */ +qsizetype QSharedMemory::size() const +{ + Q_D(const QSharedMemory); + return d->size; +} + +/*! + \enum QSharedMemory::AccessMode + + \value ReadOnly The shared memory segment is read-only. Writing to + the shared memory segment is not allowed. An attempt to write to a + shared memory segment created with ReadOnly causes the program to + abort. + + \value ReadWrite Reading and writing the shared memory segment are + both allowed. +*/ + +/*! + Attempts to attach the process to the shared memory segment + identified by the key that was passed to the constructor or to a + call to setNativeKey(). The access \a mode is \l {QSharedMemory::} + {ReadWrite} by default. It can also be \l {QSharedMemory::} + {ReadOnly}. Returns \c true if the attach operation is successful. If + false is returned, call error() to determine which error occurred. + After attaching the shared memory segment, a pointer to the shared + memory can be obtained by calling data(). + + \sa isAttached(), detach(), create() + */ +bool QSharedMemory::attach(AccessMode mode) +{ + Q_D(QSharedMemory); + + if (isAttached() || !d->initKey({})) + return false; +#if QT_CONFIG(systemsemaphore) + QSharedMemoryLocker lock(this); + if (!d->nativeKey.isEmpty() && !d->tryLocker(&lock, "QSharedMemory::attach"_L1)) + return false; +#endif + + if (isAttached() || !d->handle()) + return false; + + return d->attach(mode); +} + +/*! + Returns \c true if this process is attached to the shared memory + segment. + + \sa attach(), detach() + */ +bool QSharedMemory::isAttached() const +{ + Q_D(const QSharedMemory); + return (nullptr != d->memory); +} + +/*! + Detaches the process from the shared memory segment. If this was the + last process attached to the shared memory segment, then the shared + memory segment is released by the system, i.e., the contents are + destroyed. The function returns \c true if it detaches the shared + memory segment. If it returns \c false, it usually means the segment + either isn't attached, or it is locked by another process. + + \sa attach(), isAttached() + */ +bool QSharedMemory::detach() +{ + Q_D(QSharedMemory); + if (!isAttached()) + return false; + +#if QT_CONFIG(systemsemaphore) + QSharedMemoryLocker lock(this); + if (!d->nativeKey.isEmpty() && !d->tryLocker(&lock, "QSharedMemory::detach"_L1)) + return false; +#endif + + return d->detach(); +} + +/*! + Returns a pointer to the contents of the shared memory segment, if one is + attached. Otherwise it returns null. The value returned by this function will + not change until a \l {detach()}{detach} happens, so it is safe to store this + pointer. + + If the memory operations are not atomic, you may lock the shared memory with + lock() before reading from or writing, but remember to release the lock with + unlock() after you are done. + + \sa attach() + */ +void *QSharedMemory::data() +{ + Q_D(QSharedMemory); + return d->memory; +} + +/*! + Returns a const pointer to the contents of the shared memory segment, if one + is attached. Otherwise it returns null. The value returned by this function + will not change until a \l {detach()}{detach} happens, so it is safe to store + this pointer. + + If the memory operations are not atomic, you may lock the shared memory with + lock() before reading from or writing, but remember to release the lock with + unlock() after you are done. + + \sa attach(), create() + */ +const void *QSharedMemory::constData() const +{ + Q_D(const QSharedMemory); + return d->memory; +} + +/*! + \overload data() + */ +const void *QSharedMemory::data() const +{ + Q_D(const QSharedMemory); + return d->memory; +} + +#if QT_CONFIG(systemsemaphore) +/*! + This is a semaphore that locks the shared memory segment for access + by this process and returns \c true. If another process has locked the + segment, this function blocks until the lock is released. Then it + acquires the lock and returns \c true. If this function returns \c false, + it means that you have ignored a false return from create() or attach(), + that you have set the key with setNativeKey() or that + QSystemSemaphore::acquire() failed due to an unknown system error. + + \sa unlock(), data(), QSystemSemaphore::acquire() + */ +bool QSharedMemory::lock() +{ + Q_D(QSharedMemory); + if (d->lockedByMe) { + qWarning("QSharedMemory::lock: already locked"); + return true; + } + if (d->systemSemaphore.acquire()) { + d->lockedByMe = true; + return true; + } + const auto function = "QSharedMemory::lock"_L1; + d->errorString = QSharedMemory::tr("%1: unable to lock").arg(function); + d->error = QSharedMemory::LockError; + return false; +} + +/*! + Releases the lock on the shared memory segment and returns \c true, if + the lock is currently held by this process. If the segment is not + locked, or if the lock is held by another process, nothing happens + and false is returned. + + \sa lock() + */ +bool QSharedMemory::unlock() +{ + Q_D(QSharedMemory); + if (!d->lockedByMe) + return false; + d->lockedByMe = false; + if (d->systemSemaphore.release()) + return true; + const auto function = "QSharedMemory::unlock"_L1; + d->errorString = QSharedMemory::tr("%1: unable to unlock").arg(function); + d->error = QSharedMemory::LockError; + return false; +} +#endif // QT_CONFIG(systemsemaphore) + +/*! + \enum QSharedMemory::SharedMemoryError + + \value NoError No error occurred. + + \value PermissionDenied The operation failed because the caller + didn't have the required permissions. + + \value InvalidSize A create operation failed because the requested + size was invalid. + + \value KeyError The operation failed because of an invalid key. + + \value AlreadyExists A create() operation failed because a shared + memory segment with the specified key already existed. + + \value NotFound An attach() failed because a shared memory segment + with the specified key could not be found. + + \value LockError The attempt to lock() the shared memory segment + failed because create() or attach() failed and returned false, or + because a system error occurred in QSystemSemaphore::acquire(). + + \value OutOfResources A create() operation failed because there was + not enough memory available to fill the request. + + \value UnknownError Something else happened and it was bad. +*/ + +/*! + Returns a value indicating whether an error occurred, and, if so, + which error it was. + + \sa errorString() + */ +QSharedMemory::SharedMemoryError QSharedMemory::error() const +{ + Q_D(const QSharedMemory); + return d->error; +} + +/*! + Returns a text description of the last error that occurred. If + error() returns an \l {QSharedMemory::SharedMemoryError} {error + value}, call this function to get a text string that describes the + error. + + \sa error() + */ +QString QSharedMemory::errorString() const +{ + Q_D(const QSharedMemory); + return d->errorString; +} + +void QSharedMemoryPrivate::setUnixErrorString(QLatin1StringView function) +{ + // EINVAL is handled in functions so they can give better error strings + switch (errno) { + case EACCES: + errorString = QSharedMemory::tr("%1: permission denied").arg(function); + error = QSharedMemory::PermissionDenied; + break; + case EEXIST: + errorString = QSharedMemory::tr("%1: already exists").arg(function); + error = QSharedMemory::AlreadyExists; + break; + case ENOENT: + errorString = QSharedMemory::tr("%1: doesn't exist").arg(function); + error = QSharedMemory::NotFound; + break; + case EMFILE: + case ENOMEM: + case ENOSPC: + errorString = QSharedMemory::tr("%1: out of resources").arg(function); + error = QSharedMemory::OutOfResources; + break; + default: + errorString = QSharedMemory::tr("%1: unknown error: %2") + .arg(function, qt_error_string(errno)); + error = QSharedMemory::UnknownError; +#if defined QSHAREDMEMORY_DEBUG + qDebug() << errorString << "key" << key << "errno" << errno << EINVAL; +#endif + } +} + +bool QSharedMemory::isKeyTypeSupported(QNativeIpcKey::Type type) +{ + if (!isIpcSupported(IpcType::SharedMemory, type)) + return false; + using Variant = decltype(QSharedMemoryPrivate::backend); + return Variant::staticVisit(type, [](auto ptr) { + using Impl = std::decay_t; + return Impl::runtimeSupportCheck(); + }); +} + +QNativeIpcKey QSharedMemory::platformSafeKey(const QString &key, QNativeIpcKey::Type type) +{ + return QtIpcCommon::platformSafeKey(key, IpcType::SharedMemory, type); +} + +QNativeIpcKey QSharedMemory::legacyNativeKey(const QString &key, QNativeIpcKey::Type type) +{ + return QtIpcCommon::legacyPlatformSafeKey(key, IpcType::SharedMemory, type); +} + +#endif // QT_CONFIG(sharedmemory) + +QT_END_NAMESPACE + +#include "moc_qsharedmemory.cpp" diff --git a/src/corelib/kernel/qsharedmemory.h b/src/corelib/ipc/qsharedmemory.h similarity index 60% rename from src/corelib/kernel/qsharedmemory.h rename to src/corelib/ipc/qsharedmemory.h index 653fb1fb..f8218cb0 100644 --- a/src/corelib/kernel/qsharedmemory.h +++ b/src/corelib/ipc/qsharedmemory.h @@ -4,7 +4,7 @@ #ifndef QSHAREDMEMORY_H #define QSHAREDMEMORY_H -#include +#include #ifndef QT_NO_QOBJECT # include #else @@ -12,8 +12,8 @@ # include # include #endif -QT_BEGIN_NAMESPACE +QT_BEGIN_NAMESPACE #if QT_CONFIG(sharedmemory) @@ -30,6 +30,7 @@ public: ReadOnly, ReadWrite }; + Q_ENUM(AccessMode) enum SharedMemoryError { @@ -43,15 +44,29 @@ public: OutOfResources, UnknownError }; + Q_ENUM(SharedMemoryError) QSharedMemory(QObject *parent = nullptr); - QSharedMemory(const QString &key, QObject *parent = nullptr); + QSharedMemory(const QNativeIpcKey &key, QObject *parent = nullptr); ~QSharedMemory(); +#if QT_DEPRECATED_SINCE(6, 10) + QT_DEPRECATED_VERSION_X_6_10("Please refer to 'Native IPC Key' documentation") + QSharedMemory(const QString &key, QObject *parent = nullptr); + QT_DEPRECATED_VERSION_X_6_10("Please refer to 'Native IPC Key' documentation") void setKey(const QString &key); + QT_DEPRECATED_VERSION_X_6_10("Please refer to 'Native IPC Key' documentation") QString key() const; - void setNativeKey(const QString &key); +#endif + + void setNativeKey(const QNativeIpcKey &key); + void setNativeKey(const QString &key, QNativeIpcKey::Type type = QNativeIpcKey::legacyDefaultTypeForOs()) + { setNativeKey({ key, type }); } QString nativeKey() const; + QNativeIpcKey nativeIpcKey() const; +#if QT_CORE_REMOVED_SINCE(6, 5) + void setNativeKey(const QString &key); +#endif bool create(qsizetype size, AccessMode mode = ReadWrite); qsizetype size() const; @@ -72,6 +87,12 @@ public: SharedMemoryError error() const; QString errorString() const; + static bool isKeyTypeSupported(QNativeIpcKey::Type type) Q_DECL_CONST_FUNCTION; + static QNativeIpcKey platformSafeKey(const QString &key, + QNativeIpcKey::Type type = QNativeIpcKey::DefaultTypeForOs); + static QNativeIpcKey legacyNativeKey(const QString &key, + QNativeIpcKey::Type type = QNativeIpcKey::legacyDefaultTypeForOs()); + private: Q_DISABLE_COPY(QSharedMemory) }; @@ -81,4 +102,3 @@ private: QT_END_NAMESPACE #endif // QSHAREDMEMORY_H - diff --git a/src/corelib/ipc/qsharedmemory_p.h b/src/corelib/ipc/qsharedmemory_p.h new file mode 100644 index 00000000..987bb386 --- /dev/null +++ b/src/corelib/ipc/qsharedmemory_p.h @@ -0,0 +1,215 @@ +// 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 + +#ifndef QSHAREDMEMORY_P_H +#define QSHAREDMEMORY_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 "qsharedmemory.h" + +#include + +#if QT_CONFIG(sharedmemory) +#include "qsystemsemaphore.h" +#include "qtipccommon_p.h" +#include "private/qobject_p.h" + +#if QT_CONFIG(posix_shm) +# include +#endif +#if QT_CONFIG(sysv_shm) +# include +#endif + +QT_BEGIN_NAMESPACE + +class QSharedMemoryPrivate; + +#if QT_CONFIG(systemsemaphore) +/*! + Helper class + */ +class QSharedMemoryLocker +{ + +public: + Q_NODISCARD_CTOR QSharedMemoryLocker(QSharedMemory *sharedMemory) : q_sm(sharedMemory) + { + Q_ASSERT(q_sm); + } + + inline ~QSharedMemoryLocker() + { + if (q_sm) + q_sm->unlock(); + } + + inline bool lock() + { + if (q_sm && q_sm->lock()) + return true; + q_sm = nullptr; + return false; + } + +private: + QSharedMemory *q_sm; +}; +#endif // QT_CONFIG(systemsemaphore) + +class QSharedMemoryPosix +{ +public: + static constexpr bool Enabled = QT_CONFIG(posix_shm); + static bool supports(QNativeIpcKey::Type type) + { return type == QNativeIpcKey::Type::PosixRealtime; } + static bool runtimeSupportCheck(); + + bool handle(QSharedMemoryPrivate *self); + bool cleanHandle(QSharedMemoryPrivate *self); + bool create(QSharedMemoryPrivate *self, qsizetype size); + bool attach(QSharedMemoryPrivate *self, QSharedMemory::AccessMode mode); + bool detach(QSharedMemoryPrivate *self); + + int hand = -1; +}; + +class QSharedMemorySystemV +{ +public: + static constexpr bool Enabled = QT_CONFIG(sysv_shm); + static bool supports(QNativeIpcKey::Type type) + { return quint16(type) <= 0xff; } + static bool runtimeSupportCheck(); + +#if QT_CONFIG(sysv_sem) + key_t handle(QSharedMemoryPrivate *self); + bool cleanHandle(QSharedMemoryPrivate *self); + bool create(QSharedMemoryPrivate *self, qsizetype size); + bool attach(QSharedMemoryPrivate *self, QSharedMemory::AccessMode mode); + bool detach(QSharedMemoryPrivate *self); + +private: + void updateNativeKeyFile(const QNativeIpcKey &nativeKey); + + QByteArray nativeKeyFile; + key_t unix_key = 0; +#endif +}; + +class QSharedMemoryWin32 +{ +public: +#ifdef Q_OS_WIN32 + static constexpr bool Enabled = true; +#else + static constexpr bool Enabled = false; +#endif + static bool runtimeSupportCheck() { return Enabled; } + static bool supports(QNativeIpcKey::Type type) + { return type == QNativeIpcKey::Type::Windows; } + + Qt::HANDLE handle(QSharedMemoryPrivate *self); + bool cleanHandle(QSharedMemoryPrivate *self); + bool create(QSharedMemoryPrivate *self, qsizetype size); + bool attach(QSharedMemoryPrivate *self, QSharedMemory::AccessMode mode); + bool detach(QSharedMemoryPrivate *self); + + Qt::HANDLE hand = nullptr; +}; + +class Q_AUTOTEST_EXPORT QSharedMemoryPrivate : public QObjectPrivate +{ + Q_DECLARE_PUBLIC(QSharedMemory) + +public: + QSharedMemoryPrivate(QNativeIpcKey::Type type) : nativeKey(type) + { constructBackend(); } + ~QSharedMemoryPrivate(); + + void *memory = nullptr; + qsizetype size = 0; + QNativeIpcKey nativeKey; + QString errorString; +#if QT_CONFIG(systemsemaphore) + using SemaphoreAccessMode = QSystemSemaphore::AccessMode; + QSystemSemaphore systemSemaphore{ QNativeIpcKey() }; + bool lockedByMe = false; +#else + enum SemaphoreAccessMode {}; +#endif + QSharedMemory::SharedMemoryError error = QSharedMemory::NoError; + + union Backend { + Backend() {} + ~Backend() {} + QSharedMemoryPosix posix; + QSharedMemorySystemV sysv; + QSharedMemoryWin32 win32; + }; + QtIpcCommon::IpcStorageVariant<&Backend::posix, &Backend::sysv, &Backend::win32> backend; + + void constructBackend(); + void destructBackend(); + bool initKey(SemaphoreAccessMode mode); + + template auto visit(const Lambda &lambda) + { + return backend.visit(nativeKey.type(), lambda); + } + + bool handle() + { + return visit([&](auto p) { return !!p->handle(this); }); + } + bool cleanHandle() + { + return visit([&](auto p) { return p->cleanHandle(this); }); + } + bool create(qsizetype size) + { + return visit([&](auto p) { return p->create(this, size); }); + } + bool attach(QSharedMemory::AccessMode mode) + { + return visit([&](auto p) { return p->attach(this, mode); }); + } + bool detach() + { + return visit([&](auto p) { return p->detach(this); }); + } + + inline void setError(QSharedMemory::SharedMemoryError e, const QString &message) + { error = e; errorString = message; } + void setUnixErrorString(QLatin1StringView function); + void setWindowsErrorString(QLatin1StringView function); + +#if QT_CONFIG(systemsemaphore) + bool tryLocker(QSharedMemoryLocker *locker, const QString &function) { + if (!locker->lock()) { + errorString = QSharedMemory::tr("%1: unable to lock").arg(function); + error = QSharedMemory::LockError; + return false; + } + return true; + } + QNativeIpcKey semaphoreNativeKey() const; +#endif // QT_CONFIG(systemsemaphore) +}; + +QT_END_NAMESPACE + +#endif // QT_CONFIG(sharedmemory) + +#endif // QSHAREDMEMORY_P_H + diff --git a/src/corelib/kernel/qsharedmemory_posix.cpp b/src/corelib/ipc/qsharedmemory_posix.cpp similarity index 58% rename from src/corelib/kernel/qsharedmemory_posix.cpp rename to src/corelib/ipc/qsharedmemory_posix.cpp index ac316b9d..88448100 100644 --- a/src/corelib/kernel/qsharedmemory_posix.cpp +++ b/src/corelib/ipc/qsharedmemory_posix.cpp @@ -3,18 +3,15 @@ // Copyright (C) 2015 Klarälvdalens Datakonsult AB, a KDAB Group company, info@kdab.com, author Tobias Koenig // SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only -#include "qplatformdefs.h" - #include "qsharedmemory.h" #include "qsharedmemory_p.h" -#include "qsystemsemaphore.h" +#include "qtipccommon_p.h" #include #include -#ifdef QT_POSIX_IPC - #if QT_CONFIG(sharedmemory) +#if QT_CONFIG(posix_shm) #include #include #include @@ -23,57 +20,64 @@ #include "private/qcore_unix_p.h" +#ifndef O_CLOEXEC +# define O_CLOEXEC 0 +#endif + QT_BEGIN_NAMESPACE using namespace Qt::StringLiterals; +using namespace QtIpcCommon; -int QSharedMemoryPrivate::handle() +bool QSharedMemoryPosix::runtimeSupportCheck() { - // don't allow making handles on empty keys - const QString safeKey = makePlatformSafeKey(key); - if (safeKey.isEmpty()) { - errorString = QSharedMemory::tr("%1: key is empty").arg("QSharedMemory::handle"_L1); - error = QSharedMemory::KeyError; - return 0; - } - - return 1; + static const bool result = []() { + (void)shm_open("", 0, 0); // this WILL fail + return errno != ENOSYS; + }(); + return result; } -bool QSharedMemoryPrivate::cleanHandle() +bool QSharedMemoryPosix::handle(QSharedMemoryPrivate *self) { - qt_safe_close(hand); + // don't allow making handles on empty keys + if (self->nativeKey.isEmpty()) { + self->setError(QSharedMemory::KeyError, + QSharedMemory::tr("%1: key is empty").arg("QSharedMemory::handle"_L1)); + return false; + } + + return true; +} + +bool QSharedMemoryPosix::cleanHandle(QSharedMemoryPrivate *) +{ + if (hand != -1) + qt_safe_close(hand); hand = -1; return true; } -bool QSharedMemoryPrivate::create(qsizetype size) +bool QSharedMemoryPosix::create(QSharedMemoryPrivate *self, qsizetype size) { - if (!handle()) + if (!handle(self)) return false; - const QByteArray shmName = QFile::encodeName(makePlatformSafeKey(key)); + const QByteArray shmName = QFile::encodeName(self->nativeKey.nativeKey()); int fd; -#ifdef O_CLOEXEC - // First try with O_CLOEXEC flag, if that fails, fall back to normal flags EINTR_LOOP(fd, ::shm_open(shmName.constData(), O_RDWR | O_CREAT | O_EXCL | O_CLOEXEC, 0600)); - if (fd == -1) - EINTR_LOOP(fd, ::shm_open(shmName.constData(), O_RDWR | O_CREAT | O_EXCL, 0600)); -#else - EINTR_LOOP(fd, ::shm_open(shmName.constData(), O_RDWR | O_CREAT | O_EXCL, 0600)); -#endif if (fd == -1) { const int errorNumber = errno; const auto function = "QSharedMemory::attach (shm_open)"_L1; switch (errorNumber) { case EINVAL: - errorString = QSharedMemory::tr("%1: bad name").arg(function); - error = QSharedMemory::KeyError; + self->setError(QSharedMemory::KeyError, + QSharedMemory::tr("%1: bad name").arg(function)); break; default: - setErrorString(function); + self->setUnixErrorString(function); } return false; } @@ -82,7 +86,7 @@ bool QSharedMemoryPrivate::create(qsizetype size) int ret; EINTR_LOOP(ret, QT_FTRUNCATE(fd, size)); if (ret == -1) { - setErrorString("QSharedMemory::create (ftruncate)"_L1); + self->setUnixErrorString("QSharedMemory::create (ftruncate)"_L1); qt_safe_close(fd); return false; } @@ -92,31 +96,24 @@ bool QSharedMemoryPrivate::create(qsizetype size) return true; } -bool QSharedMemoryPrivate::attach(QSharedMemory::AccessMode mode) +bool QSharedMemoryPosix::attach(QSharedMemoryPrivate *self, QSharedMemory::AccessMode mode) { - const QByteArray shmName = QFile::encodeName(makePlatformSafeKey(key)); + const QByteArray shmName = QFile::encodeName(self->nativeKey.nativeKey()); const int oflag = (mode == QSharedMemory::ReadOnly ? O_RDONLY : O_RDWR); const mode_t omode = (mode == QSharedMemory::ReadOnly ? 0400 : 0600); -#ifdef O_CLOEXEC - // First try with O_CLOEXEC flag, if that fails, fall back to normal flags EINTR_LOOP(hand, ::shm_open(shmName.constData(), oflag | O_CLOEXEC, omode)); - if (hand == -1) - EINTR_LOOP(hand, ::shm_open(shmName.constData(), oflag, omode)); -#else - EINTR_LOOP(hand, ::shm_open(shmName.constData(), oflag, omode)); -#endif if (hand == -1) { const int errorNumber = errno; const auto function = "QSharedMemory::attach (shm_open)"_L1; switch (errorNumber) { case EINVAL: - errorString = QSharedMemory::tr("%1: bad name").arg(function); - error = QSharedMemory::KeyError; + self->setError(QSharedMemory::KeyError, + QSharedMemory::tr("%1: bad name").arg(function)); break; default: - setErrorString(function); + self->setUnixErrorString(function); } hand = -1; return false; @@ -125,20 +122,20 @@ bool QSharedMemoryPrivate::attach(QSharedMemory::AccessMode mode) // grab the size QT_STATBUF st; if (QT_FSTAT(hand, &st) == -1) { - setErrorString("QSharedMemory::attach (fstat)"_L1); - cleanHandle(); + self->setUnixErrorString("QSharedMemory::attach (fstat)"_L1); + cleanHandle(self); return false; } - size = qsizetype(st.st_size); + self->size = qsizetype(st.st_size); // grab the memory const int mprot = (mode == QSharedMemory::ReadOnly ? PROT_READ : PROT_READ | PROT_WRITE); - memory = QT_MMAP(0, size_t(size), mprot, MAP_SHARED, hand, 0); - if (memory == MAP_FAILED || !memory) { - setErrorString("QSharedMemory::attach (mmap)"_L1); - cleanHandle(); - memory = 0; - size = 0; + self->memory = QT_MMAP(0, size_t(self->size), mprot, MAP_SHARED, hand, 0); + if (self->memory == MAP_FAILED || !self->memory) { + self->setUnixErrorString("QSharedMemory::attach (mmap)"_L1); + cleanHandle(self); + self->memory = 0; + self->size = 0; return false; } @@ -152,15 +149,15 @@ bool QSharedMemoryPrivate::attach(QSharedMemory::AccessMode mode) return true; } -bool QSharedMemoryPrivate::detach() +bool QSharedMemoryPosix::detach(QSharedMemoryPrivate *self) { // detach from the memory segment - if (::munmap(memory, size_t(size)) == -1) { - setErrorString("QSharedMemory::detach (munmap)"_L1); + if (::munmap(self->memory, size_t(self->size)) == -1) { + self->setUnixErrorString("QSharedMemory::detach (munmap)"_L1); return false; } - memory = 0; - size = 0; + self->memory = 0; + self->size = 0; #ifdef Q_OS_QNX // On QNX the st_nlink field of struct stat contains the number of @@ -176,18 +173,18 @@ bool QSharedMemoryPrivate::detach() shm_nattch = st.st_nlink - 2; } - cleanHandle(); + cleanHandle(self); // if there are no attachments then unlink the shared memory if (shm_nattch == 0) { - const QByteArray shmName = QFile::encodeName(makePlatformSafeKey(key)); + const QByteArray shmName = QFile::encodeName(self->nativeKey.nativeKey()); if (::shm_unlink(shmName.constData()) == -1 && errno != ENOENT) - setErrorString("QSharedMemory::detach (shm_unlink)"_L1); + self->setUnixErrorString("QSharedMemory::detach (shm_unlink)"_L1); } #else // On non-QNX systems (tested Linux and Haiku), the st_nlink field is always 1, // so we'll simply leak the shared memory files. - cleanHandle(); + cleanHandle(self); #endif return true; @@ -195,6 +192,5 @@ bool QSharedMemoryPrivate::detach() QT_END_NAMESPACE +#endif // QT_CONFIG(posix_shm) #endif // QT_CONFIG(sharedmemory) - -#endif // QT_POSIX_IPC diff --git a/src/corelib/ipc/qsharedmemory_systemv.cpp b/src/corelib/ipc/qsharedmemory_systemv.cpp new file mode 100644 index 00000000..716b7aed --- /dev/null +++ b/src/corelib/ipc/qsharedmemory_systemv.cpp @@ -0,0 +1,209 @@ +// 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 "qsharedmemory.h" +#include "qsharedmemory_p.h" + +#include "qtipccommon_p.h" + +#include +#include + +#include + +#if QT_CONFIG(sharedmemory) +#if QT_CONFIG(sysv_shm) +#include +#include +#include +#include +#include +#include +#include + +#include "private/qcore_unix_p.h" +#if defined(Q_OS_DARWIN) +#include "private/qcore_mac_p.h" +#endif + +QT_BEGIN_NAMESPACE + +using namespace Qt::StringLiterals; +using namespace QtIpcCommon; + +bool QSharedMemorySystemV::runtimeSupportCheck() +{ +#if defined(Q_OS_DARWIN) + if (qt_apple_isSandboxed()) + return false; +#endif + static const bool result = []() { + (void)shmget(IPC_PRIVATE, ~size_t(0), 0); // this will fail + return errno != ENOSYS; + }(); + return result; +} + + +inline void QSharedMemorySystemV::updateNativeKeyFile(const QNativeIpcKey &nativeKey) +{ + Q_ASSERT(nativeKeyFile.isEmpty() ); + if (!nativeKey.nativeKey().isEmpty()) + nativeKeyFile = QFile::encodeName(nativeKey.nativeKey()); +} + +/*! + \internal + + If not already made create the handle used for accessing the shared memory. +*/ +key_t QSharedMemorySystemV::handle(QSharedMemoryPrivate *self) +{ + // already made + if (unix_key) + return unix_key; + + // don't allow making handles on empty keys + if (nativeKeyFile.isEmpty()) + updateNativeKeyFile(self->nativeKey); + if (nativeKeyFile.isEmpty()) { + self->setError(QSharedMemory::KeyError, + QSharedMemory::tr("%1: key is empty") + .arg("QSharedMemory::handle:"_L1)); + return 0; + } + + unix_key = ftok(nativeKeyFile, int(self->nativeKey.type())); + if (unix_key < 0) { + self->setUnixErrorString("QSharedMemory::handle"_L1); + unix_key = 0; + } + return unix_key; +} + +bool QSharedMemorySystemV::cleanHandle(QSharedMemoryPrivate *self) +{ + if (unix_key == 0) + return true; + + // Get the number of current attachments + struct shmid_ds shmid_ds; + QByteArray keyfile = std::exchange(nativeKeyFile, QByteArray()); + + int id = shmget(unix_key, 0, 0400); + unix_key = 0; + if (shmctl(id, IPC_STAT, &shmid_ds)) + return errno != EINVAL; + + // If there are still attachments, keep the keep file and shm + if (shmid_ds.shm_nattch != 0) + return true; + + if (shmctl(id, IPC_RMID, &shmid_ds) < 0) { + if (errno != EINVAL) { + self->setUnixErrorString("QSharedMemory::remove"_L1); + return false; + } + }; + + // remove file + return unlink(keyfile) == 0; +} + +bool QSharedMemorySystemV::create(QSharedMemoryPrivate *self, qsizetype size) +{ + // build file if needed + bool createdFile = false; + updateNativeKeyFile(self->nativeKey); + int built = createUnixKeyFile(nativeKeyFile); + if (built == -1) { + self->setError(QSharedMemory::KeyError, + QSharedMemory::tr("%1: unable to make key") + .arg("QSharedMemory::handle:"_L1)); + return false; + } + if (built == 1) { + createdFile = true; + } + + // get handle + if (!handle(self)) { + if (createdFile) + unlink(nativeKeyFile); + return false; + } + + // create + if (-1 == shmget(unix_key, size_t(size), 0600 | IPC_CREAT | IPC_EXCL)) { + const auto function = "QSharedMemory::create"_L1; + switch (errno) { + case EINVAL: + self->setError(QSharedMemory::InvalidSize, + QSharedMemory::tr("%1: system-imposed size restrictions") + .arg("QSharedMemory::handle"_L1)); + break; + default: + self->setUnixErrorString(function); + } + if (createdFile && self->error != QSharedMemory::AlreadyExists) + unlink(nativeKeyFile); + return false; + } + + return true; +} + +bool QSharedMemorySystemV::attach(QSharedMemoryPrivate *self, QSharedMemory::AccessMode mode) +{ + // grab the shared memory segment id + int id = shmget(unix_key, 0, (mode == QSharedMemory::ReadOnly ? 0400 : 0600)); + if (-1 == id) { + self->setUnixErrorString("QSharedMemory::attach (shmget)"_L1); + return false; + } + + // grab the memory + self->memory = shmat(id, nullptr, (mode == QSharedMemory::ReadOnly ? SHM_RDONLY : 0)); + if (self->memory == MAP_FAILED) { + self->memory = nullptr; + self->setUnixErrorString("QSharedMemory::attach (shmat)"_L1); + return false; + } + + // grab the size + shmid_ds shmid_ds; + if (!shmctl(id, IPC_STAT, &shmid_ds)) { + self->size = (qsizetype)shmid_ds.shm_segsz; + } else { + self->setUnixErrorString("QSharedMemory::attach (shmctl)"_L1); + return false; + } + + return true; +} + +bool QSharedMemorySystemV::detach(QSharedMemoryPrivate *self) +{ + // detach from the memory segment + if (shmdt(self->memory) < 0) { + const auto function = "QSharedMemory::detach"_L1; + switch (errno) { + case EINVAL: + self->setError(QSharedMemory::NotFound, + QSharedMemory::tr("%1: not attached").arg(function)); + break; + default: + self->setUnixErrorString(function); + } + return false; + } + self->memory = nullptr; + self->size = 0; + + return cleanHandle(self); +} + +QT_END_NAMESPACE + +#endif // QT_CONFIG(sysv_shm) +#endif // QT_CONFIG(sharedmemory) diff --git a/src/corelib/kernel/qsharedmemory_win.cpp b/src/corelib/ipc/qsharedmemory_win.cpp similarity index 61% rename from src/corelib/kernel/qsharedmemory_win.cpp rename to src/corelib/ipc/qsharedmemory_win.cpp index bf8c625b..472f34f9 100644 --- a/src/corelib/kernel/qsharedmemory_win.cpp +++ b/src/corelib/ipc/qsharedmemory_win.cpp @@ -13,7 +13,7 @@ using namespace Qt::StringLiterals; #if QT_CONFIG(sharedmemory) -void QSharedMemoryPrivate::setErrorString(QLatin1StringView function) +void QSharedMemoryPrivate::setWindowsErrorString(QLatin1StringView function) { DWORD windowsError = GetLastError(); if (windowsError == 0) @@ -41,7 +41,8 @@ void QSharedMemoryPrivate::setErrorString(QLatin1StringView function) errorString = QSharedMemory::tr("%1: permission denied").arg(function); break; default: - errorString = QSharedMemory::tr("%1: unknown error %2").arg(function).arg(windowsError); + errorString = QSharedMemory::tr("%1: unknown error: %2") + .arg(function, qt_error_string(windowsError)); error = QSharedMemory::UnknownError; #if defined QSHAREDMEMORY_DEBUG qDebug() << errorString << "key" << key; @@ -49,42 +50,41 @@ void QSharedMemoryPrivate::setErrorString(QLatin1StringView function) } } -HANDLE QSharedMemoryPrivate::handle() +HANDLE QSharedMemoryWin32::handle(QSharedMemoryPrivate *self) { if (!hand) { const auto function = "QSharedMemory::handle"_L1; - if (nativeKey.isEmpty()) { - error = QSharedMemory::KeyError; - errorString = QSharedMemory::tr("%1: unable to make key").arg(function); + if (self->nativeKey.isEmpty()) { + self->setError(QSharedMemory::KeyError, + QSharedMemory::tr("%1: unable to make key").arg(function)); return 0; } hand = OpenFileMapping(FILE_MAP_ALL_ACCESS, false, - reinterpret_cast(nativeKey.utf16())); + reinterpret_cast(self->nativeKey.nativeKey().utf16())); if (!hand) { - setErrorString(function); + self->setWindowsErrorString(function); return 0; } } return hand; } -bool QSharedMemoryPrivate::cleanHandle() +bool QSharedMemoryWin32::cleanHandle(QSharedMemoryPrivate *) { if (hand != 0 && !CloseHandle(hand)) { hand = 0; - setErrorString("QSharedMemory::cleanHandle"_L1); return false; } hand = 0; return true; } -bool QSharedMemoryPrivate::create(qsizetype size) +bool QSharedMemoryWin32::create(QSharedMemoryPrivate *self, qsizetype size) { const auto function = "QSharedMemory::create"_L1; - if (nativeKey.isEmpty()) { - error = QSharedMemory::KeyError; - errorString = QSharedMemory::tr("%1: key error").arg(function); + if (self->nativeKey.isEmpty()) { + self->setError(QSharedMemory::KeyError, + QSharedMemory::tr("%1: key error").arg(function)); return false; } @@ -96,50 +96,51 @@ bool QSharedMemoryPrivate::create(qsizetype size) high = 0; low = DWORD(size_t(size) & 0xffffffff); hand = CreateFileMapping(INVALID_HANDLE_VALUE, 0, PAGE_READWRITE, high, low, - reinterpret_cast(nativeKey.utf16())); - setErrorString(function); + reinterpret_cast(self->nativeKey.nativeKey().utf16())); + self->setWindowsErrorString(function); // hand is valid when it already exists unlike unix so explicitly check - return error != QSharedMemory::AlreadyExists && hand; + return self->error != QSharedMemory::AlreadyExists && hand; } -bool QSharedMemoryPrivate::attach(QSharedMemory::AccessMode mode) +bool QSharedMemoryWin32::attach(QSharedMemoryPrivate *self, QSharedMemory::AccessMode mode) { // Grab a pointer to the memory block int permissions = (mode == QSharedMemory::ReadOnly ? FILE_MAP_READ : FILE_MAP_ALL_ACCESS); - memory = (void *)MapViewOfFile(handle(), permissions, 0, 0, 0); - if (0 == memory) { - setErrorString("QSharedMemory::attach"_L1); - cleanHandle(); + self->memory = (void *)MapViewOfFile(handle(self), permissions, 0, 0, 0); + if (!self->memory) { + self->setWindowsErrorString("QSharedMemory::attach"_L1); + cleanHandle(self); return false; } // Grab the size of the memory we have been given (a multiple of 4K on windows) MEMORY_BASIC_INFORMATION info; - if (!VirtualQuery(memory, &info, sizeof(info))) { + if (!VirtualQuery(self->memory, &info, sizeof(info))) { // Windows doesn't set an error code on this one, // it should only be a kernel memory error. - error = QSharedMemory::UnknownError; - errorString = QSharedMemory::tr("%1: size query failed").arg("QSharedMemory::attach: "_L1); + self->setError(QSharedMemory::UnknownError, + QSharedMemory::tr("%1: size query failed") + .arg("QSharedMemory::attach: "_L1)); return false; } - size = qsizetype(info.RegionSize); + self->size = qsizetype(info.RegionSize); return true; } -bool QSharedMemoryPrivate::detach() +bool QSharedMemoryWin32::detach(QSharedMemoryPrivate *self) { // umap memory - if (!UnmapViewOfFile(memory)) { - setErrorString("QSharedMemory::detach"_L1); + if (!UnmapViewOfFile(self->memory)) { + self->setWindowsErrorString("QSharedMemory::detach"_L1); return false; } - memory = 0; - size = 0; + self->memory = 0; + self->size = 0; // close handle - return cleanHandle(); + return cleanHandle(self); } #endif // QT_CONFIG(sharedmemory) diff --git a/src/corelib/kernel/qsystemsemaphore.cpp b/src/corelib/ipc/qsystemsemaphore.cpp similarity index 60% rename from src/corelib/kernel/qsystemsemaphore.cpp rename to src/corelib/ipc/qsystemsemaphore.cpp index c4505ea9..520b627c 100644 --- a/src/corelib/kernel/qsystemsemaphore.cpp +++ b/src/corelib/ipc/qsystemsemaphore.cpp @@ -3,11 +3,24 @@ #include "qsystemsemaphore.h" #include "qsystemsemaphore_p.h" -#include + +#if QT_CONFIG(systemsemaphore) +#include QT_BEGIN_NAMESPACE -#if QT_CONFIG(systemsemaphore) +using namespace QtIpcCommon; +using namespace Qt::StringLiterals; + +inline void QSystemSemaphorePrivate::constructBackend() +{ + visit([](auto p) { q20::construct_at(p); }); +} + +inline void QSystemSemaphorePrivate::destructBackend() +{ + visit([](auto p) { std::destroy_at(p); }); +} /*! \class QSystemSemaphore @@ -16,12 +29,10 @@ QT_BEGIN_NAMESPACE \brief The QSystemSemaphore class provides a general counting system semaphore. - A semaphore is a generalization of a mutex. While a mutex can be - locked only once, a semaphore can be acquired multiple times. - Typically, a semaphore is used to protect a certain number of - identical resources. + A system semaphore is a generalization of \l QSemaphore. Typically, a + semaphore is used to protect a certain number of identical resources. - Like its lighter counterpart QSemaphore, a QSystemSemaphore can be + Like its lighter counterpart, a QSystemSemaphore can be accessed from multiple \l {QThread} {threads}. Unlike QSemaphore, a QSystemSemaphore can also be accessed from multiple \l {QProcess} {processes}. This means QSystemSemaphore is a much heavier class, so @@ -38,66 +49,44 @@ QT_BEGIN_NAMESPACE process. The function can also be called with a parameter n > 1, which releases n resources. - A system semaphore is created with a string key that other processes - can use to use the same semaphore. + System semaphores are identified by a key, represented by \l QNativeIpcKey. A + key can be created in a cross-platform manner by using platformSafeKey(). A + system semaphore is created by the QSystemSemaphore constructor when passed + an access mode parameter of AccessMode::Create. Once it is created, other + processes may attach to the same semaphore using the same key and an access + mode parameter of AccessMode::Open. Example: Create a system semaphore \snippet code/src_corelib_kernel_qsystemsemaphore.cpp 0 - A typical application of system semaphores is for controlling access - to a circular buffer shared by a producer process and a consumer - processes. + For details on the key types, platform-specific limitations, and + interoperability with older or non-Qt applications, see the \l{Native IPC + Keys} documentation. That includes important information for sandboxed + applications on Apple platforms, including all apps obtained via the Apple + App Store. - \section1 Platform-Specific Behavior - - When using this class, be aware of the following platform - differences: - - \b{Windows:} QSystemSemaphore does not own its underlying system - semaphore. Windows owns it. This means that when all instances of - QSystemSemaphore for a particular key have been destroyed, either by - having their destructors called, or because one or more processes - crash, Windows removes the underlying system semaphore. - - \b{Unix:} - - \list - \li QSystemSemaphore owns the underlying system semaphore - in Unix systems. This means that the last process having an instance of - QSystemSemaphore for a particular key must remove the underlying - system semaphore in its destructor. If the last process crashes - without running the QSystemSemaphore destructor, Unix does not - automatically remove the underlying system semaphore, and the - semaphore survives the crash. A subsequent process that constructs a - QSystemSemaphore with the same key will then be given the existing - system semaphore. In that case, if the QSystemSemaphore constructor - has specified its \l {QSystemSemaphore::AccessMode} {access mode} as - \l {QSystemSemaphore::} {Open}, its initial resource count will not - be reset to the one provided but remain set to the value it received - in the crashed process. To protect against this, the first process - to create a semaphore for a particular key (usually a server), must - pass its \l {QSystemSemaphore::AccessMode} {access mode} as \l - {QSystemSemaphore::} {Create}, which will force Unix to reset the - resource count in the underlying system semaphore. - - \li When a process using QSystemSemaphore terminates for - any reason, Unix automatically reverses the effect of all acquire - operations that were not released. Thus if the process acquires a - resource and then exits without releasing it, Unix will release that - resource. - - \endlist - - \b{Apple platforms:} Sandboxed applications (including apps - shipped through the Apple App Store) require the key to - be in the form \c {/}, - as documented \l {https://developer.apple.com/library/archive/documentation/Security/Conceptual/AppSandboxDesignGuide/AppSandboxInDepth/AppSandboxInDepth.html#//apple_ref/doc/uid/TP40011183-CH3-SW24} - {here} and \l {https://developer.apple.com/documentation/bundleresources/entitlements/com_apple_security_application-groups} - {here}, and the key length is limited to 30 characters. - - \sa QSharedMemory, QSemaphore + \sa {Inter-Process Communication}, QSharedMemory, QSemaphore */ +#if QT_DEPRECATED_SINCE(6, 10) +/*! + \deprecated + + Requests a system semaphore identified by the legacy key \a key. This + constructor does the same as: + + \code + QSystemSemaphore(QSystemSemaphore::legacyNativeKey(key), initialValue, mode) + \endcode + + except that it stores the legacy native key to retrieve using key(). + */ +QSystemSemaphore::QSystemSemaphore(const QString &key, int initialValue, AccessMode mode) + : QSystemSemaphore(legacyNativeKey(key), initialValue, mode) +{ +} +#endif + /*! Requests a system semaphore for the specified \a key. The parameters \a initialValue and \a mode are used according to the following @@ -134,10 +123,10 @@ QT_BEGIN_NAMESPACE \sa acquire(), key() */ -QSystemSemaphore::QSystemSemaphore(const QString &key, int initialValue, AccessMode mode) - : d(new QSystemSemaphorePrivate) +QSystemSemaphore::QSystemSemaphore(const QNativeIpcKey &key, int initialValue, AccessMode mode) + : d(new QSystemSemaphorePrivate(key.type())) { - setKey(key, initialValue, mode); + setNativeKey(key, initialValue, mode); } /*! @@ -192,27 +181,33 @@ QSystemSemaphore::~QSystemSemaphore() create a new semaphore with the new \a key. The \a initialValue and \a mode parameters are as defined for the constructor. - \sa QSystemSemaphore(), key() + This function is useful if the native key was shared from another process. + See \l{Native IPC Keys} for more information. + + \sa QSystemSemaphore(), nativeIpcKey() */ -void QSystemSemaphore::setKey(const QString &key, int initialValue, AccessMode mode) +void QSystemSemaphore::setNativeKey(const QNativeIpcKey &key, int initialValue, AccessMode mode) { - if (key == d->key && mode == Open) + if (key == d->nativeKey && mode == Open) return; - d->clearError(); -#if !defined(Q_OS_WIN) && !defined(QT_POSIX_IPC) - // optimization to not destroy/create the file & semaphore - if (key == d->key && mode == Create && d->createdSemaphore && d->createdFile) { - d->initialValue = initialValue; - d->unix_key = -1; - d->handle(mode); + if (!isKeyTypeSupported(key.type())) { + d->setError(KeyError, tr("%1: unsupported key type") + .arg("QSystemSemaphore::setNativeKey"_L1)); return; } -#endif + + d->clearError(); d->cleanHandle(); - d->key = key; + if (key.type() == d->nativeKey.type()) { + // we can reuse the backend + d->nativeKey = key; + } else { + // we must recreate the backend + d->destructBackend(); + d->nativeKey = key; + d->constructBackend(); + } d->initialValue = initialValue; - // cache the file name so it doesn't have to be generated all the time. - d->fileName = d->makeKeyFileName(); d->handle(mode); } @@ -220,12 +215,46 @@ void QSystemSemaphore::setKey(const QString &key, int initialValue, AccessMode m Returns the key assigned to this system semaphore. The key is the name by which the semaphore can be accessed from other processes. + You can use the native key to access system semaphores that have not been + created by Qt, or to grant access to non-Qt applications. See \l{Native IPC + Keys} for more information. + + \sa setNativeKey() + */ +QNativeIpcKey QSystemSemaphore::nativeIpcKey() const +{ + return d->nativeKey; +} + +#if QT_DEPRECATED_SINCE(6, 10) +/*! + \deprecated + This function works the same as the constructor. It reconstructs + this QSystemSemaphore object. If the new \a key is different from + the old key, calling this function is like calling the destructor of + the semaphore with the old key, then calling the constructor to + create a new semaphore with the new \a key. The \a initialValue and + \a mode parameters are as defined for the constructor. + + \sa QSystemSemaphore(), key() + */ +void QSystemSemaphore::setKey(const QString &key, int initialValue, AccessMode mode) +{ + setNativeKey(legacyNativeKey(key), initialValue, mode); +} + +/*! + \deprecated + Returns the legacy key assigned to this system semaphore. The key is the + name by which the semaphore can be accessed from other processes. + \sa setKey() */ QString QSystemSemaphore::key() const { - return d->key; + return QNativeIpcKeyPrivate::legacyKey(d->nativeKey); } +#endif /*! Acquires one of the resources guarded by this semaphore, if there is @@ -323,8 +352,66 @@ QString QSystemSemaphore::errorString() const return d->errorString; } -#endif // QT_CONFIG(systemsemaphore) +void QSystemSemaphorePrivate::setUnixErrorString(QLatin1StringView function) +{ + // EINVAL is handled in functions so they can give better error strings + switch (errno) { + case EPERM: + case EACCES: + errorString = QSystemSemaphore::tr("%1: permission denied").arg(function); + error = QSystemSemaphore::PermissionDenied; + break; + case EEXIST: + errorString = QSystemSemaphore::tr("%1: already exists").arg(function); + error = QSystemSemaphore::AlreadyExists; + break; + case ENOENT: + errorString = QSystemSemaphore::tr("%1: does not exist").arg(function); + error = QSystemSemaphore::NotFound; + break; + case ERANGE: + case ENOSPC: + case EMFILE: + errorString = QSystemSemaphore::tr("%1: out of resources").arg(function); + error = QSystemSemaphore::OutOfResources; + break; + case ENAMETOOLONG: + errorString = QSystemSemaphore::tr("%1: key too long").arg(function); + error = QSystemSemaphore::KeyError; + break; + default: + errorString = QSystemSemaphore::tr("%1: unknown error: %2") + .arg(function, qt_error_string(errno)); + error = QSystemSemaphore::UnknownError; +#if defined QSYSTEMSEMAPHORE_DEBUG + qDebug() << errorString << "key" << key << "errno" << errno << EINVAL; +#endif + } +} + +bool QSystemSemaphore::isKeyTypeSupported(QNativeIpcKey::Type type) +{ + if (!isIpcSupported(IpcType::SystemSemaphore, type)) + return false; + using Variant = decltype(QSystemSemaphorePrivate::backend); + return Variant::staticVisit(type, [](auto ptr) { + using Impl = std::decay_t; + return Impl::runtimeSupportCheck(); + }); +} + +QNativeIpcKey QSystemSemaphore::platformSafeKey(const QString &key, QNativeIpcKey::Type type) +{ + return QtIpcCommon::platformSafeKey(key, IpcType::SystemSemaphore, type); +} + +QNativeIpcKey QSystemSemaphore::legacyNativeKey(const QString &key, QNativeIpcKey::Type type) +{ + return QtIpcCommon::legacyPlatformSafeKey(key, IpcType::SystemSemaphore, type); +} QT_END_NAMESPACE #include "moc_qsystemsemaphore.cpp" + +#endif // QT_CONFIG(systemsemaphore) diff --git a/src/corelib/kernel/qsystemsemaphore.h b/src/corelib/ipc/qsystemsemaphore.h similarity index 53% rename from src/corelib/kernel/qsystemsemaphore.h rename to src/corelib/ipc/qsystemsemaphore.h index 7843ec8f..bd0057e6 100644 --- a/src/corelib/kernel/qsystemsemaphore.h +++ b/src/corelib/ipc/qsystemsemaphore.h @@ -5,12 +5,12 @@ #define QSYSTEMSEMAPHORE_H #include +#include #include #include QT_BEGIN_NAMESPACE - #if QT_CONFIG(systemsemaphore) class QSystemSemaphorePrivate; @@ -38,11 +38,23 @@ public: UnknownError }; - QSystemSemaphore(const QString &key, int initialValue = 0, AccessMode mode = Open); + QSystemSemaphore(const QNativeIpcKey &key, int initialValue = 0, AccessMode = Open); ~QSystemSemaphore(); + void setNativeKey(const QNativeIpcKey &key, int initialValue = 0, AccessMode = Open); + void setNativeKey(const QString &key, int initialValue = 0, AccessMode mode = Open, + QNativeIpcKey::Type type = QNativeIpcKey::legacyDefaultTypeForOs()) + { setNativeKey({ key, type }, initialValue, mode); } + QNativeIpcKey nativeIpcKey() const; + +#if QT_DEPRECATED_SINCE(6, 10) + QT_DEPRECATED_VERSION_X_6_10("Please refer to 'Native IPC Key' documentation") + QSystemSemaphore(const QString &key, int initialValue = 0, AccessMode mode = Open); + QT_DEPRECATED_VERSION_X_6_10("Please refer to 'Native IPC Key' documentation") void setKey(const QString &key, int initialValue = 0, AccessMode mode = Open); + QT_DEPRECATED_VERSION_X_6_10("Please refer to 'Native IPC Key' documentation") QString key() const; +#endif bool acquire(); bool release(int n = 1); @@ -50,6 +62,12 @@ public: SystemSemaphoreError error() const; QString errorString() const; + static bool isKeyTypeSupported(QNativeIpcKey::Type type) Q_DECL_CONST_FUNCTION; + static QNativeIpcKey platformSafeKey(const QString &key, + QNativeIpcKey::Type type = QNativeIpcKey::DefaultTypeForOs); + static QNativeIpcKey legacyNativeKey(const QString &key, + QNativeIpcKey::Type type = QNativeIpcKey::legacyDefaultTypeForOs()); + private: Q_DISABLE_COPY(QSystemSemaphore) QScopedPointer d; @@ -60,4 +78,3 @@ private: QT_END_NAMESPACE #endif // QSYSTEMSEMAPHORE_H - diff --git a/src/corelib/ipc/qsystemsemaphore_p.h b/src/corelib/ipc/qsystemsemaphore_p.h new file mode 100644 index 00000000..788c4fb7 --- /dev/null +++ b/src/corelib/ipc/qsystemsemaphore_p.h @@ -0,0 +1,152 @@ +// Copyright (C) 2016 The Qt Company Ltd. +// Copyright (C) 2022 Intel Corporation. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only + +#ifndef QSYSTEMSEMAPHORE_P_H +#define QSYSTEMSEMAPHORE_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 "qsystemsemaphore.h" + +#if QT_CONFIG(systemsemaphore) + +#include "qcoreapplication.h" +#include "qtipccommon_p.h" +#include "private/qtcore-config_p.h" + +#include +#if QT_CONFIG(posix_sem) +# include +#endif +#ifndef SEM_FAILED +# define SEM_FAILED nullptr +struct sem_t; +#endif +#if QT_CONFIG(sysv_sem) +# include +#endif + +QT_BEGIN_NAMESPACE + +class QSystemSemaphorePrivate; + +struct QSystemSemaphorePosix +{ + static constexpr bool Enabled = QT_CONFIG(posix_sem); + static bool supports(QNativeIpcKey::Type type) + { return type == QNativeIpcKey::Type::PosixRealtime; } + static bool runtimeSupportCheck(); + + bool handle(QSystemSemaphorePrivate *self, QSystemSemaphore::AccessMode mode); + void cleanHandle(QSystemSemaphorePrivate *self); + bool modifySemaphore(QSystemSemaphorePrivate *self, int count); + + sem_t *semaphore = SEM_FAILED; + bool createdSemaphore = false; +}; + +struct QSystemSemaphoreSystemV +{ + static constexpr bool Enabled = QT_CONFIG(sysv_sem); + static bool supports(QNativeIpcKey::Type type) + { return quint16(type) <= 0xff; } + static bool runtimeSupportCheck(); + +#if QT_CONFIG(sysv_sem) + key_t handle(QSystemSemaphorePrivate *self, QSystemSemaphore::AccessMode mode); + void cleanHandle(QSystemSemaphorePrivate *self); + bool modifySemaphore(QSystemSemaphorePrivate *self, int count); + + QByteArray nativeKeyFile; + key_t unix_key = -1; + int semaphore = -1; + bool createdFile = false; + bool createdSemaphore = false; +#endif +}; + +struct QSystemSemaphoreWin32 +{ +#ifdef Q_OS_WIN32 + static constexpr bool Enabled = true; +#else + static constexpr bool Enabled = false; +#endif + static bool supports(QNativeIpcKey::Type type) + { return type == QNativeIpcKey::Type::Windows; } + static bool runtimeSupportCheck() { return Enabled; } + + // we can declare the members without the #if + Qt::HANDLE handle(QSystemSemaphorePrivate *self, QSystemSemaphore::AccessMode mode); + void cleanHandle(QSystemSemaphorePrivate *self); + bool modifySemaphore(QSystemSemaphorePrivate *self, int count); + + Qt::HANDLE semaphore = nullptr; +}; + +class QSystemSemaphorePrivate +{ +public: + QSystemSemaphorePrivate(QNativeIpcKey::Type type) : nativeKey(type) + { constructBackend(); } + ~QSystemSemaphorePrivate() { destructBackend(); } + + void setWindowsErrorString(QLatin1StringView function); // Windows only + void setUnixErrorString(QLatin1StringView function); + inline void setError(QSystemSemaphore::SystemSemaphoreError e, const QString &message) + { error = e; errorString = message; } + inline void clearError() + { setError(QSystemSemaphore::NoError, QString()); } + + QNativeIpcKey nativeKey; + QString errorString; + int initialValue; + QSystemSemaphore::SystemSemaphoreError error = QSystemSemaphore::NoError; + + union Backend { + Backend() {} + ~Backend() {} + QSystemSemaphorePosix posix; + QSystemSemaphoreSystemV sysv; + QSystemSemaphoreWin32 win32; + }; + QtIpcCommon::IpcStorageVariant<&Backend::posix, &Backend::sysv, &Backend::win32> backend; + + void constructBackend(); + void destructBackend(); + + template auto visit(const Lambda &lambda) + { + return backend.visit(nativeKey.type(), lambda); + } + + void handle(QSystemSemaphore::AccessMode mode) + { + visit([&](auto p) { p->handle(this, mode); }); + } + void cleanHandle() + { + visit([&](auto p) { p->cleanHandle(this); }); + } + bool modifySemaphore(int count) + { + return visit([&](auto p) { return p->modifySemaphore(this, count); }); + } +}; + +QT_END_NAMESPACE + +#endif // QT_CONFIG(systemsemaphore) + +#endif // QSYSTEMSEMAPHORE_P_H + diff --git a/src/corelib/kernel/qsystemsemaphore_posix.cpp b/src/corelib/ipc/qsystemsemaphore_posix.cpp similarity index 61% rename from src/corelib/kernel/qsystemsemaphore_posix.cpp rename to src/corelib/ipc/qsystemsemaphore_posix.cpp index 4f3ad192..b290e14d 100644 --- a/src/corelib/kernel/qsystemsemaphore_posix.cpp +++ b/src/corelib/ipc/qsystemsemaphore_posix.cpp @@ -1,6 +1,7 @@ // Copyright (C) 2015 Konstantin Ritt // Copyright (C) 2016 The Qt Company Ltd. // Copyright (C) 2015 Klarälvdalens Datakonsult AB, a KDAB Group company, info@kdab.com, author Tobias Koenig +// Copyright (C) 2022 Intel Corporation. // SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only #include "qsystemsemaphore.h" @@ -10,15 +11,19 @@ #include #include -#ifdef QT_POSIX_IPC - -#if QT_CONFIG(systemsemaphore) +#if QT_CONFIG(posix_sem) #include #include #include -#include "private/qcore_unix_p.h" +#ifdef Q_OS_UNIX +# include "private/qcore_unix_p.h" +#else +#define EINTR_LOOP_VAL(var, val, cmd) \ + (void)var; var = cmd +#define EINTR_LOOP(var, cmd) EINTR_LOOP_VAL(var, -1, cmd) +#endif // OpenBSD 4.2 doesn't define EIDRM, see BUGS section: // http://www.openbsd.org/cgi-bin/man.cgi?query=semop&manpath=OpenBSD+4.2 @@ -30,29 +35,38 @@ QT_BEGIN_NAMESPACE using namespace Qt::StringLiterals; -bool QSystemSemaphorePrivate::handle(QSystemSemaphore::AccessMode mode) +bool QSystemSemaphorePosix::runtimeSupportCheck() +{ + static const bool result = []() { + sem_open("/", 0, 0, 0); // this WILL fail + return errno != ENOSYS; + }(); + return result; +} + +bool QSystemSemaphorePosix::handle(QSystemSemaphorePrivate *self, QSystemSemaphore::AccessMode mode) { if (semaphore != SEM_FAILED) return true; // we already have a semaphore - if (fileName.isEmpty()) { - errorString = QSystemSemaphore::tr("%1: key is empty").arg("QSystemSemaphore::handle"_L1); - error = QSystemSemaphore::KeyError; + const QByteArray semName = QFile::encodeName(self->nativeKey.nativeKey()); + if (semName.isEmpty()) { + self->setError(QSystemSemaphore::KeyError, + QSystemSemaphore::tr("%1: key is empty") + .arg("QSystemSemaphore::handle"_L1)); return false; } - const QByteArray semName = QFile::encodeName(fileName); - // Always try with O_EXCL so we know whether we created the semaphore. int oflag = O_CREAT | O_EXCL; for (int tryNum = 0, maxTries = 1; tryNum < maxTries; ++tryNum) { do { - semaphore = ::sem_open(semName.constData(), oflag, 0600, initialValue); + semaphore = ::sem_open(semName.constData(), oflag, 0600, self->initialValue); } while (semaphore == SEM_FAILED && errno == EINTR); if (semaphore == SEM_FAILED && errno == EEXIST) { if (mode == QSystemSemaphore::Create) { if (::sem_unlink(semName.constData()) == -1 && errno != ENOENT) { - setErrorString("QSystemSemaphore::handle (sem_unlink)"_L1); + self->setUnixErrorString("QSystemSemaphore::handle (sem_unlink)"_L1); return false; } // Race condition: the semaphore might be recreated before @@ -70,7 +84,7 @@ bool QSystemSemaphorePrivate::handle(QSystemSemaphore::AccessMode mode) } if (semaphore == SEM_FAILED) { - setErrorString("QSystemSemaphore::handle"_L1); + self->setUnixErrorString("QSystemSemaphore::handle"_L1); return false; } @@ -79,11 +93,11 @@ bool QSystemSemaphorePrivate::handle(QSystemSemaphore::AccessMode mode) return true; } -void QSystemSemaphorePrivate::cleanHandle() +void QSystemSemaphorePosix::cleanHandle(QSystemSemaphorePrivate *self) { if (semaphore != SEM_FAILED) { if (::sem_close(semaphore) == -1) { - setErrorString("QSystemSemaphore::cleanHandle (sem_close)"_L1); + self->setUnixErrorString("QSystemSemaphore::cleanHandle (sem_close)"_L1); #if defined QSYSTEMSEMAPHORE_DEBUG qDebug("QSystemSemaphore::cleanHandle sem_close failed."); #endif @@ -92,28 +106,29 @@ void QSystemSemaphorePrivate::cleanHandle() } if (createdSemaphore) { - if (::sem_unlink(QFile::encodeName(fileName).constData()) == -1 && errno != ENOENT) { - setErrorString("QSystemSemaphore::cleanHandle (sem_unlink)"_L1); + const QByteArray semName = QFile::encodeName(self->nativeKey.nativeKey()); + if (::sem_unlink(semName) == -1 && errno != ENOENT) { + self->setUnixErrorString("QSystemSemaphore::cleanHandle (sem_unlink)"_L1); #if defined QSYSTEMSEMAPHORE_DEBUG - qDebug("QSystemSemaphore::cleanHandle sem_unlink failed."); + qDebug("QSystemSemaphorePosix::cleanHandle sem_unlink failed."); #endif } createdSemaphore = false; } } -bool QSystemSemaphorePrivate::modifySemaphore(int count) +bool QSystemSemaphorePosix::modifySemaphore(QSystemSemaphorePrivate *self, int count) { - if (!handle()) + if (!handle(self, QSystemSemaphore::Open)) return false; if (count > 0) { int cnt = count; do { if (::sem_post(semaphore) == -1) { - setErrorString("QSystemSemaphore::modifySemaphore (sem_post)"_L1); + self->setUnixErrorString("QSystemSemaphore::modifySemaphore (sem_post)"_L1); #if defined QSYSTEMSEMAPHORE_DEBUG - qDebug("QSystemSemaphore::modify sem_post failed %d %d", count, errno); + qDebug("QSystemSemaphorePosix::modify sem_post failed %d %d", count, errno); #endif // rollback changes to preserve the SysV semaphore behavior for ( ; cnt < count; ++cnt) { @@ -131,22 +146,20 @@ bool QSystemSemaphorePrivate::modifySemaphore(int count) // If the semaphore was removed be nice and create it and then modifySemaphore again if (errno == EINVAL || errno == EIDRM) { semaphore = SEM_FAILED; - return modifySemaphore(count); + return modifySemaphore(self, count); } - setErrorString("QSystemSemaphore::modifySemaphore (sem_wait)"_L1); + self->setUnixErrorString("QSystemSemaphore::modifySemaphore (sem_wait)"_L1); #if defined QSYSTEMSEMAPHORE_DEBUG - qDebug("QSystemSemaphore::modify sem_wait failed %d %d", count, errno); + qDebug("QSystemSemaphorePosix::modify sem_wait failed %d %d", count, errno); #endif return false; } } - clearError(); + self->clearError(); return true; } QT_END_NAMESPACE -#endif // QT_CONFIG(systemsemaphore) - -#endif // QT_POSIX_IPC +#endif // QT_CONFIG(posix_sem) diff --git a/src/corelib/kernel/qsystemsemaphore_systemv.cpp b/src/corelib/ipc/qsystemsemaphore_systemv.cpp similarity index 50% rename from src/corelib/kernel/qsystemsemaphore_systemv.cpp rename to src/corelib/ipc/qsystemsemaphore_systemv.cpp index 28992a0f..e976fa5e 100644 --- a/src/corelib/kernel/qsystemsemaphore_systemv.cpp +++ b/src/corelib/ipc/qsystemsemaphore_systemv.cpp @@ -1,4 +1,5 @@ // Copyright (C) 2016 The Qt Company Ltd. +// Copyright (C) 2022 Intel Corporation. // SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only #include "qsystemsemaphore.h" @@ -8,9 +9,7 @@ #include #include -#ifndef QT_POSIX_IPC - -#if QT_CONFIG(systemsemaphore) +#if QT_CONFIG(sysv_sem) #include #include @@ -19,7 +18,7 @@ #include #if defined(Q_OS_DARWIN) -#include "qcore_mac_p.h" +#include "private/qcore_mac_p.h" #endif #include "private/qcore_unix_p.h" @@ -34,52 +33,66 @@ QT_BEGIN_NAMESPACE using namespace Qt::StringLiterals; +bool QSystemSemaphoreSystemV::runtimeSupportCheck() +{ +#if defined(Q_OS_DARWIN) + if (qt_apple_isSandboxed()) + return false; +#endif + static const bool result = []() { + (void)semget(IPC_PRIVATE, -1, 0); // this will fail + return errno != ENOSYS; + }(); + return result; +} + /*! \internal Setup unix_key */ -key_t QSystemSemaphorePrivate::handle(QSystemSemaphore::AccessMode mode) +key_t QSystemSemaphoreSystemV::handle(QSystemSemaphorePrivate *self, QSystemSemaphore::AccessMode mode) { + if (unix_key != -1) + return unix_key; // we already have a semaphore + #if defined(Q_OS_DARWIN) if (qt_apple_isSandboxed()) { - errorString = QSystemSemaphore::tr("%1: System V semaphores are not available " \ - "for sandboxed applications. Please build Qt with -feature-ipc_posix") - .arg("QSystemSemaphore::handle:"_L1); - error = QSystemSemaphore::PermissionDenied; + // attempting to use System V semaphores will get us a SIGSYS + self->setError(QSystemSemaphore::PermissionDenied, + QSystemSemaphore::tr("%1: System V semaphores are not available for " + "sandboxed applications. Please build Qt with " + "-feature-ipc_posix") + .arg("QSystemSemaphore::handle:"_L1)); return -1; } #endif - if (key.isEmpty()){ - errorString = QSystemSemaphore::tr("%1: key is empty") - .arg("QSystemSemaphore::handle:"_L1); - error = QSystemSemaphore::KeyError; + nativeKeyFile = QFile::encodeName(self->nativeKey.nativeKey()); + if (nativeKeyFile.isEmpty()) { + self->setError(QSystemSemaphore::KeyError, + QSystemSemaphore::tr("%1: key is empty") + .arg("QSystemSemaphore::handle:"_L1)); return -1; } // ftok requires that an actual file exists somewhere - if (-1 != unix_key) - return unix_key; - - // Create the file needed for ftok - int built = QSharedMemoryPrivate::createUnixKeyFile(fileName); + int built = QtIpcCommon::createUnixKeyFile(nativeKeyFile); if (-1 == built) { - errorString = QSystemSemaphore::tr("%1: unable to make key") - .arg("QSystemSemaphore::handle:"_L1); - error = QSystemSemaphore::KeyError; + self->setError(QSystemSemaphore::KeyError, + QSystemSemaphore::tr("%1: unable to make key") + .arg("QSystemSemaphore::handle:"_L1)); + return -1; } createdFile = (1 == built); -#if QT_CONFIG(sharedmemory) && !defined(QT_POSIX_IPC) && !defined(Q_OS_ANDROID) // Get the unix key for the created file - unix_key = ftok(QFile::encodeName(fileName).constData(), 'Q'); -#endif + unix_key = ftok(nativeKeyFile, int(self->nativeKey.type())); if (-1 == unix_key) { - errorString = QSystemSemaphore::tr("%1: ftok failed") - .arg("QSystemSemaphore::handle:"_L1); - error = QSystemSemaphore::KeyError; + self->setError(QSystemSemaphore::KeyError, + QSystemSemaphore::tr("%1: ftok failed") + .arg("QSystemSemaphore::handle:"_L1)); return -1; } @@ -89,8 +102,8 @@ key_t QSystemSemaphorePrivate::handle(QSystemSemaphore::AccessMode mode) if (errno == EEXIST) semaphore = semget(unix_key, 1, 0600 | IPC_CREAT); if (-1 == semaphore) { - setErrorString("QSystemSemaphore::handle"_L1); - cleanHandle(); + self->setUnixErrorString("QSystemSemaphore::handle"_L1); + cleanHandle(self); return -1; } } else { @@ -105,12 +118,12 @@ key_t QSystemSemaphorePrivate::handle(QSystemSemaphore::AccessMode mode) } // Created semaphore so initialize its value. - if (createdSemaphore && initialValue >= 0) { + if (createdSemaphore && self->initialValue >= 0) { qt_semun init_op; - init_op.val = initialValue; + init_op.val = self->initialValue; if (-1 == semctl(semaphore, 0, SETVAL, init_op)) { - setErrorString("QSystemSemaphore::handle"_L1); - cleanHandle(); + self->setUnixErrorString("QSystemSemaphore::handle"_L1); + cleanHandle(self); return -1; } } @@ -123,22 +136,22 @@ key_t QSystemSemaphorePrivate::handle(QSystemSemaphore::AccessMode mode) Cleanup the unix_key */ -void QSystemSemaphorePrivate::cleanHandle() +void QSystemSemaphoreSystemV::cleanHandle(QSystemSemaphorePrivate *self) { unix_key = -1; // remove the file if we made it if (createdFile) { - QFile::remove(fileName); + unlink(nativeKeyFile.constData()); createdFile = false; } if (createdSemaphore) { if (-1 != semaphore) { if (-1 == semctl(semaphore, 0, IPC_RMID, 0)) { - setErrorString("QSystemSemaphore::cleanHandle"_L1); + self->setUnixErrorString("QSystemSemaphore::cleanHandle"_L1); #if defined QSYSTEMSEMAPHORE_DEBUG - qDebug("QSystemSemaphore::cleanHandle semctl failed."); + qDebug("QSystemSemaphoreSystemV::cleanHandle semctl failed."); #endif } semaphore = -1; @@ -150,9 +163,9 @@ void QSystemSemaphorePrivate::cleanHandle() /*! \internal */ -bool QSystemSemaphorePrivate::modifySemaphore(int count) +bool QSystemSemaphoreSystemV::modifySemaphore(QSystemSemaphorePrivate *self, int count) { - if (-1 == handle()) + if (handle(self, QSystemSemaphore::Open) == -1) return false; struct sembuf operation; @@ -166,25 +179,23 @@ bool QSystemSemaphorePrivate::modifySemaphore(int count) // If the semaphore was removed be nice and create it and then modifySemaphore again if (errno == EINVAL || errno == EIDRM) { semaphore = -1; - cleanHandle(); - handle(); - return modifySemaphore(count); + cleanHandle(self); + handle(self, QSystemSemaphore::Open); + return modifySemaphore(self, count); } - setErrorString("QSystemSemaphore::modifySemaphore"_L1); + self->setUnixErrorString("QSystemSemaphore::modifySemaphore"_L1); #if defined QSYSTEMSEMAPHORE_DEBUG - qDebug("QSystemSemaphore::modify failed %d %d %d %d %d", + qDebug("QSystemSemaphoreSystemV::modify failed %d %d %d %d %d", count, int(semctl(semaphore, 0, GETVAL)), int(errno), int(EIDRM), int(EINVAL); #endif return false; } - clearError(); + self->clearError(); return true; } QT_END_NAMESPACE -#endif // QT_CONFIG(systemsemaphore) - -#endif // QT_POSIX_IPC +#endif // QT_CONFIG(sysv_sem) diff --git a/src/corelib/kernel/qsystemsemaphore_win.cpp b/src/corelib/ipc/qsystemsemaphore_win.cpp similarity index 69% rename from src/corelib/kernel/qsystemsemaphore_win.cpp rename to src/corelib/ipc/qsystemsemaphore_win.cpp index f1b7e78f..f42fecf7 100644 --- a/src/corelib/kernel/qsystemsemaphore_win.cpp +++ b/src/corelib/ipc/qsystemsemaphore_win.cpp @@ -13,7 +13,7 @@ using namespace Qt::StringLiterals; #if QT_CONFIG(systemsemaphore) -void QSystemSemaphorePrivate::setErrorString(const QString &function) +void QSystemSemaphorePrivate::setWindowsErrorString(QLatin1StringView function) { BOOL windowsError = GetLastError(); if (windowsError == 0) @@ -30,7 +30,8 @@ void QSystemSemaphorePrivate::setErrorString(const QString &function) errorString = QCoreApplication::translate("QSystemSemaphore", "%1: permission denied").arg(function); break; default: - errorString = QCoreApplication::translate("QSystemSemaphore", "%1: unknown error %2").arg(function).arg(windowsError); + errorString = QCoreApplication::translate("QSystemSemaphore", "%1: unknown error: %2") + .arg(function, qt_error_string(windowsError)); error = QSystemSemaphore::UnknownError; #if defined QSYSTEMSEMAPHORE_DEBUG qDebug() << errorString << "key" << key; @@ -38,41 +39,41 @@ void QSystemSemaphorePrivate::setErrorString(const QString &function) } } -HANDLE QSystemSemaphorePrivate::handle(QSystemSemaphore::AccessMode) +HANDLE QSystemSemaphoreWin32::handle(QSystemSemaphorePrivate *self, QSystemSemaphore::AccessMode) { // don't allow making handles on empty keys - if (key.isEmpty()) + if (self->nativeKey.isEmpty()) return 0; // Create it if it doesn't already exists. if (semaphore == 0) { - semaphore = CreateSemaphore(0, initialValue, MAXLONG, - reinterpret_cast(fileName.utf16())); + semaphore = CreateSemaphore(0, self->initialValue, MAXLONG, + reinterpret_cast(self->nativeKey.nativeKey().utf16())); if (semaphore == NULL) - setErrorString("QSystemSemaphore::handle"_L1); + self->setWindowsErrorString("QSystemSemaphore::handle"_L1); } return semaphore; } -void QSystemSemaphorePrivate::cleanHandle() +void QSystemSemaphoreWin32::cleanHandle(QSystemSemaphorePrivate *) { if (semaphore && !CloseHandle(semaphore)) { #if defined QSYSTEMSEMAPHORE_DEBUG - qDebug("QSystemSemaphorePrivate::CloseHandle: sem failed"); + qDebug("QSystemSemaphoreWin32::CloseHandle: sem failed"); #endif } semaphore = 0; } -bool QSystemSemaphorePrivate::modifySemaphore(int count) +bool QSystemSemaphoreWin32::modifySemaphore(QSystemSemaphorePrivate *self, int count) { - if (0 == handle()) + if (handle(self, QSystemSemaphore::Open) == nullptr) return false; if (count > 0) { if (0 == ReleaseSemaphore(semaphore, count, 0)) { - setErrorString("QSystemSemaphore::modifySemaphore"_L1); + self->setWindowsErrorString("QSystemSemaphore::modifySemaphore"_L1); #if defined QSYSTEMSEMAPHORE_DEBUG qDebug("QSystemSemaphore::modifySemaphore ReleaseSemaphore failed"); #endif @@ -80,7 +81,7 @@ bool QSystemSemaphorePrivate::modifySemaphore(int count) } } else { if (WAIT_OBJECT_0 != WaitForSingleObjectEx(semaphore, INFINITE, FALSE)) { - setErrorString("QSystemSemaphore::modifySemaphore"_L1); + self->setWindowsErrorString("QSystemSemaphore::modifySemaphore"_L1); #if defined QSYSTEMSEMAPHORE_DEBUG qDebug("QSystemSemaphore::modifySemaphore WaitForSingleObject failed"); #endif @@ -88,7 +89,7 @@ bool QSystemSemaphorePrivate::modifySemaphore(int count) } } - clearError(); + self->clearError(); return true; } diff --git a/src/corelib/ipc/qtipccommon.cpp b/src/corelib/ipc/qtipccommon.cpp new file mode 100644 index 00000000..68176274 --- /dev/null +++ b/src/corelib/ipc/qtipccommon.cpp @@ -0,0 +1,606 @@ +// Copyright (C) 2022 Intel Corporation. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only + +#include "qtipccommon.h" +#include "qtipccommon_p.h" + +#include +#include +#include +#include +#include + +#if defined(Q_OS_DARWIN) +# include "private/qcore_mac_p.h" +# if !defined(SHM_NAME_MAX) + // Based on PSEMNAMLEN in XNU's posix_sem.c, which would + // indicate the max length is 31, _excluding_ the zero + // terminator. But in practice (possibly due to an off- + // by-one bug in the kernel) the usable bytes are only 30. +# define SHM_NAME_MAX 30 +# endif +#endif + +#if QT_CONFIG(sharedmemory) || QT_CONFIG(systemsemaphore) + +QT_BEGIN_NAMESPACE + +using namespace Qt::StringLiterals; + +static QStringView staticTypeToString(QNativeIpcKey::Type type) +{ + switch (type) { + case QNativeIpcKey::Type::SystemV: + return u"systemv"; + case QNativeIpcKey::Type::PosixRealtime: + return u"posix"; + case QNativeIpcKey::Type::Windows: + return u"windows"; + } + return {}; +} + +static QString typeToString(QNativeIpcKey::Type type) +{ + QStringView typeString = staticTypeToString(type); + switch (type) { + case QNativeIpcKey::Type::SystemV: + case QNativeIpcKey::Type::PosixRealtime: + case QNativeIpcKey::Type::Windows: + return QString::fromRawData(typeString.constData(), typeString.size()); + } + + int value = int(type); + if (value >= 1 && value <= 0xff) { + // System V key with id different from 'Q' + typeString = staticTypeToString(QNativeIpcKey::Type::SystemV); + return typeString + QString::number(-value); // negative so it prepends a dash + } + + return QString(); // invalid! +} + +static QNativeIpcKey::Type stringToType(QStringView typeString) +{ + if (typeString == staticTypeToString(QNativeIpcKey::Type::PosixRealtime)) + return QNativeIpcKey::Type::PosixRealtime; + if (typeString == staticTypeToString(QNativeIpcKey::Type::Windows)) + return QNativeIpcKey::Type::Windows; + + auto fromNumber = [](QStringView number, int low, int high) { + bool ok; + int n = -number.toInt(&ok, 10); + if (!ok || n < low || n > high) + return QNativeIpcKey::Type{}; + return QNativeIpcKey::Type(n); + }; + + QStringView sysv = staticTypeToString(QNativeIpcKey::Type::SystemV); + if (typeString.startsWith(sysv)) { + if (typeString.size() == sysv.size()) + return QNativeIpcKey::Type::SystemV; + return fromNumber(typeString.sliced(sysv.size()), 1, 0xff); + } + + // invalid! + return QNativeIpcKey::Type{}; +} + +/*! + \internal + + Legacy: this exists for compatibility with QSharedMemory and + QSystemSemaphore between 4.4 and 6.6. + + Returns a QNativeIpcKey that contains a platform-safe key using rules + similar to QtIpcCommon::platformSafeKey() below, but using an algorithm + that is compatible with Qt 4.4 to 6.6. Additionally, the returned + QNativeIpcKey will record the input \a key so it can be included in the + string form if necessary to pass to other processes. +*/ +QNativeIpcKey QtIpcCommon::legacyPlatformSafeKey(const QString &key, QtIpcCommon::IpcType ipcType, + QNativeIpcKey::Type type) +{ + QNativeIpcKey k(type); + if (key.isEmpty()) + return k; + + QByteArray hex = QCryptographicHash::hash(key.toUtf8(), QCryptographicHash::Sha1).toHex(); + + if (type == QNativeIpcKey::Type::PosixRealtime) { +#if defined(Q_OS_DARWIN) + if (qt_apple_isSandboxed()) { + // Sandboxed applications on Apple platforms require the shared memory name + // to be in the form /. + // Since we don't know which application group identifier the user wants + // to apply, we instead document that requirement, and use the key directly. + QNativeIpcKeyPrivate::setNativeAndLegacyKeys(k, key, key); + } else { + // The shared memory name limit on Apple platforms is very low (30 characters), + // so we can't use the logic below of combining the prefix, key, and a hash, + // to ensure a unique and valid name. Instead we use the first part of the + // hash, which should still long enough to avoid collisions in practice. + QString native = u'/' + QLatin1StringView(hex).left(SHM_NAME_MAX - 1); + QNativeIpcKeyPrivate::setNativeAndLegacyKeys(k, native, key); + } + return k; +#endif + } + + QString result; + result.reserve(1 + 18 + key.size() + 40); + switch (ipcType) { + case IpcType::SharedMemory: + result += "qipc_sharedmemory_"_L1; + break; + case IpcType::SystemSemaphore: + result += "qipc_systemsem_"_L1; + break; + } + + for (QChar ch : key) { + if ((ch >= u'a' && ch <= u'z') || + (ch >= u'A' && ch <= u'Z')) + result += ch; + } + result.append(QLatin1StringView(hex)); + + switch (type) { + case QNativeIpcKey::Type::Windows: + if (isIpcSupported(ipcType, QNativeIpcKey::Type::Windows)) + QNativeIpcKeyPrivate::setNativeAndLegacyKeys(k, result, key); + return k; + case QNativeIpcKey::Type::PosixRealtime: + result.prepend(u'/'); + if (isIpcSupported(ipcType, QNativeIpcKey::Type::PosixRealtime)) + QNativeIpcKeyPrivate::setNativeAndLegacyKeys(k, result, key); + return k; + case QNativeIpcKey::Type::SystemV: + break; + } + if (isIpcSupported(ipcType, QNativeIpcKey::Type::SystemV)) { + result = QStandardPaths::writableLocation(QStandardPaths::TempLocation) + u'/' + result; + QNativeIpcKeyPrivate::setNativeAndLegacyKeys(k, result, key); + } + return k; +} + +/*! + \internal + Returns a QNativeIpcKey of type \a type, suitable for QSystemSemaphore or + QSharedMemory depending on \a ipcType. The returned native key is generated + from the Unicode input \a key and is safe for use on for the key type in + question in the current OS. +*/ +QNativeIpcKey QtIpcCommon::platformSafeKey(const QString &key, QtIpcCommon::IpcType ipcType, + QNativeIpcKey::Type type) +{ + QNativeIpcKey k(type); + if (key.isEmpty()) + return k; + + switch (type) { + case QNativeIpcKey::Type::PosixRealtime: + if (isIpcSupported(ipcType, QNativeIpcKey::Type::PosixRealtime)) { +#ifdef SHM_NAME_MAX + // The shared memory name limit on Apple platforms is very low (30 + // characters), so we have to cut it down to avoid ENAMETOOLONG. We + // hope that there won't be too many collisions... + k.setNativeKey(u'/' + QStringView(key).left(SHM_NAME_MAX - 1)); +#else + k.setNativeKey(u'/' + key); +#endif + } + return k; + + case QNativeIpcKey::Type::Windows: + if (isIpcSupported(ipcType, QNativeIpcKey::Type::Windows)) { + QStringView prefix; + QStringView payload = key; + // see https://learn.microsoft.com/en-us/windows/win32/termserv/kernel-object-namespaces + for (QStringView candidate : { u"Local\\", u"Global\\" }) { + if (!key.startsWith(candidate)) + continue; + prefix = candidate; + payload = payload.sliced(prefix.size()); + break; + } + + QStringView mid; + switch (ipcType) { + case IpcType::SharedMemory: mid = u"shm_"; break; + case IpcType::SystemSemaphore: mid = u"sem_"; break; + } + + QString result = prefix + mid + payload; +#ifdef MAX_PATH + result.truncate(MAX_PATH); +#endif + k.setNativeKey(result); + } + return k; + + case QNativeIpcKey::Type::SystemV: + break; + } + + // System V + if (isIpcSupported(ipcType, QNativeIpcKey::Type::SystemV)) { + if (key.startsWith(u'/')) { + k.setNativeKey(key); + } else { + QString baseDir = QStandardPaths::writableLocation(QStandardPaths::RuntimeLocation); + k.setNativeKey(baseDir + u'/' + key); + } + } + return k; +} + +/*! + \class QNativeIpcKey + \inmodule QtCore + \since 6.6 + \brief The QNativeIpcKey class holds a native key used by QSystemSemaphore and QSharedMemory. + + The \l QSharedMemory and \l QSystemSemaphore classes identify their + resource using a system-wide identifier known as a "key". The low-level key + value as well as the key type are encapsulated in Qt using the \l + QNativeIpcKey class. + + Those two classes also provide the means to create native keys from a + cross-platform identifier, using QSharedMemory::platformSafeKey() and + QSystemSemaphore::platformSafeKey(). Applications should never share the + input to those functions, as different versions of Qt may perform different + transformations, resulting in different native keys. Instead, the + application that created the IPC object should communicate the resulting + native key using the methods described below. + + For details on the key types, platform-specific limitations, and + interoperability with older or non-Qt applications, see the \l{Native IPC + Keys} documentation. That includes important information for sandboxed + applications on Apple platforms, including all apps obtained via the Apple + App Store. + + \section1 Communicating keys to other processes + \section2 Communicating keys to other Qt processes + + If the other process supports QNativeIpcKey, the best way of communicating + is via the string representation obtained from toString() and parsing it + using fromString(). This representation can be stored on a file whose name + is well-known or passed on the command-line to a child process using + QProcess::setArguments(). + + If the other process does not support QNativeIpcKey, then the two processes + can exchange the nativeKey() but the older code is likely unable to adjust + its key type. The legacyDefaultTypeForOs() function returns the type that + legacy code used, which may not match the \l{DefaultTypeForOs} constant. + This is still true even if the old application is not using the same build + as the new one (for example, it is a Qt 5 application), provided the + options passed to the Qt configure script are the same. + + \section2 Communicating keys to non-Qt processes + + When communicating with non-Qt processes, the application must arrange to + obtain the key type the other process is using. This is important + particularly on Unix systems, where both \l PosixRealtime and \l SystemV + are common. + + \section1 String representation of native keys + + The format of the string representation of a QNativeIpcKey is meant to be + stable and therefore backwards and forwards compatible, provided the key + type is supported by the Qt version in question. That is to say, an older + Qt will fail to parse the string representation of a key type introduced + after it was released. However, successfully parsing a string + representation does not imply the Qt classes can successfully create an + object of that type; applications should verify support using + QSharedMemory::isKeyTypeSupported() and QSystemSemaphore::isKeyTypeSupported(). + + The format of the string representation is formed by two components, + separated by a colon (':'). The first component is the key type, described + in the table below. The second component is a type-specific payload, using + \l{QByteArray::fromPercentEncoding}{percent-encoding}. For all currently + supported key types, the decoded form is identical to the contents of the + nativeKey() field. + + \table + \row \li Key type \li String representation + \row \li \l PosixRealtime \li \c "posix" + \row \li \l SystemV \li \c "systemv" + \row \li \l Windows \li \c "windows" + \row \li Non-standard SystemV \li \c "systemv-" followed by a decimal number + \endtable + + This format resembles a URI and allows parsing using URI/URL-parsing + functions, such as \l QUrl. When parsed by such API, the key type will show + up as the \l{QUrl::scheme()}{scheme}, and the payload will be the + \l{QUrl::path()}{path}. Use of query or fragments is reserved. + + \sa QSharedMemory, QSystemSemaphore +*/ + +/*! + \enum QNativeIpcKey::Type + + This enum describes the backend type for the IPC object. For details on the + key types, see the \l{Native IPC Keys} documentation. + + \value SystemV X/Open System Initiative (XSI) or System V (SVr4) API + \value PosixRealtime IEEE 1003.1b (POSIX.1b) API + \value Windows Win32 API + + \sa setType(), type() +*/ + +/*! + \variable QNativeIpcKey::DefaultTypeForOs + + This constant expression variable holds the default native IPC type for the + current OS. It will be Type::Windows for Windows systems and + Type::PosixRealtime elsewhere. Note that this constant is different from + what \l QSharedMemory and \l QSystemSemaphore defaulted to on the majority + of Unix systems prior to Qt 6.6; see legacyDefaultTypeForOs() for more + information. +*/ + +/*! + \fn QNativeIpcKey::legacyDefaultTypeForOs() noexcept + + Returns the \l{Type} that corresponds to the native IPC key that + \l{QSharedMemory} and \l{QSystemSemaphore} used to use prior to Qt 6.6. + Applications and libraries that must retain compatibility with code using + either class that was compiled with Qt prior to version 6.6 can use this + function to determine what IPC type the other applications may be using. + + Note that this function relies on Qt having been built with identical + configure-time options. +*/ +#if defined(Q_OS_DARWIN) +QNativeIpcKey::Type QNativeIpcKey::defaultTypeForOs_internal() noexcept +{ + if (qt_apple_isSandboxed()) + return Type::PosixRealtime; + return Type::SystemV; +} +#endif + +/*! + \fn QNativeIpcKey::QNativeIpcKey() noexcept + + Constructs a QNativeIpcKey object of type \l DefaultTypeForOs with an empty key. +*/ + +/*! + \fn QNativeIpcKey::QNativeIpcKey(Type type) noexcept + \fn QNativeIpcKey::QNativeIpcKey(const QString &key, Type type) + + Constructs a QNativeIpcKey object holding native key \a key (or empty on + the overload without the parameter) for type \a type. +*/ + +/*! + \fn QNativeIpcKey::QNativeIpcKey(const QNativeIpcKey &other) + \fn QNativeIpcKey::QNativeIpcKey(QNativeIpcKey &&other) noexcept + \fn QNativeIpcKey &QNativeIpcKey::operator=(const QNativeIpcKey &other) + \fn QNativeIpcKey &QNativeIpcKey::operator=(QNativeIpcKey &&other) noexcept + + Copies or moves the content of \a other. +*/ +void QNativeIpcKey::copy_internal(const QNativeIpcKey &other) +{ + d = new QNativeIpcKeyPrivate(*other.d); +} + +void QNativeIpcKey::move_internal(QNativeIpcKey &&) noexcept +{ + // inline code already moved properly, nothing for us to do here +} + +QNativeIpcKey &QNativeIpcKey::assign_internal(const QNativeIpcKey &other) +{ + Q_ASSERT(d || other.d); // only 3 cases to handle + if (d && !other.d) + *d = {}; + else if (d) + *d = *other.d; + else + d = new QNativeIpcKeyPrivate(*other.d); + return *this; +} + +/*! + \fn QNativeIpcKey::~QNativeIpcKey() + + Disposes of this QNativeIpcKey object. +*/ +void QNativeIpcKey::destroy_internal() noexcept +{ + delete d; +} + +/*! + \fn QNativeIpcKey::swap(QNativeIpcKey &other) noexcept + + Swaps the native IPC key and type \a other with this object. + This operation is very fast and never fails. +*/ + +/*! + \fn swap(QNativeIpcKey &value1, QNativeIpcKey &value2) noexcept + \relates QNativeIpcKey + + Swaps the native IPC key and type \a value1 with \a value2. + This operation is very fast and never fails. +*/ + +/*! + \fn QNativeIpcKey::isEmpty() const + + Returns true if the nativeKey() is empty. + + \sa nativeKey() +*/ + +/*! + \fn QNativeIpcKey::isValid() const + + Returns true if this object contains a valid native IPC key type. Invalid + types are usually the result of a failure to parse a string representation + using fromString(). + + This function performs no check on the whether the key string is actually + supported or valid for the current operating system. + + \sa type(), fromString() +*/ + +/*! + \fn QNativeIpcKey::type() const noexcept + + Returns the key type associated with this object. + + \sa nativeKey(), setType() +*/ + +/*! + \fn QNativeIpcKey::setType(Type type) + + Sets the IPC type of this object to \a type. + + \sa type(), setNativeKey() +*/ +void QNativeIpcKey::setType_internal(Type type) +{ + Q_UNUSED(type); +} + +/*! + \fn QNativeIpcKey::nativeKey() const noexcept + + Returns the native key string associated with this object. + + \sa setNativeKey(), type() +*/ + +/*! + \fn QNativeIpcKey::setNativeKey(const QString &newKey) + + Sets the native key for this object to \a newKey. + + \sa nativeKey(), setType() +*/ +void QNativeIpcKey::setNativeKey_internal(const QString &) +{ + d->legacyKey_.clear(); +} + +/*! + \fn size_t QNativeIpcKey::qHash(const QNativeIpcKey &ipcKey) noexcept + + Returns the hash value for \a ipcKey, using a default seed of \c 0. +*/ + +/*! + \fn size_t QNativeIpcKey::qHash(const QNativeIpcKey &ipcKey, size_t seed) noexcept + + Returns the hash value for \a ipcKey, using \a seed to seed the calculation. +*/ +size_t qHash(const QNativeIpcKey &ipcKey, size_t seed) noexcept +{ + // by *choice*, we're not including d->legacyKey_ in the hash -- it's + // already partially encoded in the key + return qHashMulti(seed, ipcKey.key, ipcKey.type()); +} + +/*! + \fn bool QNativeIpcKey::operator==(const QNativeIpcKey &lhs, const QNativeIpcKey &rhs) noexcept + \fn bool QNativeIpcKey::operator!=(const QNativeIpcKey &lhs, const QNativeIpcKey &rhs) noexcept + + Returns true if the \a lhs and \a rhs objects hold the same (or different) contents. +*/ +int QNativeIpcKey::compare_internal(const QNativeIpcKey &lhs, const QNativeIpcKey &rhs) noexcept +{ + return (QNativeIpcKeyPrivate::legacyKey(lhs) == QNativeIpcKeyPrivate::legacyKey(rhs)) ? 0 : 1; +} + +/*! + Returns the string representation of this object. String representations + are useful to inform other processes of the key this process created and + that they should attach to. + + This function returns a null string if the current object is + \l{isValid()}{invalid}. + + \sa fromString() +*/ +QString QNativeIpcKey::toString() const +{ + QString prefix = typeToString(type()); + if (prefix.isEmpty()) { + Q_ASSERT(prefix.isNull()); + return prefix; + } + + QString copy = nativeKey(); + copy.replace(u'%', "%25"_L1); + if (copy.startsWith("//"_L1)) + copy.replace(0, 2, u"/%2F"_s); // ensure it's parsed as a URL path + + QUrl u; + u.setScheme(prefix); + u.setPath(copy, QUrl::TolerantMode); + if (isSlowPath()) { + QUrlQuery q; + if (!d->legacyKey_.isEmpty()) + q.addQueryItem(u"legacyKey"_s, QString(d->legacyKey_).replace(u'%', "%25"_L1)); + u.setQuery(q); + } + return u.toString(QUrl::DecodeReserved); +} + +/*! + Parses the string form \a text and returns the corresponding QNativeIpcKey. + String representations are useful to inform other processes of the key this + process created and they should attach to. + + If the string could not be parsed, this function returns an + \l{isValid()}{invalid} object. + + \sa toString(), isValid() +*/ +QNativeIpcKey QNativeIpcKey::fromString(const QString &text) +{ + QUrl u(text, QUrl::TolerantMode); + Type invalidType = {}; + Type type = stringToType(u.scheme()); + if (type == invalidType || !u.isValid() || !u.userInfo().isEmpty() || !u.host().isEmpty() + || u.port() != -1) + return QNativeIpcKey(invalidType); + + QNativeIpcKey result(QString(), type); + if (result.type() != type) // range check, just in case + return QNativeIpcKey(invalidType); + + // decode the payload + result.setNativeKey(u.path()); + + if (u.hasQuery()) { + const QList items = QUrlQuery(u).queryItems(); + for (const auto &item : items) { + if (item.first == u"legacyKey"_s) { + QString legacyKey = QUrl::fromPercentEncoding(item.second.toUtf8()); + QNativeIpcKeyPrivate::setLegacyKey(result, std::move(legacyKey)); + } else { + // unknown query item + return QNativeIpcKey(invalidType); + } + } + } + return result; +} + +QT_END_NAMESPACE + +#include "moc_qtipccommon.cpp" + +#endif // QT_CONFIG(sharedmemory) || QT_CONFIG(systemsemaphore) diff --git a/src/corelib/ipc/qtipccommon.h b/src/corelib/ipc/qtipccommon.h new file mode 100644 index 00000000..bf0936cb --- /dev/null +++ b/src/corelib/ipc/qtipccommon.h @@ -0,0 +1,210 @@ +// Copyright (C) 2022 Intel Corporation. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only + +#ifndef QNATIVEIPCKEY_H +#define QNATIVEIPCKEY_H + +#include +#include + +#if QT_CONFIG(sharedmemory) || QT_CONFIG(systemsemaphore) +# include +# include + +QT_BEGIN_NAMESPACE + +class QNativeIpcKeyPrivate; +class QNativeIpcKey +{ + Q_GADGET_EXPORT(Q_CORE_EXPORT) +public: + enum class Type : quint16 { + // 0 is reserved for the invalid type + // keep 1 through 0xff free, except for SystemV + SystemV = 0x51, // 'Q' + + PosixRealtime = 0x100, + Windows, + }; + Q_ENUM(Type) + + static constexpr Type DefaultTypeForOs = +#ifdef Q_OS_WIN + Type::Windows +#else + Type::PosixRealtime +#endif + ; + static Type legacyDefaultTypeForOs() noexcept; + + constexpr QNativeIpcKey() noexcept = default; + + explicit constexpr QNativeIpcKey(Type type) noexcept + : typeAndFlags{type} + { + } + + Q_IMPLICIT QNativeIpcKey(const QString &k, Type type = DefaultTypeForOs) + : key(k), typeAndFlags{type} + { + } + + QNativeIpcKey(const QNativeIpcKey &other) + : d(other.d), key(other.key), typeAndFlags(other.typeAndFlags) + { + if (isSlowPath()) + copy_internal(other); + } + + QNativeIpcKey(QNativeIpcKey &&other) noexcept + : d(std::exchange(other.d, nullptr)), key(std::move(other.key)), + typeAndFlags(std::move(other.typeAndFlags)) + { + if (isSlowPath()) + move_internal(std::move(other)); + } + + ~QNativeIpcKey() + { + if (isSlowPath()) + destroy_internal(); + } + + QNativeIpcKey &operator=(const QNativeIpcKey &other) + { + typeAndFlags = other.typeAndFlags; + key = other.key; + if (isSlowPath() || other.isSlowPath()) + return assign_internal(other); + Q_ASSERT(!d); + return *this; + } + + QT_MOVE_ASSIGNMENT_OPERATOR_IMPL_VIA_PURE_SWAP(QNativeIpcKey) + void swap(QNativeIpcKey &other) noexcept + { + std::swap(d, other.d); + key.swap(other.key); + typeAndFlags.swap(other.typeAndFlags); + } + + bool isEmpty() const noexcept + { + return key.isEmpty(); + } + + bool isValid() const noexcept + { + return type() != Type{}; + } + + constexpr Type type() const noexcept + { + return typeAndFlags.type; + } + + constexpr void setType(Type type) + { + if (isSlowPath()) + return setType_internal(type); + typeAndFlags.type = type; + } + + QString nativeKey() const noexcept + { + return key; + } + void setNativeKey(const QString &newKey) + { + key = newKey; + if (isSlowPath()) + setNativeKey_internal(newKey); + } + + Q_CORE_EXPORT QString toString() const; + Q_CORE_EXPORT static QNativeIpcKey fromString(const QString &string); + +private: + struct TypeAndFlags { + Type type = DefaultTypeForOs; + quint16 reserved1 = {}; + quint32 reserved2 = {}; + + void swap(TypeAndFlags &other) noexcept + { + std::swap(type, other.type); + std::swap(reserved1, other.reserved1); + std::swap(reserved2, other.reserved2); + } + + friend constexpr bool operator==(const TypeAndFlags &lhs, const TypeAndFlags &rhs) noexcept + { + return lhs.type == rhs.type && + lhs.reserved1 == rhs.reserved1 && + lhs.reserved2 == rhs.reserved2; + } + }; + + QNativeIpcKeyPrivate *d = nullptr; + QString key; + TypeAndFlags typeAndFlags; + + friend class QNativeIpcKeyPrivate; + constexpr bool isSlowPath() const noexcept + { return Q_UNLIKELY(d); } + + friend Q_CORE_EXPORT size_t qHash(const QNativeIpcKey &ipcKey, size_t seed) noexcept; + friend size_t qHash(const QNativeIpcKey &ipcKey) noexcept + { return qHash(ipcKey, 0); } + + friend bool operator==(const QNativeIpcKey &lhs, const QNativeIpcKey &rhs) noexcept + { + if (!(lhs.typeAndFlags == rhs.typeAndFlags)) + return false; + if (lhs.key != rhs.key) + return false; + if (lhs.d == rhs.d) + return true; + return compare_internal(lhs, rhs) == 0; + } + friend bool operator!=(const QNativeIpcKey &lhs, const QNativeIpcKey &rhs) noexcept + { + return !(lhs == rhs); + } + + Q_CORE_EXPORT void copy_internal(const QNativeIpcKey &other); + Q_CORE_EXPORT void move_internal(QNativeIpcKey &&other) noexcept; + Q_CORE_EXPORT QNativeIpcKey &assign_internal(const QNativeIpcKey &other); + Q_CORE_EXPORT void destroy_internal() noexcept; + Q_CORE_EXPORT void setType_internal(Type); + Q_CORE_EXPORT void setNativeKey_internal(const QString &); + Q_DECL_PURE_FUNCTION Q_CORE_EXPORT static int + compare_internal(const QNativeIpcKey &lhs, const QNativeIpcKey &rhs) noexcept; + +#ifdef Q_OS_DARWIN + Q_DECL_CONST_FUNCTION Q_CORE_EXPORT static Type defaultTypeForOs_internal() noexcept; +#endif +}; + +// not a shared type, exactly, but this works too +Q_DECLARE_SHARED(QNativeIpcKey) + +inline auto QNativeIpcKey::legacyDefaultTypeForOs() noexcept -> Type +{ +#if defined(Q_OS_WIN) + return Type::Windows; +#elif defined(QT_POSIX_IPC) + return Type::PosixRealtime; +#elif defined(Q_OS_DARWIN) + return defaultTypeForOs_internal(); +#else + return Type::SystemV; +#endif +} + +QT_END_NAMESPACE + +#endif // QT_CONFIG(sharedmemory) || QT_CONFIG(systemsemaphore) + + +#endif // QNATIVEIPCKEY_H diff --git a/src/corelib/ipc/qtipccommon_p.h b/src/corelib/ipc/qtipccommon_p.h new file mode 100644 index 00000000..72762c5b --- /dev/null +++ b/src/corelib/ipc/qtipccommon_p.h @@ -0,0 +1,173 @@ +// Copyright (C) 2022 Intel Corporation. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only + +#ifndef QTIPCCOMMON_P_H +#define QTIPCCOMMON_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 "qtipccommon.h" +#include +#include + +#if QT_CONFIG(sharedmemory) || QT_CONFIG(systemsemaphore) + +#if defined(Q_OS_UNIX) +# include +# include +#endif + +QT_BEGIN_NAMESPACE + +class QNativeIpcKeyPrivate +{ +public: + QString legacyKey_; + + static QString legacyKey(const QNativeIpcKey &key) + { + if (key.isSlowPath()) + return key.d->legacyKey_; + return QString(); + } + static void setLegacyKey(QNativeIpcKey &key, const QString &legacyKey) + { + QNativeIpcKeyPrivate::makeExtended(key)->legacyKey_ = legacyKey; + } + static void setNativeAndLegacyKeys(QNativeIpcKey &key, const QString &nativeKey, + const QString &legacyKey) + { + key.setNativeKey(nativeKey); + setLegacyKey(key, legacyKey); + } + +private: + static QNativeIpcKeyPrivate *makeExtended(QNativeIpcKey &key) + { + if (!key.isSlowPath()) + key.d = new QNativeIpcKeyPrivate; + return key.d; + } +}; + +namespace QtIpcCommon { +enum class IpcType { + SharedMemory, + SystemSemaphore +}; + +static constexpr bool isIpcSupported(IpcType ipcType, QNativeIpcKey::Type type) +{ + switch (type) { + case QNativeIpcKey::Type::SystemV: + break; + + case QNativeIpcKey::Type::PosixRealtime: + if (ipcType == IpcType::SharedMemory) + return QT_CONFIG(posix_shm); + return QT_CONFIG(posix_sem); + + case QNativeIpcKey::Type::Windows: +#ifdef Q_OS_WIN + return true; +#else + return false; +#endif + } + + if (ipcType == IpcType::SharedMemory) + return QT_CONFIG(sysv_shm); + return QT_CONFIG(sysv_sem); +} + +template class IpcStorageVariant +{ + template static C extractClass(T C::*); + template static T extractObject(T C::*); + + template + static constexpr bool IsEnabled = decltype(extractObject(M))::Enabled; + + static_assert(std::is_member_object_pointer_v); + using StorageType = decltype(extractClass(Member1)); + StorageType d; + +public: + template static auto + visit_internal(StorageType &storage, QNativeIpcKey::Type keyType, const Lambda &lambda) + { + if constexpr ((IsEnabled || ... || IsEnabled)) { + if constexpr (IsEnabled) { + using MemberType1 = decltype(extractObject(Member1)); + if (MemberType1::supports(keyType)) + return lambda(&(storage.*Member1)); + } + if constexpr ((... || IsEnabled)) + return IpcStorageVariant::visit_internal(storage, keyType, lambda); + Q_UNREACHABLE(); + } else { + // no backends enabled, but we can't return void + return false; + } + } + + template auto visit(QNativeIpcKey::Type keyType, const Lambda &lambda) + { + return visit_internal(d, keyType, lambda); + } + + template static auto + staticVisit(QNativeIpcKey::Type keyType, const Lambda &lambda) + { + if constexpr ((IsEnabled || ... || IsEnabled)) { + if constexpr (IsEnabled) { + using MemberType1 = decltype(extractObject(Member1)); + if (MemberType1::supports(keyType)) + return lambda(static_cast(nullptr)); + } + if constexpr ((... || IsEnabled)) + return IpcStorageVariant::staticVisit(keyType, lambda); + Q_UNREACHABLE(); + } else { + // no backends enabled, but we can't return void + return false; + } + } +}; + +QNativeIpcKey legacyPlatformSafeKey(const QString &key, IpcType ipcType, QNativeIpcKey::Type type); +QNativeIpcKey platformSafeKey(const QString &key, IpcType ipcType, QNativeIpcKey::Type type); + +#ifdef Q_OS_UNIX +// Convenience function to create the file if needed +inline int createUnixKeyFile(const QByteArray &fileName) +{ + int fd = qt_safe_open(fileName.constData(), O_EXCL | O_CREAT | O_RDWR, 0640); + if (fd < 0) { + if (errno == EEXIST) + return 0; + return -1; + } else { + close(fd); + } + return 1; + +} +#endif // Q_OS_UNIX +} // namespace QtIpcCommon + +QT_END_NAMESPACE + +#endif // QT_CONFIG(sharedmemory) || QT_CONFIG(systemsemaphore) + + +#endif // QTIPCCOMMON_P_H diff --git a/src/corelib/itemmodels/qabstractitemmodel.cpp b/src/corelib/itemmodels/qabstractitemmodel.cpp index 3647ee9d..31a88e8f 100644 --- a/src/corelib/itemmodels/qabstractitemmodel.cpp +++ b/src/corelib/itemmodels/qabstractitemmodel.cpp @@ -2538,8 +2538,10 @@ QModelIndexList QAbstractItemModel::match(const QModelIndex &start, int role, } } } else if (matchType == Qt::MatchWildcard) { - if (rx.pattern().isEmpty()) - rx.setPattern(QRegularExpression::wildcardToRegularExpression(value.toString())); + if (rx.pattern().isEmpty()) { + const QString pattern = QRegularExpression::wildcardToRegularExpression(value.toString(), QRegularExpression::NonPathWildcardConversion); + rx.setPattern(pattern); + } if (cs == Qt::CaseInsensitive) rx.setPatternOptions(QRegularExpression::CaseInsensitiveOption); } else diff --git a/src/corelib/itemmodels/qabstractitemmodel.h b/src/corelib/itemmodels/qabstractitemmodel.h index e5641224..86b3eb76 100644 --- a/src/corelib/itemmodels/qabstractitemmodel.h +++ b/src/corelib/itemmodels/qabstractitemmodel.h @@ -499,7 +499,13 @@ inline Qt::ItemFlags QModelIndex::flags() const { return m ? m->flags(*this) : Qt::ItemFlags(); } inline size_t qHash(const QModelIndex &index, size_t seed = 0) noexcept -{ return size_t((size_t(index.row()) << 4) + size_t(index.column()) + index.internalId()) ^ seed; } +{ +#if QT_VERSION >= QT_VERSION_CHECK(7, 0, 0) + return qHashMulti(seed, index.row(), index.column(), index.internalId()); +#else + return size_t((size_t(index.row()) << 4) + size_t(index.column()) + index.internalId()) ^ seed; +#endif +} QT_END_NAMESPACE diff --git a/src/corelib/itemmodels/qabstractproxymodel.cpp b/src/corelib/itemmodels/qabstractproxymodel.cpp index 9b5944de..83e8cb8d 100644 --- a/src/corelib/itemmodels/qabstractproxymodel.cpp +++ b/src/corelib/itemmodels/qabstractproxymodel.cpp @@ -173,13 +173,15 @@ void QAbstractProxyModel::setSourceModel(QAbstractItemModel *sourceModel) const char *signalName; const char *slotName; } connectionTable[] = { + // clang-format off { SIGNAL(destroyed()), SLOT(_q_sourceModelDestroyed()) }, - { SIGNAL(rowsAboutToBeInserted(QModelIndex, int, int)), SLOT(_q_sourceModelRowsAboutToBeInserted(QModelIndex, int, int)) }, - { SIGNAL(rowsInserted(QModelIndex, int, int)), SLOT(_q_sourceModelRowsInserted(QModelIndex, int, int)) }, - { SIGNAL(rowsRemoved(QModelIndex, int, int)), SLOT(_q_sourceModelRowsRemoved(QModelIndex, int, int)) }, - { SIGNAL(columnsAboutToBeInserted(QModelIndex, int, int)), SLOT(_q_sourceModelColumnsAboutToBeInserted(QModelIndex, int, int)) }, - { SIGNAL(columnsInserted(QModelIndex, int, int)), SLOT(_q_sourceModelColumnsInserted(QModelIndex, int, int)) }, - { SIGNAL(columnsRemoved(QModelIndex, int, int)), SLOT(_q_sourceModelColumnsRemoved(QModelIndex, int, int)) } + { SIGNAL(rowsAboutToBeInserted(QModelIndex,int,int)), SLOT(_q_sourceModelRowsAboutToBeInserted(QModelIndex,int,int)) }, + { SIGNAL(rowsInserted(QModelIndex,int,int)), SLOT(_q_sourceModelRowsInserted(QModelIndex,int,int)) }, + { SIGNAL(rowsRemoved(QModelIndex,int,int)), SLOT(_q_sourceModelRowsRemoved(QModelIndex,int,int)) }, + { SIGNAL(columnsAboutToBeInserted(QModelIndex,int,int)), SLOT(_q_sourceModelColumnsAboutToBeInserted(QModelIndex,int,int)) }, + { SIGNAL(columnsInserted(QModelIndex,int,int)), SLOT(_q_sourceModelColumnsInserted(QModelIndex,int,int)) }, + { SIGNAL(columnsRemoved(QModelIndex,int,int)), SLOT(_q_sourceModelColumnsRemoved(QModelIndex,int,int)) } + // clang-format on }; if (sourceModel != currentModel) { diff --git a/src/corelib/itemmodels/qconcatenatetablesproxymodel.cpp b/src/corelib/itemmodels/qconcatenatetablesproxymodel.cpp index 44cf28b4..e6d12cd7 100644 --- a/src/corelib/itemmodels/qconcatenatetablesproxymodel.cpp +++ b/src/corelib/itemmodels/qconcatenatetablesproxymodel.cpp @@ -446,10 +446,10 @@ void QConcatenateTablesProxyModel::addSourceModel(QAbstractItemModel *sourceMode connect(sourceModel, SIGNAL(columnsAboutToBeInserted(QModelIndex,int,int)), this, SLOT(_q_slotColumnsAboutToBeInserted(QModelIndex,int,int))); connect(sourceModel, SIGNAL(columnsAboutToBeRemoved(QModelIndex,int,int)), this, SLOT(_q_slotColumnsAboutToBeRemoved(QModelIndex,int,int))); - connect(sourceModel, SIGNAL(layoutAboutToBeChanged(QList, QAbstractItemModel::LayoutChangeHint)), - this, SLOT(_q_slotSourceLayoutAboutToBeChanged(QList, QAbstractItemModel::LayoutChangeHint))); - connect(sourceModel, SIGNAL(layoutChanged(QList, QAbstractItemModel::LayoutChangeHint)), - this, SLOT(_q_slotSourceLayoutChanged(QList, QAbstractItemModel::LayoutChangeHint))); + connect(sourceModel, SIGNAL(layoutAboutToBeChanged(QList,QAbstractItemModel::LayoutChangeHint)), + this, SLOT(_q_slotSourceLayoutAboutToBeChanged(QList,QAbstractItemModel::LayoutChangeHint))); + connect(sourceModel, SIGNAL(layoutChanged(QList,QAbstractItemModel::LayoutChangeHint)), + this, SLOT(_q_slotSourceLayoutChanged(QList,QAbstractItemModel::LayoutChangeHint))); connect(sourceModel, SIGNAL(modelAboutToBeReset()), this, SLOT(_q_slotModelAboutToBeReset())); connect(sourceModel, SIGNAL(modelReset()), this, SLOT(_q_slotModelReset())); diff --git a/src/corelib/itemmodels/qsortfilterproxymodel.cpp b/src/corelib/itemmodels/qsortfilterproxymodel.cpp index fac5a7b7..4600bcd9 100644 --- a/src/corelib/itemmodels/qsortfilterproxymodel.cpp +++ b/src/corelib/itemmodels/qsortfilterproxymodel.cpp @@ -109,9 +109,9 @@ private: class QSortFilterProxyModelPrivate : public QAbstractProxyModelPrivate { +public: Q_DECLARE_PUBLIC(QSortFilterProxyModel) -public: enum class Direction { Rows = 1, Columns = 2, @@ -238,6 +238,8 @@ public: QModelIndexPairList saved_persistent_indexes; QList saved_layoutChange_parents; + std::array sourceConnections; + QHash::const_iterator create_mapping( const QModelIndex &source_parent) const; QHash::const_iterator create_mapping_recursive( @@ -2006,7 +2008,9 @@ void QSortFilterProxyModelPrivate::_q_sourceColumnsMoved( QSortFilterProxyModel::QSortFilterProxyModel(QObject *parent) : QAbstractProxyModel(*new QSortFilterProxyModelPrivate, parent) { - connect(this, SIGNAL(modelReset()), this, SLOT(_q_clearMapping())); + Q_D(QSortFilterProxyModel); + QObjectPrivate::connect(this, &QAbstractItemModel::modelReset, d, + &QSortFilterProxyModelPrivate::_q_clearMapping); } /*! @@ -2031,56 +2035,10 @@ void QSortFilterProxyModel::setSourceModel(QAbstractItemModel *sourceModel) beginResetModel(); - disconnect(d->model, SIGNAL(dataChanged(QModelIndex,QModelIndex,QList)), - this, SLOT(_q_sourceDataChanged(QModelIndex,QModelIndex,QList))); - - disconnect(d->model, SIGNAL(headerDataChanged(Qt::Orientation,int,int)), - this, SLOT(_q_sourceHeaderDataChanged(Qt::Orientation,int,int))); - - disconnect(d->model, SIGNAL(rowsAboutToBeInserted(QModelIndex,int,int)), - this, SLOT(_q_sourceRowsAboutToBeInserted(QModelIndex,int,int))); - - disconnect(d->model, SIGNAL(rowsInserted(QModelIndex,int,int)), - this, SLOT(_q_sourceRowsInserted(QModelIndex,int,int))); - - disconnect(d->model, SIGNAL(columnsAboutToBeInserted(QModelIndex,int,int)), - this, SLOT(_q_sourceColumnsAboutToBeInserted(QModelIndex,int,int))); - - disconnect(d->model, SIGNAL(columnsInserted(QModelIndex,int,int)), - this, SLOT(_q_sourceColumnsInserted(QModelIndex,int,int))); - - disconnect(d->model, SIGNAL(rowsAboutToBeRemoved(QModelIndex,int,int)), - this, SLOT(_q_sourceRowsAboutToBeRemoved(QModelIndex,int,int))); - - disconnect(d->model, SIGNAL(rowsRemoved(QModelIndex,int,int)), - this, SLOT(_q_sourceRowsRemoved(QModelIndex,int,int))); - - disconnect(d->model, SIGNAL(columnsAboutToBeRemoved(QModelIndex,int,int)), - this, SLOT(_q_sourceColumnsAboutToBeRemoved(QModelIndex,int,int))); - - disconnect(d->model, SIGNAL(columnsRemoved(QModelIndex,int,int)), - this, SLOT(_q_sourceColumnsRemoved(QModelIndex,int,int))); - - disconnect(d->model, SIGNAL(rowsAboutToBeMoved(QModelIndex,int,int,QModelIndex,int)), - this, SLOT(_q_sourceRowsAboutToBeMoved(QModelIndex,int,int,QModelIndex,int))); - - disconnect(d->model, SIGNAL(rowsMoved(QModelIndex,int,int,QModelIndex,int)), - this, SLOT(_q_sourceRowsMoved(QModelIndex,int,int,QModelIndex,int))); - - disconnect(d->model, SIGNAL(columnsAboutToBeMoved(QModelIndex,int,int,QModelIndex,int)), - this, SLOT(_q_sourceColumnsAboutToBeMoved(QModelIndex,int,int,QModelIndex,int))); - - disconnect(d->model, SIGNAL(columnsMoved(QModelIndex,int,int,QModelIndex,int)), - this, SLOT(_q_sourceColumnsMoved(QModelIndex,int,int,QModelIndex,int))); - - disconnect(d->model, SIGNAL(layoutAboutToBeChanged(QList,QAbstractItemModel::LayoutChangeHint)), - this, SLOT(_q_sourceLayoutAboutToBeChanged(QList,QAbstractItemModel::LayoutChangeHint))); - - disconnect(d->model, SIGNAL(layoutChanged(QList,QAbstractItemModel::LayoutChangeHint)), - this, SLOT(_q_sourceLayoutChanged(QList,QAbstractItemModel::LayoutChangeHint))); - - disconnect(d->model, SIGNAL(modelAboutToBeReset()), this, SLOT(_q_sourceAboutToBeReset())); - disconnect(d->model, SIGNAL(modelReset()), this, SLOT(_q_sourceReset())); + if (d->model) { + for (const QMetaObject::Connection &connection : std::as_const(d->sourceConnections)) + disconnect(connection); + } // same as in _q_sourceReset() d->invalidatePersistentIndexes(); @@ -2088,57 +2046,61 @@ void QSortFilterProxyModel::setSourceModel(QAbstractItemModel *sourceModel) QAbstractProxyModel::setSourceModel(sourceModel); - connect(d->model, SIGNAL(dataChanged(QModelIndex,QModelIndex,QList)), - this, SLOT(_q_sourceDataChanged(QModelIndex,QModelIndex,QList))); + d->sourceConnections = std::array{ + QObjectPrivate::connect(d->model, &QAbstractItemModel::dataChanged, d, + &QSortFilterProxyModelPrivate::_q_sourceDataChanged), - connect(d->model, SIGNAL(headerDataChanged(Qt::Orientation,int,int)), - this, SLOT(_q_sourceHeaderDataChanged(Qt::Orientation,int,int))); + QObjectPrivate::connect(d->model, &QAbstractItemModel::headerDataChanged, d, + &QSortFilterProxyModelPrivate::_q_sourceHeaderDataChanged), - connect(d->model, SIGNAL(rowsAboutToBeInserted(QModelIndex,int,int)), - this, SLOT(_q_sourceRowsAboutToBeInserted(QModelIndex,int,int))); + QObjectPrivate::connect(d->model, &QAbstractItemModel::rowsAboutToBeInserted, d, + &QSortFilterProxyModelPrivate::_q_sourceRowsAboutToBeInserted), - connect(d->model, SIGNAL(rowsInserted(QModelIndex,int,int)), - this, SLOT(_q_sourceRowsInserted(QModelIndex,int,int))); + QObjectPrivate::connect(d->model, &QAbstractItemModel::rowsInserted, d, + &QSortFilterProxyModelPrivate::_q_sourceRowsInserted), - connect(d->model, SIGNAL(columnsAboutToBeInserted(QModelIndex,int,int)), - this, SLOT(_q_sourceColumnsAboutToBeInserted(QModelIndex,int,int))); + QObjectPrivate::connect(d->model, &QAbstractItemModel::columnsAboutToBeInserted, d, + &QSortFilterProxyModelPrivate::_q_sourceColumnsAboutToBeInserted), - connect(d->model, SIGNAL(columnsInserted(QModelIndex,int,int)), - this, SLOT(_q_sourceColumnsInserted(QModelIndex,int,int))); + QObjectPrivate::connect(d->model, &QAbstractItemModel::columnsInserted, d, + &QSortFilterProxyModelPrivate::_q_sourceColumnsInserted), - connect(d->model, SIGNAL(rowsAboutToBeRemoved(QModelIndex,int,int)), - this, SLOT(_q_sourceRowsAboutToBeRemoved(QModelIndex,int,int))); + QObjectPrivate::connect(d->model, &QAbstractItemModel::rowsAboutToBeRemoved, d, + &QSortFilterProxyModelPrivate::_q_sourceRowsAboutToBeRemoved), - connect(d->model, SIGNAL(rowsRemoved(QModelIndex,int,int)), - this, SLOT(_q_sourceRowsRemoved(QModelIndex,int,int))); + QObjectPrivate::connect(d->model, &QAbstractItemModel::rowsRemoved, d, + &QSortFilterProxyModelPrivate::_q_sourceRowsRemoved), - connect(d->model, SIGNAL(columnsAboutToBeRemoved(QModelIndex,int,int)), - this, SLOT(_q_sourceColumnsAboutToBeRemoved(QModelIndex,int,int))); + QObjectPrivate::connect(d->model, &QAbstractItemModel::columnsAboutToBeRemoved, d, + &QSortFilterProxyModelPrivate::_q_sourceColumnsAboutToBeRemoved), - connect(d->model, SIGNAL(columnsRemoved(QModelIndex,int,int)), - this, SLOT(_q_sourceColumnsRemoved(QModelIndex,int,int))); + QObjectPrivate::connect(d->model, &QAbstractItemModel::columnsRemoved, d, + &QSortFilterProxyModelPrivate::_q_sourceColumnsRemoved), - connect(d->model, SIGNAL(rowsAboutToBeMoved(QModelIndex,int,int,QModelIndex,int)), - this, SLOT(_q_sourceRowsAboutToBeMoved(QModelIndex,int,int,QModelIndex,int))); + QObjectPrivate::connect(d->model, &QAbstractItemModel::rowsAboutToBeMoved, d, + &QSortFilterProxyModelPrivate::_q_sourceRowsAboutToBeMoved), - connect(d->model, SIGNAL(rowsMoved(QModelIndex,int,int,QModelIndex,int)), - this, SLOT(_q_sourceRowsMoved(QModelIndex,int,int,QModelIndex,int))); + QObjectPrivate::connect(d->model, &QAbstractItemModel::rowsMoved, d, + &QSortFilterProxyModelPrivate::_q_sourceRowsMoved), - connect(d->model, SIGNAL(columnsAboutToBeMoved(QModelIndex,int,int,QModelIndex,int)), - this, SLOT(_q_sourceColumnsAboutToBeMoved(QModelIndex,int,int,QModelIndex,int))); + QObjectPrivate::connect(d->model, &QAbstractItemModel::columnsAboutToBeMoved, d, + &QSortFilterProxyModelPrivate::_q_sourceColumnsAboutToBeMoved), - connect(d->model, SIGNAL(columnsMoved(QModelIndex,int,int,QModelIndex,int)), - this, SLOT(_q_sourceColumnsMoved(QModelIndex,int,int,QModelIndex,int))); + QObjectPrivate::connect(d->model, &QAbstractItemModel::columnsMoved, d, + &QSortFilterProxyModelPrivate::_q_sourceColumnsMoved), - connect(d->model, SIGNAL(layoutAboutToBeChanged(QList,QAbstractItemModel::LayoutChangeHint)), - this, SLOT(_q_sourceLayoutAboutToBeChanged(QList,QAbstractItemModel::LayoutChangeHint))); + QObjectPrivate::connect(d->model, &QAbstractItemModel::layoutAboutToBeChanged, d, + &QSortFilterProxyModelPrivate::_q_sourceLayoutAboutToBeChanged), - connect(d->model, SIGNAL(layoutChanged(QList,QAbstractItemModel::LayoutChangeHint)), - this, SLOT(_q_sourceLayoutChanged(QList,QAbstractItemModel::LayoutChangeHint))); + QObjectPrivate::connect(d->model, &QAbstractItemModel::layoutChanged, d, + &QSortFilterProxyModelPrivate::_q_sourceLayoutChanged), - connect(d->model, SIGNAL(modelAboutToBeReset()), this, SLOT(_q_sourceAboutToBeReset())); - connect(d->model, SIGNAL(modelReset()), this, SLOT(_q_sourceReset())); + QObjectPrivate::connect(d->model, &QAbstractItemModel::modelAboutToBeReset, d, + &QSortFilterProxyModelPrivate::_q_sourceAboutToBeReset), + QObjectPrivate::connect(d->model, &QAbstractItemModel::modelReset, d, + &QSortFilterProxyModelPrivate::_q_sourceReset) + }; endResetModel(); if (d->update_source_sort_column() && d->dynamic_sortfilter) d->sort(); @@ -2620,7 +2582,7 @@ QBindable QSortFilterProxyModel::bindableFilterRegularExpres void QSortFilterProxyModel::setFilterRegularExpression(const QRegularExpression ®ularExpression) { Q_D(QSortFilterProxyModel); - Qt::beginPropertyUpdateGroup(); + const QScopedPropertyUpdateGroup guard; const bool regExpChanged = regularExpression != d->filter_regularexpression.valueBypassingBindings(); d->filter_regularexpression.removeBindingUnlessInWrapper(); @@ -2640,7 +2602,6 @@ void QSortFilterProxyModel::setFilterRegularExpression(const QRegularExpression d->filter_regularexpression.notify(); if (cs != updatedCs) d->filter_casesensitive.notify(); - Qt::endPropertyUpdateGroup(); } /*! @@ -2716,7 +2677,7 @@ void QSortFilterProxyModel::setFilterCaseSensitivity(Qt::CaseSensitivity cs) if (cs == d->filter_casesensitive) return; - Qt::beginPropertyUpdateGroup(); + const QScopedPropertyUpdateGroup guard; QRegularExpression::PatternOptions options = d->filter_regularexpression.value().patternOptions(); options.setFlag(QRegularExpression::CaseInsensitiveOption, cs == Qt::CaseInsensitive); @@ -2729,7 +2690,6 @@ void QSortFilterProxyModel::setFilterCaseSensitivity(Qt::CaseSensitivity cs) d->filter_changed(QSortFilterProxyModelPrivate::Direction::Rows); d->filter_regularexpression.notify(); d->filter_casesensitive.notify(); - Qt::endPropertyUpdateGroup(); } QBindable QSortFilterProxyModel::bindableFilterCaseSensitivity() diff --git a/src/corelib/itemmodels/qsortfilterproxymodel.h b/src/corelib/itemmodels/qsortfilterproxymodel.h index bbd829d7..9d5b2fac 100644 --- a/src/corelib/itemmodels/qsortfilterproxymodel.h +++ b/src/corelib/itemmodels/qsortfilterproxymodel.h @@ -172,28 +172,6 @@ Q_SIGNALS: private: Q_DECLARE_PRIVATE(QSortFilterProxyModel) Q_DISABLE_COPY(QSortFilterProxyModel) - - Q_PRIVATE_SLOT(d_func(), - void _q_sourceDataChanged(const QModelIndex &source_top_left, const QModelIndex &source_bottom_right, - const QList &roles)) - Q_PRIVATE_SLOT(d_func(), void _q_sourceHeaderDataChanged(Qt::Orientation orientation, int start, int end)) - Q_PRIVATE_SLOT(d_func(), void _q_sourceAboutToBeReset()) - Q_PRIVATE_SLOT(d_func(), void _q_sourceReset()) - Q_PRIVATE_SLOT(d_func(), void _q_sourceLayoutAboutToBeChanged(const QList &sourceParents, QAbstractItemModel::LayoutChangeHint hint)) - Q_PRIVATE_SLOT(d_func(), void _q_sourceLayoutChanged(const QList &sourceParents, QAbstractItemModel::LayoutChangeHint hint)) - Q_PRIVATE_SLOT(d_func(), void _q_sourceRowsAboutToBeInserted(const QModelIndex &source_parent, int start, int end)) - Q_PRIVATE_SLOT(d_func(), void _q_sourceRowsInserted(const QModelIndex &source_parent, int start, int end)) - Q_PRIVATE_SLOT(d_func(), void _q_sourceRowsAboutToBeRemoved(const QModelIndex &source_parent, int start, int end)) - Q_PRIVATE_SLOT(d_func(), void _q_sourceRowsRemoved(const QModelIndex &source_parent, int start, int end)) - Q_PRIVATE_SLOT(d_func(), void _q_sourceRowsAboutToBeMoved(QModelIndex,int,int,QModelIndex,int)) - Q_PRIVATE_SLOT(d_func(), void _q_sourceRowsMoved(QModelIndex,int,int,QModelIndex,int)) - Q_PRIVATE_SLOT(d_func(), void _q_sourceColumnsAboutToBeInserted(const QModelIndex &source_parent, int start, int end)) - Q_PRIVATE_SLOT(d_func(), void _q_sourceColumnsInserted(const QModelIndex &source_parent, int start, int end)) - Q_PRIVATE_SLOT(d_func(), void _q_sourceColumnsAboutToBeRemoved(const QModelIndex &source_parent, int start, int end)) - Q_PRIVATE_SLOT(d_func(), void _q_sourceColumnsRemoved(const QModelIndex &source_parent, int start, int end)) - Q_PRIVATE_SLOT(d_func(), void _q_sourceColumnsAboutToBeMoved(QModelIndex,int,int,QModelIndex,int)) - Q_PRIVATE_SLOT(d_func(), void _q_sourceColumnsMoved(QModelIndex,int,int,QModelIndex,int)) - Q_PRIVATE_SLOT(d_func(), void _q_clearMapping()) }; QT_END_NAMESPACE diff --git a/src/corelib/kernel/qabstracteventdispatcher.cpp b/src/corelib/kernel/qabstracteventdispatcher.cpp index 3001e326..98493946 100644 --- a/src/corelib/kernel/qabstracteventdispatcher.cpp +++ b/src/corelib/kernel/qabstracteventdispatcher.cpp @@ -52,6 +52,15 @@ Q_CONSTINIT const int QtTimerIdFreeListConstants::Sizes[QtTimerIdFreeListConstan typedef QFreeList QtTimerIdFreeList; Q_GLOBAL_STATIC(QtTimerIdFreeList, timerIdFreeList) +QAbstractEventDispatcherPrivate::QAbstractEventDispatcherPrivate() +{ + // Create the timer ID free list here to make sure that it is destroyed + // after any global static thread that may be using it. + // See also QTBUG-58732. + if (!timerIdFreeList.isDestroyed()) + (void)timerIdFreeList(); +} + QAbstractEventDispatcherPrivate::~QAbstractEventDispatcherPrivate() = default; diff --git a/src/corelib/kernel/qabstracteventdispatcher_p.h b/src/corelib/kernel/qabstracteventdispatcher_p.h index e7b1ac3b..7d57fd03 100644 --- a/src/corelib/kernel/qabstracteventdispatcher_p.h +++ b/src/corelib/kernel/qabstracteventdispatcher_p.h @@ -26,8 +26,7 @@ class Q_CORE_EXPORT QAbstractEventDispatcherPrivate : public QObjectPrivate { Q_DECLARE_PUBLIC(QAbstractEventDispatcher) public: - inline QAbstractEventDispatcherPrivate() - { } + QAbstractEventDispatcherPrivate(); ~QAbstractEventDispatcherPrivate() override; QList eventFilters; diff --git a/src/corelib/kernel/qapplicationstatic.qdoc b/src/corelib/kernel/qapplicationstatic.qdoc index 82eb7265..5cbac65d 100644 --- a/src/corelib/kernel/qapplicationstatic.qdoc +++ b/src/corelib/kernel/qapplicationstatic.qdoc @@ -20,9 +20,8 @@ the QCoreApplication. This makes it ideal to store semi-static QObjects, which should also be destroyed once the QCoreApplication is destroyed. This means the type will get deleted once the QCoreApplication emits the destroyed signal. - However, as long as the actual holder is still in the initialized state, the - type will be recreated when it's accessed again once a new QCoreApplication - has been created. + It is permitted for the object to be recreated when it's accessed again, if + a new QCoreApplication has also been created. Since the value is bound to the QCoreApplication, it should only ever be accessed if there is a valid QCoreApplication::instance(). Accessing this @@ -45,6 +44,31 @@ this macro behaves identically to Q_GLOBAL_STATIC(). Please see that macro's documentation for more information. + \section1 Threading guarantees + + The Q_APPLICATION_STATIC macro ensures that the object is initialized only + once (per lifetime of a QCoreApplication), even if multiple threads try to + concurrently access the object. This is done by providing a per-object + mutex; application and library developers need to be aware that their + object will be constructed with this mutex locked and therefore must not + reenter the same object's initialization, or a deadlock will occur. + + There is no thread-safety on the destruction of the object: user code must + not access this object once the QCoreApplication destructor starts to run. + User code must arrange to ensure this does not happen, such as by not + accessing it once the main thread's event loop has exited. + + Like Q_GLOBAL_STATIC, Q_APPLICATION_STATIC provides no thread-safety + guarantees for accesses to the object once creation is finished. It is up + to user code to ensure that no racy data accesses happen. + + In case the object created by this operation is a QObject, its associated + thread will be the one that succeeded in creating it. It will be destroyed + by the main thread, so a \l{QObject::}{moveToThread()} to the main thread + or to no thread before destruction is adviseable. Doing so from the + constructor of the class in question is a sensible solution if one can't + guarantee that the main thread will be the one to initialize the object. + \omit \section1 Implementation details See \l Q_GLOBAL_STATIC implementation details for an introduction. diff --git a/src/corelib/kernel/qbasictimer.cpp b/src/corelib/kernel/qbasictimer.cpp index b843ec1e..cf9c0bbc 100644 --- a/src/corelib/kernel/qbasictimer.cpp +++ b/src/corelib/kernel/qbasictimer.cpp @@ -86,10 +86,18 @@ QT_BEGIN_NAMESPACE /*! \fn QBasicTimer::swap(QBasicTimer &other) - \fn swap(QBasicTimer &lhs, QBasicTimer &rhs) \since 5.14 - Swaps string \a other with this string, or \a lhs with \a rhs. + Swaps the timer \a other with this timer. + This operation is very fast and never fails. +*/ + +/*! + \fn swap(QBasicTimer &lhs, QBasicTimer &rhs) + \relates QBasicTimer + \since 5.14 + + Swaps the timer \a lhs with \a rhs. This operation is very fast and never fails. */ diff --git a/src/corelib/kernel/qcore_mac.mm b/src/corelib/kernel/qcore_mac.mm index c5f9a191..12bf81f0 100644 --- a/src/corelib/kernel/qcore_mac.mm +++ b/src/corelib/kernel/qcore_mac.mm @@ -242,10 +242,20 @@ QT_USE_NAMESPACE QT_NAMESPACE_ALIAS_OBJC_CLASS(QMacAutoReleasePoolTracker); #endif // QT_DEBUG +// Use the direct runtime interface to manage autorelease pools, as it +// has less overhead then allocating NSAutoreleasePools, and allows for +// a future where we use ARC (where NSAutoreleasePool is not allowed). +// https://clang.llvm.org/docs/AutomaticReferenceCounting.html#runtime-support + +extern "C" { +void *objc_autoreleasePoolPush(void); +void objc_autoreleasePoolPop(void *pool); +} + QT_BEGIN_NAMESPACE QMacAutoReleasePool::QMacAutoReleasePool() - : pool([[NSAutoreleasePool alloc] init]) + : pool(objc_autoreleasePoolPush()) { #ifdef QT_DEBUG static const bool debugAutoReleasePools = qEnvironmentVariableIsSet("QT_DARWIN_DEBUG_AUTORELEASEPOOLS"); @@ -290,10 +300,7 @@ QMacAutoReleasePool::QMacAutoReleasePool() QMacAutoReleasePool::~QMacAutoReleasePool() { - // Drain behaves the same as release, with the advantage that - // if we're ever used in a garbage-collected environment, the - // drain acts as a hint to the garbage collector to collect. - [static_cast(pool) drain]; + objc_autoreleasePoolPop(pool); } #ifndef QT_NO_DEBUG_STREAM diff --git a/src/corelib/kernel/qcore_mac_p.h b/src/corelib/kernel/qcore_mac_p.h index 22c045f3..1b0283e7 100644 --- a/src/corelib/kernel/qcore_mac_p.h +++ b/src/corelib/kernel/qcore_mac_p.h @@ -85,15 +85,18 @@ template class QAppleRefCounted { public: - QAppleRefCounted() : value() {} - QAppleRefCounted(const T &t) : value(t) {} - QAppleRefCounted(T &&t) noexcept(std::is_nothrow_move_constructible::value) + Q_NODISCARD_CTOR QAppleRefCounted() : value() {} + Q_NODISCARD_CTOR QAppleRefCounted(const T &t) : value(t) {} + Q_NODISCARD_CTOR QAppleRefCounted(T &&t) + noexcept(std::is_nothrow_move_constructible::value) : value(std::move(t)) {} - QAppleRefCounted(QAppleRefCounted &&other) + Q_NODISCARD_CTOR QAppleRefCounted(QAppleRefCounted &&other) noexcept(std::is_nothrow_move_assignable::value && std::is_nothrow_move_constructible::value) : value(std::exchange(other.value, T())) {} - QAppleRefCounted(const QAppleRefCounted &other) : value(other.value) { if (value) RetainFunction(value); } + Q_NODISCARD_CTOR QAppleRefCounted(const QAppleRefCounted &other) + : value(other.value) + { if (value) RetainFunction(value); } ~QAppleRefCounted() { if (value) ReleaseFunction(value); } operator T() const { return value; } void swap(QAppleRefCounted &other) noexcept(noexcept(qSwap(value, other.value))) @@ -109,11 +112,11 @@ protected: T value; }; -class Q_CORE_EXPORT QMacAutoReleasePool +class QMacAutoReleasePool { public: - QMacAutoReleasePool(); - ~QMacAutoReleasePool(); + Q_NODISCARD_CTOR Q_CORE_EXPORT QMacAutoReleasePool(); + Q_CORE_EXPORT ~QMacAutoReleasePool(); private: Q_DISABLE_COPY(QMacAutoReleasePool) void *pool; @@ -123,7 +126,7 @@ private: class QMacRootLevelAutoReleasePool { public: - QMacRootLevelAutoReleasePool(); + Q_NODISCARD_CTOR QMacRootLevelAutoReleasePool(); ~QMacRootLevelAutoReleasePool(); private: QScopedPointer pool; @@ -148,7 +151,7 @@ class QCFType : public QAppleRefCounted using Base = QAppleRefCounted; public: using Base::Base; - explicit QCFType(CFTypeRef r) : Base(static_cast(r)) {} + Q_NODISCARD_CTOR explicit QCFType(CFTypeRef r) : Base(static_cast(r)) {} template X as() const { return reinterpret_cast(this->value); } static QCFType constructFromGet(const T &t) { @@ -166,15 +169,15 @@ class QIOType : public QAppleRefCounted +class QCFString : public QCFType { public: using QCFType::QCFType; - inline QCFString(const QString &str) : QCFType(0), string(str) {} - inline QCFString(const CFStringRef cfstr = 0) : QCFType(cfstr) {} - inline QCFString(const QCFType &other) : QCFType(other) {} - operator QString() const; - operator CFStringRef() const; + Q_NODISCARD_CTOR QCFString(const QString &str) : QCFType(0), string(str) {} + Q_NODISCARD_CTOR QCFString(const CFStringRef cfstr = 0) : QCFType(cfstr) {} + Q_NODISCARD_CTOR QCFString(const QCFType &other) : QCFType(other) {} + Q_CORE_EXPORT operator QString() const; + Q_CORE_EXPORT operator CFStringRef() const; private: QString string; @@ -438,6 +441,20 @@ private: // ------------------------------------------------------------------------- +#ifdef __OBJC__ +template +typename std::enable_if::value, T>::type +qt_objc_cast(id object) +{ + if ([object isKindOfClass:[typename std::remove_pointer::type class]]) + return static_cast(object); + + return nil; +} +#endif + +// ------------------------------------------------------------------------- + QT_END_NAMESPACE #endif // QCORE_MAC_P_H diff --git a/src/corelib/kernel/qcore_unix.cpp b/src/corelib/kernel/qcore_unix.cpp index 570e3056..78d99a38 100644 --- a/src/corelib/kernel/qcore_unix.cpp +++ b/src/corelib/kernel/qcore_unix.cpp @@ -14,7 +14,7 @@ # include #endif -#ifdef Q_OS_MAC +#ifdef Q_OS_DARWIN #include #endif @@ -65,6 +65,34 @@ int qt_open64(const char *pathname, int flags, mode_t mode) #ifndef QT_BOOTSTRAPPED +static inline void do_gettime(qint64 *sec, qint64 *frac) +{ + timespec ts; + clockid_t clk = CLOCK_REALTIME; +#if defined(CLOCK_MONOTONIC_RAW) + clk = CLOCK_MONOTONIC_RAW; +#elif defined(CLOCK_MONOTONIC) + clk = CLOCK_MONOTONIC; +#endif + + clock_gettime(clk, &ts); + *sec = ts.tv_sec; + *frac = ts.tv_nsec; +} + +// also used in qeventdispatcher_unix.cpp +struct timespec qt_gettime() noexcept +{ + qint64 sec, frac; + do_gettime(&sec, &frac); + + timespec tv; + tv.tv_sec = sec; + tv.tv_nsec = frac; + + return tv; +} + #if QT_CONFIG(poll_pollts) # define ppoll pollts #endif diff --git a/src/corelib/kernel/qcore_unix_p.h b/src/corelib/kernel/qcore_unix_p.h index 38c8b64f..ed64a5d8 100644 --- a/src/corelib/kernel/qcore_unix_p.h +++ b/src/corelib/kernel/qcore_unix_p.h @@ -39,6 +39,7 @@ # include #endif +#include #include #include #include @@ -68,16 +69,41 @@ QT_BEGIN_NAMESPACE Q_DECLARE_TYPEINFO(pollfd, Q_PRIMITIVE_TYPE); +static constexpr auto OneSecAsNsecs = std::chrono::nanoseconds(std::chrono::seconds{ 1 }).count(); + +inline timespec durationToTimespec(std::chrono::nanoseconds timeout) noexcept +{ + using namespace std::chrono; + const seconds secs = duration_cast(timeout); + const nanoseconds frac = timeout - secs; + struct timespec ts; + ts.tv_sec = secs.count(); + ts.tv_nsec = frac.count(); + return ts; +} + +template +inline Duration timespecToChrono(timespec ts) noexcept +{ + using namespace std::chrono; + return duration_cast(seconds{ts.tv_sec} + nanoseconds{ts.tv_nsec}); +} + +inline std::chrono::milliseconds timespecToChronoMs(timespec ts) noexcept +{ + return timespecToChrono(ts); +} + // Internal operator functions for timespecs constexpr inline timespec &normalizedTimespec(timespec &t) { - while (t.tv_nsec >= 1000000000) { + while (t.tv_nsec >= OneSecAsNsecs) { ++t.tv_sec; - t.tv_nsec -= 1000000000; + t.tv_nsec -= OneSecAsNsecs; } while (t.tv_nsec < 0) { --t.tv_sec; - t.tv_nsec += 1000000000; + t.tv_nsec += OneSecAsNsecs; } return t; } @@ -104,7 +130,7 @@ constexpr inline timespec operator-(const timespec &t1, const timespec &t2) { timespec tmp = {}; tmp.tv_sec = t1.tv_sec - (t2.tv_sec - 1); - tmp.tv_nsec = t1.tv_nsec - (t2.tv_nsec + 1000000000); + tmp.tv_nsec = t1.tv_nsec - (t2.tv_nsec + OneSecAsNsecs); return normalizedTimespec(tmp); } constexpr inline timespec operator*(const timespec &t1, int mul) @@ -114,7 +140,7 @@ constexpr inline timespec operator*(const timespec &t1, int mul) tmp.tv_nsec = t1.tv_nsec * mul; return normalizedTimespec(tmp); } -inline timeval timespecToTimeval(const timespec &ts) +inline timeval timespecToTimeval(timespec ts) { timeval tv; tv.tv_sec = ts.tv_sec; @@ -122,6 +148,41 @@ inline timeval timespecToTimeval(const timespec &ts) return tv; } +inline timespec &operator+=(timespec &t1, std::chrono::milliseconds msecs) +{ + t1 += durationToTimespec(msecs); + return t1; +} + +inline timespec &operator+=(timespec &t1, int ms) +{ + t1 += std::chrono::milliseconds{ms}; + return t1; +} + +inline timespec operator+(const timespec &t1, std::chrono::milliseconds msecs) +{ + timespec tmp = t1; + tmp += msecs; + return tmp; +} + +inline timespec operator+(const timespec &t1, int ms) +{ + return t1 + std::chrono::milliseconds{ms}; +} + +inline timespec qAbsTimespec(timespec ts) +{ + if (ts.tv_sec < 0) { + ts.tv_sec = -ts.tv_sec - 1; + ts.tv_nsec -= OneSecAsNsecs; + } + if (ts.tv_sec == 0 && ts.tv_nsec < 0) { + ts.tv_nsec = -ts.tv_nsec; + } + return normalizedTimespec(ts); +} inline void qt_ignore_sigpipe() { diff --git a/src/corelib/kernel/qcore_wasm.cpp b/src/corelib/kernel/qcore_wasm.cpp index d7135840..fb12ae50 100644 --- a/src/corelib/kernel/qcore_wasm.cpp +++ b/src/corelib/kernel/qcore_wasm.cpp @@ -42,4 +42,56 @@ emscripten::val QRectF::toDOMRect() const return emscripten::val::global("DOMRect").new_(left(), top(), width(), height()); } +/*! + Converts the \l {https://262.ecma-international.org/#sec-string-object}{ECMAScript string} \a + jsString to QString. Behavior is undefined if the provided parameter is not a string. + + \since 6.6 + \ingroup platform-type-conversions + + \sa toEcmaString() +*/ +QString QString::fromEcmaString(emscripten::val jsString) +{ + Q_ASSERT_X(jsString.isString(), Q_FUNC_INFO, "Passed object is not a string"); + + const double length = jsString["length"].as(); + + Q_ASSERT_X((double(uint64_t(length)) != double(uint64_t(length) - 1) + && double(uint64_t(length)) != double(uint64_t(length) + 1)) + || !std::numeric_limits::is_iec559, + Q_FUNC_INFO, "The floating-point length cannot precisely represent an integer"); + + constexpr int zeroTerminatorLength = 1; + const auto lengthOfUtf16 = (length + zeroTerminatorLength) * 2; + + Q_ASSERT_X((double(uint64_t(lengthOfUtf16)) != double(uint64_t(lengthOfUtf16) - 1) + && double(uint64_t(lengthOfUtf16)) != double(uint64_t(lengthOfUtf16) + 1)) + || !std::numeric_limits::is_iec559, + Q_FUNC_INFO, + "The floating-point lengthOfUtf16 cannot precisely represent an integer"); + + const QString result(uint64_t(length), Qt::Uninitialized); + + static const emscripten::val stringToUTF16(emscripten::val::module_property("stringToUTF16")); + stringToUTF16(jsString, emscripten::val(quintptr(result.data())), + emscripten::val(lengthOfUtf16)); + return result; +} + +/*! + Converts this object to an + \l {https://262.ecma-international.org/#sec-string-object}{ECMAScript string}. + + \since 6.6 + \ingroup platform-type-conversions + + \sa fromEcmaString() +*/ +emscripten::val QString::toEcmaString() const +{ + static const emscripten::val UTF16ToString(emscripten::val::module_property("UTF16ToString")); + return UTF16ToString(emscripten::val(quintptr(utf16()))); +} + QT_END_NAMESPACE diff --git a/src/corelib/kernel/qcoreapplication.cpp b/src/corelib/kernel/qcoreapplication.cpp index 85d98557..cf773a23 100644 --- a/src/corelib/kernel/qcoreapplication.cpp +++ b/src/corelib/kernel/qcoreapplication.cpp @@ -67,7 +67,7 @@ #include #endif -#ifdef Q_OS_MAC +#ifdef Q_OS_DARWIN # include "qcore_mac_p.h" #endif @@ -123,7 +123,7 @@ Q_TRACE_POINT(qtcore, QCoreApplication_sendSpontaneousEvent, QObject *receiver, Q_TRACE_POINT(qtcore, QCoreApplication_notify_entry, QObject *receiver, QEvent *event, QEvent::Type type); Q_TRACE_POINT(qtcore, QCoreApplication_notify_exit, bool consumed, bool filtered); -#if defined(Q_OS_WIN) || defined(Q_OS_MAC) +#if defined(Q_OS_WIN) || defined(Q_OS_DARWIN) extern QString qAppFileName(); #endif @@ -2452,7 +2452,7 @@ QString QCoreApplication::applicationFilePath() if (procName != d->argv[0]) { // clear the cache if the procname changes, so we reprocess it. QCoreApplicationPrivate::clearApplicationFilePath(); - procName = QByteArray(d->argv[0]); + procName.assign(d->argv[0]); } } diff --git a/src/corelib/kernel/qcoreapplication.h b/src/corelib/kernel/qcoreapplication.h index 82580ceb..dbded9e0 100644 --- a/src/corelib/kernel/qcoreapplication.h +++ b/src/corelib/kernel/qcoreapplication.h @@ -32,6 +32,7 @@ class QTranslator; class QPostEventList; class QAbstractEventDispatcher; class QAbstractNativeEventFilter; +class QEventLoopLocker; #if QT_CONFIG(permissions) || defined(Q_QDOC) class QPermission; @@ -58,6 +59,11 @@ class Q_CORE_EXPORT QCoreApplication #endif Q_DECLARE_PRIVATE(QCoreApplication) + friend class QEventLoopLocker; +#if QT_CONFIG(permissions) + using RequestPermissionPrototype = void(*)(QPermission); +#endif + public: enum { ApplicationFlags = QT_VERSION }; @@ -87,7 +93,7 @@ public: static void setSetuidAllowed(bool allow); static bool isSetuidAllowed(); - static QCoreApplication *instance() { return self; } + static QCoreApplication *instance() noexcept { return self; } #ifndef QT_NO_QOBJECT static int exec(); @@ -115,67 +121,40 @@ public: Qt::PermissionStatus checkPermission(const QPermission &permission); # ifdef Q_QDOC - template - void requestPermission(const QPermission &permission, Functor functor); template void requestPermission(const QPermission &permission, const QObject *context, Functor functor); # else - template // requestPermission to a QObject slot + // requestPermission with context or receiver object; need to require here that receiver is the + // right type to avoid ambiguity with the private implementation function. + template ::value, + bool> = true> void requestPermission(const QPermission &permission, - const typename QtPrivate::FunctionPointer::Object *receiver, Slot slot) + const typename QtPrivate::ContextTypeForFunctor::ContextType *receiver, + Functor &&func) { - using CallbackSignature = QtPrivate::FunctionPointer; - using SlotSignature = QtPrivate::FunctionPointer; - - static_assert(int(SlotSignature::ArgumentCount) <= int(CallbackSignature::ArgumentCount), - "Slot requires more arguments than what can be provided."); - static_assert((QtPrivate::CheckCompatibleArguments::value), - "Slot arguments are not compatible (must be QPermission)"); - - auto slotObj = new QtPrivate::QSlotObject(slot); - requestPermission(permission, slotObj, receiver); - } - - // requestPermission to a functor or function pointer (with context) - template ::IsPointerToMemberFunction - && !std::is_same::value, bool> = true> - void requestPermission(const QPermission &permission, const QObject *context, Func func) - { - using CallbackSignature = QtPrivate::FunctionPointer; - constexpr int MatchingArgumentCount = QtPrivate::ComputeFunctorArgumentCount< - Func, CallbackSignature::Arguments>::Value; - - static_assert(MatchingArgumentCount == 0 - || MatchingArgumentCount == CallbackSignature::ArgumentCount, - "Functor arguments are not compatible (must be QPermission)"); - - QtPrivate::QSlotObjectBase *slotObj = nullptr; - if constexpr (MatchingArgumentCount == CallbackSignature::ArgumentCount) { - slotObj = new QtPrivate::QFunctorSlotObject(std::move(func)); - } else { - slotObj = new QtPrivate::QFunctorSlotObject::Value, void>(std::move(func)); - } - - requestPermission(permission, slotObj, context); + requestPermission(permission, + QtPrivate::makeCallableObject(std::forward(func)), + receiver); } +# endif // Q_QDOC // requestPermission to a functor or function pointer (without context) - template ::IsPointerToMemberFunction - && !std::is_same::value, bool> = true> - void requestPermission(const QPermission &permission, Func func) + template ::value, + bool> = true> + void requestPermission(const QPermission &permission, Functor &&func) { - requestPermission(permission, nullptr, std::move(func)); + requestPermission(permission, nullptr, std::forward(func)); } private: + // ### Qt 7: rename to requestPermissionImpl to avoid ambiguity void requestPermission(const QPermission &permission, QtPrivate::QSlotObjectBase *slotObj, const QObject *context); public: -# endif // Q_QDOC #endif // QT_CONFIG(permission) diff --git a/src/corelib/kernel/qcoreevent.cpp b/src/corelib/kernel/qcoreevent.cpp index 0f4af063..927a8b6a 100644 --- a/src/corelib/kernel/qcoreevent.cpp +++ b/src/corelib/kernel/qcoreevent.cpp @@ -85,6 +85,8 @@ Q_TRACE_POINT(qtcore, QEvent_dtor, QEvent *event, QEvent::Type type); \value ContextMenu Context popup menu (QContextMenuEvent). \value CursorChange The widget's cursor has changed. \value DeferredDelete The object will be deleted after it has cleaned up (QDeferredDeleteEvent) + \value [since 6.6] DevicePixelRatioChange + The devicePixelRatio has changed for this widget's or window's underlying backing store. \value DragEnter The cursor enters a widget during a drag and drop operation (QDragEnterEvent). \value DragLeave The cursor leaves a widget during a drag and drop operation (QDragLeaveEvent). \value DragMove A drag and drop operation is in progress (QDragMoveEvent). diff --git a/src/corelib/kernel/qcoreevent.h b/src/corelib/kernel/qcoreevent.h index f1f3e926..4b2915a4 100644 --- a/src/corelib/kernel/qcoreevent.h +++ b/src/corelib/kernel/qcoreevent.h @@ -284,6 +284,8 @@ public: // GraphicsSceneLeave = 220, WindowAboutToChangeInternal = 221, // internal for QQuickWidget and texture-based widgets + DevicePixelRatioChange = 222, + // 512 reserved for Qt Jambi's MetaCall event // 513 reserved for Qt Jambi's DeleteOnMainThread event diff --git a/src/corelib/kernel/qdeadlinetimer.cpp b/src/corelib/kernel/qdeadlinetimer.cpp index a5d16ddf..731ff4e9 100644 --- a/src/corelib/kernel/qdeadlinetimer.cpp +++ b/src/corelib/kernel/qdeadlinetimer.cpp @@ -2,297 +2,46 @@ // SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only #include "qdeadlinetimer.h" -#include "qdeadlinetimer_p.h" #include "private/qnumeric_p.h" QT_BEGIN_NAMESPACE QT_IMPL_METATYPE_EXTERN(QDeadlineTimer) +using namespace std::chrono; + namespace { - class TimeReference - { - enum : unsigned { - umega = 1000 * 1000, - ugiga = umega * 1000 - }; - - enum : qint64 { - kilo = 1000, - mega = kilo * 1000, - giga = mega * 1000 - }; - - public: - enum RoundingStrategy { - RoundDown, - RoundUp, - RoundDefault = RoundDown - }; - - static constexpr qint64 Min = std::numeric_limits::min(); - static constexpr qint64 Max = std::numeric_limits::max(); - - inline TimeReference(qint64 = 0, unsigned = 0); - inline void updateTimer(qint64 &, unsigned &); - - inline bool addNanoseconds(qint64); - inline bool addMilliseconds(qint64); - bool addSecsAndNSecs(qint64, qint64); - - inline bool subtract(const qint64, const unsigned); - - inline bool toMilliseconds(qint64 *, RoundingStrategy = RoundDefault) const; - inline bool toNanoseconds(qint64 *) const; - - inline void saturate(bool toMax); - static bool sign(qint64, qint64); - - private: - bool adjust(const qint64, const unsigned, qint64 = 0); - - private: - qint64 secs; - unsigned nsecs; - }; +struct TimeReference : std::numeric_limits +{ + static constexpr qint64 Min = min(); + static constexpr qint64 Max = max(); +}; } -inline TimeReference::TimeReference(qint64 t1, unsigned t2) - : secs(t1), nsecs(t2) +template +static qint64 add_saturate(qint64 t1, Duration1 dur, Durations... extra) { -} + qint64 v = dur.count(); + qint64 saturated = std::numeric_limits::max(); + if (v < 0) + saturated = std::numeric_limits::min(); -inline void TimeReference::updateTimer(qint64 &t1, unsigned &t2) -{ - t1 = secs; - t2 = nsecs; -} + // convert to nanoseconds with saturation + using Ratio = std::ratio_divide; + static_assert(Ratio::den == 1, "sub-multiples of nanosecond are not supported"); + if (qMulOverflow(v, &v)) + return saturated; -inline void TimeReference::saturate(bool toMax) -{ - secs = toMax ? Max : Min; -} - -/*! - * \internal - * - * Determines the sign of a (seconds, nanoseconds) pair - * for differentiating overflow from underflow. It doesn't - * deal with equality as it shouldn't ever be called in that case. - * - * Returns true if the pair represents a positive time offset - * false otherwise. - */ -bool TimeReference::sign(qint64 secs, qint64 nsecs) -{ - if (secs > 0) { - if (nsecs > 0) - return true; - } else { - if (nsecs < 0) - return false; + qint64 r; + if (qAddOverflow(t1, v, &r)) + return saturated; + if constexpr (sizeof...(Durations)) { + // chain more additions + return add_saturate(r, extra...); } - - // They are different in sign - secs += nsecs / giga; - if (secs > 0) - return true; - else if (secs < 0) - return false; - - // We should never get over|underflow out of - // the case: secs * giga == -nsecs - // So the sign of nsecs is the deciding factor - Q_ASSERT(nsecs % giga != 0); - return nsecs > 0; + return r; } -#if defined(Q_OS_UNIX) && !defined(Q_OS_DARWIN) -inline bool TimeReference::addNanoseconds(qint64 arg) -{ - return addSecsAndNSecs(arg / giga, arg % giga); -} - -inline bool TimeReference::addMilliseconds(qint64 arg) -{ - return addSecsAndNSecs(arg / kilo, (arg % kilo) * mega); -} - -/*! - * \internal - * - * Adds \a t1 addSecs seconds and \a addNSecs nanoseconds to the - * time reference. The arguments are normalized to seconds (qint64) - * and nanoseconds (unsigned) before the actual calculation is - * delegated to adjust(). If the nanoseconds are negative the - * owed second used for the normalization is passed on to adjust() - * as third argument. - * - * Returns true if operation was successful, false on over|underflow - */ -bool TimeReference::addSecsAndNSecs(qint64 addSecs, qint64 addNSecs) -{ - // Normalize the arguments - if (qAbs(addNSecs) >= giga) { - if (add_overflow(addSecs, addNSecs / giga, &addSecs)) - return false; - - addNSecs %= giga; - } - - if (addNSecs < 0) - return adjust(addSecs, ugiga - unsigned(-addNSecs), -1); - - return adjust(addSecs, unsigned(addNSecs)); -} - -/*! - * \internal - * - * Adds \a t1 seconds and \a t2 nanoseconds to the internal members. - * Takes into account the additional \a carrySeconds we may owe or need to carry over. - * - * Returns true if operation was successful, false on over|underflow - */ -bool TimeReference::adjust(const qint64 t1, const unsigned t2, qint64 carrySeconds) -{ - static_assert(QDeadlineTimerNanosecondsInT2); - nsecs += t2; - if (nsecs >= ugiga) { - nsecs -= ugiga; - carrySeconds++; - } - - // We don't worry about the order of addition, because the result returned by - // callers of this function is unchanged regardless of us over|underflowing. - // If we do, we do so by no more than a second, thus saturating the timer to - // Forever has the same effect as if we did the arithmetic exactly and salvaged - // the overflow. - return !add_overflow(secs, t1, &secs) && !add_overflow(secs, carrySeconds, &secs); -} - -/*! - * \internal - * - * Subtracts \a t1 seconds and \a t2 nanoseconds from the time reference. - * When normalizing the nanoseconds to a positive number the owed seconds is - * passed as third argument to adjust() as the seconds may over|underflow - * if we do the calculation directly. There is little sense to check the - * seconds for over|underflow here in case we are going to need to carry - * over a second _after_ we add the nanoseconds. - * - * Returns true if operation was successful, false on over|underflow - */ -inline bool TimeReference::subtract(const qint64 t1, const unsigned t2) -{ - Q_ASSERT(t2 < ugiga); - return adjust(-t1, ugiga - t2, -1); -} - -/*! - * \internal - * - * Converts the time reference to milliseconds. - * - * Checks are done without making use of mul_overflow because it may - * not be implemented on some 32bit platforms. - * - * Returns true if operation was successful, false on over|underflow - */ -inline bool TimeReference::toMilliseconds(qint64 *result, RoundingStrategy rounding) const -{ - static constexpr qint64 maxSeconds = Max / kilo; - static constexpr qint64 minSeconds = Min / kilo; - if (secs > maxSeconds || secs < minSeconds) - return false; - - unsigned ns = rounding == RoundDown ? nsecs : nsecs + umega - 1; - - return !add_overflow(secs * kilo, ns / umega, result); -} - -/*! - * \internal - * - * Converts the time reference to nanoseconds. - * - * Checks are done without making use of mul_overflow because it may - * not be implemented on some 32bit platforms. - * - * Returns true if operation was successful, false on over|underflow - */ -inline bool TimeReference::toNanoseconds(qint64 *result) const -{ - static constexpr qint64 maxSeconds = Max / giga; - static constexpr qint64 minSeconds = Min / giga; - if (secs > maxSeconds || secs < minSeconds) - return false; - - return !add_overflow(secs * giga, nsecs, result); -} -#else -inline bool TimeReference::addNanoseconds(qint64 arg) -{ - return adjust(arg, 0); -} - -inline bool TimeReference::addMilliseconds(qint64 arg) -{ - static constexpr qint64 maxMilliseconds = Max / mega; - if (qAbs(arg) > maxMilliseconds) - return false; - - return addNanoseconds(arg * mega); -} - -inline bool TimeReference::addSecsAndNSecs(qint64 addSecs, qint64 addNSecs) -{ - static constexpr qint64 maxSeconds = Max / giga; - static constexpr qint64 minSeconds = Min / giga; - if (addSecs > maxSeconds || addSecs < minSeconds || add_overflow(addSecs * giga, addNSecs, &addNSecs)) - return false; - - return addNanoseconds(addNSecs); -} - -inline bool TimeReference::adjust(const qint64 t1, const unsigned t2, qint64 carrySeconds) -{ - static_assert(!QDeadlineTimerNanosecondsInT2); - Q_UNUSED(t2); - Q_UNUSED(carrySeconds); - - return !add_overflow(secs, t1, &secs); -} - -inline bool TimeReference::subtract(const qint64 t1, const unsigned t2) -{ - Q_UNUSED(t2); - - return addNanoseconds(-t1); -} - -inline bool TimeReference::toMilliseconds(qint64 *result, RoundingStrategy rounding) const -{ - // Force QDeadlineTimer to treat the border cases as - // over|underflow and saturate the results returned to the user. - // We don't want to get valid milliseconds out of saturated timers. - if (secs == Max || secs == Min) - return false; - - *result = secs / mega; - if (rounding == RoundUp && secs > *result * mega) - (*result)++; - - return true; -} - -inline bool TimeReference::toNanoseconds(qint64 *result) const -{ - *result = secs; - return true; -} -#endif - /*! \class QDeadlineTimer \inmodule QtCore @@ -382,10 +131,12 @@ inline bool TimeReference::toNanoseconds(qint64 *result) const */ /*! + \fn QDeadlineTimer::QDeadlineTimer() \fn QDeadlineTimer::QDeadlineTimer(Qt::TimerType timerType) Constructs an expired QDeadlineTimer object. For this object, - remainingTime() will return 0. + remainingTime() will return 0. If \a timerType is not set, then the object + will use the \l{Qt::CoarseTimer}{coarse} \l{QDeadlineTimer#Timer types}{timer type}. The timer type \a timerType may be ignored, since the timer is already expired. Similarly, for optimization purposes, this function will not @@ -415,7 +166,7 @@ inline bool TimeReference::toNanoseconds(qint64 *result) const from the moment of the creation of this object, if msecs is positive. If \a msecs is zero, this QDeadlineTimer will be marked as expired, causing remainingTime() to return zero and deadline() to return an indeterminate - time point in the past. If \a msecs is -1, the timer will be set to never + time point in the past. If \a msecs is negative, the timer will be set to never expire, causing remainingTime() to return -1 and deadline() to return the maximum value. @@ -428,6 +179,9 @@ inline bool TimeReference::toNanoseconds(qint64 *result) const functionality is required, use QDeadlineTimer::current() and add time to it. + \note Prior to Qt 6.6, the only value that caused the timer to never expire + was -1. + \sa hasExpired(), isForever(), remainingTime(), setRemainingTime() */ QDeadlineTimer::QDeadlineTimer(qint64 msecs, Qt::TimerType type) noexcept @@ -494,51 +248,70 @@ QDeadlineTimer::QDeadlineTimer(qint64 msecs, Qt::TimerType type) noexcept /*! Sets the remaining time for this QDeadlineTimer object to \a msecs milliseconds from now, if \a msecs has a positive value. If \a msecs is - zero, this QDeadlineTimer object will be marked as expired, whereas a value - of -1 will set it to never expire. + zero, this QDeadlineTimer object will be marked as expired, whereas a + negative value will set it to never expire. + + For optimization purposes, if \a msecs is zero, this function may skip + obtaining the current time and may instead use a value known to be in the + past. If that happens, deadline() may return an unexpected value and this + object cannot be used in calculation of how long it is overdue. If that + functionality is required, use QDeadlineTimer::current() and add time to + it. The timer type for this QDeadlineTimer object will be set to the specified \a timerType. + \note Prior to Qt 6.6, the only value that caused the timer to never expire + was -1. + \sa setPreciseRemainingTime(), hasExpired(), isForever(), remainingTime() */ void QDeadlineTimer::setRemainingTime(qint64 msecs, Qt::TimerType timerType) noexcept { - if (msecs == -1) { + if (msecs < 0) { *this = QDeadlineTimer(Forever, timerType); - return; + } else if (msecs == 0) { + *this = QDeadlineTimer(timerType); + t1 = std::numeric_limits::min(); + } else { + *this = current(timerType); + milliseconds ms(msecs); + t1 = add_saturate(t1, ms); } - - *this = current(timerType); - - TimeReference ref(t1, t2); - if (!ref.addMilliseconds(msecs)) - ref.saturate(msecs > 0); - ref.updateTimer(t1, t2); } /*! Sets the remaining time for this QDeadlineTimer object to \a secs seconds plus \a nsecs nanoseconds from now, if \a secs has a positive value. If \a - secs is -1, this QDeadlineTimer will be set it to never expire. If both - parameters are zero, this QDeadlineTimer will be marked as expired. + secs is negative, this QDeadlineTimer will be set it to never expire (this + behavior does not apply to \a nsecs). If both parameters are zero, this + QDeadlineTimer will be marked as expired. + + For optimization purposes, if both \a secs and \a nsecs are zero, this + function may skip obtaining the current time and may instead use a value + known to be in the past. If that happens, deadline() may return an + unexpected value and this object cannot be used in calculation of how long + it is overdue. If that functionality is required, use + QDeadlineTimer::current() and add time to it. The timer type for this QDeadlineTimer object will be set to the specified \a timerType. + \note Prior to Qt 6.6, the only condition that caused the timer to never + expire was when \a secs was -1. + \sa setRemainingTime(), hasExpired(), isForever(), remainingTime() */ void QDeadlineTimer::setPreciseRemainingTime(qint64 secs, qint64 nsecs, Qt::TimerType timerType) noexcept { - if (secs == -1) { + if (secs < 0) { *this = QDeadlineTimer(Forever, timerType); - return; + } else if (secs == 0 && nsecs == 0) { + *this = QDeadlineTimer(timerType); + t1 = std::numeric_limits::min(); + } else { + *this = current(timerType); + t1 = add_saturate(t1, seconds{secs}, nanoseconds{nsecs}); } - - *this = current(timerType); - TimeReference ref(t1, t2); - if (!ref.addSecsAndNSecs(secs, nsecs)) - ref.saturate(TimeReference::sign(secs, nsecs)); - ref.updateTimer(t1, t2); } /*! @@ -588,6 +361,8 @@ bool QDeadlineTimer::hasExpired() const noexcept { if (isForever()) return false; + if (t1 == std::numeric_limits::min()) + return true; return *this <= current(timerType()); } @@ -636,19 +411,8 @@ qint64 QDeadlineTimer::remainingTime() const noexcept if (isForever()) return -1; - QDeadlineTimer now = current(timerType()); - TimeReference ref(t1, t2); - - qint64 msecs; - if (!ref.subtract(now.t1, now.t2)) - return 0; // We can only underflow here - - // If we fail the conversion, t1 < now.t1 means we underflowed, - // thus the deadline had long expired - if (!ref.toMilliseconds(&msecs, TimeReference::RoundUp)) - return t1 < now.t1 ? 0 : -1; - - return msecs < 0 ? 0 : msecs; + nanoseconds nsecs(remainingTimeNSecs()); + return ceil(nsecs).count(); } /*! @@ -670,23 +434,19 @@ qint64 QDeadlineTimer::remainingTimeNSecs() const noexcept /*! \internal Same as remainingTimeNSecs, but may return negative remaining times. Does - not deal with Forever. In case of underflow the result is saturated to - the minimum possible value, on overflow - the maximum possible value. + not deal with Forever. In case of underflow, which is only possible if the + timer has expired, an arbitrary negative value is returned. */ qint64 QDeadlineTimer::rawRemainingTimeNSecs() const noexcept { + if (t1 == std::numeric_limits::min()) + return t1; // we'd saturate to this anyway + QDeadlineTimer now = current(timerType()); - TimeReference ref(t1, t2); - - qint64 nsecs; - if (!ref.subtract(now.t1, now.t2)) - return TimeReference::Min; // We can only underflow here - - // If we fail the conversion, t1 < now.t1 means we underflowed, - // thus the deadline had long expired - if (!ref.toNanoseconds(&nsecs)) - return t1 < now.t1 ? TimeReference::Min : TimeReference::Max; - return nsecs; + qint64 r; + if (qSubOverflow(t1, now.t1, &r)) + return -1; // any negative number is fine + return r; } /*! @@ -713,12 +473,11 @@ qint64 QDeadlineTimer::deadline() const noexcept { if (isForever()) return TimeReference::Max; + if (t1 == TimeReference::Min) + return t1; - qint64 result; - if (!TimeReference(t1, t2).toMilliseconds(&result)) - return t1 < 0 ? TimeReference::Min : TimeReference::Max; - - return result; + nanoseconds ns(t1); + return duration_cast(ns).count(); } /*! @@ -747,11 +506,7 @@ qint64 QDeadlineTimer::deadlineNSecs() const noexcept if (isForever()) return TimeReference::Max; - qint64 result; - if (!TimeReference(t1, t2).toNanoseconds(&result)) - return t1 < 0 ? TimeReference::Min : TimeReference::Max; - - return result; + return t1; } /*! @@ -775,11 +530,7 @@ void QDeadlineTimer::setDeadline(qint64 msecs, Qt::TimerType timerType) noexcept } type = timerType; - - TimeReference ref; - if (!ref.addMilliseconds(msecs)) - ref.saturate(msecs > 0); - ref.updateTimer(t1, t2); + t1 = add_saturate(0, milliseconds{msecs}); } /*! @@ -797,13 +548,7 @@ void QDeadlineTimer::setDeadline(qint64 msecs, Qt::TimerType timerType) noexcept void QDeadlineTimer::setPreciseDeadline(qint64 secs, qint64 nsecs, Qt::TimerType timerType) noexcept { type = timerType; - - // We don't pass the seconds to the constructor, because we don't know - // at this point if t1 holds the seconds or nanoseconds; it's platform specific. - TimeReference ref; - if (!ref.addSecsAndNSecs(secs, nsecs)) - ref.saturate(TimeReference::sign(secs, nsecs)); - ref.updateTimer(t1, t2); + t1 = add_saturate(0, seconds{secs}, nanoseconds{nsecs}); } /*! @@ -819,11 +564,7 @@ QDeadlineTimer QDeadlineTimer::addNSecs(QDeadlineTimer dt, qint64 nsecs) noexcep if (dt.isForever()) return dt; - TimeReference ref(dt.t1, dt.t2); - if (!ref.addNanoseconds(nsecs)) - ref.saturate(nsecs > 0); - ref.updateTimer(dt.t1, dt.t2); - + dt.t1 = add_saturate(dt.t1, nanoseconds{nsecs}); return dt; } @@ -836,6 +577,17 @@ QDeadlineTimer QDeadlineTimer::addNSecs(QDeadlineTimer dt, qint64 nsecs) noexcep The QDeadlineTimer object will be constructed with the specified \a timerType. */ +QDeadlineTimer QDeadlineTimer::current(Qt::TimerType timerType) noexcept +{ + // ensure we get nanoseconds; this will work so long as steady_clock's + // time_point isn't of finer resolution (picoseconds) + std::chrono::nanoseconds ns = std::chrono::steady_clock::now().time_since_epoch(); + + QDeadlineTimer result; + result.t1 = ns.count(); + result.type = timerType; + return result; +} /*! \fn bool QDeadlineTimer::operator==(QDeadlineTimer d1, QDeadlineTimer d2) @@ -930,11 +682,7 @@ QDeadlineTimer operator+(QDeadlineTimer dt, qint64 msecs) if (dt.isForever()) return dt; - TimeReference ref(dt.t1, dt.t2); - if (!ref.addMilliseconds(msecs)) - ref.saturate(msecs > 0); - ref.updateTimer(dt.t1, dt.t2); - + dt.t1 = add_saturate(dt.t1, milliseconds{msecs}); return dt; } @@ -1005,6 +753,4 @@ QDeadlineTimer operator+(QDeadlineTimer dt, qint64 msecs) \internal */ -// the rest of the functions are in qelapsedtimer_xxx.cpp - QT_END_NAMESPACE diff --git a/src/corelib/kernel/qdeadlinetimer.h b/src/corelib/kernel/qdeadlinetimer.h index 253e98de..57c771de 100644 --- a/src/corelib/kernel/qdeadlinetimer.h +++ b/src/corelib/kernel/qdeadlinetimer.h @@ -23,16 +23,18 @@ QT_BEGIN_NAMESPACE class Q_CORE_EXPORT QDeadlineTimer { public: - enum ForeverConstant { Forever }; + enum class ForeverConstant { Forever }; + static constexpr ForeverConstant Forever = ForeverConstant::Forever; - constexpr QDeadlineTimer(Qt::TimerType type_ = Qt::CoarseTimer) noexcept + constexpr QDeadlineTimer() noexcept = default; + constexpr explicit QDeadlineTimer(Qt::TimerType type_) noexcept : type(type_) {} constexpr QDeadlineTimer(ForeverConstant, Qt::TimerType type_ = Qt::CoarseTimer) noexcept : t1((std::numeric_limits::max)()), type(type_) {} explicit QDeadlineTimer(qint64 msecs, Qt::TimerType type = Qt::CoarseTimer) noexcept; void swap(QDeadlineTimer &other) noexcept - { std::swap(t1, other.t1); std::swap(t2, other.t2); std::swap(type, other.type); } + { std::swap(t1, other.t1); std::swap(type, other.type); } constexpr bool isForever() const noexcept { return t1 == (std::numeric_limits::max)(); } @@ -58,11 +60,11 @@ public: static QDeadlineTimer current(Qt::TimerType timerType = Qt::CoarseTimer) noexcept; friend bool operator==(QDeadlineTimer d1, QDeadlineTimer d2) noexcept - { return d1.t1 == d2.t1 && d1.t2 == d2.t2; } + { return d1.t1 == d2.t1; } friend bool operator!=(QDeadlineTimer d1, QDeadlineTimer d2) noexcept { return !(d1 == d2); } friend bool operator<(QDeadlineTimer d1, QDeadlineTimer d2) noexcept - { return d1.t1 < d2.t1 || (d1.t1 == d2.t1 && d1.t2 < d2.t2); } + { return d1.t1 < d2.t1; } friend bool operator<=(QDeadlineTimer d1, QDeadlineTimer d2) noexcept { return d1 == d2 || d1 < d2; } friend bool operator>(QDeadlineTimer d1, QDeadlineTimer d2) noexcept @@ -91,16 +93,11 @@ public: { setDeadline(deadline_); return *this; } template - void setDeadline(std::chrono::time_point deadline_, - Qt::TimerType type_ = Qt::CoarseTimer) - { setRemainingTime(deadline_ == deadline_.max() ? Duration::max() : deadline_ - Clock::now(), type_); } + void setDeadline(std::chrono::time_point tp, + Qt::TimerType type_ = Qt::CoarseTimer); template - std::chrono::time_point deadline() const - { - auto val = std::chrono::nanoseconds(rawRemainingTimeNSecs()) + Clock::now(); - return std::chrono::time_point_cast(val); - } + std::chrono::time_point deadline() const; template QDeadlineTimer(std::chrono::duration remaining, Qt::TimerType type_ = Qt::CoarseTimer) @@ -114,10 +111,11 @@ public: template void setRemainingTime(std::chrono::duration remaining, Qt::TimerType type_ = Qt::CoarseTimer) { + using namespace std::chrono; if (remaining == remaining.max()) *this = QDeadlineTimer(Forever, type_); else - setPreciseRemainingTime(0, std::chrono::nanoseconds(remaining).count(), type_); + setPreciseRemainingTime(0, ceil(remaining).count(), type_); } std::chrono::nanoseconds remainingTimeAsDuration() const noexcept @@ -142,43 +140,42 @@ public: private: qint64 t1 = 0; +#if QT_VERSION < QT_VERSION_CHECK(7, 0, 0) unsigned t2 = 0; - unsigned type; +#endif + unsigned type = Qt::CoarseTimer; qint64 rawRemainingTimeNSecs() const noexcept; - -public: - // This is not a public function, it's here only for Qt's internal convenience... - QPair _q_data() const { return qMakePair(t1, t2); } }; -#if defined(Q_OS_DARWIN) || defined(Q_OS_LINUX) || (defined(Q_CC_MSVC) && Q_CC_MSVC >= 1900) -// We know for these OS/compilers that the std::chrono::steady_clock uses the same -// reference time as QDeadlineTimer - -template <> inline std::chrono::steady_clock::time_point -QDeadlineTimer::deadline() const +template +std::chrono::time_point QDeadlineTimer::deadline() const { - return std::chrono::steady_clock::time_point(std::chrono::nanoseconds(deadlineNSecs())); + using namespace std::chrono; + if constexpr (std::is_same_v) { + auto val = duration_cast(nanoseconds(deadlineNSecs())); + return time_point(val); + } else { + auto val = nanoseconds(rawRemainingTimeNSecs()) + Clock::now(); + return time_point_cast(val); + } } -template <> inline void -QDeadlineTimer::setDeadline(std::chrono::steady_clock::time_point tp, Qt::TimerType type_) +template +void QDeadlineTimer::setDeadline(std::chrono::time_point tp, Qt::TimerType type_) { using namespace std::chrono; if (tp == tp.max()) { *this = Forever; type = type_; - } else if (type_ != Qt::PreciseTimer) { - // if we aren't using PreciseTimer, then we need to convert - setPreciseRemainingTime(0, duration_cast(tp - steady_clock::now()).count(), type_); - } else { + } else if constexpr (std::is_same_v) { setPreciseDeadline(0, duration_cast(tp.time_since_epoch()).count(), type_); + } else { + setPreciseRemainingTime(0, duration_cast(tp - Clock::now()).count(), type_); } } -#endif Q_DECLARE_SHARED(QDeadlineTimer) diff --git a/src/corelib/kernel/qdeadlinetimer_p.h b/src/corelib/kernel/qdeadlinetimer_p.h deleted file mode 100644 index 41054435..00000000 --- a/src/corelib/kernel/qdeadlinetimer_p.h +++ /dev/null @@ -1,34 +0,0 @@ -// 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 - -#ifndef QDEADLINETIMER_P_H -#define QDEADLINETIMER_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 - -QT_BEGIN_NAMESPACE - -enum { -#if defined(Q_OS_UNIX) && !defined(Q_OS_DARWIN) - // t1 contains seconds and t2 contains nanoseconds - QDeadlineTimerNanosecondsInT2 = 1 -#else - // t1 contains nanoseconds, t2 is always zero - QDeadlineTimerNanosecondsInT2 = 0 -#endif -}; - -QT_END_NAMESPACE - -#endif diff --git a/src/corelib/kernel/qelapsedtimer.cpp b/src/corelib/kernel/qelapsedtimer.cpp index 39ddb349..fd5e0905 100644 --- a/src/corelib/kernel/qelapsedtimer.cpp +++ b/src/corelib/kernel/qelapsedtimer.cpp @@ -165,9 +165,229 @@ QT_BEGIN_NAMESPACE function will return false. */ +/*! + \fn QElapsedTimer::clockType() noexcept + + Returns the clock type that this QElapsedTimer implementation uses. + + Since Qt 6.6, QElapsedTimer uses \c{std::chrono::steady_clock}, so the + clock type is always \l MonotonicClock. + + \sa isMonotonic() +*/ + +QElapsedTimer::ClockType QElapsedTimer::clockType() noexcept +{ + // we use std::chrono::steady_clock + return MonotonicClock; +} + +/*! + \fn QElapsedTimer::isMonotonic() noexcept + + Returns \c true if this is a monotonic clock, false otherwise. See the + information on the different clock types to understand which ones are + monotonic. + + Since Qt 6.6, QElapsedTimer uses \c{std::chrono::steady_clock}, so this + function now always returns true. + + \sa clockType(), QElapsedTimer::ClockType +*/ +bool QElapsedTimer::isMonotonic() noexcept +{ + // We trust std::chrono::steady_clock to be steady (monotonic); if the + // Standard Library is lying to us, users must complain to their vendor. + return true; +} + +/*! + \typealias QElapsedTimer::Duration + Synonym for \c std::chrono::nanoseconds. +*/ + +/*! + \typealias QElapsedTimer::TimePoint + Synonym for \c {std::chrono::time_point}. +*/ + +/*! + Starts this timer. Once started, a timer value can be checked with elapsed() or msecsSinceReference(). + + Normally, a timer is started just before a lengthy operation, such as: + \snippet qelapsedtimer/main.cpp 0 + + Also, starting a timer makes it valid again. + + \sa restart(), invalidate(), elapsed() +*/ +void QElapsedTimer::start() noexcept +{ + static_assert(sizeof(t1) == sizeof(Duration::rep)); + + // This assignment will work so long as TimePoint uses the same time + // duration or one of finer granularity than steady_clock::time_point. That + // means it will work until the first steady_clock using picoseconds. + TimePoint now = std::chrono::steady_clock::now(); + t1 = now.time_since_epoch().count(); + QT6_ONLY(t2 = 0); +} + +/*! + Restarts the timer and returns the number of milliseconds elapsed since + the previous start. + This function is equivalent to obtaining the elapsed time with elapsed() + and then starting the timer again with start(), but it does so in one + single operation, avoiding the need to obtain the clock value twice. + + Calling this function on a QElapsedTimer that is invalid + results in undefined behavior. + + The following example illustrates how to use this function to calibrate a + parameter to a slow operation (for example, an iteration count) so that + this operation takes at least 250 milliseconds: + + \snippet qelapsedtimer/main.cpp 3 + + \sa start(), invalidate(), elapsed(), isValid() +*/ +qint64 QElapsedTimer::restart() noexcept +{ + QElapsedTimer old = *this; + start(); + return old.msecsTo(*this); +} + +/*! + \since 6.6 + + Returns a \c{std::chrono::nanoseconds} with the time since this QElapsedTimer was last + started. + + Calling this function on a QElapsedTimer that is invalid + results in undefined behavior. + + On platforms that do not provide nanosecond resolution, the value returned + will be the best estimate available. + + \sa start(), restart(), hasExpired(), invalidate() +*/ +auto QElapsedTimer::durationElapsed() const noexcept -> Duration +{ + TimePoint then{Duration(t1)}; + return std::chrono::steady_clock::now() - then; +} + +/*! + \since 4.8 + + Returns the number of nanoseconds since this QElapsedTimer was last + started. + + Calling this function on a QElapsedTimer that is invalid + results in undefined behavior. + + On platforms that do not provide nanosecond resolution, the value returned + will be the best estimate available. + + \sa start(), restart(), hasExpired(), invalidate() +*/ +qint64 QElapsedTimer::nsecsElapsed() const noexcept +{ + return durationElapsed().count(); +} + +/*! + Returns the number of milliseconds since this QElapsedTimer was last + started. + + Calling this function on a QElapsedTimer that is invalid + results in undefined behavior. + + \sa start(), restart(), hasExpired(), isValid(), invalidate() +*/ +qint64 QElapsedTimer::elapsed() const noexcept +{ + using namespace std::chrono; + return duration_cast(durationElapsed()).count(); +} + +/*! + Returns the number of milliseconds between last time this QElapsedTimer + object was started and its reference clock's start. + + This number is usually arbitrary for all clocks except the + QElapsedTimer::SystemTime clock. For that clock type, this number is the + number of milliseconds since January 1st, 1970 at 0:00 UTC (that is, it + is the Unix time expressed in milliseconds). + + On Linux, Windows and Apple platforms, this value is usually the time + since the system boot, though it usually does not include the time the + system has spent in sleep states. + + \sa clockType(), elapsed() +*/ +qint64 QElapsedTimer::msecsSinceReference() const noexcept +{ + using namespace std::chrono; + return duration_cast(Duration(t1)).count(); +} + +/*! + \since 6.6 + + Returns the time difference between this QElapsedTimer and \a other as a + \c{std::chrono::nanoseconds}. If \a other was started before this object, + the returned value will be negative. If it was started later, the returned + value will be positive. + + The return value is undefined if this object or \a other were invalidated. + + \sa secsTo(), elapsed() +*/ +auto QElapsedTimer::durationTo(const QElapsedTimer &other) const noexcept -> Duration +{ + Duration d1(t1); + Duration d2(other.t1); + return d2 - d1; +} + +/*! + Returns the number of milliseconds between this QElapsedTimer and \a + other. If \a other was started before this object, the returned value + will be negative. If it was started later, the returned value will be + positive. + + The return value is undefined if this object or \a other were invalidated. + + \sa secsTo(), elapsed() +*/ +qint64 QElapsedTimer::msecsTo(const QElapsedTimer &other) const noexcept +{ + using namespace std::chrono; + return duration_cast(durationTo(other)).count(); +} + +/*! + Returns the number of seconds between this QElapsedTimer and \a other. If + \a other was started before this object, the returned value will be + negative. If it was started later, the returned value will be positive. + + Calling this function on or with a QElapsedTimer that is invalid + results in undefined behavior. + + \sa msecsTo(), elapsed() +*/ +qint64 QElapsedTimer::secsTo(const QElapsedTimer &other) const noexcept +{ + using namespace std::chrono; + return duration_cast(durationTo(other)).count(); +} + static const qint64 invalidData = Q_INT64_C(0x8000000000000000); /*! + \fn QElapsedTimer::invalidate() noexcept Marks this QElapsedTimer object as invalid. An invalid object can be checked with isValid(). Calculations of timer @@ -193,10 +413,12 @@ bool QElapsedTimer::isValid() const noexcept } /*! - Returns \c true if this QElapsedTimer has already expired by \a timeout - milliseconds (that is, more than \a timeout milliseconds have elapsed). - The value of \a timeout can be -1 to indicate that this timer does not - expire, in which case this function will always return false. + Returns \c true if elapsed() exceeds the given \a timeout, otherwise \c false. + + A negative \a timeout is interpreted as infinite, so \c false is returned in + this case. Otherwise, this is equivalent to \c {elapsed() > timeout}. You + can do the same for a duration by comparing durationElapsed() to a duration + timeout. \sa elapsed(), QDeadlineTimer */ @@ -207,4 +429,9 @@ bool QElapsedTimer::hasExpired(qint64 timeout) const noexcept return quint64(elapsed()) > quint64(timeout); } +bool operator<(const QElapsedTimer &lhs, const QElapsedTimer &rhs) noexcept +{ + return lhs.t1 < rhs.t1; +} + QT_END_NAMESPACE diff --git a/src/corelib/kernel/qelapsedtimer.h b/src/corelib/kernel/qelapsedtimer.h index ab32265c..7d8b889f 100644 --- a/src/corelib/kernel/qelapsedtimer.h +++ b/src/corelib/kernel/qelapsedtimer.h @@ -6,8 +6,9 @@ #include -QT_BEGIN_NAMESPACE +#include +QT_BEGIN_NAMESPACE class Q_CORE_EXPORT QElapsedTimer { @@ -21,6 +22,10 @@ public: PerformanceCounter }; + // similar to std::chrono::*_clock + using Duration = std::chrono::nanoseconds; + using TimePoint = std::chrono::time_point; + constexpr QElapsedTimer() = default; static ClockType clockType() noexcept; @@ -31,11 +36,13 @@ public: void invalidate() noexcept; bool isValid() const noexcept; + Duration durationElapsed() const noexcept; qint64 nsecsElapsed() const noexcept; qint64 elapsed() const noexcept; bool hasExpired(qint64 timeout) const noexcept; qint64 msecsSinceReference() const noexcept; + Duration durationTo(const QElapsedTimer &other) const noexcept; qint64 msecsTo(const QElapsedTimer &other) const noexcept; qint64 secsTo(const QElapsedTimer &other) const noexcept; diff --git a/src/corelib/kernel/qelapsedtimer_generic.cpp b/src/corelib/kernel/qelapsedtimer_generic.cpp deleted file mode 100644 index 874122f4..00000000 --- a/src/corelib/kernel/qelapsedtimer_generic.cpp +++ /dev/null @@ -1,169 +0,0 @@ -// 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 "qelapsedtimer.h" -#include "qdeadlinetimer.h" -#include "qdatetime.h" - -QT_BEGIN_NAMESPACE - -/*! - Returns the clock type that this QElapsedTimer implementation uses. - - \sa isMonotonic() -*/ -QElapsedTimer::ClockType QElapsedTimer::clockType() noexcept -{ - return SystemTime; -} - -/*! - Returns \c true if this is a monotonic clock, false otherwise. See the - information on the different clock types to understand which ones are - monotonic. - - \sa clockType(), QElapsedTimer::ClockType -*/ -bool QElapsedTimer::isMonotonic() noexcept -{ - return false; -} - -/*! - Starts this timer. Once started, a timer value can be checked with elapsed() or msecsSinceReference(). - - Normally, a timer is started just before a lengthy operation, such as: - \snippet qelapsedtimer/main.cpp 0 - - Also, starting a timer makes it valid again. - - \sa restart(), invalidate(), elapsed() -*/ -void QElapsedTimer::start() noexcept -{ - restart(); -} - -/*! - Restarts the timer and returns the number of milliseconds elapsed since - the previous start. - This function is equivalent to obtaining the elapsed time with elapsed() - and then starting the timer again with start(), but it does so in one - single operation, avoiding the need to obtain the clock value twice. - - Calling this function on a QElapsedTimer that is invalid - results in undefined behavior. - - The following example illustrates how to use this function to calibrate a - parameter to a slow operation (for example, an iteration count) so that - this operation takes at least 250 milliseconds: - - \snippet qelapsedtimer/main.cpp 3 - - \sa start(), invalidate(), elapsed(), isValid() -*/ -qint64 QElapsedTimer::restart() noexcept -{ - qint64 old = t1; - t1 = QDateTime::currentMSecsSinceEpoch(); - t2 = 0; - return t1 - old; -} - -/*! \since 4.8 - - Returns the number of nanoseconds since this QElapsedTimer was last - started. - - Calling this function on a QElapsedTimer that is invalid - results in undefined behavior. - - On platforms that do not provide nanosecond resolution, the value returned - will be the best estimate available. - - \sa start(), restart(), hasExpired(), invalidate() -*/ -qint64 QElapsedTimer::nsecsElapsed() const noexcept -{ - return elapsed() * 1000000; -} - -/*! - Returns the number of milliseconds since this QElapsedTimer was last - started. - - Calling this function on a QElapsedTimer that is invalid - results in undefined behavior. - - \sa start(), restart(), hasExpired(), isValid(), invalidate() -*/ -qint64 QElapsedTimer::elapsed() const noexcept -{ - return QDateTime::currentMSecsSinceEpoch() - t1; -} - -/*! - Returns the number of milliseconds between last time this QElapsedTimer - object was started and its reference clock's start. - - This number is usually arbitrary for all clocks except the - QElapsedTimer::SystemTime clock. For that clock type, this number is the - number of milliseconds since January 1st, 1970 at 0:00 UTC (that is, it - is the Unix time expressed in milliseconds). - - On Linux, Windows and Apple platforms, this value is usually the time - since the system boot, though it usually does not include the time the - system has spent in sleep states. - - \sa clockType(), elapsed() -*/ -qint64 QElapsedTimer::msecsSinceReference() const noexcept -{ - return t1; -} - -/*! - Returns the number of milliseconds between this QElapsedTimer and \a - other. If \a other was started before this object, the returned value - will be negative. If it was started later, the returned value will be - positive. - - The return value is undefined if this object or \a other were invalidated. - - \sa secsTo(), elapsed() -*/ -qint64 QElapsedTimer::msecsTo(const QElapsedTimer &other) const noexcept -{ - qint64 diff = other.t1 - t1; - return diff; -} - -/*! - Returns the number of seconds between this QElapsedTimer and \a other. If - \a other was started before this object, the returned value will be - negative. If it was started later, the returned value will be positive. - - Calling this function on or with a QElapsedTimer that is invalid - results in undefined behavior. - - \sa msecsTo(), elapsed() -*/ -qint64 QElapsedTimer::secsTo(const QElapsedTimer &other) const noexcept -{ - return msecsTo(other) / 1000; -} - -bool operator<(const QElapsedTimer &lhs, const QElapsedTimer &rhs) noexcept -{ - return lhs.t1 < rhs.t1; -} - -QDeadlineTimer QDeadlineTimer::current(Qt::TimerType timerType) noexcept -{ - QDeadlineTimer result; - result.t1 = QDateTime::currentMSecsSinceEpoch() * 1000 * 1000; - result.type = timerType; - return result; -} - -QT_END_NAMESPACE diff --git a/src/corelib/kernel/qelapsedtimer_mac.cpp b/src/corelib/kernel/qelapsedtimer_mac.cpp deleted file mode 100644 index bc87202d..00000000 --- a/src/corelib/kernel/qelapsedtimer_mac.cpp +++ /dev/null @@ -1,130 +0,0 @@ -// 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 - -// ask for the latest POSIX, just in case -#define _POSIX_C_SOURCE 200809L - -#include "qelapsedtimer.h" -#include "qdeadlinetimer.h" -#include "qdeadlinetimer_p.h" -#include -#include -#include - -#include -#include - -QT_BEGIN_NAMESPACE - -#ifdef __LP64__ -typedef __int128_t LargeInt; -#else -typedef qint64 LargeInt; -#endif - -QElapsedTimer::ClockType QElapsedTimer::clockType() noexcept -{ - return MachAbsoluteTime; -} - -bool QElapsedTimer::isMonotonic() noexcept -{ - return true; -} - -static mach_timebase_info_data_t info = { 0, 0 }; -static qint64 absoluteToNSecs(qint64 cpuTime) -{ - if (info.denom == 0) - mach_timebase_info(&info); - - // don't do multiplication & division if those are equal - // (mathematically it would be the same, but it's computationally expensive) - if (info.numer == info.denom) - return cpuTime; - qint64 nsecs = LargeInt(cpuTime) * info.numer / info.denom; - return nsecs; -} - -static qint64 absoluteToMSecs(qint64 cpuTime) -{ - return absoluteToNSecs(cpuTime) / 1000000; -} - -timespec qt_gettime() noexcept -{ - timespec tv; - - uint64_t cpu_time = mach_absolute_time(); - uint64_t nsecs = absoluteToNSecs(cpu_time); - tv.tv_sec = nsecs / 1000000000ull; - tv.tv_nsec = nsecs - (tv.tv_sec * 1000000000ull); - return tv; -} - -void qt_nanosleep(timespec amount) -{ - // Mac doesn't have clock_nanosleep, but it does have nanosleep. - // nanosleep is POSIX.1-1993 - - int r; - EINTR_LOOP(r, nanosleep(&amount, &amount)); -} - -void QElapsedTimer::start() noexcept -{ - t1 = mach_absolute_time(); - t2 = 0; -} - -qint64 QElapsedTimer::restart() noexcept -{ - qint64 old = t1; - t1 = mach_absolute_time(); - t2 = 0; - - return absoluteToMSecs(t1 - old); -} - -qint64 QElapsedTimer::nsecsElapsed() const noexcept -{ - uint64_t cpu_time = mach_absolute_time(); - return absoluteToNSecs(cpu_time - t1); -} - -qint64 QElapsedTimer::elapsed() const noexcept -{ - uint64_t cpu_time = mach_absolute_time(); - return absoluteToMSecs(cpu_time - t1); -} - -qint64 QElapsedTimer::msecsSinceReference() const noexcept -{ - return absoluteToMSecs(t1); -} - -qint64 QElapsedTimer::msecsTo(const QElapsedTimer &other) const noexcept -{ - return absoluteToMSecs(other.t1 - t1); -} - -qint64 QElapsedTimer::secsTo(const QElapsedTimer &other) const noexcept -{ - return msecsTo(other) / 1000; -} - -bool operator<(const QElapsedTimer &v1, const QElapsedTimer &v2) noexcept -{ - return v1.t1 < v2.t1; -} - -QDeadlineTimer QDeadlineTimer::current(Qt::TimerType timerType) noexcept -{ - static_assert(!QDeadlineTimerNanosecondsInT2); - QDeadlineTimer result; - result.type = timerType; - result.t1 = absoluteToNSecs(mach_absolute_time()); - return result; -} - -QT_END_NAMESPACE diff --git a/src/corelib/kernel/qelapsedtimer_unix.cpp b/src/corelib/kernel/qelapsedtimer_unix.cpp deleted file mode 100644 index f11ebc00..00000000 --- a/src/corelib/kernel/qelapsedtimer_unix.cpp +++ /dev/null @@ -1,209 +0,0 @@ -// Copyright (C) 2016 The Qt Company Ltd. -// Copyright (C) 2016 Intel Corporation. -// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only - -#include "qelapsedtimer.h" -#include "qdeadlinetimer.h" -#include "qdeadlinetimer_p.h" -#if defined(Q_OS_VXWORKS) -#include "qfunctions_vxworks.h" -#else -#include -#include -#endif -#include - -#include -#include "private/qcore_unix_p.h" - -#if defined(QT_NO_CLOCK_MONOTONIC) || defined(QT_BOOTSTRAPPED) -// turn off the monotonic clock -# ifdef _POSIX_MONOTONIC_CLOCK -# undef _POSIX_MONOTONIC_CLOCK -# endif -# define _POSIX_MONOTONIC_CLOCK -1 -#endif - -QT_BEGIN_NAMESPACE - -/* - * Design: - * - * POSIX offers a facility to select the system's monotonic clock when getting - * the current timestamp. Whereas the functions are mandatory in POSIX.1-2008, - * the presence of a monotonic clock is a POSIX Option (see the document - * http://pubs.opengroup.org/onlinepubs/9699919799/basedefs/V1_chap02.html#tag_02_01_06 ) - * - * The macro _POSIX_MONOTONIC_CLOCK can therefore assume the following values: - * -1 monotonic clock is never supported on this system - * 0 monotonic clock might be supported, runtime check is needed - * >1 (such as 200809L) monotonic clock is always supported - * - * The unixCheckClockType() function will return the clock to use: either - * CLOCK_MONOTONIC or CLOCK_REALTIME. In the case the POSIX option has a value - * of zero, then this function stores a static that contains the clock to be - * used. - * - * There's one extra case, which is when CLOCK_REALTIME isn't defined. When - * that's the case, we'll emulate the clock_gettime function with gettimeofday. - * - * Conforming to: - * POSIX.1b-1993 section "Clocks and Timers" - * included in UNIX98 (Single Unix Specification v2) - * included in POSIX.1-2001 - * see http://pubs.opengroup.org/onlinepubs/9699919799/functions/clock_getres.html - */ - -#if !defined(CLOCK_REALTIME) -# define CLOCK_REALTIME 0 -static inline void qt_clock_gettime(int, struct timespec *ts) -{ - // support clock_gettime with gettimeofday - struct timeval tv; - gettimeofday(&tv, 0); - ts->tv_sec = tv.tv_sec; - ts->tv_nsec = tv.tv_usec * 1000; -} - -static inline int regularClock() -{ - return 0; -} -#else -static inline void qt_clock_gettime(clockid_t clock, struct timespec *ts) -{ - clock_gettime(clock, ts); -} - -static inline clock_t regularClockCheck() -{ - struct timespec regular_clock_resolution; - int r = -1; - -# ifdef CLOCK_MONOTONIC - // try the monotonic clock - r = clock_getres(CLOCK_MONOTONIC, ®ular_clock_resolution); - -# ifdef Q_OS_LINUX - // Despite glibc claiming that we should check at runtime, the Linux kernel - // always supports the monotonic clock - Q_ASSERT(r == 0); - return CLOCK_MONOTONIC; -# endif - - if (r == 0) - return CLOCK_MONOTONIC; -# endif - - // no monotonic, try the realtime clock - r = clock_getres(CLOCK_REALTIME, ®ular_clock_resolution); - Q_ASSERT(r == 0); - return CLOCK_REALTIME; -} - -static inline clock_t regularClock() -{ - static const clock_t clock = regularClockCheck(); - return clock; -} -#endif - -bool QElapsedTimer::isMonotonic() noexcept -{ - return clockType() == MonotonicClock; -} - -QElapsedTimer::ClockType QElapsedTimer::clockType() noexcept -{ - return regularClock() == CLOCK_REALTIME ? SystemTime : MonotonicClock; -} - -static inline void do_gettime(qint64 *sec, qint64 *frac) -{ - timespec ts; - qt_clock_gettime(regularClock(), &ts); - *sec = ts.tv_sec; - *frac = ts.tv_nsec; -} - -// used in qcore_unix.cpp and qeventdispatcher_unix.cpp -struct timespec qt_gettime() noexcept -{ - qint64 sec, frac; - do_gettime(&sec, &frac); - - timespec tv; - tv.tv_sec = sec; - tv.tv_nsec = frac; - - return tv; -} - -static qint64 elapsedAndRestart(qint64 sec, qint64 frac, - qint64 *nowsec, qint64 *nowfrac) -{ - do_gettime(nowsec, nowfrac); - sec = *nowsec - sec; - frac = *nowfrac - frac; - return (sec * Q_INT64_C(1000000000) + frac) / Q_INT64_C(1000000); -} - -void QElapsedTimer::start() noexcept -{ - do_gettime(&t1, &t2); -} - -qint64 QElapsedTimer::restart() noexcept -{ - return elapsedAndRestart(t1, t2, &t1, &t2); -} - -qint64 QElapsedTimer::nsecsElapsed() const noexcept -{ - qint64 sec, frac; - do_gettime(&sec, &frac); - sec = sec - t1; - frac = frac - t2; - return sec * Q_INT64_C(1000000000) + frac; -} - -qint64 QElapsedTimer::elapsed() const noexcept -{ - return nsecsElapsed() / Q_INT64_C(1000000); -} - -qint64 QElapsedTimer::msecsSinceReference() const noexcept -{ - return t1 * Q_INT64_C(1000) + t2 / Q_INT64_C(1000000); -} - -qint64 QElapsedTimer::msecsTo(const QElapsedTimer &other) const noexcept -{ - qint64 secs = other.t1 - t1; - qint64 fraction = other.t2 - t2; - return (secs * Q_INT64_C(1000000000) + fraction) / Q_INT64_C(1000000); -} - -qint64 QElapsedTimer::secsTo(const QElapsedTimer &other) const noexcept -{ - return other.t1 - t1; -} - -bool operator<(const QElapsedTimer &v1, const QElapsedTimer &v2) noexcept -{ - return v1.t1 < v2.t1 || (v1.t1 == v2.t1 && v1.t2 < v2.t2); -} - -QDeadlineTimer QDeadlineTimer::current(Qt::TimerType timerType) noexcept -{ - static_assert(QDeadlineTimerNanosecondsInT2); - QDeadlineTimer result; - qint64 cursec, curnsec; - do_gettime(&cursec, &curnsec); - result.t1 = cursec; - result.t2 = curnsec; - result.type = timerType; - return result; -} - -QT_END_NAMESPACE diff --git a/src/corelib/kernel/qelapsedtimer_win.cpp b/src/corelib/kernel/qelapsedtimer_win.cpp deleted file mode 100644 index bbd5b220..00000000 --- a/src/corelib/kernel/qelapsedtimer_win.cpp +++ /dev/null @@ -1,123 +0,0 @@ -// 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 "qelapsedtimer.h" -#include "qdeadlinetimer.h" -#include "qdeadlinetimer_p.h" -#include - -QT_BEGIN_NAMESPACE - -// Result of QueryPerformanceFrequency -static quint64 counterFrequency = 0; - -static void resolveCounterFrequency() -{ - static bool done = false; - if (done) - return; - - // Retrieve the number of high-resolution performance counter ticks per second - LARGE_INTEGER frequency; - if (!QueryPerformanceFrequency(&frequency) || frequency.QuadPart == 0) - qFatal("QueryPerformanceFrequency failed, even though Microsoft documentation promises it wouldn't."); - counterFrequency = frequency.QuadPart; - - done = true; -} - -static inline qint64 ticksToNanoseconds(qint64 ticks) -{ - // QueryPerformanceCounter uses an arbitrary frequency - qint64 seconds = ticks / counterFrequency; - qint64 nanoSeconds = (ticks - seconds * counterFrequency) * 1000000000 / counterFrequency; - return seconds * 1000000000 + nanoSeconds; -} - - -static quint64 getTickCount() -{ - resolveCounterFrequency(); - - LARGE_INTEGER counter; - bool ok = QueryPerformanceCounter(&counter); - Q_ASSERT_X(ok, "QElapsedTimer::start()", - "QueryPerformanceCounter failed, although QueryPerformanceFrequency succeeded."); - Q_UNUSED(ok); - return counter.QuadPart; -} - -quint64 qt_msectime() -{ - return ticksToNanoseconds(getTickCount()) / 1000000; -} - -QElapsedTimer::ClockType QElapsedTimer::clockType() noexcept -{ - resolveCounterFrequency(); - - return PerformanceCounter; -} - -bool QElapsedTimer::isMonotonic() noexcept -{ - return true; -} - -void QElapsedTimer::start() noexcept -{ - t1 = getTickCount(); - t2 = 0; -} - -qint64 QElapsedTimer::restart() noexcept -{ - qint64 oldt1 = t1; - t1 = getTickCount(); - t2 = 0; - return ticksToNanoseconds(t1 - oldt1) / 1000000; -} - -qint64 QElapsedTimer::nsecsElapsed() const noexcept -{ - qint64 elapsed = getTickCount() - t1; - return ticksToNanoseconds(elapsed); -} - -qint64 QElapsedTimer::elapsed() const noexcept -{ - qint64 elapsed = getTickCount() - t1; - return ticksToNanoseconds(elapsed) / 1000000; -} - -qint64 QElapsedTimer::msecsSinceReference() const noexcept -{ - return ticksToNanoseconds(t1) / 1000000; -} - -qint64 QElapsedTimer::msecsTo(const QElapsedTimer &other) const noexcept -{ - qint64 difference = other.t1 - t1; - return ticksToNanoseconds(difference) / 1000000; -} - -qint64 QElapsedTimer::secsTo(const QElapsedTimer &other) const noexcept -{ - return msecsTo(other) / 1000; -} - -bool operator<(const QElapsedTimer &v1, const QElapsedTimer &v2) noexcept -{ - return (v1.t1 - v2.t1) < 0; -} - -QDeadlineTimer QDeadlineTimer::current(Qt::TimerType timerType) noexcept -{ - static_assert(!QDeadlineTimerNanosecondsInT2); - QDeadlineTimer result; - result.t1 = ticksToNanoseconds(getTickCount()); - result.type = timerType; - return result; -} - -QT_END_NAMESPACE diff --git a/src/corelib/kernel/qeventdispatcher_glib.cpp b/src/corelib/kernel/qeventdispatcher_glib.cpp index 9e3ead09..85403bda 100644 --- a/src/corelib/kernel/qeventdispatcher_glib.cpp +++ b/src/corelib/kernel/qeventdispatcher_glib.cpp @@ -488,7 +488,8 @@ void QEventDispatcherGlib::registerTimer(int timerId, qint64 interval, Qt::Timer #endif Q_D(QEventDispatcherGlib); - d->timerSource->timerList.registerTimer(timerId, interval, timerType, object); + d->timerSource->timerList.registerTimer(timerId, std::chrono::milliseconds{ interval }, + timerType, object); } bool QEventDispatcherGlib::unregisterTimer(int timerId) diff --git a/src/corelib/kernel/qeventdispatcher_unix.cpp b/src/corelib/kernel/qeventdispatcher_unix.cpp index d7b2fd6d..c75b59ac 100644 --- a/src/corelib/kernel/qeventdispatcher_unix.cpp +++ b/src/corelib/kernel/qeventdispatcher_unix.cpp @@ -299,7 +299,7 @@ void QEventDispatcherUNIX::registerTimer(int timerId, qint64 interval, Qt::Timer #endif Q_D(QEventDispatcherUNIX); - d->timerList.registerTimer(timerId, interval, timerType, obj); + d->timerList.registerTimer(timerId, std::chrono::milliseconds{ interval }, timerType, obj); } /*! diff --git a/src/corelib/kernel/qeventdispatcher_wasm.cpp b/src/corelib/kernel/qeventdispatcher_wasm.cpp index dac02ad2..db2a3bd1 100644 --- a/src/corelib/kernel/qeventdispatcher_wasm.cpp +++ b/src/corelib/kernel/qeventdispatcher_wasm.cpp @@ -116,6 +116,8 @@ Q_CONSTINIT QEventDispatcherWasm *QEventDispatcherWasm::g_mainThreadEventDispatc #if QT_CONFIG(thread) Q_CONSTINIT QVector QEventDispatcherWasm::g_secondaryThreadEventDispatchers; Q_CONSTINIT std::mutex QEventDispatcherWasm::g_staticDataMutex; +emscripten::ProxyingQueue QEventDispatcherWasm::g_proxyingQueue; +pthread_t QEventDispatcherWasm::g_mainThread; #endif // ### dynamic initialization: std::multimap QEventDispatcherWasm::g_socketNotifiers; @@ -144,6 +146,9 @@ QEventDispatcherWasm::QEventDispatcherWasm() // dispatchers so we set a global pointer to it. Q_ASSERT(g_mainThreadEventDispatcher == nullptr); g_mainThreadEventDispatcher = this; +#if QT_CONFIG(thread) + g_mainThread = pthread_self(); +#endif } else { #if QT_CONFIG(thread) std::lock_guard lock(g_staticDataMutex); @@ -218,8 +223,7 @@ bool QEventDispatcherWasm::processEvents(QEventLoop::ProcessEventsFlags flags) handleApplicationExec(); } - QCoreApplication::sendPostedEvents(); - processWindowSystemEvents(flags); + processPostedEvents(); // The processPostedEvents() call above may process an event which deletes the // application object and the event dispatcher; stop event processing in that case. @@ -242,11 +246,6 @@ bool QEventDispatcherWasm::processEvents(QEventLoop::ProcessEventsFlags flags) return false; } -void QEventDispatcherWasm::processWindowSystemEvents(QEventLoop::ProcessEventsFlags flags) -{ - Q_UNUSED(flags); -} - void QEventDispatcherWasm::registerSocketNotifier(QSocketNotifier *notifier) { LOCK_GUARD(g_staticDataMutex); @@ -370,7 +369,7 @@ void QEventDispatcherWasm::wakeUp() m_pendingProcessEvents = true; } runOnMainThreadAsync([this](){ - QEventDispatcherWasm::callProcessEvents(this); + QEventDispatcherWasm::callProcessPostedEvents(this); }); } } @@ -462,7 +461,7 @@ bool QEventDispatcherWasm::wakeEventDispatcherThread() // Process event activation callbacks for the main thread event dispatcher. // Must be called on the main thread. -void QEventDispatcherWasm::callProcessEvents(void *context) +void QEventDispatcherWasm::callProcessPostedEvents(void *context) { Q_ASSERT(emscripten_is_main_runtime_thread()); @@ -470,7 +469,7 @@ void QEventDispatcherWasm::callProcessEvents(void *context) if (!g_mainThreadEventDispatcher) return; - // In the unlikely event that we get a callProcessEvents() call for + // In the unlikely event that we get a callProcessPostedEvents() call for // a previous main thread event dispatcher (i.e. the QApplication // object was deleted and created again): just ignore it and return. if (context != g_mainThreadEventDispatcher) @@ -480,7 +479,14 @@ void QEventDispatcherWasm::callProcessEvents(void *context) LOCK_GUARD(g_mainThreadEventDispatcher->m_mutex); g_mainThreadEventDispatcher->m_pendingProcessEvents = false; } - g_mainThreadEventDispatcher->processEvents(QEventLoop::AllEvents); + + g_mainThreadEventDispatcher->processPostedEvents(); +} + +bool QEventDispatcherWasm::processPostedEvents() +{ + QCoreApplication::sendPostedEvents(); + return false; } void QEventDispatcherWasm::processTimers() @@ -504,13 +510,13 @@ void QEventDispatcherWasm::updateNativeTimer() // access to m_timerInfo), and then call native API to set the new // wakeup time on the main thread. - auto timespecToNanosec = [](timespec ts) -> uint64_t { + auto timespecToMsec = [](timespec ts) -> uint64_t { return ts.tv_sec * 1000 + ts.tv_nsec / (1000 * 1000); }; timespec toWait; bool hasTimer = m_timerInfo->timerWait(toWait); - uint64_t currentTime = timespecToNanosec(m_timerInfo->currentTime); - uint64_t toWaitDuration = timespecToNanosec(toWait); + uint64_t currentTime = timespecToMsec(m_timerInfo->currentTime); + uint64_t toWaitDuration = timespecToMsec(toWait); uint64_t newTargetTime = currentTime + toWaitDuration; auto maintainNativeTimer = [this, hasTimer, toWaitDuration, newTargetTime]() { @@ -820,7 +826,9 @@ void QEventDispatcherWasm::runOnMainThread(std::function fn) #if QT_CONFIG(thread) if (!emscripten_is_main_runtime_thread()) { void *context = new std::function(fn); - emscripten_async_run_in_main_runtime_thread_(EM_FUNC_SIG_VI, reinterpret_cast(trampoline), context); + g_proxyingQueue.proxyAsync(g_mainThread, [context]{ + trampoline(context); + }); return; } #endif @@ -834,7 +842,9 @@ void QEventDispatcherWasm::runOnMainThreadAsync(std::function fn) void *context = new std::function(fn); #if QT_CONFIG(thread) if (!emscripten_is_main_runtime_thread()) { - emscripten_async_run_in_main_runtime_thread_(EM_FUNC_SIG_VI, reinterpret_cast(trampoline), context); + g_proxyingQueue.proxyAsync(g_mainThread, [context]{ + trampoline(context); + }); return; } #endif diff --git a/src/corelib/kernel/qeventdispatcher_wasm_p.h b/src/corelib/kernel/qeventdispatcher_wasm_p.h index 50f1bda2..1a87ad3b 100644 --- a/src/corelib/kernel/qeventdispatcher_wasm_p.h +++ b/src/corelib/kernel/qeventdispatcher_wasm_p.h @@ -24,6 +24,8 @@ #include #include +#include + QT_BEGIN_NAMESPACE Q_DECLARE_LOGGING_CATEGORY(lcEventDispatcher); @@ -53,7 +55,7 @@ public: static void socketSelect(int timeout, int socket, bool waitForRead, bool waitForWrite, bool *selectForRead, bool *selectForWrite, bool *socketDisconnect); protected: - virtual void processWindowSystemEvents(QEventLoop::ProcessEventsFlags flags); + virtual bool processPostedEvents(); private: bool isMainThreadEventDispatcher(); @@ -64,7 +66,7 @@ private: void handleDialogExec(); bool wait(int timeout = -1); bool wakeEventDispatcherThread(); - static void callProcessEvents(void *eventDispatcher); + static void callProcessPostedEvents(void *eventDispatcher); void processTimers(); void updateNativeTimer(); @@ -106,6 +108,8 @@ private: static QVector g_secondaryThreadEventDispatchers; static std::mutex g_staticDataMutex; + static emscripten::ProxyingQueue g_proxyingQueue; + static pthread_t g_mainThread; // Note on mutex usage: the global g_staticDataMutex protects the global (g_ prefixed) data, // while the per eventdispatcher m_mutex protects the state accociated with blocking and waking diff --git a/src/corelib/kernel/qeventdispatcher_win.cpp b/src/corelib/kernel/qeventdispatcher_win.cpp index 26467f34..f7fd2a7b 100644 --- a/src/corelib/kernel/qeventdispatcher_win.cpp +++ b/src/corelib/kernel/qeventdispatcher_win.cpp @@ -56,6 +56,13 @@ class QEventDispatcherWin32Private; LRESULT QT_WIN_CALLBACK qt_internal_proc(HWND hwnd, UINT message, WPARAM wp, LPARAM lp); +static quint64 qt_msectime() +{ + using namespace std::chrono; + auto t = duration_cast(steady_clock::now().time_since_epoch()); + return t.count(); +} + QEventDispatcherWin32Private::QEventDispatcherWin32Private() : interrupt(false), internalHwnd(0), sendPostedEventsTimerId(0), wakeUps(0), @@ -354,15 +361,10 @@ void QEventDispatcherWin32Private::registerTimer(WinTimerInfo *t) ok = t->fastTimerId; } - typedef BOOL (WINAPI *SetCoalescableTimerFunc) (HWND, UINT_PTR, UINT, TIMERPROC, ULONG); - static SetCoalescableTimerFunc mySetCoalescableTimerFunc = - (SetCoalescableTimerFunc)::GetProcAddress(::GetModuleHandle(L"User32"), "SetCoalescableTimer"); - - if (!ok && mySetCoalescableTimerFunc) { + if (!ok) { // user normal timers for (Very)CoarseTimers, or if no more multimedia timers available - ok = mySetCoalescableTimerFunc(internalHwnd, t->timerId, interval, nullptr, tolerance); + ok = SetCoalescableTimer(internalHwnd, t->timerId, interval, nullptr, tolerance); } - if (!ok) ok = SetTimer(internalHwnd, t->timerId, interval, nullptr); diff --git a/src/corelib/kernel/qeventdispatcher_win_p.h b/src/corelib/kernel/qeventdispatcher_win_p.h index ecd4bcb2..e6ea6b28 100644 --- a/src/corelib/kernel/qeventdispatcher_win_p.h +++ b/src/corelib/kernel/qeventdispatcher_win_p.h @@ -28,7 +28,6 @@ class QEventDispatcherWin32Private; // forward declaration LRESULT QT_WIN_CALLBACK qt_internal_proc(HWND hwnd, UINT message, WPARAM wp, LPARAM lp); -quint64 qt_msectime(); class Q_CORE_EXPORT QEventDispatcherWin32 : public QAbstractEventDispatcher { diff --git a/src/corelib/kernel/qeventloop.cpp b/src/corelib/kernel/qeventloop.cpp index 7001bfa5..769f9a68 100644 --- a/src/corelib/kernel/qeventloop.cpp +++ b/src/corelib/kernel/qeventloop.cpp @@ -293,57 +293,10 @@ bool QEventLoop::event(QEvent *event) void QEventLoop::quit() { exit(0); } - -class QEventLoopLockerPrivate -{ -public: - explicit QEventLoopLockerPrivate(QEventLoopPrivate *loop) - : loop(loop), type(EventLoop) - { - loop->ref(); - } - - explicit QEventLoopLockerPrivate(QThreadPrivate *thread) - : thread(thread), type(Thread) - { - thread->ref(); - } - - explicit QEventLoopLockerPrivate(QCoreApplicationPrivate *app) - : app(app), type(Application) - { - app->ref(); - } - - ~QEventLoopLockerPrivate() - { - switch (type) - { - case EventLoop: - loop->deref(); - break; - case Thread: - thread->deref(); - break; - default: - app->deref(); - break; - } - } - -private: - union { - QEventLoopPrivate * loop; - QThreadPrivate * thread; - QCoreApplicationPrivate * app; - }; - enum Type { - EventLoop, - Thread, - Application - }; - const Type type; -}; +// If any of these trigger, the Type bits will interfere with the pointer values: +static_assert(alignof(QEventLoop) >= 4); +static_assert(alignof(QThread) >= 4); +static_assert(alignof(QCoreApplication) >= 4); /*! \class QEventLoopLocker @@ -372,8 +325,8 @@ private: \sa QCoreApplication::quit(), QCoreApplication::isQuitLockEnabled() */ -QEventLoopLocker::QEventLoopLocker() - : d_ptr(new QEventLoopLockerPrivate(static_cast(QObjectPrivate::get(QCoreApplication::instance())))) +QEventLoopLocker::QEventLoopLocker() noexcept + : QEventLoopLocker{QCoreApplication::instance(), Type::Application} { } @@ -385,8 +338,8 @@ QEventLoopLocker::QEventLoopLocker() \sa QEventLoop::quit() */ -QEventLoopLocker::QEventLoopLocker(QEventLoop *loop) - : d_ptr(new QEventLoopLockerPrivate(static_cast(QObjectPrivate::get(loop)))) +QEventLoopLocker::QEventLoopLocker(QEventLoop *loop) noexcept + : QEventLoopLocker{loop, Type::EventLoop} { } @@ -398,8 +351,8 @@ QEventLoopLocker::QEventLoopLocker(QEventLoop *loop) \sa QThread::quit() */ -QEventLoopLocker::QEventLoopLocker(QThread *thread) - : d_ptr(new QEventLoopLockerPrivate(static_cast(QObjectPrivate::get(thread)))) +QEventLoopLocker::QEventLoopLocker(QThread *thread) noexcept + : QEventLoopLocker{thread, Type::Thread} { } @@ -409,7 +362,33 @@ QEventLoopLocker::QEventLoopLocker(QThread *thread) */ QEventLoopLocker::~QEventLoopLocker() { - delete d_ptr; + visit([](auto p) { p->d_func()->deref(); }); +} + +/*! + \internal +*/ +QEventLoopLocker::QEventLoopLocker(void *ptr, Type t) noexcept + : p{quintptr(ptr) | quintptr(t)} +{ + visit([](auto p) { p->d_func()->ref(); }); +} + +/*! + \internal +*/ +template +void QEventLoopLocker::visit(Func f) const +{ + const auto ptr = pointer(); + if (!ptr) + return; + switch (type()) { + case Type::EventLoop: return f(static_cast(ptr)); + case Type::Thread: return f(static_cast(ptr)); + case Type::Application: return f(static_cast(ptr)); + } + Q_UNREACHABLE(); } QT_END_NAMESPACE diff --git a/src/corelib/kernel/qeventloop.h b/src/corelib/kernel/qeventloop.h index 1da1d233..b8df58bd 100644 --- a/src/corelib/kernel/qeventloop.h +++ b/src/corelib/kernel/qeventloop.h @@ -8,12 +8,14 @@ QT_BEGIN_NAMESPACE +class QEventLoopLocker; class QEventLoopPrivate; class Q_CORE_EXPORT QEventLoop : public QObject { Q_OBJECT Q_DECLARE_PRIVATE(QEventLoop) + friend class QEventLoopLocker; public: explicit QEventLoop(QObject *parent = nullptr); @@ -51,17 +53,34 @@ Q_DECLARE_OPERATORS_FOR_FLAGS(QEventLoop::ProcessEventsFlags) class QEventLoopLockerPrivate; -class Q_CORE_EXPORT QEventLoopLocker +class QEventLoopLocker { public: - QEventLoopLocker(); - explicit QEventLoopLocker(QEventLoop *loop); - explicit QEventLoopLocker(QThread *thread); - ~QEventLoopLocker(); + Q_NODISCARD_CTOR Q_CORE_EXPORT QEventLoopLocker() noexcept; + Q_NODISCARD_CTOR Q_CORE_EXPORT explicit QEventLoopLocker(QEventLoop *loop) noexcept; + Q_NODISCARD_CTOR Q_CORE_EXPORT explicit QEventLoopLocker(QThread *thread) noexcept; + Q_CORE_EXPORT ~QEventLoopLocker(); private: Q_DISABLE_COPY(QEventLoopLocker) - QEventLoopLockerPrivate *d_ptr; + friend class QEventLoopLockerPrivate; + + // + // Private implementation details. + // Do not call from public inline API! + // + enum class Type : quintptr { + EventLoop, + Thread, + Application, + }; + explicit QEventLoopLocker(void *ptr, Type t) noexcept; + quintptr p; + static constexpr quintptr TypeMask = 0x3; + Type type() const { return Type(p & TypeMask); } + void *pointer() const { return reinterpret_cast(p & ~TypeMask); } + template + void visit(Func func) const; }; QT_END_NAMESPACE diff --git a/src/corelib/kernel/qfunctions_win.cpp b/src/corelib/kernel/qfunctions_win.cpp index 70b67ea0..d5ce3e58 100644 --- a/src/corelib/kernel/qfunctions_win.cpp +++ b/src/corelib/kernel/qfunctions_win.cpp @@ -43,33 +43,23 @@ QComHelper::~QComHelper() */ bool qt_win_hasPackageIdentity() { - typedef BOOL (WINAPI *GetCurrentPackageFullNameFunc) (UINT32 *, PWSTR); - static GetCurrentPackageFullNameFunc myGetCurrentPackageFullName = - (GetCurrentPackageFullNameFunc)::GetProcAddress(::GetModuleHandle(L"kernel32"), "GetCurrentPackageFullName"); - - if (myGetCurrentPackageFullName) - { #if defined(HAS_APPMODEL) - - static const bool hasPackageIdentity = []() { - UINT32 length = 0; - switch (const auto result = myGetCurrentPackageFullName(&length, nullptr)) { - case ERROR_INSUFFICIENT_BUFFER: - return true; - case APPMODEL_ERROR_NO_PACKAGE: - return false; - default: - qWarning("Failed to resolve package identity (error code %ld)", result); - return false; - } - }(); - return hasPackageIdentity; + static const bool hasPackageIdentity = []() { + UINT32 length = 0; + switch (const auto result = GetCurrentPackageFullName(&length, nullptr)) { + case ERROR_INSUFFICIENT_BUFFER: + return true; + case APPMODEL_ERROR_NO_PACKAGE: + return false; + default: + qWarning("Failed to resolve package identity (error code %ld)", result); + return false; + } + }(); + return hasPackageIdentity; #else - return false; -#endif - } - return false; +#endif } QT_END_NAMESPACE diff --git a/src/corelib/kernel/qiterable.h b/src/corelib/kernel/qiterable.h index 11bf82dc..1178c5e8 100644 --- a/src/corelib/kernel/qiterable.h +++ b/src/corelib/kernel/qiterable.h @@ -19,16 +19,16 @@ namespace QtPrivate { QTaggedPointer m_pointer; public: - QConstPreservingPointer(std::nullptr_t) : m_pointer(nullptr, Const) {} + Q_NODISCARD_CTOR QConstPreservingPointer(std::nullptr_t) : m_pointer(nullptr, Const) {} - QConstPreservingPointer(const void *pointer, qsizetype alignment) + Q_NODISCARD_CTOR QConstPreservingPointer(const void *pointer, qsizetype alignment) : m_pointer(reinterpret_cast(const_cast(pointer)), Const) { Q_UNUSED(alignment); Q_ASSERT(alignment > qsizetype(alignof(Storage))); } - QConstPreservingPointer(void *pointer, qsizetype alignment) + Q_NODISCARD_CTOR QConstPreservingPointer(void *pointer, qsizetype alignment) : m_pointer(reinterpret_cast(pointer), Mutable) { Q_UNUSED(alignment); @@ -36,20 +36,20 @@ namespace QtPrivate { } template - QConstPreservingPointer(const InputType *pointer) + Q_NODISCARD_CTOR QConstPreservingPointer(const InputType *pointer) : m_pointer(reinterpret_cast(const_cast(pointer)), Const) { static_assert(alignof(InputType) >= alignof(Storage)); } template - QConstPreservingPointer(InputType *pointer) + Q_NODISCARD_CTOR QConstPreservingPointer(InputType *pointer) : m_pointer(reinterpret_cast(pointer), Mutable) { static_assert(alignof(InputType) >= alignof(Storage)); } - QConstPreservingPointer() = default; + Q_NODISCARD_CTOR QConstPreservingPointer() = default; const Type *constPointer() const { diff --git a/src/corelib/kernel/qjnihelpers.cpp b/src/corelib/kernel/qjnihelpers.cpp index 5981a085..78d05261 100644 --- a/src/corelib/kernel/qjnihelpers.cpp +++ b/src/corelib/kernel/qjnihelpers.cpp @@ -10,6 +10,7 @@ #include "qsemaphore.h" #include "qreadwritelock.h" #include +#include #include #include @@ -34,10 +35,10 @@ static jobject g_jActivity = nullptr; static jobject g_jService = nullptr; static jobject g_jClassLoader = nullptr; -Q_GLOBAL_STATIC(QtAndroidPrivate::OnBindListener *, g_onBindListener, nullptr); -Q_GLOBAL_STATIC(QMutex, g_onBindListenerMutex); +Q_CONSTINIT static QtAndroidPrivate::OnBindListener *g_onBindListener; +Q_CONSTINIT static QBasicMutex g_onBindListenerMutex; Q_GLOBAL_STATIC(QSemaphore, g_waitForServiceSetupSemaphore); -Q_GLOBAL_STATIC(QAtomicInt, g_serviceSetupLockers); +Q_CONSTINIT static QBasicAtomicInt g_serviceSetupLockers = Q_BASIC_ATOMIC_INITIALIZER(0); Q_GLOBAL_STATIC(QReadWriteLock, g_updateMutex); @@ -361,36 +362,36 @@ void QtAndroidPrivate::waitForServiceSetup() int QtAndroidPrivate::acuqireServiceSetup(int flags) { - g_serviceSetupLockers->ref(); + g_serviceSetupLockers.ref(); return flags; } void QtAndroidPrivate::setOnBindListener(QtAndroidPrivate::OnBindListener *listener) { - QMutexLocker lock(g_onBindListenerMutex()); - *g_onBindListener = listener; - if (!g_serviceSetupLockers->deref()) + const auto lock = qt_scoped_lock(g_onBindListenerMutex); + g_onBindListener = listener; + if (!g_serviceSetupLockers.deref()) g_waitForServiceSetupSemaphore->release(); } jobject QtAndroidPrivate::callOnBindListener(jobject intent) { - QMutexLocker lock(g_onBindListenerMutex()); - if (*g_onBindListener) - return (*g_onBindListener)->onBind(intent); + const auto lock = qt_scoped_lock(g_onBindListenerMutex); + if (g_onBindListener) + return g_onBindListener->onBind(intent); return nullptr; } -Q_GLOBAL_STATIC(QAtomicInt, g_androidDeadlockProtector); +Q_CONSTINIT static QBasicAtomicInt g_androidDeadlockProtector = Q_BASIC_ATOMIC_INITIALIZER(0); bool QtAndroidPrivate::acquireAndroidDeadlockProtector() { - return g_androidDeadlockProtector->testAndSetAcquire(0, 1); + return g_androidDeadlockProtector.testAndSetAcquire(0, 1); } void QtAndroidPrivate::releaseAndroidDeadlockProtector() { - g_androidDeadlockProtector->storeRelease(0); + g_androidDeadlockProtector.storeRelease(0); } QT_END_NAMESPACE diff --git a/src/corelib/kernel/qjniobject.cpp b/src/corelib/kernel/qjniobject.cpp index df433509..07acd11d 100644 --- a/src/corelib/kernel/qjniobject.cpp +++ b/src/corelib/kernel/qjniobject.cpp @@ -864,64 +864,6 @@ QByteArray QJniObject::className() const return d->m_className; } -QJniObject QJniObject::callObjectMethodV(const char *methodName, - const char *signature, - va_list args) const -{ - QJniEnvironment env; - jobject res = nullptr; - jmethodID id = getCachedMethodID(env.jniEnv(), methodName, signature); - if (id) { - res = env->CallObjectMethodV(d->m_jobject, id, args); - if (env.checkAndClearExceptions()) { - env->DeleteLocalRef(res); - res = nullptr; - } - } - - QJniObject obj(res); - env->DeleteLocalRef(res); - return obj; -} - -QJniObject QJniObject::callStaticObjectMethodV(const char *className, - const char *methodName, - const char *signature, - va_list args) -{ - QJniEnvironment env; - jobject res = nullptr; - jclass clazz = loadClass(className, env.jniEnv()); - if (clazz) { - jmethodID id = QJniObject::getCachedMethodID(env.jniEnv(), clazz, toBinaryEncClassName(className), - methodName, signature, true); - if (id) { - res = env->CallStaticObjectMethodV(clazz, id, args); - if (env.checkAndClearExceptions()) { - env->DeleteLocalRef(res); - res = nullptr; - } - } - } - - QJniObject obj(res); - env->DeleteLocalRef(res); - return obj; -} - -QJniObject QJniObject::callStaticObjectMethodV(jclass clazz, - const char *methodName, - const char *signature, - va_list args) -{ - QJniEnvironment env; - jmethodID id = getMethodID(env.jniEnv(), clazz, methodName, signature, true); - if (!id) - return QJniObject(); - - return getCleanJniObject(env->CallStaticObjectMethodV(clazz, id, args)); -} - /*! \fn template auto QJniObject::callMethod(const char *methodName, const char *signature, Args &&...args) const \since 6.4 diff --git a/src/corelib/kernel/qjniobject.h b/src/corelib/kernel/qjniobject.h index 56dfdabf..835052eb 100644 --- a/src/corelib/kernel/qjniobject.h +++ b/src/corelib/kernel/qjniobject.h @@ -436,14 +436,6 @@ private: void callVoidMethodV(JNIEnv *env, jmethodID id, ...) const; // ### Qt 7: merge into ... overload void callVoidMethodV(JNIEnv *env, jmethodID id, va_list args) const; - QJniObject callObjectMethodV(const char *methodName, const char *signature, - va_list args) const; - - static QJniObject callStaticObjectMethodV(const char *className, const char *methodName, - const char *signature, va_list args); - - static QJniObject callStaticObjectMethodV(jclass clazz, const char *methodName, - const char *signature, va_list args); bool isSameObject(jobject obj) const; bool isSameObject(const QJniObject &other) const; diff --git a/src/corelib/kernel/qmetacontainer.h b/src/corelib/kernel/qmetacontainer.h index 56415f4d..67c0ddcf 100644 --- a/src/corelib/kernel/qmetacontainer.h +++ b/src/corelib/kernel/qmetacontainer.h @@ -984,6 +984,8 @@ public: return a.d() != b.d(); } + const QtMetaContainerPrivate::QMetaSequenceInterface *iface() const { return d(); } + private: template struct MetaSequence @@ -1178,6 +1180,8 @@ public: return a.d() != b.d(); } + const QtMetaContainerPrivate::QMetaAssociationInterface *iface() const { return d(); } + private: template struct MetaAssociation diff --git a/src/corelib/kernel/qmetaobject.cpp b/src/corelib/kernel/qmetaobject.cpp index 3f628362..188f4da5 100644 --- a/src/corelib/kernel/qmetaobject.cpp +++ b/src/corelib/kernel/qmetaobject.cpp @@ -97,12 +97,17 @@ using namespace Qt::StringLiterals; \internal - \value InvokeSlot - \value EmitSignal + \value InvokeMetaMethod \value ReadProperty \value WriteProperty \value ResetProperty \value CreateInstance + \value IndexOfMethod + \value RegisterPropertyMetaType + \value RegisterMethodArgumentMetaType + \value BindableProperty + \value CustomCall + \value ConstructInPlace */ /*! @@ -414,10 +419,30 @@ QMetaType QMetaObject::metaType() const return QMetaType::fromName(className()); } else { /* in the metatype array, we store - idx: 0 propertyCount - 1 propertyCount - data:QMetaType(prop0), ..., QMetaType(propPropCount-1), QMetaType(class),... - */ - auto iface = this->d.metaTypes[d->propertyCount]; + + | index | data | + |----------------------------------------------------------------------| + | 0 | QMetaType(property0) | + | ... | ... | + | propertyCount - 1 | QMetaType(propertyCount - 1) | + | propertyCount | QMetaType(enumerator0) | + | ... | ... | + | propertyCount + enumeratorCount - 1 | QMetaType(enumeratorCount - 1) | + | propertyCount + enumeratorCount | QMetaType(class) | + + */ +#if QT_VERSION < QT_VERSION_CHECK(7, 0, 0) + // Before revision 12 we only stored metatypes for enums if they showed + // up as types of properties or method arguments or return values. + // From revision 12 on, we always store them in a predictable place. + const qsizetype offset = d->revision < 12 + ? d->propertyCount + : d->propertyCount + d->enumeratorCount; +#else + const qsizetype offset = d->propertyCount + d->enumeratorCount; +#endif + + auto iface = this->d.metaTypes[offset]; if (iface && QtMetaTypePrivate::isInterfaceFor(iface)) return QMetaType(); // return invalid meta-type for namespaces if (iface) @@ -1042,7 +1067,7 @@ int QMetaObject::indexOfEnumerator(const char *name) const for (int i = 0; i < d->enumeratorCount; ++i) { const QMetaEnum e(m, i); const char *prop = rawStringData(m, e.data.name()); - if (name[0] == prop[0] && strcmp(name + 1, prop + 1) == 0) { + if (strcmp(name, prop) == 0) { i += m->enumeratorOffset(); return i; } @@ -1056,7 +1081,7 @@ int QMetaObject::indexOfEnumerator(const char *name) const for (int i = 0; i < d->enumeratorCount; ++i) { const QMetaEnum e(m, i); const char *prop = rawStringData(m, e.data.alias()); - if (name[0] == prop[0] && strcmp(name + 1, prop + 1) == 0) { + if (strcmp(name, prop) == 0) { i += m->enumeratorOffset(); return i; } @@ -1080,7 +1105,7 @@ int QMetaObject::indexOfProperty(const char *name) const for (int i = 0; i < d->propertyCount; ++i) { const QMetaProperty::Data data = QMetaProperty::getMetaPropertyData(m, i); const char *prop = rawStringData(m, data.name()); - if (name[0] == prop[0] && strcmp(name + 1, prop + 1) == 0) { + if (strcmp(name, prop) == 0) { i += m->propertyOffset(); return i; } @@ -1692,31 +1717,19 @@ bool QMetaObject::invokeMethodImpl(QObject *object, QtPrivate::QSlotObjectBase * */ /*! - \fn template bool QMetaObject::invokeMethod(QObject *context, Functor function, Qt::ConnectionType type, FunctorReturnType *ret) + \fn template bool QMetaObject::invokeMethod(QObject *context, Functor &&function, Qt::ConnectionType type, FunctorReturnType *ret) + \fn template bool QMetaObject::invokeMethod(QObject *context, Functor &&function, FunctorReturnType *ret) \since 5.10 - \threadsafe - \overload Invokes the \a function in the event loop of \a context. \a function can be a functor or a pointer to a member function. Returns \c true if the function could be invoked. Returns \c false if there is no such function or the parameters did not match. The return value of the function call is placed in \a ret. -*/ -/*! - \fn template bool QMetaObject::invokeMethod(QObject *context, Functor function, FunctorReturnType *ret) - - \since 5.10 - - \threadsafe - \overload - - Invokes the \a function in the event loop of \a context using the connection type Qt::AutoConnection. - \a function can be a functor or a pointer to a member function. Returns \c true if the function could - be invoked. Returns \c false if there is no such member or the parameters did not match. - The return value of the function call is placed in \a ret. + If \a type is set, then the function is invoked using that connection type. Otherwise, + Qt::AutoConnection will be used. */ /*! @@ -3013,7 +3026,7 @@ const char *QMetaEnum::name() const Returns the enum name of the flag (without the scope). For example, the Qt::AlignmentFlag flag has \c - AlignmentFlag as the enum name, but \c Alignment as as the type name. + AlignmentFlag as the enum name, but \c Alignment as the type name. Non flag enums has the same type and enum names. Enum names have the same scope as the type name. @@ -3028,6 +3041,33 @@ const char *QMetaEnum::enumName() const return rawStringData(mobj, data.alias()); } +/*! + Returns the meta type of the enum. + + If the QMetaObject this enum is part of was generated with Qt 6.5 or + earlier this will be the invalid metatype. + + \note This is the meta type of the enum itself, not of its underlying + numeric type. You can retrieve the meta type of the underlying type of the + enum using \l{QMetaType::underlyingType()}. + + \since 6.6 + \sa QMetaType::underlyingType() +*/ +QMetaType QMetaEnum::metaType() const +{ + if (!mobj) + return {}; + + const QMetaObjectPrivate *p = priv(mobj->d.data); +#if QT_VERSION < QT_VERSION_CHECK(7, 0, 0) + if (p->revision < 12) + QMetaType(); +#endif + + return QMetaType(mobj->d.metaTypes[data.index(mobj) + p->propertyCount]); +} + /*! Returns the number of keys. @@ -3277,6 +3317,11 @@ QMetaEnum::QMetaEnum(const QMetaObject *mobj, int index) Q_ASSERT(index >= 0 && index < priv(mobj->d.data)->enumeratorCount); } +int QMetaEnum::Data::index(const QMetaObject *mobj) const +{ + return (d - mobj->d.data - priv(mobj->d.data)->enumeratorData) / Size; +} + /*! \fn QMetaEnum QMetaEnum::fromType() \since 5.5 @@ -3616,31 +3661,43 @@ QVariant QMetaProperty::read(const QObject *object) const Writes \a value as the property's value to the given \a object. Returns true if the write succeeded; otherwise returns \c false. - If \a value is not of the same type type as the property, a conversion + If \a value is not of the same type as the property, a conversion is attempted. An empty QVariant() is equivalent to a call to reset() if this property is resettable, or setting a default-constructed object otherwise. + \note This function internally makes a copy of \a value. Prefer to use the + rvalue overload when possible. + \sa read(), reset(), isWritable() */ bool QMetaProperty::write(QObject *object, const QVariant &value) const { if (!object || !isWritable()) return false; + return write(object, QVariant(value)); +} - QVariant v = value; +/*! + \overload + \since 6.6 +*/ +bool QMetaProperty::write(QObject *object, QVariant &&v) const +{ + if (!object || !isWritable()) + return false; QMetaType t(mobj->d.metaTypes[data.index(mobj)]); if (t != QMetaType::fromType() && t != v.metaType()) { if (isEnumType() && !t.metaObject() && v.metaType().id() == QMetaType::QString) { // Assigning a string to a property of type Q_ENUMS (instead of Q_ENUM) bool ok; if (isFlagType()) - v = QVariant(menum.keysToValue(value.toByteArray(), &ok)); + v = QVariant(menum.keysToValue(v.toByteArray(), &ok)); else - v = QVariant(menum.keyToValue(value.toByteArray(), &ok)); + v = QVariant(menum.keyToValue(v.toByteArray(), &ok)); if (!ok) return false; - } else if (!value.isValid()) { + } else if (!v.isValid()) { if (isResettable()) return reset(object); v = QVariant(t, nullptr); @@ -3734,6 +3791,16 @@ bool QMetaProperty::writeOnGadget(void *gadget, const QVariant &value) const return write(reinterpret_cast(gadget), value); } +/*! + \overload + \since 6.6 +*/ +bool QMetaProperty::writeOnGadget(void *gadget, QVariant &&value) const +{ + Q_ASSERT(priv(mobj->d.data)->flags & PropertyAccessInStaticMetaCall && mobj->d.static_metacall); + return write(reinterpret_cast(gadget), std::move(value)); +} + /*! \since 5.5 diff --git a/src/corelib/kernel/qmetaobject.h b/src/corelib/kernel/qmetaobject.h index 3aa3a687..c51922e6 100644 --- a/src/corelib/kernel/qmetaobject.h +++ b/src/corelib/kernel/qmetaobject.h @@ -265,6 +265,8 @@ public: const char *name() const; const char *enumName() const; + QMetaType metaType() const; + bool isFlag() const; bool isScoped() const; @@ -302,6 +304,7 @@ private: quint32 flags() const { return d[2]; } qint32 keyCount() const { return static_cast(d[3]); } quint32 data() const { return d[4]; } + int index(const QMetaObject *mobj) const; const uint *d; }; @@ -360,12 +363,14 @@ public: QVariant read(const QObject *obj) const; bool write(QObject *obj, const QVariant &value) const; + bool write(QObject *obj, QVariant &&value) const; bool reset(QObject *obj) const; QUntypedBindable bindable(QObject *object) const; QVariant readOnGadget(const void *gadget) const; bool writeOnGadget(void *gadget, const QVariant &value) const; + bool writeOnGadget(void *gadget, QVariant &&value) const; bool resetOnGadget(void *gadget) const; bool hasStdCppSet() const; diff --git a/src/corelib/kernel/qmetaobject_p.h b/src/corelib/kernel/qmetaobject_p.h index d05b727f..224c9d53 100644 --- a/src/corelib/kernel/qmetaobject_p.h +++ b/src/corelib/kernel/qmetaobject_p.h @@ -175,7 +175,8 @@ struct QMetaObjectPrivate // revision 10 is Qt 6.2: The metatype of the metaobject is stored in the metatypes array // and metamethods store a flag stating whether they are const // revision 11 is Qt 6.5: The metatype for void is stored in the metatypes array - enum { OutputRevision = 11 }; // Used by moc, qmetaobjectbuilder and qdbus + // revision 12 is Qt 6.6: It adds the metatype for enums + enum { OutputRevision = 12 }; // Used by moc, qmetaobjectbuilder and qdbus enum { IntsPerMethod = QMetaMethod::Data::Size }; enum { IntsPerEnum = QMetaEnum::Data::Size }; enum { IntsPerProperty = QMetaProperty::Data::Size }; diff --git a/src/corelib/kernel/qmetaobjectbuilder.cpp b/src/corelib/kernel/qmetaobjectbuilder.cpp index e61065f4..b471bf65 100644 --- a/src/corelib/kernel/qmetaobjectbuilder.cpp +++ b/src/corelib/kernel/qmetaobjectbuilder.cpp @@ -159,6 +159,7 @@ public: QByteArray name; QByteArray enumName; + QMetaType metaType; bool isFlag; bool isScoped; QList keys; @@ -597,6 +598,7 @@ QMetaEnumBuilder QMetaObjectBuilder::addEnumerator(const QMetaEnum &prototype) { QMetaEnumBuilder en = addEnumerator(prototype.name()); en.setEnumName(prototype.enumName()); + en.setMetaType(prototype.metaType()); en.setIsFlag(prototype.isFlag()); en.setIsScoped(prototype.isScoped()); int count = prototype.keyCount(); @@ -1170,7 +1172,7 @@ static int buildMetaObject(QMetaObjectBuilderPrivate *d, char *buf, - int(d->methods.size()) // return "parameters" don't have names - int(d->constructors.size()); // "this" parameters don't have names if constexpr (mode == Construct) { - static_assert(QMetaObjectPrivate::OutputRevision == 11, "QMetaObjectBuilder should generate the same version as moc"); + static_assert(QMetaObjectPrivate::OutputRevision == 12, "QMetaObjectBuilder should generate the same version as moc"); pmeta->revision = QMetaObjectPrivate::OutputRevision; pmeta->flags = d->flags.toInt(); pmeta->className = 0; // Class name is always the first string. @@ -2305,6 +2307,31 @@ void QMetaEnumBuilder::setEnumName(const QByteArray &alias) d->enumName = alias; } +/*! + Returns the meta type of the enumerator. + + \since 6.6 +*/ +QMetaType QMetaEnumBuilder::metaType() const +{ + if (QMetaEnumBuilderPrivate *d = d_func()) + return d->metaType; + return QMetaType(); +} + +/*! + Sets this enumerator to have the given \c metaType. + + \since 6.6 + \sa metaType() +*/ +void QMetaEnumBuilder::setMetaType(QMetaType metaType) +{ + QMetaEnumBuilderPrivate *d = d_func(); + if (d) + d->metaType = metaType; +} + /*! Returns \c true if this enumerator is used as a flag; otherwise returns false. diff --git a/src/corelib/kernel/qmetaobjectbuilder_p.h b/src/corelib/kernel/qmetaobjectbuilder_p.h index 99eaca2a..b5248798 100644 --- a/src/corelib/kernel/qmetaobjectbuilder_p.h +++ b/src/corelib/kernel/qmetaobjectbuilder_p.h @@ -258,6 +258,9 @@ public: QByteArray enumName() const; void setEnumName(const QByteArray &alias); + QMetaType metaType() const; + void setMetaType(QMetaType metaType); + bool isFlag() const; void setIsFlag(bool value); diff --git a/src/corelib/kernel/qmetatype.cpp b/src/corelib/kernel/qmetatype.cpp index 44567d56..5f42bc00 100644 --- a/src/corelib/kernel/qmetatype.cpp +++ b/src/corelib/kernel/qmetatype.cpp @@ -460,7 +460,7 @@ const char *QtMetaTypePrivate::typedefNameForType(const QtPrivate::QMetaTypeInte \value IsPointer This type is a pointer to another type. \omitvalue WeakPointerToQObject \omitvalue TrackingPointerToQObject - \omitvalue IsGadget \omit This type is a Q_GADGET and it's corresponding QMetaObject can be accessed with QMetaType::metaObject Since 5.5. \endomit + \omitvalue IsGadget \omit (since Qt 5.5) This type is a Q_GADGET and its corresponding QMetaObject can be accessed with QMetaType::metaObject. \endomit \omitvalue PointerToGadget \omitvalue IsQmlList \value IsConst Indicates that values of this type are immutable; for instance, because they are pointers to const objects. @@ -1258,26 +1258,26 @@ static const struct : QMetaTypeModuleHelper QMETATYPE_CONVERTER(QByteArrayList, QVariantList, result.reserve(source.size()); - for (auto v: source) + for (const auto &v: source) result.append(v.toByteArray()); return true; ); QMETATYPE_CONVERTER(QVariantList, QByteArrayList, result.reserve(source.size()); - for (auto v: source) + for (const auto &v: source) result.append(QVariant(v)); return true; ); QMETATYPE_CONVERTER(QStringList, QVariantList, result.reserve(source.size()); - for (auto v: source) + for (const auto &v: source) result.append(v.toString()); return true; ); QMETATYPE_CONVERTER(QVariantList, QStringList, result.reserve(source.size()); - for (auto v: source) + for (const auto &v: source) result.append(QVariant(v)); return true; ); @@ -1965,13 +1965,13 @@ static bool convertFromEnum(QMetaType fromType, const void *from, QMetaType toTy QMetaEnum en = metaEnumFromType(fromType); if (en.isValid()) { if (en.isFlag()) { - const QByteArray keys = en.valueToKeys(ll); + const QByteArray keys = en.valueToKeys(static_cast(ll)); if (toType.id() == QMetaType::QString) *static_cast(to) = QString::fromUtf8(keys); else *static_cast(to) = keys; } else { - const char *key = en.valueToKey(ll); + const char *key = en.valueToKey(static_cast(ll)); if (toType.id() == QMetaType::QString) *static_cast(to) = QString::fromUtf8(key); else @@ -2900,6 +2900,59 @@ bool QMetaType::hasRegisteredDataStreamOperators() const return d_ptr && d_ptr->dataStreamIn != nullptr && d_ptr->dataStreamOut != nullptr; } +/*! + \since 6.6 + + If this metatype represents an enumeration, this method returns a + metatype of a numeric class of the same signedness and size as the + enums underlying type. + If it represents a QFlags type, it returns QMetaType::Int. + In all other cases an invalid QMetaType is returned. + */ +QMetaType QMetaType::underlyingType() const +{ + if (!d_ptr || !(flags() & IsEnumeration)) + return {}; + /* QFlags has enumeration set so that's handled here (qint32 + case), as QFlags uses int as the underlying type + Note that we do some approximation here, as we cannot + differentiate between different underlying types of the + same size and signedness (consider char <-> (un)signed char, + int <-> long <-> long long). + + ### TODO PENDING: QTBUG-111926 - QFlags supporting >32 bit int + */ + if (flags() & IsUnsignedEnumeration) { + switch (sizeOf()) { + case 1: + return QMetaType::fromType(); + case 2: + return QMetaType::fromType(); + case 4: + return QMetaType::fromType(); + case 8: + return QMetaType::fromType(); + default: + break; + } + } else { + switch (sizeOf()) { + case 1: + return QMetaType::fromType(); + case 2: + return QMetaType::fromType(); + case 4: + return QMetaType::fromType(); + case 8: + return QMetaType::fromType(); + default: + break; + } + } + // int128 can be handled above once we have qint128 + return QMetaType(); +} + /*! \fn bool QMetaType::load(QDataStream &stream, int type, void *data) \overload @@ -3167,6 +3220,7 @@ QT_FOR_EACH_STATIC_PRIMITIVE_POINTER(QT_METATYPE_DECLARE_TEMPLATE_ITER) QT_FOR_EACH_STATIC_CORE_CLASS(QT_METATYPE_DECLARE_TEMPLATE_ITER) QT_FOR_EACH_STATIC_CORE_POINTER(QT_METATYPE_DECLARE_TEMPLATE_ITER) QT_FOR_EACH_STATIC_CORE_TEMPLATE(QT_METATYPE_DECLARE_TEMPLATE_ITER) + #undef QT_METATYPE_DECLARE_TEMPLATE_ITER #endif } diff --git a/src/corelib/kernel/qmetatype.h b/src/corelib/kernel/qmetatype.h index a641313c..02ecaf98 100644 --- a/src/corelib/kernel/qmetatype.h +++ b/src/corelib/kernel/qmetatype.h @@ -492,6 +492,8 @@ public: #endif #endif + QMetaType underlyingType() const; + template constexpr static QMetaType fromType(); static QMetaType fromName(QByteArrayView name); @@ -1268,7 +1270,7 @@ namespace QtPrivate { | (IsEnumOrFlags::value ? QMetaType::IsEnumeration : 0) | (IsGadgetHelper::IsGadgetOrDerivedFrom ? QMetaType::IsGadget : 0) | (IsPointerToGadgetHelper::IsGadgetOrDerivedFrom ? QMetaType::PointerToGadget : 0) - | (QTypeInfo::isPointer ? QMetaType::IsPointer : 0) + | (std::is_pointer_v ? QMetaType::IsPointer : 0) | (IsUnsignedEnum ? QMetaType::IsUnsignedEnumeration : 0) | (IsQmlListType ? QMetaType::IsQmlList : 0) | (std::is_const_v> ? QMetaType::IsConst : 0) diff --git a/src/corelib/kernel/qmetatype_p.h b/src/corelib/kernel/qmetatype_p.h index e6493948..71a225d5 100644 --- a/src/corelib/kernel/qmetatype_p.h +++ b/src/corelib/kernel/qmetatype_p.h @@ -178,6 +178,15 @@ inline void copyConstruct(const QtPrivate::QMetaTypeInterface *iface, void *wher memcpy(where, copy, iface->size); } +inline void moveConstruct(const QtPrivate::QMetaTypeInterface *iface, void *where, void *copy) +{ + Q_ASSERT(isMoveConstructible(iface)); + if (iface->moveCtr) + iface->moveCtr(iface, where, copy); + else + memcpy(where, copy, iface->size); +} + inline void construct(const QtPrivate::QMetaTypeInterface *iface, void *where, const void *copy) { if (copy) diff --git a/src/corelib/kernel/qobject.cpp b/src/corelib/kernel/qobject.cpp index c2a9dc34..7a96918f 100644 --- a/src/corelib/kernel/qobject.cpp +++ b/src/corelib/kernel/qobject.cpp @@ -28,7 +28,6 @@ #if QT_CONFIG(thread) #include #endif -#include #include #include @@ -370,16 +369,16 @@ void QObjectPrivate::ConnectionData::removeConnection(QObjectPrivate::Connection c->prevConnectionList->nextConnectionList.storeRelaxed(n); c->prevConnectionList = nullptr; - Q_ASSERT(c != orphaned.loadRelaxed()); + Q_ASSERT(c != static_cast(orphaned.load(std::memory_order_relaxed))); // add c to orphanedConnections - Connection *o = nullptr; + TaggedSignalVector o = nullptr; /* No ABA issue here: When adding a node, we only care about the list head, it doesn't * matter if the tail changes. */ + o = orphaned.load(std::memory_order_acquire); do { - o = orphaned.loadRelaxed(); c->nextInOrphanList = o; - } while (!orphaned.testAndSetRelease(o, c)); + } while (!orphaned.compare_exchange_strong(o, TaggedSignalVector(c), std::memory_order_release)); #ifndef QT_NO_DEBUG found = false; @@ -397,7 +396,7 @@ void QObjectPrivate::ConnectionData::removeConnection(QObjectPrivate::Connection void QObjectPrivate::ConnectionData::cleanOrphanedConnectionsImpl(QObject *sender, LockPolicy lockPolicy) { QBasicMutex *senderMutex = signalSlotLock(sender); - ConnectionOrSignalVector *c = nullptr; + TaggedSignalVector c = nullptr; { std::unique_lock lock(*senderMutex, std::defer_lock_t{}); if (lockPolicy == NeedToLock) @@ -408,7 +407,7 @@ void QObjectPrivate::ConnectionData::cleanOrphanedConnectionsImpl(QObject *sende // Since ref == 1, no activate() is in process since we locked the mutex. That implies, // that nothing can reference the orphaned connection objects anymore and they can // be safely deleted - c = orphaned.fetchAndStoreRelaxed(nullptr); + c = orphaned.exchange(nullptr, std::memory_order_relaxed); } if (c) { // Deleting c might run arbitrary user code, so we must not hold the lock @@ -422,11 +421,11 @@ void QObjectPrivate::ConnectionData::cleanOrphanedConnectionsImpl(QObject *sende } } -inline void QObjectPrivate::ConnectionData::deleteOrphaned(QObjectPrivate::ConnectionOrSignalVector *o) +inline void QObjectPrivate::ConnectionData::deleteOrphaned(TaggedSignalVector o) { while (o) { - QObjectPrivate::ConnectionOrSignalVector *next = nullptr; - if (SignalVector *v = ConnectionOrSignalVector::asSignalVector(o)) { + TaggedSignalVector next = nullptr; + if (SignalVector *v = static_cast(o)) { next = v->nextInOrphanList; free(v); } else { @@ -1684,7 +1683,7 @@ void QObject::moveToThread(QThread *targetThread) "Cannot move to target thread (%p)\n", currentData->thread.loadRelaxed(), thisThreadData->thread.loadRelaxed(), targetData ? targetData->thread.loadRelaxed() : nullptr); -#ifdef Q_OS_MAC +#ifdef Q_OS_DARWIN qWarning("You might be loading two sets of Qt binaries into the same process. " "Check that all plugins are compiled against the right Qt binaries. Export " "DYLD_PRINT_LIBRARIES=1 and check that only one set of binaries are being loaded."); @@ -1825,74 +1824,33 @@ void QObjectPrivate::_q_reregisterTimers(void *pointer) // /*! - Starts a timer and returns a timer identifier, or returns zero if - it could not start a timer. + \fn int QObject::startTimer(int interval, Qt::TimerType timerType) - A timer event will occur every \a interval milliseconds until - killTimer() is called. If \a interval is 0, then the timer event - occurs once every time there are no more window system events to - process. - - The virtual timerEvent() function is called with the QTimerEvent - event parameter class when a timer event occurs. Reimplement this - function to get timer events. - - If multiple timers are running, the QTimerEvent::timerId() can be - used to find out which timer was activated. - - Example: - - \snippet code/src_corelib_kernel_qobject.cpp 8 - - Note that QTimer's accuracy depends on the underlying operating system and - hardware. The \a timerType argument allows you to customize the accuracy of - the timer. See Qt::TimerType for information on the different timer types. - Most platforms support an accuracy of 20 milliseconds; some provide more. - If Qt is unable to deliver the requested number of timer events, it will - silently discard some. - - The QTimer class provides a high-level programming interface with - single-shot timers and timer signals instead of events. There is - also a QBasicTimer class that is more lightweight than QTimer and - less clumsy than using timer IDs directly. + This is an overloaded function that will start a timer of type + \a timerType and a timeout of \a interval milliseconds. This is + equivalent to calling: + \code + startTimer(std::chrono::milliseconds{interval}, timerType); + \endcode \sa timerEvent(), killTimer(), QTimer::singleShot() */ int QObject::startTimer(int interval, Qt::TimerType timerType) { - Q_D(QObject); - - if (Q_UNLIKELY(interval < 0)) { - qWarning("QObject::startTimer: Timers cannot have negative intervals"); - return 0; - } - - auto thisThreadData = d->threadData.loadRelaxed(); - if (Q_UNLIKELY(!thisThreadData->hasEventDispatcher())) { - qWarning("QObject::startTimer: Timers can only be used with threads started with QThread"); - return 0; - } - if (Q_UNLIKELY(thread() != QThread::currentThread())) { - qWarning("QObject::startTimer: Timers cannot be started from another thread"); - return 0; - } - int timerId = thisThreadData->eventDispatcher.loadRelaxed()->registerTimer(interval, timerType, this); - d->ensureExtraData(); - d->extraData->runningTimers.append(timerId); - return timerId; + return startTimer(std::chrono::milliseconds{interval}, timerType); } /*! \since 5.9 \overload - \fn int QObject::startTimer(std::chrono::milliseconds time, Qt::TimerType timerType) + \fn int QObject::startTimer(std::chrono::milliseconds interval, Qt::TimerType timerType) Starts a timer and returns a timer identifier, or returns zero if it could not start a timer. - A timer event will occur every \a time interval until killTimer() - is called. If \a time is equal to \c{std::chrono::duration::zero()}, + A timer event will occur every \a interval until killTimer() + is called. If \a interval is equal to \c{std::chrono::duration::zero()}, then the timer event occurs once every time there are no more window system events to process. @@ -1921,6 +1879,33 @@ int QObject::startTimer(int interval, Qt::TimerType timerType) \sa timerEvent(), killTimer(), QTimer::singleShot() */ +int QObject::startTimer(std::chrono::milliseconds interval, Qt::TimerType timerType) +{ + Q_D(QObject); + + using namespace std::chrono_literals; + + if (Q_UNLIKELY(interval < 0ms)) { + qWarning("QObject::startTimer: Timers cannot have negative intervals"); + return 0; + } + + auto thisThreadData = d->threadData.loadRelaxed(); + if (Q_UNLIKELY(!thisThreadData->hasEventDispatcher())) { + qWarning("QObject::startTimer: Timers can only be used with threads started with QThread"); + return 0; + } + if (Q_UNLIKELY(thread() != QThread::currentThread())) { + qWarning("QObject::startTimer: Timers cannot be started from another thread"); + return 0; + } + + auto dispatcher = thisThreadData->eventDispatcher.loadRelaxed(); + int timerId = dispatcher->registerTimer(interval.count(), timerType, this); + d->ensureExtraData(); + d->extraData->runningTimers.append(timerId); + return timerId; +} /*! Kills the timer with timer identifier, \a id. @@ -3825,7 +3810,7 @@ struct SlotObjectGuard { SlotObjectGuard() = default; // move would be fine, but we do not need it currently Q_DISABLE_COPY_MOVE(SlotObjectGuard) - explicit SlotObjectGuard(QtPrivate::QSlotObjectBase *slotObject) + Q_NODISCARD_CTOR explicit SlotObjectGuard(QtPrivate::QSlotObjectBase *slotObject) : m_slotObject(slotObject) { if (m_slotObject) @@ -4153,6 +4138,8 @@ int QObjectPrivate::signalIndex(const char *signalName, *****************************************************************************/ /*! + \fn bool QObject::setProperty(const char *name, const QVariant &value) + Sets the value of the object's \a name property to \a value. If the property is defined in the class using Q_PROPERTY then @@ -4173,9 +4160,17 @@ int QObjectPrivate::signalIndex(const char *signalName, \sa property(), metaObject(), dynamicPropertyNames(), QMetaProperty::write() */ -bool QObject::setProperty(const char *name, const QVariant &value) + +/*! + \fn bool QObject::setProperty(const char *name, QVariant &&value) + \since 6.6 + \overload setProperty +*/ + +bool QObject::doSetProperty(const char *name, const QVariant *lvalue, QVariant *rvalue) { Q_D(QObject); + const auto &value =*lvalue; const QMetaObject *meta = metaObject(); if (!name || !meta) return false; @@ -4194,12 +4189,18 @@ bool QObject::setProperty(const char *name, const QVariant &value) } else { if (idx == -1) { d->extraData->propertyNames.append(name); - d->extraData->propertyValues.append(value); + if (rvalue) + d->extraData->propertyValues.append(std::move(*rvalue)); + else + d->extraData->propertyValues.append(*lvalue); } else { if (value.userType() == d->extraData->propertyValues.at(idx).userType() && value == d->extraData->propertyValues.at(idx)) return false; - d->extraData->propertyValues[idx] = value; + if (rvalue) + d->extraData->propertyValues[idx] = std::move(*rvalue); + else + d->extraData->propertyValues[idx] = *lvalue; } } @@ -4214,7 +4215,7 @@ bool QObject::setProperty(const char *name, const QVariant &value) qWarning("%s::setProperty: Property \"%s\" invalid," " read-only or does not exist", metaObject()->className(), name); #endif - return p.write(this, value); + return rvalue ? p.write(this, std::move(*rvalue)) : p.write(this, *lvalue); } /*! diff --git a/src/corelib/kernel/qobject.h b/src/corelib/kernel/qobject.h index 07552957..f6a3ebc5 100644 --- a/src/corelib/kernel/qobject.h +++ b/src/corelib/kernel/qobject.h @@ -126,11 +126,7 @@ public: void moveToThread(QThread *thread); int startTimer(int interval, Qt::TimerType timerType = Qt::CoarseTimer); - Q_ALWAYS_INLINE - int startTimer(std::chrono::milliseconds time, Qt::TimerType timerType = Qt::CoarseTimer) - { - return startTimer(int(time.count()), timerType); - } + int startTimer(std::chrono::milliseconds time, Qt::TimerType timerType = Qt::CoarseTimer); void killTimer(int id); template @@ -196,14 +192,28 @@ public: template static QMetaObject::Connection connect(const QObject *sender, PointerToMemberFunction signal, const QObject *context, Functor functor, Qt::ConnectionType type = Qt::AutoConnection); #else - //Connect a signal to a pointer to qobject member function + //connect with context template - static inline QMetaObject::Connection connect(const typename QtPrivate::FunctionPointer::Object *sender, Func1 signal, - const typename QtPrivate::FunctionPointer::Object *receiver, Func2 slot, - Qt::ConnectionType type = Qt::AutoConnection) + static inline QMetaObject::Connection + connect(const typename QtPrivate::FunctionPointer::Object *sender, Func1 signal, + const typename QtPrivate::ContextTypeForFunctor::ContextType *context, Func2 &&slot, + Qt::ConnectionType type = Qt::AutoConnection) { typedef QtPrivate::FunctionPointer SignalType; - typedef QtPrivate::FunctionPointer SlotType; + typedef QtPrivate::FunctionPointer> SlotType; + + if constexpr (SlotType::ArgumentCount != -1) { + static_assert((QtPrivate::AreArgumentsCompatible::value), + "Return type of the slot is not compatible with the return type of the signal."); + } else { + constexpr int FunctorArgumentCount = QtPrivate::ComputeFunctorArgumentCount, typename SignalType::Arguments>::Value; + [[maybe_unused]] + constexpr int SlotArgumentCount = (FunctorArgumentCount >= 0) ? FunctorArgumentCount : 0; + typedef typename QtPrivate::FunctorReturnType, typename QtPrivate::List_Left::Value>::Value SlotReturnType; + + static_assert((QtPrivate::AreArgumentsCompatible::value), + "Return type of the slot is not compatible with the return type of the signal."); + } static_assert(QtPrivate::HasQ_OBJECT_Macro::Value, "No Q_OBJECT in the class with the signal"); @@ -211,105 +221,26 @@ public: //compilation error if the arguments does not match. static_assert(int(SignalType::ArgumentCount) >= int(SlotType::ArgumentCount), "The slot requires more arguments than the signal provides."); - static_assert((QtPrivate::CheckCompatibleArguments::value), - "Signal and slot arguments are not compatible."); - static_assert((QtPrivate::AreArgumentsCompatible::value), - "Return type of the slot is not compatible with the return type of the signal."); const int *types = nullptr; if (type == Qt::QueuedConnection || type == Qt::BlockingQueuedConnection) types = QtPrivate::ConnectionTypes::types(); - return connectImpl(sender, reinterpret_cast(&signal), - receiver, reinterpret_cast(&slot), - new QtPrivate::QSlotObject::Value, - typename SignalType::ReturnType>(slot), - type, types, &SignalType::Object::staticMetaObject); - } + void **pSlot = nullptr; + if constexpr (std::is_member_function_pointer_v>) + pSlot = const_cast(reinterpret_cast(&slot)); - //connect to a function pointer (not a member) - template - static inline typename std::enable_if::ArgumentCount) >= 0, QMetaObject::Connection>::type - connect(const typename QtPrivate::FunctionPointer::Object *sender, Func1 signal, Func2 slot) - { - return connect(sender, signal, sender, slot, Qt::DirectConnection); - } - - //connect to a function pointer (not a member) - template - static inline typename std::enable_if::ArgumentCount) >= 0 && - !QtPrivate::FunctionPointer::IsPointerToMemberFunction, QMetaObject::Connection>::type - connect(const typename QtPrivate::FunctionPointer::Object *sender, Func1 signal, const QObject *context, Func2 slot, - Qt::ConnectionType type = Qt::AutoConnection) - { - typedef QtPrivate::FunctionPointer SignalType; - typedef QtPrivate::FunctionPointer SlotType; - - static_assert(QtPrivate::HasQ_OBJECT_Macro::Value, - "No Q_OBJECT in the class with the signal"); - - //compilation error if the arguments does not match. - static_assert(int(SignalType::ArgumentCount) >= int(SlotType::ArgumentCount), - "The slot requires more arguments than the signal provides."); - static_assert((QtPrivate::CheckCompatibleArguments::value), - "Signal and slot arguments are not compatible."); - static_assert((QtPrivate::AreArgumentsCompatible::value), - "Return type of the slot is not compatible with the return type of the signal."); - - const int *types = nullptr; - if (type == Qt::QueuedConnection || type == Qt::BlockingQueuedConnection) - types = QtPrivate::ConnectionTypes::types(); - - return connectImpl(sender, reinterpret_cast(&signal), context, nullptr, - new QtPrivate::QStaticSlotObject::Value, - typename SignalType::ReturnType>(slot), + return connectImpl(sender, reinterpret_cast(&signal), context, pSlot, + QtPrivate::makeCallableObject(std::forward(slot)), type, types, &SignalType::Object::staticMetaObject); } - //connect to a functor + //connect without context template - static inline typename std::enable_if< - QtPrivate::FunctionPointer::ArgumentCount == -1 && - !std::is_convertible_v, // don't match old-style connect - QMetaObject::Connection>::type - connect(const typename QtPrivate::FunctionPointer::Object *sender, Func1 signal, Func2 slot) + static inline QMetaObject::Connection + connect(const typename QtPrivate::FunctionPointer::Object *sender, Func1 signal, Func2 &&slot) { - return connect(sender, signal, sender, std::move(slot), Qt::DirectConnection); - } - - //connect to a functor, with a "context" object defining in which event loop is going to be executed - template - static inline typename std::enable_if< - QtPrivate::FunctionPointer::ArgumentCount == -1 && - !std::is_convertible_v, // don't match old-style connect - QMetaObject::Connection>::type - connect(const typename QtPrivate::FunctionPointer::Object *sender, Func1 signal, const QObject *context, Func2 slot, - Qt::ConnectionType type = Qt::AutoConnection) - { - typedef QtPrivate::FunctionPointer SignalType; - const int FunctorArgumentCount = QtPrivate::ComputeFunctorArgumentCount::Value; - - static_assert((FunctorArgumentCount >= 0), - "Signal and slot arguments are not compatible."); - const int SlotArgumentCount = (FunctorArgumentCount >= 0) ? FunctorArgumentCount : 0; - typedef typename QtPrivate::FunctorReturnType::Value>::Value SlotReturnType; - - static_assert((QtPrivate::AreArgumentsCompatible::value), - "Return type of the slot is not compatible with the return type of the signal."); - - static_assert(QtPrivate::HasQ_OBJECT_Macro::Value, - "No Q_OBJECT in the class with the signal"); - - const int *types = nullptr; - if (type == Qt::QueuedConnection || type == Qt::BlockingQueuedConnection) - types = QtPrivate::ConnectionTypes::types(); - - return connectImpl(sender, reinterpret_cast(&signal), context, nullptr, - new QtPrivate::QFunctorSlotObject::Value, - typename SignalType::ReturnType>(std::move(slot)), - type, types, &SignalType::Object::staticMetaObject); + return connect(sender, signal, sender, std::forward(slot), Qt::DirectConnection); } #endif //Q_QDOC @@ -362,7 +293,9 @@ public: void dumpObjectTree() const; void dumpObjectInfo() const; + QT_CORE_INLINE_SINCE(6, 6) bool setProperty(const char *name, const QVariant &value); + inline bool setProperty(const char *name, QVariant &&value); QVariant property(const char *name) const; QList dynamicPropertyNames() const; QBindingStorage *bindingStorage() { return &d_ptr->bindingStorage; } @@ -415,6 +348,8 @@ protected: private: void doSetObjectName(const QString &name); + bool doSetProperty(const char *name, const QVariant *lvalue, QVariant *rvalue); + Q_DISABLE_COPY(QObject) Q_PRIVATE_SLOT(d_func(), void _q_reregisterTimers(void *)) @@ -433,6 +368,17 @@ inline QMetaObject::Connection QObject::connect(const QObject *asender, const ch const char *amember, Qt::ConnectionType atype) const { return connect(asender, asignal, this, amember, atype); } +#if QT_CORE_INLINE_IMPL_SINCE(6, 6) +bool QObject::setProperty(const char *name, const QVariant &value) +{ + return doSetProperty(name, &value, nullptr); +} +#endif // inline since 6.6 +bool QObject::setProperty(const char *name, QVariant &&value) +{ + return doSetProperty(name, &value, &value); +} + template inline T qobject_cast(QObject *object) { @@ -494,10 +440,13 @@ Q_CORE_EXPORT QDebug operator<<(QDebug, const QObject *); class QSignalBlocker { public: + Q_NODISCARD_CTOR inline explicit QSignalBlocker(QObject *o) noexcept; + Q_NODISCARD_CTOR inline explicit QSignalBlocker(QObject &o) noexcept; inline ~QSignalBlocker(); + Q_NODISCARD_CTOR inline QSignalBlocker(QSignalBlocker &&other) noexcept; inline QSignalBlocker &operator=(QSignalBlocker &&other) noexcept; diff --git a/src/corelib/kernel/qobject_impl.h b/src/corelib/kernel/qobject_impl.h index 4d6830a3..b57d7e50 100644 --- a/src/corelib/kernel/qobject_impl.h +++ b/src/corelib/kernel/qobject_impl.h @@ -37,30 +37,6 @@ namespace QtPrivate { { static const int *types() { return nullptr; } }; template struct ConnectionTypes, true> { static const int *types() { static const int t[sizeof...(Args) + 1] = { (QtPrivate::QMetaTypeIdHelper::qt_metatype_id())..., 0 }; return t; } }; - - // implementation of QSlotObjectBase for which the slot is a static function - // Args and R are the List of arguments and the return type of the signal to which the slot is connected. - template class QStaticSlotObject : public QSlotObjectBase - { - typedef QtPrivate::FunctionPointer FuncType; - Func function; - static void impl(int which, QSlotObjectBase *this_, QObject *r, void **a, bool *ret) - { - switch (which) { - case Destroy: - delete static_cast(this_); - break; - case Call: - FuncType::template call(static_cast(this_)->function, r, a); - break; - case Compare: // not implemented - case NumOperations: - Q_UNUSED(ret); - } - } - public: - explicit QStaticSlotObject(Func f) : QSlotObjectBase(&impl), function(f) {} - }; } diff --git a/src/corelib/kernel/qobject_p.h b/src/corelib/kernel/qobject_p.h index 576666eb..41f1c2f8 100644 --- a/src/corelib/kernel/qobject_p.h +++ b/src/corelib/kernel/qobject_p.h @@ -18,18 +18,27 @@ #include #include "QtCore/qcoreevent.h" +#include #include "QtCore/qlist.h" #include "QtCore/qobject.h" #include "QtCore/qpointer.h" -#include "QtCore/qsharedpointer.h" #include "QtCore/qvariant.h" #include "QtCore/qproperty.h" +#include #include "QtCore/private/qproperty_p.h" #include QT_BEGIN_NAMESPACE +#ifdef Q_MOC_RUN +#define QT_ANONYMOUS_PROPERTY(text) QT_ANONYMOUS_PROPERTY(text) +#define QT_ANONYMOUS_PRIVATE_PROPERTY(d, text) QT_ANONYMOUS_PRIVATE_PROPERTY(d, text) +#elif !defined QT_NO_META_MACROS +#define QT_ANONYMOUS_PROPERTY(...) QT_ANNOTATE_CLASS(qt_anonymous_property, __VA_ARGS__) +#define QT_ANONYMOUS_PRIVATE_PROPERTY(d, text) QT_ANNOTATE_CLASS2(qt_anonymous_private_property, d, text) +#endif + class QVariant; class QThreadData; class QObjectConnectionListVector; @@ -103,6 +112,7 @@ public: struct ConnectionOrSignalVector; struct SignalVector; struct Sender; + struct TaggedSignalVector; /* This contains the all connections from and to an object. @@ -246,28 +256,7 @@ namespace QtPrivate { inline const QObject *getQObject(const QObjectPrivate *d) { return d->q_func(); } template -struct FunctionStorageByValue -{ - Func f; - Func &func() noexcept { return f; } -}; - -template -struct FunctionStorageEmptyBaseClassOptimization : Func -{ - Func &func() noexcept { return *this; } - using Func::Func; -}; - -template -using FunctionStorage = typename std::conditional_t< - std::conjunction_v< - std::is_empty, - std::negation> - >, - FunctionStorageEmptyBaseClassOptimization, - FunctionStorageByValue - >; +using FunctionStorage = QtPrivate::CompactStorage; template inline void assertObjectType(QObjectPrivate *d) { @@ -279,18 +268,23 @@ template class QPrivateSlotObject : public QSlotObjectBase, private FunctionStorage { typedef QtPrivate::FunctionPointer FuncType; +#if QT_VERSION < QT_VERSION_CHECK(7, 0, 0) static void impl(int which, QSlotObjectBase *this_, QObject *r, void **a, bool *ret) +#else + static void impl(QSlotObjectBase *this_, QObject *r, void **a, int which, bool *ret) +#endif { + const auto that = static_cast(this_); switch (which) { case Destroy: - delete static_cast(this_); + delete that; break; case Call: - FuncType::template call(static_cast(this_)->func(), + FuncType::template call(that->object(), static_cast(QObjectPrivate::get(r)), a); break; case Compare: - *ret = *reinterpret_cast(a) == static_cast(this_)->func(); + *ret = *reinterpret_cast(a) == that->object(); break; case NumOperations: ; } @@ -457,7 +451,9 @@ class QBoolBlocker { Q_DISABLE_COPY_MOVE(QBoolBlocker) public: - explicit inline QBoolBlocker(bool &b, bool value = true) : block(b), reset(b) { block = value; } + Q_NODISCARD_CTOR explicit QBoolBlocker(bool &b, bool value = true) + : block(b), reset(b) + { block = value; } inline ~QBoolBlocker() { block = reset; } private: diff --git a/src/corelib/kernel/qobject_p_p.h b/src/corelib/kernel/qobject_p_p.h index d79ce7e2..a9bd290d 100644 --- a/src/corelib/kernel/qobject_p_p.h +++ b/src/corelib/kernel/qobject_p_p.h @@ -34,25 +34,35 @@ struct QObjectPrivate::ConnectionList static_assert(std::is_trivially_destructible_v); Q_DECLARE_TYPEINFO(QObjectPrivate::ConnectionList, Q_RELOCATABLE_TYPE); +struct QObjectPrivate::TaggedSignalVector +{ + quintptr c; + + TaggedSignalVector() = default; + TaggedSignalVector(std::nullptr_t) noexcept : c(0) {} + TaggedSignalVector(Connection *v) noexcept : c(reinterpret_cast(v)) { Q_ASSERT(v && (reinterpret_cast(v) & 0x1) == 0); } + TaggedSignalVector(SignalVector *v) noexcept : c(reinterpret_cast(v) | quintptr(1u)) { Q_ASSERT(v); } + explicit operator SignalVector *() const noexcept + { + if (c & 0x1) + return reinterpret_cast(c & ~quintptr(1u)); + return nullptr; + } + explicit operator Connection *() const noexcept + { + return reinterpret_cast(c); + } + operator uintptr_t() const noexcept { return c; } +}; + struct QObjectPrivate::ConnectionOrSignalVector { union { // linked list of orphaned connections that need cleaning up - ConnectionOrSignalVector *nextInOrphanList; + TaggedSignalVector nextInOrphanList; // linked list of connections connected to slots in this object Connection *next; }; - - static SignalVector *asSignalVector(ConnectionOrSignalVector *c) - { - if (reinterpret_cast(c) & 1) - return reinterpret_cast(reinterpret_cast(c) & ~quintptr(1u)); - return nullptr; - } - static Connection *fromSignalVector(SignalVector *v) - { - return reinterpret_cast(reinterpret_cast(v) | quintptr(1u)); - } }; static_assert(std::is_trivial_v); @@ -132,12 +142,12 @@ struct QObjectPrivate::ConnectionData QAtomicPointer signalVector; Connection *senders = nullptr; Sender *currentSender = nullptr; // object currently activating the object - QAtomicPointer orphaned; + std::atomic orphaned = {}; ~ConnectionData() { Q_ASSERT(ref.loadRelaxed() == 0); - auto *c = orphaned.fetchAndStoreRelaxed(nullptr); + TaggedSignalVector c = orphaned.exchange(nullptr, std::memory_order_relaxed); if (c) deleteOrphaned(c); SignalVector *v = signalVector.loadRelaxed(); @@ -159,7 +169,7 @@ struct QObjectPrivate::ConnectionData }; void cleanOrphanedConnections(QObject *sender, LockPolicy lockPolicy = NeedToLock) { - if (orphaned.loadRelaxed() && ref.loadAcquire() == 1) + if (orphaned.load(std::memory_order_relaxed) && ref.loadAcquire() == 1) cleanOrphanedConnectionsImpl(sender, lockPolicy); } void cleanOrphanedConnectionsImpl(QObject *sender, LockPolicy lockPolicy); @@ -194,15 +204,14 @@ struct QObjectPrivate::ConnectionData signalVector.storeRelaxed(newVector); if (vector) { - Connection *o = nullptr; + TaggedSignalVector o = nullptr; /* No ABA issue here: When adding a node, we only care about the list head, it doesn't * matter if the tail changes. */ + o = orphaned.load(std::memory_order_acquire); do { - o = orphaned.loadRelaxed(); vector->nextInOrphanList = o; - } while (!orphaned.testAndSetRelease( - o, ConnectionOrSignalVector::fromSignalVector(vector))); + } while (!orphaned.compare_exchange_strong(o, TaggedSignalVector(vector), std::memory_order_release)); } } int signalVectorCount() const @@ -210,7 +219,7 @@ struct QObjectPrivate::ConnectionData return signalVector.loadAcquire() ? signalVector.loadRelaxed()->count() : -1; } - static void deleteOrphaned(ConnectionOrSignalVector *c); + static void deleteOrphaned(TaggedSignalVector o); }; struct QObjectPrivate::Sender diff --git a/src/corelib/kernel/qobjectcleanuphandler.cpp b/src/corelib/kernel/qobjectcleanuphandler.cpp index aae93ecc..f46afc2f 100644 --- a/src/corelib/kernel/qobjectcleanuphandler.cpp +++ b/src/corelib/kernel/qobjectcleanuphandler.cpp @@ -60,7 +60,7 @@ QObject *QObjectCleanupHandler::add(QObject *object) if (!object) return nullptr; - connect(object, SIGNAL(destroyed(QObject *)), this, SLOT(objectDestroyed(QObject *))); + connect(object, SIGNAL(destroyed(QObject*)), this, SLOT(objectDestroyed(QObject*))); cleanupObjects.insert(0, object); return object; } @@ -76,7 +76,7 @@ void QObjectCleanupHandler::remove(QObject *object) int index; if ((index = cleanupObjects.indexOf(object)) != -1) { cleanupObjects.removeAt(index); - disconnect(object, SIGNAL(destroyed(QObject *)), this, SLOT(objectDestroyed(QObject *))); + disconnect(object, SIGNAL(destroyed(QObject*)), this, SLOT(objectDestroyed(QObject*))); } } diff --git a/src/corelib/kernel/qobjectdefs.h b/src/corelib/kernel/qobjectdefs.h index 5217b0d7..42f02b97 100644 --- a/src/corelib/kernel/qobjectdefs.h +++ b/src/corelib/kernel/qobjectdefs.h @@ -406,81 +406,34 @@ struct Q_CORE_EXPORT QMetaObject #ifdef Q_QDOC template - static bool invokeMethod(QObject *context, Functor function, Qt::ConnectionType type = Qt::AutoConnection, FunctorReturnType *ret = nullptr); + static bool invokeMethod(QObject *context, Functor &&function, Qt::ConnectionType type = Qt::AutoConnection, FunctorReturnType *ret = nullptr); template - static bool invokeMethod(QObject *context, Functor function, FunctorReturnType *ret); + static bool invokeMethod(QObject *context, Functor &&function, FunctorReturnType *ret); #else - - // invokeMethod() for member function pointer template - static typename std::enable_if::IsPointerToMemberFunction - && !std::is_convertible::value - && QtPrivate::FunctionPointer::ArgumentCount == 0, bool>::type - invokeMethod(typename QtPrivate::FunctionPointer::Object *object, - Func function, + static std::enable_if_t, + QtPrivate::Invoke::AreOldStyleArgs>, + bool> + invokeMethod(typename QtPrivate::ContextTypeForFunctor::ContextType *object, + Func &&function, Qt::ConnectionType type = Qt::AutoConnection, - typename QtPrivate::FunctionPointer::ReturnType *ret = nullptr) + typename QtPrivate::Callable::ReturnType *ret = nullptr) { - return invokeMethodImpl(object, new QtPrivate::QSlotObjectWithNoArgs(function), type, ret); + static_assert(QtPrivate::Callable::ArgumentCount <= 0, + "QMetaObject::invokeMethod cannot call functions with arguments!"); + using Prototype = typename QtPrivate::Callable::Function; + return invokeMethodImpl(object, QtPrivate::makeCallableObject(std::forward(function)), type, ret); } template - static typename std::enable_if::IsPointerToMemberFunction - && !std::is_convertible::value - && QtPrivate::FunctionPointer::ArgumentCount == 0, bool>::type - invokeMethod(typename QtPrivate::FunctionPointer::Object *object, - Func function, - typename QtPrivate::FunctionPointer::ReturnType *ret) + static std::enable_if_t, + QtPrivate::Invoke::AreOldStyleArgs>, + bool> + invokeMethod(typename QtPrivate::ContextTypeForFunctor::ContextType *object, + Func &&function, + typename QtPrivate::Callable::ReturnType *ret) { - return invokeMethodImpl(object, new QtPrivate::QSlotObjectWithNoArgs(function), Qt::AutoConnection, ret); - } - - // invokeMethod() for function pointer (not member) - template - static typename std::enable_if::IsPointerToMemberFunction - && !std::is_convertible::value - && QtPrivate::FunctionPointer::ArgumentCount == 0, bool>::type - invokeMethod(QObject *context, Func function, - Qt::ConnectionType type = Qt::AutoConnection, - typename QtPrivate::FunctionPointer::ReturnType *ret = nullptr) - { - return invokeMethodImpl(context, new QtPrivate::QFunctorSlotObjectWithNoArgsImplicitReturn(function), type, ret); - } - - template - static typename std::enable_if::IsPointerToMemberFunction - && !std::is_convertible::value - && QtPrivate::FunctionPointer::ArgumentCount == 0, bool>::type - invokeMethod(QObject *context, Func function, - typename QtPrivate::FunctionPointer::ReturnType *ret) - { - return invokeMethodImpl(context, new QtPrivate::QFunctorSlotObjectWithNoArgsImplicitReturn(function), Qt::AutoConnection, ret); - } - - // invokeMethod() for Functor - template - static typename std::enable_if::IsPointerToMemberFunction - && QtPrivate::FunctionPointer::ArgumentCount == -1 - && !std::is_convertible::value, bool>::type - invokeMethod(QObject *context, Func function, - Qt::ConnectionType type = Qt::AutoConnection, decltype(function()) *ret = nullptr) - { - return invokeMethodImpl(context, - new QtPrivate::QFunctorSlotObjectWithNoArgs(std::move(function)), - type, - ret); - } - - template - static typename std::enable_if::IsPointerToMemberFunction - && QtPrivate::FunctionPointer::ArgumentCount == -1 - && !std::is_convertible::value, bool>::type - invokeMethod(QObject *context, Func function, decltype(function()) *ret) - { - return invokeMethodImpl(context, - new QtPrivate::QFunctorSlotObjectWithNoArgs(std::move(function)), - Qt::AutoConnection, - ret); + return invokeMethod(object, std::forward(function), Qt::AutoConnection, ret); } #endif @@ -521,7 +474,8 @@ struct Q_CORE_EXPORT QMetaObject RegisterPropertyMetaType, RegisterMethodArgumentMetaType, BindableProperty, - CustomCall + CustomCall, + ConstructInPlace, }; int static_metacall(Call, int, void **) const; @@ -614,7 +568,7 @@ inline const QMetaObject *QMetaObject::superClass() const { return d.superdata; } namespace QtPrivate { - /* Trait that tells is a the Object has a Q_OBJECT macro */ + // Trait that tells if a QObject has a Q_OBJECT macro template struct HasQ_OBJECT_Macro { template static char test(int (T::*)(QMetaObject::Call, int, void **)); diff --git a/src/corelib/kernel/qobjectdefs_impl.h b/src/corelib/kernel/qobjectdefs_impl.h index b95c1e69..a45792c5 100644 --- a/src/corelib/kernel/qobjectdefs_impl.h +++ b/src/corelib/kernel/qobjectdefs_impl.h @@ -12,11 +12,15 @@ #pragma qt_sync_stop_processing #endif +#include + #include QT_BEGIN_NAMESPACE class QObject; class QObjectPrivate; +class QMetaMethod; +class QByteArray; namespace QtPrivate { template struct RemoveRef { typedef T Type; }; @@ -31,23 +35,27 @@ namespace QtPrivate { the list composed of the first N element of the list */ // With variadic template, lists are represented using a variadic template argument instead of the lisp way - template struct List {}; - template struct List { typedef Head Car; typedef List Cdr; }; + template struct List { static constexpr size_t size = sizeof...(Ts); }; + template struct SizeOfList { static constexpr size_t value = 1; }; + template<> struct SizeOfList> { static constexpr size_t value = 0; }; + template struct SizeOfList> { static constexpr size_t value = List::size; }; + template struct List { + static constexpr size_t size = 1 + sizeof...(Tail); + typedef Head Car; typedef List Cdr; + }; template struct List_Append; template struct List_Append, List> { typedef List Value; }; template struct List_Left { typedef typename List_Append,typename List_Left::Value>::Value Value; }; template struct List_Left { typedef List<> Value; }; - // List_Select returns (via typedef Value) the Nth element of the list L - template struct List_Select { typedef typename List_Select::Value Value; }; - template struct List_Select { typedef typename L::Car Value; }; /* - trick to set the return value of a slot that works even if the signal or the slot returns void - to be used like function(), ApplyReturnValue(&return_value) + Trick to set the return value of a slot that works even if the signal or the slot returns void + to be used like + function(), ApplyReturnValue(&return_value) if function() returns a value, the operator,(T, ApplyReturnValue) is called, but if it - returns void, the builtin one is used without an error. + returns void, the built-in one is used without an error. */ template struct ApplyReturnValue { @@ -77,7 +85,7 @@ namespace QtPrivate { and args is the array of pointer to arguments, as used in qt_metacall The Functor struct is the helper to call a functor of N argument. - its call function is the same as the FunctionPointer::call function. + Its call function is the same as the FunctionPointer::call function. */ template using InvokeGenSeq = typename T::Type; @@ -236,14 +244,6 @@ namespace QtPrivate { } }; - template struct Functor - { - template - static void call(Function &f, void *, void **arg) { - FunctorCall::Value, SignalArgs, R, Function>::call(f, arg); - } - }; - // Traits to detect if there is a conversion between two types, // and that conversion does not include a narrowing conversion. template @@ -277,10 +277,9 @@ namespace QtPrivate { static_assert(CheckCompatibleArguments::Arguments, FunctionPointer::Arguments>::value) */ template struct AreArgumentsCompatible { - static int test(const typename RemoveRef::Type&); + static int test(const std::remove_reference_t&); static char test(...); - static const typename RemoveRef::Type &dummy(); - enum { value = sizeof(test(dummy())) == sizeof(int) }; + enum { value = sizeof(test(std::declval>())) == sizeof(int) }; #ifdef QT_NO_NARROWING_CONVERSIONS_IN_CONNECT using AreArgumentsConvertibleWithoutNarrowing = AreArgumentsConvertibleWithoutNarrowingBase, std::decay_t>; static_assert(AreArgumentsConvertibleWithoutNarrowing::value, "Signal and slot arguments are not compatible (narrowing)"); @@ -319,11 +318,10 @@ namespace QtPrivate { template struct ComputeFunctorArgumentCount> { - template static D dummy(); - template static auto test(F f) -> decltype(((f.operator()((dummy())...)), int())); + template static auto test(F f) -> decltype(((f.operator()((std::declval())...)), int())); static char test(...); enum { - Ok = sizeof(test(dummy())) == sizeof(int), + Ok = sizeof(test(std::declval())) == sizeof(int), Value = Ok ? int(sizeof...(ArgList)) : int(ComputeFunctorArgumentCountHelper, Ok>::Value) }; }; @@ -331,19 +329,79 @@ namespace QtPrivate { /* get the return type of a functor, given the signal argument list */ template struct FunctorReturnType; template struct FunctorReturnType> { - template static D dummy(); - typedef decltype(dummy().operator()((dummy())...)) Value; + typedef decltype(std::declval().operator()((std::declval())...)) Value; }; + template struct Functor + { + template + static void call(Function &f, void *, void **arg) { + FunctorCall::Value, SignalArgs, R, Function>::call(f, arg); + } + }; + + template + struct ZeroArgFunctor : Functor + { + using ReturnType = decltype(std::declval()()); + using Function = ReturnType(*)(); + enum {ArgumentCount = 0}; + using Arguments = QtPrivate::List<>; + }; + + template + using Callable = std::conditional_t>::ArgumentCount == -1, + ZeroArgFunctor>, + FunctionPointer> + >; + + /* + Wrapper around ComputeFunctorArgumentCount and CheckCompatibleArgument, + depending on whether \a Functor is a PMF or not. Returns -1 if \a Func is + not compatible with the \a ExpectedArguments, otherwise returns >= 0. + */ + template + inline constexpr std::enable_if_t, + std::is_same, QMetaMethod>, + std::is_convertible, + std::is_same, QMetaMethod> + >, + int> + countMatchingArguments() + { + using ExpectedArguments = typename QtPrivate::FunctionPointer::Arguments; + using Actual = std::decay_t; + + if constexpr (QtPrivate::FunctionPointer::IsPointerToMemberFunction + || QtPrivate::FunctionPointer::ArgumentCount >= 0) { + // PMF or free function + using ActualArguments = typename QtPrivate::FunctionPointer::Arguments; + if constexpr (QtPrivate::CheckCompatibleArguments::value) + return QtPrivate::FunctionPointer::ArgumentCount; + else + return -1; + } else { + // lambda or functor + return QtPrivate::ComputeFunctorArgumentCount::Value; + } + } + // internal base class (interface) containing functions required to call a slot managed by a pointer to function. - class QSlotObjectBase { - QAtomicInt m_ref; - // don't use virtual functions here; we don't want the + class QSlotObjectBase + { + // Don't use virtual functions here; we don't want the // compiler to create tons of per-polymorphic-class stuff that // we'll never need. We just use one function pointer, and the // Operations enum below to distinguish requests +#if QT_VERSION < QT_VERSION_CHECK(7, 0, 0) + QAtomicInt m_ref = 1; typedef void (*ImplFn)(int which, QSlotObjectBase* this_, QObject *receiver, void **args, bool *ret); const ImplFn m_impl; +#else + using ImplFn = void (*)(QSlotObjectBase* this_, QObject *receiver, void **args, int which, bool *ret); + const ImplFn m_impl; + QAtomicInt m_ref = 1; +#endif protected: // The operations that can be requested by calls to m_impl, // see the member functions that call m_impl below for details @@ -355,7 +413,7 @@ namespace QtPrivate { NumOperations }; public: - explicit QSlotObjectBase(ImplFn fn) : m_ref(1), m_impl(fn) {} + explicit QSlotObjectBase(ImplFn fn) : m_impl(fn) {} // A custom deleter compatible with std protocols (op()()) we well as // the legacy QScopedPointer protocol (cleanup()). @@ -367,11 +425,24 @@ namespace QtPrivate { }; bool ref() noexcept { return m_ref.ref(); } +#if QT_VERSION < QT_VERSION_CHECK(7, 0, 0) inline void destroyIfLastRef() noexcept { if (!m_ref.deref()) m_impl(Destroy, this, nullptr, nullptr, nullptr); } inline bool compare(void **a) { bool ret = false; m_impl(Compare, this, nullptr, a, &ret); return ret; } - inline void call(QObject *r, void **a) { m_impl(Call, this, r, a, nullptr); } + inline void call(QObject *r, void **a) { m_impl(Call, this, r, a, nullptr); } +#else + inline void destroyIfLastRef() noexcept + { if (!m_ref.deref()) m_impl(this, nullptr, nullptr, Destroy, nullptr); } + + inline bool compare(void **a) + { + bool ret = false; + m_impl(this, nullptr, a, Compare, &ret); + return ret; + } + inline void call(QObject *r, void **a) { m_impl(this, r, a, Call, nullptr); } +#endif bool isImpl(ImplFn f) const { return m_impl == f; } protected: ~QSlotObjectBase() {} @@ -391,20 +462,20 @@ namespace QtPrivate { class SlotObjSharedPtr { SlotObjUniquePtr obj; public: - Q_IMPLICIT SlotObjSharedPtr() noexcept = default; - Q_IMPLICIT SlotObjSharedPtr(std::nullptr_t) noexcept : SlotObjSharedPtr() {} - explicit SlotObjSharedPtr(SlotObjUniquePtr o) + Q_NODISCARD_CTOR Q_IMPLICIT SlotObjSharedPtr() noexcept = default; + Q_NODISCARD_CTOR Q_IMPLICIT SlotObjSharedPtr(std::nullptr_t) noexcept : SlotObjSharedPtr() {} + Q_NODISCARD_CTOR explicit SlotObjSharedPtr(SlotObjUniquePtr o) : obj(std::move(o)) { // does NOT ref() (takes unique_ptr by value) // (that's why (QSlotObjectBase*) ctor doesn't exisit: don't know whether that one _should_) } - SlotObjSharedPtr(const SlotObjSharedPtr &other) noexcept + Q_NODISCARD_CTOR SlotObjSharedPtr(const SlotObjSharedPtr &other) noexcept : obj{copy(other.obj)} {} SlotObjSharedPtr &operator=(const SlotObjSharedPtr &other) noexcept { auto copy = other; swap(copy); return *this; } - SlotObjSharedPtr(SlotObjSharedPtr &&other) noexcept = default; + Q_NODISCARD_CTOR SlotObjSharedPtr(SlotObjSharedPtr &&other) noexcept = default; SlotObjSharedPtr &operator=(SlotObjSharedPtr &&other) noexcept = default; ~SlotObjSharedPtr() = default; @@ -416,66 +487,125 @@ namespace QtPrivate { explicit operator bool() const noexcept { return bool(obj); } }; - // implementation of QSlotObjectBase for which the slot is a pointer to member function of a QObject + + // Implementation of QSlotObjectBase for which the slot is a callable (function, PMF, functor, or lambda). // Args and R are the List of arguments and the return type of the signal to which the slot is connected. - template class QSlotObject : public QSlotObjectBase + template + class QCallableObject : public QSlotObjectBase, + private QtPrivate::CompactStorage> { - typedef QtPrivate::FunctionPointer FuncType; - Func function; - static void impl(int which, QSlotObjectBase *this_, QObject *r, void **a, bool *ret) + using FunctorValue = std::decay_t; + using Storage = QtPrivate::CompactStorage; + using FuncType = std::conditional_t, + QtPrivate::FunctionPointer, + QtPrivate::Functor + >; + +#if QT_VERSION < QT_VERSION_CHECK(7, 0, 0) + Q_DECL_HIDDEN static void impl(int which, QSlotObjectBase *this_, QObject *r, void **a, bool *ret) +#else + // Design note: the first three arguments match those for typical Call + // and Destroy uses. We return void to enable tail call optimization + // for those too. + Q_DECL_HIDDEN static void impl(QSlotObjectBase *this_, QObject *r, void **a, int which, bool *ret) +#endif { + const auto that = static_cast(this_); switch (which) { case Destroy: - delete static_cast(this_); + delete that; break; case Call: - FuncType::template call(static_cast(this_)->function, static_cast(r), a); + if constexpr (std::is_member_function_pointer_v) + FuncType::template call(that->object(), static_cast(r), a); + else + FuncType::template call(that->object(), r, a); break; case Compare: - *ret = *reinterpret_cast(a) == static_cast(this_)->function; - break; - case NumOperations: ; - } - } - public: - explicit QSlotObject(Func f) : QSlotObjectBase(&impl), function(f) {} - }; - // implementation of QSlotObjectBase for which the slot is a functor (or lambda) - // N is the number of arguments - // Args and R are the List of arguments and the return type of the signal to which the slot is connected. - template class QFunctorSlotObject : public QSlotObjectBase - { - typedef QtPrivate::Functor FuncType; - Func function; - static void impl(int which, QSlotObjectBase *this_, QObject *r, void **a, bool *ret) - { - switch (which) { - case Destroy: - delete static_cast(this_); - break; - case Call: - FuncType::template call(static_cast(this_)->function, r, a); - break; - case Compare: // not implemented + if constexpr (std::is_member_function_pointer_v) { + *ret = *reinterpret_cast(a) == that->object(); + break; + } + // not implemented otherwise + Q_FALLTHROUGH(); case NumOperations: Q_UNUSED(ret); } } public: - explicit QFunctorSlotObject(Func f) : QSlotObjectBase(&impl), function(std::move(f)) {} + explicit QCallableObject(Func &&f) : QSlotObjectBase(&impl), Storage{std::move(f)} {} + explicit QCallableObject(const Func &f) : QSlotObjectBase(&impl), Storage{f} {} }; - // typedefs for readability for when there are no parameters - template - using QSlotObjectWithNoArgs = QSlotObject, - typename QtPrivate::FunctionPointer::ReturnType>; - - template - using QFunctorSlotObjectWithNoArgs = QFunctorSlotObject, R>; + // Helper to detect the context object type based on the functor type: + // QObject for free functions and lambdas; the callee for member function + // pointers. The default declaration doesn't have the ContextType typedef, + // and so non-functor APIs (like old-style string-based slots) are removed + // from the overload set. + template + struct ContextTypeForFunctor {}; template - using QFunctorSlotObjectWithNoArgsImplicitReturn = QFunctorSlotObjectWithNoArgs::ReturnType>; + struct ContextTypeForFunctor, + std::is_member_function_pointer + > + > + > + { + using ContextType = QObject; + }; + template + struct ContextTypeForFunctor>, + std::is_member_function_pointer, + std::is_convertible::Object *, QObject *> + > + > + > + { + using ContextType = typename QtPrivate::FunctionPointer::Object; + }; + + /* + Returns a suitable QSlotObjectBase object that holds \a func, if possible. + + Not available (and thus produces compile-time errors) if the Functor provided is + not compatible with the expected Prototype. + */ + template + static constexpr std::enable_if_t() >= 0, + QtPrivate::QSlotObjectBase *> + makeCallableObject(Functor &&func) + { + using ExpectedSignature = QtPrivate::FunctionPointer; + using ExpectedReturnType = typename ExpectedSignature::ReturnType; + using ExpectedArguments = typename ExpectedSignature::Arguments; + + using ActualSignature = QtPrivate::FunctionPointer; + constexpr int MatchingArgumentCount = QtPrivate::countMatchingArguments(); + using ActualArguments = typename QtPrivate::List_Left::Value; + + static_assert(int(ActualSignature::ArgumentCount) <= int(ExpectedSignature::ArgumentCount), + "Functor requires more arguments than what can be provided."); + + return new QtPrivate::QCallableObject, ActualArguments, ExpectedReturnType>(std::forward(func)); + } + + template + struct AreFunctionsCompatible : std::false_type {}; + template + struct AreFunctionsCompatible(std::forward(std::declval()))), + QtPrivate::QSlotObjectBase *>> + > : std::true_type {}; + + template + inline constexpr bool AssertCompatibleFunctions() { + static_assert(AreFunctionsCompatible::value, + "Functor is not compatible with expected prototype!"); + return true; + } } QT_END_NAMESPACE diff --git a/src/corelib/kernel/qpermissions.cpp b/src/corelib/kernel/qpermissions.cpp index 83e12ebc..46b989c5 100644 --- a/src/corelib/kernel/qpermissions.cpp +++ b/src/corelib/kernel/qpermissions.cpp @@ -46,7 +46,6 @@ Q_LOGGING_CATEGORY(lcPermissions, "qt.permissions", QtWarningMsg); \code void VoiceMemoWidget::onRecordingInitiated() { - #if QT_CONFIG(permissions) QMicrophonePermission microphonePermission; switch (qApp->checkPermission(microphonePermission)) { case Qt::PermissionStatus::Undetermined: @@ -57,10 +56,8 @@ Q_LOGGING_CATEGORY(lcPermissions, "qt.permissions", QtWarningMsg); m_permissionInstructionsDialog->show(); return; case Qt::PermissionStatus::Granted: - break; // Proceed + m_microphone->startRecording(); } - #endif - m_microphone->startRecording(); } \endcode @@ -76,9 +73,6 @@ Q_LOGGING_CATEGORY(lcPermissions, "qt.permissions", QtWarningMsg); why we can not record voice memos at this time (if the permission was denied), or proceed to using the microphone (if permission was granted). - The use of the \c{QT_CONFIG(permissions)} macro ensures that the code - will work as before on platforms where permissions are not available. - \note On \macOS and iOS permissions can currently only be requested for GUI applications. @@ -361,16 +355,86 @@ QT_PERMISSION_IMPL_COMMON(QMicrophonePermission) \row \li Android \li \l{android-uses-permission}{\c{uses-permission}} - \li \c android.permission.BLUETOOTH + \li Up to Android 11 (API Level < 31): + \list + \li \c android.permission.BLUETOOTH + \li \c android.permission.ACCESS_FINE_LOCATION + \endlist + + Starting from Android 12 (API Level >= 31): + \list + \li \c android.permission.BLUETOOTH_ADVERTISE + \li \c android.permission.BLUETOOTH_CONNECT + \li \c android.permission.BLUETOOTH_SCAN + \li \c android.permission.ACCESS_FINE_LOCATION + \endlist \include permissions.qdocinc end-usage-declarations + \note Currently on Android the \c android.permission.ACCESS_FINE_LOCATION + permission is requested together with Bluetooth permissions. This is + required for Bluetooth to work properly, unless the application provides a + strong assertion in the application manifest that it does not use Bluetooth + to derive a physical location. This permission coupling may change in + future. + \include permissions.qdocinc permission-metadata */ QT_PERMISSION_IMPL_COMMON(QBluetoothPermission) - : u{} // stateless, atm + : u{ShortData{CommunicationMode::Default, {}}} {} +/*! + \enum QBluetoothPermission::CommunicationMode + \since 6.6 + + This enum is used to control the allowed Bluetooth communication modes. + + \value Access Allow this device to access other Bluetooth devices. This + includes scanning for nearby devices and connecting to them. + \value Advertise Allow other Bluetooth devices to discover this device. + \value Default This configuration is used by default. + + \note The fine-grained permissions are currently supported only on + Android 12 and newer. On older Android versions, as well as on Apple + operating systems, any mode results in full Bluetooth access. + + \note For now the \c Access mode on Android also requests the + \l {QLocationPermission::Precise}{precise location} permission. + This permission coupling may change in the future. +*/ + +/*! + \since 6.6 + + Sets the allowed Bluetooth communication modes to \a modes. + + \note A default-constructed instance of \l {QBluetoothPermission::} + {CommunicationModes} has no sense, so an attempt to set such a mode will + raise a \c {qWarning()} and fall back to using the + \l {QBluetoothPermission::}{Default} mode. +*/ +void QBluetoothPermission::setCommunicationModes(CommunicationModes modes) +{ + if (modes == CommunicationModes{}) { + qCWarning(lcPermissions, "QBluetoothPermission: trying to set an invalid empty mode. " + "Falling back to CommunicationMode::Default."); + u.data.mode = Default; + } else { + u.data.mode = static_cast(modes.toInt()); + } +} + +/*! + \since 6.6 + + Returns the allowed Bluetooth communication modes. +*/ +QBluetoothPermission::CommunicationModes QBluetoothPermission::communicationModes() const +{ + return u.data.mode; +} + /*! \class QLocationPermission \brief Access the user's location. @@ -602,6 +666,28 @@ QDebug operator<<(QDebug debug, const QPermission &permission) #undef QT_PERMISSION_IMPL_COMMON +#if !defined(Q_OS_DARWIN) && !defined(Q_OS_ANDROID) && !defined(Q_OS_WASM) +// Default backend for platforms without a permission implementation. +// Always returns Granted, to match behavior when not using permission APIs +// https://bugreports.qt.io/browse/QTBUG-90498?focusedCommentId=725085#comment-725085 +namespace QPermissions::Private +{ + Qt::PermissionStatus checkPermission(const QPermission &permission) + { + qCDebug(lcPermissions) << "No permission backend on this platform." + << "Optimistically returning Granted for" << permission; + return Qt::PermissionStatus::Granted; + } + + void requestPermission(const QPermission &permission, const PermissionCallback &callback) + { + qCDebug(lcPermissions) << "No permission backend on this platform." + << "Optimistically returning Granted for" << permission; + callback(Qt::PermissionStatus::Granted); + } +} +#endif + QT_END_NAMESPACE #include "moc_qpermissions.cpp" diff --git a/src/corelib/kernel/qpermissions.h b/src/corelib/kernel/qpermissions.h index 22c5be5b..9573e377 100644 --- a/src/corelib/kernel/qpermissions.h +++ b/src/corelib/kernel/qpermissions.h @@ -173,6 +173,32 @@ private: }; Q_DECLARE_SHARED(QContactsPermission) +class QBluetoothPermissionPrivate; +class QBluetoothPermission +{ + Q_GADGET_EXPORT(Q_CORE_EXPORT) +public: + enum CommunicationMode : quint8 { + Access = 0x01, + Advertise = 0x02, + Default = Access | Advertise, + }; + Q_DECLARE_FLAGS(CommunicationModes, CommunicationMode) + Q_FLAG(CommunicationModes) + + Q_CORE_EXPORT void setCommunicationModes(CommunicationModes modes); + Q_CORE_EXPORT CommunicationModes communicationModes() const; + +private: + struct ShortData { + CommunicationMode mode; + char reserved[sizeof(void*) - sizeof(mode)]; + }; + QT_PERMISSION(QBluetoothPermission) +}; +Q_DECLARE_OPERATORS_FOR_FLAGS(QBluetoothPermission::CommunicationModes) +Q_DECLARE_SHARED(QBluetoothPermission) + #define Q_DECLARE_MINIMAL_PERMISSION(ClassName) \ class ClassName##Private; \ class ClassName \ @@ -184,7 +210,6 @@ Q_DECLARE_SHARED(QContactsPermission) Q_DECLARE_MINIMAL_PERMISSION(QCameraPermission) Q_DECLARE_MINIMAL_PERMISSION(QMicrophonePermission) -Q_DECLARE_MINIMAL_PERMISSION(QBluetoothPermission) #undef QT_PERMISSION #undef Q_DECLARE_MINIMAL_PERMISSION diff --git a/src/corelib/kernel/qpermissions_android.cpp b/src/corelib/kernel/qpermissions_android.cpp index 7601e896..6c21ded7 100644 --- a/src/corelib/kernel/qpermissions_android.cpp +++ b/src/corelib/kernel/qpermissions_android.cpp @@ -49,6 +49,34 @@ static QStringList nativeLocationPermission(const QLocationPermission &permissio return nativeLocationPermissionList; } +static QStringList nativeBluetoothPermission(const QBluetoothPermission &permission) +{ + // See https://developer.android.com/guide/topics/connectivity/bluetooth/permissions + // for the details. + + // API Level < 31 + static QString bluetoothGeneral = u"android.permission.BLUETOOTH"_s; + // API Level >= 31 + static QString bluetoothScan = u"android.permission.BLUETOOTH_SCAN"_s; + static QString bluetoothAdvertise = u"android.permission.BLUETOOTH_ADVERTISE"_s; + static QString bluetoothConnect = u"android.permission.BLUETOOTH_CONNECT"_s; + // Fine location is currently required for ALL API levels, but that is not + // strictly necessary for API Level >= 31. See QTBUG-112164. + static QString fineLocation = u"android.permission.ACCESS_FINE_LOCATION"_s; + + if (QtAndroidPrivate::androidSdkVersion() < 31) { + return {bluetoothGeneral, fineLocation}; + } else { + const auto modes = permission.communicationModes(); + QStringList permissionList; + if (modes & QBluetoothPermission::Advertise) + permissionList << bluetoothAdvertise; + if (modes & QBluetoothPermission::Access) + permissionList << bluetoothScan << bluetoothConnect << fineLocation; + return permissionList; + } +} + static QStringList nativeStringsFromPermission(const QPermission &permission) { const auto id = permission.type().id(); @@ -59,8 +87,7 @@ static QStringList nativeStringsFromPermission(const QPermission &permission) } else if (id == qMetaTypeId()) { return { u"android.permission.RECORD_AUDIO"_s }; } else if (id == qMetaTypeId()) { - // TODO: handle Android 12 new bluetooth permissions - return { u"android.permission.BLUETOOTH"_s }; + return nativeBluetoothPermission(*permission.value()); } else if (id == qMetaTypeId()) { const auto readContactsString = u"android.permission.READ_CONTACTS"_s; switch (permission.value()->accessMode()) { diff --git a/src/corelib/kernel/qpointer.cpp b/src/corelib/kernel/qpointer.cpp index aebdd684..e426c63a 100644 --- a/src/corelib/kernel/qpointer.cpp +++ b/src/corelib/kernel/qpointer.cpp @@ -97,6 +97,31 @@ pointed to. */ +/*! + \fn template template QPointer::QPointer(QPointer &&other) + \fn template template QPointer::QPointer(const QPointer &other) + \since 6.6 + + Conversion constructor. Constructs a new QPointer by moving or copying from + \a other. + + The moved-from QPointer is reset to nullptr. + + \note These constructors participate in overload resolution only if \c{X*} + is convertible to \c{T*}. +*/ + +/*! + \fn template template QPointer &QPointer::operator=(const QPointer &other) + \since 6.6 + + Conversion assignment operator. Makes this guarded pointer guard the + same object guarded by \a other. + + \note This operator participates in overload resolution only if \c{X*} + is convertible to \c{T*}. +*/ + /*! \fn template void QPointer::swap(QPointer &other) \since 5.6 diff --git a/src/corelib/kernel/qpointer.h b/src/corelib/kernel/qpointer.h index 2c824411..7de159a5 100644 --- a/src/corelib/kernel/qpointer.h +++ b/src/corelib/kernel/qpointer.h @@ -18,15 +18,38 @@ class QPointer { static_assert(!std::is_pointer::value, "QPointer's template type must not be a pointer type"); + template + using if_convertible = std::enable_if_t, bool>; + template + friend class QPointer; + using QObjectType = typename std::conditional::value, const QObject, QObject>::type; QWeakPointer wp; public: + Q_NODISCARD_CTOR QPointer() = default; + Q_NODISCARD_CTOR inline QPointer(T *p) : wp(p, true) { } // compiler-generated copy/move ctor/assignment operators are fine! // compiler-generated dtor is fine! + template = true> + Q_NODISCARD_CTOR + QPointer(QPointer &&other) noexcept + : wp(std::exchange(other.wp, nullptr).internalData(), true) {} + template = true> + Q_NODISCARD_CTOR + QPointer(const QPointer &other) noexcept + : wp(other.wp.internalData(), true) {} + + template = true> + QPointer &operator=(const QPointer &other) + { + wp.assign(other.data()); + return *this; + } + #ifdef Q_QDOC // Stop qdoc from complaining about missing function ~QPointer(); diff --git a/src/corelib/kernel/qproperty.cpp b/src/corelib/kernel/qproperty.cpp index 40ad4e32..b6e4caa7 100644 --- a/src/corelib/kernel/qproperty.cpp +++ b/src/corelib/kernel/qproperty.cpp @@ -161,7 +161,7 @@ struct QPropertyDelayedNotifications delayed->d_ptr = 0; if (observer) - observer.notify(delayed->propertyData); + observer.notify(delayed->propertyData); } }; @@ -185,7 +185,7 @@ Q_CONSTINIT static thread_local QBindingStatus bindingStatus; properties need to be updated, preventing any external observer from noticing an inconsistent state. - \sa Qt::endPropertyUpdateGroup + \sa Qt::endPropertyUpdateGroup, QScopedPropertyUpdateGroup */ void Qt::beginPropertyUpdateGroup() { @@ -205,7 +205,7 @@ void Qt::beginPropertyUpdateGroup() \warning Calling endPropertyUpdateGroup without a preceding call to beginPropertyUpdateGroup results in undefined behavior. - \sa Qt::beginPropertyUpdateGroup + \sa Qt::beginPropertyUpdateGroup, QScopedPropertyUpdateGroup */ void Qt::endPropertyUpdateGroup() { @@ -235,12 +235,45 @@ void Qt::endPropertyUpdateGroup() while (data) { for (qsizetype i = 0; i < data->used; ++i) data->notify(i); - auto *next = data->next; - delete data; - data = next; + delete std::exchange(data, data->next); } } +/*! + \since 6.6 + \class QScopedPropertyUpdateGroup + \inmodule QtCore + \ingroup tools + \brief RAII class around Qt::beginPropertyUpdateGroup()/Qt::endPropertyUpdateGroup(). + + This class calls Qt::beginPropertyUpdateGroup() in its constructor and + Qt::endPropertyUpdateGroup() in its destructor, making sure the latter + function is reliably called even in the presence of early returns or thrown + exceptions. + + \note Qt::endPropertyUpdateGroup() may re-throw exceptions thrown by + binding evaluations. This means your application may crash + (\c{std::terminate()} called) if another exception is causing + QScopedPropertyUpdateGroup's destructor to be called during stack + unwinding. If you expect exceptions from binding evaluations, use manual + Qt::endPropertyUpdateGroup() calls and \c{try}/\c{catch} blocks. + + \sa QProperty +*/ + +/*! + \fn QScopedPropertyUpdateGroup::QScopedPropertyUpdateGroup() + + Calls Qt::beginPropertyUpdateGroup(). +*/ + +/*! + \fn QScopedPropertyUpdateGroup::~QScopedPropertyUpdateGroup() + + Calls Qt::endPropertyUpdateGroup(). +*/ + + // check everything stored in QPropertyBindingPrivate's union is trivially destructible // (though the compiler would also complain if that weren't the case) static_assert(std::is_trivially_destructible_v); @@ -288,22 +321,6 @@ bool QPropertyBindingPrivate::evaluateRecursive(PendingBindingObserverList &bind return evaluateRecursive_inline(bindingObservers, status); } -void QPropertyBindingPrivate::notifyRecursive() -{ - if (!pendingNotify) - return; - pendingNotify = false; - Q_ASSERT(!updating); - updating = true; - if (firstObserver) { - firstObserver.noSelfDependencies(this); - firstObserver.notify(propertyDataPtr); - } - if (hasStaticObserver) - staticObserverCallback(propertyDataPtr); - updating = false; -} - void QPropertyBindingPrivate::notifyNonRecursive(const PendingBindingObserverList &bindingObservers) { notifyNonRecursive(); @@ -321,7 +338,7 @@ QPropertyBindingPrivate::NotificationState QPropertyBindingPrivate::notifyNonRec updating = true; if (firstObserver) { firstObserver.noSelfDependencies(this); - firstObserver.notifyOnlyChangeHandler(propertyDataPtr); + firstObserver.notify(propertyDataPtr); } if (hasStaticObserver) staticObserverCallback(propertyDataPtr); @@ -621,7 +638,7 @@ void QPropertyBindingData::notifyObservers(QUntypedPropertyData *propertyDataPtr if (storage) d = QPropertyBindingDataPointer {storage->bindingData(propertyDataPtr)}; if (QPropertyObserverPointer observer = d.firstObserver()) - observer.notifyOnlyChangeHandler(propertyDataPtr); + observer.notify(propertyDataPtr); for (auto &&bindingObserver: bindingObservers) bindingObserver.binding()->notifyNonRecursive(); } @@ -658,11 +675,15 @@ QPropertyObserver::QPropertyObserver(ChangeHandler changeHandler) d.setChangeHandler(changeHandler); } +#if QT_DEPRECATED_SINCE(6, 6) QPropertyObserver::QPropertyObserver(QUntypedPropertyData *data) { + QT_WARNING_PUSH QT_WARNING_DISABLE_DEPRECATED aliasData = data; next.setTag(ObserverIsAlias); + QT_WARNING_POP } +#endif /*! \internal */ @@ -741,7 +762,7 @@ void QPropertyObserverPointer::setBindingToNotify(QPropertyBindingPrivate *bindi /*! \internal - The same as as setBindingToNotify, but assumes that the tag is already correct. + The same as setBindingToNotify, but assumes that the tag is already correct. */ void QPropertyObserverPointer::setBindingToNotify_unsafe(QPropertyBindingPrivate *binding) { @@ -1349,15 +1370,6 @@ QString QPropertyBindingError::description() const Assigns \a newValue to this property and returns a reference to this QProperty. */ -/*! - \fn template QProperty &QProperty::operator=(const QPropertyBinding &newBinding) - - Associates the value of this property with the provided \a newBinding - expression and returns a reference to this property. The property's value is - set to the result of evaluating the new binding. Whenever a dependency of the - binding changes, the binding will be re-evaluated. -*/ - /*! \fn template QPropertyBinding QProperty::setBinding(const QPropertyBinding &newBinding) @@ -2022,26 +2034,6 @@ QString QPropertyBindingError::description() const QPropertyAlias. */ -/*! - \fn template QPropertyAlias &QPropertyAlias::operator=(T &&newValue) - \overload - - Assigns \a newValue to the aliased property and returns a reference to this - QPropertyAlias. -*/ - -/*! - \fn template QPropertyAlias &QPropertyAlias::operator=(const QPropertyBinding &newBinding) - \overload - - Associates the value of the aliased property with the provided \a newBinding - expression and returns a reference to this alias. The property's value is set - to the result of evaluating the new binding. Whenever a dependency of the - binding changes, the binding will be re-evaluated, and the property's value - gets updated accordingly. - -*/ - /*! \fn template QPropertyBinding QPropertyAlias::setBinding(const QPropertyBinding &newBinding) @@ -2494,8 +2486,13 @@ QPropertyAdaptorSlotObject::QPropertyAdaptorSlotObject(QObject *o, const QMetaPr { } +#if QT_VERSION < QT_VERSION_CHECK(7, 0, 0) void QPropertyAdaptorSlotObject::impl(int which, QSlotObjectBase *this_, QObject *r, void **a, bool *ret) +#else +void QPropertyAdaptorSlotObject::impl(QSlotObjectBase *this_, QObject *r, void **a, int which, + bool *ret) +#endif { auto self = static_cast(this_); switch (which) { diff --git a/src/corelib/kernel/qproperty.h b/src/corelib/kernel/qproperty.h index be960552..32d7a445 100644 --- a/src/corelib/kernel/qproperty.h +++ b/src/corelib/kernel/qproperty.h @@ -53,6 +53,17 @@ Q_CORE_EXPORT void beginPropertyUpdateGroup(); Q_CORE_EXPORT void endPropertyUpdateGroup(); } +class QScopedPropertyUpdateGroup +{ + Q_DISABLE_COPY_MOVE(QScopedPropertyUpdateGroup) +public: + Q_NODISCARD_CTOR + QScopedPropertyUpdateGroup() + { Qt::beginPropertyUpdateGroup(); } + ~QScopedPropertyUpdateGroup() noexcept(false) + { Qt::endPropertyUpdateGroup(); } +}; + template class QPropertyData : public QUntypedPropertyData { @@ -217,7 +228,9 @@ public: ObserverNotifiesBinding, // observer was installed to notify bindings that obsverved property changed ObserverNotifiesChangeHandler, // observer is a change handler, which runs on every change ObserverIsPlaceholder, // the observer before this one is currently evaluated in QPropertyObserver::notifyObservers. - ObserverIsAlias +#if QT_DEPRECATED_SINCE(6, 6) + ObserverIsAlias QT_DEPRECATED_VERSION_X_6_6("Use QProperty and add a binding to the target.") +#endif }; protected: using ChangeHandler = void (*)(QPropertyObserver*, QUntypedPropertyData *); @@ -257,7 +270,10 @@ public: protected: QPropertyObserver(ChangeHandler changeHandler); +#if QT_DEPRECATED_SINCE(6, 6) + QT_DEPRECATED_VERSION_X_6_6("This constructor was only meant for internal use. Use QProperty and add a binding to the target.") QPropertyObserver(QUntypedPropertyData *aliasedPropertyPtr); +#endif QUntypedPropertyData *aliasedProperty() const { @@ -272,10 +288,11 @@ private: }; template -class [[nodiscard]] QPropertyChangeHandler : public QPropertyObserver +class QPropertyChangeHandler : public QPropertyObserver { Functor m_handler; public: + Q_NODISCARD_CTOR QPropertyChangeHandler(Functor handler) : QPropertyObserver([](QPropertyObserver *self, QUntypedPropertyData *) { auto This = static_cast*>(self); @@ -286,6 +303,7 @@ public: } template + Q_NODISCARD_CTOR QPropertyChangeHandler(const Property &property, Functor handler) : QPropertyObserver([](QPropertyObserver *self, QUntypedPropertyData *) { auto This = static_cast*>(self); @@ -297,12 +315,14 @@ public: } }; -class [[nodiscard]] QPropertyNotifier : public QPropertyObserver +class QPropertyNotifier : public QPropertyObserver { std::function m_handler; public: + Q_NODISCARD_CTOR QPropertyNotifier() = default; template + Q_NODISCARD_CTOR QPropertyNotifier(Functor handler) : QPropertyObserver([](QPropertyObserver *self, QUntypedPropertyData *) { auto This = static_cast(self); @@ -313,6 +333,7 @@ public: } template + Q_NODISCARD_CTOR QPropertyNotifier(const Property &property, Functor handler) : QPropertyObserver([](QPropertyObserver *self, QUntypedPropertyData *) { auto This = static_cast(self); @@ -867,13 +888,16 @@ public: } }; +#if QT_DEPRECATED_SINCE(6, 6) template -class QPropertyAlias : public QPropertyObserver +class QT_DEPRECATED_VERSION_X_6_6("Class was only meant for internal use, use a QProperty and add a binding to the target") +QPropertyAlias : public QPropertyObserver { Q_DISABLE_COPY_MOVE(QPropertyAlias) const QtPrivate::QBindableInterface *iface = nullptr; public: + QT_WARNING_PUSH QT_WARNING_DISABLE_DEPRECATED QPropertyAlias(QProperty *property) : QPropertyObserver(property), iface(&QtPrivate::QBindableInterfaceForProperty>::iface) @@ -989,7 +1013,9 @@ public: { return aliasedProperty() != nullptr; } + QT_WARNING_POP }; +#endif // QT_DEPRECATED_SINCE(6, 6) template class QObjectBindableProperty : public QPropertyData diff --git a/src/corelib/kernel/qproperty_p.h b/src/corelib/kernel/qproperty_p.h index 4dfa345e..a263fe37 100644 --- a/src/corelib/kernel/qproperty_p.h +++ b/src/corelib/kernel/qproperty_p.h @@ -94,11 +94,12 @@ struct QPropertyBindingDataPointer } }; -struct [[nodiscard]] QPropertyObserverNodeProtector +struct QPropertyObserverNodeProtector { Q_DISABLE_COPY_MOVE(QPropertyObserverNodeProtector) QPropertyObserverBase m_placeHolder; + Q_NODISCARD_CTOR QPropertyObserverNodeProtector(QPropertyObserver *observer) { // insert m_placeholder after observer into the linked list @@ -124,13 +125,21 @@ struct QPropertyObserverPointer void unlink() { unlink_common(); +#if QT_DEPRECATED_SINCE(6, 6) + QT_WARNING_PUSH QT_WARNING_DISABLE_DEPRECATED if (ptr->next.tag() == QPropertyObserver::ObserverIsAlias) ptr->aliasData = nullptr; + QT_WARNING_POP +#endif } void unlink_fast() { +#if QT_DEPRECATED_SINCE(6, 6) + QT_WARNING_PUSH QT_WARNING_DISABLE_DEPRECATED Q_ASSERT(ptr->next.tag() != QPropertyObserver::ObserverIsAlias); + QT_WARNING_POP +#endif unlink_common(); } @@ -140,9 +149,7 @@ struct QPropertyObserverPointer enum class Notify {Everything, OnlyChangeHandlers}; - template void notify(QUntypedPropertyData *propertyDataPtr); - void notifyOnlyChangeHandler(QUntypedPropertyData *propertyDataPtr); #ifndef QT_NO_DEBUG void noSelfDependencies(QPropertyBindingPrivate *binding); #else @@ -368,7 +375,6 @@ public: bool Q_ALWAYS_INLINE evaluateRecursive_inline(PendingBindingObserverList &bindingObservers, QBindingStatus *status); - void notifyRecursive(); void notifyNonRecursive(const PendingBindingObserverList &bindingObservers); enum NotificationState : bool { Delayed, Sent }; NotificationState notifyNonRecursive(); @@ -629,7 +635,7 @@ public: == QtPrivate::QPropertyBindingData::Evaluated) { // evaluateBindings() can trash the observers. We need to re-fetch here. if (QPropertyObserverPointer observer = d.firstObserver()) - observer.notifyOnlyChangeHandler(this); + observer.notify(this); for (auto&& bindingObserver: bindingObservers) bindingObserver.binding()->notifyNonRecursive(); } @@ -831,7 +837,14 @@ inline bool QPropertyBindingPrivate::evaluateRecursive_inline(PendingBindingObse return true; } -template +/*! + \internal + + Walks through the list of property observers, and calls any ChangeHandler + found there. + It doesn't do anything with bindings, which are only handled in + QPropertyBindingPrivate::evaluateRecursive. + */ inline void QPropertyObserverPointer::notify(QUntypedPropertyData *propertyDataPtr) { auto observer = const_cast(ptr); @@ -870,31 +883,22 @@ inline void QPropertyObserverPointer::notify(QUntypedPropertyData *propertyDataP break; } case QPropertyObserver::ObserverNotifiesBinding: - { - if constexpr (notifyPolicy == Notify::Everything) { - auto bindingToNotify = observer->binding; - QPropertyObserverNodeProtector protector(observer); - bindingToNotify->notifyRecursive(); - next = protector.next(); - } break; - } case QPropertyObserver::ObserverIsPlaceholder: // recursion is already properly handled somewhere else break; +#if QT_DEPRECATED_SINCE(6, 6) + QT_WARNING_PUSH QT_WARNING_DISABLE_DEPRECATED case QPropertyObserver::ObserverIsAlias: break; + QT_WARNING_POP +#endif default: Q_UNREACHABLE(); } observer = next; } } -inline void QPropertyObserverPointer::notifyOnlyChangeHandler(QUntypedPropertyData *propertyDataPtr) -{ - notify(propertyDataPtr); -} - inline QPropertyObserverNodeProtector::~QPropertyObserverNodeProtector() { QPropertyObserverPointer d{static_cast(&m_placeHolder)}; @@ -920,7 +924,11 @@ class QPropertyAdaptorSlotObject : public QUntypedPropertyData, public QSlotObje QObject *obj; QMetaProperty metaProperty_; +#if QT_VERSION < QT_VERSION_CHECK(7, 0, 0) static void impl(int which, QSlotObjectBase *this_, QObject *r, void **a, bool *ret); +#else + static void impl(QSlotObjectBase *this_, QObject *r, void **a, int which, bool *ret); +#endif QPropertyAdaptorSlotObject(QObject *o, const QMetaProperty& p); diff --git a/src/corelib/kernel/qsharedmemory.cpp b/src/corelib/kernel/qsharedmemory.cpp deleted file mode 100644 index 24495641..00000000 --- a/src/corelib/kernel/qsharedmemory.cpp +++ /dev/null @@ -1,714 +0,0 @@ -// 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 "qsharedmemory.h" -#include "qsharedmemory_p.h" -#include "qsystemsemaphore.h" -#include -#include -#include -#ifdef Q_OS_WIN -# include -#endif - -#if defined(Q_OS_DARWIN) -# include "qcore_mac_p.h" -# if !defined(SHM_NAME_MAX) - // Based on PSEMNAMLEN in XNU's posix_sem.c, which would - // indicate the max length is 31, _excluding_ the zero - // terminator. But in practice (possibly due to an off- - // by-one bug in the kernel) the usable bytes are only 30. -# define SHM_NAME_MAX 30 -# endif -#endif - -QT_BEGIN_NAMESPACE - -using namespace Qt::StringLiterals; - -#if QT_CONFIG(sharedmemory) || QT_CONFIG(systemsemaphore) -/*! - \internal - - Generate a string from the key which can be any unicode string into - the subset that the win/unix kernel allows. - - On Unix this will be a file name - */ -QString -QSharedMemoryPrivate::makePlatformSafeKey(const QString &key, - const QString &prefix) -{ - if (key.isEmpty()) - return QString(); - - QByteArray hex = QCryptographicHash::hash(key.toUtf8(), QCryptographicHash::Sha1).toHex(); - -#if defined(Q_OS_DARWIN) && defined(QT_POSIX_IPC) - if (qt_apple_isSandboxed()) { - // Sandboxed applications on Apple platforms require the shared memory name - // to be in the form /. - // Since we don't know which application group identifier the user wants - // to apply, we instead document that requirement, and use the key directly. - return key; - } else { - // The shared memory name limit on Apple platforms is very low (30 characters), - // so we can't use the logic below of combining the prefix, key, and a hash, - // to ensure a unique and valid name. Instead we use the first part of the - // hash, which should still long enough to avoid collisions in practice. - return u'/' + hex.left(SHM_NAME_MAX - 1); - } -#endif - - QString result = prefix; - for (QChar ch : key) { - if ((ch >= u'a' && ch <= u'z') || - (ch >= u'A' && ch <= u'Z')) - result += ch; - } - result.append(QLatin1StringView(hex)); - -#ifdef Q_OS_WIN - return result; -#elif defined(QT_POSIX_IPC) - return u'/' + result; -#else - return QDir::tempPath() + u'/' + result; -#endif -} -#endif // QT_CONFIG(sharedmemory) || QT_CONFIG(systemsemaphore) - -#if QT_CONFIG(sharedmemory) - -/*! - \class QSharedMemory - \inmodule QtCore - \since 4.4 - - \brief The QSharedMemory class provides access to a shared memory segment. - - QSharedMemory provides access to a shared memory segment by multiple - threads and processes. It also provides a way for a single thread or - process to lock the memory for exclusive access. - - When using this class, be aware of the following platform - differences: - - \list - - \li Windows: QSharedMemory does not "own" the shared memory segment. - When all threads or processes that have an instance of QSharedMemory - attached to a particular shared memory segment have either destroyed - their instance of QSharedMemory or exited, the Windows kernel - releases the shared memory segment automatically. - - \li Unix: QSharedMemory "owns" the shared memory segment. When the - last thread or process that has an instance of QSharedMemory - attached to a particular shared memory segment detaches from the - segment by destroying its instance of QSharedMemory, the destructor - releases the shared memory segment. But if that last thread or - process crashes without running the QSharedMemory destructor, the - shared memory segment survives the crash. - - \li Unix: QSharedMemory can be implemented by one of two different - backends, selected at Qt build time: System V or POSIX. Qt defaults to - using the System V API if it is available, and POSIX if not. These two - backends do not interoperate, so two applications must ensure they use the - same one, even if the native key (see setNativeKey()) is the same. - - The POSIX backend can be explicitly selected using the - \c{-feature-ipc_posix} option to the Qt configure script. If it is enabled, - the \c{QT_POSIX_IPC} macro will be defined. - - \li Sandboxed applications on Apple platforms (including apps - shipped through the Apple App Store): This environment requires - the use of POSIX shared memory (instead of System V shared memory). - - Qt for iOS is built with support for POSIX shared memory out of the box. - However, Qt for \macos builds (including those from the Qt installer) default - to System V, making them unsuitable for App Store submission if QSharedMemory - is needed. See above for instructions to explicitly select the POSIX backend - when building Qt. - - In addition, in a sandboxed environment, the following caveats apply: - - \list - \li The key must be in the form \c {/}, - as documented \l {https://developer.apple.com/library/archive/documentation/Security/Conceptual/AppSandboxDesignGuide/AppSandboxInDepth/AppSandboxInDepth.html#//apple_ref/doc/uid/TP40011183-CH3-SW24} - {here} and \l {https://developer.apple.com/documentation/bundleresources/entitlements/com_apple_security_application-groups} - {here}. - - \li The key length is limited to 30 characters. - - \li On process exit, the named shared memory entries are not - cleaned up, so restarting the application and re-creating the - shared memory under the same name will fail. To work around this, - fall back to attaching to the existing shared memory entry: - - \code - - QSharedMemory shm("DEVTEAMID.app-group/shared"); - if (!shm.create(42) && shm.error() == QSharedMemory::AlreadyExists) - shm.attach(); - - \endcode - - \endlist - - \li Android: QSharedMemory is not supported. - - \endlist - - Remember to lock the shared memory with lock() before reading from - or writing to the shared memory, and remember to release the lock - with unlock() after you are done. - - QSharedMemory automatically destroys the shared memory segment when - the last instance of QSharedMemory is detached from the segment, and - no references to the segment remain. - - \warning QSharedMemory changes the key in a Qt-specific way, unless otherwise - specified. Interoperation with non-Qt applications is achieved by first creating - a default shared memory with QSharedMemory() and then setting a native key with - setNativeKey(), after ensuring they use the same low-level API (System V or - POSIX). When using native keys, shared memory is not protected against multiple - accesses on it (for example, unable to lock()) and a user-defined mechanism - should be used to achieve such protection. - - \section2 Alternative: Memory-Mapped File - - Another way to share memory between processes is by opening the same file - using \l QFile and mapping it into memory using QFile::map() (without - specifying the QFileDevice::MapPrivateOption option). Any writes to the - mapped segment will be observed by all other processes that have mapped the - same file. This solution has the major advantages of being independent of the - backend API and of being simpler to interoperate with from non-Qt - applications. And since \l{QTemporaryFile} is a \l{QFile}, applications can - use that class to achieve clean-up semantics and to create unique shared - memory segments too. - - To achieve locking of the shared memory segment, applications will need to - deploy their own mechanisms. This can be achieved by using \l - QBasicAtomicInteger or \c{std::atomic} in a pre-determined offset in the - segment itself. Higher-level locking primitives may be available on some - operating systems; for example, on Linux, \c{pthread_mutex_create()} can be - passed a flag to indicate that the mutex resides in a shared memory segment. - - A major drawback of using file-backed shared memory is that the operating - system will attempt to write the data to permanent storage, possibly causing - noticeable performance penalties. To avoid this, applications should locate a - RAM-backed filesystem, such as \c{tmpfs} on Linux (see - QStorageInfo::fileSystemType()), or pass a flag to the native file-opening - function to inform the OS to avoid committing the contents to storage. - - File-backed shared memory must be used with care if another process - participating is untrusted. The files may be truncated/shrunk and cause - applications accessing memory beyond the file's size to crash. - - \section3 Linux hints on memory-mapped files - - On modern Linux systems, while the \c{/tmp} directory is often a \c{tmpfs} - mount point, that is not a requirement. However, the \c{/dev/shm} directory - is required to be a \c{tmpfs} and exists for this very purpose. Do note that - it is world-readable and writable (like \c{/tmp} and \c{/var/tmp}), so one - must be careful of the contents revealed there. Another alternative is to use - the XDG Runtime Directory (see QStandardPaths::writableLocation() and - \l{QStandardPaths::RuntimeLocation}), which on Linux systems using systemd is - a user-specific \c{tmpfs}. - - An even more secure solution is to create a "memfd" using \c{memfd_create(2)} - and use interprocess communication to pass the file descriptor, like - \l{QDBusUnixFileDescriptor} or by letting the child process of a \l{QProcess} - inherit it. "memfds" can also be sealed against being shrunk, so they are - safe to be used when communicating with processes with a different privilege - level. - - \section3 FreeBSD hints on memory-mapped files - - FreeBSD also has \c{memfd_create(2)} and can pass file descriptors to other - processes using the same techniques as Linux. It does not have temporary - filesystems mounted by default. - - \section3 Windows hints on memory-mapped files - - On Windows, the application can request the operating system avoid committing - the file's contents to permanent storage. This request is performed by - passing the \c{FILE_ATTRIBUTE_TEMPORARY} flag in the \c{dwFlagsAndAttributes} - \c{CreateFile} Win32 function, the \c{_O_SHORT_LIVED} flag to \c{_open()} - low-level function, or by including the modifier "T" to the \c{fopen()} C - runtime function. - - There's also a flag to inform the operating system to delete the file when - the last handle to it is closed (\c{FILE_FLAG_DELETE_ON_CLOSE}, - \c{_O_TEMPORARY}, and the "D" modifier), but do note that all processes - attempting to open the file must agree on using this flag or not using it. A - mismatch will likely cause a sharing violation and failure to open the file. - */ - -/*! - \overload QSharedMemory() - - Constructs a shared memory object with the given \a parent. The - shared memory object's key is not set by the constructor, so the - shared memory object does not have an underlying shared memory - segment attached. The key must be set with setKey() or setNativeKey() - before create() or attach() can be used. - - \sa setKey() - */ - -QSharedMemory::QSharedMemory(QObject *parent) - : QObject(*new QSharedMemoryPrivate, parent) -{ -} - -/*! - Constructs a shared memory object with the given \a parent and with - its key set to \a key. Because its key is set, its create() and - attach() functions can be called. - - \sa setKey(), create(), attach() - */ -QSharedMemory::QSharedMemory(const QString &key, QObject *parent) - : QObject(*new QSharedMemoryPrivate, parent) -{ - setKey(key); -} - -/*! - The destructor clears the key, which forces the shared memory object - to \l {detach()} {detach} from its underlying shared memory - segment. If this shared memory object is the last one connected to - the shared memory segment, the detach() operation destroys the - shared memory segment. - - \sa detach(), isAttached() - */ -QSharedMemory::~QSharedMemory() -{ - setKey(QString()); -} - -/*! - Sets the platform independent \a key for this shared memory object. If \a key - is the same as the current key, the function returns without doing anything. - - You can call key() to retrieve the platform independent key. Internally, - QSharedMemory converts this key into a platform specific key. If you instead - call nativeKey(), you will get the platform specific, converted key. - - If the shared memory object is attached to an underlying shared memory - segment, it will \l {detach()} {detach} from it before setting the new key. - This function does not do an attach(). - - \sa key(), nativeKey(), isAttached() -*/ -void QSharedMemory::setKey(const QString &key) -{ - Q_D(QSharedMemory); - if (key == d->key && d->makePlatformSafeKey(key) == d->nativeKey) - return; - - if (isAttached()) - detach(); - d->cleanHandle(); - d->key = key; - d->nativeKey = d->makePlatformSafeKey(key); -} - -/*! - \since 4.8 - - Sets the native, platform specific, \a key for this shared memory object. If - \a key is the same as the current native key, the function returns without - doing anything. If all you want is to assign a key to a segment, you should - call setKey() instead. - - You can call nativeKey() to retrieve the native key. If a native key has been - assigned, calling key() will return a null string. - - If the shared memory object is attached to an underlying shared memory - segment, it will \l {detach()} {detach} from it before setting the new key. - This function does not do an attach(). - - The application will not be portable if you set a native key. - - \sa nativeKey(), key(), isAttached() -*/ -void QSharedMemory::setNativeKey(const QString &key) -{ - Q_D(QSharedMemory); - if (key == d->nativeKey && d->key.isNull()) - return; - - if (isAttached()) - detach(); - d->cleanHandle(); - d->key = QString(); - d->nativeKey = key; -} - -bool QSharedMemoryPrivate::initKey() -{ - if (!cleanHandle()) - return false; -#if QT_CONFIG(systemsemaphore) - systemSemaphore.setKey(QString(), 1); - systemSemaphore.setKey(key, 1); - if (systemSemaphore.error() != QSystemSemaphore::NoError) { - QString function = "QSharedMemoryPrivate::initKey"_L1; - errorString = QSharedMemory::tr("%1: unable to set key on lock").arg(function); - switch(systemSemaphore.error()) { - case QSystemSemaphore::PermissionDenied: - error = QSharedMemory::PermissionDenied; - break; - case QSystemSemaphore::KeyError: - error = QSharedMemory::KeyError; - break; - case QSystemSemaphore::AlreadyExists: - error = QSharedMemory::AlreadyExists; - break; - case QSystemSemaphore::NotFound: - error = QSharedMemory::NotFound; - break; - case QSystemSemaphore::OutOfResources: - error = QSharedMemory::OutOfResources; - break; - case QSystemSemaphore::UnknownError: - default: - error = QSharedMemory::UnknownError; - break; - } - return false; - } -#endif - errorString = QString(); - error = QSharedMemory::NoError; - return true; -} - -/*! - Returns the key assigned with setKey() to this shared memory, or a null key - if no key has been assigned, or if the segment is using a nativeKey(). The - key is the identifier used by Qt applications to identify the shared memory - segment. - - You can find the native, platform specific, key used by the operating system - by calling nativeKey(). - - \sa setKey(), setNativeKey() - */ -QString QSharedMemory::key() const -{ - Q_D(const QSharedMemory); - return d->key; -} - -/*! - \since 4.8 - - Returns the native, platform specific, key for this shared memory object. The - native key is the identifier used by the operating system to identify the - shared memory segment. - - You can use the native key to access shared memory segments that have not - been created by Qt, or to grant shared memory access to non-Qt applications. - - \sa setKey(), setNativeKey() -*/ -QString QSharedMemory::nativeKey() const -{ - Q_D(const QSharedMemory); - return d->nativeKey; -} - -/*! - Creates a shared memory segment of \a size bytes with the key passed to the - constructor, set with setKey() or set with setNativeKey(), then attaches to - the new shared memory segment with the given access \a mode and returns - \tt true. If a shared memory segment identified by the key already exists, - the attach operation is not performed and \tt false is returned. When the - return value is \tt false, call error() to determine which error occurred. - - \sa error() - */ -bool QSharedMemory::create(qsizetype size, AccessMode mode) -{ - Q_D(QSharedMemory); - - if (!d->initKey()) - return false; - -#if QT_CONFIG(systemsemaphore) -#ifndef Q_OS_WIN - // Take ownership and force set initialValue because the semaphore - // might have already existed from a previous crash. - d->systemSemaphore.setKey(d->key, 1, QSystemSemaphore::Create); -#endif -#endif - - QString function = "QSharedMemory::create"_L1; -#if QT_CONFIG(systemsemaphore) - QSharedMemoryLocker lock(this); - if (!d->key.isNull() && !d->tryLocker(&lock, function)) - return false; -#endif - - if (size <= 0) { - d->error = QSharedMemory::InvalidSize; - d->errorString = - QSharedMemory::tr("%1: create size is less then 0").arg(function); - return false; - } - - if (!d->create(size)) - return false; - - return d->attach(mode); -} - -/*! - Returns the size of the attached shared memory segment. If no shared - memory segment is attached, 0 is returned. - - \note The size of the segment may be larger than the requested size that was - passed to create(). - - \sa create(), attach() - */ -qsizetype QSharedMemory::size() const -{ - Q_D(const QSharedMemory); - return d->size; -} - -/*! - \enum QSharedMemory::AccessMode - - \value ReadOnly The shared memory segment is read-only. Writing to - the shared memory segment is not allowed. An attempt to write to a - shared memory segment created with ReadOnly causes the program to - abort. - - \value ReadWrite Reading and writing the shared memory segment are - both allowed. -*/ - -/*! - Attempts to attach the process to the shared memory segment - identified by the key that was passed to the constructor or to a - call to setKey() or setNativeKey(). The access \a mode is \l {QSharedMemory::} - {ReadWrite} by default. It can also be \l {QSharedMemory::} - {ReadOnly}. Returns \c true if the attach operation is successful. If - false is returned, call error() to determine which error occurred. - After attaching the shared memory segment, a pointer to the shared - memory can be obtained by calling data(). - - \sa isAttached(), detach(), create() - */ -bool QSharedMemory::attach(AccessMode mode) -{ - Q_D(QSharedMemory); - - if (isAttached() || !d->initKey()) - return false; -#if QT_CONFIG(systemsemaphore) - QSharedMemoryLocker lock(this); - if (!d->key.isNull() && !d->tryLocker(&lock, "QSharedMemory::attach"_L1)) - return false; -#endif - - if (isAttached() || !d->handle()) - return false; - - return d->attach(mode); -} - -/*! - Returns \c true if this process is attached to the shared memory - segment. - - \sa attach(), detach() - */ -bool QSharedMemory::isAttached() const -{ - Q_D(const QSharedMemory); - return (nullptr != d->memory); -} - -/*! - Detaches the process from the shared memory segment. If this was the - last process attached to the shared memory segment, then the shared - memory segment is released by the system, i.e., the contents are - destroyed. The function returns \c true if it detaches the shared - memory segment. If it returns \c false, it usually means the segment - either isn't attached, or it is locked by another process. - - \sa attach(), isAttached() - */ -bool QSharedMemory::detach() -{ - Q_D(QSharedMemory); - if (!isAttached()) - return false; - -#if QT_CONFIG(systemsemaphore) - QSharedMemoryLocker lock(this); - if (!d->key.isNull() && !d->tryLocker(&lock, "QSharedMemory::detach"_L1)) - return false; -#endif - - return d->detach(); -} - -/*! - Returns a pointer to the contents of the shared memory segment, if - one is attached. Otherwise it returns null. Remember to lock the - shared memory with lock() before reading from or writing to the - shared memory, and remember to release the lock with unlock() after - you are done. - - \sa attach() - */ -void *QSharedMemory::data() -{ - Q_D(QSharedMemory); - return d->memory; -} - -/*! - Returns a const pointer to the contents of the shared memory - segment, if one is attached. Otherwise it returns null. Remember to - lock the shared memory with lock() before reading from or writing to - the shared memory, and remember to release the lock with unlock() - after you are done. - - \sa attach(), create() - */ -const void *QSharedMemory::constData() const -{ - Q_D(const QSharedMemory); - return d->memory; -} - -/*! - \overload data() - */ -const void *QSharedMemory::data() const -{ - Q_D(const QSharedMemory); - return d->memory; -} - -#if QT_CONFIG(systemsemaphore) -/*! - This is a semaphore that locks the shared memory segment for access - by this process and returns \c true. If another process has locked the - segment, this function blocks until the lock is released. Then it - acquires the lock and returns \c true. If this function returns \c false, - it means that you have ignored a false return from create() or attach(), - that you have set the key with setNativeKey() or that - QSystemSemaphore::acquire() failed due to an unknown system error. - - \sa unlock(), data(), QSystemSemaphore::acquire() - */ -bool QSharedMemory::lock() -{ - Q_D(QSharedMemory); - if (d->lockedByMe) { - qWarning("QSharedMemory::lock: already locked"); - return true; - } - if (d->systemSemaphore.acquire()) { - d->lockedByMe = true; - return true; - } - const auto function = "QSharedMemory::lock"_L1; - d->errorString = QSharedMemory::tr("%1: unable to lock").arg(function); - d->error = QSharedMemory::LockError; - return false; -} - -/*! - Releases the lock on the shared memory segment and returns \c true, if - the lock is currently held by this process. If the segment is not - locked, or if the lock is held by another process, nothing happens - and false is returned. - - \sa lock() - */ -bool QSharedMemory::unlock() -{ - Q_D(QSharedMemory); - if (!d->lockedByMe) - return false; - d->lockedByMe = false; - if (d->systemSemaphore.release()) - return true; - const auto function = "QSharedMemory::unlock"_L1; - d->errorString = QSharedMemory::tr("%1: unable to unlock").arg(function); - d->error = QSharedMemory::LockError; - return false; -} -#endif // QT_CONFIG(systemsemaphore) - -/*! - \enum QSharedMemory::SharedMemoryError - - \value NoError No error occurred. - - \value PermissionDenied The operation failed because the caller - didn't have the required permissions. - - \value InvalidSize A create operation failed because the requested - size was invalid. - - \value KeyError The operation failed because of an invalid key. - - \value AlreadyExists A create() operation failed because a shared - memory segment with the specified key already existed. - - \value NotFound An attach() failed because a shared memory segment - with the specified key could not be found. - - \value LockError The attempt to lock() the shared memory segment - failed because create() or attach() failed and returned false, or - because a system error occurred in QSystemSemaphore::acquire(). - - \value OutOfResources A create() operation failed because there was - not enough memory available to fill the request. - - \value UnknownError Something else happened and it was bad. -*/ - -/*! - Returns a value indicating whether an error occurred, and, if so, - which error it was. - - \sa errorString() - */ -QSharedMemory::SharedMemoryError QSharedMemory::error() const -{ - Q_D(const QSharedMemory); - return d->error; -} - -/*! - Returns a text description of the last error that occurred. If - error() returns an \l {QSharedMemory::SharedMemoryError} {error - value}, call this function to get a text string that describes the - error. - - \sa error() - */ -QString QSharedMemory::errorString() const -{ - Q_D(const QSharedMemory); - return d->errorString; -} - -#endif // QT_CONFIG(sharedmemory) - -QT_END_NAMESPACE - -#include "moc_qsharedmemory.cpp" diff --git a/src/corelib/kernel/qsharedmemory_android.cpp b/src/corelib/kernel/qsharedmemory_android.cpp deleted file mode 100644 index 0cee2ab3..00000000 --- a/src/corelib/kernel/qsharedmemory_android.cpp +++ /dev/null @@ -1,65 +0,0 @@ -// Copyright (C) 2012 Collabora Ltd, author -// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only - -#include "qsharedmemory.h" -#include "qsharedmemory_p.h" -#include - -#if QT_CONFIG(sharedmemory) -QT_BEGIN_NAMESPACE - -void QSharedMemoryPrivate::setErrorString(QLatin1StringView function) -{ - Q_UNUSED(function); - Q_UNIMPLEMENTED(); -} - -key_t QSharedMemoryPrivate::handle() -{ - Q_UNIMPLEMENTED(); - return 0; -} - -#endif // QT_CONFIG(sharedmemory) - -#if QT_CONFIG(sharedmemory) || QT_CONFIG(systemsemaphore) -int QSharedMemoryPrivate::createUnixKeyFile(const QString &fileName) -{ - Q_UNUSED(fileName); - Q_UNIMPLEMENTED(); - return 0; -} -#endif // QT_CONFIG(sharedmemory) || QT_CONFIG(systemsemaphore) - -#if QT_CONFIG(sharedmemory) - -bool QSharedMemoryPrivate::cleanHandle() -{ - Q_UNIMPLEMENTED(); - return true; -} - -bool QSharedMemoryPrivate::create(qsizetype size) -{ - Q_UNUSED(size); - Q_UNIMPLEMENTED(); - return false; -} - -bool QSharedMemoryPrivate::attach(QSharedMemory::AccessMode mode) -{ - Q_UNUSED(mode); - Q_UNIMPLEMENTED(); - return false; -} - -bool QSharedMemoryPrivate::detach() -{ - Q_UNIMPLEMENTED(); - return false; -} - - -QT_END_NAMESPACE - -#endif // QT_CONFIG(sharedmemory) diff --git a/src/corelib/kernel/qsharedmemory_p.h b/src/corelib/kernel/qsharedmemory_p.h deleted file mode 100644 index 6d7973fa..00000000 --- a/src/corelib/kernel/qsharedmemory_p.h +++ /dev/null @@ -1,140 +0,0 @@ -// 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 - -#ifndef QSHAREDMEMORY_P_H -#define QSHAREDMEMORY_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 "qsharedmemory.h" - -#include - -#if !QT_CONFIG(sharedmemory) -# if QT_CONFIG(systemsemaphore) - -QT_BEGIN_NAMESPACE - -namespace QSharedMemoryPrivate -{ - int createUnixKeyFile(const QString &fileName); - QString makePlatformSafeKey(const QString &key, - const QString &prefix = QStringLiteral("qipc_sharedmemory_")); -} - -QT_END_NAMESPACE - -# endif -#else - -#include "qsystemsemaphore.h" -#include "private/qobject_p.h" - -#if !defined(Q_OS_WIN) && !defined(Q_OS_ANDROID) && !defined(Q_OS_INTEGRITY) && !defined(Q_OS_RTEMS) -# include -#endif - -QT_BEGIN_NAMESPACE - -#if QT_CONFIG(systemsemaphore) -/*! - Helper class - */ -class QSharedMemoryLocker -{ - -public: - inline QSharedMemoryLocker(QSharedMemory *sharedMemory) : q_sm(sharedMemory) - { - Q_ASSERT(q_sm); - } - - inline ~QSharedMemoryLocker() - { - if (q_sm) - q_sm->unlock(); - } - - inline bool lock() - { - if (q_sm && q_sm->lock()) - return true; - q_sm = nullptr; - return false; - } - -private: - QSharedMemory *q_sm; -}; -#endif // QT_CONFIG(systemsemaphore) - -class Q_AUTOTEST_EXPORT QSharedMemoryPrivate : public QObjectPrivate -{ - Q_DECLARE_PUBLIC(QSharedMemory) - -public: - void *memory = nullptr; - qsizetype size = 0; - QString key; - QString nativeKey; - QSharedMemory::SharedMemoryError error = QSharedMemory::NoError; - QString errorString; -#if QT_CONFIG(systemsemaphore) - QSystemSemaphore systemSemaphore{QString()}; - bool lockedByMe = false; -#endif - - static int createUnixKeyFile(const QString &fileName); - static QString makePlatformSafeKey(const QString &key, - const QString &prefix = QStringLiteral("qipc_sharedmemory_")); -#ifdef Q_OS_WIN - Qt::HANDLE handle(); -#elif defined(QT_POSIX_IPC) - int handle(); -#else - key_t handle(); -#endif - bool initKey(); - bool cleanHandle(); - bool create(qsizetype size); - bool attach(QSharedMemory::AccessMode mode); - bool detach(); - - void setErrorString(QLatin1StringView function); - -#if QT_CONFIG(systemsemaphore) - bool tryLocker(QSharedMemoryLocker *locker, const QString &function) { - if (!locker->lock()) { - errorString = QSharedMemory::tr("%1: unable to lock").arg(function); - error = QSharedMemory::LockError; - return false; - } - return true; - } -#endif // QT_CONFIG(systemsemaphore) - -private: -#ifdef Q_OS_WIN - Qt::HANDLE hand = nullptr; -#elif defined(QT_POSIX_IPC) - int hand = -1; -#else - key_t unix_key = 0; -#endif -}; - -QT_END_NAMESPACE - -#endif // QT_CONFIG(sharedmemory) - -#endif // QSHAREDMEMORY_P_H - diff --git a/src/corelib/kernel/qsharedmemory_systemv.cpp b/src/corelib/kernel/qsharedmemory_systemv.cpp deleted file mode 100644 index de6b4074..00000000 --- a/src/corelib/kernel/qsharedmemory_systemv.cpp +++ /dev/null @@ -1,226 +0,0 @@ -// 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 "qplatformdefs.h" - -#include "qsharedmemory.h" -#include "qsharedmemory_p.h" -#include "qsystemsemaphore.h" -#include -#include - -#include - -#ifndef QT_POSIX_IPC - -#if QT_CONFIG(sharedmemory) -#include -#include -#include -#include -#include -#include -#endif // QT_CONFIG(sharedmemory) - -#include "private/qcore_unix_p.h" - -#if QT_CONFIG(sharedmemory) -QT_BEGIN_NAMESPACE - -using namespace Qt::StringLiterals; - -/*! - \internal - - If not already made create the handle used for accessing the shared memory. -*/ -key_t QSharedMemoryPrivate::handle() -{ - // already made - if (unix_key) - return unix_key; - - // don't allow making handles on empty keys - if (nativeKey.isEmpty()) { - errorString = QSharedMemory::tr("%1: key is empty").arg("QSharedMemory::handle:"_L1); - error = QSharedMemory::KeyError; - return 0; - } - - // ftok requires that an actual file exists somewhere - if (!QFile::exists(nativeKey)) { - errorString = QSharedMemory::tr("%1: UNIX key file doesn't exist").arg("QSharedMemory::handle:"_L1); - error = QSharedMemory::NotFound; - return 0; - } - - unix_key = ftok(QFile::encodeName(nativeKey).constData(), 'Q'); - if (-1 == unix_key) { - errorString = QSharedMemory::tr("%1: ftok failed").arg("QSharedMemory::handle:"_L1); - error = QSharedMemory::KeyError; - unix_key = 0; - } - return unix_key; -} - -#endif // QT_CONFIG(sharedmemory) - -#if QT_CONFIG(sharedmemory) || QT_CONFIG(systemsemaphore) -/*! - \internal - Creates the unix file if needed. - returns \c true if the unix file was created. - - -1 error - 0 already existed - 1 created - */ -int QT_PREPEND_NAMESPACE(QSharedMemoryPrivate)::createUnixKeyFile(const QString &fileName) -{ - int fd = qt_safe_open(QFile::encodeName(fileName).constData(), - O_EXCL | O_CREAT | O_RDWR, 0640); - if (-1 == fd) { - if (errno == EEXIST) - return 0; - return -1; - } else { - close(fd); - } - return 1; -} -#endif // QT_CONFIG(sharedmemory) || QT_CONFIG(systemsemaphore) - -#if QT_CONFIG(sharedmemory) - -bool QSharedMemoryPrivate::cleanHandle() -{ - unix_key = 0; - return true; -} - -bool QSharedMemoryPrivate::create(qsizetype size) -{ - // build file if needed - bool createdFile = false; - int built = createUnixKeyFile(nativeKey); - if (built == -1) { - errorString = QSharedMemory::tr("%1: unable to make key").arg("QSharedMemory::handle:"_L1); - error = QSharedMemory::KeyError; - return false; - } - if (built == 1) { - createdFile = true; - } - - // get handle - if (!handle()) { - if (createdFile) - QFile::remove(nativeKey); - return false; - } - - // create - if (-1 == shmget(unix_key, size_t(size), 0600 | IPC_CREAT | IPC_EXCL)) { - const auto function = "QSharedMemory::create"_L1; - switch (errno) { - case EINVAL: - errorString = QSharedMemory::tr("%1: system-imposed size restrictions").arg("QSharedMemory::handle"_L1); - error = QSharedMemory::InvalidSize; - break; - default: - setErrorString(function); - } - if (createdFile && error != QSharedMemory::AlreadyExists) - QFile::remove(nativeKey); - return false; - } - - return true; -} - -bool QSharedMemoryPrivate::attach(QSharedMemory::AccessMode mode) -{ - // grab the shared memory segment id - int id = shmget(unix_key, 0, (mode == QSharedMemory::ReadOnly ? 0400 : 0600)); - if (-1 == id) { - setErrorString("QSharedMemory::attach (shmget)"_L1); - return false; - } - - // grab the memory - memory = shmat(id, nullptr, (mode == QSharedMemory::ReadOnly ? SHM_RDONLY : 0)); - if ((void *)-1 == memory) { - memory = nullptr; - setErrorString("QSharedMemory::attach (shmat)"_L1); - return false; - } - - // grab the size - shmid_ds shmid_ds; - if (!shmctl(id, IPC_STAT, &shmid_ds)) { - size = (qsizetype)shmid_ds.shm_segsz; - } else { - setErrorString("QSharedMemory::attach (shmctl)"_L1); - return false; - } - - return true; -} - -bool QSharedMemoryPrivate::detach() -{ - // detach from the memory segment - if (-1 == shmdt(memory)) { - const auto function = "QSharedMemory::detach"_L1; - switch (errno) { - case EINVAL: - errorString = QSharedMemory::tr("%1: not attached").arg(function); - error = QSharedMemory::NotFound; - break; - default: - setErrorString(function); - } - return false; - } - memory = nullptr; - size = 0; - - // Get the number of current attachments - int id = shmget(unix_key, 0, 0400); - cleanHandle(); - - struct shmid_ds shmid_ds; - if (0 != shmctl(id, IPC_STAT, &shmid_ds)) { - switch (errno) { - case EINVAL: - return true; - default: - return false; - } - } - // If there are no attachments then remove it. - if (shmid_ds.shm_nattch == 0) { - // mark for removal - if (-1 == shmctl(id, IPC_RMID, &shmid_ds)) { - setErrorString("QSharedMemory::remove"_L1); - switch (errno) { - case EINVAL: - return true; - default: - return false; - } - } - - // remove file - if (!QFile::remove(nativeKey)) - return false; - } - return true; -} - - -QT_END_NAMESPACE - -#endif // QT_CONFIG(sharedmemory) - -#endif // QT_POSIX_IPC diff --git a/src/corelib/kernel/qsharedmemory_unix.cpp b/src/corelib/kernel/qsharedmemory_unix.cpp deleted file mode 100644 index 0696c3fe..00000000 --- a/src/corelib/kernel/qsharedmemory_unix.cpp +++ /dev/null @@ -1,64 +0,0 @@ -// 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 "qplatformdefs.h" - -#include "qsharedmemory.h" -#include "qsharedmemory_p.h" -#include "qsystemsemaphore.h" -#include - -#include - -#if QT_CONFIG(sharedmemory) -#include -#ifndef QT_POSIX_IPC -#include -#include -#else -#include -#endif -#include -#include -#include -#endif // QT_CONFIG(sharedmemory) - -#include "private/qcore_unix_p.h" - -#if QT_CONFIG(sharedmemory) -QT_BEGIN_NAMESPACE - -void QSharedMemoryPrivate::setErrorString(QLatin1StringView function) -{ - // EINVAL is handled in functions so they can give better error strings - switch (errno) { - case EACCES: - errorString = QSharedMemory::tr("%1: permission denied").arg(function); - error = QSharedMemory::PermissionDenied; - break; - case EEXIST: - errorString = QSharedMemory::tr("%1: already exists").arg(function); - error = QSharedMemory::AlreadyExists; - break; - case ENOENT: - errorString = QSharedMemory::tr("%1: doesn't exist").arg(function); - error = QSharedMemory::NotFound; - break; - case EMFILE: - case ENOMEM: - case ENOSPC: - errorString = QSharedMemory::tr("%1: out of resources").arg(function); - error = QSharedMemory::OutOfResources; - break; - default: - errorString = QSharedMemory::tr("%1: unknown error %2").arg(function).arg(errno); - error = QSharedMemory::UnknownError; -#if defined QSHAREDMEMORY_DEBUG - qDebug() << errorString << "key" << key << "errno" << errno << EINVAL; -#endif - } -} - -QT_END_NAMESPACE - -#endif // QT_CONFIG(sharedmemory) diff --git a/src/corelib/kernel/qsystemsemaphore_android.cpp b/src/corelib/kernel/qsystemsemaphore_android.cpp deleted file mode 100644 index 5421dcbe..00000000 --- a/src/corelib/kernel/qsystemsemaphore_android.cpp +++ /dev/null @@ -1,41 +0,0 @@ -// Copyright (C) 2012 Collabora Ltd, author -// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only - -#include "qsystemsemaphore.h" -#include "qsystemsemaphore_p.h" - -#include - -#if QT_CONFIG(systemsemaphore) - -QT_BEGIN_NAMESPACE - -void QSystemSemaphorePrivate::setErrorString(const QString &function) -{ - Q_UNUSED(function); - Q_UNIMPLEMENTED(); -} - -key_t QSystemSemaphorePrivate::handle(QSystemSemaphore::AccessMode mode) -{ - Q_UNUSED(mode); - Q_UNIMPLEMENTED(); - return -1; -} - -void QSystemSemaphorePrivate::cleanHandle() -{ - Q_UNIMPLEMENTED(); -} - -bool QSystemSemaphorePrivate::modifySemaphore(int count) -{ - Q_UNUSED(count); - Q_UNIMPLEMENTED(); - return false; -} - - -QT_END_NAMESPACE - -#endif // QT_CONFIG(systemsemaphore) diff --git a/src/corelib/kernel/qsystemsemaphore_p.h b/src/corelib/kernel/qsystemsemaphore_p.h deleted file mode 100644 index 47c9cdfe..00000000 --- a/src/corelib/kernel/qsystemsemaphore_p.h +++ /dev/null @@ -1,82 +0,0 @@ -// 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 - -#ifndef QSYSTEMSEMAPHORE_P_H -#define QSYSTEMSEMAPHORE_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 "qsystemsemaphore.h" - -#if QT_CONFIG(systemsemaphore) - -#include "qcoreapplication.h" -#include "qsharedmemory_p.h" -#include -#ifdef QT_POSIX_IPC -# include -#endif - -QT_BEGIN_NAMESPACE - -class QSystemSemaphorePrivate -{ - -public: - - QString makeKeyFileName() - { - return QSharedMemoryPrivate::makePlatformSafeKey(key, QLatin1StringView("qipc_systemsem_")); - } - - inline void setError(QSystemSemaphore::SystemSemaphoreError e, const QString &message) - { error = e; errorString = message; } - inline void clearError() - { setError(QSystemSemaphore::NoError, QString()); } - -#ifdef Q_OS_WIN - Qt::HANDLE handle(QSystemSemaphore::AccessMode mode = QSystemSemaphore::Open); - void setErrorString(const QString &function); -#elif defined(QT_POSIX_IPC) - bool handle(QSystemSemaphore::AccessMode mode = QSystemSemaphore::Open); - void setErrorString(const QString &function); -#else - key_t handle(QSystemSemaphore::AccessMode mode = QSystemSemaphore::Open); - void setErrorString(const QString &function); -#endif - void cleanHandle(); - bool modifySemaphore(int count); - - QString key; - QString fileName; - int initialValue; -#ifdef Q_OS_WIN - Qt::HANDLE semaphore = nullptr; -#elif defined(QT_POSIX_IPC) - sem_t *semaphore = SEM_FAILED; - bool createdSemaphore = false; -#else - key_t unix_key = -1; - int semaphore = -1; - bool createdFile = false; - bool createdSemaphore = false; -#endif - QString errorString; - QSystemSemaphore::SystemSemaphoreError error = QSystemSemaphore::NoError; -}; - -QT_END_NAMESPACE - -#endif // QT_CONFIG(systemsemaphore) - -#endif // QSYSTEMSEMAPHORE_P_H - diff --git a/src/corelib/kernel/qsystemsemaphore_unix.cpp b/src/corelib/kernel/qsystemsemaphore_unix.cpp deleted file mode 100644 index 5cb89112..00000000 --- a/src/corelib/kernel/qsystemsemaphore_unix.cpp +++ /dev/null @@ -1,63 +0,0 @@ -// 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 "qsystemsemaphore.h" -#include "qsystemsemaphore_p.h" - -#include -#include - -#if QT_CONFIG(systemsemaphore) - -#include -#ifndef QT_POSIX_IPC -#include -#include -#endif -#include -#include - -#include "private/qcore_unix_p.h" - -QT_BEGIN_NAMESPACE - -void QSystemSemaphorePrivate::setErrorString(const QString &function) -{ - // EINVAL is handled in functions so they can give better error strings - switch (errno) { - case EPERM: - case EACCES: - errorString = QSystemSemaphore::tr("%1: permission denied").arg(function); - error = QSystemSemaphore::PermissionDenied; - break; - case EEXIST: - errorString = QSystemSemaphore::tr("%1: already exists").arg(function); - error = QSystemSemaphore::AlreadyExists; - break; - case ENOENT: - errorString = QSystemSemaphore::tr("%1: does not exist").arg(function); - error = QSystemSemaphore::NotFound; - break; - case ERANGE: - case ENOSPC: - errorString = QSystemSemaphore::tr("%1: out of resources").arg(function); - error = QSystemSemaphore::OutOfResources; - break; -#if defined(QT_POSIX_IPC) - case ENAMETOOLONG: - errorString = QSystemSemaphore::tr("%1: key too long").arg(function); - error = QSystemSemaphore::KeyError; - break; -#endif - default: - errorString = QSystemSemaphore::tr("%1: unknown error %2").arg(function).arg(errno); - error = QSystemSemaphore::UnknownError; -#if defined QSYSTEMSEMAPHORE_DEBUG - qDebug() << errorString << "key" << key << "errno" << errno << EINVAL; -#endif - } -} - -QT_END_NAMESPACE - -#endif // QT_CONFIG(systemsemaphore) diff --git a/src/corelib/kernel/qtestsupport_core.cpp b/src/corelib/kernel/qtestsupport_core.cpp index 3fa7f346..bc461114 100644 --- a/src/corelib/kernel/qtestsupport_core.cpp +++ b/src/corelib/kernel/qtestsupport_core.cpp @@ -3,9 +3,7 @@ #include "qtestsupport_core.h" -#ifdef Q_OS_WIN -#include -#endif +#include QT_BEGIN_NAMESPACE @@ -17,9 +15,11 @@ QT_BEGIN_NAMESPACE \a ms must be greater than 0. - \b {Note:} The qSleep() function calls either \c nanosleep() on - unix or \c Sleep() on windows, so the accuracy of time spent in - qSleep() depends on the operating system. + \note Starting from Qt 6.7, this function is implemented using + \c {std::this_thread::sleep_for}, so the accuracy of time spent depends + on the Standard Library implementation. Before Qt 6.7 this function called + either \c nanosleep() on Unix or \c Sleep() on Windows, so the accuracy of + time spent in this function depended on the operating system. Example: \snippet code/src_qtestlib_qtestcase.cpp 23 @@ -29,13 +29,7 @@ QT_BEGIN_NAMESPACE Q_CORE_EXPORT void QTest::qSleep(int ms) { Q_ASSERT(ms > 0); - -#if defined(Q_OS_WIN) - Sleep(uint(ms)); -#else - struct timespec ts = { time_t(ms / 1000), (ms % 1000) * 1000 * 1000 }; - nanosleep(&ts, nullptr); -#endif + std::this_thread::sleep_for(std::chrono::milliseconds{ms}); } /*! \fn template bool QTest::qWaitFor(Functor predicate, int timeout) diff --git a/src/corelib/kernel/qtimer.cpp b/src/corelib/kernel/qtimer.cpp index e151b041..282de8f8 100644 --- a/src/corelib/kernel/qtimer.cpp +++ b/src/corelib/kernel/qtimer.cpp @@ -8,6 +8,7 @@ #include "qabstracteventdispatcher.h" #include "qcoreapplication.h" #include "qcoreapplication_p.h" +#include "qdeadlinetimer.h" #include "qmetaobject_p.h" #include "qobject_p.h" #include "qproperty_p.h" @@ -188,7 +189,7 @@ void QTimer::start() Q_D(QTimer); if (d->id != QTimerPrivate::INV_TIMER) // stop running timer stop(); - d->id = QObject::startTimer(d->inter, d->type); + d->id = QObject::startTimer(std::chrono::milliseconds{d->inter}, d->type); d->isActiveData.notify(); } @@ -199,7 +200,13 @@ void QTimer::start() If the timer is already running, it will be \l{QTimer::stop()}{stopped} and restarted. - If \l singleShot is true, the timer will be activated only once. + If \l singleShot is true, the timer will be activated only once. This is + equivalent to: + + \code + timer.setInterval(msec); + timer.start(); + \endcode \note Keeping the event loop busy with a zero-timer is bound to cause trouble and highly erratic behavior of the UI. @@ -249,12 +256,14 @@ void QTimer::timerEvent(QTimerEvent *e) class QSingleShotTimer : public QObject { Q_OBJECT - int timerId; + int timerId = -1; public: ~QSingleShotTimer(); QSingleShotTimer(int msec, Qt::TimerType timerType, const QObject *r, const char * m); QSingleShotTimer(int msec, Qt::TimerType timerType, const QObject *r, QtPrivate::QSlotObjectBase *slotObj); + void startTimerForReceiver(int msec, Qt::TimerType timerType, const QObject *receiver); + Q_SIGNALS: void timeout(); protected: @@ -264,27 +273,20 @@ protected: QSingleShotTimer::QSingleShotTimer(int msec, Qt::TimerType timerType, const QObject *r, const char *member) : QObject(QAbstractEventDispatcher::instance()) { - timerId = startTimer(msec, timerType); connect(this, SIGNAL(timeout()), r, member); + + startTimerForReceiver(msec, timerType, r); } QSingleShotTimer::QSingleShotTimer(int msec, Qt::TimerType timerType, const QObject *r, QtPrivate::QSlotObjectBase *slotObj) : QObject(QAbstractEventDispatcher::instance()) { - timerId = startTimer(msec, timerType); - int signal_index = QMetaObjectPrivate::signalOffset(&staticMetaObject); Q_ASSERT(QMetaObjectPrivate::signal(&staticMetaObject, signal_index).name() == "timeout"); QObjectPrivate::connectImpl(this, signal_index, r ? r : this, nullptr, slotObj, Qt::AutoConnection, nullptr, &staticMetaObject); - // ### Why is this here? Why doesn't the case above need it? - if (r && thread() != r->thread()) { - // Avoid leaking the QSingleShotTimer instance in case the application exits before the timer fires - connect(QCoreApplication::instance(), &QCoreApplication::aboutToQuit, this, &QObject::deleteLater); - setParent(nullptr); - moveToThread(r->thread()); - } + startTimerForReceiver(msec, timerType, r); } QSingleShotTimer::~QSingleShotTimer() @@ -293,6 +295,32 @@ QSingleShotTimer::~QSingleShotTimer() killTimer(timerId); } +/* + Move the timer, and the dispatching and handling of the timer event, into + the same thread as where it will be handled, so that it fires reliably even + if the thread that set up the timer is busy. +*/ +void QSingleShotTimer::startTimerForReceiver(int msec, Qt::TimerType timerType, const QObject *receiver) +{ + if (receiver && receiver->thread() != thread()) { + // Avoid leaking the QSingleShotTimer instance in case the application exits before the timer fires + connect(QCoreApplication::instance(), &QCoreApplication::aboutToQuit, this, &QObject::deleteLater); + setParent(nullptr); + moveToThread(receiver->thread()); + + QDeadlineTimer deadline(std::chrono::milliseconds{msec}, timerType); + QMetaObject::invokeMethod(this, [this, deadline, timerType]{ + if (deadline.hasExpired()) + emit timeout(); + else + timerId = startTimer(std::chrono::milliseconds{deadline.remainingTime()}, timerType); + }, Qt::QueuedConnection); + } else { + timerId = startTimer(std::chrono::milliseconds{msec}, timerType); + } +} + + void QSingleShotTimer::timerEvent(QTimerEvent *) { // need to kill the timer _before_ we emit timeout() in case the @@ -420,125 +448,28 @@ void QTimer::singleShot(int msec, Qt::TimerType timerType, const QObject *receiv } } -/*! \fn template void QTimer::singleShot(int msec, const QObject *receiver, PointerToMemberFunction method) - +/*! \fn template void QTimer::singleShot(Duration msec, const QObject *context, Functor &&functor) + \fn template void QTimer::singleShot(Duration msec, Qt::TimerType timerType, const QObject *context, Functor &&functor) + \fn template void QTimer::singleShot(Duration msec, Functor &&functor) + \fn template void QTimer::singleShot(Duration msec, Qt::TimerType timerType, Functor &&functor) \since 5.4 - \overload \reentrant - This static function calls a member function of a QObject after a given time interval. + This static function calls \a functor after \a msec milliseconds. It is very convenient to use this function because you do not need to bother with a \l{QObject::timerEvent()}{timerEvent} or create a local QTimer object. - The \a receiver is the receiving object and the \a method is the member function. The - time interval is \a msec milliseconds. + If \a context is specified, then the \a functor will be called only if the + \a context object has not been destroyed before the interval occurs. The functor + will then be run the thread of \a context. The context's thread must have a + running Qt event loop. - If \a receiver is destroyed before the interval occurs, the method will not be called. - The function will be run in the thread of \a receiver. The receiver's thread must have - a running Qt event loop. + If \a functor is a member + function of \a context, then the function will be called on the object. - \sa start() -*/ - -/*! \fn template void QTimer::singleShot(int msec, Qt::TimerType timerType, const QObject *receiver, PointerToMemberFunction method) - - \since 5.4 - - \overload - \reentrant - This static function calls a member function of a QObject after a given time interval. - - It is very convenient to use this function because you do not need - to bother with a \l{QObject::timerEvent()}{timerEvent} or - create a local QTimer object. - - The \a receiver is the receiving object and the \a method is the member function. The - time interval is \a msec milliseconds. The \a timerType affects the - accuracy of the timer. - - If \a receiver is destroyed before the interval occurs, the method will not be called. - The function will be run in the thread of \a receiver. The receiver's thread must have - a running Qt event loop. - - \sa start() -*/ - -/*! \fn template void QTimer::singleShot(int msec, Functor functor) - - \since 5.4 - - \overload - \reentrant - This static function calls \a functor after a given time interval. - - It is very convenient to use this function because you do not need - to bother with a \l{QObject::timerEvent()}{timerEvent} or - create a local QTimer object. - - The time interval is \a msec milliseconds. - - \sa start() -*/ - -/*! \fn template void QTimer::singleShot(int msec, Qt::TimerType timerType, Functor functor) - - \since 5.4 - - \overload - \reentrant - This static function calls \a functor after a given time interval. - - It is very convenient to use this function because you do not need - to bother with a \l{QObject::timerEvent()}{timerEvent} or - create a local QTimer object. - - The time interval is \a msec milliseconds. The \a timerType affects the - accuracy of the timer. - - \sa start() -*/ - -/*! \fn template void QTimer::singleShot(int msec, const QObject *context, Functor functor) - - \since 5.4 - - \overload - \reentrant - This static function calls \a functor after a given time interval. - - It is very convenient to use this function because you do not need - to bother with a \l{QObject::timerEvent()}{timerEvent} or - create a local QTimer object. - - The time interval is \a msec milliseconds. - - If \a context is destroyed before the interval occurs, the method will not be called. - The function will be run in the thread of \a context. The context's thread must have - a running Qt event loop. - - \sa start() -*/ - -/*! \fn template void QTimer::singleShot(int msec, Qt::TimerType timerType, const QObject *context, Functor functor) - - \since 5.4 - - \overload - \reentrant - This static function calls \a functor after a given time interval. - - It is very convenient to use this function because you do not need - to bother with a \l{QObject::timerEvent()}{timerEvent} or - create a local QTimer object. - - The time interval is \a msec milliseconds. The \a timerType affects the - accuracy of the timer. - - If \a context is destroyed before the interval occurs, the method will not be called. - The function will be run in the thread of \a context. The context's thread must have - a running Qt event loop. + The \a msec parameter can be an \c int or a \c std::chrono::milliseconds value. \sa start() */ @@ -583,7 +514,6 @@ void QTimer::singleShot(int msec, Qt::TimerType timerType, const QObject *receiv /*! \fn template QMetaObject::Connection QTimer::callOnTimeout(Functor &&slot) \since 5.12 - \overload Creates a connection from the timer's timeout() signal to \a slot. Returns a handle to the connection. @@ -597,7 +527,7 @@ void QTimer::singleShot(int msec, Qt::TimerType timerType, const QObject *receiv */ /*! - \fn template QMetaObject::Connection QTimer::callOnTimeout(const QObject *context, Functor slot, Qt::ConnectionType connectionType = Qt::AutoConnection) + \fn template QMetaObject::Connection QTimer::callOnTimeout(const QObject *context, Functor &&slot, Qt::ConnectionType connectionType = Qt::AutoConnection) \since 5.12 \overload callOnTimeout() @@ -610,20 +540,6 @@ void QTimer::singleShot(int msec, Qt::TimerType timerType, const QObject *receiv \sa QObject::connect(), timeout() */ -/*! - \fn template QMetaObject::Connection QTimer::callOnTimeout(const QObject *receiver, MemberFunction *slot, Qt::ConnectionType connectionType = Qt::AutoConnection) - \since 5.12 - \overload callOnTimeout() - - Creates a connection from the timeout() signal to the \a slot in the \a receiver object. Returns - a handle to the connection. - - This method is provided for convenience. It's equivalent to calling - \c {QObject::connect(timer, &QTimer::timeout, receiver, slot, connectionType)}. - - \sa QObject::connect(), timeout() -*/ - /*! \fn void QTimer::start(std::chrono::milliseconds msec) \since 5.8 @@ -634,7 +550,13 @@ void QTimer::singleShot(int msec, Qt::TimerType timerType, const QObject *receiv If the timer is already running, it will be \l{QTimer::stop()}{stopped} and restarted. - If \l singleShot is true, the timer will be activated only once. + If \l singleShot is true, the timer will be activated only once. This is + equivalent to: + + \code + timer.setInterval(msec); + timer.start(); + \endcode */ /*! @@ -705,7 +627,7 @@ void QTimer::setInterval(int msec) d->inter.setValueBypassingBindings(msec); if (d->id != QTimerPrivate::INV_TIMER) { // create new timer QObject::killTimer(d->id); // restart timer - d->id = QObject::startTimer(msec, d->type); + d->id = QObject::startTimer(std::chrono::milliseconds{msec}, d->type); // No need to call markDirty() for d->isActiveData here, // as timer state actually does not change } diff --git a/src/corelib/kernel/qtimer.h b/src/corelib/kernel/qtimer.h index cddcdce3..b01cfdec 100644 --- a/src/corelib/kernel/qtimer.h +++ b/src/corelib/kernel/qtimer.h @@ -49,82 +49,50 @@ public: static void singleShot(int msec, const QObject *receiver, const char *member); static void singleShot(int msec, Qt::TimerType timerType, const QObject *receiver, const char *member); + // singleShot with context + template + static inline void singleShot(Duration interval, #ifdef Q_QDOC - template - static void singleShot(int msec, const QObject *receiver, PointerToMemberFunction method); - template - static void singleShot(int msec, Qt::TimerType timerType, const QObject *receiver, PointerToMemberFunction method); - template - static void singleShot(int msec, Functor functor); - template - static void singleShot(int msec, Qt::TimerType timerType, Functor functor); - template - static void singleShot(int msec, const QObject *context, Functor functor); - template - static void singleShot(int msec, Qt::TimerType timerType, const QObject *context, Functor functor); - template - QMetaObject::Connection callOnTimeout(Functor slot); - template - QMetaObject::Connection callOnTimeout(const QObject *context, Functor slot, Qt::ConnectionType connectionType = Qt::AutoConnection); - template - QMetaObject::Connection callOnTimeout(const QObject *receiver, MemberFunction *slot, Qt::ConnectionType connectionType = Qt::AutoConnection); + const QObject *receiver, #else - // singleShot to a QObject slot - template - static inline void singleShot(Duration interval, const typename QtPrivate::FunctionPointer::Object *receiver, Func1 slot) + const typename QtPrivate::ContextTypeForFunctor::ContextType *receiver, +#endif + + Functor &&slot) { - singleShot(interval, defaultTypeFor(interval), receiver, slot); + singleShot(interval, defaultTypeFor(interval), receiver, std::forward(slot)); } - template - static inline void singleShot(Duration interval, Qt::TimerType timerType, const typename QtPrivate::FunctionPointer::Object *receiver, - Func1 slot) + template + static inline void singleShot(Duration interval, Qt::TimerType timerType, +#ifdef Q_QDOC + const QObject *receiver, +#else + const typename QtPrivate::ContextTypeForFunctor::ContextType *receiver, +#endif + Functor &&slot) { - typedef QtPrivate::FunctionPointer SlotType; - - //compilation error if the slot has arguments. - static_assert(int(SlotType::ArgumentCount) == 0, - "The slot must not have any arguments."); - + using Prototype = void(*)(); singleShotImpl(interval, timerType, receiver, - new QtPrivate::QSlotObject(slot)); + QtPrivate::makeCallableObject(std::forward(slot))); } - // singleShot to a functor or function pointer (without context) - template - static inline typename std::enable_if::IsPointerToMemberFunction && - !std::is_same::value, void>::type - singleShot(Duration interval, Func1 slot) + // singleShot without context + template + static inline void singleShot(Duration interval, Functor &&slot) { - singleShot(interval, defaultTypeFor(interval), nullptr, std::move(slot)); + singleShot(interval, defaultTypeFor(interval), nullptr, std::forward(slot)); } - template - static inline typename std::enable_if::IsPointerToMemberFunction && - !std::is_same::value, void>::type - singleShot(Duration interval, Qt::TimerType timerType, Func1 slot) + template + static inline void singleShot(Duration interval, Qt::TimerType timerType, Functor &&slot) { - singleShot(interval, timerType, nullptr, std::move(slot)); - } - // singleShot to a functor or function pointer (with context) - template - static inline typename std::enable_if::IsPointerToMemberFunction && - !std::is_same::value, void>::type - singleShot(Duration interval, const QObject *context, Func1 slot) - { - singleShot(interval, defaultTypeFor(interval), context, std::move(slot)); - } - template - static inline typename std::enable_if::IsPointerToMemberFunction && - !std::is_same::value, void>::type - singleShot(Duration interval, Qt::TimerType timerType, const QObject *context, Func1 slot) - { - //compilation error if the slot has arguments. - typedef QtPrivate::FunctionPointer SlotType; - static_assert(int(SlotType::ArgumentCount) <= 0, "The slot must not have any arguments."); - - singleShotImpl(interval, timerType, context, - new QtPrivate::QFunctorSlotObject::Value, void>(std::move(slot))); + singleShot(interval, timerType, nullptr, std::forward(slot)); } +#ifdef Q_QDOC + template + QMetaObject::Connection callOnTimeout(Functor &&slot); + template + QMetaObject::Connection callOnTimeout(const QObject *context, Functor &&slot, Qt::ConnectionType connectionType = Qt::AutoConnection); +#else template QMetaObject::Connection callOnTimeout(Args && ...args) { diff --git a/src/corelib/kernel/qtimerinfo_unix.cpp b/src/corelib/kernel/qtimerinfo_unix.cpp index 7ec23de4..01238d8d 100644 --- a/src/corelib/kernel/qtimerinfo_unix.cpp +++ b/src/corelib/kernel/qtimerinfo_unix.cpp @@ -17,6 +17,8 @@ #include +using namespace std::chrono; + QT_BEGIN_NAMESPACE Q_CORE_EXPORT bool qt_disable_lowpriority_timers=false; @@ -28,25 +30,6 @@ Q_CORE_EXPORT bool qt_disable_lowpriority_timers=false; QTimerInfoList::QTimerInfoList() { -#if (_POSIX_MONOTONIC_CLOCK-0 <= 0) && !defined(Q_OS_MAC) - if (!QElapsedTimer::isMonotonic()) { - // not using monotonic timers, initialize the timeChanged() machinery - previousTime = qt_gettime(); - - tms unused; - previousTicks = times(&unused); - - ticksPerSecond = sysconf(_SC_CLK_TCK); - msPerTick = 1000/ticksPerSecond; - } else { - // detected monotonic timers - previousTime.tv_sec = previousTime.tv_nsec = 0; - previousTicks = 0; - ticksPerSecond = 0; - msPerTick = 0; - } -#endif - firstTimerInfo = nullptr; } @@ -55,85 +38,6 @@ timespec QTimerInfoList::updateCurrentTime() return (currentTime = qt_gettime()); } -#if ((_POSIX_MONOTONIC_CLOCK-0 <= 0) && !defined(Q_OS_MAC) && !defined(Q_OS_INTEGRITY)) || defined(QT_BOOTSTRAPPED) - -timespec qAbsTimespec(const timespec &t) -{ - timespec tmp = t; - if (tmp.tv_sec < 0) { - tmp.tv_sec = -tmp.tv_sec - 1; - tmp.tv_nsec -= 1000000000; - } - if (tmp.tv_sec == 0 && tmp.tv_nsec < 0) { - tmp.tv_nsec = -tmp.tv_nsec; - } - return normalizedTimespec(tmp); -} - -/* - Returns \c true if the real time clock has changed by more than 10% - relative to the processor time since the last time this function was - called. This presumably means that the system time has been changed. - - If /a delta is nonzero, delta is set to our best guess at how much the system clock was changed. -*/ -bool QTimerInfoList::timeChanged(timespec *delta) -{ - struct tms unused; - clock_t currentTicks = times(&unused); - - clock_t elapsedTicks = currentTicks - previousTicks; - timespec elapsedTime = currentTime - previousTime; - - timespec elapsedTimeTicks; - elapsedTimeTicks.tv_sec = elapsedTicks / ticksPerSecond; - elapsedTimeTicks.tv_nsec = (((elapsedTicks * 1000) / ticksPerSecond) % 1000) * 1000 * 1000; - - timespec dummy; - if (!delta) - delta = &dummy; - *delta = elapsedTime - elapsedTimeTicks; - - previousTicks = currentTicks; - previousTime = currentTime; - - // If tick drift is more than 10% off compared to realtime, we assume that the clock has - // been set. Of course, we have to allow for the tick granularity as well. - timespec tickGranularity; - tickGranularity.tv_sec = 0; - tickGranularity.tv_nsec = msPerTick * 1000 * 1000; - return elapsedTimeTicks < ((qAbsTimespec(*delta) - tickGranularity) * 10); -} - -/* - repair broken timer -*/ -void QTimerInfoList::timerRepair(const timespec &diff) -{ - // repair all timers - for (int i = 0; i < size(); ++i) { - QTimerInfo *t = at(i); - t->timeout = t->timeout + diff; - } -} - -void QTimerInfoList::repairTimersIfNeeded() -{ - if (QElapsedTimer::isMonotonic()) - return; - timespec delta; - if (timeChanged(&delta)) - timerRepair(delta); -} - -#else // !(_POSIX_MONOTONIC_CLOCK-0 <= 0) && !defined(QT_BOOTSTRAPPED) - -void QTimerInfoList::repairTimersIfNeeded() -{ -} - -#endif - /* insert timer info into list */ @@ -148,19 +52,6 @@ void QTimerInfoList::timerInsert(QTimerInfo *ti) insert(index+1, ti); } -inline timespec &operator+=(timespec &t1, int ms) -{ - t1.tv_sec += ms / 1000; - t1.tv_nsec += ms % 1000 * 1000 * 1000; - return normalizedTimespec(t1); -} - -inline timespec operator+(const timespec &t1, int ms) -{ - timespec t2 = t1; - return t2 += ms; -} - static constexpr timespec roundToMillisecond(timespec val) { // always round up @@ -178,6 +69,25 @@ static_assert(roundToMillisecond({0, 1'000'000}) == timespec{0, 1'000'000}); static_assert(roundToMillisecond({0, 999'999'999}) == timespec{1, 0}); static_assert(roundToMillisecond({1, 0}) == timespec{1, 0}); +static constexpr seconds roundToSecs(milliseconds msecs) +{ + // The very coarse timer is based on full second precision, so we want to + // round the interval to the closest second, rounding 500ms up to 1s. + // + // std::chrono::round() wouldn't work with all multiples of 500 because for the + // middle point it would round to even: + // value round() wanted + // 500 0 1 + // 1500 2 2 + // 2500 2 3 + + auto secs = duration_cast(msecs); + const milliseconds frac = msecs - secs; + if (frac >= 500ms) + ++secs; + return secs; +} + #ifdef QTIMERINFO_DEBUG QDebug operator<<(QDebug s, timeval tv) { @@ -194,7 +104,7 @@ QDebug operator<<(QDebug s, Qt::TimerType t) } #endif -static void calculateCoarseTimerTimeout(QTimerInfo *t, timespec currentTime) +static void calculateCoarseTimerTimeout(QTimerInfo *t, timespec now) { // The coarse timer works like this: // - interval under 40 ms: round to even @@ -212,107 +122,114 @@ static void calculateCoarseTimerTimeout(QTimerInfo *t, timespec currentTime) // // The objective is to make most timers wake up at the same time, thereby reducing CPU wakeups. - uint interval = uint(t->interval); - Q_ASSERT(interval >= 20); + Q_ASSERT(t->interval >= 20ms); + + auto recalculate = [&](const milliseconds fracMsec) { + if (fracMsec == 1000ms) { + ++t->timeout.tv_sec; + t->timeout.tv_nsec = 0; + } else { + t->timeout.tv_nsec = nanoseconds{fracMsec}.count(); + } + + if (t->timeout < now) + t->timeout += t->interval; + }; + // Calculate how much we can round and still keep within 5% error - uint absMaxRounding = interval / 20; + const milliseconds absMaxRounding = t->interval / 20; - using namespace std::chrono; - uint msec = duration_cast(nanoseconds{t->timeout.tv_nsec}).count(); + auto fracMsec = duration_cast(nanoseconds{t->timeout.tv_nsec}); - if (interval < 100 && interval != 25 && interval != 50 && interval != 75) { + if (t->interval < 100ms && t->interval != 25ms && t->interval != 50ms && t->interval != 75ms) { + auto fracCount = fracMsec.count(); // special mode for timers of less than 100 ms - if (interval < 50) { + if (t->interval < 50ms) { // round to even // round towards multiples of 50 ms - bool roundUp = (msec % 50) >= 25; - msec >>= 1; - msec |= uint(roundUp); - msec <<= 1; + bool roundUp = (fracCount % 50) >= 25; + fracCount >>= 1; + fracCount |= roundUp; + fracCount <<= 1; } else { // round to multiple of 4 // round towards multiples of 100 ms - bool roundUp = (msec % 100) >= 50; - msec >>= 2; - msec |= uint(roundUp); - msec <<= 2; + bool roundUp = (fracCount % 100) >= 50; + fracCount >>= 2; + fracCount |= roundUp; + fracCount <<= 2; } - } else { - uint min = qMax(0, msec - absMaxRounding); - uint max = qMin(1000u, msec + absMaxRounding); + fracMsec = milliseconds{fracCount}; + recalculate(fracMsec); + return; + } - // find the boundary that we want, according to the rules above - // extra rules: - // 1) whatever the interval, we'll take any round-to-the-second timeout - if (min == 0) { - msec = 0; - goto recalculate; - } else if (max == 1000) { - msec = 1000; - goto recalculate; - } + milliseconds min = std::max(0ms, fracMsec - absMaxRounding); + milliseconds max = std::min(1000ms, fracMsec + absMaxRounding); - uint wantedBoundaryMultiple; + // find the boundary that we want, according to the rules above + // extra rules: + // 1) whatever the interval, we'll take any round-to-the-second timeout + if (min == 0ms) { + fracMsec = 0ms; + recalculate(fracMsec); + return; + } else if (max == 1000ms) { + fracMsec = 1000ms; + recalculate(fracMsec); + return; + } - // 2) if the interval is a multiple of 500 ms and > 5000 ms, we'll always round - // towards a round-to-the-second - // 3) if the interval is a multiple of 500 ms, we'll round towards the nearest - // multiple of 500 ms - if ((interval % 500) == 0) { - if (interval >= 5000) { - msec = msec >= 500 ? max : min; - goto recalculate; - } else { - wantedBoundaryMultiple = 500; - } - } else if ((interval % 50) == 0) { - // 4) same for multiples of 250, 200, 100, 50 - uint mult50 = interval / 50; - if ((mult50 % 4) == 0) { - // multiple of 200 - wantedBoundaryMultiple = 200; - } else if ((mult50 % 2) == 0) { - // multiple of 100 - wantedBoundaryMultiple = 100; - } else if ((mult50 % 5) == 0) { - // multiple of 250 - wantedBoundaryMultiple = 250; - } else { - // multiple of 50 - wantedBoundaryMultiple = 50; - } + milliseconds wantedBoundaryMultiple{25}; + + // 2) if the interval is a multiple of 500 ms and > 5000 ms, we'll always round + // towards a round-to-the-second + // 3) if the interval is a multiple of 500 ms, we'll round towards the nearest + // multiple of 500 ms + if ((t->interval % 500) == 0ms) { + if (t->interval >= 5s) { + fracMsec = fracMsec >= 500ms ? max : min; + recalculate(fracMsec); + return; } else { - wantedBoundaryMultiple = 25; + wantedBoundaryMultiple = 500ms; + } + } else if ((t->interval % 50) == 0ms) { + // 4) same for multiples of 250, 200, 100, 50 + milliseconds mult50 = t->interval / 50; + if ((mult50 % 4) == 0ms) { + // multiple of 200 + wantedBoundaryMultiple = 200ms; + } else if ((mult50 % 2) == 0ms) { + // multiple of 100 + wantedBoundaryMultiple = 100ms; + } else if ((mult50 % 5) == 0ms) { + // multiple of 250 + wantedBoundaryMultiple = 250ms; + } else { + // multiple of 50 + wantedBoundaryMultiple = 50ms; } - - uint base = msec / wantedBoundaryMultiple * wantedBoundaryMultiple; - uint middlepoint = base + wantedBoundaryMultiple / 2; - if (msec < middlepoint) - msec = qMax(base, min); - else - msec = qMin(base + wantedBoundaryMultiple, max); } -recalculate: - if (msec == 1000u) { - ++t->timeout.tv_sec; - t->timeout.tv_nsec = 0; - } else { - t->timeout.tv_nsec = nanoseconds{milliseconds{msec}}.count(); - } + milliseconds base = (fracMsec / wantedBoundaryMultiple) * wantedBoundaryMultiple; + milliseconds middlepoint = base + wantedBoundaryMultiple / 2; + if (fracMsec < middlepoint) + fracMsec = qMax(base, min); + else + fracMsec = qMin(base + wantedBoundaryMultiple, max); - if (t->timeout < currentTime) - t->timeout += interval; + recalculate(fracMsec); } -static void calculateNextTimeout(QTimerInfo *t, timespec currentTime) +static void calculateNextTimeout(QTimerInfo *t, timespec now) { switch (t->timerType) { case Qt::PreciseTimer: case Qt::CoarseTimer: t->timeout += t->interval; - if (t->timeout < currentTime) { - t->timeout = currentTime; + if (t->timeout < now) { + t->timeout = now; t->timeout += t->interval; } #ifdef QTIMERINFO_DEBUG @@ -323,14 +240,15 @@ static void calculateNextTimeout(QTimerInfo *t, timespec currentTime) } #endif if (t->timerType == Qt::CoarseTimer) - calculateCoarseTimerTimeout(t, currentTime); + calculateCoarseTimerTimeout(t, now); return; case Qt::VeryCoarseTimer: - // we don't need to take care of the microsecond component of t->interval - t->timeout.tv_sec += t->interval; - if (t->timeout.tv_sec <= currentTime.tv_sec) - t->timeout.tv_sec = currentTime.tv_sec + t->interval; + // t->interval already rounded to full seconds in registerTimer() + const auto secs = duration_cast(t->interval).count(); + t->timeout.tv_sec += secs; + if (t->timeout.tv_sec <= now.tv_sec) + t->timeout.tv_sec = now.tv_sec + secs; #ifdef QTIMERINFO_DEBUG t->expected.tv_sec += t->interval; if (t->expected.tv_sec <= currentTime.tv_sec) @@ -353,29 +271,19 @@ static void calculateNextTimeout(QTimerInfo *t, timespec currentTime) */ bool QTimerInfoList::timerWait(timespec &tm) { - timespec currentTime = updateCurrentTime(); - repairTimersIfNeeded(); + timespec now = updateCurrentTime(); + auto isWaiting = [](QTimerInfo *tinfo) { return !tinfo->activateRef; }; // Find first waiting timer not already active - QTimerInfo *t = nullptr; - for (QTimerInfoList::const_iterator it = constBegin(); it != constEnd(); ++it) { - if (!(*it)->activateRef) { - t = *it; - break; - } - } + auto it = std::find_if(cbegin(), cend(), isWaiting); + if (it == cend()) + return false; - if (!t) - return false; - - if (currentTime < t->timeout) { - // time to wait - tm = roundToMillisecond(t->timeout - currentTime); - } else { - // no time to wait - tm.tv_sec = 0; - tm.tv_nsec = 0; - } + QTimerInfo *t = *it; + if (now < t->timeout) // Time to wait + tm = roundToMillisecond(t->timeout - now); + else // No time to wait + tm = {0, 0}; return true; } @@ -387,32 +295,35 @@ bool QTimerInfoList::timerWait(timespec &tm) */ qint64 QTimerInfoList::timerRemainingTime(int timerId) { - timespec currentTime = updateCurrentTime(); - repairTimersIfNeeded(); - timespec tm = {0, 0}; + return remainingDuration(timerId).count(); +} - for (const auto *t : std::as_const(*this)) { - if (t->id == timerId) { - if (currentTime < t->timeout) { - // time to wait - tm = roundToMillisecond(t->timeout - currentTime); - using namespace std::chrono; - const auto dur = duration_cast(seconds{tm.tv_sec} + nanoseconds{tm.tv_nsec}); - return dur.count(); - } else { - return 0; - } - } +milliseconds QTimerInfoList::remainingDuration(int timerId) +{ + timespec now = updateCurrentTime(); + + auto it = findTimerById(timerId); + if (it == cend()) { +#ifndef QT_NO_DEBUG + qWarning("QTimerInfoList::timerRemainingTime: timer id %i not found", timerId); +#endif + return milliseconds{-1}; } -#ifndef QT_NO_DEBUG - qWarning("QTimerInfoList::timerRemainingTime: timer id %i not found", timerId); -#endif - - return -1; + const QTimerInfo *t = *it; + if (now < t->timeout) // time to wait + return timespecToChronoMs(roundToMillisecond(t->timeout - now)); + else + return milliseconds{0}; } void QTimerInfoList::registerTimer(int timerId, qint64 interval, Qt::TimerType timerType, QObject *object) +{ + registerTimer(timerId, milliseconds{interval}, timerType, object); +} + +void QTimerInfoList::registerTimer(int timerId, milliseconds interval, + Qt::TimerType timerType, QObject *object) { QTimerInfo *t = new QTimerInfo; t->id = timerId; @@ -435,30 +346,26 @@ void QTimerInfoList::registerTimer(int timerId, qint64 interval, Qt::TimerType t // so our boundaries are 20 ms and 20 s // below 20 ms, 5% inaccuracy is below 1 ms, so we convert to high precision // above 20 s, 5% inaccuracy is above 1 s, so we convert to VeryCoarseTimer - if (interval >= 20000) { + if (interval >= 20s) { t->timerType = Qt::VeryCoarseTimer; } else { t->timeout = expected; - if (interval <= 20) { + if (interval <= 20ms) { t->timerType = Qt::PreciseTimer; // no adjustment is necessary - } else if (interval <= 20000) { + } else if (interval <= 20s) { calculateCoarseTimerTimeout(t, currentTime); } break; } Q_FALLTHROUGH(); case Qt::VeryCoarseTimer: - // the very coarse timer is based on full second precision, - // so we keep the interval in seconds (round to closest second) - t->interval /= 500; - t->interval += 1; - t->interval >>= 1; - t->timeout.tv_sec = currentTime.tv_sec + t->interval; + const seconds secs = roundToSecs(t->interval); + t->interval = secs; + t->timeout.tv_sec = currentTime.tv_sec + secs.count(); t->timeout.tv_nsec = 0; // if we're past the half-second mark, increase the timeout again - using namespace std::chrono; if (currentTime.tv_nsec > nanoseconds{500ms}.count()) ++t->timeout.tv_sec; } @@ -477,22 +384,19 @@ void QTimerInfoList::registerTimer(int timerId, qint64 interval, Qt::TimerType t bool QTimerInfoList::unregisterTimer(int timerId) { + auto it = findTimerById(timerId); + if (it == cend()) + return false; // id not found + // set timer inactive - for (int i = 0; i < size(); ++i) { - QTimerInfo *t = at(i); - if (t->id == timerId) { - // found it - removeAt(i); - if (t == firstTimerInfo) - firstTimerInfo = nullptr; - if (t->activateRef) - *(t->activateRef) = nullptr; - delete t; - return true; - } - } - // id not found - return false; + QTimerInfo *t = *it; + if (t == firstTimerInfo) + firstTimerInfo = nullptr; + if (t->activateRef) + *(t->activateRef) = nullptr; + delete t; + erase(it); + return true; } bool QTimerInfoList::unregisterTimers(QObject *object) @@ -519,15 +423,9 @@ bool QTimerInfoList::unregisterTimers(QObject *object) QList QTimerInfoList::registeredTimers(QObject *object) const { QList list; - for (int i = 0; i < size(); ++i) { - const QTimerInfo * const t = at(i); - if (t->obj == object) { - list << QAbstractEventDispatcher::TimerInfo(t->id, - (t->timerType == Qt::VeryCoarseTimer - ? t->interval * 1000 - : t->interval), - t->timerType); - } + for (const QTimerInfo *const t : std::as_const(*this)) { + if (t->obj == object) + list.emplaceBack(t->id, t->interval.count(), t->timerType); } return list; } @@ -540,28 +438,24 @@ int QTimerInfoList::activateTimers() if (qt_disable_lowpriority_timers || isEmpty()) return 0; // nothing to do - int n_act = 0, maxCount = 0; firstTimerInfo = nullptr; - timespec currentTime = updateCurrentTime(); - // qDebug() << "Thread" << QThread::currentThreadId() << "woken up at" << currentTime; - repairTimersIfNeeded(); - - + timespec now = updateCurrentTime(); + // qDebug() << "Thread" << QThread::currentThreadId() << "woken up at" << now; // Find out how many timer have expired - for (QTimerInfoList::const_iterator it = constBegin(); it != constEnd(); ++it) { - if (currentTime < (*it)->timeout) - break; - maxCount++; - } + auto stillActive = [&now](const QTimerInfo *t) { return now < t->timeout; }; + // Find first one still active (list is sorted by timeout) + auto it = std::find_if(cbegin(), cend(), stillActive); + auto maxCount = it - cbegin(); + int n_act = 0; //fire the timers. while (maxCount--) { if (isEmpty()) break; QTimerInfo *currentTimerInfo = constFirst(); - if (currentTime < currentTimerInfo->timeout) + if (now < currentTimerInfo->timeout) break; // no timer has expired if (!firstTimerInfo) { @@ -598,11 +492,11 @@ int QTimerInfoList::activateTimers() #endif // determine next timeout time - calculateNextTimeout(currentTimerInfo, currentTime); + calculateNextTimeout(currentTimerInfo, now); // reinsert timer timerInsert(currentTimerInfo); - if (currentTimerInfo->interval > 0) + if (currentTimerInfo->interval > 0ms) n_act++; // Send event, but don't allow it to recurse: diff --git a/src/corelib/kernel/qtimerinfo_unix_p.h b/src/corelib/kernel/qtimerinfo_unix_p.h index 3d80c929..fe3257b5 100644 --- a/src/corelib/kernel/qtimerinfo_unix_p.h +++ b/src/corelib/kernel/qtimerinfo_unix_p.h @@ -15,6 +15,8 @@ // We mean it. // +#include "qplatformdefs.h" // _POSIX_MONOTONIC_CLOCK-0 + #include // #define QTIMERINFO_DEBUG @@ -29,7 +31,7 @@ QT_BEGIN_NAMESPACE struct QTimerInfo { int id; // - timer identifier Qt::TimerType timerType; // - timer type - qint64 interval; // - timer interval in milliseconds + std::chrono::milliseconds interval; // - timer interval timespec timeout; // - when to actually fire QObject *obj; // - object to receive event QTimerInfo **activateRef; // - ref from activateTimers @@ -43,16 +45,6 @@ struct QTimerInfo { class Q_CORE_EXPORT QTimerInfoList : public QList { -#if ((_POSIX_MONOTONIC_CLOCK-0 <= 0) && !defined(Q_OS_MAC)) || defined(QT_BOOTSTRAPPED) - timespec previousTime; - clock_t previousTicks; - int ticksPerSecond; - int msPerTick; - - bool timeChanged(timespec *delta); - void timerRepair(const timespec &); -#endif - // state variables used by activateTimers() QTimerInfo *firstTimerInfo; @@ -62,20 +54,26 @@ public: timespec currentTime; timespec updateCurrentTime(); - // must call updateCurrentTime() first! - void repairTimersIfNeeded(); - bool timerWait(timespec &); void timerInsert(QTimerInfo *); qint64 timerRemainingTime(int timerId); + std::chrono::milliseconds remainingDuration(int timerId); void registerTimer(int timerId, qint64 interval, Qt::TimerType timerType, QObject *object); + void registerTimer(int timerId, std::chrono::milliseconds interval, Qt::TimerType timerType, + QObject *object); bool unregisterTimer(int timerId); bool unregisterTimers(QObject *object); QList registeredTimers(QObject *object) const; int activateTimers(); + + QList::const_iterator findTimerById(int timerId) const + { + auto matchesId = [timerId](const QTimerInfo *t) { return t->id == timerId; }; + return std::find_if(cbegin(), cend(), matchesId); + } }; QT_END_NAMESPACE diff --git a/src/corelib/kernel/qvariant.cpp b/src/corelib/kernel/qvariant.cpp index 647b1ded..74416ce0 100644 --- a/src/corelib/kernel/qvariant.cpp +++ b/src/corelib/kernel/qvariant.cpp @@ -3,7 +3,7 @@ // Copyright (C) 2015 Olivier Goffart // SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only -#include "qvariant.h" +#include "qvariant_p.h" #include "qbitarray.h" #include "qbytearray.h" #include "qdatastream.h" @@ -47,6 +47,8 @@ #include "qline.h" #endif +#include + #include #include #include @@ -231,24 +233,22 @@ static bool isValidMetaTypeForVariant(const QtPrivate::QMetaTypeInterface *iface return true; } -template static QVariant::PrivateShared * -customConstructShared(size_t size, size_t align, F &&construct) -{ - struct Deleter { - void operator()(QVariant::PrivateShared *p) const - { QVariant::PrivateShared::free(p); } - }; +enum CustomConstructMoveOptions { + UseCopy, // custom construct uses the copy ctor unconditionally + // future option: TryMove: uses move ctor if available, else copy ctor + ForceMove, // custom construct use the move ctor (which must exist) +}; - // this is exception-safe - std::unique_ptr ptr; - ptr.reset(QVariant::PrivateShared::create(size, align)); - construct(ptr->data()); - return ptr.release(); -} +enum CustomConstructNullabilityOption { + MaybeNull, // copy might be null, might be non-null + NonNull, // copy is guarantueed to be non-null + // future option: AlwaysNull? +}; // the type of d has already been set, but other field are not set +template static void customConstruct(const QtPrivate::QMetaTypeInterface *iface, QVariant::Private *d, - const void *copy) + std::conditional_t copy) { using namespace QtMetaTypePrivate; Q_ASSERT(iface); @@ -257,6 +257,10 @@ static void customConstruct(const QtPrivate::QMetaTypeInterface *iface, QVariant Q_ASSERT(isCopyConstructible(iface)); Q_ASSERT(isDestructible(iface)); Q_ASSERT(copy || isDefaultConstructible(iface)); + if constexpr (moveOption == ForceMove) + Q_ASSERT(isMoveConstructible(iface)); + if constexpr (nullability == NonNull) + Q_ASSUME(copy != nullptr); // need to check for nullptr_t here, as this can get called by fromValue(nullptr). fromValue() uses // std::addressof(value) which in this case returns the address of the nullptr object. @@ -266,11 +270,17 @@ static void customConstruct(const QtPrivate::QMetaTypeInterface *iface, QVariant if (QVariant::Private::canUseInternalSpace(iface)) { d->is_shared = false; if (!copy && !iface->defaultCtr) - return; // default constructor and it's OK to build in 0-filled storage, which we've already done - construct(iface, d->data.data, copy); + return; // trivial default constructor and it's OK to build in 0-filled storage, which we've already done + if constexpr (moveOption == ForceMove && nullability == NonNull) + moveConstruct(iface, d->data.data, copy); + else + construct(iface, d->data.data, copy); } else { d->data.shared = customConstructShared(iface->size, iface->alignment, [=](void *where) { - construct(iface, where, copy); + if constexpr (moveOption == ForceMove && nullability == NonNull) + moveConstruct(iface, where, copy); + else + construct(iface, where, copy); }); d->is_shared = true; } @@ -306,59 +316,6 @@ static QVariant::Private clonePrivate(const QVariant::Private &other) } // anonymous used to hide QVariant handlers -inline QVariant::PrivateShared *QVariant::PrivateShared::create(size_t size, size_t align) -{ - size += sizeof(PrivateShared); - if (align > sizeof(PrivateShared)) { - // The alignment is larger than the alignment we can guarantee for the pointer - // directly following PrivateShared, so we need to allocate some additional - // memory to be able to fit the object into the available memory with suitable - // alignment. - size += align - sizeof(PrivateShared); - } - void *data = operator new(size); - auto *ps = new (data) QVariant::PrivateShared(); - ps->offset = int(((quintptr(ps) + sizeof(PrivateShared) + align - 1) & ~(align - 1)) - quintptr(ps)); - return ps; -} - -inline void QVariant::PrivateShared::free(PrivateShared *p) -{ - p->~PrivateShared(); - operator delete(p); -} - -inline QVariant::Private::Private(const QtPrivate::QMetaTypeInterface *iface) noexcept - : is_shared(false), is_null(false), packedType(quintptr(iface) >> 2) -{ - Q_ASSERT((quintptr(iface) & 0x3) == 0); -} - -template inline -QVariant::Private::Private(std::piecewise_construct_t, const T &t) - : is_shared(!CanUseInternalSpace), is_null(std::is_same_v) -{ - // confirm noexceptness - static constexpr bool isNothrowQVariantConstructible = noexcept(QVariant(t)); - static constexpr bool isNothrowCopyConstructible = std::is_nothrow_copy_constructible_v; - static constexpr bool isNothrowCopyAssignable = std::is_nothrow_copy_assignable_v; - - const QtPrivate::QMetaTypeInterface *iface = QtPrivate::qMetaTypeInterfaceForType(); - Q_ASSERT((quintptr(iface) & 0x3) == 0); - packedType = quintptr(iface) >> 2; - - if constexpr (CanUseInternalSpace) { - static_assert(isNothrowQVariantConstructible == isNothrowCopyConstructible); - static_assert(isNothrowQVariantConstructible == isNothrowCopyAssignable); - new (data.data) T(t); - } else { - static_assert(!isNothrowQVariantConstructible); // we allocate memory, even if T doesn't - data.shared = customConstructShared(sizeof(T), alignof(T), [=](void *where) { - new (where) T(t); - }); - } -} - /*! \class QVariant \inmodule QtCore @@ -586,6 +543,106 @@ QVariant::QVariant(const QVariant &p) { } +/*! + \fn template = true> QVariant::QVariant(std::in_place_type_t, Args&&... args) noexcept(is_noexcept_constructible, Args...>::value) + + \since 6.6 + Constructs a new variant containing a value of type \c T. The contained + value is is initialized with the arguments + \c{std::forward(args)...}. + + This overload only participates in overload resolution if \c T can be + constructed from \a args. + + This constructor is provided for STL/std::any compatibility. + + \overload + */ + +/*! + + \fn template &, Args...> = true> explicit QVariant::QVariant(std::in_place_type_t, std::initializer_list il, Args&&... args) noexcept(is_noexcept_constructible, std::initializer_list &, Args... >::value) + + \since 6.6 + \overload + This overload exists to support types with constructors taking an + \c initializer_list. It behaves otherwise equivalent to the + non-initializer list \c{in_place_type_t} overload. +*/ + + +/*! + \fn template = true> QVariant::emplace(Args&&... args) + + \since 6.6 + Replaces the object currently held in \c{*this} with an object of + type \c{T}, constructed from \a{args}\c{...}. If \c{*this} was non-null, + the previously held object is destroyed first. + If possible, this method will reuse memory allocated by the QVariant. + Returns a reference to the newly-created object. + */ + +/*! + \fn template &, Args...> = true> QVariant::emplace(std::initializer_list list, Args&&... args) + + \since 6.6 + \overload + This overload exists to support types with constructors taking an + \c initializer_list. It behaves otherwise equivalent to the + non-initializer list overload. +*/ + +QVariant::QVariant(std::in_place_t, QMetaType type) : d(type.iface()) +{ + // we query the metatype instead of detecting it at compile time + // so that we can change relocatability of internal types + if (!Private::canUseInternalSpace(type.iface())) { + d.data.shared = PrivateShared::create(type.sizeOf(), type.alignOf()); + d.is_shared = true; + } +} + +/*! + \internal + Returns a pointer to data suitable for placement new + of an object of type \a type + Changes the variant's metatype to \a type + */ +void *QVariant::prepareForEmplace(QMetaType type) +{ + /* There are two cases where we can reuse the existing storage + (1) The new type fits in QVariant's SBO storage + (2) We are using the externally allocated storage, the variant is + detached, and the new type fits into the existing storage. + In all other cases (3), we cannot reuse the storage. + */ + auto typeFits = [&] { + auto newIface = type.iface(); + auto oldIface = d.typeInterface(); + auto newSize = PrivateShared::computeAllocationSize(newIface->size, newIface->alignment); + auto oldSize = PrivateShared::computeAllocationSize(oldIface->size, oldIface->alignment); + return newSize <= oldSize; + }; + if (Private::canUseInternalSpace(type.iface())) { // (1) + clear(); + d.packedType = quintptr(type.iface()) >> 2; + return d.data.data; + } else if (d.is_shared && isDetached() && typeFits()) { // (2) + QtMetaTypePrivate::destruct(d.typeInterface(), d.data.shared->data()); + // compare QVariant::PrivateShared::create + const auto ps = d.data.shared; + const auto align = type.alignOf(); + ps->offset = PrivateShared::computeOffset(ps, align); + d.packedType = quintptr(type.iface()) >> 2; + return ps->data(); + } + // (3) + QVariant newVariant(std::in_place, type); + swap(newVariant); + // const cast is safe, we're in a non-const method + return const_cast(d.storage()); +} + /*! \fn QVariant::QVariant(const QString &val) noexcept @@ -1032,7 +1089,8 @@ void QVariant::detach() Q_ASSERT(isValidMetaTypeForVariant(d.typeInterface(), constData())); Private dd(d.typeInterface()); - customConstruct(d.typeInterface(), &dd, constData()); + // null variant is never shared; anything else is NonNull + customConstruct(d.typeInterface(), &dd, constData()); if (!d.data.shared->ref.deref()) customClear(&d); d.data.shared = dd.data.shared; @@ -2398,7 +2456,7 @@ QPartialOrdering QVariant::compare(const QVariant &lhs, const QVariant &rhs) Returns a pointer to the contained object as a generic void* that cannot be written to. - \sa QMetaType + \sa get_if(), QMetaType */ /*! @@ -2408,7 +2466,7 @@ QPartialOrdering QVariant::compare(const QVariant &lhs, const QVariant &rhs) This function detaches the QVariant. When called on a \l{isNull}{null-QVariant}, the QVariant will not be null after the call. - \sa QMetaType + \sa get_if(), QMetaType */ void *QVariant::data() { @@ -2418,6 +2476,42 @@ void *QVariant::data() return const_cast(constData()); } +/*! + \since 6.6 + \fn template const T* QVariant::get_if(const QVariant *v) + \fn template T* QVariant::get_if(QVariant *v) + + If \a v contains an object of type \c T, returns a pointer to the contained + object, otherwise returns \nullptr. + + The overload taking a mutable \a v detaches \a v: When called on a + \l{isNull()}{null} \a v with matching type \c T, \a v will not be null + after the call. + + These functions are provided for compatibility with \c{std::variant}. + + \sa data() +*/ + +/*! + \since 6.6 + \fn template T &QVariant::get(QVariant &v) + \fn template const T &QVariant::get(const QVariant &v) + \fn template T &&QVariant::get(QVariant &&v) + \fn template const T &&QVariant::get(const QVariant &&v) + + If \a v contains an object of type \c T, returns a reference to the contained + object, otherwise the call has undefined behavior. + + The overloads taking a mutable \a v detach \a v: When called on a + \l{isNull()}{null} \a v with matching type \c T, \a v will not be null + after the call. + + These functions are provided for compatibility with \c{std::variant}. + + \sa get_if(), data() +*/ + /*! Returns \c true if this is a null variant, false otherwise. @@ -2456,6 +2550,22 @@ QDebug QVariant::qdebugHelper(QDebug dbg) const return dbg; } +QVariant QVariant::moveConstruct(QMetaType type, void *data) +{ + QVariant var; + var.d = QVariant::Private(type.d_ptr); + customConstruct(type.d_ptr, &var.d, data); + return var; +} + +QVariant QVariant::copyConstruct(QMetaType type, const void *data) +{ + QVariant var; + var.d = QVariant::Private(type.d_ptr); + customConstruct(type.d_ptr, &var.d, data); + return var; +} + #if QT_DEPRECATED_SINCE(6, 0) QT_WARNING_PUSH QT_WARNING_DISABLE_DEPRECATED @@ -2576,6 +2686,12 @@ QT_WARNING_POP \sa setValue(), value() */ +/*! \fn template static QVariant QVariant::fromValue(T &&value) + + \since 6.6 + \overload +*/ + /*! \fn template QVariant QVariant::fromStdVariant(const std::variant &value) \since 5.11 @@ -2589,6 +2705,12 @@ QT_WARNING_POP \sa fromValue() */ +/*! + \fn template QVariant QVariant::fromStdVariant(std::variant &&value) + \since 6.6 + \overload +*/ + /*! \fn template T qvariant_cast(const QVariant &value) \relates QVariant diff --git a/src/corelib/kernel/qvariant.h b/src/corelib/kernel/qvariant.h index d956112e..ed497869 100644 --- a/src/corelib/kernel/qvariant.h +++ b/src/corelib/kernel/qvariant.h @@ -10,9 +10,12 @@ #ifndef QT_NO_DEBUG_STREAM #include #endif + #include -#include +#include +#include #include + #if !defined(QT_LEAN_HEADERS) || QT_LEAN_HEADERS < 1 # include # include @@ -24,6 +27,9 @@ QT_BEGIN_NAMESPACE +QT_ENABLE_P0846_SEMANTICS_FOR(get_if) +QT_ENABLE_P0846_SEMANTICS_FOR(get) + class QBitArray; class QDataStream; class QDate; @@ -56,6 +62,18 @@ template<> constexpr inline bool qIsRelocatable = true; } class Q_CORE_EXPORT QVariant { + template + using if_constructible = std::enable_if_t< + std::conjunction_v< + std::is_copy_constructible>, + std::is_destructible>, + std::is_constructible, Args...> + >, + bool>; + + template + using if_rvalue = std::enable_if_t, bool>; + struct CborValueStandIn { qint64 n; void *c; int t; }; public: struct PrivateShared @@ -63,6 +81,8 @@ public: private: inline PrivateShared() : ref(1) { } public: + static int computeOffset(PrivateShared *ps, size_t align); + static size_t computeAllocationSize(size_t size, size_t align); static PrivateShared *create(size_t size, size_t align); static void free(PrivateShared *p); @@ -199,6 +219,37 @@ public: explicit QVariant(QMetaType type, const void *copy = nullptr); QVariant(const QVariant &other); +private: + template + using is_noexcept_constructible = std::conjunction< + std::bool_constant>, + std::is_nothrow_constructible + >; + +public: + template = true> + explicit QVariant(std::in_place_type_t, Args&&... args) + noexcept(is_noexcept_constructible, Args...>::value) + : QVariant(std::in_place, QMetaType::fromType>() ) + { + void *data = const_cast(constData()); + new (data) T(std::forward(args)...); + } + + template &, Args...> = true> + explicit QVariant(std::in_place_type_t, std::initializer_list il, Args&&... args) + noexcept(is_noexcept_constructible, + std::initializer_list &, + Args... + >::value) + : QVariant(std::in_place, QMetaType::fromType>()) + { + char *data = static_cast(const_cast(constData())); + new (data) T(il, std::forward(args)...); + } + // primitives QVariant(int i) noexcept; QVariant(uint ui) noexcept; @@ -396,6 +447,43 @@ public: { return d.storage(); } inline const void *data() const { return constData(); } +private: + template + void verifySuitableForEmplace() + { + static_assert(!std::is_reference_v, + "QVariant does not support reference types"); + static_assert(!std::is_const_v, + "QVariant does not support const types"); + static_assert(std::is_copy_constructible_v, + "QVariant requires that the type is copyable"); + static_assert(std::is_destructible_v, + "QVariant requires that the type is destructible"); + } + + template + T &emplaceImpl(Args&&... args) + { + verifySuitableForEmplace(); + auto data = static_cast(prepareForEmplace(QMetaType::fromType())); + return *q20::construct_at(data, std::forward(args)...); + } + +public: + template = true> + T &emplace(Args&&... args) + { + return emplaceImpl(std::forward(args)...); + } + + template &, Args...> = true> + T &emplace(std::initializer_list list, Args&&... args) + { + return emplaceImpl(list, std::forward(args)...); + } + template, QVariant>>> void setValue(T &&avalue) { @@ -431,6 +519,39 @@ public: return t; } + template = true> +#ifndef Q_QDOC + /* needs is_copy_constructible for variants semantics, is_move_constructible so that moveConstruct works + (but copy_constructible implies move_constructble, so don't bother checking) + */ + static inline auto fromValue(T &&value) + noexcept(std::is_nothrow_copy_constructible_v && Private::CanUseInternalSpace) + -> std::enable_if_t, + std::is_destructible>, QVariant> +#else + static inline QVariant fromValue(T &&value) +#endif + { + // handle special cases + using Type = std::remove_cv_t; + if constexpr (std::is_null_pointer_v) + return QVariant(QMetaType::fromType()); + else if constexpr (std::is_same_v) + return std::forward(value); + else if constexpr (std::is_same_v) + return QVariant(); + QMetaType mt = QMetaType::fromType(); + mt.registerType(); // we want the type stored in QVariant to always be registered + // T is a forwarding reference, so if T satifies the enable-ifery, + // we get this overload even if T is an lvalue reference and thus must check here + // Moreover, we only try to move if the type is actually moveable and not if T is const + // as in const int i; QVariant::fromValue(std::move(i)); + if constexpr (std::conjunction_v, std::negation>>) + return moveConstruct(QMetaType::fromType(), std::addressof(value)); + else + return copyConstruct(mt, std::addressof(value)); + } + template #ifndef Q_QDOC static inline auto fromValue(const T &value) @@ -442,15 +563,23 @@ public: { if constexpr (std::is_null_pointer_v) return QVariant(QMetaType::fromType()); + else if constexpr (std::is_same_v) + return value; + else if constexpr (std::is_same_v) + return QVariant(); return QVariant(QMetaType::fromType(), std::addressof(value)); } template static inline QVariant fromStdVariant(const std::variant &value) { - if (value.valueless_by_exception()) - return QVariant(); - return std::visit([](const auto &arg) { return QVariant::fromValue(arg); }, value); + return fromStdVariantImpl(value); + } + + template + static QVariant fromStdVariant(std::variant &&value) + { + return fromStdVariantImpl(std::move(value)); } template @@ -464,6 +593,17 @@ public: static QPartialOrdering compare(const QVariant &lhs, const QVariant &rhs); private: + template + static QVariant fromStdVariantImpl(StdVariant &&v) + { + if (Q_UNLIKELY(v.valueless_by_exception())) + return QVariant(); + auto visitor = [](auto &&arg) { + return QVariant::fromValue(q23::forward_like(arg)); + }; + return std::visit(visitor, std::forward(v)); + } + friend inline bool operator==(const QVariant &a, const QVariant &b) { return a.equals(b); } friend inline bool operator!=(const QVariant &a, const QVariant &b) @@ -475,6 +615,43 @@ private: } QDebug qdebugHelper(QDebug) const; #endif + + template + friend T *get_if(QVariant *v) noexcept + { + // data() will detach from is_null, returning non-nullptr + if (!v || v->d.type() != QMetaType::fromType()) + return nullptr; + return static_cast(v->data()); + } + template + friend const T *get_if(const QVariant *v) noexcept + { + // (const) data() will not detach from is_null, return nullptr + if (!v || v->d.is_null || v->d.type() != QMetaType::fromType()) + return nullptr; + return static_cast(v->data()); + } + +#define Q_MK_GET(cvref) \ + template \ + friend T cvref get(QVariant cvref v) \ + { \ + if constexpr (std::is_const_v) \ + Q_ASSERT(!v.d.is_null); \ + Q_ASSERT(v.d.type() == QMetaType::fromType>()); \ + return static_cast(*get_if(&v)); \ + } \ + /* end */ + Q_MK_GET(&) + Q_MK_GET(const &) + Q_MK_GET(&&) + Q_MK_GET(const &&) +#undef Q_MK_GET + + static QVariant moveConstruct(QMetaType type, void *data); + static QVariant copyConstruct(QMetaType type, const void *data); + template friend inline T qvariant_cast(const QVariant &); protected: @@ -496,6 +673,11 @@ private: // int variant, so delete this constructor: QVariant(QMetaType::Type) = delete; + // used to setup the QVariant internals for the "real" inplace ctor + QVariant(std::in_place_t, QMetaType type); + // helper for emplace + void *prepareForEmplace(QMetaType type); + // These constructors don't create QVariants of the type associated // with the enum, as expected, but they would create a QVariant of // type int with the value of the enum value. @@ -515,18 +697,6 @@ public: inline const DataPtr &data_ptr() const { return d; } }; -template<> -inline QVariant QVariant::fromValue(const QVariant &value) -{ - return value; -} - -template<> -inline QVariant QVariant::fromValue(const std::monostate &) noexcept -{ - return QVariant(); -} - inline bool QVariant::isValid() const { return d.type().isValid(); diff --git a/src/corelib/kernel/qvariant_p.h b/src/corelib/kernel/qvariant_p.h new file mode 100644 index 00000000..d2a73909 --- /dev/null +++ b/src/corelib/kernel/qvariant_p.h @@ -0,0 +1,109 @@ +// 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 QVARIANT_P_H +#define QVARIANT_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 "qvariant.h" + +QT_BEGIN_NAMESPACE + +inline auto customConstructSharedImpl(size_t size, size_t align) +{ + struct Deleter { + void operator()(QVariant::PrivateShared *p) const + { QVariant::PrivateShared::free(p); } + }; + + // this is exception-safe + std::unique_ptr ptr; + ptr.reset(QVariant::PrivateShared::create(size, align)); + return ptr; +} + +template static QVariant::PrivateShared * +customConstructShared(size_t size, size_t align, F &&construct) +{ + auto ptr = customConstructSharedImpl(size, align); + construct(ptr->data()); + return ptr.release(); +} + +inline int QVariant::PrivateShared::computeOffset(PrivateShared *ps, size_t align) +{ + return int(((quintptr(ps) + sizeof(PrivateShared) + align - 1) & ~(align - 1)) - quintptr(ps)); +} + +inline size_t QVariant::PrivateShared::computeAllocationSize(size_t size, size_t align) +{ + size += sizeof(PrivateShared); + if (align > sizeof(PrivateShared)) { + // The alignment is larger than the alignment we can guarantee for the pointer + // directly following PrivateShared, so we need to allocate some additional + // memory to be able to fit the object into the available memory with suitable + // alignment. + size += align - sizeof(PrivateShared); + } + return size; +} + +inline QVariant::PrivateShared *QVariant::PrivateShared::create(size_t size, size_t align) +{ + size = computeAllocationSize(size, align); + void *data = operator new(size); + auto *ps = new (data) QVariant::PrivateShared(); + ps->offset = computeOffset(ps, align); + return ps; +} + +inline void QVariant::PrivateShared::free(PrivateShared *p) +{ + p->~PrivateShared(); + operator delete(p); +} + +inline QVariant::Private::Private(const QtPrivate::QMetaTypeInterface *iface) noexcept + : is_shared(false), is_null(false), packedType(quintptr(iface) >> 2) +{ + Q_ASSERT((quintptr(iface) & 0x3) == 0); +} + +template inline +QVariant::Private::Private(std::piecewise_construct_t, const T &t) + : is_shared(!CanUseInternalSpace), is_null(std::is_same_v) +{ + // confirm noexceptness + static constexpr bool isNothrowQVariantConstructible = noexcept(QVariant(t)); + static constexpr bool isNothrowCopyConstructible = std::is_nothrow_copy_constructible_v; + static constexpr bool isNothrowCopyAssignable = std::is_nothrow_copy_assignable_v; + + const QtPrivate::QMetaTypeInterface *iface = QtPrivate::qMetaTypeInterfaceForType(); + Q_ASSERT((quintptr(iface) & 0x3) == 0); + packedType = quintptr(iface) >> 2; + + if constexpr (CanUseInternalSpace) { + static_assert(isNothrowQVariantConstructible == isNothrowCopyConstructible); + static_assert(isNothrowQVariantConstructible == isNothrowCopyAssignable); + new (data.data) T(t); + } else { + static_assert(!isNothrowQVariantConstructible); // we allocate memory, even if T doesn't + data.shared = customConstructShared(sizeof(T), alignof(T), [=](void *where) { + new (where) T(t); + }); + } +} + +QT_END_NAMESPACE + +#endif // QVARIANT_P_H diff --git a/src/corelib/mimetypes/qmimedatabase.cpp b/src/corelib/mimetypes/qmimedatabase.cpp index fd632495..63f398a9 100644 --- a/src/corelib/mimetypes/qmimedatabase.cpp +++ b/src/corelib/mimetypes/qmimedatabase.cpp @@ -190,7 +190,7 @@ QMimeType QMimeDatabasePrivate::mimeTypeForName(const QString &nameOrAlias) { const QString mimeName = resolveAlias(nameOrAlias); for (const auto &provider : providers()) { - const QMimeType mime = provider->mimeTypeForName(mimeName); + QMimeType mime = provider->mimeTypeForName(mimeName); if (mime.isValid()) return mime; } diff --git a/src/corelib/mimetypes/qmimemagicrule.cpp b/src/corelib/mimetypes/qmimemagicrule.cpp index 9111f1f0..3458fef6 100644 --- a/src/corelib/mimetypes/qmimemagicrule.cpp +++ b/src/corelib/mimetypes/qmimemagicrule.cpp @@ -148,12 +148,8 @@ static inline QByteArray makePattern(const QByteArray &value) char c = 0; for (int i = 0; i < 2 && p + 1 < e; ++i) { ++p; - if (*p >= '0' && *p <= '9') - c = (c << 4) + *p - '0'; - else if (*p >= 'a' && *p <= 'f') - c = (c << 4) + *p - 'a' + 10; - else if (*p >= 'A' && *p <= 'F') - c = (c << 4) + *p - 'A' + 10; + if (const int h = fromHex(*p); h != -1) + c = (c << 4) + h; else continue; } diff --git a/src/corelib/mimetypes/qmimeprovider.cpp b/src/corelib/mimetypes/qmimeprovider.cpp index 8a88a267..586a0c64 100644 --- a/src/corelib/mimetypes/qmimeprovider.cpp +++ b/src/corelib/mimetypes/qmimeprovider.cpp @@ -20,7 +20,7 @@ #include #if QT_CONFIG(mimetype_database) -# if defined(Q_CC_MSVC) +# if defined(Q_CC_MSVC_ONLY) # pragma section(".qtmimedatabase", read, shared) __declspec(allocate(".qtmimedatabase")) __declspec(align(4096)) # elif defined(Q_OS_DARWIN) @@ -116,7 +116,7 @@ bool QMimeBinaryProvider::CacheFile::load() const int minor = getUint16(2); m_valid = (major == 1 && minor >= 1 && minor <= 2); } - m_mtime = QFileInfo(file).lastModified(); + m_mtime = QFileInfo(file).lastModified(QTimeZone::UTC); return m_valid; } @@ -130,10 +130,7 @@ bool QMimeBinaryProvider::CacheFile::reload() return load(); } -QMimeBinaryProvider::~QMimeBinaryProvider() -{ - delete m_cacheFile; -} +QMimeBinaryProvider::~QMimeBinaryProvider() = default; bool QMimeBinaryProvider::isValid() { @@ -161,7 +158,7 @@ enum { bool QMimeBinaryProvider::checkCacheChanged() { QFileInfo fileInfo(m_cacheFile->file); - if (fileInfo.lastModified() > m_cacheFile->m_mtime) { + if (fileInfo.lastModified(QTimeZone::UTC) > m_cacheFile->m_mtime) { // Deletion can't happen by just running update-mime-database. // But the user could use rm -rf :-) m_cacheFile->reload(); // will mark itself as invalid on failure @@ -174,7 +171,7 @@ void QMimeBinaryProvider::ensureLoaded() { if (!m_cacheFile) { const QString cacheFileName = m_directory + "/mime.cache"_L1; - m_cacheFile = new CacheFile(cacheFileName); + m_cacheFile = std::make_unique(cacheFileName); m_mimetypeListLoaded = false; m_mimetypeExtra.clear(); } else { @@ -185,10 +182,8 @@ void QMimeBinaryProvider::ensureLoaded() return; // nothing to do } } - if (!m_cacheFile->isValid()) { // verify existence and version - delete m_cacheFile; - m_cacheFile = nullptr; - } + if (!m_cacheFile->isValid()) // verify existence and version + m_cacheFile.reset(); } static QMimeType mimeTypeForNameUnchecked(const QString &name) @@ -218,20 +213,27 @@ void QMimeBinaryProvider::addFileNameMatches(const QString &fileName, QMimeGlobM return; Q_ASSERT(m_cacheFile); const QString lowerFileName = fileName.toLower(); + int numMatches = 0; // Check literals (e.g. "Makefile") - matchGlobList(result, m_cacheFile, m_cacheFile->getUint32(PosLiteralListOffset), fileName); + numMatches = matchGlobList(result, m_cacheFile.get(), + m_cacheFile->getUint32(PosLiteralListOffset), fileName); // Check the very common *.txt cases with the suffix tree - if (result.m_matchingMimeTypes.isEmpty()) { + if (numMatches == 0) { const int reverseSuffixTreeOffset = m_cacheFile->getUint32(PosReverseSuffixTreeOffset); const int numRoots = m_cacheFile->getUint32(reverseSuffixTreeOffset); const int firstRootOffset = m_cacheFile->getUint32(reverseSuffixTreeOffset + 4); - matchSuffixTree(result, m_cacheFile, numRoots, firstRootOffset, lowerFileName, lowerFileName.size() - 1, false); - if (result.m_matchingMimeTypes.isEmpty()) - matchSuffixTree(result, m_cacheFile, numRoots, firstRootOffset, fileName, fileName.size() - 1, true); + if (matchSuffixTree(result, m_cacheFile.get(), numRoots, firstRootOffset, lowerFileName, + lowerFileName.size() - 1, false)) { + ++numMatches; + } else if (matchSuffixTree(result, m_cacheFile.get(), numRoots, firstRootOffset, fileName, + fileName.size() - 1, true)) { + ++numMatches; + } } // Check complex globs (e.g. "callgrind.out[0-9]*" or "README*") - if (result.m_matchingMimeTypes.isEmpty()) - matchGlobList(result, m_cacheFile, m_cacheFile->getUint32(PosGlobListOffset), fileName); + if (numMatches == 0) + matchGlobList(result, m_cacheFile.get(), m_cacheFile->getUint32(PosGlobListOffset), + fileName); } bool QMimeBinaryProvider::isMimeTypeGlobsExcluded(const char *mimeTypeName) @@ -245,8 +247,10 @@ void QMimeBinaryProvider::excludeMimeTypeGlobs(const QStringList &toExclude) appendIfNew(m_mimeTypesWithExcludedGlobs, mt); } -void QMimeBinaryProvider::matchGlobList(QMimeGlobMatchResult &result, CacheFile *cacheFile, int off, const QString &fileName) +int QMimeBinaryProvider::matchGlobList(QMimeGlobMatchResult &result, CacheFile *cacheFile, int off, + const QString &fileName) { + int numMatches = 0; const int numGlobs = cacheFile->getUint32(off); //qDebug() << "Loading" << numGlobs << "globs from" << cacheFile->file.fileName() << "at offset" << cacheFile->globListOffset; for (int i = 0; i < numGlobs; ++i) { @@ -264,9 +268,12 @@ void QMimeBinaryProvider::matchGlobList(QMimeGlobMatchResult &result, CacheFile continue; QMimeGlobPattern glob(pattern, QString() /*unused*/, weight, qtCaseSensitive); - if (glob.matchFileName(fileName)) + if (glob.matchFileName(fileName)) { result.addMatch(QLatin1StringView(mimeType), weight, pattern); + ++numMatches; + } } + return numMatches; } bool QMimeBinaryProvider::matchSuffixTree(QMimeGlobMatchResult &result, @@ -358,7 +365,7 @@ void QMimeBinaryProvider::findByMagic(const QByteArray &data, int *accuracyPtr, const int off = firstMatchOffset + i * 16; const int numMatchlets = m_cacheFile->getUint32(off + 8); const int firstMatchletOffset = m_cacheFile->getUint32(off + 12); - if (matchMagicRule(m_cacheFile, numMatchlets, firstMatchletOffset, data)) { + if (matchMagicRule(m_cacheFile.get(), numMatchlets, firstMatchletOffset, data)) { const int mimeTypeOffset = m_cacheFile->getUint32(off + 4); const char *mimeType = m_cacheFile->getCharStar(mimeTypeOffset); *accuracyPtr = m_cacheFile->getUint32(off); @@ -395,8 +402,7 @@ void QMimeBinaryProvider::addParents(const QString &mime, QStringList &result) const int parentOffset = m_cacheFile->getUint32(parentsOffset + 4 + 4 * i); const char *aParent = m_cacheFile->getCharStar(parentOffset); const QString strParent = QString::fromLatin1(aParent); - if (!result.contains(strParent)) - result.append(strParent); + appendIfNew(result, strParent); } break; } @@ -443,8 +449,7 @@ void QMimeBinaryProvider::addAliases(const QString &name, QStringList &result) const int aliasOffset = m_cacheFile->getUint32(off); const char *alias = m_cacheFile->getCharStar(aliasOffset); const QString strAlias = QString::fromLatin1(alias); - if (!result.contains(strAlias)) - result.append(strAlias); + appendIfNew(result, strAlias); } } } @@ -485,11 +490,7 @@ void QMimeBinaryProvider::addAllMimeTypes(QList &result) bool QMimeBinaryProvider::loadMimeTypePrivate(QMimeTypePrivate &data) { -#ifdef QT_NO_XMLSTREAMREADER - Q_UNUSED(data); - qWarning("Cannot load mime type since QXmlStreamReader is not available."); - return false; -#else +#if QT_CONFIG(xmlstreamreader) if (data.loaded) return true; @@ -540,8 +541,7 @@ bool QMimeBinaryProvider::loadMimeTypePrivate(QMimeTypePrivate &data) if (mainPattern.isEmpty() && pattern.startsWith(u'*')) { mainPattern = pattern; } - if (!extra.globPatterns.contains(pattern)) - extra.globPatterns.append(pattern); + appendIfNew(extra.globPatterns, pattern); } xml.skipCurrentElement(); } @@ -561,7 +561,11 @@ bool QMimeBinaryProvider::loadMimeTypePrivate(QMimeTypePrivate &data) data.localeComments = e.localeComments; data.globPatterns = e.globPatterns; return true; -#endif //QT_NO_XMLSTREAMREADER +#else + Q_UNUSED(data); + qWarning("Cannot load mime type since QXmlStreamReader is not available."); + return false; +#endif // feature xmlstreamreader } // Binary search in the icons or generic-icons list @@ -593,7 +597,7 @@ QLatin1StringView QMimeBinaryProvider::iconForMime(CacheFile *cacheFile, int pos void QMimeBinaryProvider::loadIcon(QMimeTypePrivate &data) { const QByteArray inputMime = data.name.toLatin1(); - const QLatin1StringView icon = iconForMime(m_cacheFile, PosIconsListOffset, inputMime); + const QLatin1StringView icon = iconForMime(m_cacheFile.get(), PosIconsListOffset, inputMime); if (!icon.isEmpty()) { data.iconName = icon; } @@ -602,7 +606,7 @@ void QMimeBinaryProvider::loadIcon(QMimeTypePrivate &data) void QMimeBinaryProvider::loadGenericIcon(QMimeTypePrivate &data) { const QByteArray inputMime = data.name.toLatin1(); - const QLatin1StringView icon = iconForMime(m_cacheFile, PosGenericIconsListOffset, inputMime); + const QLatin1StringView icon = iconForMime(m_cacheFile.get(), PosGenericIconsListOffset, inputMime); if (!icon.isEmpty()) { data.genericIconName = icon; } @@ -830,13 +834,10 @@ void QMimeXMLProvider::addParent(const QString &child, const QString &parent) void QMimeXMLProvider::addAliases(const QString &name, QStringList &result) { // Iterate through the whole hash. This method is rarely used. - for (auto it = m_aliases.constBegin(), end = m_aliases.constEnd() ; it != end ; ++it) { - if (it.value() == name) { - if (!result.contains(it.key())) - result.append(it.key()); - } + for (const auto &[alias, mimeName] : std::as_const(m_aliases).asKeyValueRange()) { + if (mimeName == name) + appendIfNew(result, alias); } - } QString QMimeXMLProvider::resolveAlias(const QString &name) diff --git a/src/corelib/mimetypes/qmimeprovider_p.h b/src/corelib/mimetypes/qmimeprovider_p.h index 7f4d9542..e498cf8c 100644 --- a/src/corelib/mimetypes/qmimeprovider_p.h +++ b/src/corelib/mimetypes/qmimeprovider_p.h @@ -31,6 +31,8 @@ class QMimeMagicRuleMatcher; class QMimeProviderBase { + Q_DISABLE_COPY(QMimeProviderBase) + public: QMimeProviderBase(QMimeDatabasePrivate *db, const QString &directory); virtual ~QMimeProviderBase() {} @@ -109,7 +111,8 @@ public: private: struct CacheFile; - void matchGlobList(QMimeGlobMatchResult &result, CacheFile *cacheFile, int offset, const QString &fileName); + int matchGlobList(QMimeGlobMatchResult &result, CacheFile *cacheFile, int offset, + const QString &fileName); bool matchSuffixTree(QMimeGlobMatchResult &result, CacheFile *cacheFile, int numEntries, int firstOffset, const QString &fileName, qsizetype charPos, bool caseSensitiveCheck); @@ -119,7 +122,7 @@ private: void loadMimeTypeList(); bool checkCacheChanged(); - CacheFile *m_cacheFile = nullptr; + std::unique_ptr m_cacheFile; QStringList m_cacheFileNames; QSet m_mimetypeNames; bool m_mimetypeListLoaded; diff --git a/src/corelib/mimetypes/qmimetype.cpp b/src/corelib/mimetypes/qmimetype.cpp index 1b2a5e23..b824a420 100644 --- a/src/corelib/mimetypes/qmimetype.cpp +++ b/src/corelib/mimetypes/qmimetype.cpp @@ -285,7 +285,7 @@ QString QMimeType::genericIconName() const // (i.e. "video-x-generic" in the previous example). const QString group = name(); QStringView groupRef(group); - const int slashindex = groupRef.indexOf(u'/'); + const qsizetype slashindex = groupRef.indexOf(u'/'); if (slashindex != -1) groupRef = groupRef.left(slashindex); return groupRef + "-x-generic"_L1; @@ -295,7 +295,7 @@ QString QMimeType::genericIconName() const static QString make_default_icon_name_from_mimetype_name(QString iconName) { - const int slashindex = iconName.indexOf(u'/'); + const qsizetype slashindex = iconName.indexOf(u'/'); if (slashindex != -1) iconName[slashindex] = u'-'; return iconName; diff --git a/src/corelib/mimetypes/qmimetypeparser.cpp b/src/corelib/mimetypes/qmimetypeparser.cpp index 0cb7b248..66cbc32b 100644 --- a/src/corelib/mimetypes/qmimetypeparser.cpp +++ b/src/corelib/mimetypes/qmimetypeparser.cpp @@ -139,7 +139,7 @@ bool QMimeTypeParserBase::parseNumber(QStringView n, int *target, QString *error return true; } -#ifndef QT_NO_XMLSTREAMREADER +#if QT_CONFIG(xmlstreamreader) struct CreateMagicMatchRuleResult { QString errorMessage; // must be first @@ -160,16 +160,11 @@ static CreateMagicMatchRuleResult createMagicMatchRule(const QXmlStreamAttribute const auto mask = atts.value(QLatin1StringView(matchMaskAttributeC)); return CreateMagicMatchRuleResult(type, value, offsets, mask); } -#endif +#endif // feature xmlstreamreader bool QMimeTypeParserBase::parse(QIODevice *dev, const QString &fileName, QString *errorMessage) { -#ifdef QT_NO_XMLSTREAMREADER - Q_UNUSED(dev); - if (errorMessage) - *errorMessage = QString::fromLatin1("QXmlStreamReader is not available, cannot parse '%1'.").arg(fileName); - return false; -#else +#if QT_CONFIG(xmlstreamreader) QMimeTypePrivate data; data.loaded = true; int priority = 50; @@ -311,7 +306,12 @@ bool QMimeTypeParserBase::parse(QIODevice *dev, const QString &fileName, QString } return true; -#endif //QT_NO_XMLSTREAMREADER +#else + Q_UNUSED(dev); + if (errorMessage) + *errorMessage = "QXmlStreamReader is not available, cannot parse '%1'."_L1.arg(fileName); + return false; +#endif // feature xmlstreamreader } QT_END_NAMESPACE diff --git a/src/corelib/platform/android/qandroidextras.cpp b/src/corelib/platform/android/qandroidextras.cpp index 7499a6ac..85f8a7e8 100644 --- a/src/corelib/platform/android/qandroidextras.cpp +++ b/src/corelib/platform/android/qandroidextras.cpp @@ -1083,29 +1083,29 @@ static void sendRequestPermissionsResult(JNIEnv *env, jobject *obj, jint request QFuture requestPermissionsInternal(const QStringList &permissions) { + // No mechanism to request permission for SDK version below 23, because + // permissions defined in the manifest are granted at install time. + if (QtAndroidPrivate::androidSdkVersion() < 23) { + QList result; + result.reserve(permissions.size()); + // ### can we kick off all checkPermission()s, and whenAll() collect results? + for (const QString &permission : permissions) + result.push_back(QtAndroidPrivate::checkPermission(permission).result()); + return QtFuture::makeReadyRangeFuture(result); + } + + if (!QtAndroidPrivate::acquireAndroidDeadlockProtector()) + return QtFuture::makeReadyValueFuture(QtAndroidPrivate::Denied); + QSharedPointer> promise; promise.reset(new QPromise()); QFuture future = promise->future(); promise->start(); - // No mechanism to request permission for SDK version below 23, because - // permissions defined in the manifest are granted at install time. - if (QtAndroidPrivate::androidSdkVersion() < 23) { - for (int i = 0; i < permissions.size(); ++i) - promise->addResult(QtAndroidPrivate::checkPermission(permissions.at(i)).result(), i); - promise->finish(); - return future; - } - - if (!QtAndroidPrivate::acquireAndroidDeadlockProtector()) { - promise->addResult(QtAndroidPrivate::Denied); - promise->finish(); - return future; - } - const int requestCode = nextRequestCode(); QMutexLocker locker(&g_pendingPermissionRequestsMutex); g_pendingPermissionRequests->insert(requestCode, promise); + locker.unlock(); QNativeInterface::QAndroidApplication::runOnAndroidMainThread([permissions, requestCode] { QJniEnvironment env; @@ -1144,15 +1144,9 @@ QFuture QtAndroidPrivate::requestPermissions(const QStringList &permissions) { // avoid the uneccessary call and response to an empty permission string - if (permissions.size() > 0) - return requestPermissionsInternal(permissions); - - QPromise promise; - QFuture future = promise.future(); - promise.start(); - promise.addResult(QtAndroidPrivate::Denied); - promise.finish(); - return future; + if (permissions.isEmpty()) + return QtFuture::makeReadyValueFuture(QtAndroidPrivate::Denied); + return requestPermissionsInternal(permissions); } /*! @@ -1166,22 +1160,15 @@ QtAndroidPrivate::requestPermissions(const QStringList &permissions) QFuture QtAndroidPrivate::checkPermission(const QString &permission) { - QPromise promise; - QFuture future = promise.future(); - promise.start(); - - if (permission.size() > 0) { + QtAndroidPrivate::PermissionResult result = Denied; + if (!permission.isEmpty()) { auto res = QJniObject::callStaticMethod(qtNativeClassName, "checkSelfPermission", "(Ljava/lang/String;)I", QJniObject::fromString(permission).object()); - promise.addResult(resultFromAndroid(res)); - } else { - promise.addResult(QtAndroidPrivate::Denied); + result = resultFromAndroid(res); } - - promise.finish(); - return future; + return QtFuture::makeReadyValueFuture(result); } bool QtAndroidPrivate::registerPermissionNatives() diff --git a/src/corelib/platform/darwin/qdarwinpermissionplugin_location.mm b/src/corelib/platform/darwin/qdarwinpermissionplugin_location.mm index b812d4d3..3676ba09 100644 --- a/src/corelib/platform/darwin/qdarwinpermissionplugin_location.mm +++ b/src/corelib/platform/darwin/qdarwinpermissionplugin_location.mm @@ -101,7 +101,7 @@ struct PermissionRequest return self.manager.authorizationStatus; } - return CLLocationManager.authorizationStatus; + return QT_IGNORE_DEPRECATIONS(CLLocationManager.authorizationStatus); } - (Qt::PermissionStatus)accuracyAuthorization:(QLocationPermission)permission diff --git a/src/corelib/platform/wasm/qstdweb.cpp b/src/corelib/platform/wasm/qstdweb.cpp index cae15d07..a00a5148 100644 --- a/src/corelib/platform/wasm/qstdweb.cpp +++ b/src/corelib/platform/wasm/qstdweb.cpp @@ -437,7 +437,7 @@ private: break; } QString a; - const QString data = QString::fromStdString(webDataTransfer.call( + const QString data = QString::fromEcmaString(webDataTransfer.call( "getData", emscripten::val(itemMimeType.toStdString()))); if (!data.isEmpty()) { @@ -623,17 +623,20 @@ void FileReader::readAsArrayBuffer(const Blob &blob) const void FileReader::onLoad(const std::function &onLoad) { - m_onLoad.reset(new EventCallback(m_fileReader, "load", onLoad)); + m_onLoad.reset(); + m_onLoad = std::make_unique(m_fileReader, "load", onLoad); } void FileReader::onError(const std::function &onError) { - m_onError.reset(new EventCallback(m_fileReader, "error", onError)); + m_onError.reset(); + m_onError = std::make_unique(m_fileReader, "error", onError); } void FileReader::onAbort(const std::function &onAbort) { - m_onAbort.reset(new EventCallback(m_fileReader, "abort", onAbort)); + m_onAbort.reset(); + m_onAbort = std::make_unique(m_fileReader, "abort", onAbort); } emscripten::val FileReader::val() @@ -765,6 +768,8 @@ EventCallback::EventCallback(emscripten::val element, const std::string &name, c ,m_eventName(name) ,m_fn(fn) { + Q_ASSERT_X(m_element[contextPropertyName(m_eventName)].isUndefined(), Q_FUNC_INFO, + "Only one event callback of type currently supported with EventCallback"); m_element.set(contextPropertyName(m_eventName).c_str(), emscripten::val(intptr_t(this))); m_element.set((std::string("on") + m_eventName).c_str(), emscripten::val::module_property("qtStdWebEventCallbackActivate")); } diff --git a/src/corelib/platform/wasm/qstdweb_p.h b/src/corelib/platform/wasm/qstdweb_p.h index 2f154180..1b832319 100644 --- a/src/corelib/platform/wasm/qstdweb_p.h +++ b/src/corelib/platform/wasm/qstdweb_p.h @@ -208,7 +208,7 @@ namespace qstdweb { { }; - std::shared_ptr + Q_CORE_EXPORT std::shared_ptr readDataTransfer(emscripten::val webObject, std::function imageReader, std::function)> onDone); } diff --git a/src/corelib/plugin/qfactoryloader.cpp b/src/corelib/plugin/qfactoryloader.cpp index 52f6f440..f555cb80 100644 --- a/src/corelib/plugin/qfactoryloader.cpp +++ b/src/corelib/plugin/qfactoryloader.cpp @@ -161,7 +161,7 @@ inline void QFactoryLoaderPrivate::updateSinglePath(const QString &path) while (plugins.hasNext()) { QString fileName = plugins.next(); -#ifdef Q_OS_MAC +#ifdef Q_OS_DARWIN const bool isDebugPlugin = fileName.endsWith("_debug.dylib"_L1); const bool isDebugLibrary = #ifdef QT_DEBUG @@ -266,7 +266,7 @@ QFactoryLoader::~QFactoryLoader() } } -#if defined(Q_OS_UNIX) && !defined (Q_OS_MAC) +#if defined(Q_OS_UNIX) && !defined (Q_OS_DARWIN) QLibraryPrivate *QFactoryLoader::library(const QString &key) const { Q_D(const QFactoryLoader); diff --git a/src/corelib/plugin/qfactoryloader_p.h b/src/corelib/plugin/qfactoryloader_p.h index 0c8f9777..fcfb4cf5 100644 --- a/src/corelib/plugin/qfactoryloader_p.h +++ b/src/corelib/plugin/qfactoryloader_p.h @@ -74,9 +74,9 @@ public: void update(); static void refreshAll(); -#if defined(Q_OS_UNIX) && !defined (Q_OS_MAC) +#if defined(Q_OS_UNIX) && !defined (Q_OS_DARWIN) QLibraryPrivate *library(const QString &key) const; -#endif // Q_OS_UNIX && !Q_OS_MAC +#endif // Q_OS_UNIX && !Q_OS_DARWIN #endif // QT_CONFIG(library) void setExtraSearchPath(const QString &path); diff --git a/src/corelib/plugin/qlibrary.cpp b/src/corelib/plugin/qlibrary.cpp index 7ce35dca..12ae0c2e 100644 --- a/src/corelib/plugin/qlibrary.cpp +++ b/src/corelib/plugin/qlibrary.cpp @@ -16,7 +16,7 @@ #include #include -#ifdef Q_OS_MAC +#ifdef Q_OS_DARWIN # include #endif #include @@ -712,7 +712,7 @@ void QLibraryPrivate::updatePluginState() bool success = false; -#if defined(Q_OS_UNIX) && !defined(Q_OS_MAC) +#if defined(Q_OS_UNIX) && !defined(Q_OS_DARWIN) if (fileName.endsWith(".debug"_L1)) { // refuse to load a file that ends in .debug // these are the debug symbols from the libraries @@ -836,13 +836,17 @@ bool QLibrary::unload() } /*! - Returns \c true if the library is loaded; otherwise returns \c false. + Returns \c true if load() succeeded; otherwise returns \c false. + + \note Prior to Qt 6.6, this function would return \c true even without a + call to load() if another QLibrary object on the same library had caused it + to be loaded. \sa load() */ bool QLibrary::isLoaded() const { - return d && d->pHnd.loadRelaxed(); + return d.tag() == Loaded; } @@ -979,8 +983,7 @@ void QLibrary::setFileNameAndVersion(const QString &fileName, const QString &ver d->release(); } QLibraryPrivate *dd = QLibraryPrivate::findOrCreate(fileName, version, lh); - d = dd; - d.setTag(isLoaded() ? Loaded : NotLoaded); + d = QTaggedPointer(dd, NotLoaded); // we haven't load()ed } /*! diff --git a/src/corelib/plugin/qlibrary_unix.cpp b/src/corelib/plugin/qlibrary_unix.cpp index 657c8d79..84f572a0 100644 --- a/src/corelib/plugin/qlibrary_unix.cpp +++ b/src/corelib/plugin/qlibrary_unix.cpp @@ -12,7 +12,7 @@ #include -#ifdef Q_OS_MAC +#ifdef Q_OS_DARWIN # include #endif @@ -72,7 +72,7 @@ QStringList QLibraryPrivate::suffixes_sys(const QString &fullVersion) # endif } #endif -# ifdef Q_OS_MAC +# ifdef Q_OS_DARWIN if (!fullVersion.isEmpty()) { suffixes << ".%1.bundle"_L1.arg(fullVersion); suffixes << ".%1.dylib"_L1.arg(fullVersion); @@ -233,7 +233,7 @@ bool QLibraryPrivate::load_sys() } } -#ifdef Q_OS_MAC +#ifdef Q_OS_DARWIN if (!hnd) { QByteArray utf8Bundle = fileName.toUtf8(); QCFType bundleUrl = CFURLCreateFromFileSystemRepresentation(NULL, reinterpret_cast(utf8Bundle.data()), utf8Bundle.length(), true); @@ -285,7 +285,7 @@ Q_CORE_EXPORT QFunctionPointer qt_linux_find_symbol_sys(const char *symbol) } #endif -#ifdef Q_OS_MAC +#ifdef Q_OS_DARWIN Q_CORE_EXPORT QFunctionPointer qt_mac_resolve_sys(void *handle, const char *symbol) { return QFunctionPointer(dlsym(handle, symbol)); diff --git a/src/corelib/plugin/qplugin.h b/src/corelib/plugin/qplugin.h index 30f5cbb9..909c8acd 100644 --- a/src/corelib/plugin/qplugin.h +++ b/src/corelib/plugin/qplugin.h @@ -127,7 +127,7 @@ void Q_CORE_EXPORT qRegisterStaticPluginFunction(QStaticPlugin staticPlugin); #if defined(Q_OF_ELF) || (defined(Q_OS_WIN) && (defined (Q_CC_GNU) || defined(Q_CC_CLANG))) # define QT_PLUGIN_METADATA_SECTION \ __attribute__ ((section (".qtmetadata"))) __attribute__((used)) -#elif defined(Q_OS_MAC) +#elif defined(Q_OS_DARWIN) # define QT_PLUGIN_METADATA_SECTION \ __attribute__ ((section ("__TEXT,qtmetadata"))) __attribute__((used)) #elif defined(Q_CC_MSVC) diff --git a/src/corelib/plugin/quuid.cpp b/src/corelib/plugin/quuid.cpp index 8613f96c..1e263a44 100644 --- a/src/corelib/plugin/quuid.cpp +++ b/src/corelib/plugin/quuid.cpp @@ -13,6 +13,9 @@ QT_BEGIN_NAMESPACE +// ensure QList of this is efficient +static_assert(QTypeInfo::isRelocatable); + // 16 bytes (a uint, two shorts and a uchar[8]), each represented by two hex // digits; plus four dashes and a pair of enclosing brace: 16*2 + 4 + 2 = 38. enum { MaxStringUuidLength = 38 }; @@ -286,6 +289,125 @@ static QUuid createFromName(const QUuid &ns, const QByteArray &baseData, QCrypto cannot parse this back again as input. */ +/*! + \class QUuid::Id128Bytes + \inmodule QtCore + \since 6.6 + + This trivial structure is 128 bits (16 bytes) in size and holds the binary + representation of a UUID. Applications can \c{memcpy()} its contents to and + from many other libraries' UUID or GUID structures that take 128-bit + values. +*/ + +/*! + \fn QUuid::Id128Bytes qFromBigEndian(QUuid::Id128Bytes src) + \since 6.6 + \relates QUuid::Id128Bytes + \overload + + Converts \a src from big-endian byte order and returns the struct holding + the binary representation of UUID in host byte order. + + \sa +*/ + +/*! + \fn QUuid::Id128Bytes qFromLittleEndian(QUuid::Id128Bytes src) + \since 6.6 + \relates QUuid::Id128Bytes + \overload + + Converts \a src from little-endian byte order and returns the struct holding + the binary representation of UUID in host byte order. + + \sa +*/ + +/*! + \fn QUuid::Id128Bytes qToBigEndian(QUuid::Id128Bytes src) + \since 6.6 + \relates QUuid::Id128Bytes + \overload + + Converts \a src from host byte order and returns the struct holding the + binary representation of UUID in big-endian byte order. + + \sa +*/ + +/*! + \fn QUuid::Id128Bytes qToLittleEndian(QUuid::Id128Bytes src) + \since 6.6 + \relates QUuid::Id128Bytes + \overload + + Converts \a src from host byte order and returns the struct holding the + binary representation of UUID in little-endian byte order. + + \sa +*/ + +/*! + \fn QUuid::QUuid(Id128Bytes id128, QSysInfo::Endian order) noexcept + \since 6.6 + + Creates a QUuid based on the integral \a id128 parameter. The input + \a id128 parameter is considered to have byte order \a order. + + \sa fromBytes(), toBytes(), toRfc4122(), toUInt128() +*/ + +/*! + \fn QUuid::fromUInt128(quint128 uuid, QSysInfo::Endian order) noexcept + \since 6.6 + + Creates a QUuid based on the integral \a uuid parameter. The input \a uuid + parameter is considered to have byte order \a order. + + \note This function is only present on platforms that offer a 128-bit + integer type. + + \sa toUInt128(), fromBytes(), toBytes(), toRfc4122() +*/ + +/*! + \fn quint128 QUuid::toUInt128(QSysInfo::Endian order) const noexcept + \since 6.6 + + Returns a 128-bit integer created from this QUuid on the byte order + specified by \a order. The binary content of this function is the same as + toRfc4122() if the order is QSysInfo::BigEndian. See that function for more + details. + + \note This function is only present on platforms that offer a 128-bit + integer type. + + \sa toRfc4122(), fromUInt128(), toBytes(), fromBytes(), QUuid() +*/ + +/*! + \fn QUuid::Id128Bytes QUuid::toBytes(QSysInfo::Endian order) const noexcept + \since 6.6 + + Returns a 128-bit ID created from this QUuid on the byte order specified + by \a order. The binary content of this function is the same as toRfc4122() + if the order is QSysInfo::BigEndian. See that function for more details. + + \sa toRfc4122(), fromBytes(), QUuid() +*/ + +/*! + \fn QUuid QUuid::fromBytes(const void *bytes, QSysInfo::Endian order) noexcept + \since 6.6 + + Reads 128 bits (16 bytes) from \a bytes using byte order \a order and + returns the QUuid corresponding to those bytes. This function does the same + as fromRfc4122() if the byte order \a order is QSysInfo::BigEndian. + + \sa fromRfc4122() +*/ + /*! \fn QUuid::QUuid(const GUID &guid) @@ -468,32 +590,13 @@ QUuid QUuid::createUuidV5(const QUuid &ns, const QByteArray &baseData) \since 4.8 - \sa toRfc4122(), QUuid() + \sa toRfc4122(), QUuid(), fromBytes() */ QUuid QUuid::fromRfc4122(QByteArrayView bytes) noexcept { if (bytes.isEmpty() || bytes.size() != 16) return QUuid(); - - uint d1; - ushort d2, d3; - uchar d4[8]; - - const uchar *data = reinterpret_cast(bytes.data()); - - d1 = qFromBigEndian(data); - data += sizeof(quint32); - d2 = qFromBigEndian(data); - data += sizeof(quint16); - d3 = qFromBigEndian(data); - data += sizeof(quint16); - - for (int i = 0; i < 8; ++i) { - d4[i] = *(data); - data++; - } - - return QUuid(d1, d2, d3, d4[0], d4[1], d4[2], d4[3], d4[4], d4[5], d4[6], d4[7]); + return fromBytes(bytes.data()); } /*! @@ -623,27 +726,16 @@ QByteArray QUuid::toByteArray(QUuid::StringFormat mode) const \endtable + The bytes in the byte array returned by this function contains the same + binary content as toBytes(). + + \sa toBytes() \since 4.8 */ QByteArray QUuid::toRfc4122() const { - // we know how many bytes a UUID has, I hope :) - QByteArray bytes(16, Qt::Uninitialized); - uchar *data = reinterpret_cast(bytes.data()); - - qToBigEndian(data1, data); - data += sizeof(quint32); - qToBigEndian(data2, data); - data += sizeof(quint16); - qToBigEndian(data3, data); - data += sizeof(quint16); - - for (int i = 0; i < 8; ++i) { - *(data) = data4[i]; - data++; - } - - return bytes; + Id128Bytes bytes = toBytes(); + return QByteArrayView(bytes).toByteArray(); } #ifndef QT_NO_DATASTREAM @@ -661,6 +753,9 @@ QDataStream &operator<<(QDataStream &s, const QUuid &id) bytes = QByteArray(16, Qt::Uninitialized); uchar *data = reinterpret_cast(bytes.data()); + // for historical reasons, our little-endian serialization format + // stores each of the UUID fields in little endian, instead of storing + // a little endian Id128 qToLittleEndian(id.data1, data); data += sizeof(quint32); qToLittleEndian(id.data2, data); diff --git a/src/corelib/plugin/quuid.h b/src/corelib/plugin/quuid.h index 64aa891b..a62651b1 100644 --- a/src/corelib/plugin/quuid.h +++ b/src/corelib/plugin/quuid.h @@ -4,6 +4,7 @@ #ifndef QUUID_H #define QUUID_H +#include #include #if defined(Q_OS_WIN) || defined(Q_QDOC) @@ -26,7 +27,6 @@ Q_FORWARD_DECLARE_OBJC_CLASS(NSUUID); QT_BEGIN_NAMESPACE - class Q_CORE_EXPORT QUuid { QUuid(Qt::Initialization) {} @@ -55,11 +55,37 @@ public: Id128 = 3 }; + union alignas(16) Id128Bytes { + quint8 data[16]; + quint16 data16[8]; + quint32 data32[4]; + quint64 data64[2]; +#ifdef QT_SUPPORTS_INT128 + quint128 data128[1]; +#endif + + constexpr explicit operator QByteArrayView() const noexcept + { + return QByteArrayView(data, sizeof(data)); + } + + friend constexpr Id128Bytes qbswap(Id128Bytes b) noexcept + { + // 128-bit byte swap + auto b0 = qbswap(b.data64[0]); + auto b1 = qbswap(b.data64[1]); + b.data64[0] = b1; + b.data64[1] = b0; + return b; + } + }; + constexpr QUuid() noexcept : data1(0), data2(0), data3(0), data4{0,0,0,0,0,0,0,0} {} constexpr QUuid(uint l, ushort w1, ushort w2, uchar b1, uchar b2, uchar b3, uchar b4, uchar b5, uchar b6, uchar b7, uchar b8) noexcept : data1(l), data2(w1), data3(w2), data4{b1, b2, b3, b4, b5, b6, b7, b8} {} + explicit inline QUuid(Id128Bytes id128, QSysInfo::Endian order = QSysInfo::BigEndian) noexcept; explicit QUuid(QAnyStringView string) noexcept : QUuid{fromString(string)} {} @@ -73,13 +99,22 @@ public: #endif QString toString(StringFormat mode = WithBraces) const; QByteArray toByteArray(StringFormat mode = WithBraces) const; + inline Id128Bytes toBytes(QSysInfo::Endian order = QSysInfo::BigEndian) const noexcept; QByteArray toRfc4122() const; + + static inline QUuid fromBytes(const void *bytes, QSysInfo::Endian order = QSysInfo::BigEndian); #if QT_CORE_REMOVED_SINCE(6, 3) static QUuid fromRfc4122(const QByteArray &); #endif static QUuid fromRfc4122(QByteArrayView) noexcept; + bool isNull() const noexcept; +#ifdef QT_SUPPORTS_INT128 + static constexpr QUuid fromUInt128(quint128 uuid, QSysInfo::Endian order = QSysInfo::BigEndian) noexcept; + constexpr quint128 toUInt128(QSysInfo::Endian order = QSysInfo::BigEndian) const noexcept; +#endif + constexpr bool operator==(const QUuid &orig) const noexcept { if (data1 != orig.data1 || data2 != orig.data2 || @@ -177,11 +212,92 @@ Q_CORE_EXPORT QDebug operator<<(QDebug, const QUuid &); Q_CORE_EXPORT size_t qHash(const QUuid &uuid, size_t seed = 0) noexcept; +QUuid::QUuid(Id128Bytes uuid, QSysInfo::Endian order) noexcept +{ + if (order == QSysInfo::LittleEndian) + uuid = qbswap(uuid); + data1 = qFromBigEndian(&uuid.data[0]); + data2 = qFromBigEndian(&uuid.data[4]); + data3 = qFromBigEndian(&uuid.data[6]); + memcpy(data4, &uuid.data[8], sizeof(data4)); +} + +QUuid::Id128Bytes QUuid::toBytes(QSysInfo::Endian order) const noexcept +{ + Id128Bytes result = {}; + qToBigEndian(data1, &result.data[0]); + qToBigEndian(data2, &result.data[4]); + qToBigEndian(data3, &result.data[6]); + memcpy(&result.data[8], data4, sizeof(data4)); + if (order == QSysInfo::LittleEndian) + return qbswap(result); + return result; +} + +QUuid QUuid::fromBytes(const void *bytes, QSysInfo::Endian order) +{ + Id128Bytes result = {}; + memcpy(result.data, bytes, sizeof(result)); + return QUuid(result, order); +} + +#ifdef QT_SUPPORTS_INT128 +constexpr QUuid QUuid::fromUInt128(quint128 uuid, QSysInfo::Endian order) noexcept +{ + QUuid result = {}; + if (order == QSysInfo::BigEndian) { + result.data1 = qFromBigEndian(int(uuid)); + result.data2 = qFromBigEndian(ushort(uuid >> 32)); + result.data3 = qFromBigEndian(ushort(uuid >> 48)); + for (int i = 0; i < 8; ++i) + result.data4[i] = uchar(uuid >> (64 + i * 8)); + } else { + result.data1 = qFromLittleEndian(uint(uuid >> 96)); + result.data2 = qFromLittleEndian(ushort(uuid >> 80)); + result.data3 = qFromLittleEndian(ushort(uuid >> 64)); + for (int i = 0; i < 8; ++i) + result.data4[i] = uchar(uuid >> (56 - i * 8)); + } + return result; +} + +constexpr quint128 QUuid::toUInt128(QSysInfo::Endian order) const noexcept +{ + quint128 result = {}; + if (order == QSysInfo::BigEndian) { + for (int i = 0; i < 8; ++i) + result |= quint64(data4[i]) << (i * 8); + result = result << 64; + result |= quint64(qToBigEndian(data3)) << 48; + result |= quint64(qToBigEndian(data2)) << 32; + result |= qToBigEndian(data1); + } else { + result = qToLittleEndian(data1); + result = result << 32; + result |= quint64(qToLittleEndian(data2)) << 16; + result |= quint64(qToLittleEndian(data3)); + result = result << 64; + for (int i = 0; i < 8; ++i) + result |= quint64(data4[i]) << (56 - i * 8); + } + return result; +} +#endif + inline bool operator<=(const QUuid &lhs, const QUuid &rhs) noexcept { return !(rhs < lhs); } inline bool operator>=(const QUuid &lhs, const QUuid &rhs) noexcept { return !(lhs < rhs); } +#if defined(Q_QDOC) +// provide fake declarations of qXXXEndian() functions, so that qDoc could +// distinguish them from the general template +QUuid::Id128Bytes qFromBigEndian(QUuid::Id128Bytes src); +QUuid::Id128Bytes qFromLittleEndian(QUuid::Id128Bytes src); +QUuid::Id128Bytes qToBigEndian(QUuid::Id128Bytes src); +QUuid::Id128Bytes qToLittleEndian(QUuid::Id128Bytes src); +#endif + QT_END_NAMESPACE #endif // QUUID_H diff --git a/src/corelib/serialization/qcborvalue.cpp b/src/corelib/serialization/qcborvalue.cpp index 337af3a8..a1c56dfc 100644 --- a/src/corelib/serialization/qcborvalue.cpp +++ b/src/corelib/serialization/qcborvalue.cpp @@ -404,7 +404,7 @@ Q_DECL_UNUSED static constexpr quint64 MaximumPreallocatedElementCount = /*! \fn QCborValue::QCborValue(QCborSimpleType st) - Creates a QCborValue of simple type \a st. The type can later later be retrieved + Creates a QCborValue of simple type \a st. The type can later be retrieved using toSimpleType() as well as isSimpleType(st). CBOR simple types are types that do not have any associated value, like diff --git a/src/corelib/serialization/qdatastream.cpp b/src/corelib/serialization/qdatastream.cpp index dd5d678c..c47c9089 100644 --- a/src/corelib/serialization/qdatastream.cpp +++ b/src/corelib/serialization/qdatastream.cpp @@ -525,6 +525,7 @@ void QDataStream::setByteOrder(ByteOrder bo) \value Qt_6_3 Same as Qt_6_0 \value Qt_6_4 Same as Qt_6_0 \value Qt_6_5 Same as Qt_6_0 + \value Qt_6_6 Same as Qt_6_0 \omitvalue Qt_DefaultCompiledVersion \sa setVersion(), version() diff --git a/src/corelib/serialization/qdatastream.h b/src/corelib/serialization/qdatastream.h index 8d89e4bb..9e35328d 100644 --- a/src/corelib/serialization/qdatastream.h +++ b/src/corelib/serialization/qdatastream.h @@ -68,8 +68,9 @@ public: Qt_6_3 = Qt_6_0, Qt_6_4 = Qt_6_0, Qt_6_5 = Qt_6_0, - Qt_DefaultCompiledVersion = Qt_6_5 -#if QT_VERSION >= QT_VERSION_CHECK(6, 6, 0) + Qt_6_6 = 21, + Qt_DefaultCompiledVersion = Qt_6_6 +#if QT_VERSION >= QT_VERSION_CHECK(6, 7, 0) #error Add the datastream version for this Qt version and update Qt_DefaultCompiledVersion #endif }; diff --git a/src/corelib/serialization/qjsonparser.cpp b/src/corelib/serialization/qjsonparser.cpp index d2938d65..8d866398 100644 --- a/src/corelib/serialization/qjsonparser.cpp +++ b/src/corelib/serialization/qjsonparser.cpp @@ -780,15 +780,13 @@ bool Parser::parseNumber() static inline bool addHexDigit(char digit, char32_t *result) { *result <<= 4; - if (digit >= '0' && digit <= '9') - *result |= (digit - '0'); - else if (digit >= 'a' && digit <= 'f') - *result |= (digit - 'a') + 10; - else if (digit >= 'A' && digit <= 'F') - *result |= (digit - 'A') + 10; - else - return false; - return true; + const int h = fromHex(digit); + if (h != -1) { + *result |= h; + return true; + } + + return false; } static inline bool scanEscapeSequence(const char *&json, const char *end, char32_t *ch) diff --git a/src/corelib/serialization/qjsonwriter.cpp b/src/corelib/serialization/qjsonwriter.cpp index 56c75142..c9da553a 100644 --- a/src/corelib/serialization/qjsonwriter.cpp +++ b/src/corelib/serialization/qjsonwriter.cpp @@ -22,23 +22,24 @@ static inline uchar hexdig(uint u) return (u < 0xa ? '0' + u : 'a' + u - 0xa); } -static QByteArray escapedString(const QString &s) +static QByteArray escapedString(QStringView s) { // give it a minimum size to ensure the resize() below always adds enough space QByteArray ba(qMax(s.size(), 16), Qt::Uninitialized); + auto ba_const_start = [&]() { return reinterpret_cast(ba.constData()); }; uchar *cursor = reinterpret_cast(const_cast(ba.constData())); const uchar *ba_end = cursor + ba.size(); - const char16_t *src = reinterpret_cast(s.constBegin()); - const char16_t *const end = reinterpret_cast(s.constEnd()); + const char16_t *src = s.utf16(); + const char16_t *const end = s.utf16() + s.size(); while (src != end) { if (cursor >= ba_end - 6) { // ensure we have enough space - qptrdiff pos = cursor - (const uchar *)ba.constData(); + qptrdiff pos = cursor - ba_const_start(); ba.resize(ba.size()*2); - cursor = (uchar *)ba.data() + pos; - ba_end = (const uchar *)ba.constData() + ba.size(); + cursor = reinterpret_cast(ba.data()) + pos; + ba_end = ba_const_start() + ba.size(); } char16_t u = *src++; @@ -88,7 +89,7 @@ static QByteArray escapedString(const QString &s) } } - ba.resize(cursor - (const uchar *)ba.constData()); + ba.resize(cursor - ba_const_start()); return ba; } diff --git a/src/corelib/serialization/qtextstream.cpp b/src/corelib/serialization/qtextstream.cpp index 10ecd4ea..96dac022 100644 --- a/src/corelib/serialization/qtextstream.cpp +++ b/src/corelib/serialization/qtextstream.cpp @@ -1752,13 +1752,10 @@ QTextStreamPrivate::NumberParsingStatus QTextStreamPrivate::getNumber(qulonglong // Parse digits int ndigits = 0; while (getChar(&dig)) { - int n = dig.toLower().unicode(); - if (n >= '0' && n <= '9') { + const int h = fromHex(dig.unicode()); + if (h != -1) { val <<= 4; - val += n - '0'; - } else if (n >= 'a' && n <= 'f') { - val <<= 4; - val += 10 + (n - 'a'); + val += h; } else { ungetChar(dig); break; diff --git a/src/corelib/serialization/qtextstream.h b/src/corelib/serialization/qtextstream.h index e1768789..7a0fc42a 100644 --- a/src/corelib/serialization/qtextstream.h +++ b/src/corelib/serialization/qtextstream.h @@ -11,6 +11,11 @@ #include +#if 0 +// the macros around the class name throw off syncqt: +#pragma qt_class(QTextStream) +#endif + #ifdef Status #error qtextstream.h must be included before any header file that defines Status #endif @@ -21,8 +26,18 @@ class QIODevice; class QLocale; class QString; +#if !QT_DEPRECATED_SINCE(6, 9) +# define QT_NO_INHERITABLE_TEXT_STREAM +#endif + +#ifdef QT_NO_INHERITABLE_TEXT_STREAM +# define QT_TEXT_STREAM_FINAL final +#else +# define QT_TEXT_STREAM_FINAL +#endif + class QTextStreamPrivate; -class Q_CORE_EXPORT QTextStream : public QIODeviceBase +class Q_CORE_EXPORT QTextStream QT_TEXT_STREAM_FINAL : public QIODeviceBase { Q_DECLARE_PRIVATE(QTextStream) @@ -59,7 +74,8 @@ public: explicit QTextStream(QString *string, OpenMode openMode = ReadWrite); explicit QTextStream(QByteArray *array, OpenMode openMode = ReadWrite); explicit QTextStream(const QByteArray &array, OpenMode openMode = ReadOnly); - virtual ~QTextStream(); + QT6_ONLY(virtual) + ~QTextStream(); void setEncoding(QStringConverter::Encoding encoding); QStringConverter::Encoding encoding() const; diff --git a/src/corelib/serialization/qxmlstream.cpp b/src/corelib/serialization/qxmlstream.cpp index 9b595000..8cd6da10 100644 --- a/src/corelib/serialization/qxmlstream.cpp +++ b/src/corelib/serialization/qxmlstream.cpp @@ -3,7 +3,7 @@ #include "QtCore/qxmlstream.h" -#ifndef QT_NO_XMLSTREAM +#if QT_CONFIG(xmlstream) #include "qxmlutils_p.h" #include @@ -140,7 +140,7 @@ WRAP(indexOf, QLatin1StringView) \value DTD The reader reports a DTD in text(), notation declarations in notationDeclarations(), and entity declarations in entityDeclarations(). Details of the DTD declaration are reported - in in dtdName(), dtdPublicId(), and dtdSystemId(). + in dtdName(), dtdPublicId(), and dtdSystemId(). \value EntityReference The reader reports an entity reference that could not be resolved. The name of the reference is reported in @@ -232,7 +232,7 @@ QString QXmlStreamEntityResolver::resolveUndeclaredEntity(const QString &/*name* return QString(); } -#ifndef QT_NO_XMLSTREAMREADER +#if QT_CONFIG(xmlstreamreader) QString QXmlStreamReaderPrivate::resolveUndeclaredEntity(const QString &name) { @@ -813,7 +813,7 @@ static constexpr QLatin1StringView contextString(QXmlStreamReaderPrivate::XmlCon return QLatin1StringView(QXmlStreamReader_XmlContextString.at(static_cast(ctxt))); } -#endif // QT_NO_XMLSTREAMREADER +#endif // feature xmlstreamreader QXmlStreamPrivateTagStack::QXmlStreamPrivateTagStack() { @@ -827,7 +827,7 @@ QXmlStreamPrivateTagStack::QXmlStreamPrivateTagStack() tagsDone = false; } -#ifndef QT_NO_XMLSTREAMREADER +#if QT_CONFIG(xmlstreamreader) QXmlStreamReaderPrivate::QXmlStreamReaderPrivate(QXmlStreamReader *q) :q_ptr(q) @@ -2320,7 +2320,7 @@ QXmlStreamAttributes QXmlStreamReader::attributes() const return d->attributes; } -#endif // QT_NO_XMLSTREAMREADER +#endif // feature xmlstreamreader /*! \class QXmlStreamAttribute @@ -2623,37 +2623,11 @@ Returns the entity's value. /*! Returns the value of the attribute \a name in the namespace described with \a namespaceUri, or an empty string reference if the attribute is not defined. The \a namespaceUri can be empty. - */ -QStringView QXmlStreamAttributes::value(const QString &namespaceUri, const QString &name) const -{ - for (const QXmlStreamAttribute &attribute : *this) { - if (attribute.name() == name && attribute.namespaceUri() == namespaceUri) - return attribute.value(); - } - return QStringView(); -} -/*!\overload - Returns the value of the attribute \a name in the namespace - described with \a namespaceUri, or an empty string reference if the - attribute is not defined. The \a namespaceUri can be empty. + \note In Qt versions prior to 6.6, this function was implemented as an + overload set accepting combinations of QString and QLatin1StringView only. */ -QStringView QXmlStreamAttributes::value(const QString &namespaceUri, QLatin1StringView name) const -{ - for (const QXmlStreamAttribute &attribute : *this) { - if (attribute.name() == name && attribute.namespaceUri() == namespaceUri) - return attribute.value(); - } - return QStringView(); -} - -/*!\overload - Returns the value of the attribute \a name in the namespace - described with \a namespaceUri, or an empty string reference if the - attribute is not defined. The \a namespaceUri can be empty. - */ -QStringView QXmlStreamAttributes::value(QLatin1StringView namespaceUri, - QLatin1StringView name) const +QStringView QXmlStreamAttributes::value(QAnyStringView namespaceUri, QAnyStringView name) const noexcept { for (const QXmlStreamAttribute &attribute : *this) { if (attribute.name() == name && attribute.namespaceUri() == namespaceUri) @@ -2673,29 +2647,12 @@ QStringView QXmlStreamAttributes::value(QLatin1StringView namespaceUri, different prefixes can point to the same namespace), you shouldn't use qualified names, but a resolved namespaceUri and the attribute's local name. - */ -QStringView QXmlStreamAttributes::value(const QString &qualifiedName) const -{ - for (const QXmlStreamAttribute &attribute : *this) { - if (attribute.qualifiedName() == qualifiedName) - return attribute.value(); - } - return QStringView(); -} -/*!\overload + \note In Qt versions prior to 6.6, this function was implemented as an + overload set accepting QString and QLatin1StringView only. - Returns the value of the attribute with qualified name \a - qualifiedName , or an empty string reference if the attribute is not - defined. A qualified name is the raw name of an attribute in the XML - data. It consists of the namespace prefix, followed by colon, - followed by the attribute's local name. Since the namespace prefix - is not unique (the same prefix can point to different namespaces and - different prefixes can point to the same namespace), you shouldn't - use qualified names, but a resolved namespaceUri and the attribute's - local name. */ -QStringView QXmlStreamAttributes::value(QLatin1StringView qualifiedName) const +QStringView QXmlStreamAttributes::value(QAnyStringView qualifiedName) const noexcept { for (const QXmlStreamAttribute &attribute : *this) { if (attribute.qualifiedName() == qualifiedName) @@ -2722,7 +2679,7 @@ void QXmlStreamAttributes::append(const QString &qualifiedName, const QString &v append(QXmlStreamAttribute(qualifiedName, value)); } -#ifndef QT_NO_XMLSTREAMREADER +#if QT_CONFIG(xmlstreamreader) /*! \fn bool QXmlStreamReader::isStartDocument() const Returns \c true if tokenType() equals \l StartDocument; otherwise returns \c false. @@ -2783,6 +2740,8 @@ bool QXmlStreamReader::isCDATA() const XML declaration; otherwise returns \c false. If no XML declaration has been parsed, this function returns \c false. + + \sa hasStandaloneDeclaration() */ bool QXmlStreamReader::isStandaloneDocument() const { @@ -2790,6 +2749,21 @@ bool QXmlStreamReader::isStandaloneDocument() const return d->standalone; } +/*! + \since 6.6 + + Returns \c true if this document has an explicit standalone + declaration (can be 'yes' or 'no'); otherwise returns \c false; + + If no XML declaration has been parsed, this function returns \c false. + + \sa isStandaloneDocument() + */ +bool QXmlStreamReader::hasStandaloneDeclaration() const +{ + Q_D(const QXmlStreamReader); + return d->hasStandalone; +} /*! \since 4.4 @@ -2821,7 +2795,7 @@ QStringView QXmlStreamReader::documentEncoding() const return QStringView(); } -#endif // QT_NO_XMLSTREAMREADER +#endif // feature xmlstreamreader /*! \class QXmlStreamWriter @@ -2898,9 +2872,10 @@ QStringView QXmlStreamReader::documentEncoding() const */ -#ifndef QT_NO_XMLSTREAMWRITER +#if QT_CONFIG(xmlstreamwriter) -class QXmlStreamWriterPrivate : public QXmlStreamPrivateTagStack { +class QXmlStreamWriterPrivate : public QXmlStreamPrivateTagStack +{ QXmlStreamWriter *q_ptr; Q_DECLARE_PUBLIC(QXmlStreamWriter) public: @@ -2924,7 +2899,7 @@ public: uint hasIoError :1; uint hasEncodingError :1; uint autoFormatting :1; - QByteArray autoFormattingIndent; + std::string autoFormattingIndent; NamespaceDeclaration emptyNamespace; qsizetype lastNamespaceDeclaration; @@ -3284,13 +3259,14 @@ bool QXmlStreamWriter::autoFormatting() const void QXmlStreamWriter::setAutoFormattingIndent(int spacesOrTabs) { Q_D(QXmlStreamWriter); - d->autoFormattingIndent = QByteArray(qAbs(spacesOrTabs), spacesOrTabs >= 0 ? ' ' : '\t'); + d->autoFormattingIndent.assign(size_t(qAbs(spacesOrTabs)), spacesOrTabs >= 0 ? ' ' : '\t'); } int QXmlStreamWriter::autoFormattingIndent() const { Q_D(const QXmlStreamWriter); - return d->autoFormattingIndent.count(' ') - d->autoFormattingIndent.count('\t'); + const QLatin1StringView indent(d->autoFormattingIndent); + return indent.count(u' ') - indent.count(u'\t'); } /*! @@ -3819,7 +3795,7 @@ void QXmlStreamWriterPrivate::writeStartElement(QAnyStringView namespaceUri, QAn tag.namespaceDeclarationsSize = lastNamespaceDeclaration; } -#ifndef QT_NO_XMLSTREAMREADER +#if QT_CONFIG(xmlstreamreader) /*! Writes the current state of the \a reader. All possible valid states are supported. @@ -3877,7 +3853,7 @@ void QXmlStreamWriter::writeCurrentToken(const QXmlStreamReader &reader) } static constexpr bool isTokenAllowedInContext(QXmlStreamReader::TokenType type, - QXmlStreamReaderPrivate::XmlContext ctxt) + QXmlStreamReaderPrivate::XmlContext ctxt) { switch (type) { case QXmlStreamReader::StartDocument: @@ -3991,9 +3967,9 @@ void QXmlStreamReaderPrivate::checkToken() otherwise returns \c false. */ -#endif // QT_NO_XMLSTREAMREADER -#endif // QT_NO_XMLSTREAMWRITER +#endif // feature xmlstreamreader +#endif // feature xmlstreamwriter QT_END_NAMESPACE -#endif // QT_NO_XMLSTREAM +#endif // feature xmlstream diff --git a/src/corelib/serialization/qxmlstream.g b/src/corelib/serialization/qxmlstream.g index fc122e66..36df37b1 100644 --- a/src/corelib/serialization/qxmlstream.g +++ b/src/corelib/serialization/qxmlstream.g @@ -58,7 +58,7 @@ %token PCDATA "PCDATA" -- error -%token ERROR +%token XML_ERROR -- entities %token PARSE_ENTITY @@ -149,7 +149,7 @@ QT_BEGIN_NAMESPACE -#ifndef QT_NO_XMLSTREAMREADER +#if QT_CONFIG(xmlstreamreader) bool QXmlStreamReaderPrivate::parse() { @@ -251,7 +251,7 @@ bool QXmlStreamReaderPrivate::parse() } else switch (token_char) { case 0xfffe: case 0xffff: - token = ERROR; + token = XML_ERROR; break; case '\r': token = SPACE; @@ -1487,7 +1487,7 @@ nmtoken ::= COLON; return false; } -#endif +#endif // feature xmlstreamreader QT_END_NAMESPACE diff --git a/src/corelib/serialization/qxmlstream.h b/src/corelib/serialization/qxmlstream.h index fffb9b68..e6dd2511 100644 --- a/src/corelib/serialization/qxmlstream.h +++ b/src/corelib/serialization/qxmlstream.h @@ -6,7 +6,7 @@ #include -#ifndef QT_NO_XMLSTREAM +#if QT_CONFIG(xmlstream) #include #include @@ -75,25 +75,25 @@ class QXmlStreamAttributes : public QList { public: inline QXmlStreamAttributes() {} +#if QT_CORE_REMOVED_SINCE(6, 6) Q_CORE_EXPORT QStringView value(const QString &namespaceUri, const QString &name) const; Q_CORE_EXPORT QStringView value(const QString &namespaceUri, QLatin1StringView name) const; Q_CORE_EXPORT QStringView value(QLatin1StringView namespaceUri, QLatin1StringView name) const; Q_CORE_EXPORT QStringView value(const QString &qualifiedName) const; Q_CORE_EXPORT QStringView value(QLatin1StringView qualifiedName) const; +#endif + Q_CORE_EXPORT QStringView value(QAnyStringView namespaceUri, QAnyStringView name) const noexcept; + Q_CORE_EXPORT QStringView value(QAnyStringView qualifiedName) const noexcept; + Q_CORE_EXPORT void append(const QString &namespaceUri, const QString &name, const QString &value); Q_CORE_EXPORT void append(const QString &qualifiedName, const QString &value); - inline bool hasAttribute(const QString &qualifiedName) const + bool hasAttribute(QAnyStringView qualifiedName) const { return !value(qualifiedName).isNull(); } - inline bool hasAttribute(QLatin1StringView qualifiedName) const - { - return !value(qualifiedName).isNull(); - } - - inline bool hasAttribute(const QString &namespaceUri, const QString &name) const + bool hasAttribute(QAnyStringView namespaceUri, QAnyStringView name) const { return !value(namespaceUri, name).isNull(); } @@ -176,8 +176,9 @@ public: virtual QString resolveUndeclaredEntity(const QString &name); }; -#ifndef QT_NO_XMLSTREAMREADER -class Q_CORE_EXPORT QXmlStreamReader { +#if QT_CONFIG(xmlstreamreader) +class Q_CORE_EXPORT QXmlStreamReader +{ QDOC_PROPERTY(bool namespaceProcessing READ namespaceProcessing WRITE setNamespaceProcessing) public: enum TokenType { @@ -246,6 +247,7 @@ public: inline bool isProcessingInstruction() const { return tokenType() == ProcessingInstruction; } bool isStandaloneDocument() const; + bool hasStandaloneDeclaration() const; QStringView documentVersion() const; QStringView documentEncoding() const; @@ -313,9 +315,9 @@ private: QScopedPointer d_ptr; }; -#endif // QT_NO_XMLSTREAMREADER +#endif // feature xmlstreamreader -#ifndef QT_NO_XMLSTREAMWRITER +#if QT_CONFIG(xmlstreamwriter) class QXmlStreamWriterPrivate; @@ -401,7 +403,7 @@ public: void writeStartElement(QAnyStringView qualifiedName); void writeStartElement(QAnyStringView namespaceUri, QAnyStringView name); -#ifndef QT_NO_XMLSTREAMREADER +#if QT_CONFIG(xmlstreamreader) void writeCurrentToken(const QXmlStreamReader &reader); #endif @@ -412,9 +414,10 @@ private: Q_DECLARE_PRIVATE(QXmlStreamWriter) QScopedPointer d_ptr; }; -#endif // QT_NO_XMLSTREAMWRITER +#endif // feature xmlstreamwriter QT_END_NAMESPACE -#endif // QT_NO_XMLSTREAM +#endif // feature xmlstream + #endif // QXMLSTREAM_H diff --git a/src/corelib/serialization/qxmlstream_p.h b/src/corelib/serialization/qxmlstream_p.h index f09adaa3..a29ee656 100644 --- a/src/corelib/serialization/qxmlstream_p.h +++ b/src/corelib/serialization/qxmlstream_p.h @@ -523,8 +523,6 @@ public: QXmlStreamEntityResolver *entityResolver; - static QXmlStreamReaderPrivate *get(QXmlStreamReader *q) { return q->d_func(); } - private: /*! \internal Never assign to variable type directly. Instead use this function. diff --git a/src/corelib/serialization/qxmlstreamgrammar_p.h b/src/corelib/serialization/qxmlstreamgrammar_p.h index 0ad32327..80ee8e92 100644 --- a/src/corelib/serialization/qxmlstreamgrammar_p.h +++ b/src/corelib/serialization/qxmlstreamgrammar_p.h @@ -44,7 +44,6 @@ public: ENTITY = 32, ENTITY_DONE = 45, EQ = 14, - XML_ERROR = 43, FIXED = 39, HASH = 6, ID = 48, @@ -81,6 +80,7 @@ public: UNRESOLVED_ENTITY = 46, VERSION = 55, XML = 54, + XML_ERROR = 43, ACCEPT_STATE = 416, RULE_COUNT = 270, diff --git a/src/corelib/serialization/qxmlstreamparser_p.h b/src/corelib/serialization/qxmlstreamparser_p.h index ae3ebe7a..1363bf4d 100644 --- a/src/corelib/serialization/qxmlstreamparser_p.h +++ b/src/corelib/serialization/qxmlstreamparser_p.h @@ -38,7 +38,7 @@ QT_BEGIN_NAMESPACE -#ifndef QT_NO_XMLSTREAMREADER +#if QT_CONFIG(xmlstreamreader) bool QXmlStreamReaderPrivate::parse() { @@ -998,7 +998,7 @@ bool QXmlStreamReaderPrivate::parse() return false; } -#endif +#endif // feature xmlstreamreader QT_END_NAMESPACE diff --git a/src/corelib/serialization/qxmlutils.cpp b/src/corelib/serialization/qxmlutils.cpp index 00a11212..e6fae7c1 100644 --- a/src/corelib/serialization/qxmlutils.cpp +++ b/src/corelib/serialization/qxmlutils.cpp @@ -48,7 +48,7 @@ bool QXmlUtils::rangeContains(RangeIter begin, RangeIter end, const QChar c) return cp >= begin->min; while (begin != end) { - int delta = (end - begin) / 2; + qptrdiff delta = (end - begin) / 2; RangeIter mid = begin + delta; if (mid->min > cp) diff --git a/src/corelib/text/qanystringview.h b/src/corelib/text/qanystringview.h index 93a084fc..4be8910c 100644 --- a/src/corelib/text/qanystringview.h +++ b/src/corelib/text/qanystringview.h @@ -4,6 +4,7 @@ #ifndef QANYSTRINGVIEW_H #define QANYSTRINGVIEW_H +#include #include #include diff --git a/src/corelib/text/qbytearray.cpp b/src/corelib/text/qbytearray.cpp index 8faa130d..267d8a33 100644 --- a/src/corelib/text/qbytearray.cpp +++ b/src/corelib/text/qbytearray.cpp @@ -34,8 +34,6 @@ #include -#define IS_RAW_DATA(d) ((d)->flags() & QArrayData::RawDataType) - QT_BEGIN_NAMESPACE Q_CONSTINIT const char QByteArray::_empty = '\0'; @@ -133,16 +131,9 @@ char *qstrcpy(char *dst, const char *src) char *qstrncpy(char *dst, const char *src, size_t len) { if (dst && len > 0) { - if (!src) { - *dst = '\0'; - return nullptr; - } -#ifdef Q_CC_MSVC - strncpy_s(dst, len, src, len - 1); -#else - strncpy(dst, src, len); -#endif - dst[len-1] = '\0'; + *dst = '\0'; + if (src) + std::strncat(dst, src, len - 1); } return src ? dst : nullptr; } @@ -2113,6 +2104,73 @@ QByteArray& QByteArray::append(char ch) return *this; } +/*! + \fn QByteArray &QByteArray::assign(QByteArrayView v) + \since 6.6 + + Replaces the contents of this byte array with a copy of \a v and returns a + reference to this byte array. + + The size of this byte array will be equal to the size of \a v. + + This function only allocates memory if the size of \a v exceeds the capacity + of this byte array or this byte array is shared. +*/ + +/*! + \fn QByteArray &QByteArray::assign(qsizetype n, char c) + \since 6.6 + + Replaces the contents of this byte array with \a n copies of \a c and + returns a reference to this byte array. + + The size of this byte array will be equal to \a n, which has to be non-negative. + + This function will only allocate memory if \a n exceeds the capacity of this + byte array or this byte array is shared. + + \sa fill() +*/ + +/*! + \fn template > QByteArray &QByteArray::assign(InputIterator first, InputIterator last) + \since 6.6 + + Replaces the contents of this byte array with a copy of the elements in the + iterator range [\a first, \a last) and returns a reference to this + byte array. + + The size of this byte array will be equal to the number of elements in the + range [\a first, \a last). + + This function will only allocate memory if the number of elements in the + range exceeds the capacity of this byte array or this byte array is shared. + + \note This function overload only participates in overload resolution if + \c InputIterator meets the requirements of a + \l {https://en.cppreference.com/w/cpp/named_req/InputIterator} {LegacyInputIterator}. + + \note The behavior is undefined if either argument is an iterator into *this or + [\a first, \a last) is not a valid range. +*/ + +QByteArray &QByteArray::assign(QByteArrayView v) +{ + const auto len = v.size(); + + if (len <= capacity() && isDetached()) { + const auto offset = d.freeSpaceAtBegin(); + if (offset) + d.setBegin(d.begin() - offset); + std::memcpy(d.begin(), v.data(), len); + d.size = len; + d.data()[d.size] = '\0'; + } else { + *this = v.toByteArray(); + } + return *this; +} + /*! Inserts \a data at index position \a i and returns a reference to this byte array. @@ -5152,5 +5210,4 @@ size_t qHash(const QByteArray::FromBase64Result &key, size_t seed) noexcept QT_END_NAMESPACE -#undef IS_RAW_DATA #undef REHASH diff --git a/src/corelib/text/qbytearray.h b/src/corelib/text/qbytearray.h index c7b96589..3b99ef78 100644 --- a/src/corelib/text/qbytearray.h +++ b/src/corelib/text/qbytearray.h @@ -40,6 +40,8 @@ namespace emscripten { } #endif +class tst_QByteArray; + QT_BEGIN_NAMESPACE class QString; @@ -60,6 +62,11 @@ private: DataPointer d; static const char _empty; + + friend class ::tst_QByteArray; + + template + using if_input_iterator = QtPrivate::IfIsInputIterator; public: enum Base64Option { @@ -227,6 +234,20 @@ public: QByteArray &append(QByteArrayView a) { return insert(size(), a); } + QByteArray &assign(QByteArrayView v); + QByteArray &assign(qsizetype n, char c) + { + Q_ASSERT(n >= 0); + return fill(c, n); + } + template = true> + QByteArray &assign(InputIterator first, InputIterator last) + { + d.assign(first, last); + d.data()[d.size] = '\0'; + return *this; + } + QByteArray &insert(qsizetype i, QByteArrayView data); inline QByteArray &insert(qsizetype i, const char *s) { return insert(i, QByteArrayView(s)); } @@ -247,7 +268,7 @@ public: template QByteArray &removeIf(Predicate pred) { - QtPrivate::sequential_erase_if(*this, pred); + removeIf_helper(pred); return *this; } @@ -478,9 +499,20 @@ private: static QByteArray trimmed_helper(QByteArray &a); static QByteArray simplified_helper(const QByteArray &a); static QByteArray simplified_helper(QByteArray &a); + template + qsizetype removeIf_helper(Predicate pred) + { + const qsizetype result = d->eraseIf(pred); + if (result > 0) + d.data()[d.size] = '\0'; + return result; + } friend class QString; friend Q_CORE_EXPORT QByteArray qUncompress(const uchar *data, qsizetype nbytes); + + template friend qsizetype erase(QByteArray &ba, const T &t); + template friend qsizetype erase_if(QByteArray &ba, Predicate pred); }; Q_DECLARE_OPERATORS_FOR_FLAGS(QByteArray::Base64Options) @@ -662,13 +694,13 @@ Q_CORE_EXPORT Q_DECL_PURE_FUNCTION size_t qHash(const QByteArray::FromBase64Resu template qsizetype erase(QByteArray &ba, const T &t) { - return QtPrivate::sequential_erase(ba, t); + return ba.removeIf_helper([&t](const auto &e) { return t == e; }); } template qsizetype erase_if(QByteArray &ba, Predicate pred) { - return QtPrivate::sequential_erase_if(ba, pred); + return ba.removeIf_helper(pred); } // @@ -683,7 +715,7 @@ namespace Qt { inline namespace Literals { inline namespace StringLiterals { -inline QByteArray operator"" _ba(const char *str, size_t size) noexcept +inline QByteArray operator""_ba(const char *str, size_t size) noexcept { return QByteArray(QByteArrayData(nullptr, const_cast(str), qsizetype(size))); } @@ -696,7 +728,7 @@ inline namespace QtLiterals { #if QT_DEPRECATED_SINCE(6, 8) QT_DEPRECATED_VERSION_X_6_8("Use _ba from Qt::StringLiterals namespace instead.") -inline QByteArray operator"" _qba(const char *str, size_t size) noexcept +inline QByteArray operator""_qba(const char *str, size_t size) noexcept { return Qt::StringLiterals::operator""_ba(str, size); } diff --git a/src/corelib/text/qbytearrayview.qdoc b/src/corelib/text/qbytearrayview.qdoc index 6afcba1e..48013f86 100644 --- a/src/corelib/text/qbytearrayview.qdoc +++ b/src/corelib/text/qbytearrayview.qdoc @@ -278,7 +278,7 @@ The container's data must remain valid for the lifetime of this byte array view object. This constructor participates in overload resolution if \a c is any contiguous - container container with elements of a compatible byte type. + container with elements of a compatible byte type. \sa {Compatible Byte Types} */ diff --git a/src/corelib/text/qchar.h b/src/corelib/text/qchar.h index e9f0cf33..30a15f96 100644 --- a/src/corelib/text/qchar.h +++ b/src/corelib/text/qchar.h @@ -627,7 +627,7 @@ namespace Qt { inline namespace Literals { inline namespace StringLiterals { -constexpr inline QLatin1Char operator"" _L1(char ch) noexcept +constexpr inline QLatin1Char operator""_L1(char ch) noexcept { return QLatin1Char(ch); } diff --git a/src/corelib/text/qcollator.cpp b/src/corelib/text/qcollator.cpp index d753a054..3e17f8cc 100644 --- a/src/corelib/text/qcollator.cpp +++ b/src/corelib/text/qcollator.cpp @@ -57,9 +57,25 @@ Q_GLOBAL_STATIC(QThreadStorage, defaultCollator) In addition to the locale, several optional flags can be set that influence the result of the collation. - \note On Linux, Qt is normally compiled to use ICU. When it isn't, all - options are ignored and the only supported locales are the system default - (that \c{setlocale(LC_COLLATE, nullptr)} would report) and the "C" locale. + \section1 POSIX fallback implementation + + On Unix systems, Qt is normally compiled to use ICU (except for \macos, + where Qt defaults to using an equivalent Apple API). However, if ICU was + not available at compile time or explicitly disabled, Qt will use a + fallback backend that uses the POSIX API only. This backend has several + limitations: + + \list + \li Only the QLocale::c() and QLocale::system() locales are supported. + Consult the POSIX and C Standard Library manuals for the + \c{} header for more information on the system locale. + \li caseSensitivity() is not supported: only case-sensitive collation + can be performed. + \li numericMode() and ignorePunctuation() are not supported. + \endlist + + The use of any of the unsupported options will cause a warning to be + printed to the application's output. */ /*! diff --git a/src/corelib/text/qcollator_posix.cpp b/src/corelib/text/qcollator_posix.cpp index 54c70001..5ed80c1b 100644 --- a/src/corelib/text/qcollator_posix.cpp +++ b/src/corelib/text/qcollator_posix.cpp @@ -69,13 +69,19 @@ QCollatorSortKey QCollator::sortKey(const QString &string) const if (d->isC()) { std::copy(original.cbegin(), original.cend(), result.begin()); } else { - size_t size = std::wcsxfrm(result.data(), original.constData(), string.size()); - if (size > size_t(result.size())) { - result.resize(size+1); - size = std::wcsxfrm(result.data(), original.constData(), string.size()); + auto availableSizeIncludingNullTerminator = result.size(); + size_t neededSizeExcludingNullTerminator = std::wcsxfrm( + result.data(), original.constData(), availableSizeIncludingNullTerminator); + if (neededSizeExcludingNullTerminator > size_t(availableSizeIncludingNullTerminator - 1)) { + result.resize(neededSizeExcludingNullTerminator + 1); + availableSizeIncludingNullTerminator = result.size(); + neededSizeExcludingNullTerminator = std::wcsxfrm(result.data(), original.constData(), + availableSizeIncludingNullTerminator); + Q_ASSERT(neededSizeExcludingNullTerminator + == size_t(availableSizeIncludingNullTerminator - 1)); } - result.resize(size+1); - result[size] = 0; + result.resize(neededSizeExcludingNullTerminator + 1); + result[neededSizeExcludingNullTerminator] = 0; } return QCollatorSortKey(new QCollatorSortKeyPrivate(std::move(result))); } diff --git a/src/corelib/text/qlatin1stringmatcher.h b/src/corelib/text/qlatin1stringmatcher.h index a256250e..3b8c24fc 100644 --- a/src/corelib/text/qlatin1stringmatcher.h +++ b/src/corelib/text/qlatin1stringmatcher.h @@ -98,6 +98,18 @@ struct QCaseInsensitiveLatin1Hash return std::size_t(latin1Lower[uchar(c)]); } + static int difference(char lhs, char rhs) + { + return int(latin1Lower[uchar(lhs)]) - int(latin1Lower[uchar(rhs)]); + } + + static auto matcher(char ch) + { + return [sought = latin1Lower[uchar(ch)]](char other) { + return latin1Lower[uchar(other)] == sought; + }; + } + private: static constexpr uchar latin1Lower[256] = { 0x00,0x01,0x02,0x03,0x04,0x05,0x06,0x07,0x08,0x09,0x0a,0x0b,0x0c,0x0d,0x0e,0x0f, diff --git a/src/corelib/text/qlatin1stringview.h b/src/corelib/text/qlatin1stringview.h new file mode 100644 index 00000000..4a7bf357 --- /dev/null +++ b/src/corelib/text/qlatin1stringview.h @@ -0,0 +1,370 @@ +// Copyright (C) 2020 The Qt Company Ltd. +// Copyright (C) 2019 Intel Corporation. +// Copyright (C) 2019 Mail.ru Group. +// Copyright (C) 2020 Klarälvdalens Datakonsult AB, a KDAB Group company, info@kdab.com, author Marc Mutz +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only + +#ifndef QLATIN1STRINGVIEW_H +#define QLATIN1STRINGVIEW_H + +#include +#include +#include +#include + +#if 0 +// Workaround for generating forward headers +#pragma qt_class(QLatin1String) +#pragma qt_class(QLatin1StringView) +#endif + +QT_BEGIN_NAMESPACE + +class QString; + +#if QT_VERSION >= QT_VERSION_CHECK(7, 0, 0) || defined(QT_BOOTSTRAPPED) || defined(Q_QDOC) +# define Q_L1S_VIEW_IS_PRIMARY +class QLatin1StringView +#else +class QLatin1String +#endif +{ +public: +#ifdef Q_L1S_VIEW_IS_PRIMARY + constexpr QLatin1StringView() noexcept {} + constexpr QLatin1StringView(std::nullptr_t) noexcept : QLatin1StringView() {} + constexpr explicit QLatin1StringView(const char *s) noexcept + : QLatin1StringView(s, s ? qsizetype(QtPrivate::lengthHelperPointer(s)) : 0) {} + constexpr QLatin1StringView(const char *f, const char *l) + : QLatin1StringView(f, qsizetype(l - f)) {} + constexpr QLatin1StringView(const char *s, qsizetype sz) noexcept : m_data(s), m_size(sz) {} + explicit QLatin1StringView(const QByteArray &s) noexcept + : QLatin1StringView(s.constData(), s.size()) {} + constexpr explicit QLatin1StringView(QByteArrayView s) noexcept + : QLatin1StringView(s.constData(), s.size()) {} +#else + constexpr QLatin1String() noexcept : m_size(0), m_data(nullptr) {} + Q_WEAK_OVERLOAD + constexpr QLatin1String(std::nullptr_t) noexcept : QLatin1String() {} + constexpr explicit QLatin1String(const char *s) noexcept + : m_size(s ? qsizetype(QtPrivate::lengthHelperPointer(s)) : 0), m_data(s) {} + constexpr QLatin1String(const char *f, const char *l) + : QLatin1String(f, qsizetype(l - f)) {} + constexpr QLatin1String(const char *s, qsizetype sz) noexcept : m_size(sz), m_data(s) {} + explicit QLatin1String(const QByteArray &s) noexcept : m_size(s.size()), m_data(s.constData()) {} + constexpr explicit QLatin1String(QByteArrayView s) noexcept : m_size(s.size()), m_data(s.data()) {} +#endif // !Q_L1S_VIEW_IS_PRIMARY + + inline QString toString() const; + + constexpr const char *latin1() const noexcept { return m_data; } + constexpr qsizetype size() const noexcept { return m_size; } + constexpr const char *data() const noexcept { return m_data; } + [[nodiscard]] constexpr const char *constData() const noexcept { return data(); } + [[nodiscard]] constexpr const char *constBegin() const noexcept { return begin(); } + [[nodiscard]] constexpr const char *constEnd() const noexcept { return end(); } + + [[nodiscard]] constexpr QLatin1Char first() const { return front(); } + [[nodiscard]] constexpr QLatin1Char last() const { return back(); } + + [[nodiscard]] constexpr qsizetype length() const noexcept { return size(); } + + constexpr bool isNull() const noexcept { return !data(); } + constexpr bool isEmpty() const noexcept { return !size(); } + + [[nodiscard]] constexpr bool empty() const noexcept { return size() == 0; } + + template + [[nodiscard]] inline QString arg(Args &&...args) const; + + [[nodiscard]] constexpr QLatin1Char at(qsizetype i) const + { + Q_ASSERT(i >= 0); + Q_ASSERT(i < size()); + return QLatin1Char(m_data[i]); + } + [[nodiscard]] constexpr QLatin1Char operator[](qsizetype i) const { return at(i); } + + [[nodiscard]] constexpr QLatin1Char front() const { return at(0); } + [[nodiscard]] constexpr QLatin1Char back() const { return at(size() - 1); } + + [[nodiscard]] int compare(QStringView other, Qt::CaseSensitivity cs = Qt::CaseSensitive) const noexcept + { return QtPrivate::compareStrings(*this, other, cs); } + [[nodiscard]] int compare(QLatin1StringView other, Qt::CaseSensitivity cs = Qt::CaseSensitive) const noexcept + { return QtPrivate::compareStrings(*this, other, cs); } + [[nodiscard]] inline int compare(QUtf8StringView other, Qt::CaseSensitivity cs = Qt::CaseSensitive) const noexcept; + [[nodiscard]] constexpr int compare(QChar c) const noexcept + { return isEmpty() ? -1 : front() == c ? int(size() > 1) : uchar(m_data[0]) - c.unicode(); } + [[nodiscard]] int compare(QChar c, Qt::CaseSensitivity cs) const noexcept + { return QtPrivate::compareStrings(*this, QStringView(&c, 1), cs); } + + [[nodiscard]] bool startsWith(QStringView s, Qt::CaseSensitivity cs = Qt::CaseSensitive) const noexcept + { return QtPrivate::startsWith(*this, s, cs); } + [[nodiscard]] bool startsWith(QLatin1StringView s, Qt::CaseSensitivity cs = Qt::CaseSensitive) const noexcept + { return QtPrivate::startsWith(*this, s, cs); } + [[nodiscard]] constexpr bool startsWith(QChar c) const noexcept + { return !isEmpty() && front() == c; } + [[nodiscard]] bool startsWith(QChar c, Qt::CaseSensitivity cs) const noexcept + { return QtPrivate::startsWith(*this, QStringView(&c, 1), cs); } + + [[nodiscard]] bool endsWith(QStringView s, Qt::CaseSensitivity cs = Qt::CaseSensitive) const noexcept + { return QtPrivate::endsWith(*this, s, cs); } + [[nodiscard]] bool endsWith(QLatin1StringView s, Qt::CaseSensitivity cs = Qt::CaseSensitive) const noexcept + { return QtPrivate::endsWith(*this, s, cs); } + [[nodiscard]] constexpr bool endsWith(QChar c) const noexcept + { return !isEmpty() && back() == c; } + [[nodiscard]] bool endsWith(QChar c, Qt::CaseSensitivity cs) const noexcept + { return QtPrivate::endsWith(*this, QStringView(&c, 1), cs); } + + [[nodiscard]] qsizetype indexOf(QStringView s, qsizetype from = 0, Qt::CaseSensitivity cs = Qt::CaseSensitive) const noexcept + { return QtPrivate::findString(*this, from, s, cs); } + [[nodiscard]] qsizetype indexOf(QLatin1StringView s, qsizetype from = 0, Qt::CaseSensitivity cs = Qt::CaseSensitive) const noexcept + { return QtPrivate::findString(*this, from, s, cs); } + [[nodiscard]] qsizetype indexOf(QChar c, qsizetype from = 0, Qt::CaseSensitivity cs = Qt::CaseSensitive) const noexcept + { return QtPrivate::findString(*this, from, QStringView(&c, 1), cs); } + + [[nodiscard]] bool contains(QStringView s, Qt::CaseSensitivity cs = Qt::CaseSensitive) const noexcept + { return indexOf(s, 0, cs) != -1; } + [[nodiscard]] bool contains(QLatin1StringView s, Qt::CaseSensitivity cs = Qt::CaseSensitive) const noexcept + { return indexOf(s, 0, cs) != -1; } + [[nodiscard]] bool contains(QChar c, Qt::CaseSensitivity cs = Qt::CaseSensitive) const noexcept + { return indexOf(QStringView(&c, 1), 0, cs) != -1; } + + [[nodiscard]] qsizetype lastIndexOf(QStringView s, Qt::CaseSensitivity cs = Qt::CaseSensitive) const noexcept + { return lastIndexOf(s, size(), cs); } + [[nodiscard]] qsizetype lastIndexOf(QStringView s, qsizetype from, Qt::CaseSensitivity cs = Qt::CaseSensitive) const noexcept + { return QtPrivate::lastIndexOf(*this, from, s, cs); } + [[nodiscard]] qsizetype lastIndexOf(QLatin1StringView s, Qt::CaseSensitivity cs = Qt::CaseSensitive) const noexcept + { return lastIndexOf(s, size(), cs); } + [[nodiscard]] qsizetype lastIndexOf(QLatin1StringView s, qsizetype from, Qt::CaseSensitivity cs = Qt::CaseSensitive) const noexcept + { return QtPrivate::lastIndexOf(*this, from, s, cs); } + [[nodiscard]] qsizetype lastIndexOf(QChar c, Qt::CaseSensitivity cs = Qt::CaseSensitive) const noexcept + { return lastIndexOf(c, -1, cs); } + [[nodiscard]] qsizetype lastIndexOf(QChar c, qsizetype from, Qt::CaseSensitivity cs = Qt::CaseSensitive) const noexcept + { return QtPrivate::lastIndexOf(*this, from, QStringView(&c, 1), cs); } + + [[nodiscard]] qsizetype count(QStringView str, Qt::CaseSensitivity cs = Qt::CaseSensitive) const + { return QtPrivate::count(*this, str, cs); } + [[nodiscard]] qsizetype count(QLatin1StringView str, Qt::CaseSensitivity cs = Qt::CaseSensitive) const + { return QtPrivate::count(*this, str, cs); } + [[nodiscard]] qsizetype count(QChar ch, Qt::CaseSensitivity cs = Qt::CaseSensitive) const noexcept + { return QtPrivate::count(*this, ch, cs); } + + [[nodiscard]] short toShort(bool *ok = nullptr, int base = 10) const + { return QtPrivate::toIntegral(QByteArrayView(*this), ok, base); } + [[nodiscard]] ushort toUShort(bool *ok = nullptr, int base = 10) const + { return QtPrivate::toIntegral(QByteArrayView(*this), ok, base); } + [[nodiscard]] int toInt(bool *ok = nullptr, int base = 10) const + { return QtPrivate::toIntegral(QByteArrayView(*this), ok, base); } + [[nodiscard]] uint toUInt(bool *ok = nullptr, int base = 10) const + { return QtPrivate::toIntegral(QByteArrayView(*this), ok, base); } + [[nodiscard]] long toLong(bool *ok = nullptr, int base = 10) const + { return QtPrivate::toIntegral(QByteArrayView(*this), ok, base); } + [[nodiscard]] ulong toULong(bool *ok = nullptr, int base = 10) const + { return QtPrivate::toIntegral(QByteArrayView(*this), ok, base); } + [[nodiscard]] qlonglong toLongLong(bool *ok = nullptr, int base = 10) const + { return QtPrivate::toIntegral(QByteArrayView(*this), ok, base); } + [[nodiscard]] qulonglong toULongLong(bool *ok = nullptr, int base = 10) const + { return QtPrivate::toIntegral(QByteArrayView(*this), ok, base); } + [[nodiscard]] float toFloat(bool *ok = nullptr) const + { + const auto r = QtPrivate::toFloat(*this); + if (ok) + *ok = bool(r); + return r.value_or(0.0f); + } + [[nodiscard]] double toDouble(bool *ok = nullptr) const + { + const auto r = QtPrivate::toDouble(*this); + if (ok) + *ok = bool(r); + return r.value_or(0.0); + } + + using value_type = const char; + using reference = value_type&; + using const_reference = reference; + using iterator = value_type*; + using const_iterator = iterator; + using difference_type = qsizetype; // violates Container concept requirements + using size_type = qsizetype; // violates Container concept requirements + + constexpr const_iterator begin() const noexcept { return data(); } + constexpr const_iterator cbegin() const noexcept { return data(); } + constexpr const_iterator end() const noexcept { return data() + size(); } + constexpr const_iterator cend() const noexcept { return data() + size(); } + + using reverse_iterator = std::reverse_iterator; + using const_reverse_iterator = reverse_iterator; + + const_reverse_iterator rbegin() const noexcept { return const_reverse_iterator(end()); } + const_reverse_iterator crbegin() const noexcept { return const_reverse_iterator(end()); } + const_reverse_iterator rend() const noexcept { return const_reverse_iterator(begin()); } + const_reverse_iterator crend() const noexcept { return const_reverse_iterator(begin()); } + + [[nodiscard]] constexpr QLatin1StringView mid(qsizetype pos, qsizetype n = -1) const + { + using namespace QtPrivate; + auto result = QContainerImplHelper::mid(size(), &pos, &n); + return result == QContainerImplHelper::Null ? QLatin1StringView() + : QLatin1StringView(m_data + pos, n); + } + [[nodiscard]] constexpr QLatin1StringView left(qsizetype n) const + { + if (size_t(n) >= size_t(size())) + n = size(); + return {m_data, n}; + } + [[nodiscard]] constexpr QLatin1StringView right(qsizetype n) const + { + if (size_t(n) >= size_t(size())) + n = size(); + return {m_data + m_size - n, n}; + } + + [[nodiscard]] constexpr QLatin1StringView sliced(qsizetype pos) const + { verify(pos); return {m_data + pos, m_size - pos}; } + [[nodiscard]] constexpr QLatin1StringView sliced(qsizetype pos, qsizetype n) const + { verify(pos, n); return {m_data + pos, n}; } + [[nodiscard]] constexpr QLatin1StringView first(qsizetype n) const + { verify(n); return {m_data, n}; } + [[nodiscard]] constexpr QLatin1StringView last(qsizetype n) const + { verify(n); return {m_data + size() - n, n}; } + [[nodiscard]] constexpr QLatin1StringView chopped(qsizetype n) const + { verify(n); return {m_data, size() - n}; } + + constexpr void chop(qsizetype n) + { verify(n); m_size -= n; } + constexpr void truncate(qsizetype n) + { verify(n); m_size = n; } + + [[nodiscard]] QLatin1StringView trimmed() const noexcept { return QtPrivate::trimmed(*this); } + + template + [[nodiscard]] constexpr auto tokenize(Needle &&needle, Flags...flags) const + noexcept(noexcept(qTokenize(std::declval(), + std::forward(needle), flags...))) + -> decltype(qTokenize(*this, std::forward(needle), flags...)) + { return qTokenize(*this, std::forward(needle), flags...); } + + friend bool operator==(QLatin1StringView s1, QLatin1StringView s2) noexcept + { return QByteArrayView(s1) == QByteArrayView(s2); } + friend bool operator!=(QLatin1StringView s1, QLatin1StringView s2) noexcept + { return !(s1 == s2); } + friend bool operator<(QLatin1StringView s1, QLatin1StringView s2) noexcept + { + const qsizetype len = qMin(s1.size(), s2.size()); + const int r = len ? memcmp(s1.latin1(), s2.latin1(), len) : 0; + return r < 0 || (r == 0 && s1.size() < s2.size()); + } + friend bool operator>(QLatin1StringView s1, QLatin1StringView s2) noexcept + { return s2 < s1; } + friend bool operator<=(QLatin1StringView s1, QLatin1StringView s2) noexcept + { return !(s1 > s2); } + friend bool operator>=(QLatin1StringView s1, QLatin1StringView s2) noexcept + { return !(s1 < s2); } + + // QChar <> QLatin1StringView + friend bool operator==(QChar lhs, QLatin1StringView rhs) noexcept { return rhs.size() == 1 && lhs == rhs.front(); } + friend bool operator< (QChar lhs, QLatin1StringView rhs) noexcept { return compare_helper(&lhs, 1, rhs) < 0; } + friend bool operator> (QChar lhs, QLatin1StringView rhs) noexcept { return compare_helper(&lhs, 1, rhs) > 0; } + friend bool operator!=(QChar lhs, QLatin1StringView rhs) noexcept { return !(lhs == rhs); } + friend bool operator<=(QChar lhs, QLatin1StringView rhs) noexcept { return !(lhs > rhs); } + friend bool operator>=(QChar lhs, QLatin1StringView rhs) noexcept { return !(lhs < rhs); } + + friend bool operator==(QLatin1StringView lhs, QChar rhs) noexcept { return rhs == lhs; } + friend bool operator!=(QLatin1StringView lhs, QChar rhs) noexcept { return !(rhs == lhs); } + friend bool operator< (QLatin1StringView lhs, QChar rhs) noexcept { return rhs > lhs; } + friend bool operator> (QLatin1StringView lhs, QChar rhs) noexcept { return rhs < lhs; } + friend bool operator<=(QLatin1StringView lhs, QChar rhs) noexcept { return !(rhs < lhs); } + friend bool operator>=(QLatin1StringView lhs, QChar rhs) noexcept { return !(rhs > lhs); } + + // QStringView <> QLatin1StringView + friend bool operator==(QStringView lhs, QLatin1StringView rhs) noexcept + { return lhs.size() == rhs.size() && QtPrivate::equalStrings(lhs, rhs); } + friend bool operator!=(QStringView lhs, QLatin1StringView rhs) noexcept { return !(lhs == rhs); } + friend bool operator< (QStringView lhs, QLatin1StringView rhs) noexcept { return QtPrivate::compareStrings(lhs, rhs) < 0; } + friend bool operator<=(QStringView lhs, QLatin1StringView rhs) noexcept { return QtPrivate::compareStrings(lhs, rhs) <= 0; } + friend bool operator> (QStringView lhs, QLatin1StringView rhs) noexcept { return QtPrivate::compareStrings(lhs, rhs) > 0; } + friend bool operator>=(QStringView lhs, QLatin1StringView rhs) noexcept { return QtPrivate::compareStrings(lhs, rhs) >= 0; } + + friend bool operator==(QLatin1StringView lhs, QStringView rhs) noexcept + { return lhs.size() == rhs.size() && QtPrivate::equalStrings(lhs, rhs); } + friend bool operator!=(QLatin1StringView lhs, QStringView rhs) noexcept { return !(lhs == rhs); } + friend bool operator< (QLatin1StringView lhs, QStringView rhs) noexcept { return QtPrivate::compareStrings(lhs, rhs) < 0; } + friend bool operator<=(QLatin1StringView lhs, QStringView rhs) noexcept { return QtPrivate::compareStrings(lhs, rhs) <= 0; } + friend bool operator> (QLatin1StringView lhs, QStringView rhs) noexcept { return QtPrivate::compareStrings(lhs, rhs) > 0; } + friend bool operator>=(QLatin1StringView lhs, QStringView rhs) noexcept { return QtPrivate::compareStrings(lhs, rhs) >= 0; } + + +#if !defined(QT_NO_CAST_FROM_ASCII) && !defined(QT_RESTRICTED_CAST_FROM_ASCII) + QT_ASCII_CAST_WARN inline bool operator==(const char *s) const; + QT_ASCII_CAST_WARN inline bool operator!=(const char *s) const; + QT_ASCII_CAST_WARN inline bool operator<(const char *s) const; + QT_ASCII_CAST_WARN inline bool operator>(const char *s) const; + QT_ASCII_CAST_WARN inline bool operator<=(const char *s) const; + QT_ASCII_CAST_WARN inline bool operator>=(const char *s) const; + + QT_ASCII_CAST_WARN inline bool operator==(const QByteArray &s) const; + QT_ASCII_CAST_WARN inline bool operator!=(const QByteArray &s) const; + QT_ASCII_CAST_WARN inline bool operator<(const QByteArray &s) const; + QT_ASCII_CAST_WARN inline bool operator>(const QByteArray &s) const; + QT_ASCII_CAST_WARN inline bool operator<=(const QByteArray &s) const; + QT_ASCII_CAST_WARN inline bool operator>=(const QByteArray &s) const; + + QT_ASCII_CAST_WARN friend bool operator==(const char *s1, QLatin1StringView s2) { return compare_helper(s2, s1) == 0; } + QT_ASCII_CAST_WARN friend bool operator!=(const char *s1, QLatin1StringView s2) { return compare_helper(s2, s1) != 0; } + QT_ASCII_CAST_WARN friend bool operator< (const char *s1, QLatin1StringView s2) { return compare_helper(s2, s1) > 0; } + QT_ASCII_CAST_WARN friend bool operator> (const char *s1, QLatin1StringView s2) { return compare_helper(s2, s1) < 0; } + QT_ASCII_CAST_WARN friend bool operator<=(const char *s1, QLatin1StringView s2) { return compare_helper(s2, s1) >= 0; } + QT_ASCII_CAST_WARN friend bool operator>=(const char *s1, QLatin1StringView s2) { return compare_helper(s2, s1) <= 0; } +#endif // !defined(QT_NO_CAST_FROM_ASCII) && !defined(QT_RESTRICTED_CAST_FROM_ASCII) + +private: + Q_ALWAYS_INLINE constexpr void verify(qsizetype pos, qsizetype n = 0) const + { + Q_ASSERT(pos >= 0); + Q_ASSERT(pos <= size()); + Q_ASSERT(n >= 0); + Q_ASSERT(n <= size() - pos); + } + static int compare_helper(const QLatin1StringView &s1, const char *s2) noexcept + { return compare_helper(s1, s2, qstrlen(s2)); } + Q_CORE_EXPORT static int compare_helper(const QLatin1StringView &s1, const char *s2, qsizetype len) noexcept; + Q_CORE_EXPORT static int compare_helper(const QChar *data1, qsizetype length1, + QLatin1StringView s2, + Qt::CaseSensitivity cs = Qt::CaseSensitive) noexcept; +#if QT_VERSION >= QT_VERSION_CHECK(7, 0, 0) || defined(QT_BOOTSTRAPPED) + const char *m_data = nullptr; + qsizetype m_size = 0; +#else + qsizetype m_size; + const char *m_data; +#endif +}; +#ifdef Q_L1S_VIEW_IS_PRIMARY +Q_DECLARE_TYPEINFO(QLatin1StringView, Q_RELOCATABLE_TYPE); +#else +Q_DECLARE_TYPEINFO(QLatin1String, Q_RELOCATABLE_TYPE); +#endif + +namespace Qt { +inline namespace Literals { +inline namespace StringLiterals { + +constexpr inline QLatin1StringView operator""_L1(const char *str, size_t size) noexcept +{ + return {str, qsizetype(size)}; +} + +} // StringLiterals +} // Literals +} // Qt + +QT_END_NAMESPACE + +#ifdef Q_L1S_VIEW_IS_PRIMARY +# undef Q_L1S_VIEW_IS_PRIMARY +#endif + +#endif // QLATIN1STRINGVIEW_H diff --git a/src/corelib/text/qlatin1stringview.qdoc b/src/corelib/text/qlatin1stringview.qdoc new file mode 100644 index 00000000..3bd93122 --- /dev/null +++ b/src/corelib/text/qlatin1stringview.qdoc @@ -0,0 +1,1285 @@ +// Copyright (C) 2021 The Qt Company Ltd. +// Copyright (C) 2022 Intel Corporation. +// Copyright (C) 2019 Mail.ru Group. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only + +/*! \class QLatin1StringView + \inmodule QtCore + \brief The QLatin1StringView class provides a thin wrapper around + a US-ASCII/Latin-1 encoded string literal. + + \ingroup string-processing + \reentrant + + Many of QString's member functions are overloaded to accept + \c{const char *} instead of QString. This includes the copy + constructor, the assignment operator, the comparison operators, + and various other functions such as \l{QString::insert()}{insert()}, + \l{QString::append()}{append()}, and \l{QString::prepend()}{prepend()}. + Some of these functions are optimized to avoid constructing a + QString object for the \c{const char *} data. For example, + assuming \c str is a QString, + + \snippet code/src_corelib_text_qstring.cpp 3 + + is much faster than + + \snippet code/src_corelib_text_qstring.cpp 4 + + because it doesn't construct four temporary QString objects and + make a deep copy of the character data. + + However, that is not true for all QString member functions that take + \c{const char *} and therefore applications should assume a temporary will + be created, such as in + + \snippet code/src_corelib_text_qstring.cpp 4bis + + Applications that define \l QT_NO_CAST_FROM_ASCII (as explained + in the QString documentation) don't have access to QString's + \c{const char *} API. To provide an efficient way of specifying + constant Latin-1 strings, Qt provides the QLatin1StringView, which is + just a very thin wrapper around a \c{const char *}. Using + QLatin1StringView, the example code above becomes + + \snippet code/src_corelib_text_qstring.cpp 5 + + This is a bit longer to type, but it provides exactly the same + benefits as the first version of the code, and is faster than + converting the Latin-1 strings using QString::fromLatin1(). + + Thanks to the QString(QLatin1StringView) constructor, + QLatin1StringView can be used everywhere a QString is expected. For + example: + + \snippet code/src_corelib_text_qstring.cpp 6 + + \note If the function you're calling with a QLatin1StringView + argument isn't actually overloaded to take QLatin1StringView, the + implicit conversion to QString will trigger a memory allocation, + which is usually what you want to avoid by using QLatin1StringView + in the first place. In those cases, using QStringLiteral may be + the better option. + + \sa QString, QLatin1Char, {QStringLiteral()}{QStringLiteral}, + QT_NO_CAST_FROM_ASCII +*/ + +/*! + \class QLatin1String + \inmodule QtCore + \brief QLatin1String is the same as QLatin1StringView. + + QLatin1String is a view to a Latin-1 string. It's the same as + QLatin1StringView and is kept for compatibility reasons. It is + recommended to use QLatin1StringView instead. + + Please see the QLatin1StringView documentation for details. +*/ + +/*! + \typedef QLatin1StringView::value_type + \since 5.10 + + Alias for \c{const char}. Provided for compatibility with the STL. +*/ + +/*! + \typedef QLatin1StringView::difference_type + \since 5.10 + + Alias for \c{qsizetype}. Provided for compatibility with the STL. +*/ + +/*! + \typedef QLatin1StringView::size_type + \since 5.10 + + Alias for \c{qsizetype}. Provided for compatibility with the STL. + + \note In version prior to Qt 6, this was an alias for \c{int}, + restricting the amount of data that could be held in a QLatin1StringView + on 64-bit architectures. +*/ + +/*! + \typedef QLatin1StringView::reference + \since 5.10 + + Alias for \c{value_type &}. Provided for compatibility with the STL. +*/ + +/*! + \typedef QLatin1StringView::const_reference + \since 5.11 + + Alias for \c{reference}. Provided for compatibility with the STL. +*/ + +/*! + \typedef QLatin1StringView::iterator + \since 5.10 + + QLatin1StringView does not support mutable iterators, so this is the same + as const_iterator. + + \sa const_iterator, reverse_iterator +*/ + +/*! + \typedef QLatin1StringView::const_iterator + \since 5.10 + + \sa iterator, const_reverse_iterator +*/ + +/*! + \typedef QLatin1StringView::reverse_iterator + \since 5.10 + + QLatin1StringView does not support mutable reverse iterators, so this is the + same as const_reverse_iterator. + + \sa const_reverse_iterator, iterator +*/ + +/*! + \typedef QLatin1StringView::const_reverse_iterator + \since 5.10 + + \sa reverse_iterator, const_iterator +*/ + +/*! \fn QLatin1StringView::QLatin1StringView() + \since 5.6 + + Constructs a QLatin1StringView object that stores a \nullptr. + + \sa data(), isEmpty(), isNull(), {Distinction Between Null and Empty Strings} +*/ + +/*! \fn QLatin1StringView::QLatin1StringView(std::nullptr_t) + \since 6.4 + + Constructs a QLatin1StringView object that stores a \nullptr. + + \sa data(), isEmpty(), isNull(), {Distinction Between Null and Empty Strings} +*/ + +/*! \fn QLatin1StringView::QLatin1StringView(const char *str) + + Constructs a QLatin1StringView object that stores \a str. + + The string data is \e not copied. The caller must be able to + guarantee that \a str will not be deleted or modified as long as + the QLatin1StringView object exists. + + \sa latin1() +*/ + +/*! \fn QLatin1StringView::QLatin1StringView(const char *str, qsizetype size) + + Constructs a QLatin1StringView object that stores \a str with \a size. + + The string data is \e not copied. The caller must be able to + guarantee that \a str will not be deleted or modified as long as + the QLatin1StringView object exists. + + \note: any null ('\\0') bytes in the byte array will be included in this + string, which will be converted to Unicode null characters (U+0000) if this + string is used by QString. This behavior is different from Qt 5.x. + + \sa latin1() +*/ + +/*! + \fn QLatin1StringView::QLatin1StringView(const char *first, const char *last) + \since 5.10 + + Constructs a QLatin1StringView object that stores \a first with length + (\a last - \a first). + + The range \c{[first,last)} must remain valid for the lifetime of + this Latin-1 string object. + + Passing \nullptr as \a first is safe if \a last is \nullptr, + too, and results in a null Latin-1 string. + + The behavior is undefined if \a last precedes \a first, \a first + is \nullptr and \a last is not, or if \c{last - first > + INT_MAX}. +*/ + +/*! \fn QLatin1StringView::QLatin1StringView(const QByteArray &str) + + Constructs a QLatin1StringView object as a view on \a str. + + The string data is \e not copied. The caller must be able to + guarantee that \a str will not be deleted or modified as long as + the QLatin1StringView object exists. + + \sa latin1() +*/ + +/*! \fn QLatin1StringView::QLatin1StringView(QByteArrayView str) + \since 6.3 + + Constructs a QLatin1StringView object as a view on \a str. + + The string data is \e not copied. The caller must be able to + guarantee that the data which \a str is pointing to will not + be deleted or modified as long as the QLatin1StringView object + exists. The size is obtained from \a str as-is, without checking + for a null-terminator. + + \note: any null ('\\0') bytes in the byte array will be included in this + string, which will be converted to Unicode null characters (U+0000) if this + string is used by QString. + + \sa latin1() +*/ + +/*! + \fn QString QLatin1StringView::toString() const + \since 6.0 + + Converts this Latin-1 string into a QString. Equivalent to + \code + return QString(*this); + \endcode +*/ + +/*! \fn const char *QLatin1StringView::latin1() const + + Returns the start of the Latin-1 string referenced by this object. +*/ + +/*! \fn const char *QLatin1StringView::data() const + + Returns the start of the Latin-1 string referenced by this object. +*/ + +/*! \fn const char *QLatin1StringView::constData() const + \since 6.4 + + Returns the start of the Latin-1 string referenced by this object. + + This function is provided for compatibility with other Qt containers. + + \sa data() +*/ + +/*! \fn qsizetype QLatin1StringView::size() const + + Returns the size of the Latin-1 string referenced by this object. + + \note In version prior to Qt 6, this function returned \c{int}, + restricting the amount of data that could be held in a QLatin1StringView + on 64-bit architectures. +*/ + +/*! \fn qsizetype QLatin1StringView::length() const + \since 6.4 + + Same as size(). + + This function is provided for compatibility with other Qt containers. +*/ + +/*! \fn bool QLatin1StringView::isNull() const + \since 5.10 + + Returns whether the Latin-1 string referenced by this object is null + (\c{data() == nullptr}) or not. + + \sa isEmpty(), data() +*/ + +/*! \fn bool QLatin1StringView::isEmpty() const + \since 5.10 + + Returns whether the Latin-1 string referenced by this object is empty + (\c{size() == 0}) or not. + + \sa isNull(), size() +*/ + +/*! \fn bool QLatin1StringView::empty() const + \since 6.4 + + Returns whether the Latin-1 string referenced by this object is empty + (\c{size() == 0}) or not. + + This function is provided for STL compatibility. + + \sa isEmpty(), isNull(), size() +*/ + +/*! \fn QLatin1Char QLatin1StringView::at(qsizetype pos) const + \since 5.8 + + Returns the character at position \a pos in this object. + + \note This function performs no error checking. + The behavior is undefined when \a pos < 0 or \a pos >= size(). + + \sa operator[]() +*/ + +/*! \fn QLatin1Char QLatin1StringView::operator[](qsizetype pos) const + \since 5.8 + + Returns the character at position \a pos in this object. + + \note This function performs no error checking. + The behavior is undefined when \a pos < 0 or \a pos >= size(). + + \sa at() +*/ + +/*! + \fn QLatin1Char QLatin1StringView::front() const + \since 5.10 + + Returns the first character in the string. + Same as \c{at(0)}. + + This function is provided for STL compatibility. + + \warning Calling this function on an empty string constitutes + undefined behavior. + + \sa back(), at(), operator[]() +*/ + +/*! + \fn QLatin1Char QLatin1StringView::first() const + \since 6.4 + + Returns the first character in the string. + Same as \c{at(0)} or front(). + + This function is provided for compatibility with other Qt containers. + + \warning Calling this function on an empty string constitutes + undefined behavior. + + \sa last(), front(), back() +*/ + +/*! + \fn QLatin1Char QLatin1StringView::back() const + \since 5.10 + + Returns the last character in the string. + Same as \c{at(size() - 1)}. + + This function is provided for STL compatibility. + + \warning Calling this function on an empty string constitutes + undefined behavior. + + \sa front(), at(), operator[]() +*/ + +/*! + \fn QLatin1Char QLatin1StringView::last() const + \since 6.4 + + Returns the last character in the string. + Same as \c{at(size() - 1)} or back(). + + This function is provided for compatibility with other Qt containers. + + \warning Calling this function on an empty string constitutes + undefined behavior. + + \sa first(), back(), front() +*/ + +/*! + \fn int QLatin1StringView::compare(QStringView str, Qt::CaseSensitivity cs) const + \fn int QLatin1StringView::compare(QLatin1StringView l1, Qt::CaseSensitivity cs) const + \fn int QLatin1StringView::compare(QChar ch) const + \fn int QLatin1StringView::compare(QChar ch, Qt::CaseSensitivity cs) const + \since 5.14 + + Returns an integer that compares to zero as this string view compares + to the UTF-16 string viewed by \a str, the Latin-1 string viewed by \a l1, + or the character \a ch, respectively. + + \include qstring.qdocinc {search-comparison-case-sensitivity} {search} + + \sa operator==(), operator<(), operator>() +*/ + +/*! + \fn int QLatin1StringView::compare(QUtf8StringView str, Qt::CaseSensitivity cs) const + \since 6.5 + + Returns an integer that compares to zero as this string view compares to the + string view \a str. + + \include qstring.qdocinc {search-comparison-case-sensitivity} {comparison} + + \sa operator==(), operator<(), operator>() +*/ + + +/*! + \fn bool QLatin1StringView::startsWith(QStringView str, Qt::CaseSensitivity cs) const + \since 5.10 + \fn bool QLatin1StringView::startsWith(QLatin1StringView l1, Qt::CaseSensitivity cs) const + \since 5.10 + \fn bool QLatin1StringView::startsWith(QChar ch) const + \since 5.10 + \fn bool QLatin1StringView::startsWith(QChar ch, Qt::CaseSensitivity cs) const + \since 5.10 + + Returns \c true if this Latin-1 string view starts with the UTF-16 + string viewed by \a str, the Latin-1 string viewed by \a l1, or the + character \a ch, respectively; otherwise returns \c false. + + \include qstring.qdocinc {search-comparison-case-sensitivity} {search} + + \sa endsWith() +*/ + +/*! + \fn bool QLatin1StringView::endsWith(QStringView str, Qt::CaseSensitivity cs) const + \since 5.10 + \fn bool QLatin1StringView::endsWith(QLatin1StringView l1, Qt::CaseSensitivity cs) const + \since 5.10 + \fn bool QLatin1StringView::endsWith(QChar ch) const + \since 5.10 + \fn bool QLatin1StringView::endsWith(QChar ch, Qt::CaseSensitivity cs) const + \since 5.10 + + Returns \c true if this Latin-1 string view ends with the UTF-16 string + viewed \a str, the Latin-1 string viewed by \a l1, or the character \a ch, + respectively; otherwise returns \c false. + + \include qstring.qdocinc {search-comparison-case-sensitivity} {search} + + \sa startsWith() +*/ + +/*! + \fn qsizetype QLatin1StringView::indexOf(QStringView str, qsizetype from = 0, Qt::CaseSensitivity cs = Qt::CaseSensitive) const + \fn qsizetype QLatin1StringView::indexOf(QLatin1StringView l1, qsizetype from = 0, Qt::CaseSensitivity cs = Qt::CaseSensitive) const + \fn qsizetype QLatin1StringView::indexOf(QChar c, qsizetype from = 0, Qt::CaseSensitivity cs = Qt::CaseSensitive) const + \since 5.14 + + Returns the index position in this Latin-1 string view of the first + occurrence of the UTF-16 string viewed by \a str, the Latin-1 string + viewed by \a l1, or the character \a ch, respectively, searching forward + from index position \a from. Returns -1 if \a str, \a l1 or \a c is not + found, respectively. + + \include qstring.qdocinc {search-comparison-case-sensitivity} {search} + + \include qstring.qdocinc negative-index-start-search-from-end + + \sa QString::indexOf() +*/ + +/*! + \fn bool QLatin1StringView::contains(QStringView str, Qt::CaseSensitivity cs) const + \fn bool QLatin1StringView::contains(QLatin1StringView l1, Qt::CaseSensitivity cs) const + \fn bool QLatin1StringView::contains(QChar c, Qt::CaseSensitivity cs) const + \since 5.14 + + Returns \c true if this Latin-1 string view contains an occurrence of the + UTF-16 string viewed by \a str, the Latin-1 string viewed by \a l1, or the + character \a ch, respectively; otherwise returns \c false. + + \include qstring.qdocinc {search-comparison-case-sensitivity} {search} + + \sa indexOf(), QStringView::contains(), QStringView::indexOf(), + QString::indexOf() +*/ + +/*! + \fn qsizetype QLatin1StringView::lastIndexOf(QStringView str, qsizetype from, Qt::CaseSensitivity cs) const + \fn qsizetype QLatin1StringView::lastIndexOf(QLatin1StringView l1, qsizetype from, Qt::CaseSensitivity cs) const + \fn qsizetype QLatin1StringView::lastIndexOf(QChar c, qsizetype from, Qt::CaseSensitivity cs) const + \since 5.14 + + Returns the index position in this Latin-1 string view of the last + occurrence of the UTF-16 string viewed by \a str, the Latin-1 string + viewed by \a l1, or the character \a ch, respectively, searching backward + from index position \a from; returns -1 if \a str, \a l1 or \a ch is not + found, respectively. + + \include qstring.qdocinc negative-index-start-search-from-end + + \include qstring.qdocinc {search-comparison-case-sensitivity} {search} + + \note When searching for a 0-length \a str or \a l1, the match at + the end of the data is excluded from the search by a negative \a + from, even though \c{-1} is normally thought of as searching from + the end of the string: the match at the end is \e after the last + character, so it is excluded. To include such a final empty match, + either give a positive value for \a from or omit the \a from + parameter entirely. + + \sa indexOf(), QStringView::lastIndexOf(), QStringView::indexOf(), + QString::indexOf() +*/ + +/*! + \fn qsizetype QLatin1StringView::lastIndexOf(QStringView str, Qt::CaseSensitivity cs = Qt::CaseSensitive) const + \fn qsizetype QLatin1StringView::lastIndexOf(QLatin1StringView l1, Qt::CaseSensitivity cs = Qt::CaseSensitive) const + \since 6.2 + \overload lastIndexOf() + + Returns the index position in this Latin-1 string view of the last + occurrence of the UTF-16 string viewed by \a str or the Latin-1 string + viewed by \a l1, respectively. Returns -1 if \a str or \a l1 is not found, + respectively. + + \include qstring.qdocinc {search-comparison-case-sensitivity} {search} +*/ + +/*! + \fn qsizetype QLatin1StringView::lastIndexOf(QChar ch, Qt::CaseSensitivity cs) const + \since 6.3 + \overload +*/ + +/*! + \fn qsizetype QLatin1StringView::count(QStringView str, Qt::CaseSensitivity cs) const + \fn qsizetype QLatin1StringView::count(QLatin1StringView l1, Qt::CaseSensitivity cs) const + \fn qsizetype QLatin1StringView::count(QChar ch, Qt::CaseSensitivity cs) const + \since 6.4 + + Returns the number of (potentially overlapping) occurrences of the + UTF-16 string viewed by \a str, the Latin-1 string viewed by \a l1, + or the character \a ch, respectively, in this string view. + + \include qstring.qdocinc {search-comparison-case-sensitivity} {search} + + \sa contains(), indexOf() +*/ + +/*! + \fn QLatin1StringView::const_iterator QLatin1StringView::begin() const + \since 5.10 + + Returns a const \l{STL-style iterators}{STL-style iterator} pointing to the + first character in the string. + + This function is provided for STL compatibility. + + \sa end(), cbegin(), rbegin(), data() +*/ + +/*! + \fn QLatin1StringView::const_iterator QLatin1StringView::cbegin() const + \since 5.10 + + Same as begin(). + + This function is provided for STL compatibility. + + \sa cend(), begin(), crbegin(), data() +*/ + +/*! + \fn QLatin1StringView::const_iterator QLatin1StringView::constBegin() const + \since 6.4 + + Same as begin(). + + This function is provided for compatibility with other Qt containers. + + \sa constEnd(), begin(), cbegin(), data() +*/ + +/*! + \fn QLatin1StringView::const_iterator QLatin1StringView::end() const + \since 5.10 + + Returns a const \l{STL-style iterators}{STL-style iterator} pointing just + after the last character in the string. + + This function is provided for STL compatibility. + + \sa begin(), cend(), rend() +*/ + +/*! \fn QLatin1StringView::const_iterator QLatin1StringView::cend() const + \since 5.10 + + Same as end(). + + This function is provided for STL compatibility. + + \sa cbegin(), end(), crend() +*/ + +/*! \fn QLatin1StringView::const_iterator QLatin1StringView::constEnd() const + \since 6.4 + + Same as end(). + + This function is provided for compatibility with other Qt containers. + + \sa constBegin(), end(), cend(), crend() +*/ + +/*! + \fn QLatin1StringView::const_reverse_iterator QLatin1StringView::rbegin() const + \since 5.10 + + Returns a const \l{STL-style iterators}{STL-style} reverse iterator pointing + to the first character in the string, in reverse order. + + This function is provided for STL compatibility. + + \sa rend(), crbegin(), begin() +*/ + +/*! + \fn QLatin1StringView::const_reverse_iterator QLatin1StringView::crbegin() const + \since 5.10 + + Same as rbegin(). + + This function is provided for STL compatibility. + + \sa crend(), rbegin(), cbegin() +*/ + +/*! + \fn QLatin1StringView::const_reverse_iterator QLatin1StringView::rend() const + \since 5.10 + + Returns a \l{STL-style iterators}{STL-style} reverse iterator pointing just + after the last character in the string, in reverse order. + + This function is provided for STL compatibility. + + \sa rbegin(), crend(), end() +*/ + +/*! + \fn QLatin1StringView::const_reverse_iterator QLatin1StringView::crend() const + \since 5.10 + + Same as rend(). + + This function is provided for STL compatibility. + + \sa crbegin(), rend(), cend() +*/ + +/*! + \fn QLatin1StringView QLatin1StringView::mid(qsizetype start, qsizetype length) const + \since 5.8 + + Returns the substring of length \a length starting at position + \a start in this Latin-1 string view. + + If you know that \a start and \a length cannot be out of bounds, use + sliced() instead in new code, because it is faster. + + Returns an empty Latin-1 string view if \a start exceeds the length + of this string view. If there are less than \a length characters available + in this string view starting at \a start, or if \a length is negative + (default), the function returns all characters that are available from + \a start. + + \sa first(), last(), sliced(), chopped(), chop(), truncate() +*/ + +/*! + \fn QLatin1StringView QLatin1StringView::left(qsizetype length) const + \since 5.8 + + If you know that \a length cannot be out of bounds, use first() instead in + new code, because it is faster. + + Returns the substring of length \a length starting at position + 0 in this Latin-1 string view. + + The entire Latin-1 string view is returned if \a length is greater + than or equal to size(), or less than zero. + + \sa first(), last(), sliced(), startsWith(), chopped(), chop(), truncate() +*/ + +/*! + \fn QLatin1StringView QLatin1StringView::right(qsizetype length) const + \since 5.8 + + If you know that \a length cannot be out of bounds, use last() instead in + new code, because it is faster. + + Returns the substring of length \a length starting at position + size() - \a length in this Latin-1 string view. + + The entire Latin-1 string view is returned if \a length is greater + than or equal to size(), or less than zero. + + \sa first(), last(), sliced(), endsWith(), chopped(), chop(), truncate() +*/ + +/*! + \fn QLatin1StringView QLatin1StringView::first(qsizetype n) const + \since 6.0 + + Returns a Latin-1 string view that contains the first \a n characters + of this string view. + + \note The behavior is undefined when \a n < 0 or \a n > size(). + + \sa last(), startsWith(), chopped(), chop(), truncate() +*/ + +/*! + \fn QLatin1StringView QLatin1StringView::last(qsizetype n) const + \since 6.0 + + Returns a Latin-1 string view that contains the last \a n characters + of this string view. + + \note The behavior is undefined when \a n < 0 or \a n > size(). + + \sa first(), endsWith(), chopped(), chop(), truncate() +*/ + +/*! + \fn QLatin1StringView QLatin1StringView::sliced(qsizetype pos, qsizetype n) const + \since 6.0 + + Returns a Latin-1 string view that points to \a n characters of this + string view, starting at position \a pos. + + \note The behavior is undefined when \a pos < 0, \a n < 0, + or \c{pos + n > size()}. + + \sa first(), last(), chopped(), chop(), truncate() +*/ + +/*! + \fn QLatin1StringView QLatin1StringView::sliced(qsizetype pos) const + \since 6.0 + + Returns a Latin-1 string view starting at position \a pos in this + string view, and extending to its end. + + \note The behavior is undefined when \a pos < 0 or \a pos > size(). + + \sa first(), last(), chopped(), chop(), truncate() +*/ + +/*! + \fn QLatin1StringView QLatin1StringView::chopped(qsizetype length) const + \since 5.10 + + Returns the substring of length size() - \a length starting at the + beginning of this object. + + Same as \c{left(size() - length)}. + + \note The behavior is undefined when \a length < 0 or \a length > size(). + + \sa sliced(), first(), last(), chop(), truncate() +*/ + +/*! + \fn void QLatin1StringView::truncate(qsizetype length) + \since 5.10 + + Truncates this string to length \a length. + + Same as \c{*this = left(length)}. + + \note The behavior is undefined when \a length < 0 or \a length > size(). + + \sa sliced(), first(), last(), chopped(), chop() +*/ + +/*! + \fn void QLatin1StringView::chop(qsizetype length) + \since 5.10 + + Truncates this string by \a length characters. + + Same as \c{*this = left(size() - length)}. + + \note The behavior is undefined when \a length < 0 or \a length > size(). + + \sa sliced(), first(), last(), chopped(), truncate() +*/ + +/*! + \fn QLatin1StringView QLatin1StringView::trimmed() const + \since 5.10 + + Strips leading and trailing whitespace and returns the result. + + Whitespace means any character for which QChar::isSpace() returns + \c true. This includes the ASCII characters '\\t', '\\n', '\\v', + '\\f', '\\r', and ' '. +*/ + +/*! + \fn bool QLatin1StringView::operator==(const char *other) const + \since 4.3 + + Returns \c true if the string is equal to const char pointer \a other; + otherwise returns \c false. + + The \a other const char pointer is converted to a QString using + the QString::fromUtf8() function. + + You can disable this operator by defining + \l QT_NO_CAST_FROM_ASCII when you compile your applications. This + can be useful if you want to ensure that all user-visible strings + go through QObject::tr(), for example. + + \sa {Comparing Strings} +*/ + +/*! + \fn bool QLatin1StringView::operator==(const QByteArray &other) const + \since 5.0 + \overload + + The \a other byte array is converted to a QString using + the QString::fromUtf8() function. + + You can disable this operator by defining + \l QT_NO_CAST_FROM_ASCII when you compile your applications. This + can be useful if you want to ensure that all user-visible strings + go through QObject::tr(), for example. +*/ + +/*! + \fn bool QLatin1StringView::operator!=(const char *other) const + \since 4.3 + + Returns \c true if this string is not equal to const char pointer \a other; + otherwise returns \c false. + + The \a other const char pointer is converted to a QString using + the QString::fromUtf8() function. + + You can disable this operator by defining + \l QT_NO_CAST_FROM_ASCII when you compile your applications. This + can be useful if you want to ensure that all user-visible strings + go through QObject::tr(), for example. + + \sa {Comparing Strings} +*/ + +/*! + \fn bool QLatin1StringView::operator!=(const QByteArray &other) const + \since 5.0 + \overload operator!=() + + The \a other byte array is converted to a QString using + the QString::fromUtf8() function. + + You can disable this operator by defining + \l QT_NO_CAST_FROM_ASCII when you compile your applications. This + can be useful if you want to ensure that all user-visible strings + go through QObject::tr(), for example. +*/ + +/*! + \fn bool QLatin1StringView::operator>(const char *other) const + \since 4.3 + + Returns \c true if this string is lexically greater than const char pointer + \a other; otherwise returns \c false. + + The \a other const char pointer is converted to a QString using + the QString::fromUtf8() function. + + You can disable this operator by defining \l QT_NO_CAST_FROM_ASCII + when you compile your applications. This can be useful if you want + to ensure that all user-visible strings go through QObject::tr(), + for example. + + \sa {Comparing Strings} +*/ + +/*! + \fn bool QLatin1StringView::operator>(const QByteArray &other) const + \since 5.0 + \overload + + The \a other byte array is converted to a QString using + the QString::fromUtf8() function. + + You can disable this operator by defining \l QT_NO_CAST_FROM_ASCII + when you compile your applications. This can be useful if you want + to ensure that all user-visible strings go through QObject::tr(), + for example. +*/ + +/*! + \fn bool QLatin1StringView::operator<(const char *other) const + \since 4.3 + + Returns \c true if this string is lexically less than const char pointer + \a other; otherwise returns \c false. + + The \a other const char pointer is converted to a QString using + the QString::fromUtf8() function. + + You can disable this operator by defining + \l QT_NO_CAST_FROM_ASCII when you compile your applications. This + can be useful if you want to ensure that all user-visible strings + go through QObject::tr(), for example. + + \sa {Comparing Strings} +*/ + +/*! + \fn bool QLatin1StringView::operator<(const QByteArray &other) const + \since 5.0 + \overload + + The \a other byte array is converted to a QString using + the QString::fromUtf8() function. + + You can disable this operator by defining + \l QT_NO_CAST_FROM_ASCII when you compile your applications. This + can be useful if you want to ensure that all user-visible strings + go through QObject::tr(), for example. +*/ + +/*! + \fn bool QLatin1StringView::operator>=(const char *other) const + \since 4.3 + + Returns \c true if this string is lexically greater than or equal to + const char pointer \a other; otherwise returns \c false. + + The \a other const char pointer is converted to a QString using + the QString::fromUtf8() function. + + You can disable this operator by defining + \l QT_NO_CAST_FROM_ASCII when you compile your applications. This + can be useful if you want to ensure that all user-visible strings + go through QObject::tr(), for example. + + \sa {Comparing Strings} +*/ + +/*! + \fn bool QLatin1StringView::operator>=(const QByteArray &other) const + \since 5.0 + \overload + + The \a other byte array is converted to a QString using + the QString::fromUtf8() function. + + You can disable this operator by defining + \l QT_NO_CAST_FROM_ASCII when you compile your applications. This + can be useful if you want to ensure that all user-visible strings + go through QObject::tr(), for example. +*/ + +/*! + \fn bool QLatin1StringView::operator<=(const char *other) const + \since 4.3 + + Returns \c true if this string is lexically less than or equal to + const char pointer \a other; otherwise returns \c false. + + The \a other const char pointer is converted to a QString using + the QString::fromUtf8() function. + + You can disable this operator by defining + \l QT_NO_CAST_FROM_ASCII when you compile your applications. This + can be useful if you want to ensure that all user-visible strings + go through QObject::tr(), for example. + + \sa {Comparing Strings} +*/ + +/*! + \fn bool QLatin1StringView::operator<=(const QByteArray &other) const + \since 5.0 + \overload + + The \a other byte array is converted to a QString using + the QString::fromUtf8() function. + + You can disable this operator by defining + \l QT_NO_CAST_FROM_ASCII when you compile your applications. This + can be useful if you want to ensure that all user-visible strings + go through QObject::tr(), for example. +*/ + +/*! \fn bool QLatin1StringView::operator==(QLatin1StringView s1, QLatin1StringView s2) + + Returns \c true if string \a s1 is lexically equal to string \a s2; + otherwise returns \c false. +*/ +/*! \fn bool QLatin1StringView::operator!=(QLatin1StringView s1, QLatin1StringView s2) + + Returns \c true if string \a s1 is lexically not equal to string \a s2; + otherwise returns \c false. +*/ +/*! \fn bool QLatin1StringView::operator<(QLatin1StringView s1, QLatin1StringView s2) + + Returns \c true if string \a s1 is lexically less than string \a s2; + otherwise returns \c false. +*/ +/*! \fn bool QLatin1StringView::operator<=(QLatin1StringView s1, QLatin1StringView s2) + + Returns \c true if string \a s1 is lexically less than or equal to + string \a s2; otherwise returns \c false. +*/ +/*! \fn bool QLatin1StringView::operator>(QLatin1StringView s1, QLatin1StringView s2) + + Returns \c true if string \a s1 is lexically greater than string \a s2; + otherwise returns \c false. +*/ +/*! \fn bool QLatin1StringView::operator>=(QLatin1StringView s1, QLatin1StringView s2) + + Returns \c true if string \a s1 is lexically greater than or equal + to string \a s2; otherwise returns \c false. +*/ + +/*! \fn bool QLatin1StringView::operator==(QChar ch, QLatin1StringView s) + + Returns \c true if char \a ch is lexically equal to string \a s; + otherwise returns \c false. +*/ +/*! \fn bool QLatin1StringView::operator<(QChar ch, QLatin1StringView s) + + Returns \c true if char \a ch is lexically less than string \a s; + otherwise returns \c false. +*/ +/*! \fn bool QLatin1StringView::operator>(QChar ch, QLatin1StringView s) + Returns \c true if char \a ch is lexically greater than string \a s; + otherwise returns \c false. +*/ +/*! \fn bool QLatin1StringView::operator!=(QChar ch, QLatin1StringView s) + + Returns \c true if char \a ch is lexically not equal to string \a s; + otherwise returns \c false. +*/ +/*! \fn bool QLatin1StringView::operator<=(QChar ch, QLatin1StringView s) + + Returns \c true if char \a ch is lexically less than or equal to + string \a s; otherwise returns \c false. +*/ +/*! \fn bool QLatin1StringView::operator>=(QChar ch, QLatin1StringView s) + + Returns \c true if char \a ch is lexically greater than or equal to + string \a s; otherwise returns \c false. +*/ + +/*! \fn bool QLatin1StringView::operator==(QLatin1StringView s, QChar ch) + + Returns \c true if string \a s is lexically equal to char \a ch; + otherwise returns \c false. +*/ +/*! \fn bool QLatin1StringView::operator<(QLatin1StringView s, QChar ch) + + Returns \c true if string \a s is lexically less than char \a ch; + otherwise returns \c false. +*/ +/*! \fn bool QLatin1StringView::operator>(QLatin1StringView s, QChar ch) + + Returns \c true if string \a s is lexically greater than char \a ch; + otherwise returns \c false. +*/ +/*! \fn bool QLatin1StringView::operator!=(QLatin1StringView s, QChar ch) + + Returns \c true if string \a s is lexically not equal to char \a ch; + otherwise returns \c false. +*/ +/*! \fn bool QLatin1StringView::operator<=(QLatin1StringView s, QChar ch) + + Returns \c true if string \a s is lexically less than or equal to + char \a ch; otherwise returns \c false. +*/ +/*! \fn bool QLatin1StringView::operator>=(QLatin1StringView s, QChar ch) + + Returns \c true if string \a s is lexically greater than or equal to + char \a ch; otherwise returns \c false. +*/ + +/*! \fn bool QLatin1StringView::operator==(QStringView s1, QLatin1StringView s2) + + Returns \c true if string view \a s1 is lexically equal to string \a s2; + otherwise returns \c false. +*/ +/*! \fn bool QLatin1StringView::operator<(QStringView s1, QLatin1StringView s2) + + Returns \c true if string view \a s1 is lexically less than string \a s2; + otherwise returns \c false. +*/ +/*! \fn bool QLatin1StringView::operator>(QStringView s1, QLatin1StringView s2) + + Returns \c true if string view \a s1 is lexically greater than string \a s2; + otherwise returns \c false. +*/ +/*! \fn bool QLatin1StringView::operator!=(QStringView s1, QLatin1StringView s2) + + Returns \c true if string view \a s1 is lexically not equal to string \a s2; + otherwise returns \c false. +*/ +/*! \fn bool QLatin1StringView::operator<=(QStringView s1, QLatin1StringView s2) + + Returns \c true if string view \a s1 is lexically less than or equal to + string \a s2; otherwise returns \c false. +*/ +/*! \fn bool QLatin1StringView::operator>=(QStringView s1, QLatin1StringView s2) + + Returns \c true if string view \a s1 is lexically greater than or equal to + string \a s2; otherwise returns \c false. +*/ + +/*! \fn bool QLatin1StringView::operator==(QLatin1StringView s1, QStringView s2) + + Returns \c true if string \a s1 is lexically equal to string view \a s2; + otherwise returns \c false. +*/ +/*! \fn bool QLatin1StringView::operator<(QLatin1StringView s1, QStringView s2) + + Returns \c true if string \a s1 is lexically less than string view \a s2; + otherwise returns \c false. +*/ +/*! \fn bool QLatin1StringView::operator>(QLatin1StringView s1, QStringView s2) + + Returns \c true if string \a s1 is lexically greater than string view \a s2; + otherwise returns \c false. +*/ +/*! \fn bool QLatin1StringView::operator!=(QLatin1StringView s1, QStringView s2) + + Returns \c true if string \a s1 is lexically not equal to string view \a s2; + otherwise returns \c false. +*/ +/*! \fn bool QLatin1StringView::operator<=(QLatin1StringView s1, QStringView s2) + + Returns \c true if string \a s1 is lexically less than or equal to + string view \a s2; otherwise returns \c false. +*/ +/*! \fn bool QLatin1StringView::operator>=(QLatin1StringView s1, QStringView s2) + + Returns \c true if string \a s1 is lexically greater than or equal to + string view \a s2; otherwise returns \c false. +*/ + +/*! \fn bool QLatin1StringView::operator==(const char *s1, QLatin1StringView s2) + + Returns \c true if const char pointer \a s1 is lexically equal to + string \a s2; otherwise returns \c false. +*/ +/*! \fn bool QLatin1StringView::operator<(const char *s1, QLatin1StringView s2) + + Returns \c true if const char pointer \a s1 is lexically less than + string \a s2; otherwise returns \c false. +*/ +/*! \fn bool QLatin1StringView::operator>(const char *s1, QLatin1StringView s2) + + Returns \c true if const char pointer \a s1 is lexically greater than + string \a s2; otherwise returns \c false. +*/ +/*! \fn bool QLatin1StringView::operator!=(const char *s1, QLatin1StringView s2) + + Returns \c true if const char pointer \a s1 is lexically not equal to + string \a s2; otherwise returns \c false. +*/ +/*! \fn bool QLatin1StringView::operator<=(const char *s1, QLatin1StringView s2) + + Returns \c true if const char pointer \a s1 is lexically less than or + equal to string \a s2; otherwise returns \c false. +*/ +/*! \fn bool QLatin1StringView::operator>=(const char *s1, QLatin1StringView s2) + + Returns \c true if const char pointer \a s1 is lexically greater than or + equal to string \a s2; otherwise returns \c false. +*/ + +/*! + \fn qlonglong QLatin1StringView::toLongLong(bool *ok, int base) const + \fn qulonglong QLatin1StringView::toULongLong(bool *ok, int base) const + \fn int QLatin1StringView::toInt(bool *ok, int base) const + \fn uint QLatin1StringView::toUInt(bool *ok, int base) const + \fn long QLatin1StringView::toLong(bool *ok, int base) const + \fn ulong QLatin1StringView::toULong(bool *ok, int base) const + \fn short QLatin1StringView::toShort(bool *ok, int base) const + \fn ushort QLatin1StringView::toUShort(bool *ok, int base) const + + \since 6.4 + + Returns this QLatin1StringView converted to a corresponding numeric value using + base \a base, which is ten by default. Bases 0 and 2 through 36 are supported, + using letters for digits beyond 9; A is ten, B is eleven and so on. + + If \a base is 0, the base is determined automatically using the following + rules (in this order), if the Latin-1 string view begins with: + + \list + \li \c "0x", the rest of it is read as hexadecimal (base 16) + \li \c "0b", the rest of it is read as binary (base 2) + \li \c "0", the rest of it is read as octal (base 8) + \li otherwise it is read as decimal + \endlist + + Returns 0 if the conversion fails. + + If \a ok is not \nullptr, failure is reported by setting *\a{ok} + to \c false, and success by setting *\a{ok} to \c true. + +//! [latin1-numeric-conversion-note] + \note The conversion of the number is performed in the default C locale, + regardless of the user's locale. Use QLocale to perform locale-aware + conversions between numbers and strings. + + This function ignores leading and trailing spacing characters. +//! [latin1-numeric-conversion-note] + + \note Support for the "0b" prefix was added in Qt 6.4. +*/ + +/*! + \fn double QLatin1StringView::toDouble(bool *ok) const + \fn float QLatin1StringView::toFloat(bool *ok) const + \since 6.4 + + Returns this QLatin1StringView converted to a corresponding floating-point value. + + Returns an infinity if the conversion overflows or 0.0 if the + conversion fails for other reasons (e.g. underflow). + + If \a ok is not \nullptr, failure is reported by setting *\a{ok} + to \c false, and success by setting *\a{ok} to \c true. + + \warning The QLatin1StringView content may only contain valid numerical + characters which includes the plus/minus sign, the character e used in + scientific notation, and the decimal point. Including the unit or additional + characters leads to a conversion error. + + \include qlatin1stringview.qdoc latin1-numeric-conversion-note +*/ + +/*! + \fn Qt::Literals::StringLiterals::operator""_L1(const char *str, size_t size) + + \relates QLatin1StringView + \since 6.4 + + Literal operator that creates a QLatin1StringView out of the first \a size + characters in the char string literal \a str. + + The following code creates a QLatin1StringView: + \code + using namespace Qt::Literals::StringLiterals; + + auto str = "hello"_L1; + \endcode + + \sa Qt::Literals::StringLiterals +*/ diff --git a/src/corelib/text/qlocale.cpp b/src/corelib/text/qlocale.cpp index 4466a460..d5cf1dc7 100644 --- a/src/corelib/text/qlocale.cpp +++ b/src/corelib/text/qlocale.cpp @@ -4,7 +4,7 @@ #include "qglobal.h" -#if (defined(QT_STATIC) || defined(QT_BOOTSTRAPPED)) && defined(Q_CC_GNU_ONLY) && Q_CC_GNU && __GNUC__ == 10 +#if (defined(QT_STATIC) || defined(QT_BOOTSTRAPPED)) && defined(Q_CC_GNU_ONLY) && Q_CC_GNU >= 1000 QT_WARNING_DISABLE_GCC("-Wfree-nonheap-object") // false positive tracking #endif @@ -79,7 +79,7 @@ static_assert(!ascii_isspace('\a')); static_assert(!ascii_isspace('a')); static_assert(!ascii_isspace('\177')); static_assert(!ascii_isspace(uchar('\200'))); -static_assert(!ascii_isspace(uchar('\xA0'))); +static_assert(!ascii_isspace(uchar('\xA0'))); // NBSP (is a space but Latin 1, not ASCII) static_assert(!ascii_isspace(uchar('\377'))); /****************************************************************************** @@ -105,7 +105,7 @@ QLocale::Language QLocalePrivate::codeToLanguage(QStringView code, if (uc1 > 0x7F || uc2 > 0x7F || uc3 > 0x7F) return QLocale::AnyLanguage; - const AlphaCode codeBuf = { { char(uc1), char(uc2), char(uc3) } }; + const AlphaCode codeBuf = { char(uc1), char(uc2), char(uc3) }; auto searchCode = [codeBuf](auto f) { return std::find_if(languageCodeList.begin(), languageCodeList.end(), @@ -201,27 +201,27 @@ QLocale::Territory QLocalePrivate::codeToTerritory(QStringView code) noexcept return QLocale::AnyTerritory; } -QLatin1StringView QLocalePrivate::languageToCode(QLocale::Language language, - QLocale::LanguageCodeTypes codeTypes) +std::array QLocalePrivate::languageToCode(QLocale::Language language, + QLocale::LanguageCodeTypes codeTypes) { if (language == QLocale::AnyLanguage || language > QLocale::LastLanguage) return {}; if (language == QLocale::C) - return "C"_L1; + return {'C'}; const LanguageCodeEntry &i = languageCodeList[language]; if (codeTypes.testFlag(QLocale::ISO639Part1) && i.part1.isValid()) - return {i.part1.code, 2}; + return i.part1.decode(); if (codeTypes.testFlag(QLocale::ISO639Part2B) && i.part2B.isValid()) - return {i.part2B.code, 3}; + return i.part2B.decode(); if (codeTypes.testFlag(QLocale::ISO639Part2T) && i.part2T.isValid()) - return {i.part2T.code, 3}; + return i.part2T.decode(); if (codeTypes.testFlag(QLocale::ISO639Part3)) - return {i.part3.code, 3}; + return i.part3.decode(); return {}; } @@ -409,14 +409,14 @@ QByteArray QLocaleId::name(char separator) const return QByteArrayLiteral("C"); const LanguageCodeEntry &language = languageCodeList[language_id]; - const char *lang; + AlphaCode lang; qsizetype langLen; if (language.part1.isValid()) { - lang = language.part1.code; + lang = language.part1; langLen = 2; } else { - lang = language.part2B.isValid() ? language.part2B.code : language.part3.code; + lang = language.part2B.isValid() ? language.part2B : language.part3; langLen = 3; } @@ -429,10 +429,12 @@ QByteArray QLocaleId::name(char separator) const QByteArray name(len, Qt::Uninitialized); char *uc = name.data(); - *uc++ = lang[0]; - *uc++ = lang[1]; + auto langArray = lang.decode(); + + *uc++ = langArray[0]; + *uc++ = langArray[1]; if (langLen > 2) - *uc++ = lang[2]; + *uc++ = langArray[2]; if (script) { *uc++ = separator; @@ -1341,37 +1343,32 @@ QLocale::Country QLocale::country() const QString QLocale::name() const { + const auto code = d->languageCode(); + QLatin1StringView view{code.data()}; + Language l = language(); if (l == C) - return d->languageCode(); + return view; Territory c = territory(); if (c == AnyTerritory) - return d->languageCode(); + return view; - return d->languageCode() + u'_' + d->territoryCode(); -} - -static qlonglong toIntegral_helper(const QLocaleData *d, QStringView str, bool *ok, - QLocale::NumberOptions mode, qlonglong) -{ - return d->stringToLongLong(str, 10, ok, mode); -} - -static qulonglong toIntegral_helper(const QLocaleData *d, QStringView str, bool *ok, - QLocale::NumberOptions mode, qulonglong) -{ - return d->stringToUnsLongLong(str, 10, ok, mode); + return view + u'_' + d->territoryCode(); } template static inline T toIntegral_helper(const QLocalePrivate *d, QStringView str, bool *ok) { - using Int64 = - typename std::conditional::value, qulonglong, qlonglong>::type; + constexpr bool isUnsigned = std::is_unsigned_v; + using Int64 = typename std::conditional_t; + + Int64 val = 0; + if constexpr (isUnsigned) + val = d->m_data->stringToUnsLongLong(str, 10, ok, d->m_numberOptions); + else + val = d->m_data->stringToLongLong(str, 10, ok, d->m_numberOptions); - // we select the right overload by the last, unused parameter - Int64 val = toIntegral_helper(d->m_data, str, ok, d->m_numberOptions, Int64()); if (T(val) != val) { if (ok != nullptr) *ok = false; @@ -1426,7 +1423,8 @@ QString QLocale::bcp47Name() const */ QString QLocale::languageToCode(Language language, LanguageCodeTypes codeTypes) { - return QLocalePrivate::languageToCode(language, codeTypes); + const auto code = QLocalePrivate::languageToCode(language, codeTypes); + return QLatin1StringView{code.data()}; } /*! @@ -2527,7 +2525,14 @@ QDateTime QLocale::toDateTime(const QString &string, const QString &format, QCal /*! \since 4.1 - Returns the decimal point character of this locale. + Returns the fractional part separator for this locale. + + This is the token that separates the whole number part from the fracional + part in the representation of a number which has a fractional part. This is + commonly called the "decimal point character" - even though, in many + locales, it is not a "point" (or similar dot). It is (since Qt 6.0) returned + as a string in case some locale needs more than one UTF-16 code-point to + represent its separator. \sa groupSeparator(), toString() */ @@ -2539,7 +2544,14 @@ QString QLocale::decimalPoint() const /*! \since 4.1 - Returns the group separator character of this locale. + Returns the digit-grouping separator for this locale. + + This is a token used to break up long sequences of digits, in the + representation of a number, to make it easier to read. In some locales it + may be empty, indicating that digits should not be broken up into groups in + this way. In others it may be a spacing character. It is (since Qt 6.0) + returned as a string in case some locale needs more than one UTF-16 + code-point to represent its separator. \sa decimalPoint(), toString() */ @@ -2551,7 +2563,12 @@ QString QLocale::groupSeparator() const /*! \since 4.1 - Returns the percent character of this locale. + Returns the percent marker of this locale. + + This is a token presumed to be appended to a number to indicate a + percentage. It is (since Qt 6.0) returned as a string because, in some + locales, it is not a single character - for example, because it includes a + text-direction-control character. \sa toString() */ @@ -2565,6 +2582,13 @@ QString QLocale::percent() const Returns the zero digit character of this locale. + This is a single Unicode character but may be encoded as a surrogate pair, + so is (since Qt 6.0) returned as a string. In most locales, other digits + follow it in Unicode ordering - however, some number systems, notably those + using U+3007 as zero, do not have contiguous digits. Use toString() to + obtain suitable representations of numbers, rather than trying to construct + them from this zero digit. + \sa toString() */ QString QLocale::zeroDigit() const @@ -2575,7 +2599,12 @@ QString QLocale::zeroDigit() const /*! \since 4.1 - Returns the negative sign character of this locale. + Returns the negative sign indicator of this locale. + + This is a token presumed to be used as a prefix to a number to indicate that + it is negative. It is (since Qt 6.0) returned as a string because, in some + locales, it is not a single character - for example, because it includes a + text-direction-control character. \sa positiveSign(), toString() */ @@ -2587,7 +2616,12 @@ QString QLocale::negativeSign() const /*! \since 4.5 - Returns the positive sign character of this locale. + Returns the positive sign indicator of this locale. + + This is a token presumed to be used as a prefix to a number to indicate that + it is positive. It is (since Qt 6.0) returned as a string because, in some + locales, it is not a single character - for example, because it includes a + text-direction-control character. \sa negativeSign(), toString() */ @@ -2599,8 +2633,13 @@ QString QLocale::positiveSign() const /*! \since 4.1 - Returns the exponential character of this locale, used to separate exponent - from mantissa in some floating-point numeric representations. + Returns the exponent separator for this locale. + + This is a token used to separate mantissa from exponent in some + floating-point numeric representations. It is (since Qt 6.0) returned as a + string because, in some locales, it is not a single character - for example, + it may consist of a multiplication sign and a representation of the "ten to + the power" operator. \sa toString(double, char, int) */ @@ -2649,17 +2688,17 @@ QString QLocale::toString(double f, char format, int precision) const uint flags = isAsciiUpper(format) ? QLocaleData::CapitalEorX : 0; switch (QtMiscUtils::toAsciiLower(format)) { - case 'f': - form = QLocaleData::DFDecimal; - break; - case 'e': - form = QLocaleData::DFExponent; - break; - case 'g': - form = QLocaleData::DFSignificantDigits; - break; - default: - break; + case 'f': + form = QLocaleData::DFDecimal; + break; + case 'e': + form = QLocaleData::DFExponent; + break; + case 'g': + form = QLocaleData::DFSignificantDigits; + break; + default: + break; } if (!(d->m_numberOptions & OmitGroupSeparator)) @@ -2749,6 +2788,15 @@ QList QLocale::matchingLocales(QLocale::Language language, QLocale::Scr ++index; } + // Add current system locale, if it matches + const auto syslocaledata = systemData(); + + if (filter.acceptLanguage(syslocaledata->m_language_id)) { + const QLocaleId id = syslocaledata->id(); + if (filter.acceptScriptTerritory(id)) + result.append(QLocale::system()); + } + return result; } @@ -3320,6 +3368,18 @@ QString QCalendarBackend::dateTimeToString(QStringView format, const QDateTime & day = parts.day; } + auto appendToResult = [&](int t, int repeat) { + auto data = locale.d->m_data; + if (repeat > 1) + result.append(data->longLongToString(t, -1, 10, repeat, QLocaleData::ZeroPadded)); + else + result.append(data->longLongToString(t)); + }; + + auto formatType = [](int repeat) { + return repeat == 3 ? QLocale::ShortFormat : QLocale::LongFormat; + }; + qsizetype i = 0; while (i < format.size()) { if (format.at(i).unicode() == '\'') { @@ -3342,15 +3402,11 @@ QString QCalendarBackend::dateTimeToString(QStringView format, const QDateTime & repeat = 2; switch (repeat) { - case 4: { - const int len = (year < 0) ? 5 : 4; - result.append(locale.d->m_data->longLongToString(year, -1, 10, len, - QLocaleData::ZeroPadded)); + case 4: + appendToResult(year, (year < 0) ? 5 : 4); break; - } case 2: - result.append(locale.d->m_data->longLongToString(year % 100, -1, 10, 2, - QLocaleData::ZeroPadded)); + appendToResult(year % 100, 2); break; default: repeat = 1; @@ -3362,43 +3418,20 @@ QString QCalendarBackend::dateTimeToString(QStringView format, const QDateTime & case 'M': used = true; repeat = qMin(repeat, 4); - switch (repeat) { - case 1: - result.append(locale.d->m_data->longLongToString(month)); - break; - case 2: - result.append(locale.d->m_data->longLongToString(month, -1, 10, 2, - QLocaleData::ZeroPadded)); - break; - case 3: - result.append(monthName(locale, month, year, QLocale::ShortFormat)); - break; - case 4: - result.append(monthName(locale, month, year, QLocale::LongFormat)); - break; - } + if (repeat <= 2) + appendToResult(month, repeat); + else + result.append(monthName(locale, month, year, formatType(repeat))); break; case 'd': used = true; repeat = qMin(repeat, 4); - switch (repeat) { - case 1: - result.append(locale.d->m_data->longLongToString(day)); - break; - case 2: - result.append(locale.d->m_data->longLongToString(day, -1, 10, 2, - QLocaleData::ZeroPadded)); - break; - case 3: - result.append(locale.dayName( - dayOfWeek(date.toJulianDay()), QLocale::ShortFormat)); - break; - case 4: - result.append(locale.dayName( - dayOfWeek(date.toJulianDay()), QLocale::LongFormat)); - break; - } + if (repeat <= 2) + appendToResult(day, repeat); + else + result.append( + locale.dayName(dayOfWeek(date.toJulianDay()), formatType(repeat))); break; default: @@ -3417,58 +3450,25 @@ QString QCalendarBackend::dateTimeToString(QStringView format, const QDateTime & else if (hour == 0) hour = 12; } - - switch (repeat) { - case 1: - result.append(locale.d->m_data->longLongToString(hour)); - break; - case 2: - result.append(locale.d->m_data->longLongToString(hour, -1, 10, 2, - QLocaleData::ZeroPadded)); - break; - } + appendToResult(hour, repeat); break; } case 'H': used = true; repeat = qMin(repeat, 2); - switch (repeat) { - case 1: - result.append(locale.d->m_data->longLongToString(time.hour())); - break; - case 2: - result.append(locale.d->m_data->longLongToString(time.hour(), -1, 10, 2, - QLocaleData::ZeroPadded)); - break; - } + appendToResult(time.hour(), repeat); break; case 'm': used = true; repeat = qMin(repeat, 2); - switch (repeat) { - case 1: - result.append(locale.d->m_data->longLongToString(time.minute())); - break; - case 2: - result.append(locale.d->m_data->longLongToString(time.minute(), -1, 10, 2, - QLocaleData::ZeroPadded)); - break; - } + appendToResult(time.minute(), repeat); break; case 's': used = true; repeat = qMin(repeat, 2); - switch (repeat) { - case 1: - result.append(locale.d->m_data->longLongToString(time.second())); - break; - case 2: - result.append(locale.d->m_data->longLongToString(time.second(), -1, 10, 2, - QLocaleData::ZeroPadded)); - break; - } + appendToResult(time.second(), repeat); break; case 'A': @@ -3493,8 +3493,7 @@ QString QCalendarBackend::dateTimeToString(QStringView format, const QDateTime & // note: the millisecond component is treated like the decimal part of the seconds // so ms == 2 is always printed as "002", but ms == 200 can be either "2" or "200" - result.append(locale.d->m_data->longLongToString(time.msec(), -1, 10, 3, - QLocaleData::ZeroPadded)); + appendToResult(time.msec(), 3); if (repeat != 3) { if (result.endsWith(locale.zeroDigit())) result.chop(1); @@ -3587,8 +3586,7 @@ QString QLocaleData::doubleToString(double d, int precision, DoubleForm form, if (zero == u"0") { // No need to convert digits. - Q_ASSERT(std::all_of(buf.cbegin(), buf.cbegin() + length, [](char ch) - { return '0' <= ch && ch <= '9'; })); + Q_ASSERT(std::all_of(buf.cbegin(), buf.cbegin() + length, isAsciiDigit)); // That check is taken care of in unicodeForDigits, below. } else if (zero.size() == 2 && zero.at(0).isHighSurrogate()) { const char32_t zeroUcs4 = QChar::surrogateToUcs4(zero.at(0), zero.at(1)); @@ -3614,76 +3612,73 @@ QString QLocaleData::doubleToString(double d, int precision, DoubleForm form, const bool groupDigits = flags & GroupDigits; const int minExponentDigits = flags & ZeroPadExponent ? 2 : 1; switch (form) { - case DFExponent: - numStr = exponentForm(std::move(digits), decpt, precision, PMDecimalDigits, - mustMarkDecimal, minExponentDigits); - break; - case DFDecimal: - numStr = decimalForm(std::move(digits), decpt, precision, PMDecimalDigits, - mustMarkDecimal, groupDigits); - break; - case DFSignificantDigits: { - PrecisionMode mode = (flags & AddTrailingZeroes) ? - PMSignificantDigits : PMChopTrailingZeros; + case DFExponent: + numStr = exponentForm(std::move(digits), decpt, precision, PMDecimalDigits, + mustMarkDecimal, minExponentDigits); + break; + case DFDecimal: + numStr = decimalForm(std::move(digits), decpt, precision, PMDecimalDigits, + mustMarkDecimal, groupDigits); + break; + case DFSignificantDigits: { + PrecisionMode mode + = (flags & AddTrailingZeroes) ? PMSignificantDigits : PMChopTrailingZeros; - /* POSIX specifies sprintf() to follow fprintf(), whose 'g/G' - format says; with P = 6 if precision unspecified else 1 if - precision is 0 else precision; when 'e/E' would have exponent - X, use: - * 'f/F' if P > X >= -4, with precision P-1-X - * 'e/E' otherwise, with precision P-1 - Helpfully, we already have mapped precision < 0 to 6 - except - for F.P.Shortest mode, which is its own story - and those of - our callers with unspecified precision either used 6 or -1 - for it. - */ - bool useDecimal; - if (precision == QLocale::FloatingPointShortest) { - // Find out which representation is shorter. - // Set bias to everything added to exponent form but not - // decimal, minus the converse. + /* POSIX specifies sprintf() to follow fprintf(), whose 'g/G' format + says; with P = 6 if precision unspecified else 1 if precision is + 0 else precision; when 'e/E' would have exponent X, use: + * 'f/F' if P > X >= -4, with precision P-1-X + * 'e/E' otherwise, with precision P-1 + Helpfully, we already have mapped precision < 0 to 6 - except for + F.P.Shortest mode, which is its own story - and those of our + callers with unspecified precision either used 6 or -1 for it. + */ + bool useDecimal; + if (precision == QLocale::FloatingPointShortest) { + // Find out which representation is shorter. + // Set bias to everything added to exponent form but not + // decimal, minus the converse. - // Exponent adds separator, sign and digits: - int bias = 2 + minExponentDigits; - // Decimal form may get grouping separators inserted: - if (groupDigits && decpt >= m_grouping_top + m_grouping_least) - bias -= (decpt - m_grouping_least) / m_grouping_higher + 1; - // X = decpt - 1 needs two digits if decpt > 10: - if (decpt > 10 && minExponentDigits == 1) - ++bias; - // Assume digitCount < 95, so we can ignore the 3-digit - // exponent case (we'll set useDecimal false anyway). + // Exponent adds separator, sign and digits: + int bias = 2 + minExponentDigits; + // Decimal form may get grouping separators inserted: + if (groupDigits && decpt >= m_grouping_top + m_grouping_least) + bias -= (decpt - m_grouping_least) / m_grouping_higher + 1; + // X = decpt - 1 needs two digits if decpt > 10: + if (decpt > 10 && minExponentDigits == 1) + ++bias; + // Assume digitCount < 95, so we can ignore the 3-digit + // exponent case (we'll set useDecimal false anyway). - const qsizetype digitCount = digits.size() / zero.size(); - if (!mustMarkDecimal) { - // Decimal separator is skipped if at end; adjust if - // that happens for only one form: - if (digitCount <= decpt && digitCount > 1) - ++bias; // decimal but not exponent - else if (digitCount == 1 && decpt <= 0) - --bias; // exponent but not decimal - } - // When 0 < decpt <= digitCount, the forms have equal digit - // counts, plus things bias has taken into account; - // otherwise decimal form's digit count is right-padded with - // zeros to decpt, when decpt is positive, otherwise it's - // left-padded with 1 - decpt zeros. - useDecimal = (decpt <= 0 ? 1 - decpt <= bias - : decpt <= digitCount ? 0 <= bias - : decpt <= digitCount + bias); - } else { - // X == decpt - 1, POSIX's P; -4 <= X < P iff -4 < decpt <= P - Q_ASSERT(precision >= 0); - useDecimal = decpt > -4 && decpt <= (precision ? precision : 1); + const qsizetype digitCount = digits.size() / zero.size(); + if (!mustMarkDecimal) { + // Decimal separator is skipped if at end; adjust if + // that happens for only one form: + if (digitCount <= decpt && digitCount > 1) + ++bias; // decimal but not exponent + else if (digitCount == 1 && decpt <= 0) + --bias; // exponent but not decimal } - - numStr = useDecimal - ? decimalForm(std::move(digits), decpt, precision, mode, - mustMarkDecimal, groupDigits) - : exponentForm(std::move(digits), decpt, precision, mode, - mustMarkDecimal, minExponentDigits); - break; + // When 0 < decpt <= digitCount, the forms have equal digit + // counts, plus things bias has taken into account; otherwise + // decimal form's digit count is right-padded with zeros to + // decpt, when decpt is positive, otherwise it's left-padded + // with 1 - decpt zeros. + useDecimal = (decpt <= 0 ? 1 - decpt <= bias + : decpt <= digitCount ? 0 <= bias : decpt <= digitCount + bias); + } else { + // X == decpt - 1, POSIX's P; -4 <= X < P iff -4 < decpt <= P + Q_ASSERT(precision >= 0); + useDecimal = decpt > -4 && decpt <= (precision ? precision : 1); } + + numStr = useDecimal + ? decimalForm(std::move(digits), decpt, precision, mode, + mustMarkDecimal, groupDigits) + : exponentForm(std::move(digits), decpt, precision, mode, + mustMarkDecimal, minExponentDigits); + break; + } } // Pad with zeros. LeftAdjusted overrides ZeroPadded. @@ -3875,10 +3870,226 @@ QString QLocaleData::applyIntegerFormatting(QString &&numStr, bool negative, int return result; } +inline QLocaleData::NumericData QLocaleData::numericData(QLocaleData::NumberMode mode) const +{ + NumericData result; + if (this == c()) { + result.isC = true; + return result; + } + result.setZero(zero().viewData(single_character_data)); + result.group = groupDelim().viewData(single_character_data); + // Note: minus, plus and exponent might not actually be single characters. + result.minus = minus().viewData(single_character_data); + result.plus = plus().viewData(single_character_data); + if (mode != IntegerMode) + result.decimal = decimalSeparator().viewData(single_character_data); + if (mode == DoubleScientificMode) { + result.exponent = exponential().viewData(single_character_data); + // exponentCyrillic means "apply the Cyrrilic-specific exponent hack" + result.exponentCyrillic = m_script_id == QLocale::CyrillicScript; + } +#ifndef QT_NO_SYSTEMLOCALE + if (this == &systemLocaleData) { + const auto getString = [sys = systemLocale()](QSystemLocale::QueryType query) { + return sys->query(query).toString(); + }; + if (mode != IntegerMode) { + result.sysDecimal = getString(QSystemLocale::DecimalPoint); + if (result.sysDecimal.size()) + result.decimal = QStringView{result.sysDecimal}; + } + result.sysGroup = getString(QSystemLocale::GroupSeparator); + if (result.sysGroup.size()) + result.group = QStringView{result.sysGroup}; + result.sysMinus = getString(QSystemLocale::NegativeSign); + if (result.sysMinus.size()) + result.minus = QStringView{result.sysMinus}; + result.sysPlus = getString(QSystemLocale::PositiveSign); + if (result.sysPlus.size()) + result.plus = QStringView{result.sysPlus}; + result.setZero(getString(QSystemLocale::ZeroDigit)); + } +#endif + + return result; +} + +namespace { +// A bit like QStringIterator but rather specialized ... and some of the tokens +// it recognizes aren't single Unicode code-points (but it does map each to a +// single character). +class NumericTokenizer +{ + // TODO: use deterministic finite-state-automata. + // TODO QTBUG-95460: CLDR has Inf/NaN representations per locale. + static constexpr char lettersInfNaN[] = "afin"; // Letters of Inf, NaN + static constexpr auto matchInfNaN = QtPrivate::makeCharacterSetMatch(); + const QStringView m_text; + const QLocaleData::NumericData m_guide; + qsizetype m_index = 0; + const QLocaleData::NumberMode m_mode; + static_assert('+' + 1 == ',' && ',' + 1 == '-' && '-' + 1 == '.'); + char lastMark; // C locale accepts '+' through lastMark. +public: + NumericTokenizer(QStringView text, QLocaleData::NumericData &&guide, + QLocaleData::NumberMode mode) + : m_text(text), m_guide(guide), m_mode(mode), + lastMark(mode == QLocaleData::IntegerMode ? '-' : '.') + { + Q_ASSERT(m_guide.isValid(mode)); + } + bool done() const { return !(m_index < m_text.size()); } + qsizetype index() const { return m_index; } + inline int asBmpDigit(char16_t digit) const; + char nextToken(); +}; + +int NumericTokenizer::asBmpDigit(char16_t digit) const +{ + // If digit *is* a digit, result will be in range 0 through 9; otherwise not. + // Must match qlocale_tools.h's unicodeForDigit() + if (m_guide.zeroUcs != u'\u3007' || digit == m_guide.zeroUcs) + return digit - m_guide.zeroUcs; + + // QTBUG-85409: Suzhou's digits aren't contiguous ! + if (digit == u'\u3020') // U+3020 POSTAL MARK FACE is not a digit. + return -1; + // ... but is followed by digits 1 through 9. + return digit - u'\u3020'; +} + +char NumericTokenizer::nextToken() +{ + // As long as caller stops iterating on a zero return, those don't need to + // keep m_index correctly updated. + Q_ASSERT(!done()); + // Mauls non-letters above 'Z' but we don't care: + const auto asciiLower = [](unsigned char c) { return c >= 'A' ? c | 0x20 : c; }; + const QStringView tail = m_text.sliced(m_index); + const QChar ch = tail.front(); + if (ch == u'\u2212') { + // Special case: match the "proper" minus sign, for all locales. + ++m_index; + return '-'; + } + if (m_guide.isC) { + // "Conversion" to C locale is just a filter: + ++m_index; + if (Q_LIKELY(ch.unicode() < 256)) { + unsigned char ascii = asciiLower(ch.toLatin1()); + if (Q_LIKELY(isAsciiDigit(ascii) || ('+' <= ascii && ascii <= lastMark) + // No caller presently (6.5) passes DoubleStandardMode, + // so !IntegerMode implies scientific, for now. + || (m_mode != QLocaleData::IntegerMode + && matchInfNaN.matches(ascii)) + || (m_mode == QLocaleData::DoubleScientificMode + && ascii == 'e'))) { + return ascii; + } + } + return 0; + } + if (ch.unicode() < 256) { + // Accept the C locale's digits and signs in all locales: + char ascii = asciiLower(ch.toLatin1()); + if (isAsciiDigit(ascii) || ascii == '-' || ascii == '+' + // Also its Inf and NaN letters: + || (m_mode != QLocaleData::IntegerMode && matchInfNaN.matches(ascii))) { + ++m_index; + return ascii; + } + } + + // Other locales may be trickier: + if (tail.startsWith(m_guide.minus)) { + m_index += m_guide.minus.size(); + return '-'; + } + if (tail.startsWith(m_guide.plus)) { + m_index += m_guide.plus.size(); + return '+'; + } + if (!m_guide.group.isEmpty() && tail.startsWith(m_guide.group)) { + m_index += m_guide.group.size(); + return ','; + } + if (m_mode != QLocaleData::IntegerMode && tail.startsWith(m_guide.decimal)) { + m_index += m_guide.decimal.size(); + return '.'; + } + if (m_mode == QLocaleData::DoubleScientificMode + && tail.startsWith(m_guide.exponent, Qt::CaseInsensitive)) { + m_index += m_guide.exponent.size(); + return 'e'; + } + + // Must match qlocale_tools.h's unicodeForDigit() + if (m_guide.zeroLen == 1) { + if (!ch.isSurrogate()) { + const uint gap = asBmpDigit(ch.unicode()); + if (gap < 10u) { + ++m_index; + return '0' + gap; + } + } else if (ch.isHighSurrogate() && tail.size() > 1 && tail.at(1).isLowSurrogate()) { + return 0; + } + } else if (ch.isHighSurrogate()) { + // None of the corner cases below matches a surrogate, so (update + // already and) return early if we don't have a digit. + if (tail.size() > 1) { + QChar low = tail.at(1); + if (low.isLowSurrogate()) { + m_index += 2; + const uint gap = QChar::surrogateToUcs4(ch, low) - m_guide.zeroUcs; + return gap < 10u ? '0' + gap : 0; + } + } + return 0; + } + + // All cases where tail starts with properly-matched surrogate pair + // have been handled by this point. + Q_ASSERT(!(ch.isHighSurrogate() && tail.size() > 1 && tail.at(1).isLowSurrogate())); + + // Weird corner cases follow (code above assumes these match no surrogates). + + // Some locales use a non-breaking space (U+00A0) or its thin version + // (U+202f) for grouping. These look like spaces, so people (and thus some + // of our tests) use a regular space instead and complain if it doesn't + // work. + // Should this be extended generally to any case where group is a space ? + if ((m_guide.group == u"\u00a0" || m_guide.group == u"\u202f") && tail.startsWith(u' ')) { + ++m_index; + return ','; + } + + // Cyrillic has its own E, used by Ukrainian as exponent; but others + // writing Cyrillic may well use that; and Ukrainians might well use E. + // All other Cyrillic locales (officially) use plain ASCII E. + if (m_guide.exponentCyrillic // Only true in scientific float mode. + && (tail.startsWith(u"\u0415", Qt::CaseInsensitive) + || tail.startsWith(u"E", Qt::CaseInsensitive))) { + ++m_index; + return 'e'; + } + + return 0; +} +} // namespace with no name + /* - Converts a number in locale to its representation in the C locale. - Only has to guarantee that a string that is a correct representation of - a number will be converted. + Converts a number in locale representation to the C locale equivalent. + + Only has to guarantee that a string that is a correct representation of a + number will be converted. Checks signs, separators and digits appear in all + the places they should, and nowhere else. + + Returns true precisely if the number appears to be well-formed, modulo + things a parser for C Locale strings (without digit-grouping separators; + they're stripped) will catch. When it returns true, it records (and + '\0'-terminates) the C locale representation in *result. Note: only QString integer-parsing methods have a base parameter (hence need to cope with letters as possible digits); but these are now all routed via @@ -3887,15 +4098,12 @@ QString QLocaleData::applyIntegerFormatting(QString &&numStr, bool negative, int other than 0 through 9. */ bool QLocaleData::numberToCLocale(QStringView s, QLocale::NumberOptions number_options, - CharBuff *result) const + NumberMode mode, CharBuff *result) const { s = s.trimmed(); if (s.size() < 1) return false; - - const QChar *uc = s.data(); - auto length = s.size(); - decltype(length) idx = 0; + NumericTokenizer tokens(s, numericData(mode), mode); // Digit-grouping details (all modes): qsizetype digitsInGroup = 0; @@ -3907,21 +4115,14 @@ bool QLocaleData::numberToCLocale(QStringView s, QLocale::NumberOptions number_o qsizetype exponent_idx = -1; char last = '\0'; - while (idx < length) { - const QStringView in = QStringView(uc + idx, uc[idx].isHighSurrogate() ? 2 : 1); + while (!tokens.done()) { + qsizetype idx = tokens.index(); // before nextToken() advances + char out = tokens.nextToken(); + if (out == 0) + return false; + Q_ASSERT(tokens.index() > idx); // it always *should* advance (except on zero return) - char out = numericToCLocale(in); - if (out == 0) { - // Allow ASCII letters of inf, nan: - if (in.size() != 1) - return false; - char16_t ch = in.front().unicode(); - if (ch > 'n') - return false; - out = int(ch | 0x20); // tolower(), when ch is a letter - if (out != 'a' && out != 'f' && out != 'i' && out != 'n') - return false; - } else if (out == '.') { + if (out == '.') { // Fail if more than one decimal point or point after e if (decpt_idx != -1 || exponent_idx != -1) return false; @@ -3930,24 +4131,23 @@ bool QLocaleData::numberToCLocale(QStringView s, QLocale::NumberOptions number_o exponent_idx = idx; } - if (number_options.testFlag(QLocale::RejectLeadingZeroInExponent)) { - if (exponent_idx != -1 && out == '0' && idx < length - 1) { - // After the exponent there can only be '+', '-' or digits. - // If we find a '0' directly after some non-digit, then that is a leading zero. - if (last < '0' || last > '9') - return false; - } + if (number_options.testFlag(QLocale::RejectLeadingZeroInExponent) + && exponent_idx != -1 && out == '0') { + // After the exponent there can only be '+', '-' or digits. + // If we find a '0' directly after some non-digit, then that is a + // leading zero, acceptable only if it is the whole exponent. + if (!tokens.done() && !isAsciiDigit(last)) + return false; } - if (number_options.testFlag(QLocale::RejectTrailingZeroesAfterDot)) { - // If we've seen a decimal point and the last character after the exponent is 0, then - // that is a trailing zero. - if (decpt_idx >= 0 && idx == exponent_idx && last == '0') - return false; + if (number_options.testFlag(QLocale::RejectTrailingZeroesAfterDot) && decpt_idx >= 0) { + // In a fractional part, a 0 just before the exponent is trailing: + if (idx == exponent_idx && last == '0') + return false; } if (!number_options.testFlag(QLocale::RejectGroupSeparator)) { - if (out >= '0' && out <= '9') { + if (isAsciiDigit(out)) { if (start_of_digits_idx == -1) start_of_digits_idx = idx; ++digitsInGroup; @@ -3970,12 +4170,11 @@ bool QLocaleData::numberToCLocale(QStringView s, QLocale::NumberOptions number_o last_separator_idx = idx; digitsInGroup = 0; - } else if (out == '.' || idx == exponent_idx) { - // Were there enough digits since the last separator? - if (last_separator_idx != -1 && digitsInGroup != m_grouping_least) + } else if (mode != IntegerMode && (out == '.' || idx == exponent_idx) + && last_separator_idx != -1) { + // Were there enough digits since the last group separator? + if (digitsInGroup != m_grouping_least) return false; - // If we saw no separator, should we fail if - // digitsInGroup > m_grouping_top + m_grouping_least ? // stop processing separators last_separator_idx = -1; @@ -3987,29 +4186,23 @@ bool QLocaleData::numberToCLocale(QStringView s, QLocale::NumberOptions number_o last = out; if (out != ',') // Leave group separators out of the result. result->append(out); - idx += in.size(); } - if (!number_options.testFlag(QLocale::RejectGroupSeparator)) { - // group separator post-processing - // did we end in a separator? - if (last_separator_idx + 1 == idx) + if (!number_options.testFlag(QLocale::RejectGroupSeparator) && last_separator_idx != -1) { + // Were there enough digits since the last group separator? + if (digitsInGroup != m_grouping_least) return false; - // Were there enough digits since the last separator? - if (last_separator_idx != -1 && digitsInGroup != m_grouping_least) - return false; - // If we saw no separator, and no decimal point, should we fail if - // digitsInGroup > m_grouping_top + m_grouping_least ? } - if (number_options.testFlag(QLocale::RejectTrailingZeroesAfterDot)) { - // In decimal form, the last character can be a trailing zero if we've seen a decpt. - if (decpt_idx != -1 && exponent_idx == -1 && last == '0') + if (number_options.testFlag(QLocale::RejectTrailingZeroesAfterDot) + && decpt_idx != -1 && exponent_idx == -1) { + // In the fractional part, a final zero is trailing: + if (last == '0') return false; } result->append('\0'); - return idx == length; + return true; } bool QLocaleData::validateChars(QStringView str, NumberMode numMode, QByteArray *buff, @@ -4020,11 +4213,11 @@ bool QLocaleData::validateChars(QStringView str, NumberMode numMode, QByteArray enum { Whole, Fractional, Exponent } state = Whole; const bool scientific = numMode == DoubleScientificMode; + NumericTokenizer tokens(str, numericData(numMode), numMode); char last = '\0'; - for (qsizetype i = 0; i < str.size();) { - const QStringView in = str.mid(i, str.at(i).isHighSurrogate() ? 2 : 1); - char c = numericToCLocale(in); + while (!tokens.done()) { + char c = tokens.nextToken(); if (isAsciiDigit(c)) { switch (state) { @@ -4037,7 +4230,7 @@ bool QLocaleData::validateChars(QStringView str, NumberMode numMode, QByteArray return false; break; case Exponent: - if (last < '0' || last > '9') { + if (!isAsciiDigit(last)) { // This is the first digit in the exponent (there may have beena '+' // or '-' in before). If it's a zero, the exponent is zero-padded. if (c == '0' && (number_options & QLocale::RejectLeadingZeroInExponent)) @@ -4048,54 +4241,54 @@ bool QLocaleData::validateChars(QStringView str, NumberMode numMode, QByteArray } else { switch (c) { - case '.': - // If an integer has a decimal point, it is Invalid. - // A double can only have one, at the end of its whole-number part. - if (numMode == IntegerMode || state != Whole) - return false; - // Even when decDigits is 0, we do allow the decimal point to be - // present - just as long as no digits follow it. - - state = Fractional; - break; - - case '+': - case '-': - // A sign can only appear at the start or after the e of scientific: - if (last != '\0' && !(scientific && last == 'e')) - return false; - break; - - case ',': - // Grouping is only allowed after a digit in the whole-number portion: - if ((number_options & QLocale::RejectGroupSeparator) || state != Whole - || last < '0' || last > '9') { - return false; - } - // We could check grouping sizes are correct, but fixup()s are - // probably better off correcting any misplacement instead. - break; - - case 'e': - // Only one e is allowed and only in scientific: - if (!scientific || state == Exponent) - return false; - state = Exponent; - break; - - default: - // Nothing else can validly appear in a number. - // In fact, numericToCLocale() must have returned 0. If anyone changes - // it to return something else, we probably need to handle it here ! - Q_ASSERT(!c); + case '.': + // If an integer has a decimal point, it is Invalid. + // A double can only have one, at the end of its whole-number part. + if (numMode == IntegerMode || state != Whole) return false; + // Even when decDigits is 0, we do allow the decimal point to be + // present - just as long as no digits follow it. + + state = Fractional; + break; + + case '+': + case '-': + // A sign can only appear at the start or after the e of scientific: + if (last != '\0' && !(scientific && last == 'e')) + return false; + break; + + case ',': + // Grouping is only allowed after a digit in the whole-number portion: + if ((number_options & QLocale::RejectGroupSeparator) || state != Whole + || !isAsciiDigit(last)) { + return false; + } + // We could check grouping sizes are correct, but fixup()s are + // probably better off correcting any misplacement instead. + break; + + case 'e': + // Only one e is allowed and only in scientific: + if (!scientific || state == Exponent) + return false; + state = Exponent; + break; + + default: + // Nothing else can validly appear in a number. + // NumericTokenizer allows letters of "inf" and "nan", but + // validators don't accept those values. + // For anything else, tokens.nextToken() must have returned 0. + Q_ASSERT(!c || c == 'a' || c == 'f' || c == 'i' || c == 'n'); + return false; } } last = c; if (c != ',') // Skip grouping buff->append(c); - i += in.size(); } return true; @@ -4105,7 +4298,7 @@ double QLocaleData::stringToDouble(QStringView str, bool *ok, QLocale::NumberOptions number_options) const { CharBuff buff; - if (!numberToCLocale(str, number_options, &buff)) { + if (!numberToCLocale(str, number_options, DoubleScientificMode, &buff)) { if (ok != nullptr) *ok = false; return 0.0; @@ -4120,7 +4313,7 @@ qlonglong QLocaleData::stringToLongLong(QStringView str, int base, bool *ok, QLocale::NumberOptions number_options) const { CharBuff buff; - if (!numberToCLocale(str, number_options, &buff)) { + if (!numberToCLocale(str, number_options, IntegerMode, &buff)) { if (ok != nullptr) *ok = false; return 0; @@ -4133,7 +4326,7 @@ qulonglong QLocaleData::stringToUnsLongLong(QStringView str, int base, bool *ok, QLocale::NumberOptions number_options) const { CharBuff buff; - if (!numberToCLocale(str, number_options, &buff)) { + if (!numberToCLocale(str, number_options, IntegerMode, &buff)) { if (ok != nullptr) *ok = false; return 0; @@ -4142,57 +4335,40 @@ qulonglong QLocaleData::stringToUnsLongLong(QStringView str, int base, bool *ok, return bytearrayToUnsLongLong(QByteArrayView(buff.constData(), buff.size()), base, ok); } -qlonglong QLocaleData::bytearrayToLongLong(QByteArrayView num, int base, bool *ok) +static bool checkParsed(QByteArrayView num, qsizetype used) { - const qsizetype len = num.size(); - auto [l, used] = qstrntoll(num.data(), len, base); - if (used <= 0) { - if (ok != nullptr) - *ok = false; - return 0; - } + if (used <= 0) + return false; + const qsizetype len = num.size(); if (used < len && num[used] != '\0') { while (used < len && ascii_isspace(num[used])) ++used; } - if (used < len && num[used] != '\0') { + if (used < len && num[used] != '\0') // we stopped at a non-digit character after converting some digits - if (ok != nullptr) - *ok = false; - return 0; - } + return false; - if (ok != nullptr) - *ok = true; - return l; + return true; +} + +qlonglong QLocaleData::bytearrayToLongLong(QByteArrayView num, int base, bool *ok) +{ + auto r = qstrntoll(num.data(), num.size(), base); + const bool parsed = checkParsed(num, r.used); + if (ok) + *ok = parsed; + return parsed ? r.result : 0; } qulonglong QLocaleData::bytearrayToUnsLongLong(QByteArrayView num, int base, bool *ok) { - const qsizetype len = num.size(); - auto [l, used] = qstrntoull(num.data(), len, base); - if (used <= 0) { - if (ok != nullptr) - *ok = false; - return 0; - } - - if (used < len && num[used] != '\0') { - while (used < len && ascii_isspace(num[used])) - ++used; - } - - if (used < len && num[used] != '\0') { - if (ok != nullptr) - *ok = false; - return 0; - } - - if (ok != nullptr) - *ok = true; - return l; + auto r = qstrntoull(num.data(), num.size(), base); + const bool parsed = checkParsed(num, r.used); + if (ok) + *ok = parsed; + return parsed ? r.result : 0; } /*! @@ -4530,7 +4706,7 @@ QLocale QLocale::collation() const \since 4.8 Returns a native name of the language for the locale. For example - "Schwiizertüütsch" for Swiss-German locale. + "Schweizer Hochdeutsch" for the Swiss-German locale. \sa nativeTerritoryName(), languageToString() */ diff --git a/src/corelib/text/qlocale.qdoc b/src/corelib/text/qlocale.qdoc index d86018e1..ece94c67 100644 --- a/src/corelib/text/qlocale.qdoc +++ b/src/corelib/text/qlocale.qdoc @@ -116,7 +116,7 @@ \value Azerbaijani \value Bafia \value [since 5.1] Balinese - \value [since 6.5.3] Baluchi + \value [since 6.6] Baluchi \value Bambara \value [since 5.1] Bamun \value [since 6.0] Bangla @@ -262,7 +262,7 @@ \value [since 5.5] Lezghian \value Limburgish \value Lingala - \value [since 6.5.3] Ligurian + \value [since 6.6] Ligurian \value [since 5.7] LiteraryChinese \value Lithuanian \value [since 5.12] Lojban @@ -348,7 +348,7 @@ \value Quechua \value [since 6.5] Rajasthani \value RhaetoRomance Obsolete, please use Romansh - \value [since 6.5.3] Rohingya + \value [since 6.6] Rohingya \value Romanian \value Romansh \value Rombo @@ -411,7 +411,7 @@ \value [since 6.5] TokiPona \value [since 5.7] TokPisin \value Tongan - \value [since 6.5.3] Torwali + \value [since 6.6] Torwali \value Tsonga \value Tswana \value Turkish @@ -818,7 +818,7 @@ \value GujaratiScript \value GurmukhiScript \value [since 5.1] HangulScript - \value [since 6.5.3] HanifiScript + \value [since 6.6] HanifiScript \value [since 5.1] HanScript \value [since 5.1] HanunooScript \value [since 5.7] HanWithBopomofoScript diff --git a/src/corelib/text/qlocale_data_p.h b/src/corelib/text/qlocale_data_p.h index 5ea4c090..af28622c 100644 --- a/src/corelib/text/qlocale_data_p.h +++ b/src/corelib/text/qlocale_data_p.h @@ -41,14 +41,27 @@ static constexpr TerritoryLanguage ImperialMeasurementSystems[] = { /* Storage for alpha codes with length of up to 4 allowing efficient comparison. */ -struct alignas(uint32_t) AlphaCode { - char code[4]; +struct AlphaCode { + constexpr AlphaCode(char c1 = 0, char c2 = 0, char c3 = 0) + : c1(c2m(c1)), c2(c2m(c2)), c3(c2m(c3)), reserved(0) { } + + uint16_t c1: 5, c2:5, c3: 5, reserved:1; + + bool isValid() const noexcept { return c1 != 0; } + + std::array decode() const { return {m2c(c1), m2c(c2), m2c(c3), 0}; } - bool isValid() const noexcept { return asU32() != 0; } private: + static constexpr uint16_t c2m(char c) { return c ? c - 'a' + 1 : 0; } + static constexpr char m2c (uint16_t c) { return c ? c + 'a' - 1 : 0; } + friend bool operator==(AlphaCode lhs, AlphaCode rhs) noexcept - { return lhs.asU32() == rhs.asU32(); } - uint32_t asU32() const noexcept { return qFromUnaligned(code); } + { + static_assert(std::has_unique_object_representations_v, + "memcmp() cannot be used to implement equality on " + "types that don't have unique object representations"); + return std::memcmp(&lhs, &rhs, sizeof(lhs)) == 0; + } }; struct LanguageCodeEntry { @@ -61,7 +74,7 @@ struct LanguageCodeEntry { // GENERATED PART STARTS HERE /* - This part of the file was generated on 2023-08-03 from the + This part of the file was generated on 2023-07-27 from the Common Locale Data Repository v43 http://www.unicode.org/cldr/ @@ -5782,347 +5795,347 @@ static constexpr quint16 territory_name_index[] = { }; constexpr std::array languageCodeList { - LanguageCodeEntry {{}, {{'u', 'n', 'd'}}, {{'u', 'n', 'd'}}, {{'u', 'n', 'd'}}}, // AnyLanguage - LanguageCodeEntry {{}, {{'u', 'n', 'd'}}, {{'u', 'n', 'd'}}, {{'u', 'n', 'd'}}}, // C - LanguageCodeEntry {{{'a', 'b'}}, {{'a', 'b', 'k'}}, {{'a', 'b', 'k'}}, {{'a', 'b', 'k'}}}, // Abkhazian - LanguageCodeEntry {{{'a', 'a'}}, {{'a', 'a', 'r'}}, {{'a', 'a', 'r'}}, {{'a', 'a', 'r'}}}, // Afar - LanguageCodeEntry {{{'a', 'f'}}, {{'a', 'f', 'r'}}, {{'a', 'f', 'r'}}, {{'a', 'f', 'r'}}}, // Afrikaans - LanguageCodeEntry {{}, {}, {}, {{'a', 'g', 'q'}}}, // Aghem - LanguageCodeEntry {{{'a', 'k'}}, {{'a', 'k', 'a'}}, {{'a', 'k', 'a'}}, {{'a', 'k', 'a'}}}, // Akan - LanguageCodeEntry {{}, {{'a', 'k', 'k'}}, {{'a', 'k', 'k'}}, {{'a', 'k', 'k'}}}, // Akkadian - LanguageCodeEntry {{}, {}, {}, {{'b', 's', 's'}}}, // Akoose - LanguageCodeEntry {{{'s', 'q'}}, {{'a', 'l', 'b'}}, {{'s', 'q', 'i'}}, {{'s', 'q', 'i'}}}, // Albanian - LanguageCodeEntry {{}, {}, {}, {{'a', 's', 'e'}}}, // American Sign Language - LanguageCodeEntry {{{'a', 'm'}}, {{'a', 'm', 'h'}}, {{'a', 'm', 'h'}}, {{'a', 'm', 'h'}}}, // Amharic - LanguageCodeEntry {{}, {{'e', 'g', 'y'}}, {{'e', 'g', 'y'}}, {{'e', 'g', 'y'}}}, // Ancient Egyptian - LanguageCodeEntry {{}, {{'g', 'r', 'c'}}, {{'g', 'r', 'c'}}, {{'g', 'r', 'c'}}}, // Ancient Greek - LanguageCodeEntry {{{'a', 'r'}}, {{'a', 'r', 'a'}}, {{'a', 'r', 'a'}}, {{'a', 'r', 'a'}}}, // Arabic - LanguageCodeEntry {{{'a', 'n'}}, {{'a', 'r', 'g'}}, {{'a', 'r', 'g'}}, {{'a', 'r', 'g'}}}, // Aragonese - LanguageCodeEntry {{}, {{'a', 'r', 'c'}}, {{'a', 'r', 'c'}}, {{'a', 'r', 'c'}}}, // Aramaic - LanguageCodeEntry {{{'h', 'y'}}, {{'a', 'r', 'm'}}, {{'h', 'y', 'e'}}, {{'h', 'y', 'e'}}}, // Armenian - LanguageCodeEntry {{{'a', 's'}}, {{'a', 's', 'm'}}, {{'a', 's', 'm'}}, {{'a', 's', 'm'}}}, // Assamese - LanguageCodeEntry {{}, {{'a', 's', 't'}}, {{'a', 's', 't'}}, {{'a', 's', 't'}}}, // Asturian - LanguageCodeEntry {{}, {}, {}, {{'a', 's', 'a'}}}, // Asu - LanguageCodeEntry {{}, {}, {}, {{'c', 'c', 'h'}}}, // Atsam - LanguageCodeEntry {{{'a', 'v'}}, {{'a', 'v', 'a'}}, {{'a', 'v', 'a'}}, {{'a', 'v', 'a'}}}, // Avaric - LanguageCodeEntry {{{'a', 'e'}}, {{'a', 'v', 'e'}}, {{'a', 'v', 'e'}}, {{'a', 'v', 'e'}}}, // Avestan - LanguageCodeEntry {{{'a', 'y'}}, {{'a', 'y', 'm'}}, {{'a', 'y', 'm'}}, {{'a', 'y', 'm'}}}, // Aymara - LanguageCodeEntry {{{'a', 'z'}}, {{'a', 'z', 'e'}}, {{'a', 'z', 'e'}}, {{'a', 'z', 'e'}}}, // Azerbaijani - LanguageCodeEntry {{}, {}, {}, {{'k', 's', 'f'}}}, // Bafia - LanguageCodeEntry {{}, {{'b', 'a', 'n'}}, {{'b', 'a', 'n'}}, {{'b', 'a', 'n'}}}, // Balinese - LanguageCodeEntry {{{'b', 'm'}}, {{'b', 'a', 'm'}}, {{'b', 'a', 'm'}}, {{'b', 'a', 'm'}}}, // Bambara - LanguageCodeEntry {{}, {}, {}, {{'b', 'a', 'x'}}}, // Bamun - LanguageCodeEntry {{{'b', 'n'}}, {{'b', 'e', 'n'}}, {{'b', 'e', 'n'}}, {{'b', 'e', 'n'}}}, // Bangla - LanguageCodeEntry {{}, {{'b', 'a', 's'}}, {{'b', 'a', 's'}}, {{'b', 'a', 's'}}}, // Basaa - LanguageCodeEntry {{{'b', 'a'}}, {{'b', 'a', 'k'}}, {{'b', 'a', 'k'}}, {{'b', 'a', 'k'}}}, // Bashkir - LanguageCodeEntry {{{'e', 'u'}}, {{'b', 'a', 'q'}}, {{'e', 'u', 's'}}, {{'e', 'u', 's'}}}, // Basque - LanguageCodeEntry {{}, {}, {}, {{'b', 'b', 'c'}}}, // Batak Toba - LanguageCodeEntry {{{'b', 'e'}}, {{'b', 'e', 'l'}}, {{'b', 'e', 'l'}}, {{'b', 'e', 'l'}}}, // Belarusian - LanguageCodeEntry {{}, {{'b', 'e', 'm'}}, {{'b', 'e', 'm'}}, {{'b', 'e', 'm'}}}, // Bemba - LanguageCodeEntry {{}, {}, {}, {{'b', 'e', 'z'}}}, // Bena - LanguageCodeEntry {{}, {{'b', 'h', 'o'}}, {{'b', 'h', 'o'}}, {{'b', 'h', 'o'}}}, // Bhojpuri - LanguageCodeEntry {{{'b', 'i'}}, {{'b', 'i', 's'}}, {{'b', 'i', 's'}}, {{'b', 'i', 's'}}}, // Bislama - LanguageCodeEntry {{}, {{'b', 'y', 'n'}}, {{'b', 'y', 'n'}}, {{'b', 'y', 'n'}}}, // Blin - LanguageCodeEntry {{}, {}, {}, {{'b', 'r', 'x'}}}, // Bodo - LanguageCodeEntry {{{'b', 's'}}, {{'b', 'o', 's'}}, {{'b', 'o', 's'}}, {{'b', 'o', 's'}}}, // Bosnian - LanguageCodeEntry {{{'b', 'r'}}, {{'b', 'r', 'e'}}, {{'b', 'r', 'e'}}, {{'b', 'r', 'e'}}}, // Breton - LanguageCodeEntry {{}, {{'b', 'u', 'g'}}, {{'b', 'u', 'g'}}, {{'b', 'u', 'g'}}}, // Buginese - LanguageCodeEntry {{{'b', 'g'}}, {{'b', 'u', 'l'}}, {{'b', 'u', 'l'}}, {{'b', 'u', 'l'}}}, // Bulgarian - LanguageCodeEntry {{{'m', 'y'}}, {{'b', 'u', 'r'}}, {{'m', 'y', 'a'}}, {{'m', 'y', 'a'}}}, // Burmese - LanguageCodeEntry {{}, {}, {}, {{'y', 'u', 'e'}}}, // Cantonese - LanguageCodeEntry {{{'c', 'a'}}, {{'c', 'a', 't'}}, {{'c', 'a', 't'}}, {{'c', 'a', 't'}}}, // Catalan - LanguageCodeEntry {{}, {{'c', 'e', 'b'}}, {{'c', 'e', 'b'}}, {{'c', 'e', 'b'}}}, // Cebuano - LanguageCodeEntry {{}, {}, {}, {{'t', 'z', 'm'}}}, // Central Atlas Tamazight - LanguageCodeEntry {{}, {}, {}, {{'c', 'k', 'b'}}}, // Central Kurdish - LanguageCodeEntry {{}, {}, {}, {{'c', 'c', 'p'}}}, // Chakma - LanguageCodeEntry {{{'c', 'h'}}, {{'c', 'h', 'a'}}, {{'c', 'h', 'a'}}, {{'c', 'h', 'a'}}}, // Chamorro - LanguageCodeEntry {{{'c', 'e'}}, {{'c', 'h', 'e'}}, {{'c', 'h', 'e'}}, {{'c', 'h', 'e'}}}, // Chechen - LanguageCodeEntry {{}, {{'c', 'h', 'r'}}, {{'c', 'h', 'r'}}, {{'c', 'h', 'r'}}}, // Cherokee - LanguageCodeEntry {{}, {}, {}, {{'c', 'i', 'c'}}}, // Chickasaw - LanguageCodeEntry {{}, {}, {}, {{'c', 'g', 'g'}}}, // Chiga - LanguageCodeEntry {{{'z', 'h'}}, {{'c', 'h', 'i'}}, {{'z', 'h', 'o'}}, {{'z', 'h', 'o'}}}, // Chinese - LanguageCodeEntry {{{'c', 'u'}}, {{'c', 'h', 'u'}}, {{'c', 'h', 'u'}}, {{'c', 'h', 'u'}}}, // Church - LanguageCodeEntry {{{'c', 'v'}}, {{'c', 'h', 'v'}}, {{'c', 'h', 'v'}}, {{'c', 'h', 'v'}}}, // Chuvash - LanguageCodeEntry {{}, {}, {}, {{'k', 's', 'h'}}}, // Colognian - LanguageCodeEntry {{}, {{'c', 'o', 'p'}}, {{'c', 'o', 'p'}}, {{'c', 'o', 'p'}}}, // Coptic - LanguageCodeEntry {{{'k', 'w'}}, {{'c', 'o', 'r'}}, {{'c', 'o', 'r'}}, {{'c', 'o', 'r'}}}, // Cornish - LanguageCodeEntry {{{'c', 'o'}}, {{'c', 'o', 's'}}, {{'c', 'o', 's'}}, {{'c', 'o', 's'}}}, // Corsican - LanguageCodeEntry {{{'c', 'r'}}, {{'c', 'r', 'e'}}, {{'c', 'r', 'e'}}, {{'c', 'r', 'e'}}}, // Cree - LanguageCodeEntry {{{'h', 'r'}}, {{'h', 'r', 'v'}}, {{'h', 'r', 'v'}}, {{'h', 'r', 'v'}}}, // Croatian - LanguageCodeEntry {{{'c', 's'}}, {{'c', 'z', 'e'}}, {{'c', 'e', 's'}}, {{'c', 'e', 's'}}}, // Czech - LanguageCodeEntry {{{'d', 'a'}}, {{'d', 'a', 'n'}}, {{'d', 'a', 'n'}}, {{'d', 'a', 'n'}}}, // Danish - LanguageCodeEntry {{{'d', 'v'}}, {{'d', 'i', 'v'}}, {{'d', 'i', 'v'}}, {{'d', 'i', 'v'}}}, // Divehi - LanguageCodeEntry {{}, {{'d', 'o', 'i'}}, {{'d', 'o', 'i'}}, {{'d', 'o', 'i'}}}, // Dogri - LanguageCodeEntry {{}, {{'d', 'u', 'a'}}, {{'d', 'u', 'a'}}, {{'d', 'u', 'a'}}}, // Duala - LanguageCodeEntry {{{'n', 'l'}}, {{'d', 'u', 't'}}, {{'n', 'l', 'd'}}, {{'n', 'l', 'd'}}}, // Dutch - LanguageCodeEntry {{{'d', 'z'}}, {{'d', 'z', 'o'}}, {{'d', 'z', 'o'}}, {{'d', 'z', 'o'}}}, // Dzongkha - LanguageCodeEntry {{}, {}, {}, {{'e', 'b', 'u'}}}, // Embu - LanguageCodeEntry {{{'e', 'n'}}, {{'e', 'n', 'g'}}, {{'e', 'n', 'g'}}, {{'e', 'n', 'g'}}}, // English - LanguageCodeEntry {{}, {{'m', 'y', 'v'}}, {{'m', 'y', 'v'}}, {{'m', 'y', 'v'}}}, // Erzya - LanguageCodeEntry {{{'e', 'o'}}, {{'e', 'p', 'o'}}, {{'e', 'p', 'o'}}, {{'e', 'p', 'o'}}}, // Esperanto - LanguageCodeEntry {{{'e', 't'}}, {{'e', 's', 't'}}, {{'e', 's', 't'}}, {{'e', 's', 't'}}}, // Estonian - LanguageCodeEntry {{{'e', 'e'}}, {{'e', 'w', 'e'}}, {{'e', 'w', 'e'}}, {{'e', 'w', 'e'}}}, // Ewe - LanguageCodeEntry {{}, {{'e', 'w', 'o'}}, {{'e', 'w', 'o'}}, {{'e', 'w', 'o'}}}, // Ewondo - LanguageCodeEntry {{{'f', 'o'}}, {{'f', 'a', 'o'}}, {{'f', 'a', 'o'}}, {{'f', 'a', 'o'}}}, // Faroese - LanguageCodeEntry {{{'f', 'j'}}, {{'f', 'i', 'j'}}, {{'f', 'i', 'j'}}, {{'f', 'i', 'j'}}}, // Fijian - LanguageCodeEntry {{}, {{'f', 'i', 'l'}}, {{'f', 'i', 'l'}}, {{'f', 'i', 'l'}}}, // Filipino - LanguageCodeEntry {{{'f', 'i'}}, {{'f', 'i', 'n'}}, {{'f', 'i', 'n'}}, {{'f', 'i', 'n'}}}, // Finnish - LanguageCodeEntry {{{'f', 'r'}}, {{'f', 'r', 'e'}}, {{'f', 'r', 'a'}}, {{'f', 'r', 'a'}}}, // French - LanguageCodeEntry {{}, {{'f', 'u', 'r'}}, {{'f', 'u', 'r'}}, {{'f', 'u', 'r'}}}, // Friulian - LanguageCodeEntry {{{'f', 'f'}}, {{'f', 'u', 'l'}}, {{'f', 'u', 'l'}}, {{'f', 'u', 'l'}}}, // Fulah - LanguageCodeEntry {{{'g', 'd'}}, {{'g', 'l', 'a'}}, {{'g', 'l', 'a'}}, {{'g', 'l', 'a'}}}, // Gaelic - LanguageCodeEntry {{}, {{'g', 'a', 'a'}}, {{'g', 'a', 'a'}}, {{'g', 'a', 'a'}}}, // Ga - LanguageCodeEntry {{{'g', 'l'}}, {{'g', 'l', 'g'}}, {{'g', 'l', 'g'}}, {{'g', 'l', 'g'}}}, // Galician - LanguageCodeEntry {{{'l', 'g'}}, {{'l', 'u', 'g'}}, {{'l', 'u', 'g'}}, {{'l', 'u', 'g'}}}, // Ganda - LanguageCodeEntry {{}, {{'g', 'e', 'z'}}, {{'g', 'e', 'z'}}, {{'g', 'e', 'z'}}}, // Geez - LanguageCodeEntry {{{'k', 'a'}}, {{'g', 'e', 'o'}}, {{'k', 'a', 't'}}, {{'k', 'a', 't'}}}, // Georgian - LanguageCodeEntry {{{'d', 'e'}}, {{'g', 'e', 'r'}}, {{'d', 'e', 'u'}}, {{'d', 'e', 'u'}}}, // German - LanguageCodeEntry {{}, {{'g', 'o', 't'}}, {{'g', 'o', 't'}}, {{'g', 'o', 't'}}}, // Gothic - LanguageCodeEntry {{{'e', 'l'}}, {{'g', 'r', 'e'}}, {{'e', 'l', 'l'}}, {{'e', 'l', 'l'}}}, // Greek - LanguageCodeEntry {{{'g', 'n'}}, {{'g', 'r', 'n'}}, {{'g', 'r', 'n'}}, {{'g', 'r', 'n'}}}, // Guarani - LanguageCodeEntry {{{'g', 'u'}}, {{'g', 'u', 'j'}}, {{'g', 'u', 'j'}}, {{'g', 'u', 'j'}}}, // Gujarati - LanguageCodeEntry {{}, {}, {}, {{'g', 'u', 'z'}}}, // Gusii - LanguageCodeEntry {{{'h', 't'}}, {{'h', 'a', 't'}}, {{'h', 'a', 't'}}, {{'h', 'a', 't'}}}, // Haitian - LanguageCodeEntry {{{'h', 'a'}}, {{'h', 'a', 'u'}}, {{'h', 'a', 'u'}}, {{'h', 'a', 'u'}}}, // Hausa - LanguageCodeEntry {{}, {{'h', 'a', 'w'}}, {{'h', 'a', 'w'}}, {{'h', 'a', 'w'}}}, // Hawaiian - LanguageCodeEntry {{{'h', 'e'}}, {{'h', 'e', 'b'}}, {{'h', 'e', 'b'}}, {{'h', 'e', 'b'}}}, // Hebrew - LanguageCodeEntry {{{'h', 'z'}}, {{'h', 'e', 'r'}}, {{'h', 'e', 'r'}}, {{'h', 'e', 'r'}}}, // Herero - LanguageCodeEntry {{{'h', 'i'}}, {{'h', 'i', 'n'}}, {{'h', 'i', 'n'}}, {{'h', 'i', 'n'}}}, // Hindi - LanguageCodeEntry {{{'h', 'o'}}, {{'h', 'm', 'o'}}, {{'h', 'm', 'o'}}, {{'h', 'm', 'o'}}}, // Hiri Motu - LanguageCodeEntry {{{'h', 'u'}}, {{'h', 'u', 'n'}}, {{'h', 'u', 'n'}}, {{'h', 'u', 'n'}}}, // Hungarian - LanguageCodeEntry {{{'i', 's'}}, {{'i', 'c', 'e'}}, {{'i', 's', 'l'}}, {{'i', 's', 'l'}}}, // Icelandic - LanguageCodeEntry {{{'i', 'o'}}, {{'i', 'd', 'o'}}, {{'i', 'd', 'o'}}, {{'i', 'd', 'o'}}}, // Ido - LanguageCodeEntry {{{'i', 'g'}}, {{'i', 'b', 'o'}}, {{'i', 'b', 'o'}}, {{'i', 'b', 'o'}}}, // Igbo - LanguageCodeEntry {{}, {{'s', 'm', 'n'}}, {{'s', 'm', 'n'}}, {{'s', 'm', 'n'}}}, // Inari Sami - LanguageCodeEntry {{{'i', 'd'}}, {{'i', 'n', 'd'}}, {{'i', 'n', 'd'}}, {{'i', 'n', 'd'}}}, // Indonesian - LanguageCodeEntry {{}, {{'i', 'n', 'h'}}, {{'i', 'n', 'h'}}, {{'i', 'n', 'h'}}}, // Ingush - LanguageCodeEntry {{{'i', 'a'}}, {{'i', 'n', 'a'}}, {{'i', 'n', 'a'}}, {{'i', 'n', 'a'}}}, // Interlingua - LanguageCodeEntry {{{'i', 'e'}}, {{'i', 'l', 'e'}}, {{'i', 'l', 'e'}}, {{'i', 'l', 'e'}}}, // Interlingue - LanguageCodeEntry {{{'i', 'u'}}, {{'i', 'k', 'u'}}, {{'i', 'k', 'u'}}, {{'i', 'k', 'u'}}}, // Inuktitut - LanguageCodeEntry {{{'i', 'k'}}, {{'i', 'p', 'k'}}, {{'i', 'p', 'k'}}, {{'i', 'p', 'k'}}}, // Inupiaq - LanguageCodeEntry {{{'g', 'a'}}, {{'g', 'l', 'e'}}, {{'g', 'l', 'e'}}, {{'g', 'l', 'e'}}}, // Irish - LanguageCodeEntry {{{'i', 't'}}, {{'i', 't', 'a'}}, {{'i', 't', 'a'}}, {{'i', 't', 'a'}}}, // Italian - LanguageCodeEntry {{{'j', 'a'}}, {{'j', 'p', 'n'}}, {{'j', 'p', 'n'}}, {{'j', 'p', 'n'}}}, // Japanese - LanguageCodeEntry {{{'j', 'v'}}, {{'j', 'a', 'v'}}, {{'j', 'a', 'v'}}, {{'j', 'a', 'v'}}}, // Javanese - LanguageCodeEntry {{}, {}, {}, {{'k', 'a', 'j'}}}, // Jju - LanguageCodeEntry {{}, {}, {}, {{'d', 'y', 'o'}}}, // Jola Fonyi - LanguageCodeEntry {{}, {}, {}, {{'k', 'e', 'a'}}}, // Kabuverdianu - LanguageCodeEntry {{}, {{'k', 'a', 'b'}}, {{'k', 'a', 'b'}}, {{'k', 'a', 'b'}}}, // Kabyle - LanguageCodeEntry {{}, {}, {}, {{'k', 'k', 'j'}}}, // Kako - LanguageCodeEntry {{{'k', 'l'}}, {{'k', 'a', 'l'}}, {{'k', 'a', 'l'}}, {{'k', 'a', 'l'}}}, // Kalaallisut - LanguageCodeEntry {{}, {}, {}, {{'k', 'l', 'n'}}}, // Kalenjin - LanguageCodeEntry {{}, {{'k', 'a', 'm'}}, {{'k', 'a', 'm'}}, {{'k', 'a', 'm'}}}, // Kamba - LanguageCodeEntry {{{'k', 'n'}}, {{'k', 'a', 'n'}}, {{'k', 'a', 'n'}}, {{'k', 'a', 'n'}}}, // Kannada - LanguageCodeEntry {{{'k', 'r'}}, {{'k', 'a', 'u'}}, {{'k', 'a', 'u'}}, {{'k', 'a', 'u'}}}, // Kanuri - LanguageCodeEntry {{{'k', 's'}}, {{'k', 'a', 's'}}, {{'k', 'a', 's'}}, {{'k', 'a', 's'}}}, // Kashmiri - LanguageCodeEntry {{{'k', 'k'}}, {{'k', 'a', 'z'}}, {{'k', 'a', 'z'}}, {{'k', 'a', 'z'}}}, // Kazakh - LanguageCodeEntry {{}, {}, {}, {{'k', 'e', 'n'}}}, // Kenyang - LanguageCodeEntry {{{'k', 'm'}}, {{'k', 'h', 'm'}}, {{'k', 'h', 'm'}}, {{'k', 'h', 'm'}}}, // Khmer - LanguageCodeEntry {{}, {}, {}, {{'q', 'u', 'c'}}}, // Kiche - LanguageCodeEntry {{{'k', 'i'}}, {{'k', 'i', 'k'}}, {{'k', 'i', 'k'}}, {{'k', 'i', 'k'}}}, // Kikuyu - LanguageCodeEntry {{{'r', 'w'}}, {{'k', 'i', 'n'}}, {{'k', 'i', 'n'}}, {{'k', 'i', 'n'}}}, // Kinyarwanda - LanguageCodeEntry {{{'k', 'v'}}, {{'k', 'o', 'm'}}, {{'k', 'o', 'm'}}, {{'k', 'o', 'm'}}}, // Komi - LanguageCodeEntry {{{'k', 'g'}}, {{'k', 'o', 'n'}}, {{'k', 'o', 'n'}}, {{'k', 'o', 'n'}}}, // Kongo - LanguageCodeEntry {{}, {{'k', 'o', 'k'}}, {{'k', 'o', 'k'}}, {{'k', 'o', 'k'}}}, // Konkani - LanguageCodeEntry {{{'k', 'o'}}, {{'k', 'o', 'r'}}, {{'k', 'o', 'r'}}, {{'k', 'o', 'r'}}}, // Korean - LanguageCodeEntry {{}, {}, {}, {{'k', 'f', 'o'}}}, // Koro - LanguageCodeEntry {{}, {}, {}, {{'s', 'e', 's'}}}, // Koyraboro Senni - LanguageCodeEntry {{}, {}, {}, {{'k', 'h', 'q'}}}, // Koyra Chiini - LanguageCodeEntry {{}, {{'k', 'p', 'e'}}, {{'k', 'p', 'e'}}, {{'k', 'p', 'e'}}}, // Kpelle - LanguageCodeEntry {{{'k', 'j'}}, {{'k', 'u', 'a'}}, {{'k', 'u', 'a'}}, {{'k', 'u', 'a'}}}, // Kuanyama - LanguageCodeEntry {{{'k', 'u'}}, {{'k', 'u', 'r'}}, {{'k', 'u', 'r'}}, {{'k', 'u', 'r'}}}, // Kurdish - LanguageCodeEntry {{}, {}, {}, {{'n', 'm', 'g'}}}, // Kwasio - LanguageCodeEntry {{{'k', 'y'}}, {{'k', 'i', 'r'}}, {{'k', 'i', 'r'}}, {{'k', 'i', 'r'}}}, // Kyrgyz - LanguageCodeEntry {{}, {}, {}, {{'l', 'k', 't'}}}, // Lakota - LanguageCodeEntry {{}, {}, {}, {{'l', 'a', 'g'}}}, // Langi - LanguageCodeEntry {{{'l', 'o'}}, {{'l', 'a', 'o'}}, {{'l', 'a', 'o'}}, {{'l', 'a', 'o'}}}, // Lao - LanguageCodeEntry {{{'l', 'a'}}, {{'l', 'a', 't'}}, {{'l', 'a', 't'}}, {{'l', 'a', 't'}}}, // Latin - LanguageCodeEntry {{{'l', 'v'}}, {{'l', 'a', 'v'}}, {{'l', 'a', 'v'}}, {{'l', 'a', 'v'}}}, // Latvian - LanguageCodeEntry {{}, {{'l', 'e', 'z'}}, {{'l', 'e', 'z'}}, {{'l', 'e', 'z'}}}, // Lezghian - LanguageCodeEntry {{{'l', 'i'}}, {{'l', 'i', 'm'}}, {{'l', 'i', 'm'}}, {{'l', 'i', 'm'}}}, // Limburgish - LanguageCodeEntry {{{'l', 'n'}}, {{'l', 'i', 'n'}}, {{'l', 'i', 'n'}}, {{'l', 'i', 'n'}}}, // Lingala - LanguageCodeEntry {{}, {}, {}, {{'l', 'z', 'h'}}}, // Literary Chinese - LanguageCodeEntry {{{'l', 't'}}, {{'l', 'i', 't'}}, {{'l', 'i', 't'}}, {{'l', 'i', 't'}}}, // Lithuanian - LanguageCodeEntry {{}, {{'j', 'b', 'o'}}, {{'j', 'b', 'o'}}, {{'j', 'b', 'o'}}}, // Lojban - LanguageCodeEntry {{}, {{'d', 's', 'b'}}, {{'d', 's', 'b'}}, {{'d', 's', 'b'}}}, // Lower Sorbian - LanguageCodeEntry {{}, {{'n', 'd', 's'}}, {{'n', 'd', 's'}}, {{'n', 'd', 's'}}}, // Low German - LanguageCodeEntry {{{'l', 'u'}}, {{'l', 'u', 'b'}}, {{'l', 'u', 'b'}}, {{'l', 'u', 'b'}}}, // Luba Katanga - LanguageCodeEntry {{}, {{'s', 'm', 'j'}}, {{'s', 'm', 'j'}}, {{'s', 'm', 'j'}}}, // Lule Sami - LanguageCodeEntry {{}, {{'l', 'u', 'o'}}, {{'l', 'u', 'o'}}, {{'l', 'u', 'o'}}}, // Luo - LanguageCodeEntry {{{'l', 'b'}}, {{'l', 't', 'z'}}, {{'l', 't', 'z'}}, {{'l', 't', 'z'}}}, // Luxembourgish - LanguageCodeEntry {{}, {}, {}, {{'l', 'u', 'y'}}}, // Luyia - LanguageCodeEntry {{{'m', 'k'}}, {{'m', 'a', 'c'}}, {{'m', 'k', 'd'}}, {{'m', 'k', 'd'}}}, // Macedonian - LanguageCodeEntry {{}, {}, {}, {{'j', 'm', 'c'}}}, // Machame - LanguageCodeEntry {{}, {{'m', 'a', 'i'}}, {{'m', 'a', 'i'}}, {{'m', 'a', 'i'}}}, // Maithili - LanguageCodeEntry {{}, {}, {}, {{'m', 'g', 'h'}}}, // Makhuwa Meetto - LanguageCodeEntry {{}, {}, {}, {{'k', 'd', 'e'}}}, // Makonde - LanguageCodeEntry {{{'m', 'g'}}, {{'m', 'l', 'g'}}, {{'m', 'l', 'g'}}, {{'m', 'l', 'g'}}}, // Malagasy - LanguageCodeEntry {{{'m', 'l'}}, {{'m', 'a', 'l'}}, {{'m', 'a', 'l'}}, {{'m', 'a', 'l'}}}, // Malayalam - LanguageCodeEntry {{{'m', 's'}}, {{'m', 'a', 'y'}}, {{'m', 's', 'a'}}, {{'m', 's', 'a'}}}, // Malay - LanguageCodeEntry {{{'m', 't'}}, {{'m', 'l', 't'}}, {{'m', 'l', 't'}}, {{'m', 'l', 't'}}}, // Maltese - LanguageCodeEntry {{}, {{'m', 'a', 'n'}}, {{'m', 'a', 'n'}}, {{'m', 'a', 'n'}}}, // Mandingo - LanguageCodeEntry {{}, {{'m', 'n', 'i'}}, {{'m', 'n', 'i'}}, {{'m', 'n', 'i'}}}, // Manipuri - LanguageCodeEntry {{{'g', 'v'}}, {{'g', 'l', 'v'}}, {{'g', 'l', 'v'}}, {{'g', 'l', 'v'}}}, // Manx - LanguageCodeEntry {{{'m', 'i'}}, {{'m', 'a', 'o'}}, {{'m', 'r', 'i'}}, {{'m', 'r', 'i'}}}, // Maori - LanguageCodeEntry {{}, {{'a', 'r', 'n'}}, {{'a', 'r', 'n'}}, {{'a', 'r', 'n'}}}, // Mapuche - LanguageCodeEntry {{{'m', 'r'}}, {{'m', 'a', 'r'}}, {{'m', 'a', 'r'}}, {{'m', 'a', 'r'}}}, // Marathi - LanguageCodeEntry {{{'m', 'h'}}, {{'m', 'a', 'h'}}, {{'m', 'a', 'h'}}, {{'m', 'a', 'h'}}}, // Marshallese - LanguageCodeEntry {{}, {{'m', 'a', 's'}}, {{'m', 'a', 's'}}, {{'m', 'a', 's'}}}, // Masai - LanguageCodeEntry {{}, {}, {}, {{'m', 'z', 'n'}}}, // Mazanderani - LanguageCodeEntry {{}, {{'m', 'e', 'n'}}, {{'m', 'e', 'n'}}, {{'m', 'e', 'n'}}}, // Mende - LanguageCodeEntry {{}, {}, {}, {{'m', 'e', 'r'}}}, // Meru - LanguageCodeEntry {{}, {}, {}, {{'m', 'g', 'o'}}}, // Meta - LanguageCodeEntry {{}, {{'m', 'o', 'h'}}, {{'m', 'o', 'h'}}, {{'m', 'o', 'h'}}}, // Mohawk - LanguageCodeEntry {{{'m', 'n'}}, {{'m', 'o', 'n'}}, {{'m', 'o', 'n'}}, {{'m', 'o', 'n'}}}, // Mongolian - LanguageCodeEntry {{}, {}, {}, {{'m', 'f', 'e'}}}, // Morisyen - LanguageCodeEntry {{}, {}, {}, {{'m', 'u', 'a'}}}, // Mundang - LanguageCodeEntry {{}, {{'m', 'u', 's'}}, {{'m', 'u', 's'}}, {{'m', 'u', 's'}}}, // Muscogee - LanguageCodeEntry {{}, {}, {}, {{'n', 'a', 'q'}}}, // Nama - LanguageCodeEntry {{{'n', 'a'}}, {{'n', 'a', 'u'}}, {{'n', 'a', 'u'}}, {{'n', 'a', 'u'}}}, // Nauru - LanguageCodeEntry {{{'n', 'v'}}, {{'n', 'a', 'v'}}, {{'n', 'a', 'v'}}, {{'n', 'a', 'v'}}}, // Navajo - LanguageCodeEntry {{{'n', 'g'}}, {{'n', 'd', 'o'}}, {{'n', 'd', 'o'}}, {{'n', 'd', 'o'}}}, // Ndonga - LanguageCodeEntry {{{'n', 'e'}}, {{'n', 'e', 'p'}}, {{'n', 'e', 'p'}}, {{'n', 'e', 'p'}}}, // Nepali - LanguageCodeEntry {{}, {{'n', 'e', 'w'}}, {{'n', 'e', 'w'}}, {{'n', 'e', 'w'}}}, // Newari - LanguageCodeEntry {{}, {}, {}, {{'n', 'n', 'h'}}}, // Ngiemboon - LanguageCodeEntry {{}, {}, {}, {{'j', 'g', 'o'}}}, // Ngomba - LanguageCodeEntry {{}, {}, {}, {{'p', 'c', 'm'}}}, // Nigerian Pidgin - LanguageCodeEntry {{}, {{'n', 'q', 'o'}}, {{'n', 'q', 'o'}}, {{'n', 'q', 'o'}}}, // Nko - LanguageCodeEntry {{}, {}, {}, {{'l', 'r', 'c'}}}, // Northern Luri - LanguageCodeEntry {{{'s', 'e'}}, {{'s', 'm', 'e'}}, {{'s', 'm', 'e'}}, {{'s', 'm', 'e'}}}, // Northern Sami - LanguageCodeEntry {{}, {{'n', 's', 'o'}}, {{'n', 's', 'o'}}, {{'n', 's', 'o'}}}, // Northern Sotho - LanguageCodeEntry {{{'n', 'd'}}, {{'n', 'd', 'e'}}, {{'n', 'd', 'e'}}, {{'n', 'd', 'e'}}}, // North Ndebele - LanguageCodeEntry {{{'n', 'b'}}, {{'n', 'o', 'b'}}, {{'n', 'o', 'b'}}, {{'n', 'o', 'b'}}}, // Norwegian Bokmal - LanguageCodeEntry {{{'n', 'n'}}, {{'n', 'n', 'o'}}, {{'n', 'n', 'o'}}, {{'n', 'n', 'o'}}}, // Norwegian Nynorsk - LanguageCodeEntry {{}, {}, {}, {{'n', 'u', 's'}}}, // Nuer - LanguageCodeEntry {{{'n', 'y'}}, {{'n', 'y', 'a'}}, {{'n', 'y', 'a'}}, {{'n', 'y', 'a'}}}, // Nyanja - LanguageCodeEntry {{}, {{'n', 'y', 'n'}}, {{'n', 'y', 'n'}}, {{'n', 'y', 'n'}}}, // Nyankole - LanguageCodeEntry {{{'o', 'c'}}, {{'o', 'c', 'i'}}, {{'o', 'c', 'i'}}, {{'o', 'c', 'i'}}}, // Occitan - LanguageCodeEntry {{{'o', 'r'}}, {{'o', 'r', 'i'}}, {{'o', 'r', 'i'}}, {{'o', 'r', 'i'}}}, // Odia - LanguageCodeEntry {{{'o', 'j'}}, {{'o', 'j', 'i'}}, {{'o', 'j', 'i'}}, {{'o', 'j', 'i'}}}, // Ojibwa - LanguageCodeEntry {{}, {{'s', 'g', 'a'}}, {{'s', 'g', 'a'}}, {{'s', 'g', 'a'}}}, // Old Irish - LanguageCodeEntry {{}, {{'n', 'o', 'n'}}, {{'n', 'o', 'n'}}, {{'n', 'o', 'n'}}}, // Old Norse - LanguageCodeEntry {{}, {{'p', 'e', 'o'}}, {{'p', 'e', 'o'}}, {{'p', 'e', 'o'}}}, // Old Persian - LanguageCodeEntry {{{'o', 'm'}}, {{'o', 'r', 'm'}}, {{'o', 'r', 'm'}}, {{'o', 'r', 'm'}}}, // Oromo - LanguageCodeEntry {{}, {{'o', 's', 'a'}}, {{'o', 's', 'a'}}, {{'o', 's', 'a'}}}, // Osage - LanguageCodeEntry {{{'o', 's'}}, {{'o', 's', 's'}}, {{'o', 's', 's'}}, {{'o', 's', 's'}}}, // Ossetic - LanguageCodeEntry {{}, {{'p', 'a', 'l'}}, {{'p', 'a', 'l'}}, {{'p', 'a', 'l'}}}, // Pahlavi - LanguageCodeEntry {{}, {{'p', 'a', 'u'}}, {{'p', 'a', 'u'}}, {{'p', 'a', 'u'}}}, // Palauan - LanguageCodeEntry {{{'p', 'i'}}, {{'p', 'l', 'i'}}, {{'p', 'l', 'i'}}, {{'p', 'l', 'i'}}}, // Pali - LanguageCodeEntry {{}, {{'p', 'a', 'p'}}, {{'p', 'a', 'p'}}, {{'p', 'a', 'p'}}}, // Papiamento - LanguageCodeEntry {{{'p', 's'}}, {{'p', 'u', 's'}}, {{'p', 'u', 's'}}, {{'p', 'u', 's'}}}, // Pashto - LanguageCodeEntry {{{'f', 'a'}}, {{'p', 'e', 'r'}}, {{'f', 'a', 's'}}, {{'f', 'a', 's'}}}, // Persian - LanguageCodeEntry {{}, {{'p', 'h', 'n'}}, {{'p', 'h', 'n'}}, {{'p', 'h', 'n'}}}, // Phoenician - LanguageCodeEntry {{{'p', 'l'}}, {{'p', 'o', 'l'}}, {{'p', 'o', 'l'}}, {{'p', 'o', 'l'}}}, // Polish - LanguageCodeEntry {{{'p', 't'}}, {{'p', 'o', 'r'}}, {{'p', 'o', 'r'}}, {{'p', 'o', 'r'}}}, // Portuguese - LanguageCodeEntry {{}, {}, {}, {{'p', 'r', 'g'}}}, // Prussian - LanguageCodeEntry {{{'p', 'a'}}, {{'p', 'a', 'n'}}, {{'p', 'a', 'n'}}, {{'p', 'a', 'n'}}}, // Punjabi - LanguageCodeEntry {{{'q', 'u'}}, {{'q', 'u', 'e'}}, {{'q', 'u', 'e'}}, {{'q', 'u', 'e'}}}, // Quechua - LanguageCodeEntry {{{'r', 'o'}}, {{'r', 'u', 'm'}}, {{'r', 'o', 'n'}}, {{'r', 'o', 'n'}}}, // Romanian - LanguageCodeEntry {{{'r', 'm'}}, {{'r', 'o', 'h'}}, {{'r', 'o', 'h'}}, {{'r', 'o', 'h'}}}, // Romansh - LanguageCodeEntry {{}, {}, {}, {{'r', 'o', 'f'}}}, // Rombo - LanguageCodeEntry {{{'r', 'n'}}, {{'r', 'u', 'n'}}, {{'r', 'u', 'n'}}, {{'r', 'u', 'n'}}}, // Rundi - LanguageCodeEntry {{{'r', 'u'}}, {{'r', 'u', 's'}}, {{'r', 'u', 's'}}, {{'r', 'u', 's'}}}, // Russian - LanguageCodeEntry {{}, {}, {}, {{'r', 'w', 'k'}}}, // Rwa - LanguageCodeEntry {{}, {}, {}, {{'s', 's', 'y'}}}, // Saho - LanguageCodeEntry {{}, {{'s', 'a', 'h'}}, {{'s', 'a', 'h'}}, {{'s', 'a', 'h'}}}, // Sakha - LanguageCodeEntry {{}, {}, {}, {{'s', 'a', 'q'}}}, // Samburu - LanguageCodeEntry {{{'s', 'm'}}, {{'s', 'm', 'o'}}, {{'s', 'm', 'o'}}, {{'s', 'm', 'o'}}}, // Samoan - LanguageCodeEntry {{{'s', 'g'}}, {{'s', 'a', 'g'}}, {{'s', 'a', 'g'}}, {{'s', 'a', 'g'}}}, // Sango - LanguageCodeEntry {{}, {}, {}, {{'s', 'b', 'p'}}}, // Sangu - LanguageCodeEntry {{{'s', 'a'}}, {{'s', 'a', 'n'}}, {{'s', 'a', 'n'}}, {{'s', 'a', 'n'}}}, // Sanskrit - LanguageCodeEntry {{}, {{'s', 'a', 't'}}, {{'s', 'a', 't'}}, {{'s', 'a', 't'}}}, // Santali - LanguageCodeEntry {{{'s', 'c'}}, {{'s', 'r', 'd'}}, {{'s', 'r', 'd'}}, {{'s', 'r', 'd'}}}, // Sardinian - LanguageCodeEntry {{}, {}, {}, {{'s', 'a', 'z'}}}, // Saurashtra - LanguageCodeEntry {{}, {}, {}, {{'s', 'e', 'h'}}}, // Sena - LanguageCodeEntry {{{'s', 'r'}}, {{'s', 'r', 'p'}}, {{'s', 'r', 'p'}}, {{'s', 'r', 'p'}}}, // Serbian - LanguageCodeEntry {{}, {}, {}, {{'k', 's', 'b'}}}, // Shambala - LanguageCodeEntry {{{'s', 'n'}}, {{'s', 'n', 'a'}}, {{'s', 'n', 'a'}}, {{'s', 'n', 'a'}}}, // Shona - LanguageCodeEntry {{{'i', 'i'}}, {{'i', 'i', 'i'}}, {{'i', 'i', 'i'}}, {{'i', 'i', 'i'}}}, // Sichuan Yi - LanguageCodeEntry {{}, {{'s', 'c', 'n'}}, {{'s', 'c', 'n'}}, {{'s', 'c', 'n'}}}, // Sicilian - LanguageCodeEntry {{}, {{'s', 'i', 'd'}}, {{'s', 'i', 'd'}}, {{'s', 'i', 'd'}}}, // Sidamo - LanguageCodeEntry {{}, {}, {}, {{'s', 'z', 'l'}}}, // Silesian - LanguageCodeEntry {{{'s', 'd'}}, {{'s', 'n', 'd'}}, {{'s', 'n', 'd'}}, {{'s', 'n', 'd'}}}, // Sindhi - LanguageCodeEntry {{{'s', 'i'}}, {{'s', 'i', 'n'}}, {{'s', 'i', 'n'}}, {{'s', 'i', 'n'}}}, // Sinhala - LanguageCodeEntry {{}, {{'s', 'm', 's'}}, {{'s', 'm', 's'}}, {{'s', 'm', 's'}}}, // Skolt Sami - LanguageCodeEntry {{{'s', 'k'}}, {{'s', 'l', 'o'}}, {{'s', 'l', 'k'}}, {{'s', 'l', 'k'}}}, // Slovak - LanguageCodeEntry {{{'s', 'l'}}, {{'s', 'l', 'v'}}, {{'s', 'l', 'v'}}, {{'s', 'l', 'v'}}}, // Slovenian - LanguageCodeEntry {{}, {}, {}, {{'x', 'o', 'g'}}}, // Soga - LanguageCodeEntry {{{'s', 'o'}}, {{'s', 'o', 'm'}}, {{'s', 'o', 'm'}}, {{'s', 'o', 'm'}}}, // Somali - LanguageCodeEntry {{}, {}, {}, {{'s', 'd', 'h'}}}, // Southern Kurdish - LanguageCodeEntry {{}, {{'s', 'm', 'a'}}, {{'s', 'm', 'a'}}, {{'s', 'm', 'a'}}}, // Southern Sami - LanguageCodeEntry {{{'s', 't'}}, {{'s', 'o', 't'}}, {{'s', 'o', 't'}}, {{'s', 'o', 't'}}}, // Southern Sotho - LanguageCodeEntry {{{'n', 'r'}}, {{'n', 'b', 'l'}}, {{'n', 'b', 'l'}}, {{'n', 'b', 'l'}}}, // South Ndebele - LanguageCodeEntry {{{'e', 's'}}, {{'s', 'p', 'a'}}, {{'s', 'p', 'a'}}, {{'s', 'p', 'a'}}}, // Spanish - LanguageCodeEntry {{}, {{'z', 'g', 'h'}}, {{'z', 'g', 'h'}}, {{'z', 'g', 'h'}}}, // Standard Moroccan Tamazight - LanguageCodeEntry {{{'s', 'u'}}, {{'s', 'u', 'n'}}, {{'s', 'u', 'n'}}, {{'s', 'u', 'n'}}}, // Sundanese - LanguageCodeEntry {{{'s', 'w'}}, {{'s', 'w', 'a'}}, {{'s', 'w', 'a'}}, {{'s', 'w', 'a'}}}, // Swahili - LanguageCodeEntry {{{'s', 's'}}, {{'s', 's', 'w'}}, {{'s', 's', 'w'}}, {{'s', 's', 'w'}}}, // Swati - LanguageCodeEntry {{{'s', 'v'}}, {{'s', 'w', 'e'}}, {{'s', 'w', 'e'}}, {{'s', 'w', 'e'}}}, // Swedish - LanguageCodeEntry {{}, {{'g', 's', 'w'}}, {{'g', 's', 'w'}}, {{'g', 's', 'w'}}}, // Swiss German - LanguageCodeEntry {{}, {{'s', 'y', 'r'}}, {{'s', 'y', 'r'}}, {{'s', 'y', 'r'}}}, // Syriac - LanguageCodeEntry {{}, {}, {}, {{'s', 'h', 'i'}}}, // Tachelhit - LanguageCodeEntry {{{'t', 'y'}}, {{'t', 'a', 'h'}}, {{'t', 'a', 'h'}}, {{'t', 'a', 'h'}}}, // Tahitian - LanguageCodeEntry {{}, {}, {}, {{'b', 'l', 't'}}}, // Tai Dam - LanguageCodeEntry {{}, {}, {}, {{'d', 'a', 'v'}}}, // Taita - LanguageCodeEntry {{{'t', 'g'}}, {{'t', 'g', 'k'}}, {{'t', 'g', 'k'}}, {{'t', 'g', 'k'}}}, // Tajik - LanguageCodeEntry {{{'t', 'a'}}, {{'t', 'a', 'm'}}, {{'t', 'a', 'm'}}, {{'t', 'a', 'm'}}}, // Tamil - LanguageCodeEntry {{}, {}, {}, {{'t', 'r', 'v'}}}, // Taroko - LanguageCodeEntry {{}, {}, {}, {{'t', 'w', 'q'}}}, // Tasawaq - LanguageCodeEntry {{{'t', 't'}}, {{'t', 'a', 't'}}, {{'t', 'a', 't'}}, {{'t', 'a', 't'}}}, // Tatar - LanguageCodeEntry {{{'t', 'e'}}, {{'t', 'e', 'l'}}, {{'t', 'e', 'l'}}, {{'t', 'e', 'l'}}}, // Telugu - LanguageCodeEntry {{}, {}, {}, {{'t', 'e', 'o'}}}, // Teso - LanguageCodeEntry {{{'t', 'h'}}, {{'t', 'h', 'a'}}, {{'t', 'h', 'a'}}, {{'t', 'h', 'a'}}}, // Thai - LanguageCodeEntry {{{'b', 'o'}}, {{'t', 'i', 'b'}}, {{'b', 'o', 'd'}}, {{'b', 'o', 'd'}}}, // Tibetan - LanguageCodeEntry {{}, {{'t', 'i', 'g'}}, {{'t', 'i', 'g'}}, {{'t', 'i', 'g'}}}, // Tigre - LanguageCodeEntry {{{'t', 'i'}}, {{'t', 'i', 'r'}}, {{'t', 'i', 'r'}}, {{'t', 'i', 'r'}}}, // Tigrinya - LanguageCodeEntry {{}, {{'t', 'k', 'l'}}, {{'t', 'k', 'l'}}, {{'t', 'k', 'l'}}}, // Tokelau - LanguageCodeEntry {{}, {{'t', 'p', 'i'}}, {{'t', 'p', 'i'}}, {{'t', 'p', 'i'}}}, // Tok Pisin - LanguageCodeEntry {{{'t', 'o'}}, {{'t', 'o', 'n'}}, {{'t', 'o', 'n'}}, {{'t', 'o', 'n'}}}, // Tongan - LanguageCodeEntry {{{'t', 's'}}, {{'t', 's', 'o'}}, {{'t', 's', 'o'}}, {{'t', 's', 'o'}}}, // Tsonga - LanguageCodeEntry {{{'t', 'n'}}, {{'t', 's', 'n'}}, {{'t', 's', 'n'}}, {{'t', 's', 'n'}}}, // Tswana - LanguageCodeEntry {{{'t', 'r'}}, {{'t', 'u', 'r'}}, {{'t', 'u', 'r'}}, {{'t', 'u', 'r'}}}, // Turkish - LanguageCodeEntry {{{'t', 'k'}}, {{'t', 'u', 'k'}}, {{'t', 'u', 'k'}}, {{'t', 'u', 'k'}}}, // Turkmen - LanguageCodeEntry {{}, {{'t', 'v', 'l'}}, {{'t', 'v', 'l'}}, {{'t', 'v', 'l'}}}, // Tuvalu - LanguageCodeEntry {{}, {}, {}, {{'k', 'c', 'g'}}}, // Tyap - LanguageCodeEntry {{}, {{'u', 'g', 'a'}}, {{'u', 'g', 'a'}}, {{'u', 'g', 'a'}}}, // Ugaritic - LanguageCodeEntry {{{'u', 'k'}}, {{'u', 'k', 'r'}}, {{'u', 'k', 'r'}}, {{'u', 'k', 'r'}}}, // Ukrainian - LanguageCodeEntry {{}, {{'h', 's', 'b'}}, {{'h', 's', 'b'}}, {{'h', 's', 'b'}}}, // Upper Sorbian - LanguageCodeEntry {{{'u', 'r'}}, {{'u', 'r', 'd'}}, {{'u', 'r', 'd'}}, {{'u', 'r', 'd'}}}, // Urdu - LanguageCodeEntry {{{'u', 'g'}}, {{'u', 'i', 'g'}}, {{'u', 'i', 'g'}}, {{'u', 'i', 'g'}}}, // Uyghur - LanguageCodeEntry {{{'u', 'z'}}, {{'u', 'z', 'b'}}, {{'u', 'z', 'b'}}, {{'u', 'z', 'b'}}}, // Uzbek - LanguageCodeEntry {{}, {{'v', 'a', 'i'}}, {{'v', 'a', 'i'}}, {{'v', 'a', 'i'}}}, // Vai - LanguageCodeEntry {{{'v', 'e'}}, {{'v', 'e', 'n'}}, {{'v', 'e', 'n'}}, {{'v', 'e', 'n'}}}, // Venda - LanguageCodeEntry {{{'v', 'i'}}, {{'v', 'i', 'e'}}, {{'v', 'i', 'e'}}, {{'v', 'i', 'e'}}}, // Vietnamese - LanguageCodeEntry {{{'v', 'o'}}, {{'v', 'o', 'l'}}, {{'v', 'o', 'l'}}, {{'v', 'o', 'l'}}}, // Volapuk - LanguageCodeEntry {{}, {}, {}, {{'v', 'u', 'n'}}}, // Vunjo - LanguageCodeEntry {{{'w', 'a'}}, {{'w', 'l', 'n'}}, {{'w', 'l', 'n'}}, {{'w', 'l', 'n'}}}, // Walloon - LanguageCodeEntry {{}, {}, {}, {{'w', 'a', 'e'}}}, // Walser - LanguageCodeEntry {{}, {}, {}, {{'w', 'b', 'p'}}}, // Warlpiri - LanguageCodeEntry {{{'c', 'y'}}, {{'w', 'e', 'l'}}, {{'c', 'y', 'm'}}, {{'c', 'y', 'm'}}}, // Welsh - LanguageCodeEntry {{}, {}, {}, {{'b', 'g', 'n'}}}, // Western Balochi - LanguageCodeEntry {{{'f', 'y'}}, {{'f', 'r', 'y'}}, {{'f', 'r', 'y'}}, {{'f', 'r', 'y'}}}, // Western Frisian - LanguageCodeEntry {{}, {{'w', 'a', 'l'}}, {{'w', 'a', 'l'}}, {{'w', 'a', 'l'}}}, // Wolaytta - LanguageCodeEntry {{{'w', 'o'}}, {{'w', 'o', 'l'}}, {{'w', 'o', 'l'}}, {{'w', 'o', 'l'}}}, // Wolof - LanguageCodeEntry {{{'x', 'h'}}, {{'x', 'h', 'o'}}, {{'x', 'h', 'o'}}, {{'x', 'h', 'o'}}}, // Xhosa - LanguageCodeEntry {{}, {}, {}, {{'y', 'a', 'v'}}}, // Yangben - LanguageCodeEntry {{{'y', 'i'}}, {{'y', 'i', 'd'}}, {{'y', 'i', 'd'}}, {{'y', 'i', 'd'}}}, // Yiddish - LanguageCodeEntry {{{'y', 'o'}}, {{'y', 'o', 'r'}}, {{'y', 'o', 'r'}}, {{'y', 'o', 'r'}}}, // Yoruba - LanguageCodeEntry {{}, {}, {}, {{'d', 'j', 'e'}}}, // Zarma - LanguageCodeEntry {{{'z', 'a'}}, {{'z', 'h', 'a'}}, {{'z', 'h', 'a'}}, {{'z', 'h', 'a'}}}, // Zhuang - LanguageCodeEntry {{{'z', 'u'}}, {{'z', 'u', 'l'}}, {{'z', 'u', 'l'}}, {{'z', 'u', 'l'}}}, // Zulu - LanguageCodeEntry {{}, {}, {}, {{'k', 'g', 'p'}}}, // Kaingang - LanguageCodeEntry {{}, {}, {}, {{'y', 'r', 'l'}}}, // Nheengatu - LanguageCodeEntry {{}, {}, {}, {{'b', 'g', 'c'}}}, // Haryanvi - LanguageCodeEntry {{}, {{'f', 'r', 'r'}}, {{'f', 'r', 'r'}}, {{'f', 'r', 'r'}}}, // Northern Frisian - LanguageCodeEntry {{}, {{'r', 'a', 'j'}}, {{'r', 'a', 'j'}}, {{'r', 'a', 'j'}}}, // Rajasthani - LanguageCodeEntry {{}, {{'m', 'd', 'f'}}, {{'m', 'd', 'f'}}, {{'m', 'd', 'f'}}}, // Moksha - LanguageCodeEntry {{}, {}, {}, {{'t', 'o', 'k'}}}, // Toki Pona - LanguageCodeEntry {{}, {}, {}, {{'p', 'i', 's'}}}, // Pijin - LanguageCodeEntry {{}, {}, {}, {{'a', 'n', 'n'}}}, // Obolo - LanguageCodeEntry {{}, {{'b', 'a', 'l'}}, {{'b', 'a', 'l'}}, {{'b', 'a', 'l'}}}, // Baluchi - LanguageCodeEntry {{}, {}, {}, {{'l', 'i', 'j'}}}, // Ligurian - LanguageCodeEntry {{}, {}, {}, {{'r', 'h', 'g'}}}, // Rohingya - LanguageCodeEntry {{}, {}, {}, {{'t', 'r', 'w'}}}, // Torwali + LanguageCodeEntry {{}, {'u', 'n', 'd'}, {'u', 'n', 'd'}, {'u', 'n', 'd'}}, // AnyLanguage + LanguageCodeEntry {{}, {'u', 'n', 'd'}, {'u', 'n', 'd'}, {'u', 'n', 'd'}}, // C + LanguageCodeEntry {{'a', 'b'}, {'a', 'b', 'k'}, {'a', 'b', 'k'}, {'a', 'b', 'k'}}, // Abkhazian + LanguageCodeEntry {{'a', 'a'}, {'a', 'a', 'r'}, {'a', 'a', 'r'}, {'a', 'a', 'r'}}, // Afar + LanguageCodeEntry {{'a', 'f'}, {'a', 'f', 'r'}, {'a', 'f', 'r'}, {'a', 'f', 'r'}}, // Afrikaans + LanguageCodeEntry {{}, {}, {}, {'a', 'g', 'q'}}, // Aghem + LanguageCodeEntry {{'a', 'k'}, {'a', 'k', 'a'}, {'a', 'k', 'a'}, {'a', 'k', 'a'}}, // Akan + LanguageCodeEntry {{}, {'a', 'k', 'k'}, {'a', 'k', 'k'}, {'a', 'k', 'k'}}, // Akkadian + LanguageCodeEntry {{}, {}, {}, {'b', 's', 's'}}, // Akoose + LanguageCodeEntry {{'s', 'q'}, {'a', 'l', 'b'}, {'s', 'q', 'i'}, {'s', 'q', 'i'}}, // Albanian + LanguageCodeEntry {{}, {}, {}, {'a', 's', 'e'}}, // American Sign Language + LanguageCodeEntry {{'a', 'm'}, {'a', 'm', 'h'}, {'a', 'm', 'h'}, {'a', 'm', 'h'}}, // Amharic + LanguageCodeEntry {{}, {'e', 'g', 'y'}, {'e', 'g', 'y'}, {'e', 'g', 'y'}}, // Ancient Egyptian + LanguageCodeEntry {{}, {'g', 'r', 'c'}, {'g', 'r', 'c'}, {'g', 'r', 'c'}}, // Ancient Greek + LanguageCodeEntry {{'a', 'r'}, {'a', 'r', 'a'}, {'a', 'r', 'a'}, {'a', 'r', 'a'}}, // Arabic + LanguageCodeEntry {{'a', 'n'}, {'a', 'r', 'g'}, {'a', 'r', 'g'}, {'a', 'r', 'g'}}, // Aragonese + LanguageCodeEntry {{}, {'a', 'r', 'c'}, {'a', 'r', 'c'}, {'a', 'r', 'c'}}, // Aramaic + LanguageCodeEntry {{'h', 'y'}, {'a', 'r', 'm'}, {'h', 'y', 'e'}, {'h', 'y', 'e'}}, // Armenian + LanguageCodeEntry {{'a', 's'}, {'a', 's', 'm'}, {'a', 's', 'm'}, {'a', 's', 'm'}}, // Assamese + LanguageCodeEntry {{}, {'a', 's', 't'}, {'a', 's', 't'}, {'a', 's', 't'}}, // Asturian + LanguageCodeEntry {{}, {}, {}, {'a', 's', 'a'}}, // Asu + LanguageCodeEntry {{}, {}, {}, {'c', 'c', 'h'}}, // Atsam + LanguageCodeEntry {{'a', 'v'}, {'a', 'v', 'a'}, {'a', 'v', 'a'}, {'a', 'v', 'a'}}, // Avaric + LanguageCodeEntry {{'a', 'e'}, {'a', 'v', 'e'}, {'a', 'v', 'e'}, {'a', 'v', 'e'}}, // Avestan + LanguageCodeEntry {{'a', 'y'}, {'a', 'y', 'm'}, {'a', 'y', 'm'}, {'a', 'y', 'm'}}, // Aymara + LanguageCodeEntry {{'a', 'z'}, {'a', 'z', 'e'}, {'a', 'z', 'e'}, {'a', 'z', 'e'}}, // Azerbaijani + LanguageCodeEntry {{}, {}, {}, {'k', 's', 'f'}}, // Bafia + LanguageCodeEntry {{}, {'b', 'a', 'n'}, {'b', 'a', 'n'}, {'b', 'a', 'n'}}, // Balinese + LanguageCodeEntry {{'b', 'm'}, {'b', 'a', 'm'}, {'b', 'a', 'm'}, {'b', 'a', 'm'}}, // Bambara + LanguageCodeEntry {{}, {}, {}, {'b', 'a', 'x'}}, // Bamun + LanguageCodeEntry {{'b', 'n'}, {'b', 'e', 'n'}, {'b', 'e', 'n'}, {'b', 'e', 'n'}}, // Bangla + LanguageCodeEntry {{}, {'b', 'a', 's'}, {'b', 'a', 's'}, {'b', 'a', 's'}}, // Basaa + LanguageCodeEntry {{'b', 'a'}, {'b', 'a', 'k'}, {'b', 'a', 'k'}, {'b', 'a', 'k'}}, // Bashkir + LanguageCodeEntry {{'e', 'u'}, {'b', 'a', 'q'}, {'e', 'u', 's'}, {'e', 'u', 's'}}, // Basque + LanguageCodeEntry {{}, {}, {}, {'b', 'b', 'c'}}, // Batak Toba + LanguageCodeEntry {{'b', 'e'}, {'b', 'e', 'l'}, {'b', 'e', 'l'}, {'b', 'e', 'l'}}, // Belarusian + LanguageCodeEntry {{}, {'b', 'e', 'm'}, {'b', 'e', 'm'}, {'b', 'e', 'm'}}, // Bemba + LanguageCodeEntry {{}, {}, {}, {'b', 'e', 'z'}}, // Bena + LanguageCodeEntry {{}, {'b', 'h', 'o'}, {'b', 'h', 'o'}, {'b', 'h', 'o'}}, // Bhojpuri + LanguageCodeEntry {{'b', 'i'}, {'b', 'i', 's'}, {'b', 'i', 's'}, {'b', 'i', 's'}}, // Bislama + LanguageCodeEntry {{}, {'b', 'y', 'n'}, {'b', 'y', 'n'}, {'b', 'y', 'n'}}, // Blin + LanguageCodeEntry {{}, {}, {}, {'b', 'r', 'x'}}, // Bodo + LanguageCodeEntry {{'b', 's'}, {'b', 'o', 's'}, {'b', 'o', 's'}, {'b', 'o', 's'}}, // Bosnian + LanguageCodeEntry {{'b', 'r'}, {'b', 'r', 'e'}, {'b', 'r', 'e'}, {'b', 'r', 'e'}}, // Breton + LanguageCodeEntry {{}, {'b', 'u', 'g'}, {'b', 'u', 'g'}, {'b', 'u', 'g'}}, // Buginese + LanguageCodeEntry {{'b', 'g'}, {'b', 'u', 'l'}, {'b', 'u', 'l'}, {'b', 'u', 'l'}}, // Bulgarian + LanguageCodeEntry {{'m', 'y'}, {'b', 'u', 'r'}, {'m', 'y', 'a'}, {'m', 'y', 'a'}}, // Burmese + LanguageCodeEntry {{}, {}, {}, {'y', 'u', 'e'}}, // Cantonese + LanguageCodeEntry {{'c', 'a'}, {'c', 'a', 't'}, {'c', 'a', 't'}, {'c', 'a', 't'}}, // Catalan + LanguageCodeEntry {{}, {'c', 'e', 'b'}, {'c', 'e', 'b'}, {'c', 'e', 'b'}}, // Cebuano + LanguageCodeEntry {{}, {}, {}, {'t', 'z', 'm'}}, // Central Atlas Tamazight + LanguageCodeEntry {{}, {}, {}, {'c', 'k', 'b'}}, // Central Kurdish + LanguageCodeEntry {{}, {}, {}, {'c', 'c', 'p'}}, // Chakma + LanguageCodeEntry {{'c', 'h'}, {'c', 'h', 'a'}, {'c', 'h', 'a'}, {'c', 'h', 'a'}}, // Chamorro + LanguageCodeEntry {{'c', 'e'}, {'c', 'h', 'e'}, {'c', 'h', 'e'}, {'c', 'h', 'e'}}, // Chechen + LanguageCodeEntry {{}, {'c', 'h', 'r'}, {'c', 'h', 'r'}, {'c', 'h', 'r'}}, // Cherokee + LanguageCodeEntry {{}, {}, {}, {'c', 'i', 'c'}}, // Chickasaw + LanguageCodeEntry {{}, {}, {}, {'c', 'g', 'g'}}, // Chiga + LanguageCodeEntry {{'z', 'h'}, {'c', 'h', 'i'}, {'z', 'h', 'o'}, {'z', 'h', 'o'}}, // Chinese + LanguageCodeEntry {{'c', 'u'}, {'c', 'h', 'u'}, {'c', 'h', 'u'}, {'c', 'h', 'u'}}, // Church + LanguageCodeEntry {{'c', 'v'}, {'c', 'h', 'v'}, {'c', 'h', 'v'}, {'c', 'h', 'v'}}, // Chuvash + LanguageCodeEntry {{}, {}, {}, {'k', 's', 'h'}}, // Colognian + LanguageCodeEntry {{}, {'c', 'o', 'p'}, {'c', 'o', 'p'}, {'c', 'o', 'p'}}, // Coptic + LanguageCodeEntry {{'k', 'w'}, {'c', 'o', 'r'}, {'c', 'o', 'r'}, {'c', 'o', 'r'}}, // Cornish + LanguageCodeEntry {{'c', 'o'}, {'c', 'o', 's'}, {'c', 'o', 's'}, {'c', 'o', 's'}}, // Corsican + LanguageCodeEntry {{'c', 'r'}, {'c', 'r', 'e'}, {'c', 'r', 'e'}, {'c', 'r', 'e'}}, // Cree + LanguageCodeEntry {{'h', 'r'}, {'h', 'r', 'v'}, {'h', 'r', 'v'}, {'h', 'r', 'v'}}, // Croatian + LanguageCodeEntry {{'c', 's'}, {'c', 'z', 'e'}, {'c', 'e', 's'}, {'c', 'e', 's'}}, // Czech + LanguageCodeEntry {{'d', 'a'}, {'d', 'a', 'n'}, {'d', 'a', 'n'}, {'d', 'a', 'n'}}, // Danish + LanguageCodeEntry {{'d', 'v'}, {'d', 'i', 'v'}, {'d', 'i', 'v'}, {'d', 'i', 'v'}}, // Divehi + LanguageCodeEntry {{}, {'d', 'o', 'i'}, {'d', 'o', 'i'}, {'d', 'o', 'i'}}, // Dogri + LanguageCodeEntry {{}, {'d', 'u', 'a'}, {'d', 'u', 'a'}, {'d', 'u', 'a'}}, // Duala + LanguageCodeEntry {{'n', 'l'}, {'d', 'u', 't'}, {'n', 'l', 'd'}, {'n', 'l', 'd'}}, // Dutch + LanguageCodeEntry {{'d', 'z'}, {'d', 'z', 'o'}, {'d', 'z', 'o'}, {'d', 'z', 'o'}}, // Dzongkha + LanguageCodeEntry {{}, {}, {}, {'e', 'b', 'u'}}, // Embu + LanguageCodeEntry {{'e', 'n'}, {'e', 'n', 'g'}, {'e', 'n', 'g'}, {'e', 'n', 'g'}}, // English + LanguageCodeEntry {{}, {'m', 'y', 'v'}, {'m', 'y', 'v'}, {'m', 'y', 'v'}}, // Erzya + LanguageCodeEntry {{'e', 'o'}, {'e', 'p', 'o'}, {'e', 'p', 'o'}, {'e', 'p', 'o'}}, // Esperanto + LanguageCodeEntry {{'e', 't'}, {'e', 's', 't'}, {'e', 's', 't'}, {'e', 's', 't'}}, // Estonian + LanguageCodeEntry {{'e', 'e'}, {'e', 'w', 'e'}, {'e', 'w', 'e'}, {'e', 'w', 'e'}}, // Ewe + LanguageCodeEntry {{}, {'e', 'w', 'o'}, {'e', 'w', 'o'}, {'e', 'w', 'o'}}, // Ewondo + LanguageCodeEntry {{'f', 'o'}, {'f', 'a', 'o'}, {'f', 'a', 'o'}, {'f', 'a', 'o'}}, // Faroese + LanguageCodeEntry {{'f', 'j'}, {'f', 'i', 'j'}, {'f', 'i', 'j'}, {'f', 'i', 'j'}}, // Fijian + LanguageCodeEntry {{}, {'f', 'i', 'l'}, {'f', 'i', 'l'}, {'f', 'i', 'l'}}, // Filipino + LanguageCodeEntry {{'f', 'i'}, {'f', 'i', 'n'}, {'f', 'i', 'n'}, {'f', 'i', 'n'}}, // Finnish + LanguageCodeEntry {{'f', 'r'}, {'f', 'r', 'e'}, {'f', 'r', 'a'}, {'f', 'r', 'a'}}, // French + LanguageCodeEntry {{}, {'f', 'u', 'r'}, {'f', 'u', 'r'}, {'f', 'u', 'r'}}, // Friulian + LanguageCodeEntry {{'f', 'f'}, {'f', 'u', 'l'}, {'f', 'u', 'l'}, {'f', 'u', 'l'}}, // Fulah + LanguageCodeEntry {{'g', 'd'}, {'g', 'l', 'a'}, {'g', 'l', 'a'}, {'g', 'l', 'a'}}, // Gaelic + LanguageCodeEntry {{}, {'g', 'a', 'a'}, {'g', 'a', 'a'}, {'g', 'a', 'a'}}, // Ga + LanguageCodeEntry {{'g', 'l'}, {'g', 'l', 'g'}, {'g', 'l', 'g'}, {'g', 'l', 'g'}}, // Galician + LanguageCodeEntry {{'l', 'g'}, {'l', 'u', 'g'}, {'l', 'u', 'g'}, {'l', 'u', 'g'}}, // Ganda + LanguageCodeEntry {{}, {'g', 'e', 'z'}, {'g', 'e', 'z'}, {'g', 'e', 'z'}}, // Geez + LanguageCodeEntry {{'k', 'a'}, {'g', 'e', 'o'}, {'k', 'a', 't'}, {'k', 'a', 't'}}, // Georgian + LanguageCodeEntry {{'d', 'e'}, {'g', 'e', 'r'}, {'d', 'e', 'u'}, {'d', 'e', 'u'}}, // German + LanguageCodeEntry {{}, {'g', 'o', 't'}, {'g', 'o', 't'}, {'g', 'o', 't'}}, // Gothic + LanguageCodeEntry {{'e', 'l'}, {'g', 'r', 'e'}, {'e', 'l', 'l'}, {'e', 'l', 'l'}}, // Greek + LanguageCodeEntry {{'g', 'n'}, {'g', 'r', 'n'}, {'g', 'r', 'n'}, {'g', 'r', 'n'}}, // Guarani + LanguageCodeEntry {{'g', 'u'}, {'g', 'u', 'j'}, {'g', 'u', 'j'}, {'g', 'u', 'j'}}, // Gujarati + LanguageCodeEntry {{}, {}, {}, {'g', 'u', 'z'}}, // Gusii + LanguageCodeEntry {{'h', 't'}, {'h', 'a', 't'}, {'h', 'a', 't'}, {'h', 'a', 't'}}, // Haitian + LanguageCodeEntry {{'h', 'a'}, {'h', 'a', 'u'}, {'h', 'a', 'u'}, {'h', 'a', 'u'}}, // Hausa + LanguageCodeEntry {{}, {'h', 'a', 'w'}, {'h', 'a', 'w'}, {'h', 'a', 'w'}}, // Hawaiian + LanguageCodeEntry {{'h', 'e'}, {'h', 'e', 'b'}, {'h', 'e', 'b'}, {'h', 'e', 'b'}}, // Hebrew + LanguageCodeEntry {{'h', 'z'}, {'h', 'e', 'r'}, {'h', 'e', 'r'}, {'h', 'e', 'r'}}, // Herero + LanguageCodeEntry {{'h', 'i'}, {'h', 'i', 'n'}, {'h', 'i', 'n'}, {'h', 'i', 'n'}}, // Hindi + LanguageCodeEntry {{'h', 'o'}, {'h', 'm', 'o'}, {'h', 'm', 'o'}, {'h', 'm', 'o'}}, // Hiri Motu + LanguageCodeEntry {{'h', 'u'}, {'h', 'u', 'n'}, {'h', 'u', 'n'}, {'h', 'u', 'n'}}, // Hungarian + LanguageCodeEntry {{'i', 's'}, {'i', 'c', 'e'}, {'i', 's', 'l'}, {'i', 's', 'l'}}, // Icelandic + LanguageCodeEntry {{'i', 'o'}, {'i', 'd', 'o'}, {'i', 'd', 'o'}, {'i', 'd', 'o'}}, // Ido + LanguageCodeEntry {{'i', 'g'}, {'i', 'b', 'o'}, {'i', 'b', 'o'}, {'i', 'b', 'o'}}, // Igbo + LanguageCodeEntry {{}, {'s', 'm', 'n'}, {'s', 'm', 'n'}, {'s', 'm', 'n'}}, // Inari Sami + LanguageCodeEntry {{'i', 'd'}, {'i', 'n', 'd'}, {'i', 'n', 'd'}, {'i', 'n', 'd'}}, // Indonesian + LanguageCodeEntry {{}, {'i', 'n', 'h'}, {'i', 'n', 'h'}, {'i', 'n', 'h'}}, // Ingush + LanguageCodeEntry {{'i', 'a'}, {'i', 'n', 'a'}, {'i', 'n', 'a'}, {'i', 'n', 'a'}}, // Interlingua + LanguageCodeEntry {{'i', 'e'}, {'i', 'l', 'e'}, {'i', 'l', 'e'}, {'i', 'l', 'e'}}, // Interlingue + LanguageCodeEntry {{'i', 'u'}, {'i', 'k', 'u'}, {'i', 'k', 'u'}, {'i', 'k', 'u'}}, // Inuktitut + LanguageCodeEntry {{'i', 'k'}, {'i', 'p', 'k'}, {'i', 'p', 'k'}, {'i', 'p', 'k'}}, // Inupiaq + LanguageCodeEntry {{'g', 'a'}, {'g', 'l', 'e'}, {'g', 'l', 'e'}, {'g', 'l', 'e'}}, // Irish + LanguageCodeEntry {{'i', 't'}, {'i', 't', 'a'}, {'i', 't', 'a'}, {'i', 't', 'a'}}, // Italian + LanguageCodeEntry {{'j', 'a'}, {'j', 'p', 'n'}, {'j', 'p', 'n'}, {'j', 'p', 'n'}}, // Japanese + LanguageCodeEntry {{'j', 'v'}, {'j', 'a', 'v'}, {'j', 'a', 'v'}, {'j', 'a', 'v'}}, // Javanese + LanguageCodeEntry {{}, {}, {}, {'k', 'a', 'j'}}, // Jju + LanguageCodeEntry {{}, {}, {}, {'d', 'y', 'o'}}, // Jola Fonyi + LanguageCodeEntry {{}, {}, {}, {'k', 'e', 'a'}}, // Kabuverdianu + LanguageCodeEntry {{}, {'k', 'a', 'b'}, {'k', 'a', 'b'}, {'k', 'a', 'b'}}, // Kabyle + LanguageCodeEntry {{}, {}, {}, {'k', 'k', 'j'}}, // Kako + LanguageCodeEntry {{'k', 'l'}, {'k', 'a', 'l'}, {'k', 'a', 'l'}, {'k', 'a', 'l'}}, // Kalaallisut + LanguageCodeEntry {{}, {}, {}, {'k', 'l', 'n'}}, // Kalenjin + LanguageCodeEntry {{}, {'k', 'a', 'm'}, {'k', 'a', 'm'}, {'k', 'a', 'm'}}, // Kamba + LanguageCodeEntry {{'k', 'n'}, {'k', 'a', 'n'}, {'k', 'a', 'n'}, {'k', 'a', 'n'}}, // Kannada + LanguageCodeEntry {{'k', 'r'}, {'k', 'a', 'u'}, {'k', 'a', 'u'}, {'k', 'a', 'u'}}, // Kanuri + LanguageCodeEntry {{'k', 's'}, {'k', 'a', 's'}, {'k', 'a', 's'}, {'k', 'a', 's'}}, // Kashmiri + LanguageCodeEntry {{'k', 'k'}, {'k', 'a', 'z'}, {'k', 'a', 'z'}, {'k', 'a', 'z'}}, // Kazakh + LanguageCodeEntry {{}, {}, {}, {'k', 'e', 'n'}}, // Kenyang + LanguageCodeEntry {{'k', 'm'}, {'k', 'h', 'm'}, {'k', 'h', 'm'}, {'k', 'h', 'm'}}, // Khmer + LanguageCodeEntry {{}, {}, {}, {'q', 'u', 'c'}}, // Kiche + LanguageCodeEntry {{'k', 'i'}, {'k', 'i', 'k'}, {'k', 'i', 'k'}, {'k', 'i', 'k'}}, // Kikuyu + LanguageCodeEntry {{'r', 'w'}, {'k', 'i', 'n'}, {'k', 'i', 'n'}, {'k', 'i', 'n'}}, // Kinyarwanda + LanguageCodeEntry {{'k', 'v'}, {'k', 'o', 'm'}, {'k', 'o', 'm'}, {'k', 'o', 'm'}}, // Komi + LanguageCodeEntry {{'k', 'g'}, {'k', 'o', 'n'}, {'k', 'o', 'n'}, {'k', 'o', 'n'}}, // Kongo + LanguageCodeEntry {{}, {'k', 'o', 'k'}, {'k', 'o', 'k'}, {'k', 'o', 'k'}}, // Konkani + LanguageCodeEntry {{'k', 'o'}, {'k', 'o', 'r'}, {'k', 'o', 'r'}, {'k', 'o', 'r'}}, // Korean + LanguageCodeEntry {{}, {}, {}, {'k', 'f', 'o'}}, // Koro + LanguageCodeEntry {{}, {}, {}, {'s', 'e', 's'}}, // Koyraboro Senni + LanguageCodeEntry {{}, {}, {}, {'k', 'h', 'q'}}, // Koyra Chiini + LanguageCodeEntry {{}, {'k', 'p', 'e'}, {'k', 'p', 'e'}, {'k', 'p', 'e'}}, // Kpelle + LanguageCodeEntry {{'k', 'j'}, {'k', 'u', 'a'}, {'k', 'u', 'a'}, {'k', 'u', 'a'}}, // Kuanyama + LanguageCodeEntry {{'k', 'u'}, {'k', 'u', 'r'}, {'k', 'u', 'r'}, {'k', 'u', 'r'}}, // Kurdish + LanguageCodeEntry {{}, {}, {}, {'n', 'm', 'g'}}, // Kwasio + LanguageCodeEntry {{'k', 'y'}, {'k', 'i', 'r'}, {'k', 'i', 'r'}, {'k', 'i', 'r'}}, // Kyrgyz + LanguageCodeEntry {{}, {}, {}, {'l', 'k', 't'}}, // Lakota + LanguageCodeEntry {{}, {}, {}, {'l', 'a', 'g'}}, // Langi + LanguageCodeEntry {{'l', 'o'}, {'l', 'a', 'o'}, {'l', 'a', 'o'}, {'l', 'a', 'o'}}, // Lao + LanguageCodeEntry {{'l', 'a'}, {'l', 'a', 't'}, {'l', 'a', 't'}, {'l', 'a', 't'}}, // Latin + LanguageCodeEntry {{'l', 'v'}, {'l', 'a', 'v'}, {'l', 'a', 'v'}, {'l', 'a', 'v'}}, // Latvian + LanguageCodeEntry {{}, {'l', 'e', 'z'}, {'l', 'e', 'z'}, {'l', 'e', 'z'}}, // Lezghian + LanguageCodeEntry {{'l', 'i'}, {'l', 'i', 'm'}, {'l', 'i', 'm'}, {'l', 'i', 'm'}}, // Limburgish + LanguageCodeEntry {{'l', 'n'}, {'l', 'i', 'n'}, {'l', 'i', 'n'}, {'l', 'i', 'n'}}, // Lingala + LanguageCodeEntry {{}, {}, {}, {'l', 'z', 'h'}}, // Literary Chinese + LanguageCodeEntry {{'l', 't'}, {'l', 'i', 't'}, {'l', 'i', 't'}, {'l', 'i', 't'}}, // Lithuanian + LanguageCodeEntry {{}, {'j', 'b', 'o'}, {'j', 'b', 'o'}, {'j', 'b', 'o'}}, // Lojban + LanguageCodeEntry {{}, {'d', 's', 'b'}, {'d', 's', 'b'}, {'d', 's', 'b'}}, // Lower Sorbian + LanguageCodeEntry {{}, {'n', 'd', 's'}, {'n', 'd', 's'}, {'n', 'd', 's'}}, // Low German + LanguageCodeEntry {{'l', 'u'}, {'l', 'u', 'b'}, {'l', 'u', 'b'}, {'l', 'u', 'b'}}, // Luba Katanga + LanguageCodeEntry {{}, {'s', 'm', 'j'}, {'s', 'm', 'j'}, {'s', 'm', 'j'}}, // Lule Sami + LanguageCodeEntry {{}, {'l', 'u', 'o'}, {'l', 'u', 'o'}, {'l', 'u', 'o'}}, // Luo + LanguageCodeEntry {{'l', 'b'}, {'l', 't', 'z'}, {'l', 't', 'z'}, {'l', 't', 'z'}}, // Luxembourgish + LanguageCodeEntry {{}, {}, {}, {'l', 'u', 'y'}}, // Luyia + LanguageCodeEntry {{'m', 'k'}, {'m', 'a', 'c'}, {'m', 'k', 'd'}, {'m', 'k', 'd'}}, // Macedonian + LanguageCodeEntry {{}, {}, {}, {'j', 'm', 'c'}}, // Machame + LanguageCodeEntry {{}, {'m', 'a', 'i'}, {'m', 'a', 'i'}, {'m', 'a', 'i'}}, // Maithili + LanguageCodeEntry {{}, {}, {}, {'m', 'g', 'h'}}, // Makhuwa Meetto + LanguageCodeEntry {{}, {}, {}, {'k', 'd', 'e'}}, // Makonde + LanguageCodeEntry {{'m', 'g'}, {'m', 'l', 'g'}, {'m', 'l', 'g'}, {'m', 'l', 'g'}}, // Malagasy + LanguageCodeEntry {{'m', 'l'}, {'m', 'a', 'l'}, {'m', 'a', 'l'}, {'m', 'a', 'l'}}, // Malayalam + LanguageCodeEntry {{'m', 's'}, {'m', 'a', 'y'}, {'m', 's', 'a'}, {'m', 's', 'a'}}, // Malay + LanguageCodeEntry {{'m', 't'}, {'m', 'l', 't'}, {'m', 'l', 't'}, {'m', 'l', 't'}}, // Maltese + LanguageCodeEntry {{}, {'m', 'a', 'n'}, {'m', 'a', 'n'}, {'m', 'a', 'n'}}, // Mandingo + LanguageCodeEntry {{}, {'m', 'n', 'i'}, {'m', 'n', 'i'}, {'m', 'n', 'i'}}, // Manipuri + LanguageCodeEntry {{'g', 'v'}, {'g', 'l', 'v'}, {'g', 'l', 'v'}, {'g', 'l', 'v'}}, // Manx + LanguageCodeEntry {{'m', 'i'}, {'m', 'a', 'o'}, {'m', 'r', 'i'}, {'m', 'r', 'i'}}, // Maori + LanguageCodeEntry {{}, {'a', 'r', 'n'}, {'a', 'r', 'n'}, {'a', 'r', 'n'}}, // Mapuche + LanguageCodeEntry {{'m', 'r'}, {'m', 'a', 'r'}, {'m', 'a', 'r'}, {'m', 'a', 'r'}}, // Marathi + LanguageCodeEntry {{'m', 'h'}, {'m', 'a', 'h'}, {'m', 'a', 'h'}, {'m', 'a', 'h'}}, // Marshallese + LanguageCodeEntry {{}, {'m', 'a', 's'}, {'m', 'a', 's'}, {'m', 'a', 's'}}, // Masai + LanguageCodeEntry {{}, {}, {}, {'m', 'z', 'n'}}, // Mazanderani + LanguageCodeEntry {{}, {'m', 'e', 'n'}, {'m', 'e', 'n'}, {'m', 'e', 'n'}}, // Mende + LanguageCodeEntry {{}, {}, {}, {'m', 'e', 'r'}}, // Meru + LanguageCodeEntry {{}, {}, {}, {'m', 'g', 'o'}}, // Meta + LanguageCodeEntry {{}, {'m', 'o', 'h'}, {'m', 'o', 'h'}, {'m', 'o', 'h'}}, // Mohawk + LanguageCodeEntry {{'m', 'n'}, {'m', 'o', 'n'}, {'m', 'o', 'n'}, {'m', 'o', 'n'}}, // Mongolian + LanguageCodeEntry {{}, {}, {}, {'m', 'f', 'e'}}, // Morisyen + LanguageCodeEntry {{}, {}, {}, {'m', 'u', 'a'}}, // Mundang + LanguageCodeEntry {{}, {'m', 'u', 's'}, {'m', 'u', 's'}, {'m', 'u', 's'}}, // Muscogee + LanguageCodeEntry {{}, {}, {}, {'n', 'a', 'q'}}, // Nama + LanguageCodeEntry {{'n', 'a'}, {'n', 'a', 'u'}, {'n', 'a', 'u'}, {'n', 'a', 'u'}}, // Nauru + LanguageCodeEntry {{'n', 'v'}, {'n', 'a', 'v'}, {'n', 'a', 'v'}, {'n', 'a', 'v'}}, // Navajo + LanguageCodeEntry {{'n', 'g'}, {'n', 'd', 'o'}, {'n', 'd', 'o'}, {'n', 'd', 'o'}}, // Ndonga + LanguageCodeEntry {{'n', 'e'}, {'n', 'e', 'p'}, {'n', 'e', 'p'}, {'n', 'e', 'p'}}, // Nepali + LanguageCodeEntry {{}, {'n', 'e', 'w'}, {'n', 'e', 'w'}, {'n', 'e', 'w'}}, // Newari + LanguageCodeEntry {{}, {}, {}, {'n', 'n', 'h'}}, // Ngiemboon + LanguageCodeEntry {{}, {}, {}, {'j', 'g', 'o'}}, // Ngomba + LanguageCodeEntry {{}, {}, {}, {'p', 'c', 'm'}}, // Nigerian Pidgin + LanguageCodeEntry {{}, {'n', 'q', 'o'}, {'n', 'q', 'o'}, {'n', 'q', 'o'}}, // Nko + LanguageCodeEntry {{}, {}, {}, {'l', 'r', 'c'}}, // Northern Luri + LanguageCodeEntry {{'s', 'e'}, {'s', 'm', 'e'}, {'s', 'm', 'e'}, {'s', 'm', 'e'}}, // Northern Sami + LanguageCodeEntry {{}, {'n', 's', 'o'}, {'n', 's', 'o'}, {'n', 's', 'o'}}, // Northern Sotho + LanguageCodeEntry {{'n', 'd'}, {'n', 'd', 'e'}, {'n', 'd', 'e'}, {'n', 'd', 'e'}}, // North Ndebele + LanguageCodeEntry {{'n', 'b'}, {'n', 'o', 'b'}, {'n', 'o', 'b'}, {'n', 'o', 'b'}}, // Norwegian Bokmal + LanguageCodeEntry {{'n', 'n'}, {'n', 'n', 'o'}, {'n', 'n', 'o'}, {'n', 'n', 'o'}}, // Norwegian Nynorsk + LanguageCodeEntry {{}, {}, {}, {'n', 'u', 's'}}, // Nuer + LanguageCodeEntry {{'n', 'y'}, {'n', 'y', 'a'}, {'n', 'y', 'a'}, {'n', 'y', 'a'}}, // Nyanja + LanguageCodeEntry {{}, {'n', 'y', 'n'}, {'n', 'y', 'n'}, {'n', 'y', 'n'}}, // Nyankole + LanguageCodeEntry {{'o', 'c'}, {'o', 'c', 'i'}, {'o', 'c', 'i'}, {'o', 'c', 'i'}}, // Occitan + LanguageCodeEntry {{'o', 'r'}, {'o', 'r', 'i'}, {'o', 'r', 'i'}, {'o', 'r', 'i'}}, // Odia + LanguageCodeEntry {{'o', 'j'}, {'o', 'j', 'i'}, {'o', 'j', 'i'}, {'o', 'j', 'i'}}, // Ojibwa + LanguageCodeEntry {{}, {'s', 'g', 'a'}, {'s', 'g', 'a'}, {'s', 'g', 'a'}}, // Old Irish + LanguageCodeEntry {{}, {'n', 'o', 'n'}, {'n', 'o', 'n'}, {'n', 'o', 'n'}}, // Old Norse + LanguageCodeEntry {{}, {'p', 'e', 'o'}, {'p', 'e', 'o'}, {'p', 'e', 'o'}}, // Old Persian + LanguageCodeEntry {{'o', 'm'}, {'o', 'r', 'm'}, {'o', 'r', 'm'}, {'o', 'r', 'm'}}, // Oromo + LanguageCodeEntry {{}, {'o', 's', 'a'}, {'o', 's', 'a'}, {'o', 's', 'a'}}, // Osage + LanguageCodeEntry {{'o', 's'}, {'o', 's', 's'}, {'o', 's', 's'}, {'o', 's', 's'}}, // Ossetic + LanguageCodeEntry {{}, {'p', 'a', 'l'}, {'p', 'a', 'l'}, {'p', 'a', 'l'}}, // Pahlavi + LanguageCodeEntry {{}, {'p', 'a', 'u'}, {'p', 'a', 'u'}, {'p', 'a', 'u'}}, // Palauan + LanguageCodeEntry {{'p', 'i'}, {'p', 'l', 'i'}, {'p', 'l', 'i'}, {'p', 'l', 'i'}}, // Pali + LanguageCodeEntry {{}, {'p', 'a', 'p'}, {'p', 'a', 'p'}, {'p', 'a', 'p'}}, // Papiamento + LanguageCodeEntry {{'p', 's'}, {'p', 'u', 's'}, {'p', 'u', 's'}, {'p', 'u', 's'}}, // Pashto + LanguageCodeEntry {{'f', 'a'}, {'p', 'e', 'r'}, {'f', 'a', 's'}, {'f', 'a', 's'}}, // Persian + LanguageCodeEntry {{}, {'p', 'h', 'n'}, {'p', 'h', 'n'}, {'p', 'h', 'n'}}, // Phoenician + LanguageCodeEntry {{'p', 'l'}, {'p', 'o', 'l'}, {'p', 'o', 'l'}, {'p', 'o', 'l'}}, // Polish + LanguageCodeEntry {{'p', 't'}, {'p', 'o', 'r'}, {'p', 'o', 'r'}, {'p', 'o', 'r'}}, // Portuguese + LanguageCodeEntry {{}, {}, {}, {'p', 'r', 'g'}}, // Prussian + LanguageCodeEntry {{'p', 'a'}, {'p', 'a', 'n'}, {'p', 'a', 'n'}, {'p', 'a', 'n'}}, // Punjabi + LanguageCodeEntry {{'q', 'u'}, {'q', 'u', 'e'}, {'q', 'u', 'e'}, {'q', 'u', 'e'}}, // Quechua + LanguageCodeEntry {{'r', 'o'}, {'r', 'u', 'm'}, {'r', 'o', 'n'}, {'r', 'o', 'n'}}, // Romanian + LanguageCodeEntry {{'r', 'm'}, {'r', 'o', 'h'}, {'r', 'o', 'h'}, {'r', 'o', 'h'}}, // Romansh + LanguageCodeEntry {{}, {}, {}, {'r', 'o', 'f'}}, // Rombo + LanguageCodeEntry {{'r', 'n'}, {'r', 'u', 'n'}, {'r', 'u', 'n'}, {'r', 'u', 'n'}}, // Rundi + LanguageCodeEntry {{'r', 'u'}, {'r', 'u', 's'}, {'r', 'u', 's'}, {'r', 'u', 's'}}, // Russian + LanguageCodeEntry {{}, {}, {}, {'r', 'w', 'k'}}, // Rwa + LanguageCodeEntry {{}, {}, {}, {'s', 's', 'y'}}, // Saho + LanguageCodeEntry {{}, {'s', 'a', 'h'}, {'s', 'a', 'h'}, {'s', 'a', 'h'}}, // Sakha + LanguageCodeEntry {{}, {}, {}, {'s', 'a', 'q'}}, // Samburu + LanguageCodeEntry {{'s', 'm'}, {'s', 'm', 'o'}, {'s', 'm', 'o'}, {'s', 'm', 'o'}}, // Samoan + LanguageCodeEntry {{'s', 'g'}, {'s', 'a', 'g'}, {'s', 'a', 'g'}, {'s', 'a', 'g'}}, // Sango + LanguageCodeEntry {{}, {}, {}, {'s', 'b', 'p'}}, // Sangu + LanguageCodeEntry {{'s', 'a'}, {'s', 'a', 'n'}, {'s', 'a', 'n'}, {'s', 'a', 'n'}}, // Sanskrit + LanguageCodeEntry {{}, {'s', 'a', 't'}, {'s', 'a', 't'}, {'s', 'a', 't'}}, // Santali + LanguageCodeEntry {{'s', 'c'}, {'s', 'r', 'd'}, {'s', 'r', 'd'}, {'s', 'r', 'd'}}, // Sardinian + LanguageCodeEntry {{}, {}, {}, {'s', 'a', 'z'}}, // Saurashtra + LanguageCodeEntry {{}, {}, {}, {'s', 'e', 'h'}}, // Sena + LanguageCodeEntry {{'s', 'r'}, {'s', 'r', 'p'}, {'s', 'r', 'p'}, {'s', 'r', 'p'}}, // Serbian + LanguageCodeEntry {{}, {}, {}, {'k', 's', 'b'}}, // Shambala + LanguageCodeEntry {{'s', 'n'}, {'s', 'n', 'a'}, {'s', 'n', 'a'}, {'s', 'n', 'a'}}, // Shona + LanguageCodeEntry {{'i', 'i'}, {'i', 'i', 'i'}, {'i', 'i', 'i'}, {'i', 'i', 'i'}}, // Sichuan Yi + LanguageCodeEntry {{}, {'s', 'c', 'n'}, {'s', 'c', 'n'}, {'s', 'c', 'n'}}, // Sicilian + LanguageCodeEntry {{}, {'s', 'i', 'd'}, {'s', 'i', 'd'}, {'s', 'i', 'd'}}, // Sidamo + LanguageCodeEntry {{}, {}, {}, {'s', 'z', 'l'}}, // Silesian + LanguageCodeEntry {{'s', 'd'}, {'s', 'n', 'd'}, {'s', 'n', 'd'}, {'s', 'n', 'd'}}, // Sindhi + LanguageCodeEntry {{'s', 'i'}, {'s', 'i', 'n'}, {'s', 'i', 'n'}, {'s', 'i', 'n'}}, // Sinhala + LanguageCodeEntry {{}, {'s', 'm', 's'}, {'s', 'm', 's'}, {'s', 'm', 's'}}, // Skolt Sami + LanguageCodeEntry {{'s', 'k'}, {'s', 'l', 'o'}, {'s', 'l', 'k'}, {'s', 'l', 'k'}}, // Slovak + LanguageCodeEntry {{'s', 'l'}, {'s', 'l', 'v'}, {'s', 'l', 'v'}, {'s', 'l', 'v'}}, // Slovenian + LanguageCodeEntry {{}, {}, {}, {'x', 'o', 'g'}}, // Soga + LanguageCodeEntry {{'s', 'o'}, {'s', 'o', 'm'}, {'s', 'o', 'm'}, {'s', 'o', 'm'}}, // Somali + LanguageCodeEntry {{}, {}, {}, {'s', 'd', 'h'}}, // Southern Kurdish + LanguageCodeEntry {{}, {'s', 'm', 'a'}, {'s', 'm', 'a'}, {'s', 'm', 'a'}}, // Southern Sami + LanguageCodeEntry {{'s', 't'}, {'s', 'o', 't'}, {'s', 'o', 't'}, {'s', 'o', 't'}}, // Southern Sotho + LanguageCodeEntry {{'n', 'r'}, {'n', 'b', 'l'}, {'n', 'b', 'l'}, {'n', 'b', 'l'}}, // South Ndebele + LanguageCodeEntry {{'e', 's'}, {'s', 'p', 'a'}, {'s', 'p', 'a'}, {'s', 'p', 'a'}}, // Spanish + LanguageCodeEntry {{}, {'z', 'g', 'h'}, {'z', 'g', 'h'}, {'z', 'g', 'h'}}, // Standard Moroccan Tamazight + LanguageCodeEntry {{'s', 'u'}, {'s', 'u', 'n'}, {'s', 'u', 'n'}, {'s', 'u', 'n'}}, // Sundanese + LanguageCodeEntry {{'s', 'w'}, {'s', 'w', 'a'}, {'s', 'w', 'a'}, {'s', 'w', 'a'}}, // Swahili + LanguageCodeEntry {{'s', 's'}, {'s', 's', 'w'}, {'s', 's', 'w'}, {'s', 's', 'w'}}, // Swati + LanguageCodeEntry {{'s', 'v'}, {'s', 'w', 'e'}, {'s', 'w', 'e'}, {'s', 'w', 'e'}}, // Swedish + LanguageCodeEntry {{}, {'g', 's', 'w'}, {'g', 's', 'w'}, {'g', 's', 'w'}}, // Swiss German + LanguageCodeEntry {{}, {'s', 'y', 'r'}, {'s', 'y', 'r'}, {'s', 'y', 'r'}}, // Syriac + LanguageCodeEntry {{}, {}, {}, {'s', 'h', 'i'}}, // Tachelhit + LanguageCodeEntry {{'t', 'y'}, {'t', 'a', 'h'}, {'t', 'a', 'h'}, {'t', 'a', 'h'}}, // Tahitian + LanguageCodeEntry {{}, {}, {}, {'b', 'l', 't'}}, // Tai Dam + LanguageCodeEntry {{}, {}, {}, {'d', 'a', 'v'}}, // Taita + LanguageCodeEntry {{'t', 'g'}, {'t', 'g', 'k'}, {'t', 'g', 'k'}, {'t', 'g', 'k'}}, // Tajik + LanguageCodeEntry {{'t', 'a'}, {'t', 'a', 'm'}, {'t', 'a', 'm'}, {'t', 'a', 'm'}}, // Tamil + LanguageCodeEntry {{}, {}, {}, {'t', 'r', 'v'}}, // Taroko + LanguageCodeEntry {{}, {}, {}, {'t', 'w', 'q'}}, // Tasawaq + LanguageCodeEntry {{'t', 't'}, {'t', 'a', 't'}, {'t', 'a', 't'}, {'t', 'a', 't'}}, // Tatar + LanguageCodeEntry {{'t', 'e'}, {'t', 'e', 'l'}, {'t', 'e', 'l'}, {'t', 'e', 'l'}}, // Telugu + LanguageCodeEntry {{}, {}, {}, {'t', 'e', 'o'}}, // Teso + LanguageCodeEntry {{'t', 'h'}, {'t', 'h', 'a'}, {'t', 'h', 'a'}, {'t', 'h', 'a'}}, // Thai + LanguageCodeEntry {{'b', 'o'}, {'t', 'i', 'b'}, {'b', 'o', 'd'}, {'b', 'o', 'd'}}, // Tibetan + LanguageCodeEntry {{}, {'t', 'i', 'g'}, {'t', 'i', 'g'}, {'t', 'i', 'g'}}, // Tigre + LanguageCodeEntry {{'t', 'i'}, {'t', 'i', 'r'}, {'t', 'i', 'r'}, {'t', 'i', 'r'}}, // Tigrinya + LanguageCodeEntry {{}, {'t', 'k', 'l'}, {'t', 'k', 'l'}, {'t', 'k', 'l'}}, // Tokelau + LanguageCodeEntry {{}, {'t', 'p', 'i'}, {'t', 'p', 'i'}, {'t', 'p', 'i'}}, // Tok Pisin + LanguageCodeEntry {{'t', 'o'}, {'t', 'o', 'n'}, {'t', 'o', 'n'}, {'t', 'o', 'n'}}, // Tongan + LanguageCodeEntry {{'t', 's'}, {'t', 's', 'o'}, {'t', 's', 'o'}, {'t', 's', 'o'}}, // Tsonga + LanguageCodeEntry {{'t', 'n'}, {'t', 's', 'n'}, {'t', 's', 'n'}, {'t', 's', 'n'}}, // Tswana + LanguageCodeEntry {{'t', 'r'}, {'t', 'u', 'r'}, {'t', 'u', 'r'}, {'t', 'u', 'r'}}, // Turkish + LanguageCodeEntry {{'t', 'k'}, {'t', 'u', 'k'}, {'t', 'u', 'k'}, {'t', 'u', 'k'}}, // Turkmen + LanguageCodeEntry {{}, {'t', 'v', 'l'}, {'t', 'v', 'l'}, {'t', 'v', 'l'}}, // Tuvalu + LanguageCodeEntry {{}, {}, {}, {'k', 'c', 'g'}}, // Tyap + LanguageCodeEntry {{}, {'u', 'g', 'a'}, {'u', 'g', 'a'}, {'u', 'g', 'a'}}, // Ugaritic + LanguageCodeEntry {{'u', 'k'}, {'u', 'k', 'r'}, {'u', 'k', 'r'}, {'u', 'k', 'r'}}, // Ukrainian + LanguageCodeEntry {{}, {'h', 's', 'b'}, {'h', 's', 'b'}, {'h', 's', 'b'}}, // Upper Sorbian + LanguageCodeEntry {{'u', 'r'}, {'u', 'r', 'd'}, {'u', 'r', 'd'}, {'u', 'r', 'd'}}, // Urdu + LanguageCodeEntry {{'u', 'g'}, {'u', 'i', 'g'}, {'u', 'i', 'g'}, {'u', 'i', 'g'}}, // Uyghur + LanguageCodeEntry {{'u', 'z'}, {'u', 'z', 'b'}, {'u', 'z', 'b'}, {'u', 'z', 'b'}}, // Uzbek + LanguageCodeEntry {{}, {'v', 'a', 'i'}, {'v', 'a', 'i'}, {'v', 'a', 'i'}}, // Vai + LanguageCodeEntry {{'v', 'e'}, {'v', 'e', 'n'}, {'v', 'e', 'n'}, {'v', 'e', 'n'}}, // Venda + LanguageCodeEntry {{'v', 'i'}, {'v', 'i', 'e'}, {'v', 'i', 'e'}, {'v', 'i', 'e'}}, // Vietnamese + LanguageCodeEntry {{'v', 'o'}, {'v', 'o', 'l'}, {'v', 'o', 'l'}, {'v', 'o', 'l'}}, // Volapuk + LanguageCodeEntry {{}, {}, {}, {'v', 'u', 'n'}}, // Vunjo + LanguageCodeEntry {{'w', 'a'}, {'w', 'l', 'n'}, {'w', 'l', 'n'}, {'w', 'l', 'n'}}, // Walloon + LanguageCodeEntry {{}, {}, {}, {'w', 'a', 'e'}}, // Walser + LanguageCodeEntry {{}, {}, {}, {'w', 'b', 'p'}}, // Warlpiri + LanguageCodeEntry {{'c', 'y'}, {'w', 'e', 'l'}, {'c', 'y', 'm'}, {'c', 'y', 'm'}}, // Welsh + LanguageCodeEntry {{}, {}, {}, {'b', 'g', 'n'}}, // Western Balochi + LanguageCodeEntry {{'f', 'y'}, {'f', 'r', 'y'}, {'f', 'r', 'y'}, {'f', 'r', 'y'}}, // Western Frisian + LanguageCodeEntry {{}, {'w', 'a', 'l'}, {'w', 'a', 'l'}, {'w', 'a', 'l'}}, // Wolaytta + LanguageCodeEntry {{'w', 'o'}, {'w', 'o', 'l'}, {'w', 'o', 'l'}, {'w', 'o', 'l'}}, // Wolof + LanguageCodeEntry {{'x', 'h'}, {'x', 'h', 'o'}, {'x', 'h', 'o'}, {'x', 'h', 'o'}}, // Xhosa + LanguageCodeEntry {{}, {}, {}, {'y', 'a', 'v'}}, // Yangben + LanguageCodeEntry {{'y', 'i'}, {'y', 'i', 'd'}, {'y', 'i', 'd'}, {'y', 'i', 'd'}}, // Yiddish + LanguageCodeEntry {{'y', 'o'}, {'y', 'o', 'r'}, {'y', 'o', 'r'}, {'y', 'o', 'r'}}, // Yoruba + LanguageCodeEntry {{}, {}, {}, {'d', 'j', 'e'}}, // Zarma + LanguageCodeEntry {{'z', 'a'}, {'z', 'h', 'a'}, {'z', 'h', 'a'}, {'z', 'h', 'a'}}, // Zhuang + LanguageCodeEntry {{'z', 'u'}, {'z', 'u', 'l'}, {'z', 'u', 'l'}, {'z', 'u', 'l'}}, // Zulu + LanguageCodeEntry {{}, {}, {}, {'k', 'g', 'p'}}, // Kaingang + LanguageCodeEntry {{}, {}, {}, {'y', 'r', 'l'}}, // Nheengatu + LanguageCodeEntry {{}, {}, {}, {'b', 'g', 'c'}}, // Haryanvi + LanguageCodeEntry {{}, {'f', 'r', 'r'}, {'f', 'r', 'r'}, {'f', 'r', 'r'}}, // Northern Frisian + LanguageCodeEntry {{}, {'r', 'a', 'j'}, {'r', 'a', 'j'}, {'r', 'a', 'j'}}, // Rajasthani + LanguageCodeEntry {{}, {'m', 'd', 'f'}, {'m', 'd', 'f'}, {'m', 'd', 'f'}}, // Moksha + LanguageCodeEntry {{}, {}, {}, {'t', 'o', 'k'}}, // Toki Pona + LanguageCodeEntry {{}, {}, {}, {'p', 'i', 's'}}, // Pijin + LanguageCodeEntry {{}, {}, {}, {'a', 'n', 'n'}}, // Obolo + LanguageCodeEntry {{}, {'b', 'a', 'l'}, {'b', 'a', 'l'}, {'b', 'a', 'l'}}, // Baluchi + LanguageCodeEntry {{}, {}, {}, {'l', 'i', 'j'}}, // Ligurian + LanguageCodeEntry {{}, {}, {}, {'r', 'h', 'g'}}, // Rohingya + LanguageCodeEntry {{}, {}, {}, {'t', 'r', 'w'}}, // Torwali }; static constexpr unsigned char script_code_list[] = diff --git a/src/corelib/text/qlocale_p.h b/src/corelib/text/qlocale_p.h index cb1adfbe..ace92e66 100644 --- a/src/corelib/text/qlocale_p.h +++ b/src/corelib/text/qlocale_p.h @@ -28,9 +28,68 @@ #include #include +#include QT_BEGIN_NAMESPACE +template struct QCharacterSetMatch +{ + static constexpr int MaxRange = std::numeric_limits::digits; + MaskType mask; + + constexpr QCharacterSetMatch(std::string_view set) + : mask(0) + { + for (char c : set) { + int idx = uchar(c) - Lowest; + mask |= MaskType(1) << idx; + } + } + + constexpr bool matches(uchar c) const + { + unsigned idx = c - Lowest; + if (idx >= MaxRange) + return false; + return (mask >> idx) & 1; + } +}; + +namespace QtPrivate { +inline constexpr char ascii_space_chars[] = + "\t" // 9: HT - horizontal tab + "\n" // 10: LF - line feed + "\v" // 11: VT - vertical tab + "\f" // 12: FF - form feed + "\r" // 13: CR - carriage return + " "; // 32: space + +template +inline constexpr auto makeCharacterSetMatch() +{ + constexpr auto view = std::string_view(Set); + constexpr uchar MinElement = *std::min_element(view.begin(), view.end()); + constexpr uchar MaxElement = *std::max_element(view.begin(), view.end()); + constexpr int Range = MaxElement - MinElement; + static_assert(Range < 64, "Characters in the set are 64 or more values apart"); + + if constexpr (ForcedLowest >= 0) { + // use the force + static_assert(ForcedLowest <= int(MinElement), "The force is not with you"); + using MaskType = std::conditional_t; + return QCharacterSetMatch(view); + } else if constexpr (MaxElement < std::numeric_limits::digits) { + // if we can use a Lowest of zero, we can remove a subtraction + // from the matches() code at runtime + using MaskType = std::conditional_t<(MaxElement < 32), quint32, qregisteruint>; + return QCharacterSetMatch(view); + } else { + using MaskType = std::conditional_t<(Range < 32), quint32, quint64>; + return QCharacterSetMatch(view); + } +} +} // QtPrivate + struct QLocaleData; // Subclassed by Android platform plugin: class Q_CORE_EXPORT QSystemLocale @@ -152,6 +211,9 @@ struct QLocaleId }; Q_DECLARE_TYPEINFO(QLocaleId, Q_PRIMITIVE_TYPE); + +using CharBuff = QVarLengthArray; + struct QLocaleData { public: @@ -185,8 +247,6 @@ public: enum NumberMode { IntegerMode, DoubleStandardMode, DoubleScientificMode }; - typedef QVarLengthArray CharBuff; - private: enum PrecisionMode { PMDecimalDigits = 0x01, @@ -252,8 +312,53 @@ public: [[nodiscard]] static quint64 bytearrayToUnsLongLong(QByteArrayView num, int base, bool *ok); [[nodiscard]] bool numberToCLocale(QStringView s, QLocale::NumberOptions number_options, - CharBuff *result) const; - [[nodiscard]] inline char numericToCLocale(QStringView in) const; + NumberMode mode, CharBuff *result) const; + + struct NumericData + { +#ifndef QT_NO_SYSTEMLOCALE + // Only used for the system locale, to store data for the view to look at: + QString sysDecimal, sysGroup, sysMinus, sysPlus; +#endif + QStringView decimal, group, minus, plus, exponent; + char32_t zeroUcs = 0; + qint8 zeroLen = 0; + bool isC = false; // C locale sets this and nothing else. + bool exponentCyrillic = false; // True only for floating-point parsing of Cyrillic. + void setZero(QStringView zero) + { + // No known locale has digits that are more than one Unicode + // code-point, so we can safely deal with digits as plain char32_t. + switch (zero.size()) { + case 1: + Q_ASSERT(!zero.at(0).isSurrogate()); + zeroUcs = zero.at(0).unicode(); + zeroLen = 1; + break; + case 2: + Q_ASSERT(zero.at(0).isHighSurrogate()); + zeroUcs = QChar::surrogateToUcs4(zero.at(0), zero.at(1)); + zeroLen = 2; + break; + default: + Q_ASSERT(zero.size() == 0); // i.e. we got no value to use + break; + } + } + [[nodiscard]] bool isValid(NumberMode mode) const // Asserted as a sanity check. + { + if (isC) + return true; + if (exponentCyrillic && exponent != u"E" && exponent != u"\u0415") + return false; + return (zeroLen == 1 || zeroLen == 2) && zeroUcs > 0 + && (mode == IntegerMode || !decimal.isEmpty()) + // group may be empty (user config in system locale) + && !minus.isEmpty() && !plus.isEmpty() + && (mode != DoubleScientificMode || !exponent.isEmpty()); + } + }; + [[nodiscard]] inline NumericData numericData(NumberMode mode) const; // this function is used in QIntValidator (QtGui) [[nodiscard]] Q_CORE_EXPORT bool validateChars( @@ -379,7 +484,7 @@ public: [[nodiscard]] QByteArray bcp47Name(char separator = '-') const; - [[nodiscard]] inline QLatin1StringView + [[nodiscard]] inline std::array languageCode(QLocale::LanguageCodeTypes codeTypes = QLocale::AnyLanguageCode) const { return languageToCode(QLocale::Language(m_data->m_language_id), codeTypes); @@ -390,7 +495,7 @@ public: { return territoryToCode(QLocale::Territory(m_data->m_territory_id)); } [[nodiscard]] static const QLocalePrivate *get(const QLocale &l) { return l.d; } - [[nodiscard]] static QLatin1StringView + [[nodiscard]] static std::array languageToCode(QLocale::Language language, QLocale::LanguageCodeTypes codeTypes = QLocale::AnyLanguageCode); [[nodiscard]] static QLatin1StringView scriptToCode(QLocale::Script script); @@ -424,58 +529,6 @@ inline QLocalePrivate *QSharedDataPointer::clone() return new QLocalePrivate(d->m_data, d->m_index, d->m_numberOptions); } -inline char QLocaleData::numericToCLocale(QStringView in) const -{ - Q_ASSERT(in.size() == 1 || (in.size() == 2 && in.at(0).isHighSurrogate())); - - if (in == positiveSign() || in == u"+") - return '+'; - - if (in == negativeSign() || in == u"-" || in == u"\x2212") - return '-'; - - if (in == decimalPoint()) - return '.'; - - if (const QString exp = exponentSeparator(); - in.compare(exp, Qt::CaseInsensitive) == 0 - || (m_script_id == QLocale::CyrillicScript - // Ukrainian officially uses the Cyrillic E, other Cyrillic-script - // languages use Latin E, but these are indistinguishable. - && in.compare(exp == u'\u0415' ? u'E' : u'\u0415', Qt::CaseInsensitive) == 0)) { - return 'e'; - } - - const QString group = groupSeparator(); - if (in == group) - return ','; - - // In several languages group() is a non-breaking space (U+00A0) or its thin - // version (U+202f), which look like spaces. People (and thus some of our - // tests) use a regular space instead and complain if it doesn't work. - // Should this be extended generally to any case where group is a space ? - if ((group == u"\xa0" || group == u"\x202f") && in == u" ") - return ','; - - const char32_t inUcs4 = in.size() == 2 - ? QChar::surrogateToUcs4(in.at(0), in.at(1)) : in.at(0).unicode(); - const char32_t zeroUcs4 = zeroUcs(); - // Must match qlocale_tools.h's unicodeForDigit() - if (zeroUcs4 == u'\u3007') { - // QTBUG-85409: Suzhou's digits aren't contiguous ! - if (inUcs4 == zeroUcs4) - return '0'; - if (inUcs4 > u'\u3020' && inUcs4 <= u'\u3029') - return inUcs4 - u'\u3020'; - } else if (zeroUcs4 <= inUcs4 && inUcs4 < zeroUcs4 + 10) { - return '0' + inUcs4 - zeroUcs4; - } - if ('0' <= inUcs4 && inUcs4 <= '9') - return inUcs4; - - return 0; -} - // Also used to merely skip over an escape in a format string, advancint idx to // point after it (so not [[nodiscard]]): QString qt_readEscapedFormatString(QStringView format, qsizetype *idx); @@ -483,15 +536,10 @@ QString qt_readEscapedFormatString(QStringView format, qsizetype *idx); QStringView *script = nullptr, QStringView *cntry = nullptr); [[nodiscard]] qsizetype qt_repeatCount(QStringView s); -enum { AsciiSpaceMask = (1u << (' ' - 1)) | - (1u << ('\t' - 1)) | // 9: HT - horizontal tab - (1u << ('\n' - 1)) | // 10: LF - line feed - (1u << ('\v' - 1)) | // 11: VT - vertical tab - (1u << ('\f' - 1)) | // 12: FF - form feed - (1u << ('\r' - 1)) }; // 13: CR - carriage return [[nodiscard]] constexpr inline bool ascii_isspace(uchar c) { - return c >= 1u && c <= 32u && (AsciiSpaceMask >> uint(c - 1)) & 1u; + constexpr auto matcher = QtPrivate::makeCharacterSetMatch(); + return matcher.matches(c); } QT_END_NAMESPACE diff --git a/src/corelib/text/qlocale_tools.cpp b/src/corelib/text/qlocale_tools.cpp index 2d0ebd2d..0402203c 100644 --- a/src/corelib/text/qlocale_tools.cpp +++ b/src/corelib/text/qlocale_tools.cpp @@ -307,7 +307,7 @@ QSimpleParsedNumber qt_asciiToDouble(const char *num, qsizetype numLen, // a number over 2 GB in length is silly, just assume it isn't valid return {}; } else { - d = conv.StringToDouble(num, numLen, &processed); + d = conv.StringToDouble(num, int(numLen), &processed); } if (!qIsFinite(d)) { @@ -555,7 +555,9 @@ QString qulltoa(qulonglong number, int base, const QStringView zero) /*! \internal - Converts the initial portion of the string pointed to by \a s00 to a double, using the 'C' locale. + Converts the initial portion of the string pointed to by \a s00 to a double, + using the 'C' locale. The function sets the pointer pointed to by \a se to + point to the character past the last character converted. */ double qstrntod(const char *s00, qsizetype len, const char **se, bool *ok) { diff --git a/src/corelib/text/qlocale_tools_p.h b/src/corelib/text/qlocale_tools_p.h index 61c4061f..3e1d441f 100644 --- a/src/corelib/text/qlocale_tools_p.h +++ b/src/corelib/text/qlocale_tools_p.h @@ -68,7 +68,7 @@ void qt_doubleToAscii(double d, QLocaleData::DoubleForm form, int precision, template [[nodiscard]] inline UcsInt unicodeForDigit(uint digit, UcsInt zero) { - // Must match QLocaleData::numericToCLocale()'s digit-digestion. + // Must match qlocale.cpp's NumberTokenizer's digit-digestion. Q_ASSERT(digit < 10); if (!digit) return zero; diff --git a/src/corelib/text/qregularexpression.cpp b/src/corelib/text/qregularexpression.cpp index 8b3cf63d..93e4f7d1 100644 --- a/src/corelib/text/qregularexpression.cpp +++ b/src/corelib/text/qregularexpression.cpp @@ -1856,22 +1856,30 @@ QString QRegularExpression::escape(QStringView str) \value UnanchoredWildcardConversion The conversion will not anchor the pattern. This allows for partial string matches of wildcard expressions. + + \value [since 6.6] NonPathWildcardConversion + The conversion will \e{not} interpret the pattern as filepath globbing. + + \sa QRegularExpression::wildcardToRegularExpression */ /*! \since 5.15 Returns a regular expression representation of the given glob \a pattern. - The transformation is targeting file path globbing, which means in particular - that path separators receive special treatment. This implies that it is not - just a basic translation from "*" to ".*". + + There are two transformations possible, one that targets file path + globbing, and another one which is more generic. + + By default, the transformation is targeting file path globbing, + which means in particular that path separators receive special + treatment. This implies that it is not just a basic translation + from "*" to ".*" and similar. \snippet code/src_corelib_text_qregularexpression.cpp 31 - By default, the returned regular expression is fully anchored. In other - words, there is no need of calling anchoredPattern() again on the - result. To get a regular expression that is not anchored, pass - UnanchoredWildcardConversion as the conversion \a options. + The more generic globbing transformation is available by passing + \c NonPathWildcardConversion in the conversion \a options. This implementation follows closely the definition of wildcard for glob patterns: @@ -1880,10 +1888,12 @@ QString QRegularExpression::escape(QStringView str) \li Any character represents itself apart from those mentioned below. Thus \b{c} matches the character \e c. \row \li \b{?} - \li Matches any single character. It is the same as - \b{.} in full regexps. + \li Matches any single character, except for a path separator + (in case file path globbing has been selected). It is the + same as b{.} in full regexps. \row \li \b{*} - \li Matches zero or more of any characters. It is the + \li Matches zero or more of any characters, except for path + separators (in case file path globbing has been selected). It is the same as \b{.*} in full regexps. \row \li \b{[abc]} \li Matches one character given in the bracket. @@ -1897,9 +1907,10 @@ QString QRegularExpression::escape(QStringView str) bracket. It is the same as \b{[^a-c]} in full regexp. \endtable - \note The backslash (\\) character is \e not an escape char in this context. - In order to match one of the special characters, place it in square brackets - (for example, \c{[?]}). + \note For historical reasons, a backslash (\\) character is \e not + an escape char in this context. In order to match one of the + special characters, place it in square brackets (for example, + \c{[?]}). More information about the implementation can be found in: \list @@ -1907,6 +1918,11 @@ QString QRegularExpression::escape(QStringView str) \li \c {man 7 glob} \endlist + By default, the returned regular expression is fully anchored. In other + words, there is no need of calling anchoredPattern() again on the + result. To get a regular expression that is not anchored, pass + UnanchoredWildcardConversion in the conversion \a options. + \sa escape() */ QString QRegularExpression::wildcardToRegularExpression(QStringView pattern, WildcardConversionOptions options) @@ -1917,29 +1933,51 @@ QString QRegularExpression::wildcardToRegularExpression(QStringView pattern, Wil qsizetype i = 0; const QChar *wc = pattern.data(); + struct GlobSettings { + char16_t nativePathSeparator; + QStringView starEscape; + QStringView questionMarkEscape; + }; + + const GlobSettings settings = [options]() { + if (options.testFlag(NonPathWildcardConversion)) { + // using [\d\D] to mean "match everything"; + // dot doesn't match newlines, unless in /s mode + return GlobSettings{ u'\0', u"[\\d\\D]*", u"[\\d\\D]" }; + } else { #ifdef Q_OS_WIN - const char16_t nativePathSeparator = u'\\'; - const auto starEscape = "[^/\\\\]*"_L1; - const auto questionMarkEscape = "[^/\\\\]"_L1; + return GlobSettings{ u'\\', u"[^/\\\\]*", u"[^/\\\\]" }; #else - const char16_t nativePathSeparator = u'/'; - const auto starEscape = "[^/]*"_L1; - const auto questionMarkEscape = "[^/]"_L1; + return GlobSettings{ u'/', u"[^/]*", u"[^/]" }; #endif + } + }(); while (i < wclen) { const QChar c = wc[i++]; switch (c.unicode()) { case '*': - rx += starEscape; + rx += settings.starEscape; break; case '?': - rx += questionMarkEscape; + rx += settings.questionMarkEscape; break; + // When not using filepath globbing: \ is escaped, / is itself + // When using filepath globbing: + // * Unix: \ gets escaped. / is itself + // * Windows: \ and / can match each other -- they become [/\\] in regexp case '\\': #ifdef Q_OS_WIN + if (options.testFlag(NonPathWildcardConversion)) + rx += u"\\\\"; + else + rx += u"[/\\\\]"; + break; case '/': - rx += "[/\\\\]"_L1; + if (options.testFlag(NonPathWildcardConversion)) + rx += u'/'; + else + rx += u"[/\\\\]"; break; #endif case '$': @@ -1967,11 +2005,13 @@ QString QRegularExpression::wildcardToRegularExpression(QStringView pattern, Wil rx += wc[i++]; while (i < wclen && wc[i] != u']') { - // The '/' appearing in a character class invalidates the - // regular expression parsing. It also concerns '\\' on - // Windows OS types. - if (wc[i] == u'/' || wc[i] == nativePathSeparator) - return rx; + if (!options.testFlag(NonPathWildcardConversion)) { + // The '/' appearing in a character class invalidates the + // regular expression parsing. It also concerns '\\' on + // Windows OS types. + if (wc[i] == u'/' || wc[i] == settings.nativePathSeparator) + return rx; + } if (wc[i] == u'\\') rx += u'\\'; rx += wc[i++]; diff --git a/src/corelib/text/qregularexpression.h b/src/corelib/text/qregularexpression.h index 4f1ad8d1..a264717b 100644 --- a/src/corelib/text/qregularexpression.h +++ b/src/corelib/text/qregularexpression.h @@ -131,7 +131,8 @@ public: enum WildcardConversionOption { DefaultWildcardConversion = 0x0, - UnanchoredWildcardConversion = 0x1 + UnanchoredWildcardConversion = 0x1, + NonPathWildcardConversion = 0x2, }; Q_DECLARE_FLAGS(WildcardConversionOptions, WildcardConversionOption) diff --git a/src/corelib/text/qstring.cpp b/src/corelib/text/qstring.cpp index 0168f157..39ae476f 100644 --- a/src/corelib/text/qstring.cpp +++ b/src/corelib/text/qstring.cpp @@ -23,8 +23,9 @@ #include "qdebug.h" #include "qendian.h" #include "qcollator.h" +#include "qttypetraits.h" -#ifdef Q_OS_MAC +#ifdef Q_OS_DARWIN #include #endif @@ -65,7 +66,6 @@ #define ULLONG_MAX quint64_C(18446744073709551615) #endif -#define IS_RAW_DATA(d) ((d.d)->flags & QArrayData::RawDataType) #define REHASH(a) \ if (sl_minus_1 < sizeof(std::size_t) * CHAR_BIT) \ hashHaystack -= std::size_t(a) << sl_minus_1; \ @@ -281,8 +281,8 @@ bool qt_ends_with_impl(Haystack haystack, Needle needle, Qt::CaseSensitivity cs) return QtPrivate::compareStrings(haystack.right(needleLen), needle, cs) == 0; } -template -static void append_helper(QString &self, T view, F appendToUtf16) +template +static void append_helper(QString &self, T view) { const auto strData = view.data(); const qsizetype strSize = view.size(); @@ -293,8 +293,18 @@ static void append_helper(QString &self, T view, F appendToUtf16) d.detachAndGrow(QArrayData::GrowsAtEnd, strSize, nullptr, nullptr); Q_CHECK_PTR(d.data()); Q_ASSERT(strSize <= d.freeSpaceAtEnd()); - const auto newEnd = appendToUtf16(self.data() + self.size(), view); - self.resize(newEnd - std::as_const(self).data()); + + auto dst = std::next(d.data(), d.size); + if constexpr (std::is_same_v) { + dst = QUtf8::convertToUnicode(dst, view); + } else if constexpr (std::is_same_v) { + QLatin1::convertToUnicode(dst, view); + dst += strSize; + } else { + static_assert(QtPrivate::type_dependent_false(), + "Can only operate on UTF-8 and Latin-1"); + } + self.resize(std::distance(d.begin(), dst)); } else if (d.isNull() && !view.isNull()) { // special case self = QLatin1StringView(""); } @@ -970,7 +980,7 @@ Q_CORE_EXPORT void qt_from_latin1(char16_t *dst, const char *str, size_t size) n return; } else { size = size % 4; - return UnrollTailLoop<3>::exec(qsizetype(size), [=](int i) { dst[i] = (uchar)str[i]; }); + return UnrollTailLoop<3>::exec(qsizetype(size), [=](qsizetype i) { dst[i] = uchar(str[i]); }); } # endif #endif @@ -987,7 +997,7 @@ Q_CORE_EXPORT void qt_from_latin1(char16_t *dst, const char *str, size_t size) n #endif } -static QVarLengthArray qt_from_latin1_to_qvla(QLatin1StringView str) noexcept +static QVarLengthArray qt_from_latin1_to_qvla(QLatin1StringView str) { const qsizetype len = str.size(); QVarLengthArray arr(len); @@ -1189,7 +1199,7 @@ void qt_to_latin1_unchecked(uchar *dst, const char16_t *src, qsizetype length) Q_NEVER_INLINE static int ucstricmp(qsizetype alen, const char16_t *a, qsizetype blen, const char16_t *b) { if (a == b) - return (alen - blen); + return qt_lencmp(alen, blen); char32_t alast = 0; char32_t blast = 0; @@ -1241,7 +1251,7 @@ Q_NEVER_INLINE static int ucstricmp8(const char *utf8, const char *utf8end, cons char32_t uc1 = 0; char32_t *output = &uc1; uchar b = *src1++; - int res = QUtf8Functions::fromUtf8(b, output, src1, end1); + const qsizetype res = QUtf8Functions::fromUtf8(b, output, src1, end1); if (res < 0) { // decoding error uc1 = QChar::ReplacementCharacter; @@ -1367,25 +1377,8 @@ static int ucstrcmp(const char16_t *a, size_t alen, const Char2 *b, size_t blen) return cmp ? cmp : qt_lencmp(alen, blen); } -static constexpr uchar latin1Lower[256] = { - 0x00,0x01,0x02,0x03,0x04,0x05,0x06,0x07,0x08,0x09,0x0a,0x0b,0x0c,0x0d,0x0e,0x0f, - 0x10,0x11,0x12,0x13,0x14,0x15,0x16,0x17,0x18,0x19,0x1a,0x1b,0x1c,0x1d,0x1e,0x1f, - 0x20,0x21,0x22,0x23,0x24,0x25,0x26,0x27,0x28,0x29,0x2a,0x2b,0x2c,0x2d,0x2e,0x2f, - 0x30,0x31,0x32,0x33,0x34,0x35,0x36,0x37,0x38,0x39,0x3a,0x3b,0x3c,0x3d,0x3e,0x3f, - 0x40,0x61,0x62,0x63,0x64,0x65,0x66,0x67,0x68,0x69,0x6a,0x6b,0x6c,0x6d,0x6e,0x6f, - 0x70,0x71,0x72,0x73,0x74,0x75,0x76,0x77,0x78,0x79,0x7a,0x5b,0x5c,0x5d,0x5e,0x5f, - 0x60,0x61,0x62,0x63,0x64,0x65,0x66,0x67,0x68,0x69,0x6a,0x6b,0x6c,0x6d,0x6e,0x6f, - 0x70,0x71,0x72,0x73,0x74,0x75,0x76,0x77,0x78,0x79,0x7a,0x7b,0x7c,0x7d,0x7e,0x7f, - 0x80,0x81,0x82,0x83,0x84,0x85,0x86,0x87,0x88,0x89,0x8a,0x8b,0x8c,0x8d,0x8e,0x8f, - 0x90,0x91,0x92,0x93,0x94,0x95,0x96,0x97,0x98,0x99,0x9a,0x9b,0x9c,0x9d,0x9e,0x9f, - 0xa0,0xa1,0xa2,0xa3,0xa4,0xa5,0xa6,0xa7,0xa8,0xa9,0xaa,0xab,0xac,0xad,0xae,0xaf, - 0xb0,0xb1,0xb2,0xb3,0xb4,0xb5,0xb6,0xb7,0xb8,0xb9,0xba,0xbb,0xbc,0xbd,0xbe,0xbf, - // 0xd7 (multiplication sign) and 0xdf (sz ligature) complicate life - 0xe0,0xe1,0xe2,0xe3,0xe4,0xe5,0xe6,0xe7,0xe8,0xe9,0xea,0xeb,0xec,0xed,0xee,0xef, - 0xf0,0xf1,0xf2,0xf3,0xf4,0xf5,0xf6,0xd7,0xf8,0xf9,0xfa,0xfb,0xfc,0xfd,0xfe,0xdf, - 0xe0,0xe1,0xe2,0xe3,0xe4,0xe5,0xe6,0xe7,0xe8,0xe9,0xea,0xeb,0xec,0xed,0xee,0xef, - 0xf0,0xf1,0xf2,0xf3,0xf4,0xf5,0xf6,0xf7,0xf8,0xf9,0xfa,0xfb,0xfc,0xfd,0xfe,0xff -}; +using CaseInsensitiveL1 = QtPrivate::QCaseInsensitiveLatin1Hash; + static int latin1nicmp(const char *lhsChar, qsizetype lSize, const char *rhsChar, qsizetype rSize) { // We're called with QLatin1StringView's .data() and .size(): @@ -1396,11 +1389,9 @@ static int latin1nicmp(const char *lhsChar, qsizetype lSize, const char *rhsChar return 1; const qsizetype size = std::min(lSize, rSize); - const uchar *lhs = reinterpret_cast(lhsChar); - const uchar *rhs = reinterpret_cast(rhsChar); - Q_ASSERT(lhs && rhs); // since both lSize and rSize are positive + Q_ASSERT(lhsChar && rhsChar); // since both lSize and rSize are positive for (qsizetype i = 0; i < size; i++) { - if (int res = latin1Lower[lhs[i]] - latin1Lower[rhs[i]]) + if (int res = CaseInsensitiveL1::difference(lhsChar[i], rhsChar[i])) return res; } return qt_lencmp(lSize, rSize); @@ -2042,7 +2033,7 @@ void qtWarnAboutInvalidRegularExpression(const QString &pattern, const char *whe Many strings are known at compile time. But the trivial constructor QString("Hello"), will copy the contents of the string, - treating the contents as Latin-1. To avoid this one can use the + treating the contents as Latin-1. To avoid this, one can use the QStringLiteral macro to directly create the required data at compile time. Constructing a QString out of the literal does then not cause any overhead at runtime. @@ -2096,14 +2087,21 @@ void qtWarnAboutInvalidRegularExpression(const QString &pattern, const char *whe \snippet qstring/stringbuilder.cpp 5 - A more global approach which is the most convenient but - not entirely source compatible, is to this define in your - .pro file: + A more global approach, which is more convenient but not entirely source + compatible, is to define \c QT_USE_QSTRINGBUILDER (by adding it to the compiler + flags) at build time. This will make concatenating strings with \c{'+'} work the + same way as \c{QStringBuilder} \c{'%'}. - \snippet qstring/stringbuilder.cpp 3 + \note Take care when using the \c auto keyword with the result of + string concatenation using QStringBuilder: + \snippet qstring/stringbuilder.cpp 6 - and the \c{'+'} will automatically be performed as the - \c{QStringBuilder} \c{'%'} everywhere. + Typically this is not what is expected (and can result in undefined behavior). + This issue can be fixed by specifying the return type: + \snippet qstring/stringbuilder.cpp 7 + + \note \l {https://invent.kde.org/sdk/clazy} {Clazy} has a check, auto-unexpected-qstringbuilder, + that catches this issue. \section1 Maximum Size and Out-of-memory Conditions @@ -2369,10 +2367,16 @@ void qtWarnAboutInvalidRegularExpression(const QString &pattern, const char *whe \sa fromLatin1(), fromLocal8Bit(), fromUtf8() */ +/* +//! [from-std-string] +Returns a copy of the \a str string. The given string is assumed to be +encoded in \1, and is converted to QString using the \2 function. +//! [from-std-string] +*/ + /*! \fn QString QString::fromStdString(const std::string &str) - Returns a copy of the \a str string. The given string is converted - to Unicode using the fromUtf8() function. + \include qstring.cpp {from-std-string} {UTF-8} {fromUtf8()} \sa fromLatin1(), fromLocal8Bit(), fromUtf8(), QByteArray::fromStdString() */ @@ -2415,7 +2419,7 @@ void qtWarnAboutInvalidRegularExpression(const QString &pattern, const char *whe toStdU32String() */ -qsizetype QString::toUcs4_helper(const ushort *uc, qsizetype length, uint *out) +qsizetype QString::toUcs4_helper(const char16_t *uc, qsizetype length, char32_t *out) { qsizetype count = 0; @@ -2605,6 +2609,12 @@ QString::QString(QChar ch) \internal */ +static bool needsReallocate(const QString &str, qsizetype newSize) +{ + const auto capacityAtEnd = str.capacity() - str.data_ptr().freeSpaceAtBegin(); + return newSize > capacityAtEnd; +} + /*! Sets the size of the string to \a size characters. @@ -2641,8 +2651,7 @@ void QString::resize(qsizetype size) if (size < 0) size = 0; - const auto capacityAtEnd = capacity() - d.freeSpaceAtBegin(); - if (d->needsDetach() || size > capacityAtEnd) + if (d->needsDetach() || needsReallocate(*this, size)) reallocData(size, QArrayData::Grow); d.size = size; if (d->allocatedCapacity()) @@ -2911,7 +2920,6 @@ QString &QString::operator=(QChar ch) defined. */ - /*! \fn QString& QString::insert(qsizetype position, const QByteArray &str) \since 5.5 @@ -2927,6 +2935,57 @@ QString &QString::operator=(QChar ch) defined. */ +/*! \internal + T is a view or a container on/of QChar, char16_t, or char +*/ +template +static void insert_helper(QString &str, qsizetype i, const T &toInsert) +{ + auto &str_d = str.data_ptr(); + qsizetype difference = 0; + if (Q_UNLIKELY(i > str_d.size)) + difference = i - str_d.size; + const qsizetype oldSize = str_d.size; + const qsizetype insert_size = toInsert.size(); + const qsizetype newSize = str_d.size + difference + insert_size; + const auto side = i == 0 ? QArrayData::GrowsAtBeginning : QArrayData::GrowsAtEnd; + + if (str_d.needsDetach() || needsReallocate(str, newSize)) { + const auto cbegin = str.cbegin(); + const auto cend = str.cend(); + const auto insert_start = difference == 0 ? std::next(cbegin, i) : cend; + QString other; + // Using detachAndGrow() so that prepend optimization works and QStringBuilder + // unittests pass + other.data_ptr().detachAndGrow(side, newSize, nullptr, nullptr); + other.append(QStringView(cbegin, insert_start)); + other.resize(i, u' '); + other.append(toInsert); + other.append(QStringView(insert_start, cend)); + str.swap(other); + return; + } + + str_d.detachAndGrow(side, difference + insert_size, nullptr, nullptr); + Q_CHECK_PTR(str_d.data()); + str.resize(newSize); + + auto begin = str_d.begin(); + auto old_end = std::next(begin, oldSize); + std::fill_n(old_end, difference, u' '); + auto insert_start = std::next(begin, i); + if (difference == 0) + std::move_backward(insert_start, old_end, str_d.end()); + + using Char = std::remove_cv_t; + if constexpr(std::is_same_v) + std::copy_n(reinterpret_cast(toInsert.data()), insert_size, insert_start); + else if constexpr (std::is_same_v) + std::copy_n(toInsert.data(), insert_size, insert_start); + else if constexpr (std::is_same_v) + qt_from_latin1(insert_start, toInsert.data(), insert_size); +} + /*! \fn QString &QString::insert(qsizetype position, QLatin1StringView str) \overload insert() @@ -2941,18 +3000,7 @@ QString &QString::insert(qsizetype i, QLatin1StringView str) if (i < 0 || !s || !(*s)) return *this; - qsizetype len = str.size(); - qsizetype difference = 0; - if (Q_UNLIKELY(i > size())) - difference = i - size(); - d.detachAndGrow(Data::GrowsAtEnd, difference + len, nullptr, nullptr); - Q_CHECK_PTR(d.data()); - d->copyAppend(difference, u' '); - d.size += len; - - ::memmove(d.data() + i + len, d.data() + i, (d.size - i - len) * sizeof(QChar)); - qt_from_latin1(d.data() + i, s, size_t(len)); - d.data()[d.size] = u'\0'; + insert_helper(*this, i, str); return *this; } @@ -2971,7 +3019,48 @@ QString &QString::insert(qsizetype i, QLatin1StringView str) */ QString &QString::insert(qsizetype i, QUtf8StringView s) { - return insert(i, s.toString()); // ### optimize (QTBUG-108546) + auto insert_size = s.size(); + if (i < 0 || insert_size <= 0) + return *this; + + qsizetype difference = 0; + if (Q_UNLIKELY(i > d.size)) + difference = i - d.size; + + const qsizetype newSize = d.size + difference + insert_size; + + if (d.needsDetach() || needsReallocate(*this, newSize)) { + const auto cbegin = this->cbegin(); + const auto insert_start = difference == 0 ? std::next(cbegin, i) : cend(); + QString other; + other.reserve(newSize); + other.append(QStringView(cbegin, insert_start)); + if (difference > 0) + other.resize(i, u' '); + other.append(s); + other.append(QStringView(insert_start, cend())); + swap(other); + return *this; + } + + if (i >= d.size) { + d.detachAndGrow(QArrayData::GrowsAtEnd, difference + insert_size, nullptr, nullptr); + Q_CHECK_PTR(d.data()); + + if (difference > 0) + resize(i, u' '); + append(s); + } else { + // Optimal insertion of Utf8 data is at the end, anywhere else could + // potentially lead to moving characters twice if Utf8 data size + // (variable-width) is less than the equiavalent Utf16 data size + QVarLengthArray buffer(insert_size); // ### optimize (QTBUG-108546) + char16_t *b = QUtf8::convertToUnicode(buffer.data(), s); + buffer.resize(std::distance(buffer.begin(), b)); + insert_helper(*this, i, buffer); + } + + return *this; } /*! @@ -2991,28 +3080,14 @@ QString& QString::insert(qsizetype i, const QChar *unicode, qsizetype size) if (i < 0 || size <= 0) return *this; - const char16_t *s = reinterpret_cast(unicode); - - // handle this specially, as QArrayDataOps::insert() doesn't handle out of - // bounds positions - if (i >= d->size) { - // In case when data points into the range or is == *this, we need to - // defer a call to free() so that it comes after we copied the data from - // the old memory: - DataPointer detached{}; // construction is free - d.detachAndGrow(Data::GrowsAtEnd, (i - d.size) + size, &s, &detached); - Q_CHECK_PTR(d.data()); - d->copyAppend(i - d->size, u' '); - d->copyAppend(s, s + size); - d.data()[d.size] = u'\0'; - return *this; + // In case when data points into "this" + if (!d->needsDetach() && QtPrivate::q_points_into_range(unicode, *this)) { + QVarLengthArray copy(unicode, unicode + size); + insert(i, copy.data(), size); + } else { + insert_helper(*this, i, QStringView(unicode, size)); } - if (!d->needsDetach() && QtPrivate::q_points_into_range(s, d.data(), d.data() + d.size)) - return insert(i, QStringView{QVarLengthArray(s, s + size)}); - - d->insert(i, s, size); - d.data()[d.size] = u'\0'; return *this; } @@ -3056,11 +3131,10 @@ QString &QString::append(const QString &str) { if (!str.isNull()) { if (isNull()) { - operator=(str); - if (Q_UNLIKELY(!d.isMutable() && d.size > 0)) { - d.detach(); // fromRawData, so we do a deep copy - d.data()[d.size] = u'\0'; - } + if (Q_UNLIKELY(!str.d.isMutable())) + assign(str); // fromRawData, so we do a deep copy + else + operator=(str); } else if (str.size()) { append(str.constData(), str.size()); } @@ -3101,15 +3175,7 @@ QString &QString::append(const QChar *str, qsizetype len) */ QString &QString::append(QLatin1StringView str) { - auto appendUtf16 = [](QChar *dst, QLatin1StringView str) { - const qsizetype len = str.size(); - const char *s = str.latin1(); - char16_t *i = reinterpret_cast(dst); - qt_from_latin1(i, s, size_t(len)); - return dst + len; - }; - - append_helper(*this, str, appendUtf16); + append_helper(*this, str); return *this; } @@ -3121,11 +3187,7 @@ QString &QString::append(QLatin1StringView str) */ QString &QString::append(QUtf8StringView str) { - auto appendUtf16 = [](QChar *dst, QUtf8StringView str) { - return QUtf8::convertToUnicode(dst, str); - }; - - append_helper(*this, str, appendUtf16); + append_helper(*this, str); return *this; } @@ -3247,6 +3309,105 @@ QString &QString::append(QChar ch) Prepends the character \a ch to this string. */ +/*! + \fn QString &QString::assign(QAnyStringView v) + \since 6.6 + + Replaces the contents of this string with a copy of \a v and returns a + reference to this string. + + The size of this string will be equal to the size of \a v, converted to + UTF-16 as if by \c{v.toString()}. Unlike QAnyStringView::toString(), however, + this function only allocates memory if the estimated size exceeds the capacity + of this string or this string is shared. + + \sa QAnyStringView::toString() +*/ + +/*! + \fn QString &QString::assign(qsizetype n, QChar c) + \since 6.6 + + Replaces the contents of this string with \a n copies of \a c and + returns a reference to this string. + + The size of this string will be equal to \a n, which has to be non-negative. + + This function will only allocate memory if \a n exceeds the capacity of this + string or this string is shared. + + \sa fill() +*/ + +/*! + \fn template > QString &QString::assign(InputIterator first, InputIterator last) + \since 6.6 + + Replaces the contents of this string with a copy of the elements in the + iterator range [\a first, \a last) and returns a reference to this string. + + The size of this string will be equal to the decoded length of the elements + in the range [\a first, \a last), which need not be the same as the length of + the range itself, because this function transparently recodes the input + character set to UTF-16. + + This function will only allocate memory if the number of elements in the + range, or, for non-UTF-16-encoded input, the maximum possible size of the + resulting string, exceeds the capacity of this string, or if this string is + shared. + + \note This function overload only participates in overload resolution if + \c InputIterator meets the requirements of a + \l {https://en.cppreference.com/w/cpp/named_req/InputIterator} {LegacyInputIterator} + and the \c{value_type} of \c InputIterator is one of the following character types: + \list + \li QChar + \li QLatin1Char + \li \c char16_t + \li (on platforms, such as Windows, where it is a 16-bit type) \c wchar_t + \li \c char32_t + \endlist + + \note The behavior is undefined if either argument is an iterator into *this or + [\a first, \a last) is not a valid range. +*/ + +QString &QString::assign(QAnyStringView s) +{ + if (s.size() <= capacity() && isDetached()) { + const auto offset = d.freeSpaceAtBegin(); + if (offset) + d.setBegin(d.begin() - offset); + resize(0); + s.visit([this](auto input) { + this->append(input); + }); + } else { + *this = s.toString(); + } + return *this; +} + +QString &QString::assign_helper(const char32_t *data, qsizetype len) +{ + // worst case: each char32_t requires a surrogate pair, so + const auto requiredCapacity = len * 2; + if (requiredCapacity <= capacity() && isDetached()) { + const auto offset = d.freeSpaceAtBegin(); + if (offset) + d.setBegin(d.begin() - offset); + auto begin = reinterpret_cast(d.begin()); + auto ba = QByteArrayView(reinterpret_cast(data), len * sizeof(char32_t)); + QStringConverter::State state; + const auto end = QUtf32::convertToUnicode(begin, ba, &state, DetectEndianness); + d.size = end - begin; + d.data()[d.size] = u'\0'; + } else { + *this = QString::fromUcs4(data, len); + } + return *this; +} + /*! \fn QString &QString::remove(qsizetype position, qsizetype n) @@ -3353,7 +3514,7 @@ static void removeStringImpl(QString &s, const T &needle, Qt::CaseSensitivity cs QString &QString::remove(const QString &str, Qt::CaseSensitivity cs) { const auto s = str.d.data(); - if (QtPrivate::q_points_into_range(s, d.data(), d.data() + d.size)) + if (QtPrivate::q_points_into_range(s, d)) removeStringImpl(*this, QStringView{QVarLengthArray(s, s + str.size())}, cs); else removeStringImpl(*this, qToStringViewIgnoringNull(str), cs); @@ -3489,6 +3650,98 @@ QString &QString::remove(QChar ch, Qt::CaseSensitivity cs) \sa remove() */ + +/*! \internal + Instead of detaching, or reallocating if "before" is shorter than "after" + and there isn't enough capacity, create a new string, copy characters to it + as needed, then swap it with "str". +*/ +static void replace_with_copy(QString &str, size_t *indices, qsizetype nIndices, qsizetype blen, + QStringView after) +{ + const qsizetype alen = after.size(); + const char16_t *after_b = after.utf16(); + + const QString::DataPointer &str_d = str.data_ptr(); + auto src_start = str_d.begin(); + const qsizetype newSize = str_d.size + nIndices * (alen - blen); + QString copy{ newSize, Qt::Uninitialized }; + QString::DataPointer ©_d = copy.data_ptr(); + auto dst = copy_d.begin(); + for (int i = 0; i < nIndices; ++i) { + auto hit = str_d.begin() + indices[i]; + dst = std::copy(src_start, hit, dst); + dst = std::copy_n(after_b, alen, dst); + src_start = hit + blen; + } + dst = std::copy(src_start, str_d.end(), dst); + str.swap(copy); +} + +// No detaching or reallocation is needed +static void replace_in_place(QString &str, size_t *indices, qsizetype nIndices, + qsizetype blen, QStringView after) +{ + const qsizetype alen = after.size(); + const char16_t *after_b = after.utf16(); + const char16_t *after_e = after.utf16() + after.size(); + + if (blen == alen) { // Replace in place + for (qsizetype i = 0; i < nIndices; ++i) + std::copy_n(after_b, alen, str.data_ptr().begin() + indices[i]); + } else if (blen > alen) { // Replace from front + char16_t *begin = str.data_ptr().begin(); + char16_t *hit = begin + indices[0]; + char16_t *to = hit; + to = std::copy_n(after_b, alen, to); + char16_t *movestart = hit + blen; + for (qsizetype i = 1; i < nIndices; ++i) { + hit = begin + indices[i]; + to = std::move(movestart, hit, to); + to = std::copy_n(after_b, alen, to); + movestart = hit + blen; + } + to = std::move(movestart, str.data_ptr().end(), to); + str.resize(std::distance(begin, to)); + } else { // blen < alen, Replace from back + const qsizetype oldSize = str.data_ptr().size; + const qsizetype adjust = nIndices * (alen - blen); + const qsizetype newSize = oldSize + adjust; + + str.resize(newSize); + char16_t *begin = str.data_ptr().begin(); + char16_t *moveend = begin + oldSize; + char16_t *to = str.data_ptr().end(); + + while (nIndices) { + --nIndices; + char16_t *hit = begin + indices[nIndices]; + char16_t *movestart = hit + blen; + to = std::move_backward(movestart, moveend, to); + to = std::copy_backward(after_b, after_e, to); + moveend = hit; + } + } +} + +static void replace_helper(QString &str, size_t *indices, qsizetype nIndices, qsizetype blen, QStringView after) +{ + const qsizetype oldSize = str.data_ptr().size; + const qsizetype adjust = nIndices * (after.size() - blen); + const qsizetype newSize = oldSize + adjust; + if (str.data_ptr().needsDetach() || needsReallocate(str, newSize)) { + replace_with_copy(str, indices, nIndices, blen, after); + return; + } + + if (QtPrivate::q_points_into_range(after.begin(), str)) + // Copy after if it lies inside our own d.b area (which we could + // possibly invalidate via a realloc or modify by replacement) + replace_in_place(str, indices, nIndices, blen, QVarLengthArray(after.begin(), after.end())); + else + replace_in_place(str, indices, nIndices, blen, after); +} + /*! \fn QString &QString::replace(qsizetype position, qsizetype n, const QString &after) @@ -3511,13 +3764,13 @@ QString &QString::replace(qsizetype pos, qsizetype len, const QString &after) } /*! - \fn QString &QString::replace(qsizetype position, qsizetype n, const QChar *unicode, qsizetype size) + \fn QString &QString::replace(qsizetype position, qsizetype n, const QChar *after, qsizetype alen) \overload replace() Replaces \a n characters beginning at index \a position with the - first \a size characters of the QChar array \a unicode and returns a + first \a alen characters of the QChar array \a after and returns a reference to this string. */ -QString &QString::replace(qsizetype pos, qsizetype len, const QChar *unicode, qsizetype size) +QString &QString::replace(qsizetype pos, qsizetype len, const QChar *after, qsizetype alen) { if (size_t(pos) > size_t(this->size())) return *this; @@ -3525,7 +3778,7 @@ QString &QString::replace(qsizetype pos, qsizetype len, const QChar *unicode, qs len = this->size() - pos; size_t index = pos; - replace_helper(&index, 1, len, unicode, size); + replace_helper(*this, &index, 1, len, QStringView{after, alen}); return *this; } @@ -3563,90 +3816,6 @@ QString &QString::replace(const QString &before, const QString &after, Qt::CaseS return replace(before.constData(), before.size(), after.constData(), after.size(), cs); } -namespace { // helpers for replace and its helper: -QChar *textCopy(const QChar *start, qsizetype len) -{ - const size_t size = len * sizeof(QChar); - QChar *const copy = static_cast(::malloc(size)); - Q_CHECK_PTR(copy); - ::memcpy(copy, start, size); - return copy; -} - -static bool pointsIntoRange(const QChar *ptr, const char16_t *base, qsizetype len) -{ - const QChar *const start = reinterpret_cast(base); - const std::less less; - return !less(ptr, start) && less(ptr, start + len); -} -} // end namespace - -/*! - \internal - */ -void QString::replace_helper(size_t *indices, qsizetype nIndices, qsizetype blen, const QChar *after, qsizetype alen) -{ - // Copy after if it lies inside our own d.b area (which we could - // possibly invalidate via a realloc or modify by replacement). - QChar *afterBuffer = nullptr; - if (pointsIntoRange(after, d.data(), d.size)) // Use copy in place of vulnerable original: - after = afterBuffer = textCopy(after, alen); - - QT_TRY { - if (blen == alen) { - // replace in place - detach(); - for (qsizetype i = 0; i < nIndices; ++i) - memcpy(d.data() + indices[i], after, alen * sizeof(QChar)); - } else if (alen < blen) { - // replace from front - detach(); - size_t to = indices[0]; - if (alen) - memcpy(d.data()+to, after, alen*sizeof(QChar)); - to += alen; - size_t movestart = indices[0] + blen; - for (qsizetype i = 1; i < nIndices; ++i) { - qsizetype msize = indices[i] - movestart; - if (msize > 0) { - memmove(d.data() + to, d.data() + movestart, msize * sizeof(QChar)); - to += msize; - } - if (alen) { - memcpy(d.data() + to, after, alen * sizeof(QChar)); - to += alen; - } - movestart = indices[i] + blen; - } - qsizetype msize = d.size - movestart; - if (msize > 0) - memmove(d.data() + to, d.data() + movestart, msize * sizeof(QChar)); - resize(d.size - nIndices*(blen-alen)); - } else { - // replace from back - qsizetype adjust = nIndices*(alen-blen); - qsizetype newLen = d.size + adjust; - qsizetype moveend = d.size; - resize(newLen); - - while (nIndices) { - --nIndices; - qsizetype movestart = indices[nIndices] + blen; - qsizetype insertstart = indices[nIndices] + nIndices*(alen-blen); - qsizetype moveto = insertstart + alen; - memmove(d.data() + moveto, d.data() + movestart, - (moveend - movestart)*sizeof(QChar)); - memcpy(d.data() + insertstart, after, alen * sizeof(QChar)); - moveend = movestart-blen; - } - } - } QT_CATCH(const std::bad_alloc &) { - ::free(afterBuffer); - QT_RETHROW; - } - ::free(afterBuffer); -} - /*! \since 4.5 \overload replace() @@ -3674,49 +3843,21 @@ QString &QString::replace(const QChar *before, qsizetype blen, return replace(*before, *after, cs); QStringMatcher matcher(before, blen, cs); - QChar *beforeBuffer = nullptr, *afterBuffer = nullptr; qsizetype index = 0; - while (1) { - size_t indices[1024]; - size_t pos = 0; - while (pos < 1024) { - index = matcher.indexIn(*this, index); - if (index == -1) - break; - indices[pos++] = index; - if (blen) // Step over before: - index += blen; - else // Only count one instance of empty between any two characters: - index++; - } - if (!pos) // Nothing to replace - break; - if (Q_UNLIKELY(index != -1)) { - /* - We're about to change data, that before and after might point - into, and we'll need that data for our next batch of indices. - */ - if (!afterBuffer && pointsIntoRange(after, d.data(), d.size)) - after = afterBuffer = textCopy(after, alen); - - if (!beforeBuffer && pointsIntoRange(before, d.data(), d.size)) { - beforeBuffer = textCopy(before, blen); - matcher = QStringMatcher(beforeBuffer, blen, cs); - } - } - - replace_helper(indices, pos, blen, after, alen); - - if (Q_LIKELY(index == -1)) // Nothing left to replace - break; - // The call to replace_helper just moved what index points at: - index += pos*(alen-blen); + QVarLengthArray indices; + while ((index = matcher.indexIn(*this, index)) != -1) { + indices.push_back(index); + if (blen) // Step over before: + index += blen; + else // Only count one instance of empty between any two characters: + index++; } - ::free(afterBuffer); - ::free(beforeBuffer); + if (indices.isEmpty()) + return *this; + replace_helper(*this, indices.data(), indices.size(), blen, QStringView{after, alen}); return *this; } @@ -3738,35 +3879,27 @@ QString& QString::replace(QChar ch, const QString &after, Qt::CaseSensitivity cs if (size() == 0) return *this; - char16_t cc = (cs == Qt::CaseSensitive ? ch.unicode() : ch.toCaseFolded().unicode()); + const char16_t cc = (cs == Qt::CaseSensitive ? ch.unicode() : ch.toCaseFolded().unicode()); - qsizetype index = 0; - while (1) { - size_t indices[1024]; - size_t pos = 0; - if (cs == Qt::CaseSensitive) { - while (pos < 1024 && index < size()) { - if (d.data()[index] == cc) - indices[pos++] = index; - index++; - } - } else { - while (pos < 1024 && index < size()) { - if (QChar::toCaseFolded(d.data()[index]) == cc) - indices[pos++] = index; - index++; - } + QVarLengthArray indices; + if (cs == Qt::CaseSensitive) { + const char16_t *begin = d.begin(); + const char16_t *end = d.end(); + QStringView view(begin, end); + const char16_t *hit = nullptr; + while ((hit = QtPrivate::qustrchr(view, cc)) != end) { + indices.push_back(std::distance(begin, hit)); + view = QStringView(std::next(hit), end); } - if (!pos) // Nothing to replace - break; - - replace_helper(indices, pos, 1, after.constData(), after.size()); - - if (Q_LIKELY(index == size())) // Nothing left to replace - break; - // The call to replace_helper just moved what index points at: - index += pos*(after.size() - 1); + } else { + for (qsizetype i = 0; i < d.size; ++i) + if (QChar::toCaseFolded(d.data()[i]) == cc) + indices.push_back(i); } + if (indices.isEmpty()) + return *this; + + replace_helper(*this, indices.data(), indices.size(), 1, after); return *this; } @@ -3779,27 +3912,39 @@ QString& QString::replace(QChar ch, const QString &after, Qt::CaseSensitivity cs */ QString& QString::replace(QChar before, QChar after, Qt::CaseSensitivity cs) { - if (d.size) { - const qsizetype idx = indexOf(before, 0, cs); - if (idx != -1) { - detach(); - const char16_t a = after.unicode(); - char16_t *i = d.data(); - char16_t *const e = i + d.size; - i += idx; - *i = a; - ++i; - if (cs == Qt::CaseSensitive) { - const char16_t toReplace = before.unicode(); - std::replace(i, e, toReplace, a); - } else { - const char16_t toReplace = foldCase(before.unicode()); - auto match = [toReplace](const char16_t c) { - return foldAndCompare(c, toReplace); - }; - std::replace_if(i, e, match, a); - } + const qsizetype idx = indexOf(before, 0, cs); + if (idx == -1) + return *this; + + const char16_t achar = after.unicode(); + char16_t bchar = before.unicode(); + + auto matchesCIS = [](char16_t beforeChar) { + return [beforeChar](char16_t ch) { return foldAndCompare(ch, beforeChar); }; + }; + + auto hit = d.begin() + idx; + if (!d.needsDetach()) { + *hit++ = achar; + if (cs == Qt::CaseSensitive) { + std::replace(hit, d.end(), bchar, achar); + } else { + bchar = foldCase(bchar); + std::replace_if(hit, d.end(), matchesCIS(bchar), achar); } + } else { + QString other{ d.size, Qt::Uninitialized }; + auto dest = std::copy(d.begin(), hit, other.d.begin()); + *dest++ = achar; + ++hit; + if (cs == Qt::CaseSensitive) { + std::replace_copy(hit, d.end(), dest, bchar, achar); + } else { + bchar = foldCase(bchar); + std::replace_copy_if(hit, d.end(), dest, matchesCIS(bchar), achar); + } + + swap(other); } return *this; } @@ -5375,6 +5520,21 @@ QByteArray QString::toLatin1_helper_inplace(QString &s) return QByteArray(std::move(ba_d)); } +// QLatin1 methods that use helpers from qstring.cpp +char16_t *QLatin1::convertToUnicode(char16_t *out, QLatin1StringView in) noexcept +{ + const qsizetype len = in.size(); + qt_from_latin1(out, in.data(), len); + return std::next(out, len); +} + +char *QLatin1::convertFromUnicode(char *out, QStringView in) noexcept +{ + const qsizetype len = in.size(); + qt_to_latin1(reinterpret_cast(out), in.utf16(), len); + return out + len; +} + /*! \fn QByteArray QString::toLatin1() const @@ -5395,8 +5555,7 @@ static QByteArray qt_convert_to_local_8bit(QStringView string); Returns the local 8-bit representation of the string as a QByteArray. - On Unix systems this is equivalent to toUtf8(), on Windows the systems - current code page is being used. + \include qstring.qdocinc {qstring-local-8-bit-equivalent} {toUtf8} If this string contains any characters that cannot be encoded in the local 8-bit encoding, the returned byte array is undefined. Those @@ -5593,8 +5752,7 @@ QString QString::fromLatin1(QByteArrayView ba) If \a size is \c{-1}, \c{strlen(str)} is used instead. - On Unix systems this is equivalent to fromUtf8(), on Windows the systems - current code page is being used. + \include qstring.qdocinc {qstring-local-8-bit-equivalent} {fromUtf8} \sa toLocal8Bit(), fromLatin1(), fromUtf8() */ @@ -5606,6 +5764,8 @@ QString QString::fromLatin1(QByteArrayView ba) Returns a QString initialized with the 8-bit string \a str. + \include qstring.qdocinc {qstring-local-8-bit-equivalent} {fromUtf8} + \note: any null ('\\0') bytes in the byte array will be included in this string, converted to Unicode null characters (U+0000). This behavior is different from Qt 5.x. @@ -5618,6 +5778,8 @@ QString QString::fromLatin1(QByteArrayView ba) Returns a QString initialized with the 8-bit string \a str. + \include qstring.qdocinc {qstring-local-8-bit-equivalent} {fromUtf8} + \note: any null ('\\0') bytes in the byte array will be included in this string, converted to Unicode null characters (U+0000). */ @@ -6371,7 +6533,7 @@ int QString::compare_helper(const QChar *data1, qsizetype length1, const char *d Q_ASSERT(length1 >= 0); Q_ASSERT(data1 || length1 == 0); if (!data2) - return length1; + return qt_lencmp(length1, 0); if (Q_UNLIKELY(length2 < 0)) length2 = qsizetype(strlen(data2)); return QtPrivate::compareStrings(QStringView(data1, length1), @@ -6388,6 +6550,18 @@ int QString::compare_helper(const QChar *data1, qsizetype length1, const char *d \overload compare() */ +/*! + \internal + \since 6.6 +*/ +int QLatin1StringView::compare_helper(const QLatin1StringView &s1, const char *s2, qsizetype len) noexcept +{ + // because qlatin1stringview.h can't include qutf8stringview.h + Q_ASSERT(len >= 0); + Q_ASSERT(s2 || len == 0); + return QtPrivate::compareStrings(s1, QUtf8StringView(s2, len)); +} + /*! \internal \since 4.5 @@ -7149,7 +7323,7 @@ QString QString::vasprintf(const char *cformat, va_list ap) } default: { int *n = va_arg(ap, int*); - *n = result.size(); + *n = int(result.size()); break; } } @@ -9094,8 +9268,7 @@ QString &QString::setRawData(const QChar *unicode, qsizetype size) /*! \fn QString QString::fromStdU16String(const std::u16string &str) \since 5.5 - Returns a copy of the \a str string. The given string is assumed - to be encoded in UTF-16. + \include qstring.cpp {from-std-string} {UTF-16} {fromUtf16()} \sa fromUtf16(), fromStdWString(), fromStdU32String() */ @@ -9114,8 +9287,7 @@ QString &QString::setRawData(const QChar *unicode, qsizetype size) /*! \fn QString QString::fromStdU32String(const std::u32string &str) \since 5.5 - Returns a copy of the \a str string. The given string is assumed - to be encoded in UCS-4. + \include qstring.cpp {from-std-string} {UCS-4} {fromUcs4()} \sa fromUcs4(), fromStdWString(), fromStdU16String() */ @@ -9131,1256 +9303,6 @@ QString &QString::setRawData(const QChar *unicode, qsizetype size) \sa toUcs4(), toStdWString(), toStdU16String() */ -/*! \class QLatin1StringView - \inmodule QtCore - \brief The QLatin1StringView class provides a thin wrapper around - a US-ASCII/Latin-1 encoded string literal. - - \ingroup string-processing - \reentrant - - Many of QString's member functions are overloaded to accept - \c{const char *} instead of QString. This includes the copy - constructor, the assignment operator, the comparison operators, - and various other functions such as \l{QString::insert()}{insert()}, - \l{QString::append()}{append()}, and \l{QString::prepend()}{prepend()}. - Some of these functions are optimized to avoid constructing a - QString object for the \c{const char *} data. For example, - assuming \c str is a QString, - - \snippet code/src_corelib_text_qstring.cpp 3 - - is much faster than - - \snippet code/src_corelib_text_qstring.cpp 4 - - because it doesn't construct four temporary QString objects and - make a deep copy of the character data. - - However, that is not true for all QString member functions that take - \c{const char *} and therefore applications should assume a temporary will - be created, such as in - - \snippet code/src_corelib_text_qstring.cpp 4bis - - Applications that define \l QT_NO_CAST_FROM_ASCII (as explained - in the QString documentation) don't have access to QString's - \c{const char *} API. To provide an efficient way of specifying - constant Latin-1 strings, Qt provides the QLatin1StringView, which is - just a very thin wrapper around a \c{const char *}. Using - QLatin1StringView, the example code above becomes - - \snippet code/src_corelib_text_qstring.cpp 5 - - This is a bit longer to type, but it provides exactly the same - benefits as the first version of the code, and is faster than - converting the Latin-1 strings using QString::fromLatin1(). - - Thanks to the QString(QLatin1StringView) constructor, - QLatin1StringView can be used everywhere a QString is expected. For - example: - - \snippet code/src_corelib_text_qstring.cpp 6 - - \note If the function you're calling with a QLatin1StringView - argument isn't actually overloaded to take QLatin1StringView, the - implicit conversion to QString will trigger a memory allocation, - which is usually what you want to avoid by using QLatin1StringView - in the first place. In those cases, using QStringLiteral may be - the better option. - - \sa QString, QLatin1Char, {QStringLiteral()}{QStringLiteral}, - QT_NO_CAST_FROM_ASCII -*/ - -/*! - \class QLatin1String - \inmodule QtCore - \brief QLatin1String is the same as QLatin1StringView. - - QLatin1String is a view to a Latin-1 string. It's the same as - QLatin1StringView and is kept for compatibility reasons. It is - recommended to use QLatin1StringView instead. - - Please see the QLatin1StringView documentation for details. -*/ - -/*! - \typedef QLatin1StringView::value_type - \since 5.10 - - Alias for \c{const char}. Provided for compatibility with the STL. -*/ - -/*! - \typedef QLatin1StringView::difference_type - \since 5.10 - - Alias for \c{qsizetype}. Provided for compatibility with the STL. -*/ - -/*! - \typedef QLatin1StringView::size_type - \since 5.10 - - Alias for \c{qsizetype}. Provided for compatibility with the STL. - - \note In version prior to Qt 6, this was an alias for \c{int}, - restricting the amount of data that could be held in a QLatin1StringView - on 64-bit architectures. -*/ - -/*! - \typedef QLatin1StringView::reference - \since 5.10 - - Alias for \c{value_type &}. Provided for compatibility with the STL. -*/ - -/*! - \typedef QLatin1StringView::const_reference - \since 5.11 - - Alias for \c{reference}. Provided for compatibility with the STL. -*/ - -/*! - \typedef QLatin1StringView::iterator - \since 5.10 - - QLatin1StringView does not support mutable iterators, so this is the same - as const_iterator. - - \sa const_iterator, reverse_iterator -*/ - -/*! - \typedef QLatin1StringView::const_iterator - \since 5.10 - - \sa iterator, const_reverse_iterator -*/ - -/*! - \typedef QLatin1StringView::reverse_iterator - \since 5.10 - - QLatin1StringView does not support mutable reverse iterators, so this is the - same as const_reverse_iterator. - - \sa const_reverse_iterator, iterator -*/ - -/*! - \typedef QLatin1StringView::const_reverse_iterator - \since 5.10 - - \sa reverse_iterator, const_iterator -*/ - -/*! \fn QLatin1StringView::QLatin1StringView() - \since 5.6 - - Constructs a QLatin1StringView object that stores a \nullptr. - - \sa data(), isEmpty(), isNull(), {Distinction Between Null and Empty Strings} -*/ - -/*! \fn QLatin1StringView::QLatin1StringView(std::nullptr_t) - \since 6.4 - - Constructs a QLatin1StringView object that stores a \nullptr. - - \sa data(), isEmpty(), isNull(), {Distinction Between Null and Empty Strings} -*/ - -/*! \fn QLatin1StringView::QLatin1StringView(const char *str) - - Constructs a QLatin1StringView object that stores \a str. - - The string data is \e not copied. The caller must be able to - guarantee that \a str will not be deleted or modified as long as - the QLatin1StringView object exists. - - \sa latin1() -*/ - -/*! \fn QLatin1StringView::QLatin1StringView(const char *str, qsizetype size) - - Constructs a QLatin1StringView object that stores \a str with \a size. - - The string data is \e not copied. The caller must be able to - guarantee that \a str will not be deleted or modified as long as - the QLatin1StringView object exists. - - \note: any null ('\\0') bytes in the byte array will be included in this - string, which will be converted to Unicode null characters (U+0000) if this - string is used by QString. This behavior is different from Qt 5.x. - - \sa latin1() -*/ - -/*! - \fn QLatin1StringView::QLatin1StringView(const char *first, const char *last) - \since 5.10 - - Constructs a QLatin1StringView object that stores \a first with length - (\a last - \a first). - - The range \c{[first,last)} must remain valid for the lifetime of - this Latin-1 string object. - - Passing \nullptr as \a first is safe if \a last is \nullptr, - too, and results in a null Latin-1 string. - - The behavior is undefined if \a last precedes \a first, \a first - is \nullptr and \a last is not, or if \c{last - first > - INT_MAX}. -*/ - -/*! \fn QLatin1StringView::QLatin1StringView(const QByteArray &str) - - Constructs a QLatin1StringView object as a view on \a str. - - The string data is \e not copied. The caller must be able to - guarantee that \a str will not be deleted or modified as long as - the QLatin1StringView object exists. - - \sa latin1() -*/ - -/*! \fn QLatin1StringView::QLatin1StringView(QByteArrayView str) - \since 6.3 - - Constructs a QLatin1StringView object as a view on \a str. - - The string data is \e not copied. The caller must be able to - guarantee that the data which \a str is pointing to will not - be deleted or modified as long as the QLatin1StringView object - exists. The size is obtained from \a str as-is, without checking - for a null-terminator. - - \note: any null ('\\0') bytes in the byte array will be included in this - string, which will be converted to Unicode null characters (U+0000) if this - string is used by QString. - - \sa latin1() -*/ - -/*! - \fn QString QLatin1StringView::toString() const - \since 6.0 - - Converts this Latin-1 string into a QString. Equivalent to - \code - return QString(*this); - \endcode -*/ - -/*! \fn const char *QLatin1StringView::latin1() const - - Returns the start of the Latin-1 string referenced by this object. -*/ - -/*! \fn const char *QLatin1StringView::data() const - - Returns the start of the Latin-1 string referenced by this object. -*/ - -/*! \fn const char *QLatin1StringView::constData() const - \since 6.4 - - Returns the start of the Latin-1 string referenced by this object. - - This function is provided for compatibility with other Qt containers. - - \sa data() -*/ - -/*! \fn qsizetype QLatin1StringView::size() const - - Returns the size of the Latin-1 string referenced by this object. - - \note In version prior to Qt 6, this function returned \c{int}, - restricting the amount of data that could be held in a QLatin1StringView - on 64-bit architectures. -*/ - -/*! \fn qsizetype QLatin1StringView::length() const - \since 6.4 - - Same as size(). - - This function is provided for compatibility with other Qt containers. -*/ - -/*! \fn bool QLatin1StringView::isNull() const - \since 5.10 - - Returns whether the Latin-1 string referenced by this object is null - (\c{data() == nullptr}) or not. - - \sa isEmpty(), data() -*/ - -/*! \fn bool QLatin1StringView::isEmpty() const - \since 5.10 - - Returns whether the Latin-1 string referenced by this object is empty - (\c{size() == 0}) or not. - - \sa isNull(), size() -*/ - -/*! \fn bool QLatin1StringView::empty() const - \since 6.4 - - Returns whether the Latin-1 string referenced by this object is empty - (\c{size() == 0}) or not. - - This function is provided for STL compatibility. - - \sa isEmpty(), isNull(), size() -*/ - -/*! \fn QLatin1Char QLatin1StringView::at(qsizetype pos) const - \since 5.8 - - Returns the character at position \a pos in this object. - - \note This function performs no error checking. - The behavior is undefined when \a pos < 0 or \a pos >= size(). - - \sa operator[]() -*/ - -/*! \fn QLatin1Char QLatin1StringView::operator[](qsizetype pos) const - \since 5.8 - - Returns the character at position \a pos in this object. - - \note This function performs no error checking. - The behavior is undefined when \a pos < 0 or \a pos >= size(). - - \sa at() -*/ - -/*! - \fn QLatin1Char QLatin1StringView::front() const - \since 5.10 - - Returns the first character in the string. - Same as \c{at(0)}. - - This function is provided for STL compatibility. - - \warning Calling this function on an empty string constitutes - undefined behavior. - - \sa back(), at(), operator[]() -*/ - -/*! - \fn QLatin1Char QLatin1StringView::first() const - \since 6.4 - - Returns the first character in the string. - Same as \c{at(0)} or front(). - - This function is provided for compatibility with other Qt containers. - - \warning Calling this function on an empty string constitutes - undefined behavior. - - \sa last(), front(), back() -*/ - -/*! - \fn QLatin1Char QLatin1StringView::back() const - \since 5.10 - - Returns the last character in the string. - Same as \c{at(size() - 1)}. - - This function is provided for STL compatibility. - - \warning Calling this function on an empty string constitutes - undefined behavior. - - \sa front(), at(), operator[]() -*/ - -/*! - \fn QLatin1Char QLatin1StringView::last() const - \since 6.4 - - Returns the last character in the string. - Same as \c{at(size() - 1)} or back(). - - This function is provided for compatibility with other Qt containers. - - \warning Calling this function on an empty string constitutes - undefined behavior. - - \sa first(), back(), front() -*/ - -/*! - \fn int QLatin1StringView::compare(QStringView str, Qt::CaseSensitivity cs) const - \fn int QLatin1StringView::compare(QLatin1StringView l1, Qt::CaseSensitivity cs) const - \fn int QLatin1StringView::compare(QChar ch) const - \fn int QLatin1StringView::compare(QChar ch, Qt::CaseSensitivity cs) const - \since 5.14 - - Returns an integer that compares to zero as this string view compares - to the UTF-16 string viewed by \a str, the Latin-1 string viewed by \a l1, - or the character \a ch, respectively. - - \include qstring.qdocinc {search-comparison-case-sensitivity} {search} - - \sa operator==(), operator<(), operator>() -*/ - - -/*! - \fn bool QLatin1StringView::startsWith(QStringView str, Qt::CaseSensitivity cs) const - \since 5.10 - \fn bool QLatin1StringView::startsWith(QLatin1StringView l1, Qt::CaseSensitivity cs) const - \since 5.10 - \fn bool QLatin1StringView::startsWith(QChar ch) const - \since 5.10 - \fn bool QLatin1StringView::startsWith(QChar ch, Qt::CaseSensitivity cs) const - \since 5.10 - - Returns \c true if this Latin-1 string view starts with the UTF-16 - string viewed by \a str, the Latin-1 string viewed by \a l1, or the - character \a ch, respectively; otherwise returns \c false. - - \include qstring.qdocinc {search-comparison-case-sensitivity} {search} - - \sa endsWith() -*/ - -/*! - \fn bool QLatin1StringView::endsWith(QStringView str, Qt::CaseSensitivity cs) const - \since 5.10 - \fn bool QLatin1StringView::endsWith(QLatin1StringView l1, Qt::CaseSensitivity cs) const - \since 5.10 - \fn bool QLatin1StringView::endsWith(QChar ch) const - \since 5.10 - \fn bool QLatin1StringView::endsWith(QChar ch, Qt::CaseSensitivity cs) const - \since 5.10 - - Returns \c true if this Latin-1 string view ends with the UTF-16 string - viewed \a str, the Latin-1 string viewed by \a l1, or the character \a ch, - respectively; otherwise returns \c false. - - \include qstring.qdocinc {search-comparison-case-sensitivity} {search} - - \sa startsWith() -*/ - -/*! - \fn qsizetype QLatin1StringView::indexOf(QStringView str, qsizetype from = 0, Qt::CaseSensitivity cs = Qt::CaseSensitive) const - \fn qsizetype QLatin1StringView::indexOf(QLatin1StringView l1, qsizetype from = 0, Qt::CaseSensitivity cs = Qt::CaseSensitive) const - \fn qsizetype QLatin1StringView::indexOf(QChar c, qsizetype from = 0, Qt::CaseSensitivity cs = Qt::CaseSensitive) const - \since 5.14 - - Returns the index position in this Latin-1 string view of the first - occurrence of the UTF-16 string viewed by \a str, the Latin-1 string - viewed by \a l1, or the character \a ch, respectively, searching forward - from index position \a from. Returns -1 if \a str, \a l1 or \a c is not - found, respectively. - - \include qstring.qdocinc {search-comparison-case-sensitivity} {search} - - \include qstring.qdocinc negative-index-start-search-from-end - - \sa QString::indexOf() -*/ - -/*! - \fn bool QLatin1StringView::contains(QStringView str, Qt::CaseSensitivity cs) const - \fn bool QLatin1StringView::contains(QLatin1StringView l1, Qt::CaseSensitivity cs) const - \fn bool QLatin1StringView::contains(QChar c, Qt::CaseSensitivity cs) const - \since 5.14 - - Returns \c true if this Latin-1 string view contains an occurrence of the - UTF-16 string viewed by \a str, the Latin-1 string viewed by \a l1, or the - character \a ch, respectively; otherwise returns \c false. - - \include qstring.qdocinc {search-comparison-case-sensitivity} {search} - - \sa indexOf(), QStringView::contains(), QStringView::indexOf(), - QString::indexOf() -*/ - -/*! - \fn qsizetype QLatin1StringView::lastIndexOf(QStringView str, qsizetype from, Qt::CaseSensitivity cs) const - \fn qsizetype QLatin1StringView::lastIndexOf(QLatin1StringView l1, qsizetype from, Qt::CaseSensitivity cs) const - \fn qsizetype QLatin1StringView::lastIndexOf(QChar c, qsizetype from, Qt::CaseSensitivity cs) const - \since 5.14 - - Returns the index position in this Latin-1 string view of the last - occurrence of the UTF-16 string viewed by \a str, the Latin-1 string - viewed by \a l1, or the character \a ch, respectively, searching backward - from index position \a from; returns -1 if \a str, \a l1 or \a ch is not - found, respectively. - - \include qstring.qdocinc negative-index-start-search-from-end - - \include qstring.qdocinc {search-comparison-case-sensitivity} {search} - - \note When searching for a 0-length \a str or \a l1, the match at - the end of the data is excluded from the search by a negative \a - from, even though \c{-1} is normally thought of as searching from - the end of the string: the match at the end is \e after the last - character, so it is excluded. To include such a final empty match, - either give a positive value for \a from or omit the \a from - parameter entirely. - - \sa indexOf(), QStringView::lastIndexOf(), QStringView::indexOf(), - QString::indexOf() -*/ - -/*! - \fn qsizetype QLatin1StringView::lastIndexOf(QStringView str, Qt::CaseSensitivity cs = Qt::CaseSensitive) const - \fn qsizetype QLatin1StringView::lastIndexOf(QLatin1StringView l1, Qt::CaseSensitivity cs = Qt::CaseSensitive) const - \since 6.2 - \overload lastIndexOf() - - Returns the index position in this Latin-1 string view of the last - occurrence of the UTF-16 string viewed by \a str or the Latin-1 string - viewed by \a l1, respectively. Returns -1 if \a str or \a l1 is not found, - respectively. - - \include qstring.qdocinc {search-comparison-case-sensitivity} {search} -*/ - -/*! - \fn qsizetype QLatin1StringView::lastIndexOf(QChar ch, Qt::CaseSensitivity cs) const - \since 6.3 - \overload -*/ - -/*! - \fn qsizetype QLatin1StringView::count(QStringView str, Qt::CaseSensitivity cs) const - \fn qsizetype QLatin1StringView::count(QLatin1StringView l1, Qt::CaseSensitivity cs) const - \fn qsizetype QLatin1StringView::count(QChar ch, Qt::CaseSensitivity cs) const - \since 6.4 - - Returns the number of (potentially overlapping) occurrences of the - UTF-16 string viewed by \a str, the Latin-1 string viewed by \a l1, - or the character \a ch, respectively, in this string view. - - \include qstring.qdocinc {search-comparison-case-sensitivity} {search} - - \sa contains(), indexOf() -*/ - -/*! - \fn QLatin1StringView::const_iterator QLatin1StringView::begin() const - \since 5.10 - - Returns a const \l{STL-style iterators}{STL-style iterator} pointing to the - first character in the string. - - This function is provided for STL compatibility. - - \sa end(), cbegin(), rbegin(), data() -*/ - -/*! - \fn QLatin1StringView::const_iterator QLatin1StringView::cbegin() const - \since 5.10 - - Same as begin(). - - This function is provided for STL compatibility. - - \sa cend(), begin(), crbegin(), data() -*/ - -/*! - \fn QLatin1StringView::const_iterator QLatin1StringView::constBegin() const - \since 6.4 - - Same as begin(). - - This function is provided for compatibility with other Qt containers. - - \sa constEnd(), begin(), cbegin(), data() -*/ - -/*! - \fn QLatin1StringView::const_iterator QLatin1StringView::end() const - \since 5.10 - - Returns a const \l{STL-style iterators}{STL-style iterator} pointing just - after the last character in the string. - - This function is provided for STL compatibility. - - \sa begin(), cend(), rend() -*/ - -/*! \fn QLatin1StringView::const_iterator QLatin1StringView::cend() const - \since 5.10 - - Same as end(). - - This function is provided for STL compatibility. - - \sa cbegin(), end(), crend() -*/ - -/*! \fn QLatin1StringView::const_iterator QLatin1StringView::constEnd() const - \since 6.4 - - Same as end(). - - This function is provided for compatibility with other Qt containers. - - \sa constBegin(), end(), cend(), crend() -*/ - -/*! - \fn QLatin1StringView::const_reverse_iterator QLatin1StringView::rbegin() const - \since 5.10 - - Returns a const \l{STL-style iterators}{STL-style} reverse iterator pointing - to the first character in the string, in reverse order. - - This function is provided for STL compatibility. - - \sa rend(), crbegin(), begin() -*/ - -/*! - \fn QLatin1StringView::const_reverse_iterator QLatin1StringView::crbegin() const - \since 5.10 - - Same as rbegin(). - - This function is provided for STL compatibility. - - \sa crend(), rbegin(), cbegin() -*/ - -/*! - \fn QLatin1StringView::const_reverse_iterator QLatin1StringView::rend() const - \since 5.10 - - Returns a \l{STL-style iterators}{STL-style} reverse iterator pointing just - after the last character in the string, in reverse order. - - This function is provided for STL compatibility. - - \sa rbegin(), crend(), end() -*/ - -/*! - \fn QLatin1StringView::const_reverse_iterator QLatin1StringView::crend() const - \since 5.10 - - Same as rend(). - - This function is provided for STL compatibility. - - \sa crbegin(), rend(), cend() -*/ - -/*! - \fn QLatin1StringView QLatin1StringView::mid(qsizetype start, qsizetype length) const - \since 5.8 - - Returns the substring of length \a length starting at position - \a start in this Latin-1 string view. - - If you know that \a start and \a length cannot be out of bounds, use - sliced() instead in new code, because it is faster. - - Returns an empty Latin-1 string view if \a start exceeds the length - of this string view. If there are less than \a length characters available - in this string view starting at \a start, or if \a length is negative - (default), the function returns all characters that are available from - \a start. - - \sa first(), last(), sliced(), chopped(), chop(), truncate() -*/ - -/*! - \fn QLatin1StringView QLatin1StringView::left(qsizetype length) const - \since 5.8 - - If you know that \a length cannot be out of bounds, use first() instead in - new code, because it is faster. - - Returns the substring of length \a length starting at position - 0 in this Latin-1 string view. - - The entire Latin-1 string view is returned if \a length is greater - than or equal to size(), or less than zero. - - \sa first(), last(), sliced(), startsWith(), chopped(), chop(), truncate() -*/ - -/*! - \fn QLatin1StringView QLatin1StringView::right(qsizetype length) const - \since 5.8 - - If you know that \a length cannot be out of bounds, use last() instead in - new code, because it is faster. - - Returns the substring of length \a length starting at position - size() - \a length in this Latin-1 string view. - - The entire Latin-1 string view is returned if \a length is greater - than or equal to size(), or less than zero. - - \sa first(), last(), sliced(), endsWith(), chopped(), chop(), truncate() -*/ - -/*! - \fn QLatin1StringView QLatin1StringView::first(qsizetype n) const - \since 6.0 - - Returns a Latin-1 string view that contains the first \a n characters - of this string view. - - \note The behavior is undefined when \a n < 0 or \a n > size(). - - \sa last(), startsWith(), chopped(), chop(), truncate() -*/ - -/*! - \fn QLatin1StringView QLatin1StringView::last(qsizetype n) const - \since 6.0 - - Returns a Latin-1 string view that contains the last \a n characters - of this string view. - - \note The behavior is undefined when \a n < 0 or \a n > size(). - - \sa first(), endsWith(), chopped(), chop(), truncate() -*/ - -/*! - \fn QLatin1StringView QLatin1StringView::sliced(qsizetype pos, qsizetype n) const - \since 6.0 - - Returns a Latin-1 string view that points to \a n characters of this - string view, starting at position \a pos. - - \note The behavior is undefined when \a pos < 0, \a n < 0, - or \c{pos + n > size()}. - - \sa first(), last(), chopped(), chop(), truncate() -*/ - -/*! - \fn QLatin1StringView QLatin1StringView::sliced(qsizetype pos) const - \since 6.0 - - Returns a Latin-1 string view starting at position \a pos in this - string view, and extending to its end. - - \note The behavior is undefined when \a pos < 0 or \a pos > size(). - - \sa first(), last(), chopped(), chop(), truncate() -*/ - -/*! - \fn QLatin1StringView QLatin1StringView::chopped(qsizetype length) const - \since 5.10 - - Returns the substring of length size() - \a length starting at the - beginning of this object. - - Same as \c{left(size() - length)}. - - \note The behavior is undefined when \a length < 0 or \a length > size(). - - \sa sliced(), first(), last(), chop(), truncate() -*/ - -/*! - \fn void QLatin1StringView::truncate(qsizetype length) - \since 5.10 - - Truncates this string to length \a length. - - Same as \c{*this = left(length)}. - - \note The behavior is undefined when \a length < 0 or \a length > size(). - - \sa sliced(), first(), last(), chopped(), chop() -*/ - -/*! - \fn void QLatin1StringView::chop(qsizetype length) - \since 5.10 - - Truncates this string by \a length characters. - - Same as \c{*this = left(size() - length)}. - - \note The behavior is undefined when \a length < 0 or \a length > size(). - - \sa sliced(), first(), last(), chopped(), truncate() -*/ - -/*! - \fn QLatin1StringView QLatin1StringView::trimmed() const - \since 5.10 - - Strips leading and trailing whitespace and returns the result. - - Whitespace means any character for which QChar::isSpace() returns - \c true. This includes the ASCII characters '\\t', '\\n', '\\v', - '\\f', '\\r', and ' '. -*/ - -/*! - \fn bool QLatin1StringView::operator==(const char *other) const - \since 4.3 - - Returns \c true if the string is equal to const char pointer \a other; - otherwise returns \c false. - - The \a other const char pointer is converted to a QString using - the QString::fromUtf8() function. - - You can disable this operator by defining - \l QT_NO_CAST_FROM_ASCII when you compile your applications. This - can be useful if you want to ensure that all user-visible strings - go through QObject::tr(), for example. - - \sa {Comparing Strings} -*/ - -/*! - \fn bool QLatin1StringView::operator==(const QByteArray &other) const - \since 5.0 - \overload - - The \a other byte array is converted to a QString using - the QString::fromUtf8() function. - - You can disable this operator by defining - \l QT_NO_CAST_FROM_ASCII when you compile your applications. This - can be useful if you want to ensure that all user-visible strings - go through QObject::tr(), for example. -*/ - -/*! - \fn bool QLatin1StringView::operator!=(const char *other) const - \since 4.3 - - Returns \c true if this string is not equal to const char pointer \a other; - otherwise returns \c false. - - The \a other const char pointer is converted to a QString using - the QString::fromUtf8() function. - - You can disable this operator by defining - \l QT_NO_CAST_FROM_ASCII when you compile your applications. This - can be useful if you want to ensure that all user-visible strings - go through QObject::tr(), for example. - - \sa {Comparing Strings} -*/ - -/*! - \fn bool QLatin1StringView::operator!=(const QByteArray &other) const - \since 5.0 - \overload operator!=() - - The \a other byte array is converted to a QString using - the QString::fromUtf8() function. - - You can disable this operator by defining - \l QT_NO_CAST_FROM_ASCII when you compile your applications. This - can be useful if you want to ensure that all user-visible strings - go through QObject::tr(), for example. -*/ - -/*! - \fn bool QLatin1StringView::operator>(const char *other) const - \since 4.3 - - Returns \c true if this string is lexically greater than const char pointer - \a other; otherwise returns \c false. - - The \a other const char pointer is converted to a QString using - the QString::fromUtf8() function. - - You can disable this operator by defining \l QT_NO_CAST_FROM_ASCII - when you compile your applications. This can be useful if you want - to ensure that all user-visible strings go through QObject::tr(), - for example. - - \sa {Comparing Strings} -*/ - -/*! - \fn bool QLatin1StringView::operator>(const QByteArray &other) const - \since 5.0 - \overload - - The \a other byte array is converted to a QString using - the QString::fromUtf8() function. - - You can disable this operator by defining \l QT_NO_CAST_FROM_ASCII - when you compile your applications. This can be useful if you want - to ensure that all user-visible strings go through QObject::tr(), - for example. -*/ - -/*! - \fn bool QLatin1StringView::operator<(const char *other) const - \since 4.3 - - Returns \c true if this string is lexically less than const char pointer - \a other; otherwise returns \c false. - - The \a other const char pointer is converted to a QString using - the QString::fromUtf8() function. - - You can disable this operator by defining - \l QT_NO_CAST_FROM_ASCII when you compile your applications. This - can be useful if you want to ensure that all user-visible strings - go through QObject::tr(), for example. - - \sa {Comparing Strings} -*/ - -/*! - \fn bool QLatin1StringView::operator<(const QByteArray &other) const - \since 5.0 - \overload - - The \a other byte array is converted to a QString using - the QString::fromUtf8() function. - - You can disable this operator by defining - \l QT_NO_CAST_FROM_ASCII when you compile your applications. This - can be useful if you want to ensure that all user-visible strings - go through QObject::tr(), for example. -*/ - -/*! - \fn bool QLatin1StringView::operator>=(const char *other) const - \since 4.3 - - Returns \c true if this string is lexically greater than or equal to - const char pointer \a other; otherwise returns \c false. - - The \a other const char pointer is converted to a QString using - the QString::fromUtf8() function. - - You can disable this operator by defining - \l QT_NO_CAST_FROM_ASCII when you compile your applications. This - can be useful if you want to ensure that all user-visible strings - go through QObject::tr(), for example. - - \sa {Comparing Strings} -*/ - -/*! - \fn bool QLatin1StringView::operator>=(const QByteArray &other) const - \since 5.0 - \overload - - The \a other byte array is converted to a QString using - the QString::fromUtf8() function. - - You can disable this operator by defining - \l QT_NO_CAST_FROM_ASCII when you compile your applications. This - can be useful if you want to ensure that all user-visible strings - go through QObject::tr(), for example. -*/ - -/*! - \fn bool QLatin1StringView::operator<=(const char *other) const - \since 4.3 - - Returns \c true if this string is lexically less than or equal to - const char pointer \a other; otherwise returns \c false. - - The \a other const char pointer is converted to a QString using - the QString::fromUtf8() function. - - You can disable this operator by defining - \l QT_NO_CAST_FROM_ASCII when you compile your applications. This - can be useful if you want to ensure that all user-visible strings - go through QObject::tr(), for example. - - \sa {Comparing Strings} -*/ - -/*! - \fn bool QLatin1StringView::operator<=(const QByteArray &other) const - \since 5.0 - \overload - - The \a other byte array is converted to a QString using - the QString::fromUtf8() function. - - You can disable this operator by defining - \l QT_NO_CAST_FROM_ASCII when you compile your applications. This - can be useful if you want to ensure that all user-visible strings - go through QObject::tr(), for example. -*/ - -/*! \fn bool QLatin1StringView::operator==(QLatin1StringView s1, QLatin1StringView s2) - - Returns \c true if string \a s1 is lexically equal to string \a s2; - otherwise returns \c false. -*/ -/*! \fn bool QLatin1StringView::operator!=(QLatin1StringView s1, QLatin1StringView s2) - - Returns \c true if string \a s1 is lexically not equal to string \a s2; - otherwise returns \c false. -*/ -/*! \fn bool QLatin1StringView::operator<(QLatin1StringView s1, QLatin1StringView s2) - - Returns \c true if string \a s1 is lexically less than string \a s2; - otherwise returns \c false. -*/ -/*! \fn bool QLatin1StringView::operator<=(QLatin1StringView s1, QLatin1StringView s2) - - Returns \c true if string \a s1 is lexically less than or equal to - string \a s2; otherwise returns \c false. -*/ -/*! \fn bool QLatin1StringView::operator>(QLatin1StringView s1, QLatin1StringView s2) - - Returns \c true if string \a s1 is lexically greater than string \a s2; - otherwise returns \c false. -*/ -/*! \fn bool QLatin1StringView::operator>=(QLatin1StringView s1, QLatin1StringView s2) - - Returns \c true if string \a s1 is lexically greater than or equal - to string \a s2; otherwise returns \c false. -*/ - -/*! \fn bool QLatin1StringView::operator==(QChar ch, QLatin1StringView s) - - Returns \c true if char \a ch is lexically equal to string \a s; - otherwise returns \c false. -*/ -/*! \fn bool QLatin1StringView::operator<(QChar ch, QLatin1StringView s) - - Returns \c true if char \a ch is lexically less than string \a s; - otherwise returns \c false. -*/ -/*! \fn bool QLatin1StringView::operator>(QChar ch, QLatin1StringView s) - Returns \c true if char \a ch is lexically greater than string \a s; - otherwise returns \c false. -*/ -/*! \fn bool QLatin1StringView::operator!=(QChar ch, QLatin1StringView s) - - Returns \c true if char \a ch is lexically not equal to string \a s; - otherwise returns \c false. -*/ -/*! \fn bool QLatin1StringView::operator<=(QChar ch, QLatin1StringView s) - - Returns \c true if char \a ch is lexically less than or equal to - string \a s; otherwise returns \c false. -*/ -/*! \fn bool QLatin1StringView::operator>=(QChar ch, QLatin1StringView s) - - Returns \c true if char \a ch is lexically greater than or equal to - string \a s; otherwise returns \c false. -*/ - -/*! \fn bool QLatin1StringView::operator==(QLatin1StringView s, QChar ch) - - Returns \c true if string \a s is lexically equal to char \a ch; - otherwise returns \c false. -*/ -/*! \fn bool QLatin1StringView::operator<(QLatin1StringView s, QChar ch) - - Returns \c true if string \a s is lexically less than char \a ch; - otherwise returns \c false. -*/ -/*! \fn bool QLatin1StringView::operator>(QLatin1StringView s, QChar ch) - - Returns \c true if string \a s is lexically greater than char \a ch; - otherwise returns \c false. -*/ -/*! \fn bool QLatin1StringView::operator!=(QLatin1StringView s, QChar ch) - - Returns \c true if string \a s is lexically not equal to char \a ch; - otherwise returns \c false. -*/ -/*! \fn bool QLatin1StringView::operator<=(QLatin1StringView s, QChar ch) - - Returns \c true if string \a s is lexically less than or equal to - char \a ch; otherwise returns \c false. -*/ -/*! \fn bool QLatin1StringView::operator>=(QLatin1StringView s, QChar ch) - - Returns \c true if string \a s is lexically greater than or equal to - char \a ch; otherwise returns \c false. -*/ - -/*! \fn bool QLatin1StringView::operator==(QStringView s1, QLatin1StringView s2) - - Returns \c true if string view \a s1 is lexically equal to string \a s2; - otherwise returns \c false. -*/ -/*! \fn bool QLatin1StringView::operator<(QStringView s1, QLatin1StringView s2) - - Returns \c true if string view \a s1 is lexically less than string \a s2; - otherwise returns \c false. -*/ -/*! \fn bool QLatin1StringView::operator>(QStringView s1, QLatin1StringView s2) - - Returns \c true if string view \a s1 is lexically greater than string \a s2; - otherwise returns \c false. -*/ -/*! \fn bool QLatin1StringView::operator!=(QStringView s1, QLatin1StringView s2) - - Returns \c true if string view \a s1 is lexically not equal to string \a s2; - otherwise returns \c false. -*/ -/*! \fn bool QLatin1StringView::operator<=(QStringView s1, QLatin1StringView s2) - - Returns \c true if string view \a s1 is lexically less than or equal to - string \a s2; otherwise returns \c false. -*/ -/*! \fn bool QLatin1StringView::operator>=(QStringView s1, QLatin1StringView s2) - - Returns \c true if string view \a s1 is lexically greater than or equal to - string \a s2; otherwise returns \c false. -*/ - -/*! \fn bool QLatin1StringView::operator==(QLatin1StringView s1, QStringView s2) - - Returns \c true if string \a s1 is lexically equal to string view \a s2; - otherwise returns \c false. -*/ -/*! \fn bool QLatin1StringView::operator<(QLatin1StringView s1, QStringView s2) - - Returns \c true if string \a s1 is lexically less than string view \a s2; - otherwise returns \c false. -*/ -/*! \fn bool QLatin1StringView::operator>(QLatin1StringView s1, QStringView s2) - - Returns \c true if string \a s1 is lexically greater than string view \a s2; - otherwise returns \c false. -*/ -/*! \fn bool QLatin1StringView::operator!=(QLatin1StringView s1, QStringView s2) - - Returns \c true if string \a s1 is lexically not equal to string view \a s2; - otherwise returns \c false. -*/ -/*! \fn bool QLatin1StringView::operator<=(QLatin1StringView s1, QStringView s2) - - Returns \c true if string \a s1 is lexically less than or equal to - string view \a s2; otherwise returns \c false. -*/ -/*! \fn bool QLatin1StringView::operator>=(QLatin1StringView s1, QStringView s2) - - Returns \c true if string \a s1 is lexically greater than or equal to - string view \a s2; otherwise returns \c false. -*/ - -/*! \fn bool QLatin1StringView::operator==(const char *s1, QLatin1StringView s2) - - Returns \c true if const char pointer \a s1 is lexically equal to - string \a s2; otherwise returns \c false. -*/ -/*! \fn bool QLatin1StringView::operator<(const char *s1, QLatin1StringView s2) - - Returns \c true if const char pointer \a s1 is lexically less than - string \a s2; otherwise returns \c false. -*/ -/*! \fn bool QLatin1StringView::operator>(const char *s1, QLatin1StringView s2) - - Returns \c true if const char pointer \a s1 is lexically greater than - string \a s2; otherwise returns \c false. -*/ -/*! \fn bool QLatin1StringView::operator!=(const char *s1, QLatin1StringView s2) - - Returns \c true if const char pointer \a s1 is lexically not equal to - string \a s2; otherwise returns \c false. -*/ -/*! \fn bool QLatin1StringView::operator<=(const char *s1, QLatin1StringView s2) - - Returns \c true if const char pointer \a s1 is lexically less than or - equal to string \a s2; otherwise returns \c false. -*/ -/*! \fn bool QLatin1StringView::operator>=(const char *s1, QLatin1StringView s2) - - Returns \c true if const char pointer \a s1 is lexically greater than or - equal to string \a s2; otherwise returns \c false. -*/ - -/*! - \fn qlonglong QLatin1StringView::toLongLong(bool *ok, int base) const - \fn qulonglong QLatin1StringView::toULongLong(bool *ok, int base) const - \fn int QLatin1StringView::toInt(bool *ok, int base) const - \fn uint QLatin1StringView::toUInt(bool *ok, int base) const - \fn long QLatin1StringView::toLong(bool *ok, int base) const - \fn ulong QLatin1StringView::toULong(bool *ok, int base) const - \fn short QLatin1StringView::toShort(bool *ok, int base) const - \fn ushort QLatin1StringView::toUShort(bool *ok, int base) const - - \since 6.4 - - Returns this QLatin1StringView converted to a corresponding numeric value using - base \a base, which is ten by default. Bases 0 and 2 through 36 are supported, - using letters for digits beyond 9; A is ten, B is eleven and so on. - - If \a base is 0, the base is determined automatically using the following - rules (in this order), if the Latin-1 string view begins with: - - \list - \li \c "0x", the rest of it is read as hexadecimal (base 16) - \li \c "0b", the rest of it is read as binary (base 2) - \li \c "0", the rest of it is read as octal (base 8) - \li otherwise it is read as decimal - \endlist - - Returns 0 if the conversion fails. - - If \a ok is not \nullptr, failure is reported by setting *\a{ok} - to \c false, and success by setting *\a{ok} to \c true. - -//! [latin1-numeric-conversion-note] - \note The conversion of the number is performed in the default C locale, - regardless of the user's locale. Use QLocale to perform locale-aware - conversions between numbers and strings. - - This function ignores leading and trailing spacing characters. -//! [latin1-numeric-conversion-note] - - \note Support for the "0b" prefix was added in Qt 6.4. -*/ - -/*! - \fn double QLatin1StringView::toDouble(bool *ok) const - \fn float QLatin1StringView::toFloat(bool *ok) const - \since 6.4 - - Returns this QLatin1StringView converted to a corresponding floating-point value. - - Returns an infinity if the conversion overflows or 0.0 if the - conversion fails for other reasons (e.g. underflow). - - If \a ok is not \nullptr, failure is reported by setting *\a{ok} - to \c false, and success by setting *\a{ok} to \c true. - - \warning The QLatin1StringView content may only contain valid numerical - characters which includes the plus/minus sign, the character e used in - scientific notation, and the decimal point. Including the unit or additional - characters leads to a conversion error. - - \include qstring.cpp latin1-numeric-conversion-note -*/ - #if !defined(QT_NO_DATASTREAM) || defined(QT_BOOTSTRAPPED) /*! \fn QDataStream &operator<<(QDataStream &stream, const QString &string) @@ -10619,11 +9541,8 @@ qsizetype QtPrivate::count(QLatin1StringView haystack, QChar needle, Qt::CaseSen if (cs == Qt::CaseSensitive) { return std::count(haystack.cbegin(), haystack.cend(), needle.toLatin1()); } else { - auto toLower = [](char ch) { return latin1Lower[uchar(ch)]; }; - const uchar ch = toLower(needle.toLatin1()); - return std::count_if(haystack.cbegin(), haystack.cend(), [&toLower, ch](const char c) { - return toLower(c) == ch; - }); + return std::count_if(haystack.cbegin(), haystack.cend(), + CaseInsensitiveL1::matcher(needle.toLatin1())); } } @@ -10851,10 +9770,7 @@ qsizetype QtPrivate::findString(QLatin1StringView haystack, qsizetype from, QLat if (needle.size() <= threshold) { const auto begin = haystack.begin(); const auto end = haystack.end() - needle.size() + 1; - const uchar needle1 = latin1Lower[uchar(needle[0].toLatin1())]; - auto ciMatch = [needle1](const char ch) { - return latin1Lower[uchar(ch)] == needle1; - }; + auto ciMatch = CaseInsensitiveL1::matcher(needle[0].toLatin1()); const qsizetype nlen1 = needle.size() - 1; for (auto it = std::find_if(begin + from, end, ciMatch); it < end; it = std::find_if(it + 1, end, ciMatch)) { @@ -11121,25 +10037,6 @@ QString QString::toHtmlEscaped() const \sa Qt::Literals::StringLiterals */ -/*! - \fn Qt::Literals::StringLiterals::operator""_L1(const char *str, size_t size) - - \relates QLatin1StringView - \since 6.4 - - Literal operator that creates a QLatin1StringView out of the first \a size - characters in the char string literal \a str. - - The following code creates a QLatin1StringView: - \code - using namespace Qt::Literals::StringLiterals; - - auto str = "hello"_L1; - \endcode - - \sa Qt::Literals::StringLiterals -*/ - /*! \internal */ @@ -11234,5 +10131,4 @@ void QAbstractConcatenable::appendLatin1To(QLatin1StringView in, QChar *out) noe QT_END_NAMESPACE -#undef IS_RAW_DATA #undef REHASH diff --git a/src/corelib/text/qstring.h b/src/corelib/text/qstring.h index e6fa0d49..c00885da 100644 --- a/src/corelib/text/qstring.h +++ b/src/corelib/text/qstring.h @@ -15,6 +15,7 @@ #include #include #include +#include #include #include #include @@ -23,6 +24,7 @@ #include #include +#include #include @@ -35,11 +37,7 @@ Q_FORWARD_DECLARE_CF_TYPE(CFString); Q_FORWARD_DECLARE_OBJC_CLASS(NSString); #endif -#if 0 -// Workaround for generating forward headers -#pragma qt_class(QLatin1String) -#pragma qt_class(QLatin1StringView) -#endif +class tst_QString; QT_BEGIN_NAMESPACE @@ -49,333 +47,15 @@ class QString; namespace QtPrivate { template class BoolList; + +template +using IsCompatibleChar32TypeHelper = + std::is_same; +template +using IsCompatibleChar32Type + = IsCompatibleChar32TypeHelper>; } -#if QT_VERSION >= QT_VERSION_CHECK(7, 0, 0) || defined(QT_BOOTSTRAPPED) || defined(Q_QDOC) -# define Q_L1S_VIEW_IS_PRIMARY -class QLatin1StringView -#else -class QLatin1String -#endif -{ -public: -#ifdef Q_L1S_VIEW_IS_PRIMARY - constexpr inline QLatin1StringView() noexcept {} - constexpr QLatin1StringView(std::nullptr_t) noexcept : QLatin1StringView() {} - constexpr inline explicit QLatin1StringView(const char *s) noexcept - : QLatin1StringView(s, s ? qsizetype(QtPrivate::lengthHelperPointer(s)) : 0) {} - constexpr QLatin1StringView(const char *f, const char *l) - : QLatin1StringView(f, qsizetype(l - f)) {} - constexpr inline QLatin1StringView(const char *s, qsizetype sz) noexcept : m_data(s), m_size(sz) {} - explicit QLatin1StringView(const QByteArray &s) noexcept - : QLatin1StringView(s.constData(), s.size()) {} - constexpr explicit QLatin1StringView(QByteArrayView s) noexcept - : QLatin1StringView(s.constData(), s.size()) {} -#else - constexpr inline QLatin1String() noexcept : m_size(0), m_data(nullptr) {} - Q_WEAK_OVERLOAD - constexpr QLatin1String(std::nullptr_t) noexcept : QLatin1String() {} - constexpr inline explicit QLatin1String(const char *s) noexcept - : m_size(s ? qsizetype(QtPrivate::lengthHelperPointer(s)) : 0), m_data(s) {} - constexpr QLatin1String(const char *f, const char *l) - : QLatin1String(f, qsizetype(l - f)) {} - constexpr inline QLatin1String(const char *s, qsizetype sz) noexcept : m_size(sz), m_data(s) {} - explicit QLatin1String(const QByteArray &s) noexcept : m_size(s.size()), m_data(s.constData()) {} - constexpr explicit QLatin1String(QByteArrayView s) noexcept : m_size(s.size()), m_data(s.data()) {} -#endif // !Q_L1S_VIEW_IS_PRIMARY - - inline QString toString() const; - - constexpr const char *latin1() const noexcept { return m_data; } - constexpr qsizetype size() const noexcept { return m_size; } - constexpr const char *data() const noexcept { return m_data; } - [[nodiscard]] constexpr const char *constData() const noexcept { return data(); } - [[nodiscard]] constexpr const char *constBegin() const noexcept { return begin(); } - [[nodiscard]] constexpr const char *constEnd() const noexcept { return end(); } - - [[nodiscard]] constexpr QLatin1Char first() const { return front(); } - [[nodiscard]] constexpr QLatin1Char last() const { return back(); } - - [[nodiscard]] constexpr qsizetype length() const noexcept { return size(); } - - constexpr bool isNull() const noexcept { return !data(); } - constexpr bool isEmpty() const noexcept { return !size(); } - - [[nodiscard]] constexpr bool empty() const noexcept { return size() == 0; } - - template - [[nodiscard]] inline QString arg(Args &&...args) const; - - [[nodiscard]] constexpr QLatin1Char at(qsizetype i) const - { - Q_ASSERT(i >= 0); - Q_ASSERT(i < size()); - return QLatin1Char(m_data[i]); - } - [[nodiscard]] constexpr QLatin1Char operator[](qsizetype i) const { return at(i); } - - [[nodiscard]] constexpr QLatin1Char front() const { return at(0); } - [[nodiscard]] constexpr QLatin1Char back() const { return at(size() - 1); } - - [[nodiscard]] int compare(QStringView other, Qt::CaseSensitivity cs = Qt::CaseSensitive) const noexcept - { return QtPrivate::compareStrings(*this, other, cs); } - [[nodiscard]] int compare(QLatin1StringView other, Qt::CaseSensitivity cs = Qt::CaseSensitive) const noexcept - { return QtPrivate::compareStrings(*this, other, cs); } - [[nodiscard]] inline int compare(QUtf8StringView other, Qt::CaseSensitivity cs = Qt::CaseSensitive) const noexcept; - [[nodiscard]] constexpr int compare(QChar c) const noexcept - { return isEmpty() ? -1 : front() == c ? int(size() > 1) : uchar(m_data[0]) - c.unicode(); } - [[nodiscard]] int compare(QChar c, Qt::CaseSensitivity cs) const noexcept - { return QtPrivate::compareStrings(*this, QStringView(&c, 1), cs); } - [[nodiscard]] bool startsWith(QStringView s, Qt::CaseSensitivity cs = Qt::CaseSensitive) const noexcept - { return QtPrivate::startsWith(*this, s, cs); } - [[nodiscard]] bool startsWith(QLatin1StringView s, Qt::CaseSensitivity cs = Qt::CaseSensitive) const noexcept - { return QtPrivate::startsWith(*this, s, cs); } - [[nodiscard]] constexpr bool startsWith(QChar c) const noexcept - { return !isEmpty() && front() == c; } - [[nodiscard]] inline bool startsWith(QChar c, Qt::CaseSensitivity cs) const noexcept - { return QtPrivate::startsWith(*this, QStringView(&c, 1), cs); } - - [[nodiscard]] bool endsWith(QStringView s, Qt::CaseSensitivity cs = Qt::CaseSensitive) const noexcept - { return QtPrivate::endsWith(*this, s, cs); } - [[nodiscard]] bool endsWith(QLatin1StringView s, Qt::CaseSensitivity cs = Qt::CaseSensitive) const noexcept - { return QtPrivate::endsWith(*this, s, cs); } - [[nodiscard]] constexpr bool endsWith(QChar c) const noexcept - { return !isEmpty() && back() == c; } - [[nodiscard]] inline bool endsWith(QChar c, Qt::CaseSensitivity cs) const noexcept - { return QtPrivate::endsWith(*this, QStringView(&c, 1), cs); } - - [[nodiscard]] qsizetype indexOf(QStringView s, qsizetype from = 0, Qt::CaseSensitivity cs = Qt::CaseSensitive) const noexcept - { return QtPrivate::findString(*this, from, s, cs); } - [[nodiscard]] qsizetype indexOf(QLatin1StringView s, qsizetype from = 0, Qt::CaseSensitivity cs = Qt::CaseSensitive) const noexcept - { return QtPrivate::findString(*this, from, s, cs); } - [[nodiscard]] qsizetype indexOf(QChar c, qsizetype from = 0, Qt::CaseSensitivity cs = Qt::CaseSensitive) const noexcept - { return QtPrivate::findString(*this, from, QStringView(&c, 1), cs); } - - [[nodiscard]] bool contains(QStringView s, Qt::CaseSensitivity cs = Qt::CaseSensitive) const noexcept - { return indexOf(s, 0, cs) != -1; } - [[nodiscard]] bool contains(QLatin1StringView s, Qt::CaseSensitivity cs = Qt::CaseSensitive) const noexcept - { return indexOf(s, 0, cs) != -1; } - [[nodiscard]] inline bool contains(QChar c, Qt::CaseSensitivity cs = Qt::CaseSensitive) const noexcept - { return indexOf(QStringView(&c, 1), 0, cs) != -1; } - - [[nodiscard]] qsizetype lastIndexOf(QStringView s, Qt::CaseSensitivity cs = Qt::CaseSensitive) const noexcept - { return lastIndexOf(s, size(), cs); } - [[nodiscard]] qsizetype lastIndexOf(QStringView s, qsizetype from, Qt::CaseSensitivity cs = Qt::CaseSensitive) const noexcept - { return QtPrivate::lastIndexOf(*this, from, s, cs); } - [[nodiscard]] qsizetype lastIndexOf(QLatin1StringView s, Qt::CaseSensitivity cs = Qt::CaseSensitive) const noexcept - { return lastIndexOf(s, size(), cs); } - [[nodiscard]] qsizetype lastIndexOf(QLatin1StringView s, qsizetype from, Qt::CaseSensitivity cs = Qt::CaseSensitive) const noexcept - { return QtPrivate::lastIndexOf(*this, from, s, cs); } - [[nodiscard]] qsizetype lastIndexOf(QChar c, Qt::CaseSensitivity cs = Qt::CaseSensitive) const noexcept - { return lastIndexOf(c, -1, cs); } - [[nodiscard]] qsizetype lastIndexOf(QChar c, qsizetype from, Qt::CaseSensitivity cs = Qt::CaseSensitive) const noexcept - { return QtPrivate::lastIndexOf(*this, from, QStringView(&c, 1), cs); } - - [[nodiscard]] qsizetype count(QStringView str, Qt::CaseSensitivity cs = Qt::CaseSensitive) const - { return QtPrivate::count(*this, str, cs); } - [[nodiscard]] qsizetype count(QLatin1StringView str, Qt::CaseSensitivity cs = Qt::CaseSensitive) const - { return QtPrivate::count(*this, str, cs); } - [[nodiscard]] qsizetype count(QChar ch, Qt::CaseSensitivity cs = Qt::CaseSensitive) const noexcept - { return QtPrivate::count(*this, ch, cs); } - - [[nodiscard]] short toShort(bool *ok = nullptr, int base = 10) const - { return QtPrivate::toIntegral(QByteArrayView(*this), ok, base); } - [[nodiscard]] ushort toUShort(bool *ok = nullptr, int base = 10) const - { return QtPrivate::toIntegral(QByteArrayView(*this), ok, base); } - [[nodiscard]] int toInt(bool *ok = nullptr, int base = 10) const - { return QtPrivate::toIntegral(QByteArrayView(*this), ok, base); } - [[nodiscard]] uint toUInt(bool *ok = nullptr, int base = 10) const - { return QtPrivate::toIntegral(QByteArrayView(*this), ok, base); } - [[nodiscard]] long toLong(bool *ok = nullptr, int base = 10) const - { return QtPrivate::toIntegral(QByteArrayView(*this), ok, base); } - [[nodiscard]] ulong toULong(bool *ok = nullptr, int base = 10) const - { return QtPrivate::toIntegral(QByteArrayView(*this), ok, base); } - [[nodiscard]] qlonglong toLongLong(bool *ok = nullptr, int base = 10) const - { return QtPrivate::toIntegral(QByteArrayView(*this), ok, base); } - [[nodiscard]] qulonglong toULongLong(bool *ok = nullptr, int base = 10) const - { return QtPrivate::toIntegral(QByteArrayView(*this), ok, base); } - [[nodiscard]] float toFloat(bool *ok = nullptr) const - { - const auto r = QtPrivate::toFloat(*this); - if (ok) - *ok = bool(r); - return r.value_or(0.0f); - } - [[nodiscard]] double toDouble(bool *ok = nullptr) const - { - const auto r = QtPrivate::toDouble(*this); - if (ok) - *ok = bool(r); - return r.value_or(0.0); - } - - using value_type = const char; - using reference = value_type&; - using const_reference = reference; - using iterator = value_type*; - using const_iterator = iterator; - using difference_type = qsizetype; // violates Container concept requirements - using size_type = qsizetype; // violates Container concept requirements - - constexpr const_iterator begin() const noexcept { return data(); } - constexpr const_iterator cbegin() const noexcept { return data(); } - constexpr const_iterator end() const noexcept { return data() + size(); } - constexpr const_iterator cend() const noexcept { return data() + size(); } - - using reverse_iterator = std::reverse_iterator; - using const_reverse_iterator = reverse_iterator; - - const_reverse_iterator rbegin() const noexcept { return const_reverse_iterator(end()); } - const_reverse_iterator crbegin() const noexcept { return const_reverse_iterator(end()); } - const_reverse_iterator rend() const noexcept { return const_reverse_iterator(begin()); } - const_reverse_iterator crend() const noexcept { return const_reverse_iterator(begin()); } - - [[nodiscard]] constexpr QLatin1StringView mid(qsizetype pos, qsizetype n = -1) const - { - using namespace QtPrivate; - auto result = QContainerImplHelper::mid(size(), &pos, &n); - return result == QContainerImplHelper::Null ? QLatin1StringView() - : QLatin1StringView(m_data + pos, n); - } - [[nodiscard]] constexpr QLatin1StringView left(qsizetype n) const - { - if (size_t(n) >= size_t(size())) - n = size(); - return {m_data, n}; - } - [[nodiscard]] constexpr QLatin1StringView right(qsizetype n) const - { - if (size_t(n) >= size_t(size())) - n = size(); - return {m_data + m_size - n, n}; - } - - [[nodiscard]] constexpr QLatin1StringView sliced(qsizetype pos) const - { verify(pos); return {m_data + pos, m_size - pos}; } - [[nodiscard]] constexpr QLatin1StringView sliced(qsizetype pos, qsizetype n) const - { verify(pos, n); return {m_data + pos, n}; } - [[nodiscard]] constexpr QLatin1StringView first(qsizetype n) const - { verify(n); return {m_data, n}; } - [[nodiscard]] constexpr QLatin1StringView last(qsizetype n) const - { verify(n); return {m_data + size() - n, n}; } - [[nodiscard]] constexpr QLatin1StringView chopped(qsizetype n) const - { verify(n); return {m_data, size() - n}; } - - constexpr void chop(qsizetype n) - { verify(n); m_size -= n; } - constexpr void truncate(qsizetype n) - { verify(n); m_size = n; } - - [[nodiscard]] QLatin1StringView trimmed() const noexcept { return QtPrivate::trimmed(*this); } - - template - [[nodiscard]] inline constexpr auto tokenize(Needle &&needle, Flags...flags) const - noexcept(noexcept(qTokenize(std::declval(), - std::forward(needle), flags...))) - -> decltype(qTokenize(*this, std::forward(needle), flags...)) - { return qTokenize(*this, std::forward(needle), flags...); } - - friend inline bool operator==(QLatin1StringView s1, QLatin1StringView s2) noexcept - { return QByteArrayView(s1) == QByteArrayView(s2); } - friend inline bool operator!=(QLatin1StringView s1, QLatin1StringView s2) noexcept - { return !(s1 == s2); } - friend inline bool operator<(QLatin1StringView s1, QLatin1StringView s2) noexcept - { - const qsizetype len = qMin(s1.size(), s2.size()); - const int r = len ? memcmp(s1.latin1(), s2.latin1(), len) : 0; - return r < 0 || (r == 0 && s1.size() < s2.size()); - } - friend inline bool operator>(QLatin1StringView s1, QLatin1StringView s2) noexcept - { return s2 < s1; } - friend inline bool operator<=(QLatin1StringView s1, QLatin1StringView s2) noexcept - { return !(s1 > s2); } - friend inline bool operator>=(QLatin1StringView s1, QLatin1StringView s2) noexcept - { return !(s1 < s2); } - - // QChar <> QLatin1StringView - friend inline bool operator==(QChar lhs, QLatin1StringView rhs) noexcept { return rhs.size() == 1 && lhs == rhs.front(); } - friend inline bool operator< (QChar lhs, QLatin1StringView rhs) noexcept { return compare_helper(&lhs, 1, rhs) < 0; } - friend inline bool operator> (QChar lhs, QLatin1StringView rhs) noexcept { return compare_helper(&lhs, 1, rhs) > 0; } - friend inline bool operator!=(QChar lhs, QLatin1StringView rhs) noexcept { return !(lhs == rhs); } - friend inline bool operator<=(QChar lhs, QLatin1StringView rhs) noexcept { return !(lhs > rhs); } - friend inline bool operator>=(QChar lhs, QLatin1StringView rhs) noexcept { return !(lhs < rhs); } - - friend inline bool operator==(QLatin1StringView lhs, QChar rhs) noexcept { return rhs == lhs; } - friend inline bool operator!=(QLatin1StringView lhs, QChar rhs) noexcept { return !(rhs == lhs); } - friend inline bool operator< (QLatin1StringView lhs, QChar rhs) noexcept { return rhs > lhs; } - friend inline bool operator> (QLatin1StringView lhs, QChar rhs) noexcept { return rhs < lhs; } - friend inline bool operator<=(QLatin1StringView lhs, QChar rhs) noexcept { return !(rhs < lhs); } - friend inline bool operator>=(QLatin1StringView lhs, QChar rhs) noexcept { return !(rhs > lhs); } - - // QStringView <> QLatin1StringView - friend inline bool operator==(QStringView lhs, QLatin1StringView rhs) noexcept - { return lhs.size() == rhs.size() && QtPrivate::equalStrings(lhs, rhs); } - friend inline bool operator!=(QStringView lhs, QLatin1StringView rhs) noexcept { return !(lhs == rhs); } - friend inline bool operator< (QStringView lhs, QLatin1StringView rhs) noexcept { return QtPrivate::compareStrings(lhs, rhs) < 0; } - friend inline bool operator<=(QStringView lhs, QLatin1StringView rhs) noexcept { return QtPrivate::compareStrings(lhs, rhs) <= 0; } - friend inline bool operator> (QStringView lhs, QLatin1StringView rhs) noexcept { return QtPrivate::compareStrings(lhs, rhs) > 0; } - friend inline bool operator>=(QStringView lhs, QLatin1StringView rhs) noexcept { return QtPrivate::compareStrings(lhs, rhs) >= 0; } - - friend inline bool operator==(QLatin1StringView lhs, QStringView rhs) noexcept - { return lhs.size() == rhs.size() && QtPrivate::equalStrings(lhs, rhs); } - friend inline bool operator!=(QLatin1StringView lhs, QStringView rhs) noexcept { return !(lhs == rhs); } - friend inline bool operator< (QLatin1StringView lhs, QStringView rhs) noexcept { return QtPrivate::compareStrings(lhs, rhs) < 0; } - friend inline bool operator<=(QLatin1StringView lhs, QStringView rhs) noexcept { return QtPrivate::compareStrings(lhs, rhs) <= 0; } - friend inline bool operator> (QLatin1StringView lhs, QStringView rhs) noexcept { return QtPrivate::compareStrings(lhs, rhs) > 0; } - friend inline bool operator>=(QLatin1StringView lhs, QStringView rhs) noexcept { return QtPrivate::compareStrings(lhs, rhs) >= 0; } - - -#if !defined(QT_NO_CAST_FROM_ASCII) && !defined(QT_RESTRICTED_CAST_FROM_ASCII) - QT_ASCII_CAST_WARN inline bool operator==(const char *s) const; - QT_ASCII_CAST_WARN inline bool operator!=(const char *s) const; - QT_ASCII_CAST_WARN inline bool operator<(const char *s) const; - QT_ASCII_CAST_WARN inline bool operator>(const char *s) const; - QT_ASCII_CAST_WARN inline bool operator<=(const char *s) const; - QT_ASCII_CAST_WARN inline bool operator>=(const char *s) const; - - QT_ASCII_CAST_WARN inline bool operator==(const QByteArray &s) const; - QT_ASCII_CAST_WARN inline bool operator!=(const QByteArray &s) const; - QT_ASCII_CAST_WARN inline bool operator<(const QByteArray &s) const; - QT_ASCII_CAST_WARN inline bool operator>(const QByteArray &s) const; - QT_ASCII_CAST_WARN inline bool operator<=(const QByteArray &s) const; - QT_ASCII_CAST_WARN inline bool operator>=(const QByteArray &s) const; - - QT_ASCII_CAST_WARN friend bool operator==(const char *s1, QLatin1StringView s2) { return compare_helper(s2, s1) == 0; } - QT_ASCII_CAST_WARN friend bool operator!=(const char *s1, QLatin1StringView s2) { return compare_helper(s2, s1) != 0; } - QT_ASCII_CAST_WARN friend bool operator< (const char *s1, QLatin1StringView s2) { return compare_helper(s2, s1) > 0; } - QT_ASCII_CAST_WARN friend bool operator> (const char *s1, QLatin1StringView s2) { return compare_helper(s2, s1) < 0; } - QT_ASCII_CAST_WARN friend bool operator<=(const char *s1, QLatin1StringView s2) { return compare_helper(s2, s1) >= 0; } - QT_ASCII_CAST_WARN friend bool operator>=(const char *s1, QLatin1StringView s2) { return compare_helper(s2, s1) <= 0; } -#endif // !defined(QT_NO_CAST_FROM_ASCII) && !defined(QT_RESTRICTED_CAST_FROM_ASCII) - -private: -#if !defined(QT_NO_CAST_FROM_ASCII) && !defined(QT_RESTRICTED_CAST_FROM_ASCII) - static inline int compare_helper(const QLatin1StringView &s1, const char *s2); -#endif - Q_ALWAYS_INLINE constexpr void verify(qsizetype pos, qsizetype n = 0) const - { - Q_ASSERT(pos >= 0); - Q_ASSERT(pos <= size()); - Q_ASSERT(n >= 0); - Q_ASSERT(n <= size() - pos); - } - Q_CORE_EXPORT static int compare_helper(const QChar *data1, qsizetype length1, - QLatin1StringView s2, - Qt::CaseSensitivity cs = Qt::CaseSensitive) noexcept; -#if QT_VERSION >= QT_VERSION_CHECK(7, 0, 0) || defined(QT_BOOTSTRAPPED) - const char *m_data = nullptr; - qsizetype m_size = 0; -#else - qsizetype m_size; - const char *m_data; -#endif -}; -#ifdef Q_L1S_VIEW_IS_PRIMARY -Q_DECLARE_TYPEINFO(QLatin1StringView, Q_RELOCATABLE_TYPE); -#else -Q_DECLARE_TYPEINFO(QLatin1String, Q_RELOCATABLE_TYPE); -#endif - // Qt 4.x compatibility // @@ -404,7 +84,6 @@ qsizetype QStringView::lastIndexOf(QLatin1StringView s, qsizetype from, Qt::Case qsizetype QStringView::count(QLatin1StringView s, Qt::CaseSensitivity cs) const { return QtPrivate::count(*this, s, cs); } - // // QAnyStringView members that require QLatin1StringView // @@ -418,6 +97,7 @@ constexpr QLatin1StringView QAnyStringView::asLatin1StringView() const return {m_data_utf8, size()}; } + template constexpr decltype(auto) QAnyStringView::visit(Visitor &&v) const { @@ -446,6 +126,36 @@ constexpr QChar QAnyStringView::back() const class Q_CORE_EXPORT QString { typedef QTypedArrayData Data; + + friend class ::tst_QString; + + template + static constexpr bool is_contiguous_iterator_v = + // Can't use contiguous_iterator_tag here, as STL impls can't agree on feature macro. + // To avoid differences in C++20 and C++17 builds, treat only pointers as contiguous + // for now: + // std::contiguous_iterator; + std::is_pointer_v; + + template + using is_compatible_char_helper = std::disjunction< + QtPrivate::IsCompatibleCharType, + QtPrivate::IsCompatibleChar32Type, + std::is_same // special case + >; + + template + static constexpr bool is_compatible_iterator_v = std::conjunction_v< + std::is_convertible< + typename std::iterator_traits::iterator_category, + std::input_iterator_tag + >, + is_compatible_char_helper::value_type> + >; + + template + using if_compatible_iterator = std::enable_if_t, bool>; + public: typedef QStringPrivate DataPointer; @@ -469,13 +179,13 @@ public: = default; QT_MOVE_ASSIGNMENT_OPERATOR_IMPL_VIA_PURE_SWAP(QString) void swap(QString &other) noexcept { d.swap(other.d); } - inline qsizetype size() const { return d.size; } + inline qsizetype size() const noexcept { return d.size; } #if QT_DEPRECATED_SINCE(6, 4) QT_DEPRECATED_VERSION_X_6_4("Use size() or length() instead.") inline qsizetype count() const { return d.size; } #endif - inline qsizetype length() const { return d.size; } - inline bool isEmpty() const; + inline qsizetype length() const noexcept { return d.size; } + inline bool isEmpty() const noexcept { return d.size == 0; } void resize(qsizetype size); void resize(qsizetype size, QChar fillChar); @@ -705,6 +415,49 @@ public: inline QString &prepend(QLatin1StringView s) { return insert(0, s); } QString &prepend(QUtf8StringView s) { return insert(0, s); } + QString &assign(QAnyStringView s); + inline QString &assign(qsizetype n, QChar c) + { + Q_ASSERT(n >= 0); + return fill(c, n); + } + template = true> + QString &assign(InputIterator first, InputIterator last) + { + using V = typename std::iterator_traits::value_type; + constexpr bool IsL1C = std::is_same_v, QLatin1Char>; + constexpr bool IsFwdIt = std::is_convertible_v< + typename std::iterator_traits::iterator_category, + std::forward_iterator_tag + >; + + if constexpr (is_contiguous_iterator_v) { + const auto p = q20::to_address(first); + const auto len = qsizetype(last - first); + if constexpr (IsL1C) + return assign(QLatin1StringView(reinterpret_cast(p), len)); + else if constexpr (sizeof(V) == 4) + return assign_helper(p, len); + else + return assign(QAnyStringView(p, len)); + } else if constexpr (sizeof(V) == 4) { // non-contiguous iterator, feed data piecemeal + resize(0); + if constexpr (IsFwdIt) { + const qsizetype requiredCapacity = 2 * std::distance(first, last); + reserve(requiredCapacity); + } + while (first != last) { + append(QChar::fromUcs4(*first)); + ++first; + } + return *this; + } else { + d.assign(first, last, [](QChar ch) -> char16_t { return ch.unicode(); }); + d.data()[d.size] = u'\0'; + return *this; + } + } + inline QString &operator+=(QChar c) { return append(c); } inline QString &operator+=(const QString &s) { return append(s); } @@ -736,9 +489,10 @@ public: template QString &removeIf(Predicate pred) { - QtPrivate::sequential_erase_if(*this, pred); + removeIf_helper(pred); return *this; } + QString &replace(qsizetype i, qsizetype len, QChar after); QString &replace(qsizetype i, qsizetype len, const QChar *s, qsizetype slen); QString &replace(qsizetype i, qsizetype len, const QString &after); @@ -1136,6 +890,11 @@ public: NSString *toNSString() const Q_DECL_NS_RETURNS_AUTORELEASED; #endif +#if defined(Q_OS_WASM) || defined(Q_QDOC) + static QString fromEcmaString(emscripten::val jsString); + emscripten::val toEcmaString() const; +#endif + inline bool isNull() const { return d->isNull(); } @@ -1162,6 +921,8 @@ private: void reallocData(qsizetype alloc, QArrayData::AllocationOption option); void reallocGrowData(qsizetype n); + // ### remove once QAnyStringView supports UTF-32: + QString &assign_helper(const char32_t *data, qsizetype len); static int compare_helper(const QChar *data1, qsizetype length1, const QChar *data2, qsizetype length2, Qt::CaseSensitivity cs = Qt::CaseSensitive) noexcept; @@ -1184,13 +945,26 @@ private: static QByteArray toLatin1_helper_inplace(QString &); static QByteArray toUtf8_helper(const QString &); static QByteArray toLocal8Bit_helper(const QChar *data, qsizetype size); - static qsizetype toUcs4_helper(const ushort *uc, qsizetype length, uint *out); // ### Qt 7 char16_t +#if QT_CORE_REMOVED_SINCE(6, 6) + static qsizetype toUcs4_helper(const ushort *uc, qsizetype length, uint *out); +#endif + static qsizetype toUcs4_helper(const char16_t *uc, qsizetype length, char32_t *out); static qlonglong toIntegral_helper(QStringView string, bool *ok, int base); static qulonglong toIntegral_helper(QStringView string, bool *ok, uint base); - void replace_helper(size_t *indices, qsizetype nIndices, qsizetype blen, const QChar *after, qsizetype alen); + template + qsizetype removeIf_helper(Predicate pred) + { + const qsizetype result = d->eraseIf(pred); + if (result > 0) + d.data()[d.size] = u'\0'; + return result; + } + friend class QStringView; friend class QByteArray; friend struct QAbstractConcatenable; + template friend qsizetype erase(QString &s, const T &t); + template friend qsizetype erase_if(QString &s, Predicate pred); template static T toIntegral_helper(QStringView string, bool *ok, int base) @@ -1306,8 +1080,6 @@ const QChar QString::at(qsizetype i) const { Q_ASSERT(size_t(i) < size_t(size())); return QChar(d.data()[i]); } const QChar QString::operator[](qsizetype i) const { Q_ASSERT(size_t(i) < size_t(size())); return QChar(d.data()[i]); } -bool QString::isEmpty() const -{ return d.size == 0; } const QChar *QString::unicode() const { return data(); } const QChar *QString::data() const @@ -1381,8 +1153,7 @@ qsizetype QStringView::toWCharArray(wchar_t *array) const memcpy(array, src, sizeof(QChar) * size()); return size(); } else { - return QString::toUcs4_helper(reinterpret_cast(data()), size(), - reinterpret_cast(array)); + return QString::toUcs4_helper(utf16(), size(), reinterpret_cast(array)); } } @@ -1460,6 +1231,9 @@ bool QString::operator<=(const char *s) const bool QString::operator>=(const char *s) const { return QString::compare_helper(constData(), size(), s, -1) >= 0; } +// +// QLatin1StringView inline members that require QString: +// QT_ASCII_CAST_WARN bool QLatin1StringView::operator==(const char *s) const { return QString::fromUtf8(s) == *this; } QT_ASCII_CAST_WARN bool QLatin1StringView::operator!=(const char *s) const @@ -1486,11 +1260,6 @@ QT_ASCII_CAST_WARN bool QLatin1StringView::operator<=(const QByteArray &s) const QT_ASCII_CAST_WARN bool QLatin1StringView::operator>=(const QByteArray &s) const { return QString::fromUtf8(s) <= *this; } -int QLatin1StringView::compare_helper(const QLatin1StringView &s1, const char *s2) -{ - return QString::compare(s1, QString::fromUtf8(s2)); -} - QT_ASCII_CAST_WARN bool QString::operator==(const QByteArray &s) const { return QString::compare_helper(constData(), size(), s.constData(), s.size()) == 0; } QT_ASCII_CAST_WARN bool QString::operator!=(const QByteArray &s) const @@ -1574,8 +1343,8 @@ QString QString::fromStdU32String(const std::u32string &s) std::u32string QString::toStdU32String() const { std::u32string u32str(size(), char32_t(0)); - qsizetype len = toUcs4_helper(reinterpret_cast(constData()), - size(), reinterpret_cast(&u32str[0])); + const qsizetype len = toUcs4_helper(reinterpret_cast(data()), + size(), u32str.data()); u32str.resize(len); return u32str; } @@ -1689,25 +1458,19 @@ QString QLatin1StringView::arg(Args &&...args) const template qsizetype erase(QString &s, const T &t) { - return QtPrivate::sequential_erase(s, t); + return s.removeIf_helper([&t](const auto &e) { return t == e; }); } template qsizetype erase_if(QString &s, Predicate pred) { - return QtPrivate::sequential_erase_if(s, pred); + return s.removeIf_helper(pred); } namespace Qt { inline namespace Literals { inline namespace StringLiterals { - -constexpr inline QLatin1StringView operator"" _L1(const char *str, size_t size) noexcept -{ - return {str, qsizetype(size)}; -} - -inline QString operator"" _s(const char16_t *str, size_t size) noexcept +inline QString operator""_s(const char16_t *str, size_t size) noexcept { return QString(QStringPrivate(nullptr, const_cast(str), qsizetype(size))); } @@ -1720,7 +1483,7 @@ inline namespace QtLiterals { #if QT_DEPRECATED_SINCE(6, 8) QT_DEPRECATED_VERSION_X_6_8("Use _s from Qt::StringLiterals namespace instead.") -inline QString operator"" _qs(const char16_t *str, size_t size) noexcept +inline QString operator""_qs(const char16_t *str, size_t size) noexcept { return Qt::StringLiterals::operator""_s(str, size); } diff --git a/src/corelib/text/qstringbuilder.cpp b/src/corelib/text/qstringbuilder.cpp index d4ab8db0..738ce833 100644 --- a/src/corelib/text/qstringbuilder.cpp +++ b/src/corelib/text/qstringbuilder.cpp @@ -57,36 +57,57 @@ QT_BEGIN_NAMESPACE if there are three or more of them, and performs equally well in other cases. + \note Defining \c QT_USE_QSTRINGBUILDER at build time (this is the + default when building Qt libraries and tools), will make using \c {'+'} + when concatenating strings work the same way as \c operator%(). + \sa QLatin1StringView, QString */ -/*! \fn template QStringBuilder::QStringBuilder(const A &a, const B &b) - Constructs a QStringBuilder from \a a and \a b. +/*! + \internal + \fn template QStringBuilder::QStringBuilder(const A &a, const B &b) + + Constructs a QStringBuilder from \a a and \a b. */ -/* \fn template QStringBuilder::operator%(const A &a, const B &b) +/*! + \internal + \fn template QStringBuilder::operator%(const A &a, const B &b) Returns a \c QStringBuilder object that is converted to a QString object when assigned to a variable of QString type or passed to a function that takes a QString parameter. - This function is usable with arguments of type \c QString, - \c QLatin1StringView, - \c QChar, \c QLatin1Char, and \c char. + This function is usable with arguments of any of the following types: + \list + \li \c QAnyStringView, + \li \c QString, \c QStringView + \li \c QByteArray, \c QByteArrayView, \c QLatin1StringView + \li \c QChar, \c QLatin1Char, \c char, (since 5.10:) \c char16_t + \li (since 5.10:) \c{const char16_t[]} (\c{u"foo"}), + \endlist */ -/* \fn template QByteArray QStringBuilder::toLatin1() const - Returns a Latin-1 representation of the string as a QByteArray. The - returned byte array is undefined if the string contains non-Latin1 - characters. - */ -/* \fn template QByteArray QStringBuilder::toUtf8() const - Returns a UTF-8 representation of the string as a QByteArray. - */ - - /*! \internal + \fn template QByteArray QStringBuilder::toLatin1() const + + Returns a Latin-1 representation of the string as a QByteArray. It + is undefined behavior if the string contains non-Latin1 characters. + */ + +/*! + \internal + \fn template QByteArray QStringBuilder::toUtf8() const + + Returns a UTF-8 representation of the string as a QByteArray. + */ + +/*! + \internal + Converts the UTF-8 string viewed by \a in to UTF-16 and writes the result + to the buffer starting at \a out. */ void QAbstractConcatenable::convertFromUtf8(QByteArrayView in, QChar *&out) noexcept { diff --git a/src/corelib/text/qstringbuilder.h b/src/corelib/text/qstringbuilder.h index d03dd992..30cf46de 100644 --- a/src/corelib/text/qstringbuilder.h +++ b/src/corelib/text/qstringbuilder.h @@ -101,6 +101,9 @@ public: const B &b; }; +// This specialization is here for backwards compatibility: appending +// two null strings must give back a null string, so we're special +// casing this one out. template <> class QStringBuilder : public QStringBuilderBase, QString> { @@ -118,6 +121,7 @@ class QStringBuilder : public QStringBuilderBase class QStringBuilder : public QStringBuilderBase, QByteArray> { diff --git a/src/corelib/text/qstringconverter.cpp b/src/corelib/text/qstringconverter.cpp index 91d38fd0..7f68c511 100644 --- a/src/corelib/text/qstringconverter.cpp +++ b/src/corelib/text/qstringconverter.cpp @@ -505,7 +505,6 @@ QByteArray QUtf8::convertFromUnicode(QStringView in, QStringConverterBase::State char *QUtf8::convertFromUnicode(char *out, QStringView in, QStringConverter::State *state) { Q_ASSERT(state); - const QChar *uc = in.data(); qsizetype len = in.size(); if (!len) return out; @@ -523,7 +522,7 @@ char *QUtf8::convertFromUnicode(char *out, QStringView in, QStringConverter::Sta }; uchar *cursor = reinterpret_cast(out); - const char16_t *src = reinterpret_cast(uc); + const char16_t *src = in.utf16(); const char16_t *const end = src + len; if (!(state->flags & QStringDecoder::Flag::Stateless)) { @@ -609,14 +608,14 @@ QString QUtf8::convertToUnicode(QByteArrayView in) return result; } -/*! - \since 5.7 +/*! \internal + \since 6.6 \overload Converts the UTF-8 sequence of bytes viewed by \a in to a sequence of - QChar starting at \a buffer. The buffer is expected to be large enough - to hold the result. An upper bound for the size of the buffer is - \c in.size() QChars. + QChar starting at \a dst in the destination buffer. The buffer is expected + to be large enough to hold the result. An upper bound for the size of the + buffer is \c in.size() QChars. If, during decoding, an error occurs, a QChar::ReplacementCharacter is written. @@ -624,11 +623,12 @@ QString QUtf8::convertToUnicode(QByteArrayView in) Returns a pointer to one past the last QChar written. This function never throws. -*/ -QChar *QUtf8::convertToUnicode(QChar *buffer, QByteArrayView in) noexcept + For QChar buffers, instead of casting manually, you can use the static + QUtf8::convertToUnicode(QChar *, QByteArrayView) directly. +*/ +char16_t *QUtf8::convertToUnicode(char16_t *dst, QByteArrayView in) noexcept { - char16_t *dst = reinterpret_cast(buffer); const uchar *const start = reinterpret_cast(in.data()); const uchar *src = start; const uchar *end = src + in.size(); @@ -651,7 +651,7 @@ QChar *QUtf8::convertToUnicode(QChar *buffer, QByteArrayView in) noexcept do { uchar b = *src++; - int res = QUtf8Functions::fromUtf8(b, dst, src, end); + const qsizetype res = QUtf8Functions::fromUtf8(b, dst, src, end); if (res < 0) { // decoding error *dst++ = QChar::ReplacementCharacter; @@ -660,7 +660,7 @@ QChar *QUtf8::convertToUnicode(QChar *buffer, QByteArrayView in) noexcept } } - return reinterpret_cast(dst); + return dst; } QString QUtf8::convertToUnicode(QByteArrayView in, QStringConverter::State *state) @@ -681,23 +681,22 @@ QString QUtf8::convertToUnicode(QByteArrayView in, QStringConverter::State *stat return result; } -QChar *QUtf8::convertToUnicode(QChar *out, QByteArrayView in, QStringConverter::State *state) +char16_t *QUtf8::convertToUnicode(char16_t *dst, QByteArrayView in, QStringConverter::State *state) { qsizetype len = in.size(); Q_ASSERT(state); if (!len) - return out; + return dst; char16_t replacement = QChar::ReplacementCharacter; if (state->flags & QStringConverter::Flag::ConvertInvalidToNull) replacement = QChar::Null; - int res; + qsizetype res; uchar ch = 0; - char16_t *dst = reinterpret_cast(out); const uchar *src = reinterpret_cast(in.data()); const uchar *end = src + len; @@ -725,7 +724,7 @@ QChar *QUtf8::convertToUnicode(QChar *out, QByteArrayView in, QStringConverter:: // copy to our state and return state->remainingChars = remainingCharsCount + newCharsToCopy; memcpy(&state->state_data[0], remainingCharsData, state->remainingChars); - return out; + return dst; } else if (!headerdone) { // eat the UTF-8 BOM if (dst[-1] == 0xfeff) @@ -781,7 +780,7 @@ QChar *QUtf8::convertToUnicode(QChar *out, QByteArrayView in, QStringConverter:: state->remainingChars = 0; } - return reinterpret_cast(dst); + return dst; } struct QUtf8NoOutputTraits : public QUtf8BaseTraitsNoAscii @@ -811,7 +810,7 @@ QUtf8::ValidUtf8Result QUtf8::isValidUtf8(QByteArrayView in) isValidAscii = false; QUtf8NoOutputTraits::NoOutput output; - int res = QUtf8Functions::fromUtf8(b, output, src, end); + const qsizetype res = QUtf8Functions::fromUtf8(b, output, src, end); if (res < 0) { // decoding error return { false, false }; @@ -838,7 +837,7 @@ int QUtf8::compareUtf8(QByteArrayView utf8, QStringView utf16, Qt::CaseSensitivi if (uc1 >= 0x80) { char32_t *output = &uc1; - int res = QUtf8Functions::fromUtf8(uc1, output, src1, end1); + qsizetype res = QUtf8Functions::fromUtf8(uc1, output, src1, end1); if (res < 0) { // decoding error uc1 = QChar::ReplacementCharacter; @@ -873,7 +872,7 @@ int QUtf8::compareUtf8(QByteArrayView utf8, QLatin1StringView s, Qt::CaseSensiti while (src1 < end1 && src2 < end2) { uchar b = *src1++; char32_t *output = &uc1; - int res = QUtf8Functions::fromUtf8(b, output, src1, end1); + const qsizetype res = QUtf8Functions::fromUtf8(b, output, src1, end1); if (res < 0) { // decoding error uc1 = QChar::ReplacementCharacter; @@ -913,7 +912,7 @@ int QUtf8::compareUtf8(QByteArrayView lhs, QByteArrayView rhs, Qt::CaseSensitivi while (src1 < end1 && src2 < end2) { uchar b = *src1++; char32_t *output = &uc1; - int res = QUtf8Functions::fromUtf8(b, output, src1, end1); + qsizetype res = QUtf8Functions::fromUtf8(b, output, src1, end1); if (res < 0) { // decoding error uc1 = QChar::ReplacementCharacter; @@ -1523,19 +1522,7 @@ static char *toUtf32LE(char *out, QStringView in, QStringConverter::State *state return QUtf32::convertFromUnicode(out, in, state, LittleEndianness); } -void qt_from_latin1(char16_t *dst, const char *str, size_t size) noexcept; - -static QChar *fromLatin1(QChar *out, QByteArrayView in, QStringConverter::State *state) -{ - Q_ASSERT(state); - Q_UNUSED(state); - - qt_from_latin1(reinterpret_cast(out), in.data(), size_t(in.size())); - return out + in.size(); -} - - -static char *toLatin1(char *out, QStringView in, QStringConverter::State *state) +char *QLatin1::convertFromUnicode(char *out, QStringView in, QStringConverter::State *state) noexcept { Q_ASSERT(state); if (state->flags & QStringConverter::Flag::Stateless) // temporary @@ -1721,7 +1708,7 @@ const QStringConverter::Interface QStringConverter::encodingInterfaces[QStringCo { "UTF-32", fromUtf32, fromUtf32Len, toUtf32, toUtf32Len }, { "UTF-32LE", fromUtf32LE, fromUtf32Len, toUtf32LE, toUtf32Len }, { "UTF-32BE", fromUtf32BE, fromUtf32Len, toUtf32BE, toUtf32Len }, - { "ISO-8859-1", fromLatin1, fromLatin1Len, toLatin1, toLatin1Len }, + { "ISO-8859-1", QLatin1::convertToUnicode, fromLatin1Len, QLatin1::convertFromUnicode, toLatin1Len }, { "Locale", fromLocal8Bit, fromUtf8Len, toLocal8Bit, toUtf8Len } }; @@ -2387,4 +2374,10 @@ const char *QStringConverter::nameForEncoding(QStringConverter::Encoding e) \sa requiredSpace */ +/*! + \fn char16_t *QStringDecoder::appendToBuffer(char16_t *out, QByteArrayView in) + \since 6.6 + \overload +*/ + QT_END_NAMESPACE diff --git a/src/corelib/text/qstringconverter.h b/src/corelib/text/qstringconverter.h index a61973ca..e1251696 100644 --- a/src/corelib/text/qstringconverter.h +++ b/src/corelib/text/qstringconverter.h @@ -138,6 +138,8 @@ public: } return iface->toUtf16(out, ba, &state); } + char16_t *appendToBuffer(char16_t *out, QByteArrayView ba) + { return reinterpret_cast(appendToBuffer(reinterpret_cast(out), ba)); } Q_CORE_EXPORT static QStringDecoder decoderForHtml(QByteArrayView data); diff --git a/src/corelib/text/qstringconverter_p.h b/src/corelib/text/qstringconverter_p.h index 95d1eea0..edbe1b54 100644 --- a/src/corelib/text/qstringconverter_p.h +++ b/src/corelib/text/qstringconverter_p.h @@ -29,6 +29,32 @@ enum qchar8_t : uchar {}; using qchar8_t = char8_t; #endif +struct QLatin1 +{ + // Defined in qstring.cpp + static char16_t *convertToUnicode(char16_t *dst, QLatin1StringView in) noexcept; + + static QChar *convertToUnicode(QChar *buffer, QLatin1StringView in) noexcept + { + char16_t *dst = reinterpret_cast(buffer); + dst = convertToUnicode(dst, in); + return reinterpret_cast(dst); + } + + static QChar *convertToUnicode(QChar *dst, QByteArrayView in, + [[maybe_unused]] QStringConverterBase::State *state) noexcept + { + Q_ASSERT(state); + + return convertToUnicode(dst, QLatin1StringView(in.data(), in.size())); + } + + static char *convertFromUnicode(char *out, QStringView in, QStringConverter::State *state) noexcept; + + // Defined in qstring.cpp + static char *convertFromUnicode(char *out, QStringView in) noexcept; +}; + struct QUtf8BaseTraits { static const bool isTrusted = false; @@ -263,10 +289,26 @@ enum DataEndianness struct QUtf8 { - Q_CORE_EXPORT static QChar *convertToUnicode(QChar *buffer, QByteArrayView in) noexcept; + static QChar *convertToUnicode(QChar *buffer, QByteArrayView in) noexcept + { + char16_t *dst = reinterpret_cast(buffer); + dst = QUtf8::convertToUnicode(dst, in); + return reinterpret_cast(dst); + } + + Q_CORE_EXPORT static char16_t* convertToUnicode(char16_t *dst, QByteArrayView in) noexcept; static QString convertToUnicode(QByteArrayView in); Q_CORE_EXPORT static QString convertToUnicode(QByteArrayView in, QStringConverter::State *state); - static QChar *convertToUnicode(QChar *out, QByteArrayView in, QStringConverter::State *state); + + static QChar *convertToUnicode(QChar *out, QByteArrayView in, QStringConverter::State *state) + { + char16_t *buffer = reinterpret_cast(out); + buffer = convertToUnicode(buffer, in, state); + return reinterpret_cast(buffer); + } + + static char16_t *convertToUnicode(char16_t *dst, QByteArrayView in, QStringConverter::State *state); + Q_CORE_EXPORT static QByteArray convertFromUnicode(QStringView in); Q_CORE_EXPORT static QByteArray convertFromUnicode(QStringView in, QStringConverterBase::State *state); static char *convertFromUnicode(char *out, QStringView in, QStringConverter::State *state); diff --git a/src/corelib/text/qstringmatcher.cpp b/src/corelib/text/qstringmatcher.cpp index 39fd45cf..ac66ec80 100644 --- a/src/corelib/text/qstringmatcher.cpp +++ b/src/corelib/text/qstringmatcher.cpp @@ -13,7 +13,7 @@ static void bm_init_skiptable(QStringView needle, uchar *skiptable, Qt::CaseSens const char16_t *uc = needle.utf16(); const qsizetype len = cs == Qt::CaseSensitive ? needle.size() : qMin(needle.size(), FoldBufferCapacity); - qsizetype l = qMin(len, qsizetype(255)); + int l = qMin(int(len), 255); memset(skiptable, l, 256 * sizeof(uchar)); uc += len - l; if (cs == Qt::CaseSensitive) { diff --git a/src/corelib/text/qstringview.cpp b/src/corelib/text/qstringview.cpp index 4e9234fd..302637ee 100644 --- a/src/corelib/text/qstringview.cpp +++ b/src/corelib/text/qstringview.cpp @@ -1369,17 +1369,6 @@ or the character \a ch \since 6.0 */ -/*! - \fn int QLatin1StringView::compare(QUtf8StringView str, Qt::CaseSensitivity cs) const - \since 6.5 - - Returns an integer that compares to zero as this string view compares to the - string view \a str. - - \include qstring.qdocinc {search-comparison-case-sensitivity} {comparison} - - \sa operator==(), operator<(), operator>() -*/ /*! \fn template auto QStringView::tokenize(Needle &&sep, Flags...flags) const diff --git a/src/corelib/thread/qatomic.h b/src/corelib/thread/qatomic.h index 24f2616e..7fe5ac69 100644 --- a/src/corelib/thread/qatomic.h +++ b/src/corelib/thread/qatomic.h @@ -2,8 +2,6 @@ // Copyright (C) 2016 Intel Corporation. // SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only -#include - #ifndef QATOMIC_H #define QATOMIC_H diff --git a/src/corelib/thread/qbasicatomic.h b/src/corelib/thread/qbasicatomic.h index 93f9648a..e3fbe558 100644 --- a/src/corelib/thread/qbasicatomic.h +++ b/src/corelib/thread/qbasicatomic.h @@ -2,8 +2,6 @@ // Copyright (C) 2018 Intel Corporation. // SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only -#include - #ifndef QBASICATOMIC_H #define QBASICATOMIC_H @@ -30,7 +28,7 @@ public: typedef T Type; typedef QAtomicOps Ops; // static check that this is a valid integer - static_assert(QTypeInfo::isIntegral, "template parameter is not an integral type"); + static_assert(std::is_integral_v, "template parameter is not an integral type"); static_assert(QAtomicOpsSupport::IsSupported, "template parameter is an integral of a size not supported on this platform"); typename Ops::Type _q_value; diff --git a/src/corelib/thread/qfutex_p.h b/src/corelib/thread/qfutex_p.h index efb14851..48f03f5e 100644 --- a/src/corelib/thread/qfutex_p.h +++ b/src/corelib/thread/qfutex_p.h @@ -107,6 +107,39 @@ namespace QtLinuxFutex { } namespace QtFutex = QtLinuxFutex; QT_END_NAMESPACE + +#elif defined(Q_OS_WIN) +# include + +QT_BEGIN_NAMESPACE +namespace QtWindowsFutex { +#define QT_ALWAYS_USE_FUTEX +constexpr inline bool futexAvailable() { return true; } + +template +inline void futexWait(Atomic &futex, typename Atomic::Type expectedValue) +{ + QtTsan::futexRelease(&futex); + WaitOnAddress(&futex, &expectedValue, sizeof(expectedValue), INFINITE); + QtTsan::futexAcquire(&futex); +} +template +inline bool futexWait(Atomic &futex, typename Atomic::Type expectedValue, qint64 nstimeout) +{ + BOOL r = WaitOnAddress(&futex, &expectedValue, sizeof(expectedValue), DWORD(nstimeout / 1000 / 1000)); + return r || GetLastError() != ERROR_TIMEOUT; +} +template inline void futexWakeAll(Atomic &futex) +{ + WakeByAddressAll(&futex); +} +template inline void futexWakeOne(Atomic &futex) +{ + WakeByAddressSingle(&futex); +} +} +namespace QtFutex = QtWindowsFutex; +QT_END_NAMESPACE #else QT_BEGIN_NAMESPACE diff --git a/src/corelib/thread/qfuture.h b/src/corelib/thread/qfuture.h index 34fb8a03..572c8054 100644 --- a/src/corelib/thread/qfuture.h +++ b/src/corelib/thread/qfuture.h @@ -522,6 +522,19 @@ QFuture...>> whenAny(Futures &&... futures); #endif // Q_QDOC +#if QT_DEPRECATED_SINCE(6, 10) +#if defined(Q_QDOC) +static QFuture makeReadyFuture() +#else +template +QT_DEPRECATED_VERSION_X(6, 10, "Use makeReadyVoidFuture() instead") +static QFuture makeReadyFuture() +#endif +{ + return makeReadyVoidFuture(); +} +#endif // QT_DEPRECATED_SINCE(6, 10) + } // namespace QtFuture Q_DECLARE_SEQUENTIAL_ITERATOR(Future) diff --git a/src/corelib/thread/qfuture.qdoc b/src/corelib/thread/qfuture.qdoc index 55a85b3a..2535d621 100644 --- a/src/corelib/thread/qfuture.qdoc +++ b/src/corelib/thread/qfuture.qdoc @@ -118,7 +118,8 @@ combine several futures and track when the last or first of them completes. A ready QFuture object with a value or a QFuture object holding exception can - be created using convenience functions QtFuture::makeReadyFuture() and + be created using convenience functions QtFuture::makeReadyVoidFuture(), + QtFuture::makeReadyValueFuture(), QtFuture::makeReadyRangeFuture(), and QtFuture::makeExceptionalFuture(). \note Some APIs (see \l {QFuture::then()} or various QtConcurrent method @@ -130,7 +131,8 @@ \note To start a computation and store results in a QFuture, use QPromise or one of the APIs in the \l {Qt Concurrent} framework. - \sa QPromise, QtFuture::connect(), QtFuture::makeReadyFuture(), + \sa QPromise, QtFuture::connect(), QtFuture::makeReadyVoidFuture(), + QtFuture::makeReadyValueFuture(), QtFuture::makeReadyRangeFuture(), QtFuture::makeExceptionalFuture(), QFutureWatcher, {Qt Concurrent} */ @@ -977,6 +979,12 @@ \since 6.1 \overload + \deprecated [6.10] Use makeReadyValueFuture() instead + + The QtFuture::makeReadyFuture() method should be avoided because it has an + inconsistent set of overloads. It will be deprecated in future Qt releases. + Use QtFuture::makeReadyVoidFuture(), QtFuture::makeReadyValueFuture() or + QtFuture::makeReadyRangeFuture() instead. Creates and returns a QFuture which already has a result \a value. The returned QFuture has a type of std::decay_t, where T is not void. @@ -987,13 +995,21 @@ const int result = *f.takeResult(); // result == 42 \endcode - \sa QFuture, QtFuture::makeExceptionalFuture() + \sa QFuture, QtFuture::makeReadyVoidFuture(), + QtFuture::makeReadyValueFuture(), QtFuture::makeReadyRangeFuture(), + QtFuture::makeExceptionalFuture() */ /*! \fn QFuture QtFuture::makeReadyFuture() \since 6.1 \overload + \deprecated [6.10] Use makeReadyVoidFuture() instead + + The QtFuture::makeReadyFuture() method should be avoided because it has an + inconsistent set of overloads. It will be deprecated in future Qt releases. + Use QtFuture::makeReadyVoidFuture(), QtFuture::makeReadyValueFuture() or + QtFuture::makeReadyRangeFuture() instead. Creates and returns a void QFuture. Such QFuture can't store any result. One can use it to query the state of the computation. @@ -1008,13 +1024,21 @@ \endcode \sa QFuture, QFuture::isStarted(), QFuture::isRunning(), - QFuture::isFinished(), QtFuture::makeExceptionalFuture() + QFuture::isFinished(), QtFuture::makeReadyVoidFuture(), + QtFuture::makeReadyValueFuture(), QtFuture::makeReadyRangeFuture(), + QtFuture::makeExceptionalFuture() */ /*! \fn template static QFuture QtFuture::makeReadyFuture(const QList &values) \since 6.1 \overload + \deprecated [6.10] Use makeReadyRangeFuture() instead + + The QtFuture::makeReadyFuture() method should be avoided because it has an + inconsistent set of overloads. It will be deprecated in future Qt releases. + Use QtFuture::makeReadyVoidFuture(), QtFuture::makeReadyValueFuture() or + QtFuture::makeReadyRangeFuture() instead. Creates and returns a QFuture which already has multiple results set from \a values. @@ -1026,7 +1050,38 @@ const auto results = f.results(); // results == { 1, 2, 3 } \endcode - \sa QFuture, QtFuture::makeExceptionalFuture() + \sa QFuture, QtFuture::makeReadyVoidFuture(), + QtFuture::makeReadyValueFuture(), QtFuture::makeReadyRangeFuture(), + QtFuture::makeExceptionalFuture() +*/ + +/*! \fn template static QFuture> QtFuture::makeReadyValueFuture(T &&value) + + \since 6.6 + + Creates and returns a QFuture which already has a result \a value. + The returned QFuture has a type of std::decay_t, where T is not void. + The returned QFuture will already be in the finished state. + + \snippet code/src_corelib_thread_qfuture.cpp 35 + + \sa QFuture, QtFuture::makeReadyRangeFuture(), + QtFuture::makeReadyVoidFuture(), QtFuture::makeExceptionalFuture() +*/ + +/*! \fn QFuture QtFuture::makeReadyVoidFuture() + + \since 6.6 + + Creates and returns a void QFuture. Such QFuture can't store any result. + One can use it to query the state of the computation. + The returned QFuture will already be in the finished state. + + \snippet code/src_corelib_thread_qfuture.cpp 36 + + \sa QFuture, QFuture::isStarted(), QFuture::isRunning(), + QFuture::isFinished(), QtFuture::makeReadyValueFuture(), + QtFuture::makeReadyRangeFuture(), QtFuture::makeExceptionalFuture() */ /*! \fn template static QFuture QtFuture::makeExceptionalFuture(const QException &exception) @@ -1046,7 +1101,8 @@ } \endcode - \sa QFuture, QException, QtFuture::makeReadyFuture() + \sa QFuture, QException, QtFuture::makeReadyVoidFuture(), + QtFuture::makeReadyValueFuture() */ /*! \fn template static QFuture QtFuture::makeExceptionalFuture(std::exception_ptr exception) @@ -1071,7 +1127,44 @@ } \endcode - \sa QFuture, QException, QtFuture::makeReadyFuture() + \sa QFuture, QException, QtFuture::makeReadyVoidFuture(), + QtFuture::makeReadyValueFuture() +*/ + +/*! \fn template> static QFuture> QtFuture::makeReadyRangeFuture(Container &&container) + + \since 6.6 + \overload + + Takes an input container \a container and returns a QFuture with multiple + results of type \c ContainedType initialized from the values of the + \a container. + + \note This overload only participates in overload resolution if the + \c Container has input iterators. + + \snippet code/src_corelib_thread_qfuture.cpp 32 + \dots + \snippet code/src_corelib_thread_qfuture.cpp 34 + + \sa QFuture, QtFuture::makeReadyVoidFuture(), + QtFuture::makeReadyValueFuture(), QtFuture::makeExceptionalFuture() +*/ + +/*! \fn template static QFuture QtFuture::makeReadyRangeFuture(std::initializer_list values) + + \since 6.6 + \overload + + Returns a QFuture with multiple results of type \c ValueType initialized + from the input initializer list \a values. + + \snippet code/src_corelib_thread_qfuture.cpp 33 + \dots + \snippet code/src_corelib_thread_qfuture.cpp 34 + + \sa QFuture, QtFuture::makeReadyVoidFuture(), + QtFuture::makeReadyValueFuture(), QtFuture::makeExceptionalFuture() */ /*! \fn template template QFuture::ResultType> QFuture::then(Function &&function) @@ -1210,8 +1303,18 @@ is attached. In this case it will be resolved in the current thread. Therefore, when in doubt, pass the context explicitly. + \target context_lifetime + If the \a context is destroyed before the chain has finished, the future is canceled. + This implies that a cancellation handler might be invoked when the \a context is not valid + anymore. To guard against this, capture the \a context as a QPointer: + + \snippet code/src_corelib_thread_qfuture.cpp 37 + + When the context object is destroyed, cancellation happens immediately. Previous futures in the + chain are \e {not} cancelled and keep running until they are finished. + \note When calling this method, it should be guaranteed that the \a context stays alive - throughout the execution of the chain. + during setup of the chain. \sa onFailed(), onCanceled() */ @@ -1276,8 +1379,11 @@ be invoked from a non-gui thread. So \c this is provided as a context to \c .onFailed(), to make sure that it will be invoked in the main thread. + If the \a context is destroyed before the chain has finished, the future is canceled. + See \l {context_lifetime}{then()} for details. + \note When calling this method, it should be guaranteed that the \a context stays alive - throughout the execution of the chain. + during setup of the chain. See the documentation of the other overload for more details about \a handler. @@ -1339,8 +1445,11 @@ invoked in the thread of the \a context object. This can be useful if the cancellation needs to be handled in a specific thread. + If the \a context is destroyed before the chain has finished, the future is canceled. + See \l {context_lifetime}{then()} for details. + \note When calling this method, it should be guaranteed that the \a context stays alive - throughout the execution of the chain. + during setup of the chain. See the documentation of the other overload for more details about \a handler. diff --git a/src/corelib/thread/qfuture_impl.h b/src/corelib/thread/qfuture_impl.h index bcf29eb3..bf980716 100644 --- a/src/corelib/thread/qfuture_impl.h +++ b/src/corelib/thread/qfuture_impl.h @@ -274,6 +274,12 @@ using IsRandomAccessible = std::begin(std::declval()))>>::iterator_category, std::random_access_iterator_tag>; +template +using HasInputIterator = + std::is_convertible()))>>::iterator_category, + std::input_iterator_tag>; + template using IsForwardIterable = std::is_convertible::iterator_category, @@ -583,6 +589,18 @@ void Continuation::create(F &&func, f->d.setContinuation(ContinuationWrapper(std::move(continuation)), fi.d); } +// defined in qfutureinterface.cpp: +Q_CORE_EXPORT void watchContinuationImpl(const QObject *context, QSlotObjectBase *slotObj, + QFutureInterfaceBase &fi); +template +void watchContinuation(const QObject *context, Continuation &&c, QFutureInterfaceBase &fi) +{ + using Prototype = typename QtPrivate::Callable::Function; + watchContinuationImpl(context, + QtPrivate::makeCallableObject(std::forward(c)), + fi); +} + template template void Continuation::create(F &&func, @@ -591,21 +609,19 @@ void Continuation::create(F &&func, QObject *context) { Q_ASSERT(f); + Q_ASSERT(context); - auto continuation = [func = std::forward(func), fi, - context = QPointer(context)]( - const QFutureInterfaceBase &parentData) mutable { - Q_ASSERT(context); - const auto parent = QFutureInterface(parentData).future(); - QMetaObject::invokeMethod( - context, - [func = std::forward(func), promise = QPromise(fi), parent]() mutable { - SyncContinuation continuationJob( - std::forward(func), parent, std::move(promise)); - continuationJob.execute(); - }); + // When the context object is destroyed, the signal-slot connection is broken and the + // continuation callback is destroyed. The promise that is created in the capture list is + // destroyed and, if it is not yet finished, cancelled. + auto continuation = [func = std::forward(func), parent = *f, + promise = QPromise(fi)]() mutable { + SyncContinuation continuationJob( + std::forward(func), parent, std::move(promise)); + continuationJob.execute(); }; - f->d.setContinuation(ContinuationWrapper(std::move(continuation)), fi.d); + + QtPrivate::watchContinuation(context, std::move(continuation), f->d); } template @@ -689,22 +705,15 @@ void FailureHandler::create(F &&function, QFuture(function), + parent = *future, promise = QPromise(fi)]() mutable { + FailureHandler failureHandler( + std::forward(function), parent, std::move(promise)); + failureHandler.run(); + }; - auto failureContinuation = - [function = std::forward(function), fi, - context = QPointer(context)](const QFutureInterfaceBase &parentData) mutable { - Q_ASSERT(context); - const auto parent = QFutureInterface(parentData).future(); - QMetaObject::invokeMethod(context, - [function = std::forward(function), - promise = QPromise(fi), parent]() mutable { - FailureHandler failureHandler( - std::forward(function), parent, std::move(promise)); - failureHandler.run(); - }); - }; - - future->d.setContinuation(ContinuationWrapper(std::move(failureContinuation))); + QtPrivate::watchContinuation(context, std::move(failureContinuation), future->d); } template @@ -792,19 +801,13 @@ public: QObject *context) { Q_ASSERT(future); - auto canceledContinuation = [fi, handler = std::forward(handler), - context = QPointer(context)]( - const QFutureInterfaceBase &parentData) mutable { - Q_ASSERT(context); - auto parentFuture = QFutureInterface(parentData).future(); - QMetaObject::invokeMethod(context, - [promise = QPromise(fi), parentFuture, - handler = std::forward(handler)]() mutable { - run(std::forward(handler), parentFuture, std::move(promise)); - }); + Q_ASSERT(context); + auto canceledContinuation = [handler = std::forward(handler), + parentFuture = *future, promise = QPromise(fi)]() mutable { + run(std::forward(handler), parentFuture, std::move(promise)); }; - future->d.setContinuation(ContinuationWrapper(std::move(canceledContinuation))); + QtPrivate::watchContinuation(context, std::move(canceledContinuation), future->d); } template @@ -888,6 +891,16 @@ struct UnwrapHandler } }; +template +QFuture makeReadyRangeFutureImpl(const QList &values) +{ + QFutureInterface promise; + promise.reportStarted(); + promise.reportResults(values); + promise.reportFinished(); + return promise.future(); +} + } // namespace QtPrivate namespace QtFuture { @@ -953,8 +966,37 @@ static QFuture> connect(Sender *sender, Signal signal) return promise.future(); } -template> -static QFuture> makeReadyFuture(T &&value) +template +using if_container_with_input_iterators = + std::enable_if_t::value, bool>; + +template +using ContainedType = + typename std::iterator_traits()))>::value_type; + +template = true> +static QFuture> makeReadyRangeFuture(Container &&container) +{ + // handle QList separately, because reportResults() takes a QList + // as an input + using ValueType = ContainedType; + if constexpr (std::is_convertible_v, QList>) { + return QtPrivate::makeReadyRangeFutureImpl(container); + } else { + return QtPrivate::makeReadyRangeFutureImpl(QList{std::cbegin(container), + std::cend(container)}); + } +} + +template +static QFuture makeReadyRangeFuture(std::initializer_list values) +{ + return QtPrivate::makeReadyRangeFutureImpl(QList{values}); +} + +template +static QFuture> makeReadyValueFuture(T &&value) { QFutureInterface> promise; promise.reportStarted(); @@ -964,30 +1006,26 @@ static QFuture> makeReadyFuture(T &&value) return promise.future(); } -#if defined(Q_QDOC) -static QFuture makeReadyFuture() -#else -template -static QFuture makeReadyFuture() -#endif -{ - QFutureInterface promise; - promise.reportStarted(); - promise.reportFinished(); +Q_CORE_EXPORT QFuture makeReadyVoidFuture(); // implemented in qfutureinterface.cpp - return promise.future(); +#if QT_DEPRECATED_SINCE(6, 10) +template> +QT_DEPRECATED_VERSION_X(6, 10, "Use makeReadyValueFuture() instead") +static QFuture> makeReadyFuture(T &&value) +{ + return makeReadyValueFuture(std::forward(value)); } +// the void specialization is moved to the end of qfuture.h, because it now +// uses makeReadyVoidFuture() and required QFuture to be defined. + template +QT_DEPRECATED_VERSION_X(6, 10, "Use makeReadyRangeFuture() instead") static QFuture makeReadyFuture(const QList &values) { - QFutureInterface promise; - promise.reportStarted(); - promise.reportResults(values); - promise.reportFinished(); - - return promise.future(); + return makeReadyRangeFuture(values); } +#endif // QT_DEPRECATED_SINCE(6, 10) #ifndef QT_NO_EXCEPTIONS @@ -1067,9 +1105,10 @@ void addCompletionHandlersImpl(const std::shared_ptr &context, { auto future = std::get(t); using ResultType = typename ContextType::ValueType; - future.then([context](const std::tuple_element_t> &f) { + // Need context=context so that the compiler does not infer the captured variable's type as 'const' + future.then([context=context](const std::tuple_element_t> &f) { context->checkForCompletion(Index, ResultType { std::in_place_index, f }); - }).onCanceled([context, future]() { + }).onCanceled([context=context, future]() { context->checkForCompletion(Index, ResultType { std::in_place_index, future }); }); @@ -1089,7 +1128,7 @@ QFuture whenAllImpl(InputIt first, InputIt last) { const qsizetype size = std::distance(first, last); if (size == 0) - return QtFuture::makeReadyFuture(OutputSequence()); + return QtFuture::makeReadyValueFuture(OutputSequence()); const auto context = std::make_shared>(size); context->futures.resize(size); @@ -1097,9 +1136,10 @@ QFuture whenAllImpl(InputIt first, InputIt last) qsizetype idx = 0; for (auto it = first; it != last; ++it, ++idx) { - it->then([context, idx](const ValueType &f) { + // Need context=context so that the compiler does not infer the captured variable's type as 'const' + it->then([context=context, idx](const ValueType &f) { context->checkForCompletion(idx, f); - }).onCanceled([context, idx, f = *it] { + }).onCanceled([context=context, idx, f = *it] { context->checkForCompletion(idx, f); }); } @@ -1128,7 +1168,7 @@ QFuture::type>> whenAnyImpl(I const qsizetype size = std::distance(first, last); if (size == 0) { - return QtFuture::makeReadyFuture( + return QtFuture::makeReadyValueFuture( QtFuture::WhenAnyResult { qsizetype(-1), QFuture() }); } @@ -1137,9 +1177,10 @@ QFuture::type>> whenAnyImpl(I qsizetype idx = 0; for (auto it = first; it != last; ++it, ++idx) { - it->then([context, idx](const ValueType &f) { + // Need context=context so that the compiler does not infer the captured variable's type as 'const' + it->then([context=context, idx](const ValueType &f) { context->checkForCompletion(idx, QtFuture::WhenAnyResult { idx, f }); - }).onCanceled([context, idx, f = *it] { + }).onCanceled([context=context, idx, f = *it] { context->checkForCompletion(idx, QtFuture::WhenAnyResult { idx, f }); }); } diff --git a/src/corelib/thread/qfutureinterface.cpp b/src/corelib/thread/qfutureinterface.cpp index 680afa00..de35089b 100644 --- a/src/corelib/thread/qfutureinterface.cpp +++ b/src/corelib/thread/qfutureinterface.cpp @@ -6,9 +6,12 @@ #include "qfutureinterface_p.h" #include +#include #include +#include #include // for qYieldCpu() #include +#include #ifdef interface # undef interface @@ -29,6 +32,7 @@ namespace { class ThreadPoolThreadReleaser { QThreadPool *m_pool; public: + Q_NODISCARD_CTOR explicit ThreadPoolThreadReleaser(QThreadPool *pool) : m_pool(pool) { if (pool) pool->releaseThread(); } @@ -41,6 +45,99 @@ const auto suspendingOrSuspended = } // unnamed namespace +class QBasicFutureWatcher : public QObject, QFutureCallOutInterface +{ + Q_OBJECT +public: + explicit QBasicFutureWatcher(QObject *parent = nullptr); + ~QBasicFutureWatcher() override; + + void setFuture(QFutureInterfaceBase &fi); + + bool event(QEvent *event) override; + +Q_SIGNALS: + void finished(); + +private: + QFutureInterfaceBase future; + + void postCallOutEvent(const QFutureCallOutEvent &event) override; + void callOutInterfaceDisconnected() override; +}; + +void QBasicFutureWatcher::postCallOutEvent(const QFutureCallOutEvent &event) +{ + if (thread() == QThread::currentThread()) { + // If we are in the same thread, don't queue up anything. + std::unique_ptr clonedEvent(event.clone()); + QCoreApplication::sendEvent(this, clonedEvent.get()); + } else { + QCoreApplication::postEvent(this, event.clone()); + } +} + +void QBasicFutureWatcher::callOutInterfaceDisconnected() +{ + QCoreApplication::removePostedEvents(this, QEvent::FutureCallOut); +} + +/* + * QBasicFutureWatcher is a more lightweight version of QFutureWatcher for internal use + */ +QBasicFutureWatcher::QBasicFutureWatcher(QObject *parent) + : QObject(parent) +{ +} + +QBasicFutureWatcher::~QBasicFutureWatcher() +{ + future.d->disconnectOutputInterface(this); +} + +void QBasicFutureWatcher::setFuture(QFutureInterfaceBase &fi) +{ + future = fi; + future.d->connectOutputInterface(this); +} + +bool QBasicFutureWatcher::event(QEvent *event) +{ + if (event->type() == QEvent::FutureCallOut) { + QFutureCallOutEvent *callOutEvent = static_cast(event); + if (callOutEvent->callOutType == QFutureCallOutEvent::Finished) + emit finished(); + return true; + } + return QObject::event(event); +} + +void QtPrivate::watchContinuationImpl(const QObject *context, QSlotObjectBase *slotObj, + QFutureInterfaceBase &fi) +{ + Q_ASSERT(context); + Q_ASSERT(slotObj); + + auto slot = SlotObjUniquePtr(slotObj); + + auto *watcher = new QBasicFutureWatcher; + watcher->moveToThread(context->thread()); + // ### we're missing a convenient way to `QObject::connect()` to a `QSlotObjectBase`... + QObject::connect(watcher, &QBasicFutureWatcher::finished, + // for the following, cf. QMetaObject::invokeMethodImpl(): + // we know `slot` is a lambda returning `void`, so we can just + // `call()` with `obj` and `args[0]` set to `nullptr`: + watcher, [slot = std::move(slot)] { + void *args[] = { nullptr }; // for `void` return value + slot->call(nullptr, args); + }); + QObject::connect(watcher, &QBasicFutureWatcher::finished, + watcher, &QObject::deleteLater); + QObject::connect(context, &QObject::destroyed, + watcher, &QObject::deleteLater); + watcher->setFuture(fi); +} + QFutureCallOutInterface::~QFutureCallOutInterface() = default; @@ -750,23 +847,25 @@ void QFutureInterfaceBasePrivate::connectOutputInterface(QFutureCallOutInterface { QMutexLocker locker(&m_mutex); + QVarLengthArray, 3> events; + const auto currentState = state.loadRelaxed(); if (currentState & QFutureInterfaceBase::Started) { - interface->postCallOutEvent(QFutureCallOutEvent(QFutureCallOutEvent::Started)); + events.emplace_back(new QFutureCallOutEvent(QFutureCallOutEvent::Started)); if (m_progress) { - interface->postCallOutEvent(QFutureCallOutEvent(QFutureCallOutEvent::ProgressRange, - m_progress->minimum, - m_progress->maximum)); - interface->postCallOutEvent(QFutureCallOutEvent(QFutureCallOutEvent::Progress, - m_progressValue, - m_progress->text)); + events.emplace_back(new QFutureCallOutEvent(QFutureCallOutEvent::ProgressRange, + m_progress->minimum, + m_progress->maximum)); + events.emplace_back(new QFutureCallOutEvent(QFutureCallOutEvent::Progress, + m_progressValue, + m_progress->text)); } else { - interface->postCallOutEvent(QFutureCallOutEvent(QFutureCallOutEvent::ProgressRange, - 0, - 0)); - interface->postCallOutEvent(QFutureCallOutEvent(QFutureCallOutEvent::Progress, - m_progressValue, - QString())); + events.emplace_back(new QFutureCallOutEvent(QFutureCallOutEvent::ProgressRange, + 0, + 0)); + events.emplace_back(new QFutureCallOutEvent(QFutureCallOutEvent::Progress, + m_progressValue, + QString())); } } @@ -775,25 +874,29 @@ void QFutureInterfaceBasePrivate::connectOutputInterface(QFutureCallOutInterface while (it != data.m_results.end()) { const int begin = it.resultIndex(); const int end = begin + it.batchSize(); - interface->postCallOutEvent(QFutureCallOutEvent(QFutureCallOutEvent::ResultsReady, - begin, - end)); + events.emplace_back(new QFutureCallOutEvent(QFutureCallOutEvent::ResultsReady, + begin, + end)); it.batchedAdvance(); } } if (currentState & QFutureInterfaceBase::Suspended) - interface->postCallOutEvent(QFutureCallOutEvent(QFutureCallOutEvent::Suspended)); + events.emplace_back(new QFutureCallOutEvent(QFutureCallOutEvent::Suspended)); else if (currentState & QFutureInterfaceBase::Suspending) - interface->postCallOutEvent(QFutureCallOutEvent(QFutureCallOutEvent::Suspending)); + events.emplace_back(new QFutureCallOutEvent(QFutureCallOutEvent::Suspending)); if (currentState & QFutureInterfaceBase::Canceled) - interface->postCallOutEvent(QFutureCallOutEvent(QFutureCallOutEvent::Canceled)); + events.emplace_back(new QFutureCallOutEvent(QFutureCallOutEvent::Canceled)); if (currentState & QFutureInterfaceBase::Finished) - interface->postCallOutEvent(QFutureCallOutEvent(QFutureCallOutEvent::Finished)); + events.emplace_back(new QFutureCallOutEvent(QFutureCallOutEvent::Finished)); outputConnections.append(interface); + + locker.unlock(); + for (auto &&event : events) + interface->postCallOutEvent(*event); } void QFutureInterfaceBasePrivate::disconnectOutputInterface(QFutureCallOutInterface *interface) @@ -888,4 +991,19 @@ bool QFutureInterfaceBase::launchAsync() const return d->launchAsync; } +namespace QtFuture { + +QFuture makeReadyVoidFuture() +{ + QFutureInterface promise; + promise.reportStarted(); + promise.reportFinished(); + + return promise.future(); +} + +} // namespace QtFuture + QT_END_NAMESPACE + +#include "qfutureinterface.moc" diff --git a/src/corelib/thread/qfutureinterface.h b/src/corelib/thread/qfutureinterface.h index 7e682e69..151584e6 100644 --- a/src/corelib/thread/qfutureinterface.h +++ b/src/corelib/thread/qfutureinterface.h @@ -38,7 +38,9 @@ class CanceledHandler; template class FailureHandler; #endif + } +class QBasicFutureWatcher; class Q_CORE_EXPORT QFutureInterfaceBase { @@ -176,6 +178,8 @@ private: friend class QtPrivate::FailureHandler; #endif + friend class QBasicFutureWatcher; + template friend class QPromise; @@ -236,6 +240,8 @@ public: inline QFuture future(); // implemented in qfuture.h + template , bool> = true> + inline bool reportAndEmplaceResult(int index, Args&&...args); inline bool reportResult(const T *result, int index = -1); inline bool reportAndMoveResult(T &&result, int index = -1); inline bool reportResult(T &&result, int index = -1); @@ -301,7 +307,8 @@ inline bool QFutureInterface::reportResult(const T *result, int index) } template -bool QFutureInterface::reportAndMoveResult(T &&result, int index) +template, bool>> +bool QFutureInterface::reportAndEmplaceResult(int index, Args&&...args) { QMutexLocker locker{&mutex()}; if (queryState(Canceled) || queryState(Finished)) @@ -311,13 +318,19 @@ bool QFutureInterface::reportAndMoveResult(T &&result, int index) QtPrivate::ResultStoreBase &store = resultStoreBase(); const int oldResultCount = store.count(); - const int insertIndex = store.moveResult(index, std::move(result)); + const int insertIndex = store.emplaceResult(index, std::forward(args)...); // Let's make sure it's not in pending results. if (insertIndex != -1 && (!store.filterMode() || oldResultCount < store.count())) reportResultsReady(insertIndex, store.count()); return insertIndex != -1; } +template +bool QFutureInterface::reportAndMoveResult(T &&result, int index) +{ + return reportAndEmplaceResult(index, std::move(result)); +} + template bool QFutureInterface::reportResult(T &&result, int index) { diff --git a/src/corelib/thread/qfuturesynchronizer.h b/src/corelib/thread/qfuturesynchronizer.h index 183e3ab3..368ef0c8 100644 --- a/src/corelib/thread/qfuturesynchronizer.h +++ b/src/corelib/thread/qfuturesynchronizer.h @@ -17,7 +17,8 @@ class QFutureSynchronizer Q_DISABLE_COPY(QFutureSynchronizer) public: - QFutureSynchronizer() : m_cancelOnWait(false) { } + Q_NODISCARD_CTOR QFutureSynchronizer() : m_cancelOnWait(false) { } + Q_NODISCARD_CTOR explicit QFutureSynchronizer(QFuture future) : m_cancelOnWait(false) { addFuture(std::move(future)); } diff --git a/src/corelib/thread/qgenericatomic.h b/src/corelib/thread/qgenericatomic.h index 4f4f959f..91ccbbd1 100644 --- a/src/corelib/thread/qgenericatomic.h +++ b/src/corelib/thread/qgenericatomic.h @@ -5,8 +5,9 @@ #ifndef QGENERICATOMIC_H #define QGENERICATOMIC_H -#include -#include +#include +#include +#include QT_BEGIN_NAMESPACE @@ -33,335 +34,5 @@ template struct QAtomicAdditiveType static const int AddScale = sizeof(T); }; -// not really atomic... -template struct QGenericAtomicOps -{ - template struct AtomicType { typedef T Type; typedef T *PointerType; }; - - template static void acquireMemoryFence(const T &_q_value) noexcept - { - BaseClass::orderedMemoryFence(_q_value); - } - template static void releaseMemoryFence(const T &_q_value) noexcept - { - BaseClass::orderedMemoryFence(_q_value); - } - template static void orderedMemoryFence(const T &) noexcept - { - } - - template static Q_ALWAYS_INLINE - T load(const T &_q_value) noexcept - { - return _q_value; - } - - template static Q_ALWAYS_INLINE - void store(T &_q_value, X newValue) noexcept - { - _q_value = newValue; - } - - template static Q_ALWAYS_INLINE - T loadRelaxed(const T &_q_value) noexcept - { - return _q_value; - } - - template static Q_ALWAYS_INLINE - void storeRelaxed(T &_q_value, X newValue) noexcept - { - _q_value = newValue; - } - - template static Q_ALWAYS_INLINE - T loadAcquire(const T &_q_value) noexcept - { - T tmp = *static_cast(&_q_value); - BaseClass::acquireMemoryFence(_q_value); - return tmp; - } - - template static Q_ALWAYS_INLINE - void storeRelease(T &_q_value, X newValue) noexcept - { - BaseClass::releaseMemoryFence(_q_value); - *static_cast(&_q_value) = newValue; - } - - static inline constexpr bool isReferenceCountingNative() noexcept - { return BaseClass::isFetchAndAddNative(); } - static inline constexpr bool isReferenceCountingWaitFree() noexcept - { return BaseClass::isFetchAndAddWaitFree(); } - template static Q_ALWAYS_INLINE - bool ref(T &_q_value) noexcept - { - return BaseClass::fetchAndAddRelaxed(_q_value, 1) != T(-1); - } - - template static Q_ALWAYS_INLINE - bool deref(T &_q_value) noexcept - { - return BaseClass::fetchAndAddRelaxed(_q_value, -1) != 1; - } - -#if 0 - // These functions have no default implementation - // Architectures must implement them - static inline constexpr bool isTestAndSetNative() noexcept; - static inline constexpr bool isTestAndSetWaitFree() noexcept; - template static inline - bool testAndSetRelaxed(T &_q_value, X expectedValue, X newValue) noexcept; - template static inline - bool testAndSetRelaxed(T &_q_value, X expectedValue, X newValue, X *currentValue) noexcept; -#endif - - template static Q_ALWAYS_INLINE - bool testAndSetAcquire(T &_q_value, X expectedValue, X newValue) noexcept - { - bool tmp = BaseClass::testAndSetRelaxed(_q_value, expectedValue, newValue); - BaseClass::acquireMemoryFence(_q_value); - return tmp; - } - - template static Q_ALWAYS_INLINE - bool testAndSetRelease(T &_q_value, X expectedValue, X newValue) noexcept - { - BaseClass::releaseMemoryFence(_q_value); - return BaseClass::testAndSetRelaxed(_q_value, expectedValue, newValue); - } - - template static Q_ALWAYS_INLINE - bool testAndSetOrdered(T &_q_value, X expectedValue, X newValue) noexcept - { - BaseClass::orderedMemoryFence(_q_value); - return BaseClass::testAndSetRelaxed(_q_value, expectedValue, newValue); - } - - template static Q_ALWAYS_INLINE - bool testAndSetAcquire(T &_q_value, X expectedValue, X newValue, X *currentValue) noexcept - { - bool tmp = BaseClass::testAndSetRelaxed(_q_value, expectedValue, newValue, currentValue); - BaseClass::acquireMemoryFence(_q_value); - return tmp; - } - - template static Q_ALWAYS_INLINE - bool testAndSetRelease(T &_q_value, X expectedValue, X newValue, X *currentValue) noexcept - { - BaseClass::releaseMemoryFence(_q_value); - return BaseClass::testAndSetRelaxed(_q_value, expectedValue, newValue, currentValue); - } - - template static Q_ALWAYS_INLINE - bool testAndSetOrdered(T &_q_value, X expectedValue, X newValue, X *currentValue) noexcept - { - BaseClass::orderedMemoryFence(_q_value); - return BaseClass::testAndSetRelaxed(_q_value, expectedValue, newValue, currentValue); - } - - static inline constexpr bool isFetchAndStoreNative() noexcept { return false; } - static inline constexpr bool isFetchAndStoreWaitFree() noexcept { return false; } - - template static Q_ALWAYS_INLINE - T fetchAndStoreRelaxed(T &_q_value, X newValue) noexcept - { - // implement fetchAndStore on top of testAndSet - for (;;) { - T tmp = loadRelaxed(_q_value); - if (BaseClass::testAndSetRelaxed(_q_value, tmp, newValue)) - return tmp; - } - } - - template static Q_ALWAYS_INLINE - T fetchAndStoreAcquire(T &_q_value, X newValue) noexcept - { - T tmp = BaseClass::fetchAndStoreRelaxed(_q_value, newValue); - BaseClass::acquireMemoryFence(_q_value); - return tmp; - } - - template static Q_ALWAYS_INLINE - T fetchAndStoreRelease(T &_q_value, X newValue) noexcept - { - BaseClass::releaseMemoryFence(_q_value); - return BaseClass::fetchAndStoreRelaxed(_q_value, newValue); - } - - template static Q_ALWAYS_INLINE - T fetchAndStoreOrdered(T &_q_value, X newValue) noexcept - { - BaseClass::orderedMemoryFence(_q_value); - return BaseClass::fetchAndStoreRelaxed(_q_value, newValue); - } - - static inline constexpr bool isFetchAndAddNative() noexcept { return false; } - static inline constexpr bool isFetchAndAddWaitFree() noexcept { return false; } - template static Q_ALWAYS_INLINE - T fetchAndAddRelaxed(T &_q_value, typename QAtomicAdditiveType::AdditiveT valueToAdd) noexcept - { - // implement fetchAndAdd on top of testAndSet - for (;;) { - T tmp = BaseClass::loadRelaxed(_q_value); - if (BaseClass::testAndSetRelaxed(_q_value, tmp, T(tmp + valueToAdd))) - return tmp; - } - } - - template static Q_ALWAYS_INLINE - T fetchAndAddAcquire(T &_q_value, typename QAtomicAdditiveType::AdditiveT valueToAdd) noexcept - { - T tmp = BaseClass::fetchAndAddRelaxed(_q_value, valueToAdd); - BaseClass::acquireMemoryFence(_q_value); - return tmp; - } - - template static Q_ALWAYS_INLINE - T fetchAndAddRelease(T &_q_value, typename QAtomicAdditiveType::AdditiveT valueToAdd) noexcept - { - BaseClass::releaseMemoryFence(_q_value); - return BaseClass::fetchAndAddRelaxed(_q_value, valueToAdd); - } - - template static Q_ALWAYS_INLINE - T fetchAndAddOrdered(T &_q_value, typename QAtomicAdditiveType::AdditiveT valueToAdd) noexcept - { - BaseClass::orderedMemoryFence(_q_value); - return BaseClass::fetchAndAddRelaxed(_q_value, valueToAdd); - } - -QT_WARNING_PUSH -QT_WARNING_DISABLE_MSVC(4146) // unary minus operator applied to unsigned type, result still unsigned - template static Q_ALWAYS_INLINE - T fetchAndSubRelaxed(T &_q_value, typename QAtomicAdditiveType::AdditiveT operand) noexcept - { - // implement fetchAndSub on top of fetchAndAdd - return fetchAndAddRelaxed(_q_value, -operand); - } -QT_WARNING_POP - - template static Q_ALWAYS_INLINE - T fetchAndSubAcquire(T &_q_value, typename QAtomicAdditiveType::AdditiveT operand) noexcept - { - T tmp = BaseClass::fetchAndSubRelaxed(_q_value, operand); - BaseClass::acquireMemoryFence(_q_value); - return tmp; - } - - template static Q_ALWAYS_INLINE - T fetchAndSubRelease(T &_q_value, typename QAtomicAdditiveType::AdditiveT operand) noexcept - { - BaseClass::releaseMemoryFence(_q_value); - return BaseClass::fetchAndSubRelaxed(_q_value, operand); - } - - template static Q_ALWAYS_INLINE - T fetchAndSubOrdered(T &_q_value, typename QAtomicAdditiveType::AdditiveT operand) noexcept - { - BaseClass::orderedMemoryFence(_q_value); - return BaseClass::fetchAndSubRelaxed(_q_value, operand); - } - - template static Q_ALWAYS_INLINE - T fetchAndAndRelaxed(T &_q_value, typename std::enable_if::isIntegral, T>::type operand) noexcept - { - // implement fetchAndAnd on top of testAndSet - T tmp = BaseClass::loadRelaxed(_q_value); - for (;;) { - if (BaseClass::testAndSetRelaxed(_q_value, tmp, T(tmp & operand), &tmp)) - return tmp; - } - } - - template static Q_ALWAYS_INLINE - T fetchAndAndAcquire(T &_q_value, typename std::enable_if::isIntegral, T>::type operand) noexcept - { - T tmp = BaseClass::fetchAndAndRelaxed(_q_value, operand); - BaseClass::acquireMemoryFence(_q_value); - return tmp; - } - - template static Q_ALWAYS_INLINE - T fetchAndAndRelease(T &_q_value, typename std::enable_if::isIntegral, T>::type operand) noexcept - { - BaseClass::releaseMemoryFence(_q_value); - return BaseClass::fetchAndAndRelaxed(_q_value, operand); - } - - template static Q_ALWAYS_INLINE - T fetchAndAndOrdered(T &_q_value, typename std::enable_if::isIntegral, T>::type operand) noexcept - { - BaseClass::orderedMemoryFence(_q_value); - return BaseClass::fetchAndAndRelaxed(_q_value, operand); - } - - template static Q_ALWAYS_INLINE - T fetchAndOrRelaxed(T &_q_value, typename std::enable_if::isIntegral, T>::type operand) noexcept - { - // implement fetchAndOr on top of testAndSet - T tmp = BaseClass::loadRelaxed(_q_value); - for (;;) { - if (BaseClass::testAndSetRelaxed(_q_value, tmp, T(tmp | operand), &tmp)) - return tmp; - } - } - - template static Q_ALWAYS_INLINE - T fetchAndOrAcquire(T &_q_value, typename std::enable_if::isIntegral, T>::type operand) noexcept - { - T tmp = BaseClass::fetchAndOrRelaxed(_q_value, operand); - BaseClass::acquireMemoryFence(_q_value); - return tmp; - } - - template static Q_ALWAYS_INLINE - T fetchAndOrRelease(T &_q_value, typename std::enable_if::isIntegral, T>::type operand) noexcept - { - BaseClass::releaseMemoryFence(_q_value); - return BaseClass::fetchAndOrRelaxed(_q_value, operand); - } - - template static Q_ALWAYS_INLINE - T fetchAndOrOrdered(T &_q_value, typename std::enable_if::isIntegral, T>::type operand) noexcept - { - BaseClass::orderedMemoryFence(_q_value); - return BaseClass::fetchAndOrRelaxed(_q_value, operand); - } - - template static Q_ALWAYS_INLINE - T fetchAndXorRelaxed(T &_q_value, typename std::enable_if::isIntegral, T>::type operand) noexcept - { - // implement fetchAndXor on top of testAndSet - T tmp = BaseClass::loadRelaxed(_q_value); - for (;;) { - if (BaseClass::testAndSetRelaxed(_q_value, tmp, T(tmp ^ operand), &tmp)) - return tmp; - } - } - - template static Q_ALWAYS_INLINE - T fetchAndXorAcquire(T &_q_value, typename std::enable_if::isIntegral, T>::type operand) noexcept - { - T tmp = BaseClass::fetchAndXorRelaxed(_q_value, operand); - BaseClass::acquireMemoryFence(_q_value); - return tmp; - } - - template static Q_ALWAYS_INLINE - T fetchAndXorRelease(T &_q_value, typename std::enable_if::isIntegral, T>::type operand) noexcept - { - BaseClass::releaseMemoryFence(_q_value); - return BaseClass::fetchAndXorRelaxed(_q_value, operand); - } - - template static Q_ALWAYS_INLINE - T fetchAndXorOrdered(T &_q_value, typename std::enable_if::isIntegral, T>::type operand) noexcept - { - BaseClass::orderedMemoryFence(_q_value); - return BaseClass::fetchAndXorRelaxed(_q_value, operand); - } -}; - QT_END_NAMESPACE #endif // QGENERICATOMIC_H diff --git a/src/corelib/thread/qmutex.cpp b/src/corelib/thread/qmutex.cpp index ae1a3a73..b794d79e 100644 --- a/src/corelib/thread/qmutex.cpp +++ b/src/corelib/thread/qmutex.cpp @@ -146,6 +146,23 @@ void QBasicMutex::destroyInternal(QMutexPrivate *d) \sa lock(), unlock() */ +/*! \fn bool QMutex::tryLock(QDeadlineTimer timer) + \since 6.6 + + Attempts to lock the mutex. This function returns \c true if the lock + was obtained; otherwise it returns \c false. If another thread has + locked the mutex, this function will wait until \a timer expires + for the mutex to become available. + + If the lock was obtained, the mutex must be unlocked with unlock() + before another thread can successfully lock it. + + Calling this function multiple times on the same mutex from the + same thread will cause a \e dead-lock. + + \sa lock(), unlock() +*/ + /*! \fn bool QMutex::tryLock() \overload @@ -279,6 +296,8 @@ QRecursiveMutex::~QRecursiveMutex() */ /*! + \fn QRecursiveMutex::tryLock(int timeout) + Attempts to lock the mutex. This function returns \c true if the lock was obtained; otherwise it returns \c false. If another thread has locked the mutex, this function will wait for at most \a timeout @@ -296,7 +315,24 @@ QRecursiveMutex::~QRecursiveMutex() \sa lock(), unlock() */ -bool QRecursiveMutex::tryLock(int timeout) QT_MUTEX_LOCK_NOEXCEPT + +/*! + \since 6.6 + + Attempts to lock the mutex. This function returns \c true if the lock + was obtained; otherwise it returns \c false. If another thread has + locked the mutex, this function will wait until \a timeout expires + for the mutex to become available. + + If the lock was obtained, the mutex must be unlocked with unlock() + before another thread can successfully lock it. + + Calling this function multiple times on the same mutex from the + same thread is allowed. + + \sa lock(), unlock() +*/ +bool QRecursiveMutex::tryLock(QDeadlineTimer timeout) QT_MUTEX_LOCK_NOEXCEPT { unsigned tsanFlags = QtTsan::MutexWriteReentrant | QtTsan::TryLock; QtTsan::mutexPreLock(this, tsanFlags); @@ -309,7 +345,7 @@ bool QRecursiveMutex::tryLock(int timeout) QT_MUTEX_LOCK_NOEXCEPT return true; } bool success = true; - if (timeout == -1) { + if (timeout.isForever()) { mutex.lock(); } else { success = mutex.tryLock(timeout); @@ -622,25 +658,37 @@ void QBasicMutex::lockInternal() QT_MUTEX_LOCK_NOEXCEPT /*! \internal helper for lock(int) */ +#if QT_VERSION < QT_VERSION_CHECK(7, 0, 0) bool QBasicMutex::lockInternal(int timeout) QT_MUTEX_LOCK_NOEXCEPT { if (timeout == 0) return false; + return lockInternal(QDeadlineTimer(timeout)); +} +#endif + +/*! + \internal helper for tryLock(QDeadlineTimer) + */ +bool QBasicMutex::lockInternal(QDeadlineTimer deadlineTimer) QT_MUTEX_LOCK_NOEXCEPT +{ + qint64 remainingTime = deadlineTimer.remainingTimeNSecs(); + if (remainingTime == 0) + return false; + if (futexAvailable()) { - if (Q_UNLIKELY(timeout < 0)) { + if (Q_UNLIKELY(remainingTime < 0)) { // deadlineTimer.isForever() lockInternal(); return true; } - QDeadlineTimer deadlineTimer(timeout); // The mutex is already locked, set a bit indicating we're waiting. // Note we must set to dummyFutexValue because there could be other threads // also waiting. if (d_ptr.fetchAndStoreAcquire(dummyFutexValue()) == nullptr) return true; - qint64 remainingTime = deadlineTimer.remainingTimeNSecs(); Q_FOREVER { if (!futexWait(d_ptr, dummyFutexValue(), remainingTime)) return false; @@ -665,7 +713,7 @@ bool QBasicMutex::lockInternal(int timeout) QT_MUTEX_LOCK_NOEXCEPT continue; if (copy == dummyLocked()) { - if (timeout == 0) + if (remainingTime == 0) return false; // The mutex is locked but does not have a QMutexPrivate yet. // we need to allocate a QMutexPrivate @@ -680,7 +728,7 @@ bool QBasicMutex::lockInternal(int timeout) QT_MUTEX_LOCK_NOEXCEPT } QMutexPrivate *d = static_cast(copy); - if (timeout == 0 && !d->possiblyUnlocked.loadRelaxed()) + if (remainingTime == 0 && !d->possiblyUnlocked.loadRelaxed()) return false; // At this point we have a pointer to a QMutexPrivate. But the other thread @@ -733,7 +781,7 @@ bool QBasicMutex::lockInternal(int timeout) QT_MUTEX_LOCK_NOEXCEPT continue; } - if (d->wait(timeout)) { + if (d->wait(deadlineTimer)) { // reset the possiblyUnlocked flag if needed (and deref its corresponding reference) if (d->possiblyUnlocked.loadRelaxed() && d->possiblyUnlocked.testAndSetRelaxed(true, false)) d->deref(); @@ -742,8 +790,8 @@ bool QBasicMutex::lockInternal(int timeout) QT_MUTEX_LOCK_NOEXCEPT Q_ASSERT(d == d_ptr.loadRelaxed()); return true; } else { - Q_ASSERT(timeout >= 0); - //timeout + Q_ASSERT(remainingTime >= 0); + // timed out d->derefWaiters(1); //There may be a race in which the mutex is unlocked right after we timed out, // and before we deref the waiters, so maybe the mutex is actually unlocked. @@ -867,10 +915,8 @@ QT_END_NAMESPACE #if defined(QT_ALWAYS_USE_FUTEX) // nothing -#elif defined(Q_OS_MAC) +#elif defined(Q_OS_DARWIN) # include "qmutex_mac.cpp" -#elif defined(Q_OS_WIN) -# include "qmutex_win.cpp" #else # include "qmutex_unix.cpp" #endif diff --git a/src/corelib/thread/qmutex.h b/src/corelib/thread/qmutex.h index a4400891..743b8693 100644 --- a/src/corelib/thread/qmutex.h +++ b/src/corelib/thread/qmutex.h @@ -6,11 +6,10 @@ #include #include +#include #include -#include #include -#include QT_BEGIN_NAMESPACE @@ -26,32 +25,6 @@ class QMutex; class QRecursiveMutex; class QMutexPrivate; -namespace QtPrivate -{ - template - static int convertToMilliseconds(std::chrono::duration duration) - { - // N4606 § 30.4.1.3.5 [thread.timedmutex.requirements] specifies that a - // duration less than or equal to duration.zero() shall result in a - // try_lock, unlike QMutex's tryLock with a negative duration which - // results in a lock. - - if (duration <= duration.zero()) - return 0; - - // when converting from 'duration' to milliseconds, make sure that - // the result is not shorter than 'duration': - std::chrono::milliseconds wait = std::chrono::duration_cast(duration); - if (wait < duration) - wait += std::chrono::milliseconds(1); - Q_ASSERT(wait >= duration); - const auto ms = wait.count(); - const auto maxInt = (std::numeric_limits::max)(); - - return ms < maxInt ? int(ms) : maxInt; - } -} - class Q_CORE_EXPORT QBasicMutex { Q_DISABLE_COPY_MOVE(QBasicMutex) @@ -99,7 +72,10 @@ public: bool try_lock() noexcept { return tryLock(); } private: - inline bool fastTryLock() noexcept { + inline bool fastTryLock() noexcept + { + if (d_ptr.loadRelaxed() != nullptr) + return false; return d_ptr.testAndSetAcquire(nullptr, dummyLocked()); } inline bool fastTryUnlock() noexcept { @@ -107,7 +83,10 @@ private: } void lockInternal() QT_MUTEX_LOCK_NOEXCEPT; + bool lockInternal(QDeadlineTimer timeout) QT_MUTEX_LOCK_NOEXCEPT; +#if QT_VERSION < QT_VERSION_CHECK(7, 0, 0) bool lockInternal(int timeout) QT_MUTEX_LOCK_NOEXCEPT; +#endif void unlockInternal() noexcept; void destroyInternal(QMutexPrivate *d); @@ -143,6 +122,11 @@ public: using QBasicMutex::tryLock; bool tryLock(int timeout) QT_MUTEX_LOCK_NOEXCEPT + { + return tryLock(QDeadlineTimer(timeout)); + } + + bool tryLock(QDeadlineTimer timeout) QT_MUTEX_LOCK_NOEXCEPT { unsigned tsanFlags = QtTsan::TryLock; QtTsan::mutexPreLock(this, tsanFlags); @@ -167,17 +151,14 @@ public: template bool try_lock_for(std::chrono::duration duration) { - return tryLock(QtPrivate::convertToMilliseconds(duration)); + return tryLock(QDeadlineTimer(duration)); } // TimedLockable concept template bool try_lock_until(std::chrono::time_point timePoint) { - // Implemented in terms of try_lock_for to honor the similar - // requirement in N4606 § 30.4.1.3 [thread.timedmutex.requirements]/12. - - return try_lock_for(timePoint - Clock::now()); + return tryLock(QDeadlineTimer(timePoint)); } }; @@ -198,8 +179,10 @@ public: // BasicLockable concept void lock() QT_MUTEX_LOCK_NOEXCEPT - { tryLock(-1); } - bool tryLock(int timeout = 0) QT_MUTEX_LOCK_NOEXCEPT; + { tryLock(QDeadlineTimer(QDeadlineTimer::Forever)); } + QT_CORE_INLINE_SINCE(6, 6) + bool tryLock(int timeout) QT_MUTEX_LOCK_NOEXCEPT; + bool tryLock(QDeadlineTimer timer = {}) QT_MUTEX_LOCK_NOEXCEPT; // BasicLockable concept void unlock() noexcept; @@ -210,24 +193,29 @@ public: template bool try_lock_for(std::chrono::duration duration) { - return tryLock(QtPrivate::convertToMilliseconds(duration)); + return tryLock(QDeadlineTimer(duration)); } // TimedLockable concept template bool try_lock_until(std::chrono::time_point timePoint) { - // Implemented in terms of try_lock_for to honor the similar - // requirement in N4606 § 30.4.1.3 [thread.timedmutex.requirements]/12. - - return try_lock_for(timePoint - Clock::now()); + return tryLock(QDeadlineTimer(timePoint)); } }; +#if QT_CORE_INLINE_IMPL_SINCE(6, 6) +bool QRecursiveMutex::tryLock(int timeout) QT_MUTEX_LOCK_NOEXCEPT +{ + return tryLock(QDeadlineTimer(timeout)); +} +#endif + template -class [[nodiscard]] QMutexLocker +class QMutexLocker { public: + Q_NODISCARD_CTOR inline explicit QMutexLocker(Mutex *mutex) QT_MUTEX_LOCK_NOEXCEPT { m_mutex = mutex; @@ -237,6 +225,7 @@ public: } } + Q_NODISCARD_CTOR inline QMutexLocker(QMutexLocker &&other) noexcept : m_mutex(std::exchange(other.m_mutex, nullptr)), m_isLocked(std::exchange(other.m_isLocked, false)) @@ -320,9 +309,10 @@ private: class QRecursiveMutex : public QMutex {}; template -class [[nodiscard]] QMutexLocker +class QMutexLocker { public: + Q_NODISCARD_CTOR inline explicit QMutexLocker(Mutex *) noexcept {} inline ~QMutexLocker() noexcept {} diff --git a/src/corelib/thread/qmutex_mac.cpp b/src/corelib/thread/qmutex_mac.cpp index a9e2330b..7849133e 100644 --- a/src/corelib/thread/qmutex_mac.cpp +++ b/src/corelib/thread/qmutex_mac.cpp @@ -5,6 +5,8 @@ #include "qmutex.h" #include "qmutex_p.h" +#include "private/qcore_unix_p.h" + #include #include @@ -26,18 +28,19 @@ QMutexPrivate::~QMutexPrivate() qWarning("QMutex: failed to destroy semaphore, error %d", r); } -bool QMutexPrivate::wait(int timeout) +bool QMutexPrivate::wait(QDeadlineTimer timeout) { kern_return_t r; - if (timeout < 0) { + if (timeout.isForever()) { do { r = semaphore_wait(mach_semaphore); } while (r == KERN_ABORTED); Q_ASSERT(r == KERN_SUCCESS); } else { + timespec tv = durationToTimespec(timeout.remainingTimeAsDuration()); mach_timespec_t ts; - ts.tv_nsec = ((timeout % 1000) * 1000) * 1000; - ts.tv_sec = (timeout / 1000); + ts.tv_nsec = tv.tv_nsec; + ts.tv_sec = tv.tv_sec; r = semaphore_timedwait(mach_semaphore, ts); } return (r == KERN_SUCCESS); diff --git a/src/corelib/thread/qmutex_p.h b/src/corelib/thread/qmutex_p.h index 21f3fb0d..aabb66fa 100644 --- a/src/corelib/thread/qmutex_p.h +++ b/src/corelib/thread/qmutex_p.h @@ -23,13 +23,12 @@ #include #include -#if defined(Q_OS_MAC) +#include "qplatformdefs.h" // _POSIX_VERSION + +#if defined(Q_OS_DARWIN) # include #elif defined(Q_OS_UNIX) -# if _POSIX_VERSION-0 >= 200112L || _XOPEN_VERSION-0 >= 600 # include -# define QT_UNIX_SEMAPHORE -# endif #endif struct timespec; @@ -44,7 +43,7 @@ public: ~QMutexPrivate(); QMutexPrivate(); - bool wait(int timeout = -1); + bool wait(QDeadlineTimer timeout = QDeadlineTimer::Forever); void wakeUp() noexcept; // Control the lifetime of the privates @@ -83,27 +82,13 @@ public: void derefWaiters(int value) noexcept; //platform specific stuff -#if defined(Q_OS_MAC) +#if defined(Q_OS_DARWIN) semaphore_t mach_semaphore; -#elif defined(QT_UNIX_SEMAPHORE) - sem_t semaphore; #elif defined(Q_OS_UNIX) - bool wakeup; - pthread_mutex_t mutex; - pthread_cond_t cond; -#elif defined(Q_OS_WIN) - Qt::HANDLE event; + sem_t semaphore; #endif }; - -#ifdef Q_OS_UNIX -// helper functions for qmutex_unix.cpp and qwaitcondition_unix.cpp -// they are in qwaitcondition_unix.cpp actually -void qt_initialize_pthread_cond(pthread_cond_t *cond, const char *where); -void qt_abstime_for_timeout(struct timespec *ts, QDeadlineTimer deadline); -#endif - QT_END_NAMESPACE #endif // QMUTEX_P_H diff --git a/src/corelib/thread/qmutex_unix.cpp b/src/corelib/thread/qmutex_unix.cpp index f665f192..79c479a3 100644 --- a/src/corelib/thread/qmutex_unix.cpp +++ b/src/corelib/thread/qmutex_unix.cpp @@ -19,105 +19,48 @@ QT_BEGIN_NAMESPACE -static void report_error(int code, const char *where, const char *what) +static void qt_report_error(int code, const char *where, const char *what) { if (code != 0) qErrnoWarning(code, "%s: %s failure", where, what); } -#ifdef QT_UNIX_SEMAPHORE - QMutexPrivate::QMutexPrivate() { - report_error(sem_init(&semaphore, 0, 0), "QMutex", "sem_init"); + qt_report_error(sem_init(&semaphore, 0, 0), "QMutex", "sem_init"); } QMutexPrivate::~QMutexPrivate() { - report_error(sem_destroy(&semaphore), "QMutex", "sem_destroy"); + qt_report_error(sem_destroy(&semaphore), "QMutex", "sem_destroy"); } -bool QMutexPrivate::wait(int timeout) +bool QMutexPrivate::wait(QDeadlineTimer timeout) { int errorCode; - if (timeout < 0) { + if (timeout.isForever()) { do { errorCode = sem_wait(&semaphore); } while (errorCode && errno == EINTR); - report_error(errorCode, "QMutex::lock()", "sem_wait"); + qt_report_error(errorCode, "QMutex::lock()", "sem_wait"); } else { - timespec ts; - report_error(clock_gettime(CLOCK_REALTIME, &ts), "QMutex::lock()", "clock_gettime"); - ts.tv_sec += timeout / 1000; - ts.tv_nsec += timeout % 1000 * Q_UINT64_C(1000) * 1000; - normalizedTimespec(ts); do { + auto tp = timeout.deadline(); + timespec ts = durationToTimespec(tp.time_since_epoch()); errorCode = sem_timedwait(&semaphore, &ts); } while (errorCode && errno == EINTR); if (errorCode && errno == ETIMEDOUT) return false; - report_error(errorCode, "QMutex::lock()", "sem_timedwait"); + qt_report_error(errorCode, "QMutex::lock()", "sem_timedwait"); } return true; } void QMutexPrivate::wakeUp() noexcept { - report_error(sem_post(&semaphore), "QMutex::unlock", "sem_post"); + qt_report_error(sem_post(&semaphore), "QMutex::unlock", "sem_post"); } -#else // QT_UNIX_SEMAPHORE - -QMutexPrivate::QMutexPrivate() - : wakeup(false) -{ - report_error(pthread_mutex_init(&mutex, NULL), "QMutex", "mutex init"); - qt_initialize_pthread_cond(&cond, "QMutex"); -} - -QMutexPrivate::~QMutexPrivate() -{ - report_error(pthread_cond_destroy(&cond), "QMutex", "cv destroy"); - report_error(pthread_mutex_destroy(&mutex), "QMutex", "mutex destroy"); -} - -bool QMutexPrivate::wait(int timeout) -{ - report_error(pthread_mutex_lock(&mutex), "QMutex::lock", "mutex lock"); - int errorCode = 0; - while (!wakeup) { - if (timeout < 0) { - errorCode = pthread_cond_wait(&cond, &mutex); - } else { - timespec ti; - qt_abstime_for_timeout(&ti, QDeadlineTimer(timeout)); - errorCode = pthread_cond_timedwait(&cond, &mutex, &ti); - } - if (errorCode) { - if (errorCode == ETIMEDOUT) { - if (wakeup) - errorCode = 0; - break; - } - report_error(errorCode, "QMutex::lock()", "cv wait"); - } - } - bool ret = wakeup; - wakeup = false; - report_error(pthread_mutex_unlock(&mutex), "QMutex::lock", "mutex unlock"); - return ret; -} - -void QMutexPrivate::wakeUp() noexcept -{ - report_error(pthread_mutex_lock(&mutex), "QMutex::unlock", "mutex lock"); - wakeup = true; - report_error(pthread_cond_signal(&cond), "QMutex::unlock", "cv signal"); - report_error(pthread_mutex_unlock(&mutex), "QMutex::unlock", "mutex unlock"); -} - -#endif - QT_END_NAMESPACE diff --git a/src/corelib/thread/qmutex_win.cpp b/src/corelib/thread/qmutex_win.cpp deleted file mode 100644 index 87d0289f..00000000 --- a/src/corelib/thread/qmutex_win.cpp +++ /dev/null @@ -1,30 +0,0 @@ -// 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 "qmutex.h" -#include -#include "qmutex_p.h" -#include - -QT_BEGIN_NAMESPACE - -QMutexPrivate::QMutexPrivate() -{ - event = CreateEvent(0, FALSE, FALSE, 0); - - if (!event) - qWarning("QMutexPrivate::QMutexPrivate: Cannot create event"); -} - -QMutexPrivate::~QMutexPrivate() -{ CloseHandle(event); } - -bool QMutexPrivate::wait(int timeout) -{ - return (WaitForSingleObjectEx(event, timeout < 0 ? INFINITE : timeout, FALSE) == WAIT_OBJECT_0); -} - -void QMutexPrivate::wakeUp() noexcept -{ SetEvent(event); } - -QT_END_NAMESPACE \ No newline at end of file diff --git a/src/corelib/thread/qorderedmutexlocker_p.h b/src/corelib/thread/qorderedmutexlocker_p.h index 68c33e49..fb2e223e 100644 --- a/src/corelib/thread/qorderedmutexlocker_p.h +++ b/src/corelib/thread/qorderedmutexlocker_p.h @@ -31,6 +31,7 @@ QT_BEGIN_NAMESPACE class QOrderedMutexLocker { public: + Q_NODISCARD_CTOR QOrderedMutexLocker(QBasicMutex *m1, QBasicMutex *m2) : mtx1((m1 == m2) ? m1 : (std::less()(m1, m2) ? m1 : m2)), mtx2((m1 == m2) ? nullptr : (std::less()(m1, m2) ? m2 : m1)), @@ -50,6 +51,7 @@ public: QT_MOVE_ASSIGNMENT_OPERATOR_IMPL_VIA_MOVE_AND_SWAP(QOrderedMutexLocker) + Q_NODISCARD_CTOR QOrderedMutexLocker(QOrderedMutexLocker &&other) noexcept : mtx1(std::exchange(other.mtx1, nullptr)) , mtx2(std::exchange(other.mtx2, nullptr)) @@ -122,7 +124,9 @@ class QOrderedMutexLocker { public: Q_DISABLE_COPY(QOrderedMutexLocker) + Q_NODISCARD_CTOR QOrderedMutexLocker(QBasicMutex *, QBasicMutex *) {} + Q_NODISCARD_CTOR QOrderedMutexLocker(QOrderedMutexLocker &&) = default; QOrderedMutexLocker& operator=(QOrderedMutexLocker &&other) = default; ~QOrderedMutexLocker() {} diff --git a/src/corelib/thread/qpromise.h b/src/corelib/thread/qpromise.h index 5620811b..c2b6c119 100644 --- a/src/corelib/thread/qpromise.h +++ b/src/corelib/thread/qpromise.h @@ -46,11 +46,23 @@ public: // Core QPromise APIs QFuture future() const { return d.future(); } - template> + template, bool> = true> + bool emplaceResultAt(int index, Args&&...args) + { + return d.reportAndEmplaceResult(index, std::forward(args)...); + } + template, bool> = true> + bool emplaceResult(Args&&...args) + { + return d.reportAndEmplaceResult(-1, std::forward(args)...); + } + template> bool addResult(U &&result, int index = -1) { - return d.reportResult(std::forward(result), index); + return d.reportAndEmplaceResult(index, std::forward(result)); } + bool addResults(const QList &result) + { return d.reportResults(result); } #ifndef QT_NO_EXCEPTIONS void setException(const QException &e) { d.reportException(e); } #if QT_VERSION < QT_VERSION_CHECK(7, 0, 0) diff --git a/src/corelib/thread/qpromise.qdoc b/src/corelib/thread/qpromise.qdoc index a3af927e..aaef6121 100644 --- a/src/corelib/thread/qpromise.qdoc +++ b/src/corelib/thread/qpromise.qdoc @@ -91,15 +91,38 @@ /*! \fn template bool QPromise::addResult(const T &result, int index = -1) \fn template bool QPromise::addResult(T &&result, int index = -1) - Adds \a result to the internal result collection at \a index position. If - index is unspecified, \a result is added to the end of the collection. + Same as + \code + emplaceResultAt(index, result); // first overload + emplaceResultAt(index, std::move(result)); // second overload + \endcode + or, if \c{index == -1} (the default) + \code + emplaceResult(result); // first overload + emplaceResult(std::move(result)); // second overload + \endcode - Returns \c true when \a result is added to the collection. + \sa emplaceResultAt(), emplaceResult(), addResults() +*/ + +/*! + \fn template template , bool> = true> QPromise::emplaceResultAt(int index, Args&&...args) + \fn template template , bool> = true> QPromise::emplaceResult(Args&&...args) + \since 6.6 + + Adds a result constructed from \a args... to the internal result collection + at \a index position (emplaceResultAt()) or the end of of the collection + (emplaceResult()). + + Returns \c true when the result was added to the collection. Returns \c false when this promise is in canceled or finished state or when - \a result is rejected. addResult() rejects \a result if there's already + the result was rejected. addResult() rejects to add a result if there's already another result in the collection stored at the same index. + These functions only participate in overload resolutions if \c T is + constructible from \a args.... + You can get a result at a specific index by calling QFuture::resultAt(). \note It is possible to specify an arbitrary index and request result at @@ -107,6 +130,29 @@ For instance, iterative approaches that use QFuture::resultCount() or QFuture::const_iterator. In order to get all available results without thinking if there are index gaps or not, use QFuture::results(). + + \sa addResult(), addResults() +*/ + +/*! + \fn template bool QPromise::addResults(const QList &results) + \since 6.6 + + Adds \a results at the end of the internal result collection. + + Returns \c true when \a results are added to the collection. + + Returns \c false when this promise is in canceled or finished state. + + This is more efficient than looping over addResult(), because associated + futures will be notified only once per addResults() call, instead of once + per element contained in \a results, as would be the case with individual + addResult() calls. But if the calculation of each element takes time, then + the code on the receiving end (future) cannot make progress until all + results are reported, so use this function only if the calculation of + consecutive elements is relatively fast. + + \sa addResult() */ /*! \fn template void QPromise::setException(const QException &e) diff --git a/src/corelib/thread/qreadwritelock.cpp b/src/corelib/thread/qreadwritelock.cpp index 12a9b373..3a9fb9d4 100644 --- a/src/corelib/thread/qreadwritelock.cpp +++ b/src/corelib/thread/qreadwritelock.cpp @@ -31,7 +31,7 @@ QT_BEGIN_NAMESPACE using namespace QReadWriteLockStates; namespace { -using ms = std::chrono::milliseconds; +using steady_clock = std::chrono::steady_clock; const auto dummyLockedForRead = reinterpret_cast(quintptr(StateLockedForRead)); const auto dummyLockedForWrite = reinterpret_cast(quintptr(StateLockedForWrite)); @@ -40,9 +40,9 @@ inline bool isUncontendedLocked(const QReadWriteLockPrivate *d) } static bool contendedTryLockForRead(QAtomicPointer &d_ptr, - int timeout, QReadWriteLockPrivate *d); + QDeadlineTimer timeout, QReadWriteLockPrivate *d); static bool contendedTryLockForWrite(QAtomicPointer &d_ptr, - int timeout, QReadWriteLockPrivate *d); + QDeadlineTimer timeout, QReadWriteLockPrivate *d); /*! \class QReadWriteLock \inmodule QtCore @@ -101,6 +101,7 @@ static bool contendedTryLockForWrite(QAtomicPointer &d_pt */ /*! + \fn QReadWriteLock::QReadWriteLock(RecursionMode recursionMode) \since 4.4 Constructs a QReadWriteLock object in the given \a recursionMode. @@ -109,21 +110,22 @@ static bool contendedTryLockForWrite(QAtomicPointer &d_pt \sa lockForRead(), lockForWrite(), RecursionMode */ -QReadWriteLock::QReadWriteLock(RecursionMode recursionMode) - : d_ptr(recursionMode == Recursive ? new QReadWriteLockPrivate(true) : nullptr) +QReadWriteLockPrivate *QReadWriteLock::initRecursive() { - Q_ASSERT_X(!(quintptr(d_ptr.loadRelaxed()) & StateMask), "QReadWriteLock::QReadWriteLock", "bad d_ptr alignment"); + auto d = new QReadWriteLockPrivate(true); + Q_ASSERT_X(!(quintptr(d) & StateMask), "QReadWriteLock::QReadWriteLock", "bad d_ptr alignment"); + return d; } /*! + \fn QReadWriteLock::~QReadWriteLock() Destroys the QReadWriteLock object. \warning Destroying a read-write lock that is in use may result in undefined behavior. */ -QReadWriteLock::~QReadWriteLock() +void QReadWriteLock::destroyRecursive(QReadWriteLockPrivate *d) { - auto d = d_ptr.loadAcquire(); if (isUncontendedLocked(d)) { qWarning("QReadWriteLock: destroying locked QReadWriteLock"); return; @@ -132,6 +134,7 @@ QReadWriteLock::~QReadWriteLock() } /*! + \fn QReadWriteLock::lockForRead() Locks the lock for reading. This function will block the current thread if another thread has locked for writing. @@ -140,33 +143,9 @@ QReadWriteLock::~QReadWriteLock() \sa unlock(), lockForWrite(), tryLockForRead() */ -void QReadWriteLock::lockForRead() -{ - tryLockForRead(-1); -} /*! - Attempts to lock for reading. If the lock was obtained, this - function returns \c true, otherwise it returns \c false instead of - waiting for the lock to become available, i.e. it does not block. - - The lock attempt will fail if another thread has locked for - writing. - - If the lock was obtained, the lock must be unlocked with unlock() - before another thread can successfully lock it for writing. - - It is not possible to lock for read if the thread already has - locked for write. - - \sa unlock(), lockForRead() -*/ -bool QReadWriteLock::tryLockForRead() -{ - return tryLockForRead(0); -} - -/*! \overload + \fn bool QReadWriteLock::tryLockForRead(int timeout) Attempts to lock for reading. This function returns \c true if the lock was obtained; otherwise it returns \c false. If another thread @@ -185,17 +164,35 @@ bool QReadWriteLock::tryLockForRead() \sa unlock(), lockForRead() */ -bool QReadWriteLock::tryLockForRead(int timeout) + +/*! + \overload + \since 6.6 + + Attempts to lock for reading. This function returns \c true if the lock was + obtained; otherwise it returns \c false. If another thread has locked for + writing, this function will wait until \a timeout expires for the lock to + become available. + + If the lock was obtained, the lock must be unlocked with unlock() + before another thread can successfully lock it for writing. + + It is not possible to lock for read if the thread already has + locked for write. + + \sa unlock(), lockForRead() +*/ +bool QReadWriteLock::tryLockForRead(QDeadlineTimer timeout) { // Fast case: non contended: - QReadWriteLockPrivate *d; - if (d_ptr.testAndSetAcquire(nullptr, dummyLockedForRead, d)) + QReadWriteLockPrivate *d = d_ptr.loadRelaxed(); + if (d == nullptr && d_ptr.testAndSetAcquire(nullptr, dummyLockedForRead, d)) return true; return contendedTryLockForRead(d_ptr, timeout, d); } Q_NEVER_INLINE static bool contendedTryLockForRead(QAtomicPointer &d_ptr, - int timeout, QReadWriteLockPrivate *d) + QDeadlineTimer timeout, QReadWriteLockPrivate *d) { while (true) { if (d == nullptr) { @@ -215,7 +212,7 @@ Q_NEVER_INLINE static bool contendedTryLockForRead(QAtomicPointer &d_ptr, - int timeout, QReadWriteLockPrivate *d) + QDeadlineTimer timeout, QReadWriteLockPrivate *d) { while (true) { if (d == nullptr) { @@ -324,7 +317,7 @@ Q_NEVER_INLINE static bool contendedTryLockForWrite(QAtomicPointer &lock, int timeout) +bool QReadWriteLockPrivate::lockForRead(std::unique_lock &lock, QDeadlineTimer timeout) { Q_ASSERT(!mutex.try_lock()); // mutex must be locked when entering this function - QElapsedTimer t; - if (timeout > 0) - t.start(); - while (waitingWriters || writerCount) { - if (timeout == 0) + if (timeout.hasExpired()) return false; - if (timeout > 0) { - auto elapsed = t.elapsed(); - if (elapsed > timeout) - return false; + if (!timeout.isForever()) { waitingReaders++; - readerCond.wait_for(lock, ms{timeout - elapsed}); + readerCond.wait_until(lock, timeout.deadline()); } else { waitingReaders++; readerCond.wait(lock); @@ -446,29 +432,22 @@ bool QReadWriteLockPrivate::lockForRead(std::unique_lock &lock return true; } -bool QReadWriteLockPrivate::lockForWrite(std::unique_lock &lock, int timeout) +bool QReadWriteLockPrivate::lockForWrite(std::unique_lock &lock, QDeadlineTimer timeout) { Q_ASSERT(!mutex.try_lock()); // mutex must be locked when entering this function - QElapsedTimer t; - if (timeout > 0) - t.start(); - while (readerCount || writerCount) { - if (timeout == 0) - return false; - if (timeout > 0) { - auto elapsed = t.elapsed(); - if (elapsed > timeout) { - if (waitingReaders && !waitingWriters && !writerCount) { - // We timed out and now there is no more writers or waiting writers, but some - // readers were queued (probably because of us). Wake the waiting readers. - readerCond.notify_all(); - } - return false; + if (timeout.hasExpired()) { + if (waitingReaders && !waitingWriters && !writerCount) { + // We timed out and now there is no more writers or waiting writers, but some + // readers were queued (probably because of us). Wake the waiting readers. + readerCond.notify_all(); } + return false; + } + if (!timeout.isForever()) { waitingWriters++; - writerCond.wait_for(lock, ms{timeout - elapsed}); + writerCond.wait_until(lock, timeout.deadline()); } else { waitingWriters++; writerCond.wait(lock); @@ -496,7 +475,7 @@ static auto handleEquals(Qt::HANDLE handle) return [handle](QReadWriteLockPrivate::Reader reader) { return reader.handle == handle; }; } -bool QReadWriteLockPrivate::recursiveLockForRead(int timeout) +bool QReadWriteLockPrivate::recursiveLockForRead(QDeadlineTimer timeout) { Q_ASSERT(recursive); auto lock = qt_unique_lock(mutex); @@ -518,7 +497,7 @@ bool QReadWriteLockPrivate::recursiveLockForRead(int timeout) return true; } -bool QReadWriteLockPrivate::recursiveLockForWrite(int timeout) +bool QReadWriteLockPrivate::recursiveLockForWrite(QDeadlineTimer timeout) { Q_ASSERT(recursive); auto lock = qt_unique_lock(mutex); diff --git a/src/corelib/thread/qreadwritelock.h b/src/corelib/thread/qreadwritelock.h index 60078b8f..6ca9be44 100644 --- a/src/corelib/thread/qreadwritelock.h +++ b/src/corelib/thread/qreadwritelock.h @@ -5,10 +5,10 @@ #define QREADWRITELOCK_H #include +#include QT_BEGIN_NAMESPACE - #if QT_CONFIG(thread) class QReadWriteLockPrivate; @@ -18,16 +18,28 @@ class Q_CORE_EXPORT QReadWriteLock public: enum RecursionMode { NonRecursive, Recursive }; + QT_CORE_INLINE_SINCE(6, 6) explicit QReadWriteLock(RecursionMode recursionMode = NonRecursive); + QT_CORE_INLINE_SINCE(6, 6) ~QReadWriteLock(); + QT_CORE_INLINE_SINCE(6, 6) void lockForRead(); +#if QT_CORE_REMOVED_SINCE(6, 6) bool tryLockForRead(); +#endif + QT_CORE_INLINE_SINCE(6, 6) bool tryLockForRead(int timeout); + bool tryLockForRead(QDeadlineTimer timeout = {}); + QT_CORE_INLINE_SINCE(6, 6) void lockForWrite(); +#if QT_CORE_REMOVED_SINCE(6, 6) bool tryLockForWrite(); +#endif + QT_CORE_INLINE_SINCE(6, 6) bool tryLockForWrite(int timeout); + bool tryLockForWrite(QDeadlineTimer timeout = {}); void unlock(); @@ -35,16 +47,52 @@ private: Q_DISABLE_COPY(QReadWriteLock) QAtomicPointer d_ptr; friend class QReadWriteLockPrivate; + static QReadWriteLockPrivate *initRecursive(); + static void destroyRecursive(QReadWriteLockPrivate *); }; +#if QT_CORE_INLINE_IMPL_SINCE(6, 6) +QReadWriteLock::QReadWriteLock(RecursionMode recursionMode) + : d_ptr(recursionMode == Recursive ? initRecursive() : nullptr) +{ +} + +QReadWriteLock::~QReadWriteLock() +{ + if (auto d = d_ptr.loadAcquire()) + destroyRecursive(d); +} + +void QReadWriteLock::lockForRead() +{ + tryLockForRead(QDeadlineTimer(QDeadlineTimer::Forever)); +} + +bool QReadWriteLock::tryLockForRead(int timeout) +{ + return tryLockForRead(QDeadlineTimer(timeout)); +} + +void QReadWriteLock::lockForWrite() +{ + tryLockForWrite(QDeadlineTimer(QDeadlineTimer::Forever)); +} + +bool QReadWriteLock::tryLockForWrite(int timeout) +{ + return tryLockForWrite(QDeadlineTimer(timeout)); +} +#endif // inline since 6.6 + #if defined(Q_CC_MSVC) #pragma warning( push ) #pragma warning( disable : 4312 ) // ignoring the warning from /Wp64 #endif -class Q_CORE_EXPORT QReadLocker +class QT6_ONLY(Q_CORE_EXPORT) QReadLocker { public: + Q_NODISCARD_CTOR inline QReadLocker(QReadWriteLock *readWriteLock); inline ~QReadLocker() @@ -86,9 +134,10 @@ inline QReadLocker::QReadLocker(QReadWriteLock *areadWriteLock) relock(); } -class Q_CORE_EXPORT QWriteLocker +class QT6_ONLY(Q_CORE_EXPORT) QWriteLocker { public: + Q_NODISCARD_CTOR inline QWriteLocker(QReadWriteLock *readWriteLock); inline ~QWriteLocker() @@ -137,7 +186,7 @@ inline QWriteLocker::QWriteLocker(QReadWriteLock *areadWriteLock) #else // QT_CONFIG(thread) -class Q_CORE_EXPORT QReadWriteLock +class QT6_ONLY(Q_CORE_EXPORT) QReadWriteLock { public: enum RecursionMode { NonRecursive, Recursive }; @@ -146,10 +195,12 @@ public: void lockForRead() noexcept { } bool tryLockForRead() noexcept { return true; } + bool tryLockForRead(QDeadlineTimer) noexcept { return true; } bool tryLockForRead(int timeout) noexcept { Q_UNUSED(timeout); return true; } void lockForWrite() noexcept { } bool tryLockForWrite() noexcept { return true; } + bool tryLockForWrite(QDeadlineTimer) noexcept { return true; } bool tryLockForWrite(int timeout) noexcept { Q_UNUSED(timeout); return true; } void unlock() noexcept { } @@ -158,9 +209,10 @@ private: Q_DISABLE_COPY(QReadWriteLock) }; -class Q_CORE_EXPORT QReadLocker +class QT6_ONLY(Q_CORE_EXPORT) QReadLocker { public: + Q_NODISCARD_CTOR inline explicit QReadLocker(QReadWriteLock *) noexcept { } inline ~QReadLocker() noexcept { } @@ -172,9 +224,10 @@ private: Q_DISABLE_COPY(QReadLocker) }; -class Q_CORE_EXPORT QWriteLocker +class QT6_ONLY(Q_CORE_EXPORT) QWriteLocker { public: + Q_NODISCARD_CTOR inline explicit QWriteLocker(QReadWriteLock *) noexcept { } inline ~QWriteLocker() noexcept { } diff --git a/src/corelib/thread/qreadwritelock_p.h b/src/corelib/thread/qreadwritelock_p.h index e8379fcb..d1f887eb 100644 --- a/src/corelib/thread/qreadwritelock_p.h +++ b/src/corelib/thread/qreadwritelock_p.h @@ -55,8 +55,8 @@ public: const bool recursive; //Called with the mutex locked - bool lockForWrite(std::unique_lock &lock, int timeout); - bool lockForRead(std::unique_lock &lock, int timeout); + bool lockForWrite(std::unique_lock &lock, QDeadlineTimer timeout); + bool lockForRead(std::unique_lock &lock, QDeadlineTimer timeout); void unlock(); //memory management @@ -75,8 +75,8 @@ public: QVarLengthArray currentReaders; // called with the mutex unlocked - bool recursiveLockForWrite(int timeout); - bool recursiveLockForRead(int timeout); + bool recursiveLockForWrite(QDeadlineTimer timeout); + bool recursiveLockForRead(QDeadlineTimer timeout); void recursiveUnlock(); static QReadWriteLockStates::StateForWaitCondition diff --git a/src/corelib/thread/qresultstore.h b/src/corelib/thread/qresultstore.h index d19fc195..30ce1fe9 100644 --- a/src/corelib/thread/qresultstore.h +++ b/src/corelib/thread/qresultstore.h @@ -134,6 +134,14 @@ protected: } public: + template + int emplaceResult(int index, Args&&...args) + { + if (containsValidResultItem(index)) // reject if already present + return -1; + return addResult(index, static_cast(new T(std::forward(args)...))); + } + template int addResult(int index, const T *result) { @@ -151,10 +159,7 @@ public: { static_assert(!std::is_reference_v, "trying to move from an lvalue!"); - if (containsValidResultItem(index)) // reject if already present - return -1; - - return addResult(index, static_cast(new T(std::move_if_noexcept(result)))); + return emplaceResult>(index, std::forward(result)); } template diff --git a/src/corelib/thread/qrunnable.cpp b/src/corelib/thread/qrunnable.cpp index 5bb354cb..bcb19d7a 100644 --- a/src/corelib/thread/qrunnable.cpp +++ b/src/corelib/thread/qrunnable.cpp @@ -3,12 +3,35 @@ #include "qrunnable.h" +#include + QT_BEGIN_NAMESPACE QRunnable::~QRunnable() { } +/*! + \internal + Prints a warning and returns nullptr. +*/ +QRunnable *QRunnable::warnNullCallable() +{ + qWarning("Trying to create null QRunnable. This may stop working."); + return nullptr; +} + + +QRunnable::QGenericRunnable::~QGenericRunnable() +{ + runHelper->destroy(); +} + +void QRunnable::QGenericRunnable::run() +{ + runHelper->run(); +} + /*! \class QRunnable \inmodule QtCore @@ -77,31 +100,20 @@ QRunnable::~QRunnable() \sa autoDelete(), QThreadPool */ -class FunctionRunnable : public QRunnable -{ - std::function m_functionToRun; -public: - FunctionRunnable(std::function functionToRun) : m_functionToRun(std::move(functionToRun)) - { - } - void run() override - { - m_functionToRun(); - } -}; - /*! + \fn template> QRunnable *QRunnable::create(Callable &&callableToRun); \since 5.15 - Creates a QRunnable that calls \a functionToRun in run(). + Creates a QRunnable that calls \a callableToRun in run(). Auto-deletion is enabled by default. + \note This function participates in overload resolution only if \c Callable + is a function or function object which can be called with zero arguments. + + \note In Qt versions prior to 6.6, this method took copyable functions only. + \sa run(), autoDelete() */ -QRunnable *QRunnable::create(std::function functionToRun) -{ - return new FunctionRunnable(std::move(functionToRun)); -} QT_END_NAMESPACE diff --git a/src/corelib/thread/qrunnable.h b/src/corelib/thread/qrunnable.h index c77f8eea..f0dd0a58 100644 --- a/src/corelib/thread/qrunnable.h +++ b/src/corelib/thread/qrunnable.h @@ -1,11 +1,16 @@ -// Copyright (C) 2016 The Qt Company Ltd. +// 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 QRUNNABLE_H #define QRUNNABLE_H -#include +#include +#include +#include +#include + #include +#include QT_BEGIN_NAMESPACE @@ -19,12 +24,114 @@ public: constexpr QRunnable() noexcept = default; virtual ~QRunnable(); +#if QT_CORE_REMOVED_SINCE(6, 6) static QRunnable *create(std::function functionToRun); +#endif + template + using if_callable = std::enable_if_t, bool>; + + template = true> + static QRunnable *create(Callable &&functionToRun); + static QRunnable *create(std::nullptr_t) = delete; bool autoDelete() const { return m_autoDelete; } void setAutoDelete(bool autoDelete) { m_autoDelete = autoDelete; } + +private: + static Q_DECL_COLD_FUNCTION QRunnable *warnNullCallable(); + class QGenericRunnable; }; +class Q_CORE_EXPORT QRunnable::QGenericRunnable : public QRunnable +{ + // Type erasure, to only instantiate a non-virtual class per Callable: + class HelperBase + { + protected: + enum class Op { + Run, + Destroy, + }; + using OpFn = void* (*)(Op, HelperBase *, void*); + OpFn fn; + protected: + constexpr explicit HelperBase(OpFn f) noexcept : fn(f) {} + ~HelperBase() = default; + public: + void run() { fn(Op::Run, this, nullptr); } + void destroy() { fn(Op::Destroy, this, nullptr); } + }; + + template + class Helper : public HelperBase, private QtPrivate::CompactStorage + { + using Storage = QtPrivate::CompactStorage; + static void *impl(Op op, HelperBase *that, [[maybe_unused]] void *arg) + { + const auto _this = static_cast(that); + switch (op) { + case Op::Run: _this->object()(); break; + case Op::Destroy: delete _this; break; + } + return nullptr; + } + public: + template + explicit Helper(UniCallable &&functionToRun) noexcept + : HelperBase(&impl), + Storage{std::forward(functionToRun)} + { + } + }; + + HelperBase *runHelper; +public: + template = true> + explicit QGenericRunnable(Callable &&c) + : runHelper(new Helper>(std::forward(c))) + { + } + ~QGenericRunnable() override; + + void run() override; +}; + +namespace QtPrivate { + +template +constexpr inline bool is_function_pointer_v = std::conjunction_v< + std::is_pointer, + std::is_function> + >; +template +constexpr inline bool is_std_function_v = false; +template +constexpr inline bool is_std_function_v> = true; + +} // namespace QtPrivate + +template > +QRunnable *QRunnable::create(Callable &&functionToRun) +{ + using F = std::decay_t; + constexpr bool is_std_function = QtPrivate::is_std_function_v; + constexpr bool is_function_pointer = QtPrivate::is_function_pointer_v; + if constexpr (is_std_function || is_function_pointer) { + bool is_null; + if constexpr (is_std_function) { + is_null = !functionToRun; + } else if constexpr (is_function_pointer) { + // shut up warnings about functions always having a non-null address: + const void *functionPtr = reinterpret_cast(functionToRun); + is_null = !functionPtr; + } + if (is_null) + return warnNullCallable(); + } + + return new QGenericRunnable(std::forward(functionToRun)); +} + QT_END_NAMESPACE #endif diff --git a/src/corelib/thread/qsemaphore.cpp b/src/corelib/thread/qsemaphore.cpp index 803aa30b..a7b423e4 100644 --- a/src/corelib/thread/qsemaphore.cpp +++ b/src/corelib/thread/qsemaphore.cpp @@ -145,10 +145,10 @@ static QBasicAtomicInteger *futexHigh32(QBasicAtomicInteger * } template bool -futexSemaphoreTryAcquire_loop(QBasicAtomicInteger &u, quintptr curValue, quintptr nn, int timeout) +futexSemaphoreTryAcquire_loop(QBasicAtomicInteger &u, quintptr curValue, quintptr nn, + QDeadlineTimer timer) { - QDeadlineTimer timer(IsTimed ? QDeadlineTimer(timeout) : QDeadlineTimer()); - qint64 remainingTime = timeout * Q_INT64_C(1000) * 1000; + qint64 remainingTime = IsTimed ? timer.remainingTimeNSecs() : -1; int n = int(unsigned(nn)); // we're called after one testAndSet, so start by waiting first @@ -190,8 +190,13 @@ futexSemaphoreTryAcquire_loop(QBasicAtomicInteger &u, quintptr curValu } } -template bool futexSemaphoreTryAcquire(QBasicAtomicInteger &u, int n, int timeout) +static constexpr QDeadlineTimer::ForeverConstant Expired = + QDeadlineTimer::ForeverConstant(1); + +template bool +futexSemaphoreTryAcquire(QBasicAtomicInteger &u, int n, T timeout) { + constexpr bool IsTimed = std::is_same_v; // Try to acquire without waiting (we still loop because the testAndSet // call can fail). quintptr nn = unsigned(n); @@ -205,8 +210,13 @@ template bool futexSemaphoreTryAcquire(QBasicAtomicInteger= QT_VERSION_CHECK(7, 0, 0) +# warning "Move the Q_ASSERT to inline code, make QSemaphore have wide contract, " \ + "and mark noexcept where futexes are in use." +#else Q_ASSERT_X(n >= 0, "QSemaphore::acquire", "parameter 'n' must be non-negative"); +#endif if (futexAvailable()) { - futexSemaphoreTryAcquire(u, n, -1); + futexSemaphoreTryAcquire(u, n, QDeadlineTimer::Forever); return; } @@ -409,7 +424,7 @@ bool QSemaphore::tryAcquire(int n) Q_ASSERT_X(n >= 0, "QSemaphore::tryAcquire", "parameter 'n' must be non-negative"); if (futexAvailable()) - return futexSemaphoreTryAcquire(u, n, 0); + return futexSemaphoreTryAcquire(u, n, Expired); const auto locker = qt_scoped_lock(d->mutex); if (n > d->avail) @@ -419,6 +434,8 @@ bool QSemaphore::tryAcquire(int n) } /*! + \fn QSemaphore::tryAcquire(int n, int timeout) + Tries to acquire \c n resources guarded by the semaphore and returns \c true on success. If available() < \a n, this call will wait for at most \a timeout milliseconds for resources to become @@ -434,26 +451,40 @@ bool QSemaphore::tryAcquire(int n) \sa acquire() */ -bool QSemaphore::tryAcquire(int n, int timeout) + +/*! + \since 6.6 + + Tries to acquire \c n resources guarded by the semaphore and returns \c + true on success. If available() < \a n, this call will wait until \a timer + expires for resources to become available. + + Example: + + \snippet code/src_corelib_thread_qsemaphore.cpp tryAcquire-QDeadlineTimer + + \sa acquire() +*/ +bool QSemaphore::tryAcquire(int n, QDeadlineTimer timer) { - if (timeout < 0) { + if (timer.isForever()) { acquire(n); return true; } - if (timeout == 0) + if (timer.hasExpired()) return tryAcquire(n); Q_ASSERT_X(n >= 0, "QSemaphore::tryAcquire", "parameter 'n' must be non-negative"); if (futexAvailable()) - return futexSemaphoreTryAcquire(u, n, timeout); + return futexSemaphoreTryAcquire(u, n, timer); using namespace std::chrono; const auto sufficientResourcesAvailable = [this, n] { return d->avail >= n; }; auto locker = qt_unique_lock(d->mutex); - if (!d->cond.wait_for(locker, milliseconds{timeout}, sufficientResourcesAvailable)) + if (!d->cond.wait_until(locker, timer.deadline(), sufficientResourcesAvailable)) return false; d->avail -= n; return true; diff --git a/src/corelib/thread/qsemaphore.h b/src/corelib/thread/qsemaphore.h index 8823468d..dda722a5 100644 --- a/src/corelib/thread/qsemaphore.h +++ b/src/corelib/thread/qsemaphore.h @@ -5,7 +5,7 @@ #define QSEMAPHORE_H #include -#include // for convertToMilliseconds() +#include #include @@ -14,7 +14,6 @@ QT_REQUIRE_CONFIG(thread); QT_BEGIN_NAMESPACE class QSemaphorePrivate; - class Q_CORE_EXPORT QSemaphore { public: @@ -23,10 +22,14 @@ public: void acquire(int n = 1); bool tryAcquire(int n = 1); + QT_CORE_INLINE_SINCE(6, 6) bool tryAcquire(int n, int timeout); + bool tryAcquire(int n, QDeadlineTimer timeout); +#if QT_VERSION < QT_VERSION_CHECK(7, 0, 0) template bool tryAcquire(int n, std::chrono::duration timeout) - { return tryAcquire(n, QtPrivate::convertToMilliseconds(timeout)); } + { return tryAcquire(n, QDeadlineTimer(timeout)); } +#endif void release(int n = 1); @@ -53,14 +56,25 @@ private: }; }; +#if QT_CORE_INLINE_IMPL_SINCE(6, 6) +bool QSemaphore::tryAcquire(int n, int timeout) +{ + return tryAcquire(n, QDeadlineTimer(timeout)); +} +#endif + class QSemaphoreReleaser { public: + Q_NODISCARD_CTOR QSemaphoreReleaser() = default; + Q_NODISCARD_CTOR explicit QSemaphoreReleaser(QSemaphore &sem, int n = 1) noexcept : m_sem(&sem), m_n(n) {} + Q_NODISCARD_CTOR explicit QSemaphoreReleaser(QSemaphore *sem, int n = 1) noexcept : m_sem(sem), m_n(n) {} + Q_NODISCARD_CTOR QSemaphoreReleaser(QSemaphoreReleaser &&other) noexcept : m_sem(other.cancel()), m_n(other.m_n) {} QT_MOVE_ASSIGNMENT_OPERATOR_IMPL_VIA_MOVE_AND_SWAP(QSemaphoreReleaser) diff --git a/src/corelib/thread/qthread.cpp b/src/corelib/thread/qthread.cpp index 40ee2a60..c54fa0f6 100644 --- a/src/corelib/thread/qthread.cpp +++ b/src/corelib/thread/qthread.cpp @@ -751,16 +751,28 @@ QThread::Priority QThread::priority() const } /*! - \fn void QThread::sleep(unsigned long secs) + \fn void QThread::sleep(std::chrono::nanoseconds nsecs) + \since 6.6 - Forces the current thread to sleep for \a secs seconds. + Forces the current thread to sleep for \a nsecs. Avoid using this function if you need to wait for a given condition to change. Instead, connect a slot to the signal that indicates the change or use an event handler (see \l QObject::event()). \note This function does not guarantee accuracy. The application may sleep - longer than \a secs under heavy load conditions. + longer than \a nsecs under heavy load conditions. +*/ + +/*! + \fn void QThread::sleep(unsigned long secs) + + Forces the current thread to sleep for \a secs seconds. + + This is an overloaded function, equivalent to calling: + \code + QThread::sleep(std::chrono::seconds{secs}); + \endcode \sa msleep(), usleep() */ @@ -768,11 +780,10 @@ QThread::Priority QThread::priority() const /*! \fn void QThread::msleep(unsigned long msecs) - Forces the current thread to sleep for \a msecs milliseconds. - - Avoid using this function if you need to wait for a given condition to - change. Instead, connect a slot to the signal that indicates the change or - use an event handler (see \l QObject::event()). + This is an overloaded function, equivalent to calling: + \code + QThread::sleep(std::chrono::milliseconds{msecs}); + \endcode \note This function does not guarantee accuracy. The application may sleep longer than \a msecs under heavy load conditions. Some OSes might round \a @@ -784,11 +795,10 @@ QThread::Priority QThread::priority() const /*! \fn void QThread::usleep(unsigned long usecs) - Forces the current thread to sleep for \a usecs microseconds. - - Avoid using this function if you need to wait for a given condition to - change. Instead, connect a slot to the signal that indicates the change or - use an event handler (see \l QObject::event()). + This is an overloaded function, equivalent to calling: + \code + QThread::sleep(std::chrono::microseconds{secs}); + \endcode \note This function does not guarantee accuracy. The application may sleep longer than \a usecs under heavy load conditions. Some OSes might round \a diff --git a/src/corelib/thread/qthread.h b/src/corelib/thread/qthread.h index e9af85ee..40f363ee 100644 --- a/src/corelib/thread/qthread.h +++ b/src/corelib/thread/qthread.h @@ -24,6 +24,7 @@ QT_BEGIN_NAMESPACE class QThreadData; class QThreadPrivate; class QAbstractEventDispatcher; +class QEventLoopLocker; class Q_CORE_EXPORT QThread : public QObject { @@ -92,6 +93,7 @@ public: static void sleep(unsigned long); static void msleep(unsigned long); static void usleep(unsigned long); + static void sleep(std::chrono::nanoseconds nsec); Q_SIGNALS: void started(QPrivateSignal); @@ -108,6 +110,7 @@ protected: private: Q_DECLARE_PRIVATE(QThread) + friend class QEventLoopLocker; #if QT_CONFIG(cxx11_future) [[nodiscard]] static QThread *createThreadImpl(std::future &&future); @@ -157,7 +160,7 @@ inline Qt::HANDLE QThread::currentThreadId() noexcept // See https://akkadia.org/drepper/tls.pdf for x86 ABI #if defined(Q_PROCESSOR_X86_32) && ((defined(Q_OS_LINUX) && defined(__GLIBC__)) || defined(Q_OS_FREEBSD)) // x86 32-bit always uses GS __asm__("movl %%gs:%c1, %0" : "=r" (tid) : "i" (2 * sizeof(void*)) : ); -#elif defined(Q_PROCESSOR_X86_64) && defined(Q_OS_DARWIN64) +#elif defined(Q_PROCESSOR_X86_64) && defined(Q_OS_DARWIN) // 64bit macOS uses GS, see https://github.com/apple/darwin-xnu/blob/master/libsyscall/os/tsd.h __asm__("movq %%gs:0, %0" : "=r" (tid) : : ); #elif defined(Q_PROCESSOR_X86_64) && ((defined(Q_OS_LINUX) && defined(__GLIBC__)) || defined(Q_OS_FREEBSD)) diff --git a/src/corelib/thread/qthread_unix.cpp b/src/corelib/thread/qthread_unix.cpp index fa03b2ea..ee66e1c3 100644 --- a/src/corelib/thread/qthread_unix.cpp +++ b/src/corelib/thread/qthread_unix.cpp @@ -8,6 +8,7 @@ #include #include +#include #if defined(Q_OS_DARWIN) # include @@ -70,6 +71,8 @@ QT_BEGIN_NAMESPACE +using namespace QtMiscUtils; + #if QT_CONFIG(thread) static_assert(sizeof(pthread_t) <= sizeof(Qt::HANDLE)); @@ -141,25 +144,25 @@ static void clear_thread_data() } template -static typename std::enable_if::isIntegral, Qt::HANDLE>::type to_HANDLE(T id) +static typename std::enable_if, Qt::HANDLE>::type to_HANDLE(T id) { return reinterpret_cast(static_cast(id)); } template -static typename std::enable_if::isIntegral, T>::type from_HANDLE(Qt::HANDLE id) +static typename std::enable_if, T>::type from_HANDLE(Qt::HANDLE id) { return static_cast(reinterpret_cast(id)); } template -static typename std::enable_if::isPointer, Qt::HANDLE>::type to_HANDLE(T id) +static typename std::enable_if, Qt::HANDLE>::type to_HANDLE(T id) { return id; } template -static typename std::enable_if::isPointer, T>::type from_HANDLE(Qt::HANDLE id) +static typename std::enable_if, T>::type from_HANDLE(Qt::HANDLE id) { return static_cast(id); } @@ -234,12 +237,12 @@ QAbstractEventDispatcher *QThreadPrivate::createEventDispatcher(QThreadData *dat #if QT_CONFIG(thread) -#if (defined(Q_OS_LINUX) || defined(Q_OS_MAC) || defined(Q_OS_QNX)) +#if (defined(Q_OS_LINUX) || defined(Q_OS_DARWIN) || defined(Q_OS_QNX)) static void setCurrentThreadName(const char *name) { # if defined(Q_OS_LINUX) && !defined(QT_LINUXBASE) prctl(PR_SET_NAME, (unsigned long)name, 0, 0, 0); -# elif defined(Q_OS_MAC) +# elif defined(Q_OS_DARWIN) pthread_setname_np(name); # elif defined(Q_OS_QNX) pthread_setname_np(pthread_self(), name); @@ -301,7 +304,7 @@ void *QThreadPrivate::start(void *arg) data->ensureEventDispatcher(); data->eventDispatcher.loadRelaxed()->startingUp(); -#if (defined(Q_OS_LINUX) || defined(Q_OS_MAC) || defined(Q_OS_QNX)) +#if (defined(Q_OS_LINUX) || defined(Q_OS_DARWIN) || defined(Q_OS_QNX)) { // Sets the name of the current thread. We can only do this // when the thread is starting, as we don't have a cross @@ -488,17 +491,6 @@ void QThread::yieldCurrentThread() #endif // QT_CONFIG(thread) -static timespec makeTimespec(std::chrono::nanoseconds nsecs) -{ - using namespace std::chrono; - const seconds secs = duration_cast(nsecs); - const nanoseconds frac = nsecs - secs; - struct timespec ts; - ts.tv_sec = secs.count(); - ts.tv_nsec = frac.count(); - return ts; -} - static void qt_nanosleep(timespec amount) { // We'd like to use clock_nanosleep. @@ -515,17 +507,22 @@ static void qt_nanosleep(timespec amount) void QThread::sleep(unsigned long secs) { - qt_nanosleep(makeTimespec(std::chrono::seconds{secs})); + sleep(std::chrono::seconds{secs}); } void QThread::msleep(unsigned long msecs) { - qt_nanosleep(makeTimespec(std::chrono::milliseconds{msecs})); + sleep(std::chrono::milliseconds{msecs}); } void QThread::usleep(unsigned long usecs) { - qt_nanosleep(makeTimespec(std::chrono::microseconds{usecs})); + sleep(std::chrono::microseconds{usecs}); +} + +void QThread::sleep(std::chrono::nanoseconds nsec) +{ + qt_nanosleep(durationToTimespec(nsec)); } #if QT_CONFIG(thread) diff --git a/src/corelib/thread/qthread_win.cpp b/src/corelib/thread/qthread_win.cpp index 14e3f330..b4a10d24 100644 --- a/src/corelib/thread/qthread_win.cpp +++ b/src/corelib/thread/qthread_win.cpp @@ -368,6 +368,12 @@ void QThread::yieldCurrentThread() #endif // QT_CONFIG(thread) +void QThread::sleep(std::chrono::nanoseconds nsecs) +{ + using namespace std::chrono; + ::Sleep(DWORD(duration_cast(nsecs).count())); +} + void QThread::sleep(unsigned long secs) { ::Sleep(secs * 1000); diff --git a/src/corelib/thread/qthreadpool.cpp b/src/corelib/thread/qthreadpool.cpp index 51783321..fb7ef030 100644 --- a/src/corelib/thread/qthreadpool.cpp +++ b/src/corelib/thread/qthreadpool.cpp @@ -517,20 +517,21 @@ void QThreadPool::start(QRunnable *runnable, int priority) } /*! + \fn template> void QThreadPool::start(Callable &&callableToRun, int priority) \overload \since 5.15 - Reserves a thread and uses it to run \a functionToRun, unless this thread will + Reserves a thread and uses it to run \a callableToRun, unless this thread will make the current thread count exceed maxThreadCount(). In that case, - \a functionToRun is added to a run queue instead. The \a priority argument can + \a callableToRun is added to a run queue instead. The \a priority argument can be used to control the run queue's order of execution. + + \note This function participates in overload resolution only if \c Callable + is a function or function object which can be called with zero arguments. + + \note In Qt version prior to 6.6, this function took std::function, + and therefore couldn't handle move-only callables. */ -void QThreadPool::start(std::function functionToRun, int priority) -{ - if (!functionToRun) - return; - start(QRunnable::create(std::move(functionToRun)), priority); -} /*! Attempts to reserve a thread to run \a runnable. @@ -562,30 +563,21 @@ bool QThreadPool::tryStart(QRunnable *runnable) } /*! + \fn template> bool QThreadPool::tryStart(Callable &&callableToRun) \overload \since 5.15 - Attempts to reserve a thread to run \a functionToRun. + Attempts to reserve a thread to run \a callableToRun. If no threads are available at the time of calling, then this function - does nothing and returns \c false. Otherwise, \a functionToRun is run immediately + does nothing and returns \c false. Otherwise, \a callableToRun is run immediately using one available thread and this function returns \c true. + + \note This function participates in overload resolution only if \c Callable + is a function or function object which can be called with zero arguments. + + \note In Qt version prior to 6.6, this function took std::function, + and therefore couldn't handle move-only callables. */ -bool QThreadPool::tryStart(std::function functionToRun) -{ - if (!functionToRun) - return false; - - Q_D(QThreadPool); - QMutexLocker locker(&d->mutex); - if (!d->allThreads.isEmpty() && d->areAllThreadsActive()) - return false; - - QRunnable *runnable = QRunnable::create(std::move(functionToRun)); - if (d->tryStart(runnable)) - return true; - delete runnable; - return false; -} /*! \property QThreadPool::expiryTimeout \brief the thread expiry timeout value in milliseconds. @@ -799,19 +791,19 @@ void QThreadPool::startOnReservedThread(QRunnable *runnable) } /*! + \fn template> void QThreadPool::startOnReservedThread(Callable &&callableToRun) \overload \since 6.3 Releases a thread previously reserved with reserveThread() and uses it - to run \a functionToRun. -*/ -void QThreadPool::startOnReservedThread(std::function functionToRun) -{ - if (!functionToRun) - return releaseThread(); + to run \a callableToRun. - startOnReservedThread(QRunnable::create(std::move(functionToRun))); -} + \note This function participates in overload resolution only if \c Callable + is a function or function object which can be called with zero arguments. + + \note In Qt version prior to 6.6, this function took std::function, + and therefore couldn't handle move-only callables. +*/ /*! Waits up to \a msecs milliseconds for all threads to exit and removes all diff --git a/src/corelib/thread/qthreadpool.h b/src/corelib/thread/qthreadpool.h index e6d0326c..a097ace1 100644 --- a/src/corelib/thread/qthreadpool.h +++ b/src/corelib/thread/qthreadpool.h @@ -36,11 +36,22 @@ public: void start(QRunnable *runnable, int priority = 0); bool tryStart(QRunnable *runnable); +#if QT_CORE_REMOVED_SINCE(6, 6) void start(std::function functionToRun, int priority = 0); bool tryStart(std::function functionToRun); +#endif void startOnReservedThread(QRunnable *runnable); +#if QT_CORE_REMOVED_SINCE(6, 6) void startOnReservedThread(std::function functionToRun); +#endif + + template = true> + void start(Callable &&functionToRun, int priority = 0); + template = true> + bool tryStart(Callable &&functionToRun); + template = true> + void startOnReservedThread(Callable &&functionToRun); int expiryTimeout() const; void setExpiryTimeout(int expiryTimeout); @@ -68,6 +79,28 @@ public: [[nodiscard]] bool tryTake(QRunnable *runnable); }; +template > +void QThreadPool::start(Callable &&functionToRun, int priority) +{ + start(QRunnable::create(std::forward(functionToRun)), priority); +} + +template > +bool QThreadPool::tryStart(Callable &&functionToRun) +{ + QRunnable *runnable = QRunnable::create(std::forward(functionToRun)); + if (tryStart(runnable)) + return true; + delete runnable; + return false; +} + +template > +void QThreadPool::startOnReservedThread(Callable &&functionToRun) +{ + startOnReservedThread(QRunnable::create(std::forward(functionToRun))); +} + QT_END_NAMESPACE #endif diff --git a/src/corelib/thread/qwaitcondition_unix.cpp b/src/corelib/thread/qwaitcondition_unix.cpp index c97023d9..0e29316d 100644 --- a/src/corelib/thread/qwaitcondition_unix.cpp +++ b/src/corelib/thread/qwaitcondition_unix.cpp @@ -2,18 +2,17 @@ // Copyright (C) 2016 Intel Corporation. // SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only -#include "qplatformdefs.h" #include "qwaitcondition.h" -#include "qmutex.h" -#include "qreadwritelock.h" -#include "qatomic.h" -#include "qstring.h" -#include "qdeadlinetimer.h" -#include "private/qdeadlinetimer_p.h" -#include "qelapsedtimer.h" -#include "private/qcore_unix_p.h" -#include "qmutex_p.h" +#include "qatomic.h" +#include "qdeadlinetimer.h" +#include "qelapsedtimer.h" +#include "qmutex.h" +#include "qplatformdefs.h" +#include "qreadwritelock.h" +#include "qstring.h" + +#include "private/qcore_unix_p.h" #include "qreadwritelock_p.h" #include @@ -22,44 +21,61 @@ QT_BEGIN_NAMESPACE -static void report_error(int code, const char *where, const char *what) +static constexpr clockid_t SteadyClockClockId = +#if !defined(CLOCK_MONOTONIC) + // we don't know how to set the monotonic clock + CLOCK_REALTIME +#elif defined(_LIBCPP_VERSION) && defined(_LIBCPP_HAS_NO_MONOTONIC_CLOCK) + // libc++ falling back to system_clock + CLOCK_REALTIME +#elif defined(__GLIBCXX__) && !defined(_GLIBCXX_USE_CLOCK_MONOTONIC) + // libstdc++ falling back to system_clock + CLOCK_REALTIME +#elif defined(Q_OS_DARWIN) + // Darwin lacks pthread_condattr_setclock() + CLOCK_REALTIME +#elif defined(Q_OS_QNX) + // unknown why + CLOCK_REALTIME +#elif defined(__GLIBCXX__) || defined(_LIBCPP_VERSION) + // both libstdc++ and libc++ do use CLOCK_MONOTONIC + CLOCK_MONOTONIC +#else +# warning "Unknown C++ Standard Library implementation - code may be sub-optimal" + CLOCK_REALTIME +#endif + ; + +static void qt_report_pthread_error(int code, const char *where, const char *what) { if (code != 0) qErrnoWarning(code, "%s: %s failure", where, what); } -void qt_initialize_pthread_cond(pthread_cond_t *cond, const char *where) +static void qt_initialize_pthread_cond(pthread_cond_t *cond, const char *where) { + pthread_condattr_t *attrp = nullptr; + +#if defined(CLOCK_MONOTONIC) && !defined(Q_OS_DARWIN) pthread_condattr_t condattr; + attrp = &condattr; pthread_condattr_init(&condattr); -#if defined(CLOCK_MONOTONIC) && !defined(Q_OS_DARWIN) - if (QElapsedTimer::clockType() == QElapsedTimer::MonotonicClock) - pthread_condattr_setclock(&condattr, CLOCK_MONOTONIC); + auto destroy = qScopeGuard([&] { pthread_condattr_destroy(&condattr); }); + if (SteadyClockClockId != CLOCK_REALTIME) + pthread_condattr_setclock(&condattr, SteadyClockClockId); #endif - report_error(pthread_cond_init(cond, &condattr), where, "cv init"); - pthread_condattr_destroy(&condattr); + + qt_report_pthread_error(pthread_cond_init(cond, attrp), where, "cv init"); } -void qt_abstime_for_timeout(timespec *ts, QDeadlineTimer deadline) +static void qt_abstime_for_timeout(timespec *ts, QDeadlineTimer deadline) { -#ifdef Q_OS_MAC - // on Mac, qt_gettime() (on qelapsedtimer_mac.cpp) returns ticks related to the Mach absolute time - // that doesn't work with pthread - // Mac also doesn't have clock_gettime - struct timeval tv; - qint64 nsec = deadline.remainingTimeNSecs(); - gettimeofday(&tv, 0); - ts->tv_sec = tv.tv_sec + nsec / (1000 * 1000 * 1000); - ts->tv_nsec = tv.tv_usec * 1000 + nsec % (1000 * 1000 * 1000); - - normalizedTimespec(*ts); -#else - // depends on QDeadlineTimer's internals!! - static_assert(QDeadlineTimerNanosecondsInT2); - ts->tv_sec = deadline._q_data().first; - ts->tv_nsec = deadline._q_data().second; -#endif + using namespace std::chrono; + using Clock = + std::conditional_t; + auto timePoint = deadline.deadline(); + *ts = durationToTimespec(timePoint.time_since_epoch()); } class QWaitConditionPrivate @@ -87,9 +103,7 @@ public: code = pthread_cond_wait(&cond, &mutex); } if (code == 0 && wakeups == 0) { - // many vendors warn of spurious wakeups from - // pthread_cond_wait(), especially after signal delivery, - // even though POSIX doesn't allow for it... sigh + // spurious wakeup continue; } break; @@ -101,10 +115,11 @@ public: Q_ASSERT_X(wakeups > 0, "QWaitCondition::wait", "internal error (wakeups)"); --wakeups; } - report_error(pthread_mutex_unlock(&mutex), "QWaitCondition::wait()", "mutex unlock"); + qt_report_pthread_error(pthread_mutex_unlock(&mutex), "QWaitCondition::wait()", + "mutex unlock"); if (code && code != ETIMEDOUT) - report_error(code, "QWaitCondition::wait()", "cv wait"); + qt_report_pthread_error(code, "QWaitCondition::wait()", "cv wait"); return (code == 0); } @@ -113,32 +128,38 @@ public: QWaitCondition::QWaitCondition() { d = new QWaitConditionPrivate; - report_error(pthread_mutex_init(&d->mutex, nullptr), "QWaitCondition", "mutex init"); + qt_report_pthread_error(pthread_mutex_init(&d->mutex, nullptr), "QWaitCondition", "mutex init"); qt_initialize_pthread_cond(&d->cond, "QWaitCondition"); d->waiters = d->wakeups = 0; } QWaitCondition::~QWaitCondition() { - report_error(pthread_cond_destroy(&d->cond), "QWaitCondition", "cv destroy"); - report_error(pthread_mutex_destroy(&d->mutex), "QWaitCondition", "mutex destroy"); + qt_report_pthread_error(pthread_cond_destroy(&d->cond), "QWaitCondition", "cv destroy"); + qt_report_pthread_error(pthread_mutex_destroy(&d->mutex), "QWaitCondition", "mutex destroy"); delete d; } void QWaitCondition::wakeOne() { - report_error(pthread_mutex_lock(&d->mutex), "QWaitCondition::wakeOne()", "mutex lock"); + qt_report_pthread_error(pthread_mutex_lock(&d->mutex), "QWaitCondition::wakeOne()", + "mutex lock"); d->wakeups = qMin(d->wakeups + 1, d->waiters); - report_error(pthread_cond_signal(&d->cond), "QWaitCondition::wakeOne()", "cv signal"); - report_error(pthread_mutex_unlock(&d->mutex), "QWaitCondition::wakeOne()", "mutex unlock"); + qt_report_pthread_error(pthread_cond_signal(&d->cond), "QWaitCondition::wakeOne()", + "cv signal"); + qt_report_pthread_error(pthread_mutex_unlock(&d->mutex), "QWaitCondition::wakeOne()", + "mutex unlock"); } void QWaitCondition::wakeAll() { - report_error(pthread_mutex_lock(&d->mutex), "QWaitCondition::wakeAll()", "mutex lock"); + qt_report_pthread_error(pthread_mutex_lock(&d->mutex), "QWaitCondition::wakeAll()", + "mutex lock"); d->wakeups = d->waiters; - report_error(pthread_cond_broadcast(&d->cond), "QWaitCondition::wakeAll()", "cv broadcast"); - report_error(pthread_mutex_unlock(&d->mutex), "QWaitCondition::wakeAll()", "mutex unlock"); + qt_report_pthread_error(pthread_cond_broadcast(&d->cond), "QWaitCondition::wakeAll()", + "cv broadcast"); + qt_report_pthread_error(pthread_mutex_unlock(&d->mutex), "QWaitCondition::wakeAll()", + "mutex unlock"); } bool QWaitCondition::wait(QMutex *mutex, unsigned long time) @@ -153,7 +174,7 @@ bool QWaitCondition::wait(QMutex *mutex, QDeadlineTimer deadline) if (!mutex) return false; - report_error(pthread_mutex_lock(&d->mutex), "QWaitCondition::wait()", "mutex lock"); + qt_report_pthread_error(pthread_mutex_lock(&d->mutex), "QWaitCondition::wait()", "mutex lock"); ++d->waiters; mutex->unlock(); @@ -185,7 +206,7 @@ bool QWaitCondition::wait(QReadWriteLock *readWriteLock, QDeadlineTimer deadline return false; } - report_error(pthread_mutex_lock(&d->mutex), "QWaitCondition::wait()", "mutex lock"); + qt_report_pthread_error(pthread_mutex_lock(&d->mutex), "QWaitCondition::wait()", "mutex lock"); ++d->waiters; readWriteLock->unlock(); diff --git a/src/corelib/thread/qwaitcondition_win.cpp b/src/corelib/thread/qwaitcondition_win.cpp index c176241f..ba53309e 100644 --- a/src/corelib/thread/qwaitcondition_win.cpp +++ b/src/corelib/thread/qwaitcondition_win.cpp @@ -43,7 +43,7 @@ public: EventQueue freeQueue; QWaitConditionEvent *pre(); - bool wait(QWaitConditionEvent *wce, unsigned long time); + bool wait(QWaitConditionEvent *wce, QDeadlineTimer deadline); void post(QWaitConditionEvent *wce, bool ret); }; @@ -68,19 +68,25 @@ QWaitConditionEvent *QWaitConditionPrivate::pre() return wce; } -bool QWaitConditionPrivate::wait(QWaitConditionEvent *wce, unsigned long time) +bool QWaitConditionPrivate::wait(QWaitConditionEvent *wce, QDeadlineTimer deadline) { // wait for the event - bool ret = false; - switch (WaitForSingleObjectEx(wce->event, time, FALSE)) { - default: - break; + while (true) { + const DWORD timeout = deadline.isForever() + ? INFINITE + : DWORD(std::min(deadline.remainingTime(), qint64(INFINITE - 1))); - case WAIT_OBJECT_0: - ret = true; - break; + switch (WaitForSingleObjectEx(wce->event, timeout, FALSE)) { + case WAIT_OBJECT_0: + return true; + case WAIT_TIMEOUT: + if (deadline.hasExpired()) + return false; + break; + default: + return false; + } } - return ret; } void QWaitConditionPrivate::post(QWaitConditionEvent *wce, bool ret) @@ -123,6 +129,13 @@ QWaitCondition::~QWaitCondition() } bool QWaitCondition::wait(QMutex *mutex, unsigned long time) +{ + if (time == std::numeric_limits::max()) + return wait(mutex, QDeadlineTimer(QDeadlineTimer::Forever)); + return wait(mutex, QDeadlineTimer(time)); +} + +bool QWaitCondition::wait(QMutex *mutex, QDeadlineTimer deadline) { if (!mutex) return false; @@ -130,7 +143,7 @@ bool QWaitCondition::wait(QMutex *mutex, unsigned long time) QWaitConditionEvent *wce = d->pre(); mutex->unlock(); - bool returnValue = d->wait(wce, time); + bool returnValue = d->wait(wce, deadline); mutex->lock(); d->post(wce, returnValue); @@ -138,12 +151,14 @@ bool QWaitCondition::wait(QMutex *mutex, unsigned long time) return returnValue; } -bool QWaitCondition::wait(QMutex *mutex, QDeadlineTimer deadline) +bool QWaitCondition::wait(QReadWriteLock *readWriteLock, unsigned long time) { - return wait(mutex, deadline.remainingTime()); + if (time == std::numeric_limits::max()) + return wait(readWriteLock, QDeadlineTimer(QDeadlineTimer::Forever)); + return wait(readWriteLock, QDeadlineTimer(time)); } -bool QWaitCondition::wait(QReadWriteLock *readWriteLock, unsigned long time) +bool QWaitCondition::wait(QReadWriteLock *readWriteLock, QDeadlineTimer deadline) { using namespace QReadWriteLockStates; @@ -160,7 +175,7 @@ bool QWaitCondition::wait(QReadWriteLock *readWriteLock, unsigned long time) QWaitConditionEvent *wce = d->pre(); readWriteLock->unlock(); - bool returnValue = d->wait(wce, time); + bool returnValue = d->wait(wce, deadline); if (previousState == LockedForWrite) readWriteLock->lockForWrite(); @@ -171,11 +186,6 @@ bool QWaitCondition::wait(QReadWriteLock *readWriteLock, unsigned long time) return returnValue; } -bool QWaitCondition::wait(QReadWriteLock *readWriteLock, QDeadlineTimer deadline) -{ - return wait(readWriteLock, deadline.remainingTime()); -} - void QWaitCondition::wakeOne() { // wake up the first waiting thread in the queue diff --git a/src/corelib/time/qcalendar.cpp b/src/corelib/time/qcalendar.cpp index 7f3ea9e2..5eaecedb 100644 --- a/src/corelib/time/qcalendar.cpp +++ b/src/corelib/time/qcalendar.cpp @@ -864,7 +864,7 @@ int QCalendarBackend::maximumMonthsInYear() const */ int QCalendarBackend::dayOfWeek(qint64 jd) const { - return QRoundingDown::qMod(jd, 7) + 1; + return QRoundingDown::qMod<7>(jd) + 1; } // Month and week-day name look-ups (implemented in qlocale.cpp): diff --git a/src/corelib/time/qcalendarmath_p.h b/src/corelib/time/qcalendarmath_p.h index 885f5fab..c785803c 100644 --- a/src/corelib/time/qcalendarmath_p.h +++ b/src/corelib/time/qcalendarmath_p.h @@ -16,10 +16,27 @@ // #include +#include QT_BEGIN_NAMESPACE namespace QRoundingDown { +// Note: qgregoriancalendar.cpp contains some static asserts to verify this all works. +namespace QRoundingDownPrivate { +#ifdef Q_CC_MSVC +// MSVC 2019 doesn't believe in the constexpr-ness of the #else clause's version :-( +#define QCALMATH_ISPOW2(b) ((b > 0) && !(b & (b - 1))) // See #else's comment. +#else +// Subtracting one toggles the least significant set bit and any unset bits less +// significant than it, leaving other bits unchanged. Thus the & of this with +// the original number preserves all more significant bits, clearing the least +// significant. If there are no such bits, either our number was 0 or it only +// had one bit set, hence is a power of two. +template +inline constexpr bool isPowerOfTwo(Int b) { return b > 0 && (b & (b - 1)) == 0; } +#define QCALMATH_ISPOW2(b) QRoundingDownPrivate::isPowerOfTwo(b) +#endif +} /* Division, rounding down (rather than towards zero). @@ -33,16 +50,96 @@ namespace QRoundingDown { doesn't change the result of dividing by b; and we want one less than that result. This is equivalent to subtracting b - 1 and simply dividing, except when that subtraction would underflow. + + For the remainder, with negative a, aside from having to add one and subtract + it later to deal with the exact multiples, we can simply use the truncating + remainder and then add b. When b is a power of two we can, of course, get the + remainder correctly by the same masking that works for positive a. */ -template constexpr Int qDiv(Int a, unsigned b) -{ return a < 0 ? (a + 1) / int(b) - 1 : a / int(b); } +// Fall-back, to ensure intelligible error messages on mis-use: +template = true> +constexpr auto qDivMod(Int) +{ + static_assert(b, "Division by 0 is undefined"); + // Use complement of earlier cases || new check, to ensure only one error: + static_assert(!b || int(b) > 0, "Denominator is too big"); + static_assert(int(b) < 1 || b > 1, "Division by 1 is fautous"); + struct R { Int quotient; Int remainder; }; + return R { 0, 0 }; +} -template constexpr Int qMod(Int a, unsigned b) -{ return a - qDiv(a, b) * b; } +template 1) && !QCALMATH_ISPOW2(b) && (int(b) > 0), + bool> = true> +constexpr auto qDivMod(Int a) +{ + struct R { Int quotient; Int remainder; }; + if constexpr (std::is_signed_v) { + if (a < 0) { + ++a; // can't overflow, it's negative + return R { Int(a / int(b) - 1), Int(a % int(b) - 1 + int(b)) }; + } + } + return R { Int(a / int(b)), Int(a % int(b)) }; +} + +template 1) && QCALMATH_ISPOW2(b) && (int(b) > 0), + bool> = true> +constexpr auto qDivMod(Int a) +{ + constexpr unsigned w = QtPrivate::qConstexprCountTrailingZeroBits(b); + struct R { Int quotient; Int remainder; }; + if constexpr (std::is_signed_v) { + if (a < 0) + return R { Int((a + 1) / int(b) - 1), Int(a & int(b - 1)) }; + } + return R { Int(a >> w), Int(a & int(b - 1)) }; +} + +#undef QCALMATH_ISPOW2 +// + +template constexpr Int qDiv(Int a) { return qDivMod(a).quotient; } +template constexpr Int qMod(Int a) { return qDivMod(a).remainder; } } // QRoundingDown +namespace QRomanCalendrical { +// Julian Day number of Gregorian 1 BCE, February 29th: +constexpr qint64 LeapDayGregorian1Bce = 1721119; +// Aside from (maybe) some turns of centuries, one year in four is leap: +constexpr unsigned FourYears = 4 * 365 + 1; +constexpr unsigned FiveMonths = 31 + 30 + 31 + 30 + 31; // Mar-Jul or Aug-Dec. + +constexpr auto yearMonthToYearDays(int year, int month) +{ + // Pre-digests year and month to (possibly denormal) year count and day-within-year. + struct R { qint64 year; qint64 days; }; + if (year < 0) // Represent -N BCE as 1-N so year numbering is contiguous. + ++year; + month -= 3; // Adjust month numbering so March = 0, ... + if (month < 0) { // and Jan = 10, Feb = 11, in the previous year. + --year; + month += 12; + } + return R { year, QRoundingDown::qDiv<5>(FiveMonths * month + 2) }; +} + +constexpr auto dayInYearToYmd(int dayInYear) +{ + // The year is an adjustment to the year for which dayInYear may be denormal. + struct R { int year; int month; int day; }; + // Shared code for Julian and Milankovic (at least). + using namespace QRoundingDown; + const auto month5Day = qDivMod(5 * dayInYear + 2); + // Its remainder changes by 5 per day, except at roughly monthly quotient steps. + const auto yearMonth = qDivMod<12>(month5Day.quotient + 2); + return R { yearMonth.quotient, yearMonth.remainder + 1, qDiv<5>(month5Day.remainder) + 1 }; +} +} + QT_END_NAMESPACE #endif // QCALENDARMATH_P_H diff --git a/src/corelib/time/qdatetime.cpp b/src/corelib/time/qdatetime.cpp index 9772d436..cd4cdf7d 100644 --- a/src/corelib/time/qdatetime.cpp +++ b/src/corelib/time/qdatetime.cpp @@ -19,8 +19,10 @@ #include "private/qcore_mac_p.h" #endif #include "private/qgregoriancalendar_p.h" +#include "private/qlocale_tools_p.h" #include "private/qlocaltime_p.h" #include "private/qnumeric_p.h" +#include "private/qstringconverter_p.h" #include "private/qstringiterator_p.h" #if QT_CONFIG(timezone) #include "private/qtimezoneprivate_p.h" @@ -31,10 +33,13 @@ # include #endif +#include + QT_BEGIN_NAMESPACE using namespace Qt::StringLiterals; using namespace QtPrivate::DateTimeConstants; +using namespace QtMiscUtils; /***************************************************************************** Date/Time Constants @@ -58,9 +63,9 @@ static inline QDate fixedDate(QCalendar::YearMonthDay parts) { if (parts.year) { parts.day = qMin(parts.day, QGregorianCalendar::monthLength(parts.month, parts.year)); - qint64 jd; - if (QGregorianCalendar::julianFromParts(parts.year, parts.month, parts.day, &jd)) - return QDate::fromJulianDay(jd); + const auto jd = QGregorianCalendar::julianFromParts(parts.year, parts.month, parts.day); + if (jd) + return QDate::fromJulianDay(*jd); } return QDate(); } @@ -86,6 +91,42 @@ static int fromShortMonthName(QStringView monthName) #endif // textdate #if QT_CONFIG(datestring) // depends on, so implies, textdate +namespace { +using ParsedInt = QSimpleParsedNumber; + +/* + Reads a whole number that must be the whole text. +*/ +ParsedInt readInt(QLatin1StringView text) +{ + // Various date formats' fields (e.g. all in ISO) should not accept spaces + // or signs, so check that the string starts with a digit and that qstrntoull() + // converted the whole string. + + if (text.isEmpty() || !isAsciiDigit(text.front().toLatin1())) + return {}; + + QSimpleParsedNumber res = qstrntoull(text.data(), text.size(), 10); + return res.used == text.size() ? res : ParsedInt{}; +} + +ParsedInt readInt(QStringView text) +{ + if (text.isEmpty()) + return {}; + + // Converting to Latin-1 because QStringView::toULongLong() works with + // US-ASCII only by design anyway. + // Also QStringView::toULongLong() can't be used here as it will happily ignore + // spaces and accept signs; but various date formats' fields (e.g. all in ISO) + // should not. + QVarLengthArray latin1(text.size()); + QLatin1::convertFromUnicode(latin1.data(), text); + return readInt(QLatin1StringView{latin1.data(), latin1.size()}); +} + +} // namespace + struct ParsedRfcDateTime { QDate date; QTime time; @@ -394,8 +435,9 @@ static int fromOffsetString(QStringView offsetString, bool *valid) noexcept QDate::QDate(int y, int m, int d) { - if (!QGregorianCalendar::julianFromParts(y, m, d, &jd)) - jd = nullJd(); + static_assert(QDate::maxJd() == JulianDayMax); + static_assert(QDate::minJd() == JulianDayMin); + jd = QGregorianCalendar::julianFromParts(y, m, d).value_or(nullJd()); } QDate::QDate(int y, int m, int d, QCalendar cal) @@ -654,9 +696,8 @@ int QDate::dayOfYear(QCalendar cal) const int QDate::dayOfYear() const { if (isValid()) { - qint64 first; - if (QGregorianCalendar::julianFromParts(year(), 1, 1, &first)) - return jd - first + 1; + if (const auto first = QGregorianCalendar::julianFromParts(year(), 1, 1)) + return jd - *first + 1; } return 0; } @@ -787,7 +828,9 @@ static QTimeZone asTimeZone(Qt::TimeSpec spec, int offset, const char *warner) } #endif // Helper for 6.9 deprecation -static bool inDateTimeRange(qint64 jd, bool start) +enum class DaySide { Start, End }; + +static bool inDateTimeRange(qint64 jd, DaySide side) { using Bounds = std::numeric_limits; if (jd < Bounds::min() + JULIAN_DAY_FOR_EPOCH) @@ -795,11 +838,16 @@ static bool inDateTimeRange(qint64 jd, bool start) jd -= JULIAN_DAY_FOR_EPOCH; const qint64 maxDay = Bounds::max() / MSECS_PER_DAY; const qint64 minDay = Bounds::min() / MSECS_PER_DAY - 1; - // (Divisions rounded towards zero, as MSECS_PER_DAY has factors other than two.) + // (Divisions rounded towards zero, as MSECS_PER_DAY is even - so doesn't + // divide max() - and has factors other than two, so doesn't divide min().) // Range includes start of last day and end of first: - if (start) + switch (side) { + case DaySide::Start: return jd > minDay && jd <= maxDay; - return jd >= minDay && jd < maxDay; + case DaySide::End: + return jd >= minDay && jd < maxDay; + } + Q_UNREACHABLE_RETURN(false); } static QDateTime toEarliest(QDate day, const QTimeZone &zone) @@ -822,8 +870,8 @@ static QDateTime toEarliest(QDate day, const QTimeZone &zone) int low = 0; // Binary chop to the right minute while (high > low + 1) { - int mid = (high + low) / 2; - QDateTime probe = moment(QTime(mid / 60, mid % 60)); + const int mid = (high + low) / 2; + const QDateTime probe = moment(QTime(mid / 60, mid % 60)); if (probe.isValid() && probe.date() == day) { high = mid; when = probe; @@ -831,6 +879,24 @@ static QDateTime toEarliest(QDate day, const QTimeZone &zone) low = mid; } } + // Transitions out of local solar mean time, and the few international + // date-line crossings before that (Alaska, Philippines), may have happened + // between minute boundaries. Don't try to fix milliseconds. + if (QDateTime p = moment(when.time().addSecs(-1)); Q_UNLIKELY(p.isValid() && p.date() == day)) { + high *= 60; + low *= 60; + while (high > low + 1) { + const int mid = (high + low) / 2; + const int min = mid / 60; + const QDateTime probe = moment(QTime(min / 60, min % 60, mid % 60)); + if (probe.isValid() && probe.date() == day) { + high = mid; + when = probe; + } else { + low = mid; + } + } + } return when.isValid() ? when : QDateTime(); } @@ -864,11 +930,11 @@ static QDateTime toEarliest(QDate day, const QTimeZone &zone) */ QDateTime QDate::startOfDay(const QTimeZone &zone) const { - if (!inDateTimeRange(jd, true) || !zone.isValid()) + if (!inDateTimeRange(jd, DaySide::Start) || !zone.isValid()) return QDateTime(); QDateTime when(*this, QTime(0, 0), zone); - if (when.isValid()) + if (Q_LIKELY(when.isValid())) return when; #if QT_CONFIG(timezone) @@ -953,8 +1019,8 @@ static QDateTime toLatest(QDate day, const QTimeZone &zone) int low = when.time().msecsSinceStartOfDay() / 60000; // Binary chop to the right minute while (high > low + 1) { - int mid = (high + low) / 2; - QDateTime probe = moment(QTime(mid / 60, mid % 60, 59, 999)); + const int mid = (high + low) / 2; + const QDateTime probe = moment(QTime(mid / 60, mid % 60, 59, 999)); if (probe.isValid() && probe.date() == day) { low = mid; when = probe; @@ -962,6 +1028,24 @@ static QDateTime toLatest(QDate day, const QTimeZone &zone) high = mid; } } + // Transitions out of local solar mean time, and the few international + // date-line crossings before that (Alaska, Philippines), may have happened + // between minute boundaries. Don't try to fix milliseconds. + if (QDateTime p = moment(when.time().addSecs(1)); Q_UNLIKELY(p.isValid() && p.date() == day)) { + high *= 60; + low *= 60; + while (high > low + 1) { + const int mid = (high + low) / 2; + const int min = mid / 60; + const QDateTime probe = moment(QTime(min / 60, min % 60, mid % 60, 999)); + if (probe.isValid() && probe.date() == day) { + low = mid; + when = probe; + } else { + high = mid; + } + } + } return when.isValid() ? when : QDateTime(); } @@ -996,11 +1080,11 @@ static QDateTime toLatest(QDate day, const QTimeZone &zone) */ QDateTime QDate::endOfDay(const QTimeZone &zone) const { - if (!inDateTimeRange(jd, false) || !zone.isValid()) + if (!inDateTimeRange(jd, DaySide::End) || !zone.isValid()) return QDateTime(); QDateTime when(*this, QTime(23, 59, 59, 999), zone); - if (when.isValid()) + if (Q_LIKELY(when.isValid())) return when; #if QT_CONFIG(timezone) @@ -1216,11 +1300,9 @@ QString QDate::toString(QStringView format, QCalendar cal) const */ bool QDate::setDate(int year, int month, int day) { - if (QGregorianCalendar::julianFromParts(year, month, day, &jd)) - return true; - - jd = nullJd(); - return false; + const auto maybe = QGregorianCalendar::julianFromParts(year, month, day); + jd = maybe.value_or(nullJd()); + return bool(maybe); } /*! @@ -1511,29 +1593,6 @@ qint64 QDate::daysTo(QDate d) const */ #if QT_CONFIG(datestring) // depends on, so implies, textdate -namespace { - -struct ParsedInt { qulonglong value = 0; bool ok = false; }; - -/* - /internal - - Read a whole number that must be the whole text. QStringView::toULongLong() - will happily ignore spaces and accept signs; but various date formats' - fields (e.g. all in ISO) should not. -*/ -ParsedInt readInt(QStringView text) -{ - ParsedInt result; - for (QStringIterator it(text); it.hasNext();) { - if (!QChar::isDigit(it.next())) - return result; - } - result.value = text.toULongLong(&result.ok); - return result; -} - -} /*! \fn QDate QDate::fromString(const QString &string, Qt::DateFormat format) @@ -1591,8 +1650,8 @@ QDate QDate::fromString(QStringView string, Qt::DateFormat format) const ParsedInt year = readInt(string.first(4)); const ParsedInt month = readInt(string.sliced(5, 2)); const ParsedInt day = readInt(string.sliced(8, 2)); - if (year.ok && year.value > 0 && year.value <= 9999 && month.ok && day.ok) - return QDate(year.value, month.value, day.value); + if (year.ok() && year.result > 0 && year.result <= 9999 && month.ok() && day.ok()) + return QDate(year.result, month.result, day.result); } break; } @@ -2139,7 +2198,7 @@ QTime QTime::addMSecs(int ms) const { QTime t; if (isValid()) - t.mds = QRoundingDown::qMod(ds() + ms, MSECS_PER_DAY); + t.mds = QRoundingDown::qMod(ds() + ms); return t; } @@ -2264,63 +2323,63 @@ static QTime fromIsoTimeString(QStringView string, Qt::DateFormat format, bool * const ParsedInt frac = readInt(tail); // There must be *some* digits in a fractional part; and it must be all digits: - if (tail.isEmpty() ? dot != -1 || comma != -1 : !frac.ok) + if (tail.isEmpty() ? dot != -1 || comma != -1 : !frac.ok()) return QTime(); - Q_ASSERT(frac.ok ^ tail.isEmpty()); - double fraction = frac.ok ? frac.value * std::pow(0.1, tail.size()) : 0.0; + Q_ASSERT(frac.ok() ^ tail.isEmpty()); + double fraction = frac.ok() ? frac.result * std::pow(0.1, tail.size()) : 0.0; const int size = string.size(); if (size < 2 || size > 8) return QTime(); ParsedInt hour = readInt(string.first(2)); - if (!hour.ok || hour.value > (format == Qt::TextDate ? 23 : 24)) + if (!hour.ok() || hour.result > (format == Qt::TextDate ? 23 : 24)) return QTime(); - ParsedInt minute; + ParsedInt minute{}; if (string.size() > 2) { if (string[2] == u':' && string.size() > 4) minute = readInt(string.sliced(3, 2)); - if (!minute.ok || minute.value >= MINS_PER_HOUR) + if (!minute.ok() || minute.result >= MINS_PER_HOUR) return QTime(); } else if (format == Qt::TextDate) { // Requires minutes return QTime(); - } else if (frac.ok) { + } else if (frac.ok()) { Q_ASSERT(!(fraction < 0.0) && fraction < 1.0); fraction *= MINS_PER_HOUR; - minute.value = qulonglong(fraction); - fraction -= minute.value; + minute.result = qulonglong(fraction); + fraction -= minute.result; } - ParsedInt second; + ParsedInt second{}; if (string.size() > 5) { if (string[5] == u':' && string.size() == 8) second = readInt(string.sliced(6, 2)); - if (!second.ok || second.value >= SECS_PER_MIN) + if (!second.ok() || second.result >= SECS_PER_MIN) return QTime(); - } else if (frac.ok) { + } else if (frac.ok()) { if (format == Qt::TextDate) // Doesn't allow fraction of minutes return QTime(); Q_ASSERT(!(fraction < 0.0) && fraction < 1.0); fraction *= SECS_PER_MIN; - second.value = qulonglong(fraction); - fraction -= second.value; + second.result = qulonglong(fraction); + fraction -= second.result; } Q_ASSERT(!(fraction < 0.0) && fraction < 1.0); // Round millis to nearest (unlike minutes and seconds, rounded down): - int msec = frac.ok ? qRound(MSECS_PER_SEC * fraction) : 0; + int msec = frac.ok() ? qRound(MSECS_PER_SEC * fraction) : 0; // But handle overflow gracefully: if (msec == MSECS_PER_SEC) { // If we can (when data were otherwise valid) validly propagate overflow // into other fields, do so: - if (isMidnight24 || hour.value < 23 || minute.value < 59 || second.value < 59) { + if (isMidnight24 || hour.result < 23 || minute.result < 59 || second.result < 59) { msec = 0; - if (++second.value == SECS_PER_MIN) { - second.value = 0; - if (++minute.value == MINS_PER_HOUR) { - minute.value = 0; - ++hour.value; + if (++second.result == SECS_PER_MIN) { + second.result = 0; + if (++minute.result == MINS_PER_HOUR) { + minute.result = 0; + ++hour.result; // May need to propagate further via isMidnight24, see below } } @@ -2332,14 +2391,14 @@ static QTime fromIsoTimeString(QStringView string, Qt::DateFormat format, bool * } // For ISO date format, 24:0:0 means 0:0:0 on the next day: - if (hour.value == 24 && minute.value == 0 && second.value == 0 && msec == 0) { + if (hour.result == 24 && minute.result == 0 && second.result == 0 && msec == 0) { Q_ASSERT(format != Qt::TextDate); // It clipped hour at 23, above. if (isMidnight24) *isMidnight24 = true; - hour.value = 0; + hour.result = 0; } - return QTime(hour.value, minute.value, second.value, msec); + return QTime(hour.result, minute.result, second.result, msec); } /*! @@ -2507,7 +2566,7 @@ typedef QDateTimePrivate::QDateTimeData QDateTimeData; // Converts milliseconds since the start of 1970 into a date and/or time: static qint64 msecsToJulianDay(qint64 msecs) { - return JULIAN_DAY_FOR_EPOCH + QRoundingDown::qDiv(msecs, MSECS_PER_DAY); + return JULIAN_DAY_FOR_EPOCH + QRoundingDown::qDiv(msecs); } static QDate msecsToDate(qint64 msecs) @@ -2517,7 +2576,7 @@ static QDate msecsToDate(qint64 msecs) static QTime msecsToTime(qint64 msecs) { - return QTime::fromMSecsSinceStartOfDay(QRoundingDown::qMod(msecs, MSECS_PER_DAY)); + return QTime::fromMSecsSinceStartOfDay(QRoundingDown::qMod(msecs)); } // True if combining days with millis overflows; otherwise, stores result in *sumMillis @@ -2634,10 +2693,11 @@ QDateTimePrivate::ZoneState QDateTimePrivate::expressUtcAsLocal(qint64 utcMSecs) // dates might be right, and adjust by the number of days that was off: const qint64 jd = msecsToJulianDay(utcMSecs); const auto ymd = QGregorianCalendar::partsFromJulian(jd); - qint64 fakeJd, diffMillis, fakeUtc; - if (Q_UNLIKELY(!QGregorianCalendar::julianFromParts(systemTimeYearMatching(ymd.year), - ymd.month, ymd.day, &fakeJd) - || qMulOverflow(jd - fakeJd, std::integral_constant(), + qint64 diffMillis, fakeUtc; + const auto fakeJd = QGregorianCalendar::julianFromParts(systemTimeYearMatching(ymd.year), + ymd.month, ymd.day); + if (Q_UNLIKELY(!fakeJd + || qMulOverflow(jd - *fakeJd, std::integral_constant(), &diffMillis) || qSubOverflow(utcMSecs, diffMillis, &fakeUtc))) { return result; @@ -2660,11 +2720,11 @@ static auto millisToWithinRange(qint64 millis) qint64 shifted = 0; bool good = false; } result; - qint64 jd = msecsToJulianDay(millis), fakeJd; + qint64 jd = msecsToJulianDay(millis); auto ymd = QGregorianCalendar::partsFromJulian(jd); - result.good = QGregorianCalendar::julianFromParts(systemTimeYearMatching(ymd.year), - ymd.month, ymd.day, &fakeJd) - && !daysAndMillisOverflow(fakeJd - jd, millis, &result.shifted); + const auto fakeJd = QGregorianCalendar::julianFromParts(systemTimeYearMatching(ymd.year), + ymd.month, ymd.day); + result.good = fakeJd && !daysAndMillisOverflow(*fakeJd - jd, millis, &result.shifted); return result; } @@ -2782,7 +2842,7 @@ static inline bool specCanBeSmall(Qt::TimeSpec spec) static inline bool msecsCanBeSmall(qint64 msecs) { - if (!QDateTimeData::CanBeSmall) + if constexpr (!QDateTimeData::CanBeSmall) return false; ShortData sd; @@ -3053,12 +3113,12 @@ static QPair getDateTime(const QDateTimeData &d) { auto status = getStatus(d); const qint64 msecs = getMSecs(d); - const qint64 days = QRoundingDown::qDiv(msecs, MSECS_PER_DAY); + const auto dayMilli = QRoundingDown::qDivMod(msecs); return { status.testFlag(QDateTimePrivate::ValidDate) - ? QDate::fromJulianDay(JULIAN_DAY_FOR_EPOCH + days) + ? QDate::fromJulianDay(JULIAN_DAY_FOR_EPOCH + dayMilli.quotient) : QDate(), status.testFlag(QDateTimePrivate::ValidTime) - ? QTime::fromMSecsSinceStartOfDay(msecs - days * MSECS_PER_DAY) + ? QTime::fromMSecsSinceStartOfDay(dayMilli.remainder) : QTime() }; } @@ -3161,7 +3221,7 @@ inline bool QDateTime::Data::isShort() const // even if CanBeSmall = false, we have short data for a default-constructed // QDateTime object. But it's unlikely. - if (CanBeSmall) + if constexpr (CanBeSmall) return Q_LIKELY(b); return Q_UNLIKELY(b); } @@ -5567,7 +5627,11 @@ QDebug operator<<(QDebug dbg, QDate date) QDebugStateSaver saver(dbg); dbg.nospace() << "QDate("; if (date.isValid()) - dbg.nospace() << date.toString(Qt::ISODate); + // QTBUG-91070, ISODate only supports years in the range 0-9999 + if (int y = date.year(); y > 0 && y <= 9999) + dbg.nospace() << date.toString(Qt::ISODate); + else + dbg.nospace() << date.toString(Qt::TextDate); else dbg.nospace() << "Invalid"; dbg.nospace() << ')'; diff --git a/src/corelib/time/qdatetime.h b/src/corelib/time/qdatetime.h index 50760134..5f41fa47 100644 --- a/src/corelib/time/qdatetime.h +++ b/src/corelib/time/qdatetime.h @@ -281,12 +281,10 @@ class Q_CORE_EXPORT QDateTime }; union Data { - enum { - // To be of any use, we need at least 60 years around 1970, which - // is 1,893,456,000,000 ms. That requires 41 bits to store, plus - // the sign bit. With the status byte, the minimum size is 50 bits. - CanBeSmall = sizeof(ShortData) * 8 > 50 - }; + // To be of any use, we need at least 60 years around 1970, which + // is 1,893,456,000,000 ms. That requires 41 bits to store, plus + // the sign bit. With the status byte, the minimum size is 50 bits. + static constexpr bool CanBeSmall = sizeof(ShortData) * 8 > 50; Data() noexcept; Data(const QTimeZone &); diff --git a/src/corelib/time/qdatetime_p.h b/src/corelib/time/qdatetime_p.h index ab91ec33..bd4e1e33 100644 --- a/src/corelib/time/qdatetime_p.h +++ b/src/corelib/time/qdatetime_p.h @@ -122,6 +122,9 @@ constexpr qint64 MSECS_PER_HOUR = SECS_PER_HOUR * MSECS_PER_SEC; constexpr qint64 MSECS_PER_DAY = SECS_PER_DAY * MSECS_PER_SEC; constexpr qint64 JULIAN_DAY_FOR_EPOCH = 2440588; // result of QDate(1970, 1, 1).toJulianDay() + +constexpr qint64 JulianDayMax = Q_INT64_C( 784354017364); +constexpr qint64 JulianDayMin = Q_INT64_C(-784350574879); } } diff --git a/src/corelib/time/qdatetimeparser.cpp b/src/corelib/time/qdatetimeparser.cpp index 58abd645..64679810 100644 --- a/src/corelib/time/qdatetimeparser.cpp +++ b/src/corelib/time/qdatetimeparser.cpp @@ -3,7 +3,6 @@ #include "qplatformdefs.h" #include "private/qdatetimeparser_p.h" -#include "private/qstringiterator_p.h" #include "qdatastream.h" #include "qdatetime.h" @@ -12,6 +11,10 @@ #include "qset.h" #include "qtimezone.h" #include "qvarlengtharray.h" +#include "private/qlocale_p.h" + +#include "private/qstringiterator_p.h" +#include "private/qtenvironmentvariables_p.h" //#define QDATETIMEPARSER_DEBUG #if defined (QDATETIMEPARSER_DEBUG) && !defined(QT_NO_DEBUG_STREAM) @@ -369,9 +372,9 @@ static qsizetype digitCount(QStringView str) not escaped and removes the escaping on those that are escaped */ - static QString unquote(QStringView str) { + // ### Align unquoting format strings for both from/toString(), QTBUG-110669 const QLatin1Char quote('\''); const QLatin1Char slash('\\'); const QLatin1Char zero('0'); @@ -396,14 +399,10 @@ static QString unquote(QStringView str) static inline int countRepeat(QStringView str, int index, int maxCount) { str = str.sliced(index); - if (maxCount > str.size()) - maxCount = str.size(); + if (maxCount < str.size()) + str = str.first(maxCount); - const QChar ch(str[0]); - int count = 1; - while (count < maxCount && str[count] == ch) - ++count; - return count; + return qt_repeatCount(str); } static inline void appendSeparator(QStringList *list, QStringView string, @@ -907,7 +906,7 @@ QDateTimeParser::parseSection(const QDateTime ¤tValue, int sectionIndex, i const int absMax = absoluteMax(sectionIndex); const int absMin = absoluteMin(sectionIndex); - int last = -1; + int lastVal = -1; for (; digitsStr.size(); digitsStr.chop(1)) { bool ok = false; @@ -923,49 +922,49 @@ QDateTimeParser::parseSection(const QDateTime ¤tValue, int sectionIndex, i } QDTPDEBUG << digitsStr << value << digitsStr.size(); - last = value; + lastVal = value; used += digitsStr.size(); break; } - if (last == -1) { + if (lastVal == -1) { if (!checkSeparator()) { QDTPDEBUG << "invalid because" << sectionTextRef << "can't become a uint" - << last; + << lastVal; } } else { if (negate) - last = -last; + lastVal = -lastVal; const FieldInfo fi = fieldInfo(sectionIndex); const bool unfilled = used - negativeYearOffset < sectionmaxsize; if (unfilled && fi & Fraction) { // typing 2 in a zzz field should be .200, not .002 for (int i = used; i < sectionmaxsize; ++i) - last *= 10; + lastVal *= 10; } // Even those *= 10s can't take last above absMax: - Q_ASSERT(negate ? last >= absMin : last <= absMax); - if (negate ? last > absMax : last < absMin) { + Q_ASSERT(negate ? lastVal >= absMin : lastVal <= absMax); + if (negate ? lastVal > absMax : lastVal < absMin) { if (unfilled) { - result = ParsedSection(Intermediate, last, used); + result = ParsedSection(Intermediate, lastVal, used); } else if (negate) { - QDTPDEBUG << "invalid because" << last << "is greater than absoluteMax" + QDTPDEBUG << "invalid because" << lastVal << "is greater than absoluteMax" << absMax; } else { - QDTPDEBUG << "invalid because" << last << "is less than absoluteMin" + QDTPDEBUG << "invalid because" << lastVal << "is less than absoluteMin" << absMin; } } else if (unfilled && (fi & (FixedWidth | Numeric)) == (FixedWidth | Numeric)) { if (skipToNextSection(sectionIndex, defaultValue, digitsStr)) { const int missingZeroes = sectionmaxsize - digitsStr.size(); - result = ParsedSection(Acceptable, last, sectionmaxsize, missingZeroes); + result = ParsedSection(Acceptable, lastVal, sectionmaxsize, missingZeroes); m_text.insert(offset, QString(missingZeroes, u'0')); ++(const_cast(this)->sectionNodes[sectionIndex].zeroesAdded); } else { - result = ParsedSection(Intermediate, last, used);; + result = ParsedSection(Intermediate, lastVal, used);; } } else { - result = ParsedSection(Acceptable, last, used); + result = ParsedSection(Acceptable, lastVal, used); } } } @@ -1181,6 +1180,24 @@ static QTime actualTime(QDateTimeParser::Sections known, return actual; } +/* + \internal +*/ +static int startsWithLocalTimeZone(QStringView name, const QDateTime &when) +{ + // On MS-Win, at least when system zone is UTC, the tzname[]s may be empty. + for (int i = 0; i < 2; ++i) { + const QString zone(qTzName(i)); + if (!zone.isEmpty() && name.startsWith(zone)) + return zone.size(); + } + // Mimic what QLocale::toString() would have used, to ensure round-trips work: + const QString local = QDateTime(when.date(), when.time()).timeZoneAbbreviation(); + if (name.startsWith(local)) + return local.size(); + return 0; +} + /*! \internal */ @@ -1914,20 +1931,21 @@ QDateTimeParser::AmPmFinder QDateTimeParser::findAmPm(QString &str, int sectionI bool broken[2] = {false, false}; for (int i=0; i QT_BEGIN_NAMESPACE using namespace QRoundingDown; +// Verification that QRoundingDown::qDivMod() works correctly: +static_assert(qDivMod<2>(-86400).quotient == -43200); +static_assert(qDivMod<2>(-86400).remainder == 0); +static_assert(qDivMod<86400>(-86400).quotient == -1); +static_assert(qDivMod<86400>(-86400).remainder == 0); +static_assert(qDivMod<86400>(-86401).quotient == -2); +static_assert(qDivMod<86400>(-86401).remainder == 86399); +static_assert(qDivMod<86400>(-100000).quotient == -2); +static_assert(qDivMod<86400>(-100000).remainder == 72800); +static_assert(qDivMod<86400>(-172799).quotient == -2); +static_assert(qDivMod<86400>(-172799).remainder == 1); +static_assert(qDivMod<86400>(-172800).quotient == -2); +static_assert(qDivMod<86400>(-172800).remainder == 0); + +// Uncomment to verify error on bad denominator is clear and intelligible: +// static_assert(qDivMod<1>(17).remainder == 0); +// static_assert(qDivMod<0>(17).remainder == 0); +// static_assert(qDivMod::max()>(17).remainder == 0); + /*! \since 5.14 @@ -73,41 +93,27 @@ bool QGregorianCalendar::validParts(int year, int month, int day) int QGregorianCalendar::weekDayOfJulian(qint64 jd) { - return qMod(jd, 7) + 1; + return int(qMod<7>(jd) + 1); } bool QGregorianCalendar::dateToJulianDay(int year, int month, int day, qint64 *jd) const { - return julianFromParts(year, month, day, jd); + const auto maybe = julianFromParts(year, month, day); + if (maybe) + *jd = *maybe; + return bool(maybe); } -bool QGregorianCalendar::julianFromParts(int year, int month, int day, qint64 *jd) +QCalendar::YearMonthDay QGregorianCalendar::julianDayToDate(qint64 jd) const { - Q_ASSERT(jd); - if (!validParts(year, month, day)) - return false; - - if (year < 0) - ++year; - - /* - * Math from The Calendar FAQ at http://www.tondering.dk/claus/cal/julperiod.php - * This formula is correct for all julian days, when using mathematical integer - * division (round to negative infinity), not c++11 integer division (round to zero) - */ - int a = month < 3 ? 1 : 0; - qint64 y = qint64(year) + 4800 - a; - int m = month + 12 * a - 3; - *jd = day + qDiv(153 * m + 2, 5) - 32045 - + 365 * y + qDiv(y, 4) - qDiv(y, 100) + qDiv(y, 400); - return true; + return partsFromJulian(jd); } int QGregorianCalendar::yearStartWeekDay(int year) { // Equivalent to weekDayOfJulian(julianForParts({year, 1, 1}) const int y = year - (year < 0 ? 800 : 801); - return qMod(y + qDiv(y, 4) - qDiv(y, 100) + qDiv(y, 400), 7) + 1; + return qMod<7>(y + qDiv<4>(y) - qDiv<100>(y) + qDiv<400>(y)) + 1; } int QGregorianCalendar::yearSharingWeekDays(QDate date) @@ -156,34 +162,52 @@ int QGregorianCalendar::yearSharingWeekDays(QDate date) return res; } -QCalendar::YearMonthDay QGregorianCalendar::julianDayToDate(qint64 jd) const +/* + * Math from The Calendar FAQ at http://www.tondering.dk/claus/cal/julperiod.php + * This formula is correct for all julian days, when using mathematical integer + * division (round to negative infinity), not c++11 integer division (round to zero). + * + * The source given uses 4801 BCE as base date; the following adjusts that by + * 4800 years to simplify part of the arithmetic (and match more closely what we + * do for Milankovic). + */ + +using namespace QRomanCalendrical; +// End a Gregorian four-century cycle on 1 BC's leap day: +constexpr qint64 BaseJd = LeapDayGregorian1Bce; +// Every four centures there are 97 leap years: +constexpr unsigned FourCenturies = 400 * 365 + 97; + +std::optional QGregorianCalendar::julianFromParts(int year, int month, int day) { - return partsFromJulian(jd); + if (!validParts(year, month, day)) + return std::nullopt; + + const auto yearDays = yearMonthToYearDays(year, month); + const qint64 y = yearDays.year; + const qint64 fromYear = 365 * y + qDiv<4>(y) - qDiv<100>(y) + qDiv<400>(y); + return fromYear + yearDays.days + day + BaseJd; } QCalendar::YearMonthDay QGregorianCalendar::partsFromJulian(qint64 jd) { - /* - * Math from The Calendar FAQ at http://www.tondering.dk/claus/cal/julperiod.php - * This formula is correct for all julian days, when using mathematical integer - * division (round to negative infinity), not c++11 integer division (round to zero) - */ - qint64 a = jd + 32044; - qint64 b = qDiv(4 * a + 3, 146097); - int c = a - qDiv(146097 * b, 4); + const qint64 dayNumber = jd - BaseJd; + const qint64 century = qDiv(4 * dayNumber - 1); + const int dayInCentury = dayNumber - qDiv<4>(FourCenturies * century); - int d = qDiv(4 * c + 3, 1461); - int e = c - qDiv(1461 * d, 4); - int m = qDiv(5 * e + 2, 153); + const int yearInCentury = qDiv(4 * dayInCentury - 1); + const int dayInYear = dayInCentury - qDiv<4>(FourYears * yearInCentury); + const int m = qDiv(5 * dayInYear - 3); + Q_ASSERT(m < 12 && m >= 0); + // That m is a month adjusted to March = 0, with Jan = 10, Feb = 11 in the previous year. + const int yearOffset = m < 10 ? 0 : 1; - int y = 100 * b + d - 4800 + qDiv(m, 10); + const int y = 100 * century + yearInCentury + yearOffset; + const int month = m + 3 - 12 * yearOffset; + const int day = dayInYear - qDiv<5>(FiveMonths * m + 2); // Adjust for no year 0 - int year = y > 0 ? y : y - 1; - int month = m + 3 - 12 * qDiv(m, 10); - int day = e - qDiv(153 * m + 2, 5) + 1; - - return QCalendar::YearMonthDay(year, month, day); + return QCalendar::YearMonthDay(y > 0 ? y : y - 1, month, day); } QT_END_NAMESPACE diff --git a/src/corelib/time/qgregoriancalendar_p.h b/src/corelib/time/qgregoriancalendar_p.h index dc409375..ed950ecf 100644 --- a/src/corelib/time/qgregoriancalendar_p.h +++ b/src/corelib/time/qgregoriancalendar_p.h @@ -17,6 +17,8 @@ #include "qromancalendar_p.h" +#include + QT_BEGIN_NAMESPACE class Q_CORE_EXPORT QGregorianCalendar : public QRomanCalendar @@ -45,7 +47,7 @@ public: static int monthLength(int month, int year); static bool validParts(int year, int month, int day); static QCalendar::YearMonthDay partsFromJulian(qint64 jd); - static bool julianFromParts(int year, int month, int day, qint64 *jd); + static std::optional julianFromParts(int year, int month, int day); // Used internally: static int yearStartWeekDay(int year); static int yearSharingWeekDays(QDate date); diff --git a/src/corelib/time/qhijricalendar_data_p.h b/src/corelib/time/qhijricalendar_data_p.h index c3c0b681..70541ce3 100644 --- a/src/corelib/time/qhijricalendar_data_p.h +++ b/src/corelib/time/qhijricalendar_data_p.h @@ -25,7 +25,7 @@ namespace QtPrivate::Hijri { // GENERATED PART STARTS HERE /* - This part of the file was generated on 2023-08-03 from the + This part of the file was generated on 2023-07-27 from the Common Locale Data Repository v43 http://www.unicode.org/cldr/ diff --git a/src/corelib/time/qislamiccivilcalendar.cpp b/src/corelib/time/qislamiccivilcalendar.cpp index 23854410..ac1f97cb 100644 --- a/src/corelib/time/qislamiccivilcalendar.cpp +++ b/src/corelib/time/qislamiccivilcalendar.cpp @@ -4,7 +4,6 @@ #include "qglobal.h" #include "qislamiccivilcalendar_p.h" #include "qcalendarmath_p.h" -#include QT_BEGIN_NAMESPACE @@ -58,30 +57,37 @@ bool QIslamicCivilCalendar::isLeapYear(int year) const return false; if (year < 0) ++year; - return qMod(year * 11 + 14, 30) < 11; + return qMod<30>(year * 11 + 14) < 11; } +// First day of first year (Gregorian 622 CE July 19th) is the base date here: +constexpr qint64 EpochJd = 1948440; +// Each 30 years has 11 leap years of 355 days and 19 ordinary years of 354: +constexpr unsigned ThirtyYears = 11 * 355 + 19 * 354; +// The first eleven months of the year alternate 30, 29, ..., 29, 30 days in length. +constexpr unsigned ElevenMonths = 6 * 30 + 5 * 29; + bool QIslamicCivilCalendar::dateToJulianDay(int year, int month, int day, qint64 *jd) const { Q_ASSERT(jd); if (!isDateValid(year, month, day)) return false; - if (year <= 0) - ++year; - *jd = qDiv(10631 * year - 10617, 30) - + qDiv(325 * month - 320, 11) - + day + 1948439; + + *jd = qDiv<30>(qint64(ThirtyYears) * (year > 0 ? year - 1 : year) + 14) + + qDiv<11>(ElevenMonths * (month - 1) + 5) + + day + EpochJd - 1; return true; } QCalendar::YearMonthDay QIslamicCivilCalendar::julianDayToDate(qint64 jd) const { - const qint64 epoch = 1948440; - const int32_t k2 = 30 * (jd - epoch) + 15; - const int32_t k1 = 11 * qDiv(qMod(k2, 10631), 30) + 5; - int y = qDiv(k2, 10631) + 1; - const int month = qDiv(k1, 325) + 1; - const int day = qDiv(qMod(k1, 325), 11) + 1; + const auto year30Day = qDivMod(30 * (jd - EpochJd) + 15); + // Its remainder changes by 30 per day, except roughly yearly. + const auto month11Day = qDivMod(11 * qDiv<30>(year30Day.remainder) + 5); + // Its remainder changes by 11 per day except roughly monthly. + const int month = month11Day.quotient + 1; + const int day = qDiv<11>(month11Day.remainder) + 1; + const int y = year30Day.quotient + 1; return QCalendar::YearMonthDay(y > 0 ? y : y - 1, month, day); } diff --git a/src/corelib/time/qjalalicalendar.cpp b/src/corelib/time/qjalalicalendar.cpp index b796b651..fcbdf143 100644 --- a/src/corelib/time/qjalalicalendar.cpp +++ b/src/corelib/time/qjalalicalendar.cpp @@ -13,16 +13,17 @@ using namespace QRoundingDown; // Constants -static const qint64 cycleDays = 1029983; -static const int cycleYears = 2820; -static const double yearLength = 365.24219858156028368; // 365 + leapRatio; -static const qint64 jalaliEpoch = 2121446; // 475/01/01 AP, start of 2820 cycle +constexpr qint64 cycleDays = 1029983; +constexpr int cycleYears = 2820; +constexpr double yearLength = 365.24219858156028368; // 365 + 683 / 2820. +constexpr qint64 jalaliEpoch = 2121446; // 475/01/01 AP, start of 2820 cycle +// This appears to be based on Ahmad Birashk's algorithm. // Calendar implementation static inline int cycle(qint64 jdn) { - return qDiv(jdn - jalaliEpoch, cycleDays); + return qDiv(jdn - jalaliEpoch); } qint64 cycleStart(int cycleNo) @@ -75,7 +76,7 @@ qint64 firstDayOfYear(int year, int cycleNo) Source: \l {https://en.wikipedia.org/wiki/Solar_Hijri_calendar}{Wikipedia page on Solar Hijri Calendar} - */ +*/ QString QJalaliCalendar::name() const { @@ -96,7 +97,7 @@ bool QJalaliCalendar::isLeapYear(int year) const return false; if (year < 0) year++; - return qMod((year + 2346) * 683, 2820) < 683; + return qMod<2820>((year + 2346) * 683) < 683; } bool QJalaliCalendar::isLunar() const @@ -121,7 +122,7 @@ bool QJalaliCalendar::dateToJulianDay(int year, int month, int day, qint64 *jd) return false; const int y = year - (year < 0 ? 474 : 475); - const int c = qDiv(y, cycleYears); + const int c = qDiv(y); const int yearInCycle = y - c * cycleYears; int dayInYear = day; for (int i = 1; i < month; ++i) diff --git a/src/corelib/time/qjalalicalendar_data_p.h b/src/corelib/time/qjalalicalendar_data_p.h index e8cdb85e..1b07056d 100644 --- a/src/corelib/time/qjalalicalendar_data_p.h +++ b/src/corelib/time/qjalalicalendar_data_p.h @@ -25,7 +25,7 @@ namespace QtPrivate::Jalali { // GENERATED PART STARTS HERE /* - This part of the file was generated on 2023-08-03 from the + This part of the file was generated on 2023-07-27 from the Common Locale Data Repository v43 http://www.unicode.org/cldr/ diff --git a/src/corelib/time/qjuliancalendar.cpp b/src/corelib/time/qjuliancalendar.cpp index 627c92c4..1439ae3e 100644 --- a/src/corelib/time/qjuliancalendar.cpp +++ b/src/corelib/time/qjuliancalendar.cpp @@ -5,8 +5,7 @@ #include "qjuliancalendar_p.h" #include "qromancalendar_data_p.h" #include "qcalendarmath_p.h" -#include -#include + #include QT_BEGIN_NAMESPACE @@ -54,36 +53,32 @@ bool QJulianCalendar::isLeapYear(int year) const if (year == QCalendar::Unspecified || !year) return false; - return qMod(year < 0 ? year + 1 : year, 4) == 0; + return qMod<4>(year < 0 ? year + 1 : year) == 0; } -// Julian Day 0 was January the first in the proleptic Julian calendar's 4713 BC +// Julian Day 0 was January the first in the proleptic Julian calendar's 4713 BC. +using namespace QRomanCalendrical; +// End a Julian four-year cycle on 1 BC's leap day (Gregorian Feb 27th): +constexpr qint64 JulianBaseJd = LeapDayGregorian1Bce - 2; bool QJulianCalendar::dateToJulianDay(int year, int month, int day, qint64 *jd) const { Q_ASSERT(jd); if (!isDateValid(year, month, day)) return false; - if (year < 0) - ++year; - const qint64 c0 = month < 3 ? -1 : 0; - const qint64 j1 = qDiv(1461 * (year + c0), 4); - const qint64 j2 = qDiv(153 * month - 1836 * c0 - 457, 5); - *jd = j1 + j2 + day + 1721117; + + const auto yearDays = yearMonthToYearDays(year, month); + *jd = qDiv<4>(FourYears * yearDays.year) + yearDays.days + day + JulianBaseJd; return true; } QCalendar::YearMonthDay QJulianCalendar::julianDayToDate(qint64 jd) const { - const qint64 y2 = jd - 1721118; - const qint64 k2 = 4 * y2 + 3; - const qint64 k1 = 5 * qDiv(qMod(k2, 1461), 4) + 2; - const qint64 x1 = qDiv(k1, 153); - const qint64 c0 = qDiv(x1 + 2, 12); - const int y = qint16(qDiv(k2, 1461) + c0); - const int month = quint8(x1 - 12 * c0 + 3); - const int day = qDiv(qMod(k1, 153), 5) + 1; - return QCalendar::YearMonthDay(y > 0 ? y : y - 1, month, day); + const auto year4Day = qDivMod(4 * (jd - JulianBaseJd) - 1); + // Its remainder changes by 4 per day, except at roughly yearly quotient steps. + const auto ymd = dayInYearToYmd(qDiv<4>(year4Day.remainder)); + const int y = year4Day.quotient + ymd.year; + return QCalendar::YearMonthDay(y > 0 ? y : y - 1, ymd.month, ymd.day); } QT_END_NAMESPACE diff --git a/src/corelib/time/qlocaltime.cpp b/src/corelib/time/qlocaltime.cpp index 5709fefa..e059b30b 100644 --- a/src/corelib/time/qlocaltime.cpp +++ b/src/corelib/time/qlocaltime.cpp @@ -10,6 +10,7 @@ #endif #include "private/qgregoriancalendar_p.h" #include "private/qnumeric_p.h" +#include "private/qtenvironmentvariables_p.h" #if QT_CONFIG(timezone) #include "private/qtimezoneprivate_p.h" #endif @@ -164,132 +165,12 @@ struct tm timeToTm(qint64 localDay, int secs, QDateTimePrivate::DaylightStatus d return local; } -bool qtLocalTime(time_t utc, struct tm *local) +inline std::optional tmToJd(const struct tm &date) { - // This should really be done under the environmentMutex wrapper qglobal.cpp - // uses in qTzSet() and friends. However, the only sane way to do that would - // be to move this whole function there (and replace its qTzSet() with a - // naked tzset(), since it'd already be mutex-protected). -#if defined(Q_OS_WIN) - // The doc of localtime_s() says that localtime_s() corrects for the same - // things _tzset() sets the globals for, but doesn't explicitly say that it - // calls _tzset(), and QTBUG-109974 reveals the need for a _tzset() call. - qTzSet(); - return !localtime_s(local, &utc); -#elif QT_CONFIG(thread) && defined(_POSIX_THREAD_SAFE_FUNCTIONS) - // Use the reentrant version of localtime() where available, as it is - // thread-safe and doesn't use a shared static data area. - // As localtime() is specified to work as if it called tzset(), but - // localtime_r() does not have this constraint, make an explicit call. - // The explicit call should also request a re-parse of timezone info. - qTzSet(); - if (tm *res = localtime_r(&utc, local)) { - Q_ASSERT(res == local); - return true; - } - return false; -#else - // POSIX mandates that localtime() behaves as if it called tzset(). - // Returns shared static data which may be overwritten at any time - // So copy the result asap: - if (tm *res = localtime(&utc)) { - *local = *res; - return true; - } - return false; -#endif + return QGregorianCalendar::julianFromParts(qYearFromTmYear(date.tm_year), + date.tm_mon + 1, date.tm_mday); } -// Returns the tzname, assume tzset has been called already -QString qt_tzname(QDateTimePrivate::DaylightStatus daylightStatus) -{ - int isDst = (daylightStatus == QDateTimePrivate::DaylightTime) ? 1 : 0; -#if defined(Q_CC_MSVC) - size_t s = 0; - char name[512]; - if (_get_tzname(&s, name, 512, isDst)) - return QString(); - return QString::fromLocal8Bit(name); -#else - return QString::fromLocal8Bit(tzname[isDst]); -#endif // Q_OS_WIN -} - -} // namespace - -#if QT_CONFIG(datetimeparser) -/* - \internal - Implemented here to share qt_tzname() -*/ -int QDateTimeParser::startsWithLocalTimeZone(QStringView name, const QDateTime &when) -{ - QDateTimePrivate::DaylightStatus zones[2] = { - QDateTimePrivate::StandardTime, - QDateTimePrivate::DaylightTime - }; - // On MS-Win, at least when system zone is UTC, the tzname[]s may be empty. - for (const auto z : zones) { - QString zone(qt_tzname(z)); - if (!zone.isEmpty() && name.startsWith(zone)) - return zone.size(); - } - // Mimic what QLocale::toString() would have used, to ensure round-trips work: - const QString local = QDateTime(when.date(), when.time()).timeZoneAbbreviation(); - if (name.startsWith(local)) - return local.size(); - return 0; -} -#endif // datetimeparser - -namespace QLocalTime { - -#ifndef QT_BOOTSTRAPPED -// Even if local time is currently in DST, this returns the standard time offset -// (in seconds) nominally in effect at present: -int getCurrentStandardUtcOffset() -{ -#ifdef Q_OS_WIN - TIME_ZONE_INFORMATION tzInfo; - GetTimeZoneInformation(&tzInfo); - return -tzInfo.Bias * SECS_PER_MIN; -#else - qTzSet(); - const time_t curr = time(nullptr); - /* Set t to the UTC representation of curr; the time whose local standard - time representation coincides with that differs from curr by local time's - standard offset. Note that gmtime() leaves the tm_isdst flag set to 0, - so mktime() will, even if local time is currently using DST, return the - time since epoch at which local standard time would have the same - representation as UTC's representation of curr. The fact that mktime() - also flips tm_isdst and updates the time fields to the DST-equivalent - time needn't concern us here; all that matters is that it returns the - time after epoch at which standard time's representation would have - matched UTC's, had it been in effect. - */ -# if defined(_POSIX_THREAD_SAFE_FUNCTIONS) - struct tm t; - if (gmtime_r(&curr, &t)) - return curr - qMkTime(&t); -# else - if (struct tm *tp = gmtime(&curr)) { - struct tm t = *tp; // Copy it quick, hopefully before it can get stomped - return curr - qMkTime(&t); - } -# endif - // We can't tell, presume UTC. - return 0; -#endif // Platform choice -} - -// This is local time's offset (in seconds), at the specified time, including -// any DST part. -int getUtcOffset(qint64 atMSecsSinceEpoch) -{ - return QDateTimePrivate::expressUtcAsLocal(atMSecsSinceEpoch).offset; -} -#endif // QT_BOOTSTRAPPED - #define IC(N) std::integral_constant() // True if combining day and seconds overflows qint64; otherwise, sets *epochSeconds @@ -308,29 +189,96 @@ inline bool secondsAndMillisOverflow(qint64 epochSeconds, qint64 millis, qint64 #undef IC +} // namespace + +namespace QLocalTime { + +#ifndef QT_BOOTSTRAPPED +// Even if local time is currently in DST, this returns the standard time offset +// (in seconds) nominally in effect at present: +int getCurrentStandardUtcOffset() +{ +#ifdef Q_OS_WIN + TIME_ZONE_INFORMATION tzInfo; + if (GetTimeZoneInformation(&tzInfo) != TIME_ZONE_ID_INVALID) { + int bias = tzInfo.Bias; // In minutes. + // StandardBias is usually zero, but include it if given: + if (tzInfo.StandardDate.wMonth) // Zero month means ignore StandardBias. + bias += tzInfo.StandardBias; + // MS's bias is +ve in the USA, so minutes *behind* UTC - we want seconds *ahead*: + return -bias * SECS_PER_MIN; + } +#else + qTzSet(); + const time_t curr = time(nullptr); + if (curr != -1) { + /* Set t to the UTC representation of curr; the time whose local + standard time representation coincides with that differs from curr by + local time's standard offset. Note that gmtime() leaves the tm_isdst + flag set to 0, so mktime() will, even if local time is currently + using DST, return the time since epoch at which local standard time + would have the same representation as UTC's representation of + curr. The fact that mktime() also flips tm_isdst and updates the time + fields to the DST-equivalent time needn't concern us here; all that + matters is that it returns the time after epoch at which standard + time's representation would have matched UTC's, had it been in + effect. + */ +# if defined(_POSIX_THREAD_SAFE_FUNCTIONS) + struct tm t; + if (gmtime_r(&curr, &t)) { + time_t mkt = qMkTime(&t); + int offset = int(curr - mkt); + Q_ASSERT(std::abs(offset) <= SECS_PER_DAY); + return offset; + } +# else + if (struct tm *tp = gmtime(&curr)) { + struct tm t = *tp; // Copy it quick, hopefully before it can get stomped + time_t mkt = qMkTime(&t); + int offset = int(curr - mkt); + Q_ASSERT(std::abs(offset) <= SECS_PER_DAY); + return offset; + } +# endif + } // else, presumably: errno == EOVERFLOW +#endif // Platform choice + qDebug("Unable to determine current standard time offset from UTC"); + // We can't tell, presume UTC. + return 0; +} + +// This is local time's offset (in seconds), at the specified time, including +// any DST part. +int getUtcOffset(qint64 atMSecsSinceEpoch) +{ + return QDateTimePrivate::expressUtcAsLocal(atMSecsSinceEpoch).offset; +} +#endif // QT_BOOTSTRAPPED + // Calls the platform variant of localtime() for the given utcMillis, and // returns the local milliseconds, offset from UTC and DST status. QDateTimePrivate::ZoneState utcToLocal(qint64 utcMillis) { - const time_t epochSeconds = QRoundingDown::qDiv(utcMillis, MSECS_PER_SEC); - const int msec = utcMillis - epochSeconds * MSECS_PER_SEC; + const auto epoch = QRoundingDown::qDivMod(utcMillis); + const time_t epochSeconds = epoch.quotient; + const int msec = epoch.remainder; Q_ASSERT(msec >= 0 && msec < MSECS_PER_SEC); if (qint64(epochSeconds) * MSECS_PER_SEC + msec != utcMillis) // time_t range too narrow return {utcMillis}; tm local; - if (!qtLocalTime(epochSeconds, &local)) + if (!qLocalTime(epochSeconds, &local)) return {utcMillis}; - qint64 jd; - if (Q_UNLIKELY(!QGregorianCalendar::julianFromParts(qYearFromTmYear(local.tm_year), - local.tm_mon + 1, local.tm_mday, &jd))) { + auto jd = tmToJd(local); + if (Q_UNLIKELY(!jd)) return {utcMillis}; - } + const qint64 daySeconds = tmSecsWithinDay(local); Q_ASSERT(0 <= daySeconds && daySeconds < SECS_PER_DAY); qint64 localSeconds, localMillis; - if (Q_UNLIKELY(daysAndSecondsOverflow(jd, daySeconds, &localSeconds) + if (Q_UNLIKELY(daysAndSecondsOverflow(*jd, daySeconds, &localSeconds) || secondsAndMillisOverflow(localSeconds, qint64(msec), &localMillis))) { return {utcMillis}; } @@ -341,26 +289,25 @@ QDateTimePrivate::ZoneState utcToLocal(qint64 utcMillis) QString localTimeAbbbreviationAt(qint64 local, QDateTimePrivate::DaylightStatus dst) { - const qint64 localDays = QRoundingDown::qDiv(local, MSECS_PER_DAY); - qint64 millis = local - localDays * MSECS_PER_DAY; + const auto localDayMilli = QRoundingDown::qDivMod(local); + qint64 millis = localDayMilli.remainder; Q_ASSERT(0 <= millis && millis < MSECS_PER_DAY); // Definition of QRD::qDiv. - struct tm tmLocal = timeToTm(localDays, int(millis / MSECS_PER_SEC), dst); + struct tm tmLocal = timeToTm(localDayMilli.quotient, int(millis / MSECS_PER_SEC), dst); time_t utcSecs; if (!callMkTime(&tmLocal, &utcSecs)) return {}; - return qt_tzname(tmLocal.tm_isdst > 0 ? QDateTimePrivate::DaylightTime - : QDateTimePrivate::StandardTime); + return qTzName(tmLocal.tm_isdst > 0 ? 1 : 0); } QDateTimePrivate::ZoneState mapLocalTime(qint64 local, QDateTimePrivate::DaylightStatus dst) { qint64 localSecs = local / MSECS_PER_SEC; qint64 millis = local - localSecs * MSECS_PER_SEC; // 0 or with same sign as local - const qint64 localDays = QRoundingDown::qDiv(localSecs, SECS_PER_DAY); - qint64 daySecs = localSecs - localDays * SECS_PER_DAY; + const auto localDaySec = QRoundingDown::qDivMod(localSecs); + qint64 daySecs = localDaySec.remainder; Q_ASSERT(0 <= daySecs && daySecs < SECS_PER_DAY); // Definition of QRD::qDiv. - struct tm tmLocal = timeToTm(localDays, daySecs, dst); + struct tm tmLocal = timeToTm(localDaySec.quotient, daySecs, dst); time_t utcSecs; if (!callMkTime(&tmLocal, &utcSecs)) return {local}; @@ -368,21 +315,19 @@ QDateTimePrivate::ZoneState mapLocalTime(qint64 local, QDateTimePrivate::Dayligh // TODO: for glibc, we could use tmLocal.tm_gmtoff // That would give us offset directly, hence localSecs = offset + utcSecs // Provisional offset, until we have a revised localSeconds: - int offset = QRoundingDown::qDiv(local, MSECS_PER_SEC) - utcSecs; + int offset = localSecs - utcSecs; dst = tmLocal.tm_isdst > 0 ? QDateTimePrivate::DaylightTime : QDateTimePrivate::StandardTime; - qint64 jd; - if (Q_UNLIKELY(!QGregorianCalendar::julianFromParts( - qYearFromTmYear(tmLocal.tm_year), tmLocal.tm_mon + 1, tmLocal.tm_mday, - &jd))) { + auto jd = tmToJd(tmLocal); + if (Q_UNLIKELY(!jd)) return {local, offset, dst, false}; - } + daySecs = tmSecsWithinDay(tmLocal); Q_ASSERT(0 <= daySecs && daySecs < SECS_PER_DAY); - if (daySecs > 0 && jd < JULIAN_DAY_FOR_EPOCH) { - ++jd; + if (daySecs > 0 && *jd < JULIAN_DAY_FOR_EPOCH) { + jd = *jd + 1; daySecs -= SECS_PER_DAY; } - if (Q_UNLIKELY(daysAndSecondsOverflow(jd, daySecs, &localSecs))) + if (Q_UNLIKELY(daysAndSecondsOverflow(*jd, daySecs, &localSecs))) return {local, offset, dst, false}; offset = localSecs - utcSecs; @@ -427,9 +372,7 @@ QDateTimePrivate::ZoneState mapLocalTime(qint64 local, QDateTimePrivate::Dayligh SystemMillisRange computeSystemMillisRange() { // Assert this here, as this is called just once, in a static initialization. - [[maybe_unused]] qint64 epochJd; - Q_ASSERT(QGregorianCalendar::julianFromParts(1970, 1, 1, &epochJd) - && epochJd == JULIAN_DAY_FOR_EPOCH); + Q_ASSERT(QGregorianCalendar::julianFromParts(1970, 1, 1) == JULIAN_DAY_FOR_EPOCH); constexpr qint64 TIME_T_MAX = std::numeric_limits::max(); using Bounds = std::numeric_limits; diff --git a/src/corelib/time/qmilankoviccalendar.cpp b/src/corelib/time/qmilankoviccalendar.cpp index 6c72787e..a3ffa2a3 100644 --- a/src/corelib/time/qmilankoviccalendar.cpp +++ b/src/corelib/time/qmilankoviccalendar.cpp @@ -4,8 +4,7 @@ #include "qglobal.h" #include "qmilankoviccalendar_p.h" #include "qcalendarmath_p.h" -#include -#include + #include QT_BEGIN_NAMESPACE @@ -53,48 +52,48 @@ bool QMilankovicCalendar::isLeapYear(int year) const return false; if (year <= 0) ++year; - if (qMod(year, 4)) + if (qMod<4>(year)) return false; - if (qMod(year, 100) == 0) { - const qint16 century = qMod(qDiv(year, 100), 9); + const auto yeardm = qDivMod<100>(year); + if (yeardm.remainder == 0) { + const qint16 century = qMod<9>(yeardm.quotient); if (century != 2 && century != 6) return false; } return true; } +using namespace QRomanCalendrical; +// End a Milankovic nine-century cycle on 1 BC, Feb 28 (Gregorian Feb 29): +constexpr qint64 MilankovicBaseJd = LeapDayGregorian1Bce; +// Leap years every 4 years, except for 7 turn-of-century years per nine centuries: +constexpr unsigned NineCenturies = 365 * 900 + 900 / 4 - 7; +// When the turn-of-century is a leap year, the century has 25 leap years in it: +constexpr unsigned LeapCentury = 365 * 100 + 25; + bool QMilankovicCalendar::dateToJulianDay(int year, int month, int day, qint64 *jd) const { Q_ASSERT(jd); if (!isDateValid(year, month, day)) return false; - if (year <= 0) - ++year; - const qint16 c0 = month < 3 ? -1 : 0; - const qint16 x1 = month - 12 * c0 - 3; - const qint16 x4 = year + c0; - const qint16 x3 = qDiv(x4, 100); - const qint16 x2 = qMod(x4, 100); - *jd = qDiv(328718 * x3 + 6, 9) - + qDiv(36525 * x2 , 100) - + qDiv(153 * x1 + 2 , 5) - + day + 1721119; + + const auto yearDays = yearMonthToYearDays(year, month); + const auto centuryYear = qDivMod<100>(yearDays.year); + const qint64 fromYear = qDiv<9>(NineCenturies * centuryYear.quotient + 6) + + qDiv<100>(LeapCentury * centuryYear.remainder); + *jd = fromYear + yearDays.days + day + MilankovicBaseJd; return true; } QCalendar::YearMonthDay QMilankovicCalendar::julianDayToDate(qint64 jd) const { - const qint64 k3 = 9 * (jd - 1721120) + 2; - const qint64 x3 = qDiv(k3, 328718); - const qint64 k2 = 100 * qDiv(qMod(k3, 328718), 9) + 99; - const qint64 k1 = qDiv(qMod(k2, 36525), 100) * 5 + 2; - const qint64 x2 = qDiv(k2, 36525); - const qint64 x1 = qDiv(5 * qDiv(qMod(k2, 36525), 100) + 2, 153); - const qint64 c0 = qDiv(x1 + 2, 12); - const int y = 100 * x3 + x2 + c0; - const int month = x1 - 12 * c0 + 3; - const int day = qDiv(qMod(k1, 153), 5) + 1; - return QCalendar::YearMonthDay(y > 0 ? y : y - 1, month, day); + const auto century9Day = qDivMod(9 * (jd - MilankovicBaseJd) - 7); + // Its remainder changes by 9 per day, except roughly once per century. + const auto year100Day = qDivMod(100 * qDiv<9>(century9Day.remainder) + 99); + // Its remainder changes by 100 per day, except roughly once per year. + const auto ymd = dayInYearToYmd(qDiv<100>(year100Day.remainder)); + const int y = 100 * century9Day.quotient + year100Day.quotient + ymd.year; + return QCalendar::YearMonthDay(y > 0 ? y : y - 1, ymd.month, ymd.day); } QT_END_NAMESPACE diff --git a/src/corelib/time/qromancalendar_data_p.h b/src/corelib/time/qromancalendar_data_p.h index 13368c4a..077d33d9 100644 --- a/src/corelib/time/qromancalendar_data_p.h +++ b/src/corelib/time/qromancalendar_data_p.h @@ -25,7 +25,7 @@ namespace QtPrivate::Roman { // GENERATED PART STARTS HERE /* - This part of the file was generated on 2023-08-03 from the + This part of the file was generated on 2023-07-27 from the Common Locale Data Repository v43 http://www.unicode.org/cldr/ diff --git a/src/corelib/time/qtimezone.cpp b/src/corelib/time/qtimezone.cpp index f882602d..44b6662b 100644 --- a/src/corelib/time/qtimezone.cpp +++ b/src/corelib/time/qtimezone.cpp @@ -29,7 +29,7 @@ static QTimeZonePrivate *newBackendTimeZone() return new QUtcTimeZonePrivate(); #endif #else -#if defined Q_OS_MAC +#if defined(Q_OS_DARWIN) return new QMacTimeZonePrivate(); #elif defined(Q_OS_ANDROID) return new QAndroidTimeZonePrivate(); @@ -37,7 +37,7 @@ static QTimeZonePrivate *newBackendTimeZone() return new QTzTimeZonePrivate(); #elif QT_CONFIG(icu) return new QIcuTimeZonePrivate(); -#elif defined Q_OS_WIN +#elif defined(Q_OS_WIN) return new QWinTimeZonePrivate(); #else return new QUtcTimeZonePrivate(); @@ -56,7 +56,7 @@ static QTimeZonePrivate *newBackendTimeZone(const QByteArray &ianaId) return new QUtcTimeZonePrivate(ianaId); #endif #else -#if defined Q_OS_MAC +#if defined(Q_OS_DARWIN) return new QMacTimeZonePrivate(ianaId); #elif defined(Q_OS_ANDROID) return new QAndroidTimeZonePrivate(ianaId); @@ -64,7 +64,7 @@ static QTimeZonePrivate *newBackendTimeZone(const QByteArray &ianaId) return new QTzTimeZonePrivate(ianaId); #elif QT_CONFIG(icu) return new QIcuTimeZonePrivate(ianaId); -#elif defined Q_OS_WIN +#elif defined(Q_OS_WIN) return new QWinTimeZonePrivate(ianaId); #else return new QUtcTimeZonePrivate(ianaId); @@ -177,7 +177,7 @@ Q_GLOBAL_STATIC(QTimeZoneSingleton, global_tz); A default UTC time zone backend is provided which is always available when feature \c timezone is enabled. This provides a set of generic Offset From - UTC time zones in the range UTC-14:00 to UTC+14:00. These time zones can be + UTC time zones in the range UTC-16:00 to UTC+16:00. These time zones can be created using either the standard ISO format names, such as "UTC+00:00", as listed by availableTimeZoneIds(), or using a name of similar form in combination with the number of offset seconds. @@ -254,17 +254,34 @@ Q_GLOBAL_STATIC(QTimeZoneSingleton, global_tz); */ /*! - \enum QTimeZone::anonymous + \variable QTimeZone::MinUtcOffsetSecs + \brief Timezone offsets from UTC are expected to be no lower than this. - This enumeration provides constants bounding the range of plausible timezone - offsets from UTC, measured in seconds. - Sane UTC offsets range from -14 to +14 hours. - No known zone has offset > 12 hrs West of Greenwich (Baker Island, USA). - No known zone has offset > 14 hrs East of Greenwich (Kiritimati, Christmas Island, Kiribati). - Note that there are zones whose offsets differ by more than a day. + The lowest UTC offset of any early 21st century timezone is -12 hours (Baker + Island, USA), or 12 hours west of Greenwich. - \value MinUtcOffsetSecs -14 * 3600, - \value MaxUtcOffsetSecs +14 * 3600 + Historically, until 1844, The Philippines (then controlled by Spain) used + the same date as Spain's American holdings, so had offsets close to 16 hours + west of Greenwich. As The Philippines was using local solar mean time, it is + possible some outlying territory of it may have been operating at more than + 16 hours west of Greenwich, but no early 21st century timezone traces its + history back to such an extreme. + + \sa MaxUtcOffsetSecs +*/ +/*! + \variable QTimeZone::MaxUtcOffsetSecs + \brief Timezone offsets from UTC are expected to be no higher than this. + + The highest UTC offset of any early 21st century timezone is +14 hours + (Christmas Island, Kiribati, Kiritimati), or 14 hours east of Greenwich. + + Historically, before 1867, when Russia sold Alaska to America, Alaska used + the same date as Russia, so had offsets over 15 hours east of Greenwich. As + Alaska was using local solar mean time, its offsets varied, but all were + less than 16 hours east of Greenwich. + + \sa MinUtcOffsetSecs */ #if QT_CONFIG(timezone) @@ -449,8 +466,13 @@ QTimeZone::QTimeZone(const QByteArray &ianaId) d = new QUtcTimeZonePrivate(ianaId); // If not a CLDR UTC offset ID then try creating it with the system backend. // Relies on backend not creating valid TZ with invalid name. - if (!d->isValid()) - d = ianaId.isEmpty() ? newBackendTimeZone() : newBackendTimeZone(ianaId); + if (!d->isValid()) { + if (ianaId.isEmpty()) + d = newBackendTimeZone(); + else if (global_tz->backend->isTimeZoneIdAvailable(ianaId)) + d = newBackendTimeZone(ianaId); + // else: No such ID, avoid creating a TZ cache entry for it. + } // Can also handle UTC with arbitrary (valid) offset, but only do so as // fall-back, since either of the above may handle it more informatively. if (!d->isValid()) { @@ -468,13 +490,15 @@ QTimeZone::QTimeZone(const QByteArray &ianaId) /*! Creates a time zone instance with the given offset, \a offsetSeconds, from UTC. - The \a offsetSeconds from UTC must be in the range -14 hours to +14 hours + The \a offsetSeconds from UTC must be in the range -16 hours to +16 hours otherwise an invalid time zone will be returned. This constructor is only available when feature \c timezone is enabled. The returned instance is equivalent to the lightweight time representation \c{QTimeZone::fromSecondsAfterUtc(offsetSeconds)}, albeit implemented as a time zone. + + \sa MinUtcOffsetSecs, MaxUtcOffsetSecs */ QTimeZone::QTimeZone(int offsetSeconds) @@ -495,14 +519,15 @@ QTimeZone::QTimeZone(int offsetSeconds) The \a ianaId must not be one of the available system IDs returned by availableTimeZoneIds(). The \a offsetSeconds from UTC must be in the range - -14 hours to +14 hours. + -16 hours to +16 hours. If the custom time zone does not have a specific territory then set it to the default value of QLocale::AnyTerritory. This constructor is only available when feature \c timezone is enabled. - \sa id(), offsetFromUtc(), displayName(), abbreviation(), territory(), comment() + \sa id(), offsetFromUtc(), displayName(), abbreviation(), territory(), comment(), + MinUtcOffsetSecs, MaxUtcOffsetSecs */ QTimeZone::QTimeZone(const QByteArray &ianaId, int offsetSeconds, const QString &name, @@ -605,17 +630,18 @@ QTimeZone QTimeZone::asBackendZone() const Returns a time representation at a fixed \a offset, in seconds, ahead of UTC. - The \a offset from UTC must be in the range -14 hours to +14 hours otherwise an - invalid time zone will be returned. The returned QTimeZone is a lightweight - time representation, not a time zone (backed by system-supplied or standard - data). + The \a offset from UTC must be in the range -16 hours to +16 hours otherwise + an invalid time zone will be returned. The returned QTimeZone is a + lightweight time representation, not a time zone (backed by system-supplied + or standard data). If the offset is 0, the \l timeSpec() of the returned instance will be Qt::UTC. Otherwise, if \a offset is valid, timeSpec() is Qt::OffsetFromUTC. An invalid time zone, when returned, has Qt::TimeZone as its timeSpec(). - \sa QTimeZone(int), asBackendZone(), fixedSecondsAheadOfUtc() + \sa QTimeZone(int), asBackendZone(), fixedSecondsAheadOfUtc(), + MinUtcOffsetSecs, MaxUtcOffsetSecs */ /*! @@ -1120,9 +1146,12 @@ bool QTimeZone::isDaylightTime(const QDateTime &atDateTime) const } /*! - Returns the effective offset details at the given \a forDateTime. This is - the equivalent of calling offsetFromUtc(), abbreviation(), etc individually but is - more efficient. + Returns the effective offset details at the given \a forDateTime. + + This is the equivalent of calling abbreviation() and all three offset + functions individually but is more efficient. If this data is not available + for the given datetime, an invalid OffsetData will be returned with an + invalid QDateTime as its \c atUtc. This method is only available when feature \c timezone is enabled. @@ -1142,9 +1171,9 @@ QTimeZone::OffsetData QTimeZone::offsetData(const QDateTime &forDateTime) const Q_UNREACHABLE(); break; } - } else if (hasTransitions()) { - return QTimeZonePrivate::toOffsetData(d->data(forDateTime.toMSecsSinceEpoch())); } + if (isValid()) + return QTimeZonePrivate::toOffsetData(d->data(forDateTime.toMSecsSinceEpoch())); return QTimeZonePrivate::invalidOffsetData(); } @@ -1185,7 +1214,7 @@ bool QTimeZone::hasTransitions() const Transition after it. If there is no transition after the given \a afterDateTime then an invalid - OffsetData will be returned with an invalid QDateTime. + OffsetData will be returned with an invalid QDateTime as its \c atUtc. The given \a afterDateTime is exclusive. @@ -1220,7 +1249,7 @@ QTimeZone::OffsetData QTimeZone::nextTransition(const QDateTime &afterDateTime) Transition before it. If there is no transition before the given \a beforeDateTime then an invalid - OffsetData will be returned with an invalid QDateTime. + OffsetData will be returned with an invalid QDateTime as its \c atUtc. The given \a beforeDateTime is exclusive. @@ -1357,12 +1386,18 @@ QTimeZone QTimeZone::utc() bool QTimeZone::isTimeZoneIdAvailable(const QByteArray &ianaId) { +#if defined(Q_OS_UNIX) && !(defined(Q_OS_ANDROID) || defined(Q_OS_DARWIN)) + // Keep #if-ery consistent with selection of QTzTimeZonePrivate in + // newBackendTimeZone(). Skip the pre-check, as the TZ backend accepts POSIX + // zone IDs, which need not be valid IANA IDs. +#else // isValidId is not strictly required, but faster to weed out invalid // IDs as availableTimeZoneIds() may be slow if (!QTimeZonePrivate::isValidId(ianaId)) return false; - return QUtcTimeZonePrivate().isTimeZoneIdAvailable(ianaId) || - global_tz->backend->isTimeZoneIdAvailable(ianaId); +#endif + return QUtcTimeZonePrivate().isTimeZoneIdAvailable(ianaId) + || global_tz->backend->isTimeZoneIdAvailable(ianaId); } static QList set_union(const QList &l1, const QList &l2) diff --git a/src/corelib/time/qtimezone.h b/src/corelib/time/qtimezone.h index 52e51f20..6040624f 100644 --- a/src/corelib/time/qtimezone.h +++ b/src/corelib/time/qtimezone.h @@ -56,7 +56,7 @@ class Q_CORE_EXPORT QTimeZone union Data { Data() noexcept; - Data(ShortData &&sd) : s(std::move(sd)) {} + Data(ShortData sd) : s(sd) {} Data(const Data &other) noexcept; Data(Data &&other) noexcept : d(std::exchange(other.d, nullptr)) {} Data &operator=(const Data &other) noexcept; @@ -79,16 +79,16 @@ class Q_CORE_EXPORT QTimeZone QTimeZonePrivate *d = nullptr; ShortData s; }; - QTimeZone(ShortData &&sd) : d(std::move(sd)) {} + QTimeZone(ShortData sd) : d(sd) {} public: - // Sane UTC offsets range from -14 to +14 hours: - enum { - // No known zone > 12 hrs West of Greenwich (Baker Island, USA) - MinUtcOffsetSecs = -14 * 3600, - // No known zone > 14 hrs East of Greenwich (Kiritimati, Christmas Island, Kiribati) - MaxUtcOffsetSecs = +14 * 3600 - }; + // Sane UTC offsets range from -16 to +16 hours: + static constexpr int MinUtcOffsetSecs = -16 * 3600; + // No known modern zone > 12 hrs West of Greenwich. + // Until 1844, Asia/Manila (in The Philippines) was at 15:56 West. + static constexpr int MaxUtcOffsetSecs = +16 * 3600; + // No known modern zone > 14 hrs East of Greenwich. + // Until 1867, America/Metlakatla (in Alaska) was at 15:13:42 East. enum Initialization { LocalTime, UTC }; diff --git a/src/corelib/time/qtimezoneprivate.cpp b/src/corelib/time/qtimezoneprivate.cpp index 96bbf7df..6f0b51f8 100644 --- a/src/corelib/time/qtimezoneprivate.cpp +++ b/src/corelib/time/qtimezoneprivate.cpp @@ -182,28 +182,28 @@ QTimeZonePrivate::Data QTimeZonePrivate::dataForLocalTime(qint64 forLocalMSecs, /* We need a UTC time at which to ask for the offset, in order to be able to - add that offset to forLocalMSecs, to get the UTC time we - need. Fortunately, no time-zone offset is more than 14 hours; and DST - transitions happen (much) more than thirty-two hours apart. So sampling - offset sixteen hours each side gives us information we can be sure + add that offset to forLocalMSecs, to get the UTC time we need. + Fortunately, all time-zone offsets have been less than 17 hours; and DST + transitions happen (much) more than thirty-four hours apart. So sampling + offset seventeen hours each side gives us information we can be sure brackets the correct time and at most one DST transition. */ - std::integral_constant sixteenHoursInMSecs; - static_assert(-sixteenHoursInMSecs / 1000 < QTimeZone::MinUtcOffsetSecs - && sixteenHoursInMSecs / 1000 > QTimeZone::MaxUtcOffsetSecs); + std::integral_constant seventeenHoursInMSecs; + static_assert(-seventeenHoursInMSecs / 1000 < QTimeZone::MinUtcOffsetSecs + && seventeenHoursInMSecs / 1000 > QTimeZone::MaxUtcOffsetSecs); qint64 millis; // Clip the bracketing times to the bounds of the supported range. Exclude // minMSecs(), because at least one backend (Windows) uses it for a // start-of-time fake transition, that we want previousTransition() to find. const qint64 recent = - qSubOverflow(forLocalMSecs, sixteenHoursInMSecs, &millis) || millis <= minMSecs() + qSubOverflow(forLocalMSecs, seventeenHoursInMSecs, &millis) || millis <= minMSecs() ? minMSecs() + 1 : millis; // Necessarily <= forLocalMSecs + 2. // (Given that minMSecs() is std::numeric_limits::min() + 1.) const qint64 imminent = - qAddOverflow(forLocalMSecs, sixteenHoursInMSecs, &millis) + qAddOverflow(forLocalMSecs, seventeenHoursInMSecs, &millis) ? maxMSecs() : millis; // Necessarily >= forLocalMSecs // At most one of those was clipped to its boundary value: - Q_ASSERT(recent < imminent && sixteenHoursInMSecs < imminent - recent + 2); + Q_ASSERT(recent < imminent && seventeenHoursInMSecs < imminent - recent + 2); /* Offsets are Local - UTC, positive to the east of Greenwich, negative to the west; DST offset always exceeds standard offset, when DST applies. @@ -639,9 +639,13 @@ QString QTimeZonePrivate::isoOffsetFormat(int offsetFromUtc, QTimeZone::NameType QByteArray QTimeZonePrivate::ianaIdToWindowsId(const QByteArray &id) { + // We don't have a Latin1/UTF-8 mixed comparator (QTBUG-100234), + // so we have to allocate here... + const auto idUtf8 = QString::fromUtf8(id); + for (const QZoneData &data : zoneDataTable) { for (auto l1 : data.ids()) { - if (l1 == QByteArrayView(id)) + if (l1 == idUtf8) return toWindowsIdLiteral(data.windowsIdKey); } } diff --git a/src/corelib/time/qtimezoneprivate_p.h b/src/corelib/time/qtimezoneprivate_p.h index 6ea95070..cb045ab5 100644 --- a/src/corelib/time/qtimezoneprivate_p.h +++ b/src/corelib/time/qtimezoneprivate_p.h @@ -334,7 +334,7 @@ private: }; #endif // Q_OS_UNIX -#ifdef Q_OS_MAC +#ifdef Q_OS_DARWIN class Q_AUTOTEST_EXPORT QMacTimeZonePrivate final : public QTimeZonePrivate { public: @@ -377,7 +377,7 @@ private: NSTimeZone *m_nstz; }; -#endif // Q_OS_MAC +#endif // Q_OS_DARWIN #ifdef Q_OS_WIN class Q_AUTOTEST_EXPORT QWinTimeZonePrivate final : public QTimeZonePrivate diff --git a/src/corelib/time/qtimezoneprivate_tz.cpp b/src/corelib/time/qtimezoneprivate_tz.cpp index 067191d8..e702a5d6 100644 --- a/src/corelib/time/qtimezoneprivate_tz.cpp +++ b/src/corelib/time/qtimezoneprivate_tz.cpp @@ -384,13 +384,15 @@ static QDate calculateDowDate(int year, int month, int dayOfWeek, int week) static QDate calculatePosixDate(const QByteArray &dateRule, int year) { + Q_ASSERT(!dateRule.isEmpty()); bool ok; // Can start with M, J, or a digit if (dateRule.at(0) == 'M') { // nth week in month format "Mmonth.week.dow" QList dateParts = dateRule.split('.'); if (dateParts.size() > 2) { - int month = dateParts.at(0).mid(1).toInt(&ok); + Q_ASSERT(!dateParts.at(0).isEmpty()); // the 'M' is its [0]. + int month = QByteArrayView{ dateParts.at(0) }.sliced(1).toInt(&ok); int week = ok ? dateParts.at(1).toInt(&ok) : 0; int dow = ok ? dateParts.at(2).toInt(&ok) : 0; if (ok) @@ -399,7 +401,7 @@ static QDate calculatePosixDate(const QByteArray &dateRule, int year) } else if (dateRule.at(0) == 'J') { // Day of Year 1...365, ignores Feb 29. // So March always starts on day 60. - int doy = dateRule.mid(1).toInt(&ok); + int doy = QByteArrayView{ dateRule }.sliced(1).toInt(&ok); if (ok && doy > 0 && doy < 366) { // Subtract 1 because we're adding days *after* the first of // January, unless it's after February in a leap year, when the leap @@ -553,7 +555,12 @@ PosixZone PosixZone::parse(const char *&pos, const char *end) return {std::move(name), offset}; } -static auto validatePosixRule(const QByteArray &posixRule) +/* Parse and check a POSIX rule. + + By default a simple zone abbreviation with no offset information is accepted. + Set \a requireOffset to \c true to require that there be offset data present. +*/ +static auto validatePosixRule(const QByteArray &posixRule, bool requireOffset = false) { // Format is described here: // http://www.gnu.org/software/libc/manual/html_node/TZ-Variable.html @@ -565,15 +572,19 @@ static auto validatePosixRule(const QByteArray &posixRule) return fail; const char *begin = zoneinfo.begin(); - - // Updates begin to point after the name and offset it parses: - if (PosixZone::parse(begin, zoneinfo.end()).name.isEmpty()) - return fail; + { + // Updates begin to point after the name and offset it parses: + const auto posix = PosixZone::parse(begin, zoneinfo.end()); + if (posix.name.isEmpty()) + return fail; + if (requireOffset && !posix.hasValidOffset()) + return fail; + } if (good.hasDst) { if (begin >= zoneinfo.end()) return fail; - // Expect a second name and offset after the first: + // Expect a second name (and optional offset) after the first: if (PosixZone::parse(begin, zoneinfo.end()).name.isEmpty()) return fail; } @@ -928,8 +939,8 @@ QTzTimeZoneCacheEntry QTzTimeZoneCache::findEntry(const QByteArray &ianaId) if (ruleIndex == -1) { if (rule.dstOffset != 0) ret.m_hasDst = true; + tran.ruleIndex = ret.m_tranRules.size(); ret.m_tranRules.append(rule); - tran.ruleIndex = ret.m_tranRules.size() - 1; } else { tran.ruleIndex = ruleIndex; } @@ -1221,7 +1232,11 @@ QTimeZonePrivate::Data QTzTimeZonePrivate::previousTransition(qint64 beforeMSecs bool QTzTimeZonePrivate::isTimeZoneIdAvailable(const QByteArray &ianaId) const { - return tzZones->contains(ianaId); + // Allow a POSIX rule as long as it has offset data. (This needs to reject a + // plain abbreviation, without offset, since claiming to support such zones + // would prevent the custom QTimeZone constructor from accepting such a + // name, as it doesn't want a custom zone to over-ride a "real" one.) + return tzZones->contains(ianaId) || validatePosixRule(ianaId, true).isValid; } QList QTzTimeZonePrivate::availableTimeZoneIds() const @@ -1327,7 +1342,7 @@ private: path = QFile::symLinkTarget(path); int index = path.indexOf(zoneinfo); if (index >= 0) // Found zoneinfo file; extract zone name from path: - return QStringView{ path }.mid(index + zoneinfo.size()).toUtf8(); + return QStringView{ path }.sliced(index + zoneinfo.size()).toUtf8(); } while (!path.isEmpty() && --iteration > 0); return QByteArray(); @@ -1384,7 +1399,7 @@ QByteArray QTzTimeZonePrivate::staticSystemTimeZoneId() if (ianaId == ":/etc/localtime") ianaId.clear(); else if (ianaId.startsWith(':')) - ianaId = ianaId.mid(1); + ianaId = ianaId.sliced(1); if (ianaId.isEmpty()) { Q_CONSTINIT thread_local static ZoneNameReader reader; diff --git a/src/corelib/tools/qarraydata.cpp b/src/corelib/tools/qarraydata.cpp index 433385ad..56f14616 100644 --- a/src/corelib/tools/qarraydata.cpp +++ b/src/corelib/tools/qarraydata.cpp @@ -107,33 +107,30 @@ qCalculateGrowingBlockSize(qsizetype elementCount, qsizetype elementSize, qsizet return result; } -/*! - \internal +/* + Calculate the byte size for a block of \a capacity objects of size \a + objectSize, with a header of size \a headerSize. If the \a option is + QArrayData::Grow, the capacity itself adjusted up, preallocating room for + more elements to be added later; otherwise, it is an exact calculation. - Returns \a allocSize plus extra reserved bytes necessary to store '\0'. - */ -static inline qsizetype reserveExtraBytes(qsizetype allocSize) + Returns a structure containing the size in bytes and elements available. +*/ +static inline CalculateGrowingBlockSizeResult +calculateBlockSize(qsizetype capacity, qsizetype objectSize, qsizetype headerSize, QArrayData::AllocationOption option) { - // We deal with QByteArray and QString only - constexpr qsizetype extra = qMax(sizeof(QByteArray::value_type), sizeof(QString::value_type)); - if (Q_UNLIKELY(allocSize < 0)) - return -1; - if (Q_UNLIKELY(qAddOverflow(allocSize, extra, &allocSize))) - return -1; - return allocSize; -} + // Adjust the header size up to account for the trailing null for QString + // and QByteArray. This is not checked for overflow because headers sizes + // should not be anywhere near the overflow limit. + constexpr qsizetype FooterSize = qMax(sizeof(QString::value_type), sizeof(QByteArray::value_type)); + if (objectSize <= FooterSize) + headerSize += FooterSize; -static inline qsizetype calculateBlockSize(qsizetype &capacity, qsizetype objectSize, qsizetype headerSize, QArrayData::AllocationOption option) -{ - // Calculate the byte size // allocSize = objectSize * capacity + headerSize, but checked for overflow // plus padded to grow in size if (option == QArrayData::Grow) { - auto r = qCalculateGrowingBlockSize(capacity, objectSize, headerSize); - capacity = r.elementCount; - return r.size; + return qCalculateGrowingBlockSize(capacity, objectSize, headerSize); } else { - return qCalculateBlockSize(capacity, objectSize, headerSize); + return { qCalculateBlockSize(capacity, objectSize, headerSize), capacity }; } } @@ -181,8 +178,9 @@ void *QArrayData::allocate(QArrayData **dptr, qsizetype objectSize, qsizetype al } Q_ASSERT(headerSize > 0); - qsizetype allocSize = calculateBlockSize(capacity, objectSize, headerSize, option); - allocSize = reserveExtraBytes(allocSize); + auto blockSize = calculateBlockSize(capacity, objectSize, headerSize, option); + capacity = blockSize.elementCount; + qsizetype allocSize = blockSize.size; if (Q_UNLIKELY(allocSize < 0)) { // handle overflow. cannot allocate reliably *dptr = nullptr; return nullptr; @@ -207,7 +205,9 @@ QArrayData::reallocateUnaligned(QArrayData *data, void *dataPointer, Q_ASSERT(!data || !data->isShared()); const qsizetype headerSize = sizeof(AlignedQArrayData); - qsizetype allocSize = calculateBlockSize(capacity, objectSize, headerSize, option); + auto r = calculateBlockSize(capacity, objectSize, headerSize, option); + qsizetype allocSize = r.size; + capacity = r.elementCount; if (Q_UNLIKELY(allocSize < 0)) return qMakePair(nullptr, nullptr); @@ -217,10 +217,6 @@ QArrayData::reallocateUnaligned(QArrayData *data, void *dataPointer, Q_ASSERT(offset > 0); Q_ASSERT(offset <= allocSize); // equals when all free space is at the beginning - allocSize = reserveExtraBytes(allocSize); - if (Q_UNLIKELY(allocSize < 0)) // handle overflow. cannot reallocate reliably - return qMakePair(data, dataPointer); - QArrayData *header = static_cast(::realloc(data, size_t(allocSize))); if (header) { header->alloc = capacity; diff --git a/src/corelib/tools/qarraydataops.h b/src/corelib/tools/qarraydataops.h index 9b29b8a1..bd8ead0a 100644 --- a/src/corelib/tools/qarraydataops.h +++ b/src/corelib/tools/qarraydataops.h @@ -35,6 +35,8 @@ protected: public: typedef typename QArrayDataPointer::parameter_type parameter_type; + using QArrayDataPointer::QArrayDataPointer; + void appendInitialize(qsizetype newSize) noexcept { Q_ASSERT(this->isMutable()); @@ -213,6 +215,40 @@ public: --this->size; } + template + qsizetype eraseIf(Predicate pred) + { + qsizetype result = 0; + if (this->size == 0) + return result; + + if (!this->needsDetach()) { + auto end = this->end(); + auto it = std::remove_if(this->begin(), end, pred); + if (it != end) { + result = std::distance(it, end); + erase(it, result); + } + } else { + const auto begin = this->begin(); + const auto end = this->end(); + auto it = std::find_if(begin, end, pred); + if (it == end) + return result; + + QPodArrayOps other{ Data::allocate(this->size), this->size }; + Q_CHECK_PTR(other.data()); + auto dest = other.begin(); + // std::uninitialized_copy will fallback to ::memcpy/memmove() + dest = std::uninitialized_copy(begin, it, dest); + dest = q_uninitialized_remove_copy_if(std::next(it), end, dest, pred); + other.size = std::distance(other.data(), dest); + result = this->size - other.size; + this->swap(other); + } + return result; + } + struct Span { T *begin; T *end; }; void copyRanges(std::initializer_list ranges) diff --git a/src/corelib/tools/qarraydatapointer.h b/src/corelib/tools/qarraydatapointer.h index f0b689b4..a77db21c 100644 --- a/src/corelib/tools/qarraydatapointer.h +++ b/src/corelib/tools/qarraydatapointer.h @@ -7,6 +7,9 @@ #include #include +#include +#include + QT_BEGIN_NAMESPACE template @@ -24,27 +27,32 @@ public: typedef typename std::conditional::type parameter_type; + Q_NODISCARD_CTOR constexpr QArrayDataPointer() noexcept : d(nullptr), ptr(nullptr), size(0) { } + Q_NODISCARD_CTOR QArrayDataPointer(const QArrayDataPointer &other) noexcept : d(other.d), ptr(other.ptr), size(other.size) { ref(); } + Q_NODISCARD_CTOR constexpr QArrayDataPointer(Data *header, T *adata, qsizetype n = 0) noexcept : d(header), ptr(adata), size(n) { } + Q_NODISCARD_CTOR explicit QArrayDataPointer(QPair *, T *> adata, qsizetype n = 0) noexcept : d(adata.first), ptr(adata.second), size(n) { } + Q_NODISCARD_CTOR static QArrayDataPointer fromRawData(const T *rawData, qsizetype length) noexcept { Q_ASSERT(rawData || !length); @@ -58,6 +66,7 @@ public: return *this; } + Q_NODISCARD_CTOR QArrayDataPointer(QArrayDataPointer &&other) noexcept : d(other.d), ptr(other.ptr), size(other.size) { @@ -92,7 +101,7 @@ public: { if (!deref()) { (*this)->destroyAll(); - Data::deallocate(d); + free(d); } } @@ -305,6 +314,98 @@ public: this->ptr = res; } + template + void assign(InputIterator first, InputIterator last, Projection proj = {}) + { + // This function only provides the basic exception guarantee. + constexpr bool IsFwdIt = std::is_convertible_v< + typename std::iterator_traits::iterator_category, + std::forward_iterator_tag>; + constexpr bool IsIdentity = std::is_same_v; + + if constexpr (IsFwdIt) { + const qsizetype n = std::distance(first, last); + if (needsDetach() || n > constAllocatedCapacity()) { + QArrayDataPointer allocated(Data::allocate(detachCapacity(n))); + swap(allocated); + } + } else if (needsDetach()) { + QArrayDataPointer allocated(Data::allocate(allocatedCapacity())); + swap(allocated); + // We don't want to copy data that we know we'll overwrite + } + + auto offset = freeSpaceAtBegin(); + const auto capacityBegin = begin() - offset; + const auto prependBufferEnd = begin(); + + if constexpr (!std::is_nothrow_constructible_v) { + // If construction can throw, and we have freeSpaceAtBegin(), + // it's easiest to just clear the container and start fresh. + // The alternative would be to keep track of two active, disjoint ranges. + if (offset) { + (*this)->truncate(0); + setBegin(capacityBegin); + offset = 0; + } + } + + auto dst = capacityBegin; + const auto dend = end(); + if (offset) { // avoids dead stores + setBegin(capacityBegin); // undo prepend optimization + + // By construction, the following loop is nothrow! + // (otherwise, we can't reach here) + // Assumes InputIterator operations don't throw. + // (but we can't statically assert that, as these operations + // have preconditons, so typically aren't noexcept) + while (true) { + if (dst == prependBufferEnd) { // ran out of prepend buffer space + size += offset; + // we now have a contiguous buffer, continue with the main loop: + break; + } + if (first == last) { // ran out of elements to assign + std::destroy(prependBufferEnd, dend); + size = dst - begin(); + return; + } + // construct element in prepend buffer + q20::construct_at(dst, std::invoke(proj, *first)); + ++dst; + ++first; + } + } + + while (true) { + if (first == last) { // ran out of elements to assign + std::destroy(dst, dend); + break; + } + if (dst == dend) { // ran out of existing elements to overwrite + if constexpr (IsFwdIt && IsIdentity) { + dst = std::uninitialized_copy(first, last, dst); + break; + } else if constexpr (IsFwdIt && !IsIdentity + && std::is_nothrow_constructible_v) { + for (; first != last; ++dst, ++first) // uninitialized_copy with projection + q20::construct_at(dst, std::invoke(proj, *first)); + break; + } else { + do { + (*this)->emplace(size, std::invoke(proj, *first)); + } while (++first != last); + return; // size() is already correct (and dst invalidated)! + } + } + *dst = std::invoke(proj, *first); // overwrite existing element + ++dst; + ++first; + } + size = dst - begin(); + } + // forwards from QArrayData qsizetype allocatedCapacity() noexcept { return d ? d->allocatedCapacity() : 0; } qsizetype constAllocatedCapacity() const noexcept { return d ? d->constAllocatedCapacity() : 0; } diff --git a/src/corelib/tools/qatomicscopedvaluerollback_p.h b/src/corelib/tools/qatomicscopedvaluerollback_p.h index 7b2161d7..a5c1a2fa 100644 --- a/src/corelib/tools/qatomicscopedvaluerollback_p.h +++ b/src/corelib/tools/qatomicscopedvaluerollback_p.h @@ -23,7 +23,7 @@ QT_BEGIN_NAMESPACE template -class [[nodiscard]] QAtomicScopedValueRollback +class QAtomicScopedValueRollback { std::atomic &m_atomic; T m_value; @@ -52,11 +52,13 @@ public: // // std::atomic: // + Q_NODISCARD_CTOR explicit constexpr QAtomicScopedValueRollback(std::atomic &var, std::memory_order mo = std::memory_order_seq_cst) : m_atomic(var), m_value(var.load(mo)), m_mo(mo) {} + Q_NODISCARD_CTOR explicit constexpr QAtomicScopedValueRollback(std::atomic &var, T value, std::memory_order mo = std::memory_order_seq_cst) @@ -65,11 +67,13 @@ public: // // Q(Basic)AtomicInteger: // + Q_NODISCARD_CTOR explicit constexpr QAtomicScopedValueRollback(QBasicAtomicInteger &var, std::memory_order mo = std::memory_order_seq_cst) : QAtomicScopedValueRollback(var._q_value, mo) {} + Q_NODISCARD_CTOR explicit constexpr QAtomicScopedValueRollback(QBasicAtomicInteger &var, T value, std::memory_order mo = std::memory_order_seq_cst) @@ -78,11 +82,13 @@ public: // // Q(Basic)AtomicPointer: // + Q_NODISCARD_CTOR explicit constexpr QAtomicScopedValueRollback(QBasicAtomicPointer> &var, std::memory_order mo = std::memory_order_seq_cst) : QAtomicScopedValueRollback(var._q_value, mo) {} + Q_NODISCARD_CTOR explicit constexpr QAtomicScopedValueRollback(QBasicAtomicPointer> &var, T value, std::memory_order mo = std::memory_order_seq_cst) diff --git a/src/corelib/tools/qcommandlineparser.cpp b/src/corelib/tools/qcommandlineparser.cpp index fee1647c..c1581a27 100644 --- a/src/corelib/tools/qcommandlineparser.cpp +++ b/src/corelib/tools/qcommandlineparser.cpp @@ -253,7 +253,7 @@ QCommandLineParser::~QCommandLineParser() \enum QCommandLineParser::SingleDashWordOptionMode This enum describes the way the parser interprets command-line - options that use a single dash followed by multiple letters, as as \c{-abc}. + options that use a single dash followed by multiple letters, as \c{-abc}. \value ParseAsCompactedShortOptions \c{-abc} is interpreted as \c{-a -b -c}, i.e. as three short options that have been compacted on the command-line, @@ -415,7 +415,8 @@ QCommandLineOption QCommandLineParser::addHelpOption() << QStringLiteral("h") << QStringLiteral("help"), tr("Displays help on commandline options.")); addOption(opt); - QCommandLineOption optHelpAll(QStringLiteral("help-all"), tr("Displays help including Qt specific options.")); + QCommandLineOption optHelpAll(QStringLiteral("help-all"), + tr("Displays help, including generic Qt options.")); addOption(optHelpAll); d->builtinHelpOption = true; return opt; diff --git a/src/corelib/tools/qcontainerfwd.h b/src/corelib/tools/qcontainerfwd.h index b876c464..013ba9ea 100644 --- a/src/corelib/tools/qcontainerfwd.h +++ b/src/corelib/tools/qcontainerfwd.h @@ -1,11 +1,12 @@ // 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 - #ifndef QCONTAINERFWD_H #define QCONTAINERFWD_H +#include +#include + #if 0 #pragma qt_class(QtContainerFwd) #endif diff --git a/src/corelib/tools/qcontainertools_impl.h b/src/corelib/tools/qcontainertools_impl.h index d3804211..40ed0a84 100644 --- a/src/corelib/tools/qcontainertools_impl.h +++ b/src/corelib/tools/qcontainertools_impl.h @@ -72,10 +72,10 @@ template void q_uninitialized_relocate_n(T* first, N n, T* out) { if constexpr (QTypeInfo::isRelocatable) { - if (n != N(0)) { // even if N == 0, out == nullptr or first == nullptr are UB for memmove() - std::memmove(static_cast(out), - static_cast(first), - n * sizeof(T)); + if (n != N(0)) { // even if N == 0, out == nullptr or first == nullptr are UB for memcpy() + std::memcpy(static_cast(out), + static_cast(first), + n * sizeof(T)); } } else { q_uninitialized_move_if_noexcept_n(first, n, out); @@ -106,6 +106,41 @@ void q_rotate(T *first, T *mid, T *last) } } +/*! + \internal + Copies all elements, except the ones for which \a pred returns \c true, from + range [first, last), to the uninitialized memory buffer starting at \a out. + + It's undefined behavior if \a out points into [first, last). + + Returns a pointer one past the last copied element. + + If an exception is thrown, all the already copied elements in the destination + buffer are destroyed. +*/ +template +T *q_uninitialized_remove_copy_if(T *first, T *last, T *out, Predicate &pred) +{ + static_assert(std::is_nothrow_destructible_v, + "This algorithm requires that T has a non-throwing destructor"); + Q_ASSERT(!q_points_into_range(out, first, last)); + + T *dest_begin = out; + QT_TRY { + while (first != last) { + if (!pred(*first)) { + new (std::addressof(*out)) T(*first); + ++out; + } + ++first; + } + } QT_CATCH (...) { + std::destroy(std::reverse_iterator(out), std::reverse_iterator(dest_begin)); + QT_RETHROW; + } + return out; +} + template void q_relocate_overlap_n_left_move(iterator first, N n, iterator d_first) { @@ -267,6 +302,15 @@ template using IfAssociativeIteratorHasFirstAndSecond = std::enable_if_t, bool>; +template +using MoveBackwardsTest = decltype( + std::declval().operator--() +); + +template +using IfIteratorCanMoveBackwards = + std::enable_if_t, bool>; + template using IfIsNotSame = typename std::enable_if::value, bool>::type; diff --git a/src/corelib/tools/qcontiguouscache.cpp b/src/corelib/tools/qcontiguouscache.cpp index 73cfed97..d28d1e71 100644 --- a/src/corelib/tools/qcontiguouscache.cpp +++ b/src/corelib/tools/qcontiguouscache.cpp @@ -6,6 +6,8 @@ #include #endif +#include + QT_BEGIN_NAMESPACE #ifdef QT_QCONTIGUOUSCACHE_DEBUG diff --git a/src/corelib/tools/qcontiguouscache.h b/src/corelib/tools/qcontiguouscache.h index 98fa82fd..c01dbb93 100644 --- a/src/corelib/tools/qcontiguouscache.h +++ b/src/corelib/tools/qcontiguouscache.h @@ -5,7 +5,13 @@ #define QCONTIGUOUSCACHE_H #include -#include +#include +#include +#include +#include + +#include +#include #include QT_BEGIN_NAMESPACE diff --git a/src/corelib/tools/qcryptographichash.cpp b/src/corelib/tools/qcryptographichash.cpp index 59c95199..1d759e04 100644 --- a/src/corelib/tools/qcryptographichash.cpp +++ b/src/corelib/tools/qcryptographichash.cpp @@ -25,7 +25,7 @@ #include "../../3rdparty/rfc6234/sha.h" #ifndef QT_CRYPTOGRAPHICHASH_ONLY_SHA1 -#if !QT_CONFIG(opensslv30) || !QT_CONFIG(openssl_linked) +#if !QT_CONFIG(openssl_hash) // qdoc and qmake only need SHA-1 #include "../../3rdparty/md5/md5.h" #include "../../3rdparty/md5/md5.cpp" @@ -114,7 +114,7 @@ static inline int SHA384_512AddLength(SHA512Context *context, unsigned int lengt #endif #endif // QT_CRYPTOGRAPHICHASH_ONLY_SHA1 -#if !defined(QT_BOOTSTRAPPED) && QT_CONFIG(opensslv30) && QT_CONFIG(openssl_linked) +#if !defined(QT_BOOTSTRAPPED) && QT_CONFIG(openssl_hash) #define USING_OPENSSL30 #include #include @@ -1401,9 +1401,18 @@ void QMessageAuthenticationCodePrivate::initMessageHash() noexcept /*! Constructs an object that can be used to create a cryptographic hash from data using method \a method and key \a key. + +//! [qba-to-qbav-6.6] + \note In Qt versions prior to 6.6, this function took its arguments as + QByteArray, not QByteArrayView. If you experience compile errors, it's + because your code is passing objects that are implicitly convertible to + QByteArray, but not QByteArrayView. Wrap the corresponding argument in + \c{QByteArray{~~~}} to make the cast explicit. This is backwards-compatible + with old Qt versions. +//! [qba-to-qbav-6.6] */ QMessageAuthenticationCode::QMessageAuthenticationCode(QCryptographicHash::Algorithm method, - const QByteArray &key) + QByteArrayView key) : d(new QMessageAuthenticationCodePrivate(method)) { d->setKey(key); @@ -1417,10 +1426,43 @@ QMessageAuthenticationCode::~QMessageAuthenticationCode() delete d; } +/*! + \fn QMessageAuthenticationCode::QMessageAuthenticationCode(QMessageAuthenticationCode &&other) + + Move-constructs a new QMessageAuthenticationCode 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 object. + + \since 6.6 +*/ + +/*! + \fn QMessageAuthenticationCode &QMessageAuthenticationCode::operator=(QMessageAuthenticationCode &&other) + + Move-assigns \a other to this QMessageAuthenticationCode 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 object. + + \since 6.6 +*/ + +/*! + \fn void QMessageAuthenticationCode::swap(QMessageAuthenticationCode &other) + + Swaps message authentication code \a other with this message authentication + code. This operation is very fast and never fails. + + \since 6.6 +*/ + /*! Resets message data. Calling this method doesn't affect the key. */ -void QMessageAuthenticationCode::reset() +void QMessageAuthenticationCode::reset() noexcept { d->messageHash.reset(); d->initMessageHash(); @@ -1455,14 +1497,17 @@ void QMessageAuthenticationCode::reset() mac.emplace(method, key); use(*mac); \endcode + + \include qcryptographichash.cpp {qba-to-qbav-6.6} */ -void QMessageAuthenticationCode::setKey(const QByteArray &key) +void QMessageAuthenticationCode::setKey(QByteArrayView key) noexcept { d->messageHash.reset(); d->setKey(key); } /*! + \overload Adds the first \a length chars of \a data to the message. */ void QMessageAuthenticationCode::addData(const char *data, qsizetype length) @@ -1471,9 +1516,13 @@ void QMessageAuthenticationCode::addData(const char *data, qsizetype length) } /*! - \overload addData() + Adds \a data to the message. + + \include qcryptographichash.cpp {qba-to-qbav-6.6} + + \sa resultView(), result() */ -void QMessageAuthenticationCode::addData(const QByteArray &data) +void QMessageAuthenticationCode::addData(QByteArrayView data) noexcept { d->messageHash.addData(data); } @@ -1489,15 +1538,30 @@ bool QMessageAuthenticationCode::addData(QIODevice *device) return d->messageHash.addData(device); } +/*! + \since 6.6 + + Returns the final hash value. + + Note that the returned view remains valid only as long as the + QMessageAuthenticationCode object is not modified by other means. + + \sa result() +*/ +QByteArrayView QMessageAuthenticationCode::resultView() const noexcept +{ + d->finalize(); + return d->messageHash.resultView(); +} + /*! Returns the final authentication code. - \sa QByteArray::toHex() + \sa resultView(), QByteArray::toHex() */ QByteArray QMessageAuthenticationCode::result() const { - d->finalize(); - return d->messageHash.resultView().toByteArray(); + return resultView().toByteArray(); } void QMessageAuthenticationCodePrivate::finalize() @@ -1522,8 +1586,10 @@ void QMessageAuthenticationCodePrivate::finalizeUnchecked() noexcept /*! Returns the authentication code for the message \a message using the key \a key and the method \a method. + + \include qcryptographichash.cpp {qba-to-qbav-6.6} */ -QByteArray QMessageAuthenticationCode::hash(const QByteArray &message, const QByteArray &key, +QByteArray QMessageAuthenticationCode::hash(QByteArrayView message, QByteArrayView key, QCryptographicHash::Algorithm method) { QMessageAuthenticationCodePrivate mac(method); diff --git a/src/corelib/tools/qfunctionaltools_impl.h b/src/corelib/tools/qfunctionaltools_impl.h new file mode 100644 index 00000000..5878b90b --- /dev/null +++ b/src/corelib/tools/qfunctionaltools_impl.h @@ -0,0 +1,76 @@ +// 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 + +#if 0 +#pragma qt_sync_skip_header_check +#pragma qt_sync_stop_processing +#endif + +#ifndef QFUNCTIONALTOOLS_IMPL_H +#define QFUNCTIONALTOOLS_IMPL_H + +#include + +#include +#include + +QT_BEGIN_NAMESPACE + +namespace QtPrivate { + +namespace detail { + +#define FOR_EACH_CVREF(op) \ + op(&) \ + op(const &) \ + op(&&) \ + op(const &&) \ + /* end */ + + +template +struct StorageByValue +{ + Object o; +#define MAKE_GETTER(cvref) \ + constexpr Object cvref object() cvref noexcept \ + { return static_cast(o); } + FOR_EACH_CVREF(MAKE_GETTER) +#undef MAKE_GETTER +}; + +template +struct StorageEmptyBaseClassOptimization : Object +{ + StorageEmptyBaseClassOptimization(Object &&o) + : Object(std::move(o)) + {} + StorageEmptyBaseClassOptimization(const Object &o) + : Object(o) + {} + +#define MAKE_GETTER(cvref) \ + constexpr Object cvref object() cvref noexcept \ + { return static_cast(*this); } + FOR_EACH_CVREF(MAKE_GETTER) +#undef MAKE_GETTER +}; +} // namespace detail + +template +using CompactStorage = typename std::conditional_t< + std::conjunction_v< + std::is_empty, + std::negation> + >, + detail::StorageEmptyBaseClassOptimization, + detail::StorageByValue + >; + +} // namespace QtPrivate + +#undef FOR_EACH_CVREF + +QT_END_NAMESPACE + +#endif // QFUNCTIONALTOOLS_IMPL_H diff --git a/src/corelib/tools/qhash.h b/src/corelib/tools/qhash.h index 9b294295..a4de3e37 100644 --- a/src/corelib/tools/qhash.h +++ b/src/corelib/tools/qhash.h @@ -699,22 +699,10 @@ struct Data Node *findNode(const Key &key) const noexcept { - Q_ASSERT(numBuckets > 0); - size_t hash = QHashPrivate::calculateHash(key, seed); - Bucket bucket(this, GrowthPolicy::bucketForHash(numBuckets, hash)); - // loop over the buckets until we find the entry we search for - // or an empty slot, in which case we know the entry doesn't exist - while (true) { - size_t offset = bucket.offset(); - if (offset == SpanConstants::UnusedEntry) { - return nullptr; - } else { - Node &n = bucket.nodeAtOffset(offset); - if (qHashEquals(n.key, key)) - return &n; - } - bucket.advanceWrapped(this); - } + auto bucket = findBucket(key); + if (bucket.isUnused()) + return nullptr; + return bucket.node(); } struct InsertionResult diff --git a/src/corelib/tools/qiterator.h b/src/corelib/tools/qiterator.h index cff535d0..ab3d71f7 100644 --- a/src/corelib/tools/qiterator.h +++ b/src/corelib/tools/qiterator.h @@ -10,6 +10,13 @@ QT_BEGIN_NAMESPACE #if !defined(QT_NO_JAVA_STYLE_ITERATORS) +#ifdef Q_QDOC +#define Q_DISABLE_BACKWARD_ITERATOR +#else +#define Q_DISABLE_BACKWARD_ITERATOR \ + template = true> +#endif + #define Q_DECLARE_SEQUENTIAL_ITERATOR(C) \ \ template \ @@ -28,11 +35,15 @@ public: \ inline bool hasNext() const { return i != c.constEnd(); } \ inline const T &next() { return *i++; } \ inline const T &peekNext() const { return *i; } \ + Q_DISABLE_BACKWARD_ITERATOR \ inline bool hasPrevious() const { return i != c.constBegin(); } \ + Q_DISABLE_BACKWARD_ITERATOR \ inline const T &previous() { return *--i; } \ + Q_DISABLE_BACKWARD_ITERATOR \ inline const T &peekPrevious() const { const_iterator p = i; return *--p; } \ inline bool findNext(const T &t) \ { while (i != c.constEnd()) if (*i++ == t) return true; return false; } \ + Q_DISABLE_BACKWARD_ITERATOR \ inline bool findPrevious(const T &t) \ { while (i != c.constBegin()) if (*(--i) == t) return true; \ return false; } \ @@ -59,8 +70,11 @@ public: \ inline bool hasNext() const { return c->constEnd() != const_iterator(i); } \ inline T &next() { n = i++; return *n; } \ inline T &peekNext() const { return *i; } \ + Q_DISABLE_BACKWARD_ITERATOR \ inline bool hasPrevious() const { return c->constBegin() != const_iterator(i); } \ + Q_DISABLE_BACKWARD_ITERATOR \ inline T &previous() { n = --i; return *n; } \ + Q_DISABLE_BACKWARD_ITERATOR \ inline T &peekPrevious() const { iterator p = i; return *--p; } \ inline void remove() \ { if (c->constEnd() != const_iterator(n)) { i = c->erase(n); n = c->end(); } } \ @@ -70,6 +84,7 @@ public: \ inline void insert(const T &t) { n = i = c->insert(i, t); ++i; } \ inline bool findNext(const T &t) \ { while (c->constEnd() != const_iterator(n = i)) if (*i++ == t) return true; return false; } \ + Q_DISABLE_BACKWARD_ITERATOR \ inline bool findPrevious(const T &t) \ { while (c->constBegin() != const_iterator(i)) if (*(n = --i) == t) return true; \ n = c->end(); return false; } \ @@ -95,13 +110,17 @@ public: \ inline bool hasNext() const { return i != c.constEnd(); } \ inline Item next() { n = i++; return n; } \ inline Item peekNext() const { return i; } \ + Q_DISABLE_BACKWARD_ITERATOR \ inline bool hasPrevious() const { return i != c.constBegin(); } \ + Q_DISABLE_BACKWARD_ITERATOR \ inline Item previous() { n = --i; return n; } \ + Q_DISABLE_BACKWARD_ITERATOR \ inline Item peekPrevious() const { const_iterator p = i; return --p; } \ inline const T &value() const { Q_ASSERT(item_exists()); return *n; } \ inline const Key &key() const { Q_ASSERT(item_exists()); return n.key(); } \ inline bool findNext(const T &t) \ { while ((n = i) != c.constEnd()) if (*i++ == t) return true; return false; } \ + Q_DISABLE_BACKWARD_ITERATOR \ inline bool findPrevious(const T &t) \ { while (i != c.constBegin()) if (*(n = --i) == t) return true; \ n = c.constEnd(); return false; } \ @@ -129,8 +148,11 @@ public: \ inline bool hasNext() const { return const_iterator(i) != c->constEnd(); } \ inline Item next() { n = i++; return n; } \ inline Item peekNext() const { return i; } \ + Q_DISABLE_BACKWARD_ITERATOR \ inline bool hasPrevious() const { return const_iterator(i) != c->constBegin(); } \ + Q_DISABLE_BACKWARD_ITERATOR \ inline Item previous() { n = --i; return n; } \ + Q_DISABLE_BACKWARD_ITERATOR \ inline Item peekPrevious() const { iterator p = i; return --p; } \ inline void remove() \ { if (const_iterator(n) != c->constEnd()) { i = c->erase(n); n = c->end(); } } \ @@ -140,6 +162,7 @@ public: \ inline const Key &key() const { Q_ASSERT(item_exists()); return n.key(); } \ inline bool findNext(const T &t) \ { while (const_iterator(n = i) != c->constEnd()) if (*i++ == t) return true; return false; } \ + Q_DISABLE_BACKWARD_ITERATOR \ inline bool findPrevious(const T &t) \ { while (const_iterator(i) != c->constBegin()) if (*(n = --i) == t) return true; \ n = c->end(); return false; } \ diff --git a/src/corelib/tools/qiterator.qdoc b/src/corelib/tools/qiterator.qdoc index 3fc68e08..f84d1cb3 100644 --- a/src/corelib/tools/qiterator.qdoc +++ b/src/corelib/tools/qiterator.qdoc @@ -213,12 +213,8 @@ \image javaiterators1.png - Here's how to iterate over the elements in reverse order: - - \snippet code/doc_src_qiterator.cpp 7 - If you want to find all occurrences of a particular value, use - findNext() or findPrevious() in a loop. + findNext() in a loop. Multiple iterators can be used on the same set. If the set is modified while a QSetIterator is active, the QSetIterator @@ -389,15 +385,21 @@ */ /*! \fn template void QListIterator::toBack() - \fn template void QSetIterator::toBack() \fn template void QMutableListIterator::toBack() +//! [toBack] Moves the iterator to the back of the container (after the last item). +//! [toBack] \sa toFront(), previous() */ +/*! \fn template void QSetIterator::toBack() + \include qiterator.qdoc toBack + \sa toFront() +*/ + /*! \fn template void QMutableSetIterator::toBack() Moves the iterator to the back of the container (after the last @@ -407,16 +409,22 @@ */ /*! \fn template bool QListIterator::hasNext() const - \fn template bool QSetIterator::hasNext() const \fn template bool QMutableListIterator::hasNext() const +//! [hasNext] Returns \c true if there is at least one item ahead of the iterator, i.e. the iterator is \e not at the back of the container; otherwise returns \c false. +//! [hasNext] \sa hasPrevious(), next() */ +/*! \fn template bool QSetIterator::hasNext() const + \include qiterator.qdoc hasNext + \sa next() +*/ + /*! \fn template bool QMutableSetIterator::hasNext() const Returns \c true if there is at least one item ahead of the iterator, @@ -427,16 +435,23 @@ */ /*! \fn template const T &QListIterator::next() - \fn template const T &QSetIterator::next() +//! [next] Returns the next item and advances the iterator by one position. Calling this function on an iterator located at the back of the container leads to undefined results. +//! [next] \sa hasNext(), peekNext(), previous() */ +/*! + \fn template const T &QSetIterator::next() + \include qiterator.qdoc next + \sa hasNext(), peekNext() +*/ + /* \fn template const T &QMutableSetIterator::next() Returns the next item and advances the iterator by one position. @@ -468,16 +483,23 @@ */ /*! \fn template const T &QListIterator::peekNext() const - \fn template const T &QSetIterator::peekNext() const +//! [peekNext] Returns the next item without moving the iterator. Calling this function on an iterator located at the back of the container leads to undefined results. +//! [peekNext] \sa hasNext(), next(), peekPrevious() */ +/*! + \fn template const T &QSetIterator::peekNext() const + \include qiterator.qdoc peekNext + \sa hasNext(), next() +*/ + /*! \fn template const T &QMutableSetIterator::peekNext() const @@ -500,7 +522,6 @@ */ /*! \fn template bool QListIterator::hasPrevious() const - \fn template bool QSetIterator::hasPrevious() const \fn template bool QMutableListIterator::hasPrevious() const Returns \c true if there is at least one item behind the iterator, @@ -511,7 +532,6 @@ */ /*! \fn template const T &QListIterator::previous() - \fn template const T &QSetIterator::previous() Returns the previous item and moves the iterator back by one position. @@ -534,7 +554,6 @@ */ /*! \fn template const T &QListIterator::peekPrevious() const - \fn template const T &QSetIterator::peekPrevious() const Returns the previous item without moving the iterator. @@ -566,21 +585,25 @@ */ /*! \fn template bool QListIterator::findNext(const T &value) - \fn template bool QSetIterator::findNext(const T &value) \fn template bool QMutableListIterator::findNext(const T &value) +//! [findNext] Searches for \a value starting from the current iterator position forward. Returns \c true if \a value is found; otherwise returns \c false. After the call, if \a value was found, the iterator is positioned just after the matching item; otherwise, the iterator is positioned at the back of the container. +//! [findNext] \sa findPrevious() */ +/*! \fn template bool QSetIterator::findNext(const T &value) + \include qiterator.qdoc findNext +*/ + /*! \fn template bool QListIterator::findPrevious(const T &value) - \fn template bool QSetIterator::findPrevious(const T &value) \fn template bool QMutableListIterator::findPrevious(const T &value) Searches for \a value starting from the current iterator position diff --git a/src/corelib/tools/qlist.h b/src/corelib/tools/qlist.h index d61b9050..79de8fce 100644 --- a/src/corelib/tools/qlist.h +++ b/src/corelib/tools/qlist.h @@ -16,6 +16,8 @@ #include #include +class tst_QList; + QT_BEGIN_NAMESPACE namespace QtPrivate { @@ -75,10 +77,15 @@ class QList using DataPointer = QArrayDataPointer; class DisableRValueRefs {}; + friend class ::tst_QList; + DataPointer d; template friend qsizetype QtPrivate::indexOf(const QList &list, const U &u, qsizetype from) noexcept; template friend qsizetype QtPrivate::lastIndexOf(const QList &list, const U &u, qsizetype from) noexcept; + // This alias prevents the QtPrivate namespace from being exposed into the docs. + template + using if_input_iterator = QtPrivate::IfIsInputIterator; public: using Type = T; @@ -287,7 +294,8 @@ public: d->copyAppend(args.begin(), args.end()); return *this; } - template = true> + + template = true> QList(InputIterator i1, InputIterator i2) { if constexpr (!std::is_convertible_v::iterator_category, std::forward_iterator_tag>) { @@ -488,6 +496,19 @@ public: } } + QList &assign(qsizetype n, parameter_type t) + { + Q_ASSERT(n >= 0); + return fill(t, n); + } + + template = true> + QList &assign(InputIterator first, InputIterator last) + { d.assign(first, last); return *this; } + + QList &assign(std::initializer_list l) + { return assign(l.begin(), l.end()); } + template iterator emplace(const_iterator before, Args&&... args) { diff --git a/src/corelib/tools/qlist.qdoc b/src/corelib/tools/qlist.qdoc index 52aa359c..f0e24df2 100644 --- a/src/corelib/tools/qlist.qdoc +++ b/src/corelib/tools/qlist.qdoc @@ -274,11 +274,15 @@ Constructs a list from the std::initializer_list given by \a args. */ -/*! \fn template template QList::QList(InputIterator first, InputIterator last) +/*! \fn template template> QList::QList(InputIterator first, InputIterator last) \since 5.14 Constructs a list with the contents in the iterator range [\a first, \a last). + \note This constructor only participates in overload resolution if + \c InputIterator meets the requirements of a + \l {https://en.cppreference.com/w/cpp/named_req/InputIterator} {LegacyInputIterator}. + The value type of \c InputIterator must be convertible to \c T. */ @@ -1534,3 +1538,47 @@ \sa erase */ + +/*! \fn template QList::assign(qsizetype n, parameter_type t) + \since 6.6 + + Replaces the contents of this list with \a n copies of \a t. + + The size of this list will be equal to \a n. + + This function will only allocate memory if \a n exceeds the capacity of the + list or this list is shared. +*/ + +/*! \fn template template > QList::assign(InputIterator first, InputIterator last) + \since 6.6 + + Replaces the contents of this list with a copy of the elements in the + iterator range [\a first, \a last). + + The size of this list will be equal to the number of elements in the + range [\a first, \a last). + + This function will only allocate memory if the number of elements in the + range exceeds the capacity of this list or this list is shared. + + \note This function overload only participates in overload resolution if + \c InputIterator meets the requirements of a + \l {https://en.cppreference.com/w/cpp/named_req/InputIterator} {LegacyInputIterator}. + + \note The behavior is undefined if either argument is an iterator into + *this. +*/ + +/*! \fn template QList::assign(std::initializer_list l) + \since 6.6 + + Replaces the contents of this list with a copy of the elements of + \a l. + + The size of this list will be equal to the number of elements in + \a l. + + This function only allocates memory if the number of elements in \a l + exceeds the capacity of this list or this list is shared. +*/ diff --git a/src/corelib/tools/qmargins.h b/src/corelib/tools/qmargins.h index 2d494f44..f8d7150d 100644 --- a/src/corelib/tools/qmargins.h +++ b/src/corelib/tools/qmargins.h @@ -6,8 +6,13 @@ #include +#include +#include + QT_BEGIN_NAMESPACE +QT_ENABLE_P0846_SEMANTICS_FOR(get) + class QMarginsF; /***************************************************************************** @@ -66,17 +71,17 @@ private: template = true, - std::enable_if_t, QMargins>, bool> = true> + std::enable_if_t, QMargins>, bool> = true> friend constexpr decltype(auto) get(M &&m) noexcept { if constexpr (I == 0) - return (std::forward(m).m_left); + return q23::forward_like(m.m_left); else if constexpr (I == 1) - return (std::forward(m).m_top); + return q23::forward_like(m.m_top); else if constexpr (I == 2) - return (std::forward(m).m_right); + return q23::forward_like(m.m_right); else if constexpr (I == 3) - return (std::forward(m).m_bottom); + return q23::forward_like(m.m_bottom); } }; @@ -315,17 +320,17 @@ private: template = true, - std::enable_if_t, QMarginsF>, bool> = true> + std::enable_if_t, QMarginsF>, bool> = true> friend constexpr decltype(auto) get(M &&m) noexcept { if constexpr (I == 0) - return (std::forward(m).m_left); + return q23::forward_like(m.m_left); else if constexpr (I == 1) - return (std::forward(m).m_top); + return q23::forward_like(m.m_top); else if constexpr (I == 2) - return (std::forward(m).m_right); + return q23::forward_like(m.m_right); else if constexpr (I == 3) - return (std::forward(m).m_bottom); + return q23::forward_like(m.m_bottom); } }; diff --git a/src/corelib/tools/qmessageauthenticationcode.h b/src/corelib/tools/qmessageauthenticationcode.h index 3951a6e5..4e881387 100644 --- a/src/corelib/tools/qmessageauthenticationcode.h +++ b/src/corelib/tools/qmessageauthenticationcode.h @@ -16,22 +16,44 @@ class QIODevice; class Q_CORE_EXPORT QMessageAuthenticationCode { public: +#if QT_CORE_REMOVED_SINCE(6, 6) explicit QMessageAuthenticationCode(QCryptographicHash::Algorithm method, - const QByteArray &key = QByteArray()); + const QByteArray &key); +#endif + explicit QMessageAuthenticationCode(QCryptographicHash::Algorithm method, + QByteArrayView key = {}); + + QMessageAuthenticationCode(QMessageAuthenticationCode &&other) noexcept + : d{std::exchange(other.d, nullptr)} {} ~QMessageAuthenticationCode(); - void reset(); + QT_MOVE_ASSIGNMENT_OPERATOR_IMPL_VIA_MOVE_AND_SWAP(QMessageAuthenticationCode) + void swap(QMessageAuthenticationCode &other) noexcept + { qt_ptr_swap(d, other.d); } + void reset() noexcept; + +#if QT_CORE_REMOVED_SINCE(6, 6) void setKey(const QByteArray &key); +#endif + void setKey(QByteArrayView key) noexcept; void addData(const char *data, qsizetype length); +#if QT_CORE_REMOVED_SINCE(6, 6) void addData(const QByteArray &data); +#endif + void addData(QByteArrayView data) noexcept; bool addData(QIODevice *device); + QByteArrayView resultView() const noexcept; QByteArray result() const; +#if QT_CORE_REMOVED_SINCE(6, 6) static QByteArray hash(const QByteArray &message, const QByteArray &key, QCryptographicHash::Algorithm method); +#endif + static QByteArray hash(QByteArrayView message, QByteArrayView key, + QCryptographicHash::Algorithm method); private: Q_DISABLE_COPY(QMessageAuthenticationCode) diff --git a/src/corelib/tools/qpoint.h b/src/corelib/tools/qpoint.h index 48c5e91f..7df4d490 100644 --- a/src/corelib/tools/qpoint.h +++ b/src/corelib/tools/qpoint.h @@ -6,12 +6,17 @@ #include +#include +#include + #if defined(Q_OS_DARWIN) || defined(Q_QDOC) struct CGPoint; #endif QT_BEGIN_NAMESPACE +QT_ENABLE_P0846_SEMANTICS_FOR(get) + class QPointF; class QPoint @@ -86,13 +91,13 @@ private: template = true, - std::enable_if_t, QPoint>, bool> = true> + std::enable_if_t, QPoint>, bool> = true> friend constexpr decltype(auto) get(P &&p) noexcept { if constexpr (I == 0) - return (std::forward

(p).xp); + return q23::forward_like

(p.xp); else if constexpr (I == 1) - return (std::forward

(p).yp); + return q23::forward_like

(p.yp); } }; @@ -283,13 +288,13 @@ private: template = true, - std::enable_if_t, QPointF>, bool> = true> + std::enable_if_t, QPointF>, bool> = true> friend constexpr decltype(auto) get(P &&p) noexcept { if constexpr (I == 0) - return (std::forward

(p).xp); + return q23::forward_like

(p.xp); else if constexpr (I == 1) - return (std::forward

(p).yp); + return q23::forward_like

(p.yp); } }; diff --git a/src/corelib/tools/qscopedpointer.h b/src/corelib/tools/qscopedpointer.h index 1637bb40..59bae9b9 100644 --- a/src/corelib/tools/qscopedpointer.h +++ b/src/corelib/tools/qscopedpointer.h @@ -67,9 +67,10 @@ typedef QScopedPointerObjectDeleteLater QScopedPointerDeleteLater; #endif template > -class [[nodiscard]] QScopedPointer +class QScopedPointer { public: + Q_NODISCARD_CTOR explicit QScopedPointer(T *p = nullptr) noexcept : d(p) { } @@ -187,15 +188,17 @@ private: }; template > -class [[nodiscard]] QScopedArrayPointer : public QScopedPointer +class QScopedArrayPointer : public QScopedPointer { template using if_same_type = typename std::enable_if::type, Ptr>::value, bool>::type; public: + Q_NODISCARD_CTOR inline QScopedArrayPointer() : QScopedPointer(nullptr) {} inline ~QScopedArrayPointer() = default; template = true> + Q_NODISCARD_CTOR explicit QScopedArrayPointer(D *p) : QScopedPointer(p) { diff --git a/src/corelib/tools/qscopedvaluerollback.h b/src/corelib/tools/qscopedvaluerollback.h index a4a451b0..0ae3efd0 100644 --- a/src/corelib/tools/qscopedvaluerollback.h +++ b/src/corelib/tools/qscopedvaluerollback.h @@ -9,17 +9,20 @@ QT_BEGIN_NAMESPACE template -class [[nodiscard]] QScopedValueRollback +class QScopedValueRollback { public: + Q_NODISCARD_CTOR explicit constexpr QScopedValueRollback(T &var) : varRef(var), oldValue(var) { } + Q_NODISCARD_CTOR explicit constexpr QScopedValueRollback(T &var, T value) - : varRef(var), oldValue(qExchange(var, std::move(value))) + : varRef(var), oldValue(std::move(var)) // ### C++20: std::exchange(var, std::move(value)) { + var = std::move(value); } #if __cpp_constexpr >= 201907L diff --git a/src/corelib/tools/qscopeguard.h b/src/corelib/tools/qscopeguard.h index 5bd202ce..9be6634c 100644 --- a/src/corelib/tools/qscopeguard.h +++ b/src/corelib/tools/qscopeguard.h @@ -13,19 +13,22 @@ QT_BEGIN_NAMESPACE template -class [[nodiscard]] QScopeGuard +class QScopeGuard { public: + Q_NODISCARD_CTOR explicit QScopeGuard(F &&f) noexcept : m_func(std::move(f)) { } + Q_NODISCARD_CTOR explicit QScopeGuard(const F &f) noexcept : m_func(f) { } + Q_NODISCARD_CTOR QScopeGuard(QScopeGuard &&other) noexcept : m_func(std::move(other.m_func)) , m_invoke(std::exchange(other.m_invoke, false)) diff --git a/src/corelib/tools/qshareddata.h b/src/corelib/tools/qshareddata.h index 826152b6..4c4153a5 100644 --- a/src/corelib/tools/qshareddata.h +++ b/src/corelib/tools/qshareddata.h @@ -51,13 +51,17 @@ public: const T *constData() const noexcept { return d; } T *take() noexcept { return std::exchange(d, nullptr); } + Q_NODISCARD_CTOR QSharedDataPointer() noexcept : d(nullptr) { } ~QSharedDataPointer() { if (d && !d->ref.deref()) delete d; } + Q_NODISCARD_CTOR explicit QSharedDataPointer(T *data) noexcept : d(data) { if (d) d->ref.ref(); } + Q_NODISCARD_CTOR QSharedDataPointer(T *data, QAdoptSharedDataTag) noexcept : d(data) {} + Q_NODISCARD_CTOR QSharedDataPointer(const QSharedDataPointer &o) noexcept : d(o.d) { if (d) d->ref.ref(); } @@ -82,6 +86,7 @@ public: reset(o); return *this; } + Q_NODISCARD_CTOR QSharedDataPointer(QSharedDataPointer &&o) noexcept : d(std::exchange(o.d, nullptr)) {} QT_MOVE_ASSIGNMENT_OPERATOR_IMPL_VIA_MOVE_AND_SWAP(QSharedDataPointer) @@ -139,17 +144,22 @@ public: void detach() { if (d && d->ref.loadRelaxed() != 1) detach_helper(); } + Q_NODISCARD_CTOR QExplicitlySharedDataPointer() noexcept : d(nullptr) { } ~QExplicitlySharedDataPointer() { if (d && !d->ref.deref()) delete d; } + Q_NODISCARD_CTOR explicit QExplicitlySharedDataPointer(T *data) noexcept : d(data) { if (d) d->ref.ref(); } + Q_NODISCARD_CTOR QExplicitlySharedDataPointer(T *data, QAdoptSharedDataTag) noexcept : d(data) {} + Q_NODISCARD_CTOR QExplicitlySharedDataPointer(const QExplicitlySharedDataPointer &o) noexcept : d(o.d) { if (d) d->ref.ref(); } template + Q_NODISCARD_CTOR QExplicitlySharedDataPointer(const QExplicitlySharedDataPointer &o) noexcept #ifdef QT_ENABLE_QEXPLICITLYSHAREDDATAPOINTER_STATICCAST : d(static_cast(o.data())) @@ -179,6 +189,7 @@ public: reset(o); return *this; } + Q_NODISCARD_CTOR QExplicitlySharedDataPointer(QExplicitlySharedDataPointer &&o) noexcept : d(std::exchange(o.d, nullptr)) {} QT_MOVE_ASSIGNMENT_OPERATOR_IMPL_VIA_MOVE_AND_SWAP(QExplicitlySharedDataPointer) diff --git a/src/corelib/tools/qsharedpointer.cpp b/src/corelib/tools/qsharedpointer.cpp index 3fa9bbd0..7ed38471 100644 --- a/src/corelib/tools/qsharedpointer.cpp +++ b/src/corelib/tools/qsharedpointer.cpp @@ -1463,7 +1463,7 @@ QT_END_NAMESPACE # ifdef QT_SHARED_POINTER_BACKTRACE_SUPPORT # if defined(__GLIBC__) && (__GLIBC__ >= 2) && !defined(__UCLIBC__) && !defined(QT_LINUXBASE) # define BACKTRACE_SUPPORTED -# elif defined(Q_OS_MAC) +# elif defined(Q_OS_DARWIN) # define BACKTRACE_SUPPORTED # endif # endif diff --git a/src/corelib/tools/qsharedpointer_impl.h b/src/corelib/tools/qsharedpointer_impl.h index 9777b1a0..7927a6dd 100644 --- a/src/corelib/tools/qsharedpointer_impl.h +++ b/src/corelib/tools/qsharedpointer_impl.h @@ -276,23 +276,29 @@ public: T &operator*() const { return *data(); } T *operator->() const noexcept { return data(); } + Q_NODISCARD_CTOR constexpr QSharedPointer() noexcept : value(nullptr), d(nullptr) { } ~QSharedPointer() { deref(); } + Q_NODISCARD_CTOR constexpr QSharedPointer(std::nullptr_t) noexcept : value(nullptr), d(nullptr) { } template = true> + Q_NODISCARD_CTOR inline explicit QSharedPointer(X *ptr) : value(ptr) // noexcept { internalConstruct(ptr, QtSharedPointer::NormalDeleter()); } template = true> + Q_NODISCARD_CTOR inline QSharedPointer(X *ptr, Deleter deleter) : value(ptr) // throws { internalConstruct(ptr, deleter); } template + Q_NODISCARD_CTOR QSharedPointer(std::nullptr_t, Deleter deleter) : value(nullptr) { internalConstruct(static_cast(nullptr), deleter); } + Q_NODISCARD_CTOR QSharedPointer(const QSharedPointer &other) noexcept : value(other.value), d(other.d) { if (d) ref(); } QSharedPointer &operator=(const QSharedPointer &other) noexcept @@ -301,6 +307,7 @@ public: swap(copy); return *this; } + Q_NODISCARD_CTOR QSharedPointer(QSharedPointer &&other) noexcept : value(other.value), d(other.d) { @@ -310,6 +317,7 @@ public: QT_MOVE_ASSIGNMENT_OPERATOR_IMPL_VIA_MOVE_AND_SWAP(QSharedPointer) template = true> + Q_NODISCARD_CTOR QSharedPointer(QSharedPointer &&other) noexcept : value(other.value), d(other.d) { @@ -326,6 +334,7 @@ public: } template = true> + Q_NODISCARD_CTOR QSharedPointer(const QSharedPointer &other) noexcept : value(other.value), d(other.d) { if (d) ref(); } @@ -338,6 +347,7 @@ public: } template = true> + Q_NODISCARD_CTOR inline QSharedPointer(const QWeakPointer &other) : value(nullptr), d(nullptr) { *this = other; } @@ -434,6 +444,7 @@ public: #undef DECLARE_COMPARE_SET private: + Q_NODISCARD_CTOR explicit QSharedPointer(Qt::Initialization) {} void deref() noexcept @@ -539,11 +550,14 @@ public: explicit operator bool() const noexcept { return !isNull(); } bool operator !() const noexcept { return isNull(); } + Q_NODISCARD_CTOR constexpr QWeakPointer() noexcept : d(nullptr), value(nullptr) { } inline ~QWeakPointer() { if (d && !d->weakref.deref()) delete d; } + Q_NODISCARD_CTOR QWeakPointer(const QWeakPointer &other) noexcept : d(other.d), value(other.value) { if (d) d->weakref.ref(); } + Q_NODISCARD_CTOR QWeakPointer(QWeakPointer &&other) noexcept : d(other.d), value(other.value) { @@ -553,6 +567,7 @@ public: QT_MOVE_ASSIGNMENT_OPERATOR_IMPL_VIA_MOVE_AND_SWAP(QWeakPointer) template = true> + Q_NODISCARD_CTOR QWeakPointer(QWeakPointer &&other) noexcept : d(other.d), value(other.value) { @@ -581,6 +596,7 @@ public: qt_ptr_swap(this->value, other.value); } + Q_NODISCARD_CTOR inline QWeakPointer(const QSharedPointer &o) : d(o.d), value(o.data()) { if (d) d->weakref.ref();} inline QWeakPointer &operator=(const QSharedPointer &o) @@ -590,6 +606,7 @@ public: } template = true> + Q_NODISCARD_CTOR inline QWeakPointer(const QWeakPointer &o) : d(nullptr), value(nullptr) { *this = o; } @@ -603,6 +620,7 @@ public: } template = true> + Q_NODISCARD_CTOR inline QWeakPointer(const QSharedPointer &o) : d(nullptr), value(nullptr) { *this = o; } @@ -663,6 +681,7 @@ private: #ifndef QT_NO_QOBJECT template = true> + Q_NODISCARD_CTOR inline QWeakPointer(X *ptr, bool) : d(ptr ? Data::getAndRef(ptr) : nullptr), value(ptr) { } #endif diff --git a/src/corelib/tools/qsize.h b/src/corelib/tools/qsize.h index 71066c35..a5eaf34a 100644 --- a/src/corelib/tools/qsize.h +++ b/src/corelib/tools/qsize.h @@ -8,12 +8,17 @@ #include #include +#include +#include + #if defined(Q_OS_DARWIN) || defined(Q_QDOC) struct CGSize; #endif QT_BEGIN_NAMESPACE +// QT_ENABLE_P0846_SEMANTICS_FOR(get) // from qmargins.h + class QSizeF; class Q_CORE_EXPORT QSize @@ -83,13 +88,13 @@ private: template = true, - std::enable_if_t, QSize>, bool> = true> + std::enable_if_t, QSize>, bool> = true> friend constexpr decltype(auto) get(S &&s) noexcept { if constexpr (I == 0) - return (std::forward(s).wd); + return q23::forward_like(s.wd); else if constexpr (I == 1) - return (std::forward(s).ht); + return q23::forward_like(s.ht); } }; Q_DECLARE_TYPEINFO(QSize, Q_RELOCATABLE_TYPE); @@ -272,13 +277,13 @@ private: template = true, - std::enable_if_t, QSizeF>, bool> = true> + std::enable_if_t, QSizeF>, bool> = true> friend constexpr decltype(auto) get(S &&s) noexcept { if constexpr (I == 0) - return (std::forward(s).wd); + return q23::forward_like(s.wd); else if constexpr (I == 1) - return (std::forward(s).ht); + return q23::forward_like(s.ht); } }; Q_DECLARE_TYPEINFO(QSizeF, Q_RELOCATABLE_TYPE); diff --git a/src/corelib/tools/qspan_p.h b/src/corelib/tools/qspan_p.h new file mode 100644 index 00000000..c2fc6386 --- /dev/null +++ b/src/corelib/tools/qspan_p.h @@ -0,0 +1,371 @@ +// 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 QSPAN_P_H +#define QSPAN_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 +#include + +#include +#include +#include +#include +#include +#ifdef __cpp_lib_span +#include +#endif +#include + +QT_BEGIN_NAMESPACE + +// like std::dynamic_extent +namespace q20 { + inline constexpr std::size_t dynamic_extent = -1; +} // namespace q20 + +template class QSpan; + +namespace QSpanPrivate { + +template class QSpanBase; + +template +struct is_qspan_helper : std::false_type {}; +template +struct is_qspan_helper> : std::true_type {}; +template +struct is_qspan_helper> : std::true_type {}; +template +using is_qspan = is_qspan_helper>; + +template +struct is_std_array_helper : std::false_type {}; +template +struct is_std_array_helper> : std::true_type {}; +template +using is_std_array = is_std_array_helper>; + +template +using is_qualification_conversion = + std::is_convertible; // https://eel.is/c++draft/span.cons#note-1 +template +constexpr inline bool is_qualification_conversion_v = is_qualification_conversion::value; + +// Replacements for std::ranges::XXX(), but only bringing in ADL XXX()s, +// not doing the extra work C++20 requires +template +decltype(auto) adl_begin(Range &&r) { using std::begin; return begin(r); } +template +decltype(auto) adl_data(Range &&r) { using std::data; return data(r); } +template +decltype(auto) adl_size(Range &&r) { using std::size; return size(r); } + +// Replacement for std::ranges::iterator_t (which depends on C++20 std::ranges::begin) +// This one uses adl_begin() instead. +template +using iterator_t = decltype(QSpanPrivate::adl_begin(std::declval())); +template +using range_reference_t = q20::iter_reference_t>; + +template +class QSpanCommon { +protected: + template + using is_compatible_iterator = std::conjunction< + std::is_base_of< + std::random_access_iterator_tag, + typename std::iterator_traits::iterator_category + >, + is_qualification_conversion< + std::remove_reference_t>, + T + > + >; + template + using is_compatible_iterator_and_sentinel = std::conjunction< + is_compatible_iterator, + std::negation> + >; + template // wrap use of SFINAE-unfriendly iterator_t: + struct is_compatible_range_helper : std::false_type {}; + template + struct is_compatible_range_helper>> + : is_compatible_iterator> {}; + template + using is_compatible_range = std::conjunction< + // ### this needs more work, esp. extension to C++20 contiguous iterators + std::negation>, + std::negation>, + std::negation>>, + is_compatible_range_helper + >; + + // constraints + template + using if_compatible_iterator = std::enable_if_t< + is_compatible_iterator::value + , bool>; + template + using if_compatible_iterator_and_sentinel = std::enable_if_t< + is_compatible_iterator_and_sentinel::value + , bool>; + template + using if_compatible_range = std::enable_if_t::value, bool>; +}; // class QSpanCommon + +template +class QSpanBase : protected QSpanCommon +{ + static_assert(E < size_t{(std::numeric_limits::max)()}, + "QSpan only supports extents that fit into the signed size type (qsizetype)."); + + struct Enabled_t { explicit Enabled_t() = default; }; + static inline constexpr Enabled_t Enable{}; + + template + using if_compatible_array = std::enable_if_t< + N == E && is_qualification_conversion_v + , bool>; + + template + using if_qualification_conversion = std::enable_if_t< + is_qualification_conversion_v + , bool>; +protected: + using Base = QSpanCommon; + + // data members: + T *m_data; + static constexpr qsizetype m_size = qsizetype(E); + + // types and constants: + + static constexpr size_t extent = E; + + // constructors (need to be public d/t the way ctor inheriting works): +public: + template = true> + Q_IMPLICIT constexpr QSpanBase() noexcept : m_data{nullptr} {} + + template = true> + explicit constexpr QSpanBase(It first, qsizetype count) + : m_data{q20::to_address(first)} + { + Q_ASSERT(count == m_size); + } + + template = true> + explicit constexpr QSpanBase(It first, End last) + : QSpanBase(first, last - first) {} + + template = true> + Q_IMPLICIT constexpr QSpanBase(q20::type_identity_t (&arr)[N]) noexcept + : QSpanBase(arr, N) {} + + template = true> + Q_IMPLICIT constexpr QSpanBase(std::array &arr) noexcept + : QSpanBase(arr.data(), N) {} + + template = true> + Q_IMPLICIT constexpr QSpanBase(const std::array &arr) noexcept + : QSpanBase(arr.data(), N) {} + + template = true> + Q_IMPLICIT constexpr QSpanBase(Range &&r) + : QSpanBase(QSpanPrivate::adl_data(r), // no forward<>() here (std doesn't have it, either) + qsizetype(QSpanPrivate::adl_size(r))) // ditto, no forward<>() + {} + + template = true> + Q_IMPLICIT constexpr QSpanBase(QSpan other) noexcept + : QSpanBase(other.data(), other.size()) + {} + + template = true> + Q_IMPLICIT constexpr QSpanBase(QSpan other) + : QSpanBase(other.data(), other.size()) + {} + +}; // class QSpanBase (fixed extent) + +template +class QSpanBase : protected QSpanCommon +{ + template + using if_qualification_conversion = std::enable_if_t< + is_qualification_conversion_v + , bool>; +protected: + using Base = QSpanCommon; + + // data members: + T *m_data; + qsizetype m_size; + + // constructors (need to be public d/t the way ctor inheriting works): +public: + Q_IMPLICIT constexpr QSpanBase() noexcept : m_data{nullptr}, m_size{0} {} + + template = true> + Q_IMPLICIT constexpr QSpanBase(It first, qsizetype count) + : m_data{q20::to_address(first)}, m_size{count} {} + + template = true> + Q_IMPLICIT constexpr QSpanBase(It first, End last) + : QSpanBase(first, last - first) {} + + template + Q_IMPLICIT constexpr QSpanBase(q20::type_identity_t (&arr)[N]) noexcept + : QSpanBase(arr, N) {} + + template = true> + Q_IMPLICIT constexpr QSpanBase(std::array &arr) noexcept + : QSpanBase(arr.data(), N) {} + + template = true> + Q_IMPLICIT constexpr QSpanBase(const std::array &arr) noexcept + : QSpanBase(arr.data(), N) {} + + template = true> + Q_IMPLICIT constexpr QSpanBase(Range &&r) + : QSpanBase(QSpanPrivate::adl_data(r), // no forward<>() here (std doesn't have it, either) + qsizetype(QSpanPrivate::adl_size(r))) // ditto, no forward<>() + {} + + template = true> + Q_IMPLICIT constexpr QSpanBase(QSpan other) noexcept + : QSpanBase(other.data(), other.size()) + {} +}; // class QSpanBase (dynamic extent) + +} // namespace QSpanPrivate + +template +class QSpan : private QSpanPrivate::QSpanBase +{ + using Base = QSpanPrivate::QSpanBase; + Q_ALWAYS_INLINE constexpr void verify([[maybe_unused]] qsizetype pos = 0, + [[maybe_unused]] qsizetype n = 1) const + { + Q_ASSERT(pos >= 0); + Q_ASSERT(pos <= size()); + Q_ASSERT(n >= 0); + Q_ASSERT(n <= size() - pos); + } + + template + static constexpr bool subspan_always_succeeds_v = N <= E && E != q20::dynamic_extent; +public: + // constants and types + using element_type = T; + using value_type = std::remove_cv_t; + using size_type = qsizetype; // difference to std::span + using difference_type = qptrdiff; // difference to std::span + using pointer = element_type*; + using const_pointer = const element_type*; + using reference = element_type&; + using const_reference = const element_type&; + using iterator = pointer; // implementation-defined choice + using reverse_iterator = std::reverse_iterator; + static constexpr size_type extent = E; + + // [span.cons], constructors, copy, and assignment + using Base::Base; + + // [span.obs] + [[nodiscard]] constexpr size_type size() const noexcept { return this->m_size; } + [[nodiscard]] constexpr size_type size_bytes() const noexcept { return size() * sizeof(T); } + [[nodiscard]] constexpr bool empty() const noexcept { return size() == 0; } + + // [span.elem] + [[nodiscard]] constexpr reference operator[](size_type idx) const + { verify(idx); return data()[idx]; } + [[nodiscard]] constexpr reference front() const { verify(); return *data(); } + [[nodiscard]] constexpr reference back() const { verify(); return data()[size() - 1]; } + [[nodiscard]] constexpr pointer data() const noexcept { return this->m_data; } + + // [span.iterators] + [[nodiscard]] constexpr auto begin() const noexcept { return data(); } + [[nodiscard]] constexpr auto end() const noexcept { return data() + size(); } + [[nodiscard]] constexpr auto rbegin() const noexcept { return reverse_iterator{end()}; } + [[nodiscard]] constexpr auto rend() const noexcept { return reverse_iterator{begin()}; } + + // [span.sub] + template + [[nodiscard]] constexpr QSpan first() const + noexcept(subspan_always_succeeds_v) + { + static_assert(Count <= E, + "Count cannot be larger than the span's extent."); + verify(0, Count); + return QSpan{data(), Count}; + } + + template + [[nodiscard]] constexpr QSpan last() const + noexcept(subspan_always_succeeds_v) + { + static_assert(Count <= E, + "Count cannot be larger than the span's extent."); + verify(0, Count); + return QSpan{data() + (size() - Count), Count}; + } + + template + [[nodiscard]] constexpr auto subspan() const + noexcept(subspan_always_succeeds_v) + { + static_assert(Offset <= E, + "Offset cannot be larger than the span's extent."); + verify(Offset, 0); + if constexpr (E == q20::dynamic_extent) + return QSpan{data() + Offset, qsizetype(size() - Offset)}; + else + return QSpan{data() + Offset, qsizetype(E - Offset)}; + } + + template + [[nodiscard]] constexpr auto subspan() const + noexcept(subspan_always_succeeds_v) + { return subspan().template first(); } + + [[nodiscard]] constexpr QSpan first(size_type n) const { verify(0, n); return {data(), n}; } + [[nodiscard]] constexpr QSpan last(size_type n) const { verify(0, n); return {data() + (size() - n), n}; } + [[nodiscard]] constexpr QSpan subspan(size_type pos) const { verify(pos, 0); return {data() + pos, size() - pos}; } + [[nodiscard]] constexpr QSpan subspan(size_type pos, size_type n) const { return subspan(pos).first(n); } + + // Qt-compatibility API: + [[nodiscard]] bool isEmpty() const noexcept { return empty(); } + // nullary first()/last() clash with first<>() and last<>(), so they're not provided for QSpan + [[nodiscard]] constexpr QSpan sliced(size_type pos) const { return subspan(pos); } + [[nodiscard]] constexpr QSpan sliced(size_type pos, size_type n) const { return subspan(pos, n); } + +}; // class QSpan + +// [span.deduct] +template +QSpan(It, EndOrSize) -> QSpan>>; +template +QSpan(T (&)[N]) -> QSpan; +template +QSpan(std::array &) -> QSpan; +template +QSpan(const std::array &) -> QSpan; +template +QSpan(R&&) -> QSpan>>; + +QT_END_NAMESPACE + +#endif // QSPAN_P_H diff --git a/src/corelib/tools/qtaggedpointer.h b/src/corelib/tools/qtaggedpointer.h index bc43f765..6c467d59 100644 --- a/src/corelib/tools/qtaggedpointer.h +++ b/src/corelib/tools/qtaggedpointer.h @@ -43,10 +43,10 @@ public: static constexpr quintptr tagMask() { return QtPrivate::TagInfo::alignment - 1; } static constexpr quintptr pointerMask() { return ~tagMask(); } - constexpr QTaggedPointer() noexcept : d(0) {} - constexpr QTaggedPointer(std::nullptr_t) noexcept : QTaggedPointer() {} + Q_NODISCARD_CTOR constexpr QTaggedPointer() noexcept : d(0) {} + Q_NODISCARD_CTOR constexpr QTaggedPointer(std::nullptr_t) noexcept : QTaggedPointer() {} - explicit QTaggedPointer(T *pointer, Tag tag = Tag()) noexcept + Q_NODISCARD_CTOR explicit QTaggedPointer(T *pointer, Tag tag = Tag()) noexcept : d(quintptr(pointer) | quintptr(tag)) { static_assert(sizeof(Type*) == sizeof(QTaggedPointer)); diff --git a/src/corelib/tools/qtimeline.cpp b/src/corelib/tools/qtimeline.cpp index 8b9d5428..5512da86 100644 --- a/src/corelib/tools/qtimeline.cpp +++ b/src/corelib/tools/qtimeline.cpp @@ -292,7 +292,7 @@ QTimeLine::State QTimeLine::state() const \property QTimeLine::loopCount \brief the number of times the timeline should loop before it's finished. - A loop count of of 0 means that the timeline will loop forever. + A loop count of 0 means that the timeline will loop forever. By default, this property contains a value of 1. */ diff --git a/src/corelib/tools/qtools_p.h b/src/corelib/tools/qtools_p.h index b2d63a66..93432a95 100644 --- a/src/corelib/tools/qtools_p.h +++ b/src/corelib/tools/qtools_p.h @@ -16,17 +16,20 @@ // #include "QtCore/private/qglobal_p.h" + +#include #include +#include QT_BEGIN_NAMESPACE namespace QtMiscUtils { -constexpr inline char toHexUpper(uint value) noexcept +constexpr inline char toHexUpper(char32_t value) noexcept { return "0123456789ABCDEF"[value & 0xF]; } -constexpr inline char toHexLower(uint value) noexcept +constexpr inline char toHexLower(char32_t value) noexcept { return "0123456789abcdef"[value & 0xF]; } @@ -34,11 +37,11 @@ constexpr inline char toHexLower(uint value) noexcept [[nodiscard]] constexpr inline bool isHexDigit(char32_t c) noexcept { return (c >= '0' && c <= '9') - || (c >= 'A' && c <= 'F') - || (c >= 'a' && c <= 'f'); + || (c >= 'A' && c <= 'F') + || (c >= 'a' && c <= 'f'); } -constexpr inline int fromHex(uint c) noexcept +constexpr inline int fromHex(char32_t c) noexcept { return ((c >= '0') && (c <= '9')) ? int(c - '0') : ((c >= 'A') && (c <= 'F')) ? int(c - 'A' + 10) : @@ -46,7 +49,7 @@ constexpr inline int fromHex(uint c) noexcept /* otherwise */ -1; } -constexpr inline char toOct(uint value) noexcept +constexpr inline char toOct(char32_t value) noexcept { return char('0' + (value & 0x7)); } @@ -56,7 +59,7 @@ constexpr inline char toOct(uint value) noexcept return c >= '0' && c <= '7'; } -constexpr inline int fromOct(uint c) noexcept +constexpr inline int fromOct(char32_t c) noexcept { return isOctalDigit(c) ? int(c - '0') : -1; } @@ -109,7 +112,8 @@ constexpr inline int qt_lencmp(qsizetype lhs, qsizetype rhs) noexcept lhs > rhs ? 1 : /* else */ -1 ; } -} + +} // namespace QtMiscUtils // We typically need an extra bit for qNextPowerOfTwo when determining the next allocation size. constexpr qsizetype MaxAllocSize = (std::numeric_limits::max)(); diff --git a/src/corelib/tools/qvarlengtharray.h b/src/corelib/tools/qvarlengtharray.h index 02e74f3d..1759fba4 100644 --- a/src/corelib/tools/qvarlengtharray.h +++ b/src/corelib/tools/qvarlengtharray.h @@ -243,6 +243,10 @@ protected: } } + void assign_impl(qsizetype prealloc, void *array, qsizetype n, const T &t); + template + void assign_impl(qsizetype prealloc, void *array, Iterator first, Iterator last); + bool isValidIterator(const const_iterator &i) const { const std::less less = {}; @@ -253,8 +257,13 @@ protected: // Prealloc = 256 by default, specified in qcontainerfwd.h template class QVarLengthArray - : public QVLABase, // ### Qt 7: swap base class order +#if QT_VERSION >= QT_VERSION_CHECK(7,0,0) || defined(QT_BOOTSTRAPPED) + : public QVLAStorage, + public QVLABase +#else + : public QVLABase, public QVLAStorage +#endif { template friend class QVarLengthArray; @@ -266,6 +275,8 @@ class QVarLengthArray template using if_copyable = std::enable_if_t, bool>; + template + using if_input_iterator = QtPrivate::IfIsInputIterator; public: using size_type = typename Base::size_type; using value_type = typename Base::value_type; @@ -327,7 +338,7 @@ public: { } - template = true> + template = true> inline QVarLengthArray(InputIterator first, InputIterator last) : QVarLengthArray() { @@ -374,9 +385,7 @@ public: QVarLengthArray &operator=(std::initializer_list list) { - resize(qsizetype(list.size())); - std::copy(list.begin(), list.end(), - QT_MAKE_CHECKED_ARRAY_ITERATOR(begin(), size())); + assign(list); return *this; } @@ -491,6 +500,15 @@ public: void insert(qsizetype i, T &&t); void insert(qsizetype i, const T &t); void insert(qsizetype i, qsizetype n, const T &t); + + QVarLengthArray &assign(qsizetype n, const T &t) + { Base::assign_impl(Prealloc, this->array, n, t); return *this; } + template = true> + QVarLengthArray &assign(InputIterator first, InputIterator last) + { Base::assign_impl(Prealloc, this->array, first, last); return *this; } + QVarLengthArray &assign(std::initializer_list list) + { assign(list.begin(), list.end()); return *this; } + #ifdef Q_QDOC void replace(qsizetype i, const T &t); void remove(qsizetype i, qsizetype n = 1); @@ -741,6 +759,61 @@ Q_OUTOFLINE_TEMPLATE void QVLABase::append_impl(qsizetype prealloc, void *arr this->s = asize; } +template +Q_OUTOFLINE_TEMPLATE void QVLABase::assign_impl(qsizetype prealloc, void *array, qsizetype n, const T &t) +{ + Q_ASSERT(n >= 0); + if (n > capacity()) { + reallocate_impl(prealloc, array, 0, capacity()); // clear + resize_impl(prealloc, array, n, t); + } else { + auto mid = (std::min)(n, size()); + std::fill(data(), data() + mid, t); + std::uninitialized_fill(data() + mid, data() + n, t); + s = n; + erase(data() + n, data() + size()); + } +} + +template +template +Q_OUTOFLINE_TEMPLATE void QVLABase::assign_impl(qsizetype prealloc, void *array, Iterator first, Iterator last) +{ + // This function only provides the basic exception guarantee. + constexpr bool IsFwdIt = + std::is_convertible_v::iterator_category, + std::forward_iterator_tag>; + if constexpr (IsFwdIt) { + const qsizetype n = std::distance(first, last); + if (n > capacity()) + reallocate_impl(prealloc, array, 0, n); // clear & reserve n + } + + auto dst = begin(); + const auto dend = end(); + while (true) { + if (first == last) { // ran out of elements to assign + std::destroy(dst, dend); + break; + } + if (dst == dend) { // ran out of existing elements to overwrite + if constexpr (IsFwdIt) { + dst = std::uninitialized_copy(first, last, dst); + break; + } else { + do { + emplace_back_impl(prealloc, array, *first); + } while (++first != last); + return; // size() is already correct (and dst invalidated)! + } + } + *dst = *first; // overwrite existing element + ++dst; + ++first; + } + this->s = dst - begin(); +} + template Q_OUTOFLINE_TEMPLATE void QVLABase::reallocate_impl(qsizetype prealloc, void *array, qsizetype asize, qsizetype aalloc) { diff --git a/src/corelib/tools/qvarlengtharray.qdoc b/src/corelib/tools/qvarlengtharray.qdoc index 4f41304c..70dc4168 100644 --- a/src/corelib/tools/qvarlengtharray.qdoc +++ b/src/corelib/tools/qvarlengtharray.qdoc @@ -103,11 +103,15 @@ lists. */ -/*! \fn template template QVarLengthArray::QVarLengthArray(InputIterator first, InputIterator last) +/*! \fn template template> QVarLengthArray::QVarLengthArray(InputIterator first, InputIterator last) \since 5.14 Constructs an array with the contents in the iterator range [\a first, \a last). + This constructor only participates in overload resolution if + \c InputIterator meets the requirements of an + \l {https://en.cppreference.com/w/cpp/named_req/InputIterator} {LegacyInputIterator}. + The value type of \c InputIterator must be convertible to \c T. */ @@ -992,3 +996,40 @@ \sa erase() */ + +/*! \fn template QVarLengthArray::assign(qsizetype n, const T &t) + \since 6.6 + + Replaces the contents of this container with \a n copies of \a t. + + The size of this container will be equal to \a n. This function will only + allocate memory if \a n exceeds the capacity of the container. +*/ + +/*! \fn template template > QVarLengthArray::assign(InputIterator first, InputIterator last) + \since 6.6 + + Replaces the contents of this container with a copy of the elements in the + iterator range [\a first, \a last). + + The size of this container will be equal to the number of elements in the + range [\a first, \a last). This function will only allocate memory if the + number of elements in the range exceeds the capacity of the container. + + This function overload only participates in overload resolution if + \c InputIterator meets the requirements of an + \l {https://en.cppreference.com/w/cpp/named_req/InputIterator} {LegacyInputIterator}. + + The behavior is undefined if either argument is an iterator into *this. +*/ + +/*! \fn template QVarLengthArray::assign(std::initializer_list list) + \since 6.6 + + Replaces the contents of this container with a copy of the elements of \a list. + + The size of this container will be equal to the number of elements in \a list. + + This function only allocates memory if the number of elements in \a list + exceeds the capacity of the container. +*/ diff --git a/src/dbus/CMakeLists.txt b/src/dbus/CMakeLists.txt index 8022f265..f489eac2 100644 --- a/src/dbus/CMakeLists.txt +++ b/src/dbus/CMakeLists.txt @@ -52,8 +52,9 @@ qt_internal_add_module(DBus GENERATE_CPP_EXPORTS ) +# This file is included by qdbusargument.cpp set_source_files_properties(qdbusmarshaller.cpp - PROPERTIES HEADER_FILE_ONLY ON) # special case: This file is included by qdbusargument.cpp + PROPERTIES HEADER_FILE_ONLY ON) ## Scopes: ##################################################################### diff --git a/src/dbus/doc/snippets/code/src_qdbus_qdbusabstractinterface.cpp b/src/dbus/doc/snippets/code/src_qdbus_qdbusabstractinterface.cpp index 5348d18b..85945ac2 100644 --- a/src/dbus/doc/snippets/code/src_qdbus_qdbusabstractinterface.cpp +++ b/src/dbus/doc/snippets/code/src_qdbus_qdbusabstractinterface.cpp @@ -52,7 +52,7 @@ QDBusPendingCall pcall = interface->asyncCall("Process"_L1, value); QDBusPendingCallWatcher *watcher = new QDBusPendingCallWatcher(pcall); -QObject::connect(watcher, SIGNAL(finished(QDBusPendingCallWatcher*)), - this, SLOT(callFinishedSlot(QDBusPendingCallWatcher*))); +QObject::connect(watcher, &QDBusPendingCallWatcher::finished, this, + &Abstract_DBus_Interface::callFinishedSlot); //! [1] } diff --git a/src/dbus/doc/snippets/code/src_qdbus_qdbuscontext.cpp b/src/dbus/doc/snippets/code/src_qdbus_qdbuscontext.cpp index 9e4e26cb..75b43945 100644 --- a/src/dbus/doc/snippets/code/src_qdbus_qdbuscontext.cpp +++ b/src/dbus/doc/snippets/code/src_qdbus_qdbuscontext.cpp @@ -37,7 +37,7 @@ QString MyObject::methodWithDelayedReply() conn = connection(); msg = message(); setDelayedReply(true); - QMetaObject::invokeMethod(this, "process", Qt::QueuedConnection); + QMetaObject::invokeMethod(this, &MyObject::process, Qt::QueuedConnection); return QString(); } //! [0] diff --git a/src/dbus/doc/snippets/code/src_qdbus_qdbuspendingcall.cpp b/src/dbus/doc/snippets/code/src_qdbus_qdbuspendingcall.cpp index c44337ad..67b019a6 100644 --- a/src/dbus/doc/snippets/code/src_qdbus_qdbuspendingcall.cpp +++ b/src/dbus/doc/snippets/code/src_qdbus_qdbuspendingcall.cpp @@ -33,8 +33,8 @@ void DBus_PendingCall_Interface::callInterfaceMain() QDBusPendingCall async = iface->asyncCall("RemoteMethod", value1, value2); QDBusPendingCallWatcher *watcher = new QDBusPendingCallWatcher(async, this); - QObject::connect(watcher, SIGNAL(finished(QDBusPendingCallWatcher*)), - this, SLOT(callFinishedSlot(QDBusPendingCallWatcher*))); + QObject::connect(watcher, &QDBusPendingCallWatcher::finished, this, + &DBus_PendingCall_Interface::callFinishedSlot); //! [0] } diff --git a/src/dbus/doc/src/qtdbus-overview.qdoc b/src/dbus/doc/src/qtdbus-overview.qdoc index 1d570b12..6342e674 100644 --- a/src/dbus/doc/src/qtdbus-overview.qdoc +++ b/src/dbus/doc/src/qtdbus-overview.qdoc @@ -5,6 +5,7 @@ \page qtdbus-overview.html \title Qt D-Bus Overview \brief Provides insight into the Qt Qt D-Bus module. + \ingroup explanations-networkingandconnectivity D-Bus is an Inter-Process Communication (IPC) and Remote Procedure Calling (RPC) mechanism originally developed for Linux to replace diff --git a/src/dbus/qdbus_symbols.cpp b/src/dbus/qdbus_symbols.cpp index 9b1ccfd7..676bc3d4 100644 --- a/src/dbus/qdbus_symbols.cpp +++ b/src/dbus/qdbus_symbols.cpp @@ -3,6 +3,7 @@ // SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only #include +#include #if QT_CONFIG(library) #include #include diff --git a/src/dbus/qdbusabstractadaptor.cpp b/src/dbus/qdbusabstractadaptor.cpp index 04bd7c9c..9da2fd38 100644 --- a/src/dbus/qdbusabstractadaptor.cpp +++ b/src/dbus/qdbusabstractadaptor.cpp @@ -37,11 +37,9 @@ QDBusAdaptorConnector *qDBusFindAdaptorConnector(QObject *obj) { if (!obj) return nullptr; - const QObjectList &children = obj->children(); - QObjectList::ConstIterator it = children.constBegin(); - QObjectList::ConstIterator end = children.constEnd(); - for ( ; it != end; ++it) { - QDBusAdaptorConnector *connector = qobject_cast(*it); + + for (QObject *child : std::as_const(obj->children())) { + QDBusAdaptorConnector *connector = qobject_cast(child); if (connector) { connector->polish(); return connector; @@ -113,7 +111,7 @@ QDBusAbstractAdaptor::QDBusAbstractAdaptor(QObject* obj) QDBusAdaptorConnector *connector = qDBusCreateAdaptorConnector(obj); connector->waitingForPolish = true; - QMetaObject::invokeMethod(connector, "polish", Qt::QueuedConnection); + QMetaObject::invokeMethod(connector, &QDBusAdaptorConnector::polish, Qt::QueuedConnection); } /*! @@ -227,11 +225,8 @@ void QDBusAdaptorConnector::polish() return; // avoid working multiple times if multiple adaptors were added waitingForPolish = false; - const QObjectList &objs = parent()->children(); - QObjectList::ConstIterator it = objs.constBegin(); - QObjectList::ConstIterator end = objs.constEnd(); - for ( ; it != end; ++it) { - QDBusAbstractAdaptor *adaptor = qobject_cast(*it); + for (QObject *child : std::as_const(parent()->children())) { + QDBusAbstractAdaptor *adaptor = qobject_cast(child); if (adaptor) addAdaptor(adaptor); } diff --git a/src/dbus/qdbusargument.h b/src/dbus/qdbusargument.h index 475c47f4..f1ecb91f 100644 --- a/src/dbus/qdbusargument.h +++ b/src/dbus/qdbusargument.h @@ -222,10 +222,8 @@ inline const QDBusArgument &operator>>(const QDBusArgument &arg, Container &l inline QDBusArgument &operator<<(QDBusArgument &arg, const QVariantList &list) { arg.beginArray(QMetaType::fromType()); - QVariantList::ConstIterator it = list.constBegin(); - QVariantList::ConstIterator end = list.constEnd(); - for ( ; it != end; ++it) - arg << QDBusVariant(*it); + for (const QVariant &value : list) + arg << QDBusVariant(value); arg.endArray(); return arg; } @@ -284,11 +282,9 @@ inline const QDBusArgument &operator>>(const QDBusArgument &arg, Container(), QMetaType::fromType()); - QVariantMap::ConstIterator it = map.constBegin(); - QVariantMap::ConstIterator end = map.constEnd(); - for ( ; it != end; ++it) { + for (const auto &[key, value] : map.asKeyValueRange()) { arg.beginMapEntry(); - arg << it.key() << QDBusVariant(it.value()); + arg << key << QDBusVariant(value); arg.endMapEntry(); } arg.endMap(); @@ -298,11 +294,9 @@ inline QDBusArgument &operator<<(QDBusArgument &arg, const QVariantMap &map) inline QDBusArgument &operator<<(QDBusArgument &arg, const QVariantHash &map) { arg.beginMap(QMetaType::fromType(), QMetaType::fromType()); - QVariantHash::ConstIterator it = map.constBegin(); - QVariantHash::ConstIterator end = map.constEnd(); - for ( ; it != end; ++it) { + for (const auto &[key, value] : map.asKeyValueRange()) { arg.beginMapEntry(); - arg << it.key() << QDBusVariant(it.value()); + arg << key << QDBusVariant(value); arg.endMapEntry(); } arg.endMap(); diff --git a/src/dbus/qdbusconnection.cpp b/src/dbus/qdbusconnection.cpp index af6604bf..baab2b8c 100644 --- a/src/dbus/qdbusconnection.cpp +++ b/src/dbus/qdbusconnection.cpp @@ -22,6 +22,7 @@ #include "qdbuspendingcall_p.h" #include "qdbusthreaddebug_p.h" +#include "qdbusmetatype_p.h" #include @@ -82,6 +83,13 @@ void QDBusConnectionManager::removeConnection(const QString &name) QDBusConnectionManager::QDBusConnectionManager() { + // Ensure that the custom metatype registry is created before the instance + // of this class. This will ensure that the registry is not destroyed before + // the connection manager at application exit (see also QTBUG-58732). This + // works with compilers that use mechanism similar to atexit() to call + // destructurs for global statics. + QDBusMetaTypeId::init(); + connect(this, &QDBusConnectionManager::connectionRequested, this, &QDBusConnectionManager::executeConnectionRequest, Qt::BlockingQueuedConnection); connect(this, &QDBusConnectionManager::serverRequested, @@ -124,9 +132,7 @@ void QDBusConnectionManager::run() // cleanup: const auto locker = qt_scoped_lock(mutex); - for (QHash::const_iterator it = connectionHash.constBegin(); - it != connectionHash.constEnd(); ++it) { - QDBusConnectionPrivate *d = it.value(); + for (QDBusConnectionPrivate *d : std::as_const(connectionHash)) { if (!d->ref.deref()) { delete d; } else { @@ -150,12 +156,9 @@ QDBusConnectionPrivate *QDBusConnectionManager::connectToBus(QDBusConnection::Bu data.suspendedDelivery = suspendedDelivery; emit connectionRequested(&data); - if (suspendedDelivery && data.result->connection) { - data.result->ref.ref(); - QDBusConnectionDispatchEnabler *o = new QDBusConnectionDispatchEnabler(data.result); - QTimer::singleShot(0, o, SLOT(execute())); - o->moveToThread(qApp->thread()); // qApp was checked in the caller - } + if (suspendedDelivery && data.result->connection) + data.result->enableDispatchDelayed(qApp); // qApp was checked in the caller + return data.result; } diff --git a/src/dbus/qdbusconnection_p.h b/src/dbus/qdbusconnection_p.h index 3431992e..dc16da0a 100644 --- a/src/dbus/qdbusconnection_p.h +++ b/src/dbus/qdbusconnection_p.h @@ -212,6 +212,8 @@ public: void postEventToThread(int action, QObject *target, QEvent *event); + void enableDispatchDelayed(QObject *context); + private: void checkThread(); bool handleError(const QDBusErrorInternal &error); @@ -357,26 +359,6 @@ extern QDBusMessage qDBusPropertySet(const QDBusConnectionPrivate::ObjectTreeNod extern QDBusMessage qDBusPropertyGetAll(const QDBusConnectionPrivate::ObjectTreeNode &node, const QDBusMessage &msg); -// can be replaced with a lambda in Qt 5.7 -class QDBusConnectionDispatchEnabler : public QObject -{ - Q_OBJECT - QDBusConnectionPrivate *con; -public: - QDBusConnectionDispatchEnabler(QDBusConnectionPrivate *con) : con(con) {} - -public slots: - void execute() - { - // This call cannot race with something disabling dispatch only because dispatch is - // never re-disabled from Qt code on an in-use connection once it has been enabled. - QMetaObject::invokeMethod(con, "setDispatchEnabled", Qt::QueuedConnection, Q_ARG(bool, true)); - if (!con->ref.deref()) - con->deleteLater(); - deleteLater(); - } -}; - #endif // QT_BOOTSTRAPPED QT_END_NAMESPACE diff --git a/src/dbus/qdbusintegrator.cpp b/src/dbus/qdbusintegrator.cpp index 127f2b31..ec34d638 100644 --- a/src/dbus/qdbusintegrator.cpp +++ b/src/dbus/qdbusintegrator.cpp @@ -124,8 +124,7 @@ static dbus_bool_t qDBusAddTimeout(DBusTimeout *timeout, void *data) Q_ASSERT(d->timeouts.key(timeout, 0) == 0); - int timerId = d->startTimer(q_dbus_timeout_get_interval(timeout)); - Q_ASSERT_X(timerId, "QDBusConnection", "Failed to start a timer"); + int timerId = d->startTimer(std::chrono::milliseconds{q_dbus_timeout_get_interval(timeout)}); if (!timerId) return false; @@ -293,12 +292,8 @@ static void qDBusNewConnection(DBusServer *server, DBusConnection *connection, v // QDBusServer's thread in order to enable it after the // QDBusServer::newConnection() signal has been received by the // application's code - newConnection->ref.ref(); QReadLocker serverLock(&serverConnection->lock); - QDBusConnectionDispatchEnabler *o = new QDBusConnectionDispatchEnabler(newConnection); - QTimer::singleShot(0, o, SLOT(execute())); - if (serverConnection->serverObject) - o->moveToThread(serverConnection->serverObject->thread()); + newConnection->enableDispatchDelayed(serverConnection->serverObject); } void QDBusConnectionPrivate::_q_newConnection(QDBusConnectionPrivate *newConnection) @@ -409,17 +404,14 @@ static QObject *findChildObject(const QDBusConnectionPrivate::ObjectTreeNode *ro pos = (pos == -1 ? length : pos); auto pathComponent = QStringView{fullpath}.mid(start, pos - start); - const QObjectList children = obj->children(); - // find a child with the proper name QObject *next = nullptr; - QObjectList::ConstIterator it = children.constBegin(); - QObjectList::ConstIterator end = children.constEnd(); - for ( ; it != end; ++it) - if ((*it)->objectName() == pathComponent) { - next = *it; + for (QObject *child : std::as_const(obj->children())) { + if (child->objectName() == pathComponent) { + next = child; break; } + } if (!next) break; @@ -561,7 +553,7 @@ bool QDBusConnectionPrivate::handleMessage(const QDBusMessage &amsg) static void huntAndDestroy(QObject *needle, QDBusConnectionPrivate::ObjectTreeNode &haystack) { - for (auto &node : haystack.children) + for (QDBusConnectionPrivate::ObjectTreeNode &node : haystack.children) huntAndDestroy(needle, node); auto isInactive = [](const QDBusConnectionPrivate::ObjectTreeNode &node) { return !node.isActive(); }; @@ -605,11 +597,11 @@ static void huntAndEmit(DBusConnection *connection, DBusMessage *msg, QObject *needle, const QDBusConnectionPrivate::ObjectTreeNode &haystack, bool isScriptable, bool isAdaptor, const QString &path = QString()) { - QDBusConnectionPrivate::ObjectTreeNode::DataList::ConstIterator it = haystack.children.constBegin(); - QDBusConnectionPrivate::ObjectTreeNode::DataList::ConstIterator end = haystack.children.constEnd(); - for ( ; it != end; ++it) { - if (it->isActive()) - huntAndEmit(connection, msg, needle, *it, isScriptable, isAdaptor, path + u'/' + it->name); + for (const QDBusConnectionPrivate::ObjectTreeNode &node : std::as_const(haystack.children)) { + if (node.isActive()) { + huntAndEmit(connection, msg, needle, node, isScriptable, isAdaptor, + path + u'/' + node.name); + } } if (needle == haystack.obj) { @@ -996,8 +988,6 @@ void QDBusConnectionPrivate::deliverCall(QObject *object, int /*flags*/, const Q return; } -extern bool qDBusInitThreads(); - QDBusConnectionPrivate::QDBusConnectionPrivate(QObject *p) : QObject(p), ref(1), @@ -1076,12 +1066,8 @@ QDBusConnectionPrivate::~QDBusConnectionPrivate() void QDBusConnectionPrivate::collectAllObjects(QDBusConnectionPrivate::ObjectTreeNode &haystack, QSet &set) { - QDBusConnectionPrivate::ObjectTreeNode::DataList::Iterator it = haystack.children.begin(); - - while (it != haystack.children.end()) { - collectAllObjects(*it, set); - it++; - } + for (ObjectTreeNode &child : haystack.children) + collectAllObjects(child, set); if (haystack.obj) set.insert(haystack.obj); @@ -1109,11 +1095,9 @@ void QDBusConnectionPrivate::closeConnection() } } - for (auto it = pendingCalls.begin(); it != pendingCalls.end(); ++it) { - auto call = *it; - if (!call->ref.deref()) { + for (QDBusPendingCallPrivate *call : pendingCalls) { + if (!call->ref.deref()) delete call; - } } pendingCalls.clear(); @@ -1124,18 +1108,12 @@ void QDBusConnectionPrivate::closeConnection() // dangling pointer. QSet allObjects; collectAllObjects(rootNode, allObjects); - SignalHookHash::const_iterator sit = signalHooks.constBegin(); - while (sit != signalHooks.constEnd()) { - allObjects.insert(sit.value().obj); - ++sit; - } + for (const SignalHook &signalHook : std::as_const(signalHooks)) + allObjects.insert(signalHook.obj); // now disconnect ourselves - QSet::const_iterator oit = allObjects.constBegin(); - while (oit != allObjects.constEnd()) { - (*oit)->disconnect(this); - ++oit; - } + for (QObject *obj : std::as_const(allObjects)) + obj->disconnect(this); } void QDBusConnectionPrivate::handleDBusDisconnection() @@ -1177,11 +1155,9 @@ void QDBusConnectionPrivate::doDispatch() if (mode == ClientMode || mode == PeerMode) { if (dispatchEnabled && !pendingMessages.isEmpty()) { // dispatch previously queued messages - PendingMessageList::Iterator it = pendingMessages.begin(); - PendingMessageList::Iterator end = pendingMessages.end(); - for ( ; it != end; ++it) { - qDBusDebug() << this << "dequeueing message" << *it; - handleMessage(std::move(*it)); + for (QDBusMessage &message : pendingMessages) { + qDBusDebug() << this << "dequeueing message" << message; + handleMessage(std::move(message)); } pendingMessages.clear(); } @@ -1461,14 +1437,11 @@ void QDBusConnectionPrivate::activateObject(ObjectTreeNode &node, const QDBusMes if (msg.interface().isEmpty()) { // place the call in all interfaces // let the first one that handles it to work - QDBusAdaptorConnector::AdaptorMap::ConstIterator it = - connector->adaptors.constBegin(); - QDBusAdaptorConnector::AdaptorMap::ConstIterator end = - connector->adaptors.constEnd(); - - for ( ; it != end; ++it) - if (activateCall(it->adaptor, newflags, msg)) + for (const QDBusAdaptorConnector::AdaptorData &adaptorData : + std::as_const(connector->adaptors)) { + if (activateCall(adaptorData.adaptor, newflags, msg)) return; + } } else { // check if we have an interface matching the name that was asked: QDBusAdaptorConnector::AdaptorMap::ConstIterator it; @@ -1757,10 +1730,10 @@ void QDBusConnectionPrivate::setPeer(DBusConnection *c, const QDBusErrorInternal watchForDBusDisconnection(); - QMetaObject::invokeMethod(this, "doDispatch", Qt::QueuedConnection); + QMetaObject::invokeMethod(this, &QDBusConnectionPrivate::doDispatch, Qt::QueuedConnection); } -static QDBusConnection::ConnectionCapabilities connectionCapabilies(DBusConnection *connection) +static QDBusConnection::ConnectionCapabilities connectionCapabilities(DBusConnection *connection) { QDBusConnection::ConnectionCapabilities result; typedef dbus_bool_t (*can_send_type_t)(DBusConnection *, int); @@ -1786,7 +1759,7 @@ static QDBusConnection::ConnectionCapabilities connectionCapabilies(DBusConnecti void QDBusConnectionPrivate::handleAuthentication() { - capabilities.storeRelaxed(connectionCapabilies(connection)); + capabilities.storeRelaxed(::connectionCapabilities(connection)); isAuthenticated = true; } @@ -1845,7 +1818,7 @@ void QDBusConnectionPrivate::setConnection(DBusConnection *dbc, const QDBusError qDBusDebug() << this << ": connected successfully"; // schedule a dispatch: - QMetaObject::invokeMethod(this, "doDispatch", Qt::QueuedConnection); + QMetaObject::invokeMethod(this, &QDBusConnectionPrivate::doDispatch, Qt::QueuedConnection); } extern "C"{ @@ -1970,7 +1943,7 @@ bool QDBusConnectionPrivate::send(const QDBusMessage& message) class QDBusBlockingCallWatcher { public: - QDBusBlockingCallWatcher(const QDBusMessage &message) + Q_NODISCARD_CTOR QDBusBlockingCallWatcher(const QDBusMessage &message) : m_message(message), m_maxCallTimeoutMs(0) { #if defined(QT_NO_DEBUG) @@ -2428,8 +2401,8 @@ void QDBusConnectionPrivate::registerObject(const ObjectTreeNode *node) connector->connectAllSignals(node->obj); } - connect(connector, SIGNAL(relaySignal(QObject*,const QMetaObject*,int,QVariantList)), - this, SLOT(relaySignal(QObject*,const QMetaObject*,int,QVariantList)), + connect(connector, &QDBusAdaptorConnector::relaySignal, this, + &QDBusConnectionPrivate::relaySignal, Qt::ConnectionType(Qt::QueuedConnection | Qt::UniqueConnection)); } } @@ -2678,6 +2651,28 @@ void QDBusConnectionPrivate::postEventToThread(int action, QObject *object, QEve QDBusLockerBase::reportThreadAction(action, QDBusLockerBase::AfterPost, this); } +/* + * Enable dispatch of D-Bus events for this connection, but only after + * context's thread's event loop has started and processed any already + * pending events. The event dispatch is then enabled in the DBus aux thread. + */ +void QDBusConnectionPrivate::enableDispatchDelayed(QObject *context) +{ + ref.ref(); + QMetaObject::invokeMethod( + context, + [this]() { + // This call cannot race with something disabling dispatch only + // because dispatch is never re-disabled from Qt code on an + // in-use connection once it has been enabled. + QMetaObject::invokeMethod( + this, [this] { setDispatchEnabled(true); }, Qt::QueuedConnection); + if (!ref.deref()) + deleteLater(); + }, + Qt::QueuedConnection); +} + QT_END_NAMESPACE #endif // QT_NO_DBUS diff --git a/src/dbus/qdbusinternalfilters.cpp b/src/dbus/qdbusinternalfilters.cpp index d963903d..d860a021 100644 --- a/src/dbus/qdbusinternalfilters.cpp +++ b/src/dbus/qdbusinternalfilters.cpp @@ -76,11 +76,8 @@ static const char peerInterfaceXml[] = static QString generateSubObjectXml(QObject *object) { QString retval; - const QObjectList &objs = object->children(); - QObjectList::ConstIterator it = objs.constBegin(); - QObjectList::ConstIterator end = objs.constEnd(); - for ( ; it != end; ++it) { - QString name = (*it)->objectName(); + for (const QObject *child : object->children()) { + QString name = child->objectName(); if (!name.isEmpty() && QDBusUtil::isValidPartOfObjectPath(name)) retval += " \n"_L1; } @@ -116,20 +113,22 @@ QString qDBusIntrospectObject(const QDBusConnectionPrivate::ObjectTreeNode &node (connector = qDBusFindAdaptorConnector(node.obj))) { // trasverse every adaptor in this object - QDBusAdaptorConnector::AdaptorMap::ConstIterator it = connector->adaptors.constBegin(); - QDBusAdaptorConnector::AdaptorMap::ConstIterator end = connector->adaptors.constEnd(); - for ( ; it != end; ++it) { + for (const QDBusAdaptorConnector::AdaptorData &adaptorData : + std::as_const(connector->adaptors)) { // add the interface: - QString ifaceXml = QDBusAbstractAdaptorPrivate::retrieveIntrospectionXml(it->adaptor); + QString ifaceXml = + QDBusAbstractAdaptorPrivate::retrieveIntrospectionXml(adaptorData.adaptor); if (ifaceXml.isEmpty()) { // add the interface's contents: - ifaceXml += qDBusGenerateMetaObjectXml(QString::fromLatin1(it->interface), - it->adaptor->metaObject(), - &QDBusAbstractAdaptor::staticMetaObject, - QDBusConnection::ExportScriptableContents - | QDBusConnection::ExportNonScriptableContents); + ifaceXml += qDBusGenerateMetaObjectXml( + QString::fromLatin1(adaptorData.interface), + adaptorData.adaptor->metaObject(), + &QDBusAbstractAdaptor::staticMetaObject, + QDBusConnection::ExportScriptableContents + | QDBusConnection::ExportNonScriptableContents); - QDBusAbstractAdaptorPrivate::saveIntrospectionXml(it->adaptor, ifaceXml); + QDBusAbstractAdaptorPrivate::saveIntrospectionXml(adaptorData.adaptor, + ifaceXml); } xml_data += ifaceXml; @@ -151,13 +150,10 @@ QString qDBusIntrospectObject(const QDBusConnectionPrivate::ObjectTreeNode &node xml_data += generateSubObjectXml(node.obj); } else { // generate from the object tree - QDBusConnectionPrivate::ObjectTreeNode::DataList::ConstIterator it = - node.children.constBegin(); - QDBusConnectionPrivate::ObjectTreeNode::DataList::ConstIterator end = - node.children.constEnd(); - for ( ; it != end; ++it) - if (it->obj || !it->children.isEmpty()) - xml_data += " name + "\"/>\n"_L1; + for (const QDBusConnectionPrivate::ObjectTreeNode &node : node.children) { + if (node.obj || !node.children.isEmpty()) + xml_data += " \n"_L1; + } } xml_data += "\n"_L1; @@ -195,7 +191,7 @@ QDBusMessage qDBusPropertyGet(const QDBusConnectionPrivate::ObjectTreeNode &node QString interface_name = msg.arguments().at(0).toString(); QByteArray property_name = msg.arguments().at(1).toString().toUtf8(); - QDBusAdaptorConnector *connector; + const QDBusAdaptorConnector *connector; QVariant value; bool interfaceFound = false; if (node.flags & QDBusConnection::ExportAdaptors && @@ -204,12 +200,11 @@ QDBusMessage qDBusPropertyGet(const QDBusConnectionPrivate::ObjectTreeNode &node // find the class that implements interface_name or try until we've found the property // in case of an empty interface if (interface_name.isEmpty()) { - for (QDBusAdaptorConnector::AdaptorMap::ConstIterator it = connector->adaptors.constBegin(), - end = connector->adaptors.constEnd(); it != end; ++it) { - const QMetaObject *mo = it->adaptor->metaObject(); + for (const QDBusAdaptorConnector::AdaptorData &adaptorData : connector->adaptors) { + const QMetaObject *mo = adaptorData.adaptor->metaObject(); int pidx = mo->indexOfProperty(property_name); if (pidx != -1) { - value = mo->property(pidx).read(it->adaptor); + value = mo->property(pidx).read(adaptorData.adaptor); break; } } @@ -332,14 +327,14 @@ static int writeProperty(QObject *obj, const QByteArray &property_name, QVariant return PropertyWriteFailed; } - value = other; + value = std::move(other); } if (mp.metaType() == QMetaType::fromType()) value = QVariant::fromValue(QDBusVariant(value)); // the property type here should match - return mp.write(obj, value) ? PropertyWriteSuccess : PropertyWriteFailed; + return mp.write(obj, std::move(value)) ? PropertyWriteSuccess : PropertyWriteFailed; } QDBusMessage qDBusPropertySet(const QDBusConnectionPrivate::ObjectTreeNode &node, @@ -361,9 +356,9 @@ QDBusMessage qDBusPropertySet(const QDBusConnectionPrivate::ObjectTreeNode &node // find the class that implements interface_name or try until we've found the property // in case of an empty interface if (interface_name.isEmpty()) { - for (QDBusAdaptorConnector::AdaptorMap::ConstIterator it = connector->adaptors.constBegin(), - end = connector->adaptors.constEnd(); it != end; ++it) { - int status = writeProperty(it->adaptor, property_name, value); + for (const QDBusAdaptorConnector::AdaptorData &adaptorData : + std::as_const(connector->adaptors)) { + int status = writeProperty(adaptorData.adaptor, property_name, value); if (status == PropertyNotFound) continue; return propertyWriteReply(msg, interface_name, property_name, status); @@ -401,10 +396,8 @@ QDBusMessage qDBusPropertySet(const QDBusConnectionPrivate::ObjectTreeNode &node // unite two QVariantMaps, but don't generate duplicate keys static QVariantMap &operator+=(QVariantMap &lhs, const QVariantMap &rhs) { - QVariantMap::ConstIterator it = rhs.constBegin(), - end = rhs.constEnd(); - for ( ; it != end; ++it) - lhs.insert(it.key(), it.value()); + for (const auto &[key, value] : rhs.asKeyValueRange()) + lhs.insert(key, value); return lhs; } @@ -461,9 +454,10 @@ QDBusMessage qDBusPropertyGetAll(const QDBusConnectionPrivate::ObjectTreeNode &n if (interface_name.isEmpty()) { // iterate over all interfaces - for (QDBusAdaptorConnector::AdaptorMap::ConstIterator it = connector->adaptors.constBegin(), - end = connector->adaptors.constEnd(); it != end; ++it) { - result += readAllProperties(it->adaptor, QDBusConnection::ExportAllProperties); + for (const QDBusAdaptorConnector::AdaptorData &adaptorData : + std::as_const(connector->adaptors)) { + result += readAllProperties(adaptorData.adaptor, + QDBusConnection::ExportAllProperties); } } else { // find the class that implements interface_name diff --git a/src/dbus/qdbusmarshaller.cpp b/src/dbus/qdbusmarshaller.cpp index e3fb298f..480b1977 100644 --- a/src/dbus/qdbusmarshaller.cpp +++ b/src/dbus/qdbusmarshaller.cpp @@ -206,10 +206,8 @@ inline void QDBusMarshaller::append(const QStringList &arg) QDBusMarshaller sub(capabilities); open(sub, DBUS_TYPE_ARRAY, DBUS_TYPE_STRING_AS_STRING); - QStringList::ConstIterator it = arg.constBegin(); - QStringList::ConstIterator end = arg.constEnd(); - for ( ; it != end; ++it) - sub.append(*it); + for (const QString &s : arg) + sub.append(s); // don't call sub.close(): it auto-closes } diff --git a/src/dbus/qdbusmessage.cpp b/src/dbus/qdbusmessage.cpp index fb3f8600..6372fe04 100644 --- a/src/dbus/qdbusmessage.cpp +++ b/src/dbus/qdbusmessage.cpp @@ -155,14 +155,12 @@ DBusMessage *QDBusMessagePrivate::toDBusMessage(const QDBusMessage &message, QDB d_ptr->parametersValidated = true; QDBusMarshaller marshaller(capabilities); - QVariantList::ConstIterator it = d_ptr->arguments.constBegin(); - QVariantList::ConstIterator cend = d_ptr->arguments.constEnd(); q_dbus_message_iter_init_append(msg, &marshaller.iterator); if (!d_ptr->message.isEmpty()) // prepend the error message marshaller.append(d_ptr->message); - for ( ; it != cend; ++it) - marshaller.appendVariantInternal(*it); + for (const QVariant &argument : std::as_const(d_ptr->arguments)) + marshaller.appendVariantInternal(argument); // check if everything is ok if (marshaller.ok) @@ -239,10 +237,8 @@ QDBusMessage QDBusMessagePrivate::makeLocal(const QDBusConnectionPrivate &conn, // determine if we are carrying any complex types QString computedSignature; - QVariantList::ConstIterator it = asSent.d_ptr->arguments.constBegin(); - QVariantList::ConstIterator end = asSent.d_ptr->arguments.constEnd(); - for ( ; it != end; ++it) { - QMetaType id = it->metaType(); + for (const QVariant &argument : std::as_const(asSent.d_ptr->arguments)) { + QMetaType id = argument.metaType(); const char *signature = QDBusMetaType::typeToSignature(id); if ((id.id() != QMetaType::QStringList && id.id() != QMetaType::QByteArray && qstrlen(signature) != 1) || id == QMetaType::fromType()) { @@ -804,12 +800,10 @@ static QDebug operator<<(QDebug dbg, QDBusMessage::MessageType t) static void debugVariantList(QDebug dbg, const QVariantList &list) { bool first = true; - QVariantList::ConstIterator it = list.constBegin(); - QVariantList::ConstIterator end = list.constEnd(); - for ( ; it != end; ++it) { + for (const QVariant &elem : list) { if (!first) dbg.nospace() << ", "; - dbg.nospace() << qPrintable(QDBusUtil::argumentToString(*it)); + dbg.nospace() << qPrintable(QDBusUtil::argumentToString(elem)); first = false; } } diff --git a/src/dbus/qdbusmetaobject.cpp b/src/dbus/qdbusmetaobject.cpp index 054bc448..2e367e63 100644 --- a/src/dbus/qdbusmetaobject.cpp +++ b/src/dbus/qdbusmetaobject.cpp @@ -55,8 +55,9 @@ private: QByteArray name; }; - QMap signals_; - QMap methods; + using MethodMap = QMap; + MethodMap signals_; + MethodMap methods; QMap properties; const QDBusIntrospection::Interface *data; @@ -70,7 +71,7 @@ private: void parseSignals(); void parseProperties(); - static qsizetype aggregateParameterCount(const QMap &map); + static qsizetype aggregateParameterCount(const MethodMap &map); }; static const qsizetype intsPerProperty = 2; @@ -208,10 +209,7 @@ void QDBusMetaObjectGenerator::parseMethods() // Add cloned methods when the remote object has return types // - QDBusIntrospection::Methods::ConstIterator method_it = data->methods.constBegin(); - QDBusIntrospection::Methods::ConstIterator method_end = data->methods.constEnd(); - for ( ; method_it != method_end; ++method_it) { - const QDBusIntrospection::Method &m = *method_it; + for (const QDBusIntrospection::Method &m : std::as_const(data->methods)) { Method mm; mm.name = m.name.toLatin1(); @@ -284,10 +282,7 @@ void QDBusMetaObjectGenerator::parseMethods() void QDBusMetaObjectGenerator::parseSignals() { - QDBusIntrospection::Signals::ConstIterator signal_it = data->signals_.constBegin(); - QDBusIntrospection::Signals::ConstIterator signal_end = data->signals_.constEnd(); - for ( ; signal_it != signal_end; ++signal_it) { - const QDBusIntrospection::Signal &s = *signal_it; + for (const QDBusIntrospection::Signal &s : std::as_const(data->signals_)) { Method mm; mm.name = s.name.toLatin1(); @@ -331,10 +326,7 @@ void QDBusMetaObjectGenerator::parseSignals() void QDBusMetaObjectGenerator::parseProperties() { - QDBusIntrospection::Properties::ConstIterator prop_it = data->properties.constBegin(); - QDBusIntrospection::Properties::ConstIterator prop_end = data->properties.constEnd(); - for ( ; prop_it != prop_end; ++prop_it) { - const QDBusIntrospection::Property &p = *prop_it; + for (const QDBusIntrospection::Property &p : std::as_const(data->properties)) { Property mp; Type type = findType(p.type.toLatin1(), p.annotations); if (type.id == QMetaType::UnknownType) @@ -360,14 +352,11 @@ void QDBusMetaObjectGenerator::parseProperties() // Returns the sum of all parameters (including return type) for the given // \a map of methods. This is needed for calculating the size of the methods' // parameter type/name meta-data. -qsizetype QDBusMetaObjectGenerator::aggregateParameterCount(const QMap &map) +qsizetype QDBusMetaObjectGenerator::aggregateParameterCount(const MethodMap &map) { qsizetype sum = 0; - QMap::const_iterator it; - for (it = map.constBegin(); it != map.constEnd(); ++it) { - const Method &m = it.value(); + for (const Method &m : map) sum += m.inputTypes.size() + qMax(qsizetype(1), m.outputTypes.size()); - } return sum; } @@ -391,7 +380,7 @@ void QDBusMetaObjectGenerator::write(QDBusMetaObject *obj) - methods.size(); // ditto QDBusMetaObjectPrivate *header = reinterpret_cast(idata.data()); - static_assert(QMetaObjectPrivate::OutputRevision == 11, "QtDBus meta-object generator should generate the same version as moc"); + static_assert(QMetaObjectPrivate::OutputRevision == 12, "QtDBus meta-object generator should generate the same version as moc"); header->revision = QMetaObjectPrivate::OutputRevision; header->className = 0; header->classInfoCount = 0; @@ -415,10 +404,14 @@ void QDBusMetaObjectGenerator::write(QDBusMetaObject *obj) qsizetype data_size = idata.size() + (header->methodCount * (QMetaObjectPrivate::IntsPerMethod+intsPerMethod)) + methodParametersDataSize + (header->propertyCount * (QMetaObjectPrivate::IntsPerProperty+intsPerProperty)); - for (const Method &mm : std::as_const(signals_)) - data_size += 2 + mm.inputTypes.size() + mm.outputTypes.size(); - for (const Method &mm : std::as_const(methods)) - data_size += 2 + mm.inputTypes.size() + mm.outputTypes.size(); + + // Signals must be added before other methods, to match moc. + std::array, 2> methodMaps = { signals_, methods }; + + for (const auto &methodMap : methodMaps) { + for (const Method &mm : methodMap.get()) + data_size += 2 + mm.inputTypes.size() + mm.outputTypes.size(); + } idata.resize(data_size + 1); QMetaStringTable strings(className.toLatin1()); @@ -431,9 +424,9 @@ void QDBusMetaObjectGenerator::write(QDBusMetaObject *obj) qsizetype totalMetaTypeCount = properties.size(); ++totalMetaTypeCount; // + 1 for metatype of dynamic metaobject - for (const auto& methodContainer: {signals_, methods}) { - for (const auto& method: methodContainer) { - qsizetype argc = method.inputTypes.size() + qMax(qsizetype(0), method.outputTypes.size() - 1); + for (const auto &methodMap : methodMaps) { + for (const Method &mm : methodMap.get()) { + qsizetype argc = mm.inputTypes.size() + qMax(qsizetype(0), mm.outputTypes.size() - 1); totalMetaTypeCount += argc + 1; } } @@ -442,13 +435,9 @@ void QDBusMetaObjectGenerator::write(QDBusMetaObject *obj) // add each method: qsizetype currentMethodMetaTypeOffset = properties.size() + 1; - for (int x = 0; x < 2; ++x) { - // Signals must be added before other methods, to match moc. - QMap &map = (x == 0) ? signals_ : methods; - for (QMap::ConstIterator it = map.constBegin(); - it != map.constEnd(); ++it) { - const Method &mm = it.value(); + for (const auto &methodMap : methodMaps) { + for (const Method &mm : methodMap.get()) { qsizetype argc = mm.inputTypes.size() + qMax(qsizetype(0), mm.outputTypes.size() - 1); idata[offset++] = strings.enter(mm.name); @@ -514,12 +503,9 @@ void QDBusMetaObjectGenerator::write(QDBusMetaObject *obj) // add each property signatureOffset = header->propertyDBusData; - for (QMap::ConstIterator it = properties.constBegin(); - it != properties.constEnd(); ++it) { - const Property &mp = it.value(); - + for (const auto &[name, mp] : std::as_const(properties).asKeyValueRange()) { // form is name, typeinfo, flags - idata[offset++] = strings.enter(it.key()); // name + idata[offset++] = strings.enter(name); Q_ASSERT(mp.type != QMetaType::UnknownType); idata[offset++] = mp.type; idata[offset++] = mp.flags; diff --git a/src/dbus/qdbusmetatype.cpp b/src/dbus/qdbusmetatype.cpp index 6238a5a0..5fe5a60a 100644 --- a/src/dbus/qdbusmetatype.cpp +++ b/src/dbus/qdbusmetatype.cpp @@ -99,9 +99,13 @@ void QDBusMetaTypeId::init() } } -using QDBusCustomTypeHash = QHash; -Q_GLOBAL_STATIC(QDBusCustomTypeHash, customTypes) -Q_GLOBAL_STATIC(QReadWriteLock, customTypesLock) +struct QDBusCustomTypes +{ + QReadWriteLock lock; + QHash hash; +}; + +Q_GLOBAL_STATIC(QDBusCustomTypes, customTypes) /*! \class QDBusMetaType @@ -182,12 +186,15 @@ void QDBusMetaType::registerMarshallOperators(QMetaType metaType, MarshallFuncti DemarshallFunction df) { int id = metaType.id(); - auto *ct = customTypes(); - if (id < 0 || !mf || !df || !ct) + if (id < 0 || !mf || !df) return; // error! - QWriteLocker locker(customTypesLock()); - QDBusCustomTypeInfo &info = (*ct)[id]; + auto *ct = customTypes(); + if (!ct) + return; + + QWriteLocker locker(&ct->lock); + QDBusCustomTypeInfo &info = ct->hash[id]; info.marshall = mf; info.demarshall = df; } @@ -200,15 +207,19 @@ void QDBusMetaType::registerMarshallOperators(QMetaType metaType, MarshallFuncti */ bool QDBusMetaType::marshall(QDBusArgument &arg, QMetaType metaType, const void *data) { + auto *ct = customTypes(); + if (!ct) + return false; + int id = metaType.id(); QDBusMetaTypeId::init(); MarshallFunction mf; { - QReadLocker locker(customTypesLock()); - auto *ct = customTypes(); - auto it = ct->constFind(id); - if (it == ct->cend()) + QReadLocker locker(&ct->lock); + + auto it = ct->hash.constFind(id); + if (it == ct->hash.cend()) return false; // non-existent const QDBusCustomTypeInfo &info = *it; @@ -231,15 +242,19 @@ bool QDBusMetaType::marshall(QDBusArgument &arg, QMetaType metaType, const void */ bool QDBusMetaType::demarshall(const QDBusArgument &arg, QMetaType metaType, void *data) { + auto *ct = customTypes(); + if (!ct) + return false; + int id = metaType.id(); QDBusMetaTypeId::init(); DemarshallFunction df; { - QReadLocker locker(customTypesLock()); - auto *ct = customTypes(); - auto it = ct->constFind(id); - if (it == ct->cend()) + QReadLocker locker(&ct->lock); + + auto it = ct->hash.constFind(id); + if (it == ct->hash.cend()) return false; // non-existent const QDBusCustomTypeInfo &info = *it; @@ -357,8 +372,11 @@ QMetaType QDBusMetaType::signatureToMetaType(const char *signature) void QDBusMetaType::registerCustomType(QMetaType type, const QByteArray &signature) { auto *ct = customTypes(); - QWriteLocker locker(customTypesLock()); - auto &info = (*ct)[type.id()]; + if (!ct) + return; + + QWriteLocker locker(&ct->lock); + auto &info = ct->hash[type.id()]; info.signature = signature; // note how marshall/demarshall are not set, the type is never used at runtime } @@ -430,10 +448,13 @@ const char *QDBusMetaType::typeToSignature(QMetaType type) // try the database auto *ct = customTypes(); + if (!ct) + return nullptr; + { - QReadLocker locker(customTypesLock()); - auto it = ct->constFind(type.id()); - if (it == ct->end()) + QReadLocker locker(&ct->lock); + auto it = ct->hash.constFind(type.id()); + if (it == ct->hash.end()) return nullptr; const QDBusCustomTypeInfo &info = *it; @@ -453,8 +474,8 @@ const char *QDBusMetaType::typeToSignature(QMetaType type) QByteArray signature = QDBusArgumentPrivate::createSignature(type.id()); // re-acquire lock - QWriteLocker locker(customTypesLock()); - info = &(*ct)[type.id()]; + QWriteLocker locker(&ct->lock); + info = &ct->hash[type.id()]; info->signature = signature; } return info->signature; diff --git a/src/dbus/qdbusmetatype_p.h b/src/dbus/qdbusmetatype_p.h index fdae8035..447d5f48 100644 --- a/src/dbus/qdbusmetatype_p.h +++ b/src/dbus/qdbusmetatype_p.h @@ -28,18 +28,17 @@ QT_BEGIN_NAMESPACE -struct QDBusMetaTypeId -{ - static QMetaType message(); // QDBusMessage - static QMetaType argument(); // QDBusArgument - static QMetaType variant(); // QDBusVariant - static QMetaType objectpath(); // QDBusObjectPath - static QMetaType signature(); // QDBusSignature - static QMetaType error(); // QDBusError - static QMetaType unixfd(); // QDBusUnixFileDescriptor +namespace QDBusMetaTypeId { +QMetaType message(); // QDBusMessage +QMetaType argument(); // QDBusArgument +QMetaType variant(); // QDBusVariant +QMetaType objectpath(); // QDBusObjectPath +QMetaType signature(); // QDBusSignature +QMetaType error(); // QDBusError +QMetaType unixfd(); // QDBusUnixFileDescriptor - static void init(); -}; +void init(); +}; // namespace QDBusMetaTypeId inline QMetaType QDBusMetaTypeId::message() { return QMetaType::fromType(); } diff --git a/src/dbus/qdbusmisc.cpp b/src/dbus/qdbusmisc.cpp index b1da4711..502624cd 100644 --- a/src/dbus/qdbusmisc.cpp +++ b/src/dbus/qdbusmisc.cpp @@ -117,10 +117,7 @@ int qDBusParametersForMethod(const QList ¶meterTypes, QList::ConstIterator it = parameterTypes.constBegin(); - QList::ConstIterator end = parameterTypes.constEnd(); - for ( ; it != end; ++it) { - QByteArray type = *it; + for (QByteArray type : parameterTypes) { if (type.endsWith('*')) { errorMsg = "Pointers are not supported: "_L1 + QLatin1StringView(type); return -1; diff --git a/src/dbus/qdbuspendingcall.cpp b/src/dbus/qdbuspendingcall.cpp index ff5dfc89..87589178 100644 --- a/src/dbus/qdbuspendingcall.cpp +++ b/src/dbus/qdbuspendingcall.cpp @@ -474,7 +474,9 @@ QDBusPendingCallWatcher::QDBusPendingCallWatcher(const QDBusPendingCall &call, Q d->watcherHelper = new QDBusPendingCallWatcherHelper; if (d->replyMessage.type() != QDBusMessage::InvalidMessage) { // cause a signal emission anyways - QMetaObject::invokeMethod(d->watcherHelper, "finished", Qt::QueuedConnection); + QMetaObject::invokeMethod(d->watcherHelper, + &QDBusPendingCallWatcherHelper::finished, + Qt::QueuedConnection); } } d->watcherHelper->add(this); diff --git a/src/dbus/qdbusthreaddebug_p.h b/src/dbus/qdbusthreaddebug_p.h index 73be3d75..a1d3a420 100644 --- a/src/dbus/qdbusthreaddebug_p.h +++ b/src/dbus/qdbusthreaddebug_p.h @@ -93,7 +93,7 @@ struct QDBusReadLocker: QDBusLockerBase { QDBusConnectionPrivate *self; ThreadAction action; - inline QDBusReadLocker(ThreadAction a, QDBusConnectionPrivate *s) + Q_NODISCARD_CTOR QDBusReadLocker(ThreadAction a, QDBusConnectionPrivate *s) : self(s), action(a) { reportThreadAction(action, BeforeLock, self); @@ -113,7 +113,7 @@ struct QDBusWriteLocker: QDBusLockerBase { QDBusConnectionPrivate *self; ThreadAction action; - inline QDBusWriteLocker(ThreadAction a, QDBusConnectionPrivate *s) + Q_NODISCARD_CTOR QDBusWriteLocker(ThreadAction a, QDBusConnectionPrivate *s) : self(s), action(a) { reportThreadAction(action, BeforeLock, self); diff --git a/src/gui/CMakeLists.txt b/src/gui/CMakeLists.txt index 84c51c75..c2c9b964 100644 --- a/src/gui/CMakeLists.txt +++ b/src/gui/CMakeLists.txt @@ -56,14 +56,13 @@ qt_internal_add_module(Gui Qt::Network QMAKE_MODULE_CONFIG "${qmake_module_config}" SOURCES - accessible/qaccessible.h - accessible/qplatformaccessibility.h + compat/removed_api.cpp image/qabstractfileiconengine.cpp image/qabstractfileiconengine_p.h image/qabstractfileiconprovider.cpp image/qabstractfileiconprovider.h image/qabstractfileiconprovider_p.h 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 @@ -213,14 +212,10 @@ qt_internal_add_module(Gui painting/qtriangulatingstroker.cpp painting/qtriangulatingstroker_p.h painting/qtriangulator.cpp painting/qtriangulator_p.h painting/qvectorpath_p.h - rhi/qrhi.cpp rhi/qrhi_p.h - rhi/qrhi_p_p.h + rhi/qrhi.cpp rhi/qrhi.h rhi/qrhi_platform.h rhi/qrhi_p.h rhi/qrhinull.cpp rhi/qrhinull_p.h - rhi/qrhinull_p_p.h - rhi/qshader.cpp rhi/qshader_p.h - rhi/qshader_p_p.h - rhi/qshaderdescription.cpp rhi/qshaderdescription_p.h - rhi/qshaderdescription_p_p.h + rhi/qshader.cpp rhi/qshader.h rhi/qshader_p.h + rhi/qshaderdescription.cpp rhi/qshaderdescription.h rhi/qshaderdescription_p.h text/qabstracttextdocumentlayout.cpp text/qabstracttextdocumentlayout.h text/qabstracttextdocumentlayout_p.h text/qdistancefield.cpp text/qdistancefield_p.h text/qfont.cpp text/qfont.h text/qfont_p.h @@ -269,6 +264,7 @@ qt_internal_add_module(Gui QT_QPA_DEFAULT_PLATFORM_NAME="${QT_QPA_DEFAULT_PLATFORM}" INCLUDE_DIRECTORIES ../3rdparty/VulkanMemoryAllocator + ../3rdparty/D3D12MemoryAllocator LIBRARIES Qt::CorePrivate PUBLIC_LIBRARIES @@ -276,12 +272,15 @@ qt_internal_add_module(Gui PRIVATE_MODULE_INTERFACE Qt::CorePrivate NO_PCH_SOURCES + compat/removed_api.cpp painting/qdrawhelper.cpp PRECOMPILED_HEADER "kernel/qt_gui_pch.h" GENERATE_CPP_EXPORTS QPA_HEADER_FILTERS "(^|/)qplatform.+\\.h$|(^|/)qwindowsystem.+\\.h$" + RHI_HEADER_FILTERS + "(^|/)qrhi\\.h$|(^|/)qrhi_platform\\.h$|(^|/)qshader\\.h$|(^|/)qshaderdescription\\.h$" ) # Resources: @@ -357,7 +356,6 @@ qt_internal_extend_target(Gui CONDITION QT_FEATURE_opengl opengl/qopenglfunctions.cpp opengl/qopenglprogrambinarycache.cpp opengl/qopenglprogrambinarycache_p.h rhi/qrhigles2.cpp rhi/qrhigles2_p.h - rhi/qrhigles2_p_p.h ) qt_internal_extend_target(Gui CONDITION MACOS @@ -370,6 +368,11 @@ qt_internal_extend_target(Gui CONDITION MACOS ${FWAppKit} ) +qt_internal_extend_target(Gui CONDITION WASM + SOURCES + platform/wasm/qwasmnativeinterface.cpp +) + qt_internal_extend_target(Gui CONDITION APPLE SOURCES image/qimage_darwin.mm @@ -402,10 +405,12 @@ 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 - rhi/cs_tdr_p.h - rhi/qrhid3d11.cpp rhi/qrhid3d11_p.h - rhi/qrhid3d11_p_p.h + rhi/qrhid3d11.cpp rhi/qrhid3d11_p.h rhi/qrhid3dhelpers_p.h rhi/vs_test_p.h + rhi/qrhid3d12.cpp rhi/qrhid3d12_p.h + rhi/cs_mipmap_p.h + ../3rdparty/D3D12MemoryAllocator/D3D12MemAlloc.h + ../3rdparty/D3D12MemoryAllocator/D3D12MemAlloc.cpp text/windows/qwindowsfontdatabase.cpp text/windows/qwindowsfontdatabase_p.h text/windows/qwindowsfontdatabasebase.cpp text/windows/qwindowsfontdatabasebase_p.h text/windows/qwindowsfontengine.cpp text/windows/qwindowsfontengine_p.h @@ -420,6 +425,7 @@ qt_internal_extend_target(Gui CONDITION WIN32 d3d11 dxgi dxguid + d3d12 ) if(QT_FEATURE_egl) @@ -435,6 +441,14 @@ qt_internal_extend_target(Gui CONDITION QT_FEATURE_egl EGL::EGL ) +# 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 @@ -637,9 +651,6 @@ qt_internal_extend_target(Gui CONDITION QT_FEATURE_harfbuzz AND UIKIT qt_internal_extend_target(Gui CONDITION QT_FEATURE_textodfwriter SOURCES text/qtextodfwriter.cpp text/qtextodfwriter_p.h - text/qzip.cpp - text/qzipreader_p.h - text/qzipwriter_p.h ) qt_internal_extend_target(Gui CONDITION QT_FEATURE_textmarkdownreader @@ -822,7 +833,6 @@ 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 vulkan/qbasicvulkanplatforminstance.cpp vulkan/qbasicvulkanplatforminstance_p.h vulkan/qplatformvulkaninstance.cpp vulkan/qplatformvulkaninstance.h vulkan/qvulkandefaultinstance.cpp vulkan/qvulkandefaultinstance_p.h @@ -958,7 +968,6 @@ qt_internal_extend_target(Gui CONDITION QT_FEATURE_xkbcommon AND UNIX qt_internal_extend_target(Gui CONDITION IOS OR MACOS SOURCES rhi/qrhimetal.mm rhi/qrhimetal_p.h - rhi/qrhimetal_p_p.h PUBLIC_LIBRARIES ${FWMetal} ) diff --git a/src/gui/accessible/linux/atspiadaptor_p.h b/src/gui/accessible/linux/atspiadaptor_p.h index c9877f04..3a890f3d 100644 --- a/src/gui/accessible/linux/atspiadaptor_p.h +++ b/src/gui/accessible/linux/atspiadaptor_p.h @@ -19,7 +19,6 @@ #include #include -#include #include #include diff --git a/src/gui/accessible/linux/qspi_constant_mappings.cpp b/src/gui/accessible/linux/qspi_constant_mappings.cpp index b3e8816d..4fc7bdf8 100644 --- a/src/gui/accessible/linux/qspi_constant_mappings.cpp +++ b/src/gui/accessible/linux/qspi_constant_mappings.cpp @@ -97,6 +97,7 @@ QSpiUIntList spiStateSetFromSpiStates(quint64 states) AtspiRelationType qAccessibleRelationToAtSpiRelation(QAccessible::Relation relation) { + // direction of the relation is "inversed" in Qt and AT-SPI switch (relation) { case QAccessible::Label: return ATSPI_RELATION_LABELLED_BY; @@ -106,6 +107,14 @@ AtspiRelationType qAccessibleRelationToAtSpiRelation(QAccessible::Relation relat return ATSPI_RELATION_CONTROLLED_BY; case QAccessible::Controlled: return ATSPI_RELATION_CONTROLLER_FOR; + case QAccessible::DescriptionFor: + return ATSPI_RELATION_DESCRIBED_BY; + case QAccessible::Described: + return ATSPI_RELATION_DESCRIPTION_FOR; + case QAccessible::FlowsFrom: + return ATSPI_RELATION_FLOWS_TO; + case QAccessible::FlowsTo: + return ATSPI_RELATION_FLOWS_FROM; default: qWarning() << "Cannot return AT-SPI relation for:" << relation; } diff --git a/src/gui/accessible/qaccessible.cpp b/src/gui/accessible/qaccessible.cpp index 13333bf0..583abf53 100644 --- a/src/gui/accessible/qaccessible.cpp +++ b/src/gui/accessible/qaccessible.cpp @@ -354,14 +354,29 @@ Q_LOGGING_CATEGORY(lcAccessibilityCore, "qt.accessibility.core"); \enum QAccessible::RelationFlag This enum type defines bit flags that can be combined to indicate - the relationship between two accessible objects. + the relationship between two accessible objects. It is used by + the relations() function, which returns a list of all the related + interfaces of the calling object, together with the relations + for each object. - \value Label The first object is the label of the second object. - \value Labelled The first object is labelled by the second object. - \value Controller The first object controls the second object. - \value Controlled The first object is controlled by the second object. - \value AllRelations Used as a mask to specify that we are interesting in information - about all relations + Each entry in the list is a QPair where the \c second member stores + the relation type(s) between the \c returned object represented by the + \c first member and the \c origin (the caller) interface/object. + + In the table below, the \c returned object refers to the object in + the returned list, and the \c origin object is the one represented + by the calling interface. + + \value Label The \c returned object is the label for the \c origin object. + \value Labelled The \c returned object is labelled by the \c origin object. + \value Controller The \c returned object controls the \c origin object. + \value Controlled The \c returned object is controlled by the \c origin object. + \value [since 6.6] DescriptionFor The \c returned object provides a description for the \c origin object. + \value [since 6.6] Described The \c returned object is described by the \c origin object. + \value [since 6.6] FlowsFrom Content logically flows from the \c returned object to the \c origin object. + \value [since 6.6] FlowsTo Content logically flows to the \c returned object from the \c origin object. + \value AllRelations Used as a mask to specify that we are interesting in information + about all relations Implementations of relations() return a combination of these flags. Some values are mutually exclusive. @@ -851,11 +866,11 @@ void QAccessible::updateAccessibility(QAccessibleEvent *event) if (iface->tableInterface()) iface->tableInterface()->modelChange(static_cast(event)); } + } - if (updateHandler) { - updateHandler(event); - return; - } + if (updateHandler) { + updateHandler(event); + return; } if (QPlatformAccessibility *pfAccessibility = platformAccessibility()) diff --git a/src/gui/accessible/qaccessible_base.h b/src/gui/accessible/qaccessible_base.h index ac50c262..74926a35 100644 --- a/src/gui/accessible/qaccessible_base.h +++ b/src/gui/accessible/qaccessible_base.h @@ -331,6 +331,10 @@ public: Labelled = 0x00000002, Controller = 0x00000004, Controlled = 0x00000008, + DescriptionFor = 0x00000010, + Described = 0x00000020, + FlowsFrom = 0x00000040, + FlowsTo = 0x00000080, AllRelations = 0xffffffff }; Q_DECLARE_FLAGS(Relation, RelationFlag) diff --git a/src/gui/accessible/qaccessiblecache.cpp b/src/gui/accessible/qaccessiblecache.cpp index 3010ffdd..b41a2481 100644 --- a/src/gui/accessible/qaccessiblecache.cpp +++ b/src/gui/accessible/qaccessiblecache.cpp @@ -130,7 +130,7 @@ void QAccessibleCache::objectDestroyed(QObject* obj) /* In some cases we might add a not fully-constructed object to the cache. This might happen with for instance QWidget subclasses that are in the construction phase. If updateAccessibility() is - called in the constructor of QWidget (directly or indirectly), it it will end up asking for the + called in the constructor of QWidget (directly or indirectly), it will end up asking for the classname of that widget in order to know which accessibility interface subclass the accessibility factory should instantiate and return. However, since that requires a virtual call to metaObject(), it will return the metaObject() of QWidget (not for the subclass), and so diff --git a/src/gui/compat/removed_api.cpp b/src/gui/compat/removed_api.cpp new file mode 100644 index 00000000..8d1c8a19 --- /dev/null +++ b/src/gui/compat/removed_api.cpp @@ -0,0 +1,26 @@ +// 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 + +#define QT_GUI_BUILD_REMOVED_API + +#include "qtguiglobal.h" + +QT_USE_NAMESPACE + +#if QT_GUI_REMOVED_SINCE(6, 6) + +#include "qpixmapcache.h" // inlined API + +// #include "qotherheader.h" +// // implement removed functions from qotherheader.h +// order sections alphabetically + +#endif // QT_GUI_REMOVED_SINCE(6, 6) + +#if QT_GUI_REMOVED_SINCE(6, 7) + +// #include "qotherheader.h" +// // implement removed functions from qotherheader.h +// order sections alphabetically + +#endif // QT_GUI_REMOVED_SINCE(6, 7) diff --git a/src/gui/configure.cmake b/src/gui/configure.cmake index d8319c12..02a80130 100644 --- a/src/gui/configure.cmake +++ b/src/gui/configure.cmake @@ -28,7 +28,8 @@ set_property(CACHE INPUT_libpng PROPERTY STRINGS undefined no qt system) #### Libraries -qt_set01(X11_SUPPORTED LINUX OR HPUX OR FREEBSD OR NETBSD OR OPENBSD OR SOLARIS OR HURD) # special case +qt_set01(X11_SUPPORTED LINUX OR HPUX OR FREEBSD OR NETBSD OR OPENBSD OR SOLARIS OR + HURD) qt_find_package(ATSPI2 PROVIDED_TARGETS PkgConfig::ATSPI2 MODULE_NAME gui QMAKE_LIB atspi) qt_find_package(DirectFB PROVIDED_TARGETS PkgConfig::DirectFB MODULE_NAME gui QMAKE_LIB directfb) qt_find_package(Libdrm PROVIDED_TARGETS Libdrm::Libdrm MODULE_NAME gui QMAKE_LIB drm) @@ -57,7 +58,8 @@ qt_find_package(Mtdev PROVIDED_TARGETS PkgConfig::Mtdev MODULE_NAME gui QMAKE_LI qt_find_package(WrapOpenGL PROVIDED_TARGETS WrapOpenGL::WrapOpenGL MODULE_NAME gui QMAKE_LIB opengl) qt_find_package(GLESv2 PROVIDED_TARGETS GLESv2::GLESv2 MODULE_NAME gui QMAKE_LIB opengl_es2) qt_find_package(Tslib PROVIDED_TARGETS PkgConfig::Tslib MODULE_NAME gui QMAKE_LIB tslib) -qt_find_package(WrapVulkanHeaders PROVIDED_TARGETS WrapVulkanHeaders::WrapVulkanHeaders MODULE_NAME gui QMAKE_LIB vulkan MARK_OPTIONAL) # special case +qt_find_package(WrapVulkanHeaders PROVIDED_TARGETS WrapVulkanHeaders::WrapVulkanHeaders + 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) endif() @@ -277,8 +279,8 @@ qt_config_compile_test(egl_viv LABEL "i.Mx6 EGL" LIBRARIES EGL::EGL - COMPILE_OPTIONS # special case - "-DEGL_API_FB=1" # special case + COMPILE_OPTIONS + "-DEGL_API_FB=1" CODE "#include #include @@ -406,11 +408,9 @@ ioctl(fd, FBIOGET_VSCREENINFO, &vinfo); ") # opengles3 -# special case begin if(WASM) set(extra_compiler_options "-s FULL_ES3=1") endif() -# special case end set(test_libs GLESv2::GLESv2) if(INTEGRITY AND _qt_igy_gui_libs) @@ -421,9 +421,7 @@ qt_config_compile_test(opengles3 LABEL "OpenGL ES 3.0" LIBRARIES ${test_libs} -# special case begin COMPILE_OPTIONS ${extra_compiler_options} -# special case end CODE "#ifdef __APPLE__ # include @@ -553,7 +551,6 @@ libinput_event_pointer_get_scroll_value_v120(nullptr, LIBINPUT_POINTER_AXIS_SCRO } ") -# special case begin # directwrite (assumes DirectWrite2) qt_config_compile_test(directwrite LABEL "WINDOWS directwrite" @@ -615,7 +612,6 @@ int main(int, char **) return 0; } ") -# special case end #### Features @@ -633,21 +629,21 @@ qt_feature("directfb" PRIVATE ) qt_feature("directwrite" PRIVATE LABEL "DirectWrite" - CONDITION TEST_directwrite # special case + CONDITION TEST_directwrite EMIT_IF WIN32 ) qt_feature("directwrite3" PRIVATE LABEL "DirectWrite 3" - CONDITION QT_FEATURE_directwrite AND TEST_directwrite3 # special case + CONDITION QT_FEATURE_directwrite AND TEST_directwrite3 EMIT_IF WIN32 ) qt_feature("direct2d" PRIVATE LABEL "Direct 2D" - CONDITION WIN32 AND NOT WINRT AND TEST_d2d1 # special case + CONDITION WIN32 AND NOT WINRT AND TEST_d2d1 ) qt_feature("direct2d1_1" PRIVATE LABEL "Direct 2D 1.1" - CONDITION QT_FEATURE_direct2d AND TEST_d2d1_1 # special case + CONDITION QT_FEATURE_direct2d AND TEST_d2d1_1 ) qt_feature("evdev" PRIVATE LABEL "evdev" @@ -1316,7 +1312,7 @@ qt_configure_end_summary_section() # end of "GL integrations" section qt_configure_end_summary_section() # end of "XCB" section qt_configure_add_summary_section(NAME "Windows") qt_configure_add_summary_entry(ARGS "direct2d") -qt_configure_add_summary_entry(ARGS "direct2d1_1") ### special case +qt_configure_add_summary_entry(ARGS "direct2d1_1") qt_configure_add_summary_entry(ARGS "directwrite") qt_configure_add_summary_entry(ARGS "directwrite3") qt_configure_end_summary_section() # end of "Windows" section diff --git a/src/gui/doc/includes/QtGuiDoc b/src/gui/doc/includes/QtGuiDoc index 80292141..e8fa7378 100644 --- a/src/gui/doc/includes/QtGuiDoc +++ b/src/gui/doc/includes/QtGuiDoc @@ -16,3 +16,8 @@ #include #include #include + +// rhi +#include +#include +#include diff --git a/src/gui/doc/qtgui.qdocconf b/src/gui/doc/qtgui.qdocconf index 00dc548d..3698cf34 100644 --- a/src/gui/doc/qtgui.qdocconf +++ b/src/gui/doc/qtgui.qdocconf @@ -39,6 +39,7 @@ depends += \ qtdoc \ qmake \ qtcmake \ + qtshadertools \ qttestlib \ qtplatformintegration \ qthelp diff --git a/src/gui/doc/snippets/qfileopenevent/main.cpp b/src/gui/doc/snippets/qfileopenevent/main.cpp index b733bfc3..a94ff581 100644 --- a/src/gui/doc/snippets/qfileopenevent/main.cpp +++ b/src/gui/doc/snippets/qfileopenevent/main.cpp @@ -1,4 +1,4 @@ -// Copyright (C) 2016 Samuel Gaist +// Copyright (C) 2023 Samuel Gaist // SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause //! [QApplication subclass] @@ -19,7 +19,15 @@ public: { if (event->type() == QEvent::FileOpen) { QFileOpenEvent *openEvent = static_cast(event); - qDebug() << "Open file" << openEvent->file(); + const QUrl url = openEvent->url(); + if (url.isLocalFile()) { + QFile localFile(url.toLocalFile()); + // read from local file + } else if (url.isValid()) { + // process according to the URL's schema + } else { + // parse openEvent->file() + } } return QApplication::event(event); diff --git a/src/gui/doc/snippets/rhioffscreen/color.frag b/src/gui/doc/snippets/rhioffscreen/color.frag new file mode 100644 index 00000000..ad9d953d --- /dev/null +++ b/src/gui/doc/snippets/rhioffscreen/color.frag @@ -0,0 +1,16 @@ +//! [0] +#version 440 + +layout(location = 0) in vec3 v_color; +layout(location = 0) out vec4 fragColor; + +layout(std140, binding = 0) uniform buf { + mat4 mvp; + float opacity; +}; + +void main() +{ + fragColor = vec4(v_color * opacity, opacity); +} +//! [0] diff --git a/src/gui/doc/snippets/rhioffscreen/color.vert b/src/gui/doc/snippets/rhioffscreen/color.vert new file mode 100644 index 00000000..0010e555 --- /dev/null +++ b/src/gui/doc/snippets/rhioffscreen/color.vert @@ -0,0 +1,18 @@ +//! [0] +#version 440 + +layout(location = 0) in vec4 position; +layout(location = 1) in vec3 color; +layout(location = 0) out vec3 v_color; + +layout(std140, binding = 0) uniform buf { + mat4 mvp; + float opacity; +}; + +void main() +{ + v_color = color; + gl_Position = mvp * position; +} +//! [0] diff --git a/src/gui/doc/snippets/rhioffscreen/main.cpp b/src/gui/doc/snippets/rhioffscreen/main.cpp new file mode 100644 index 00000000..217b07dc --- /dev/null +++ b/src/gui/doc/snippets/rhioffscreen/main.cpp @@ -0,0 +1,149 @@ +// Copyright (C) 2023 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause + +//! [0] +#include +#include +#include +#include + +int main(int argc, char **argv) +{ + QGuiApplication app(argc, argv); + + std::unique_ptr rhi; +#if defined(Q_OS_WIN) + QRhiD3D12InitParams params; + rhi.reset(QRhi::create(QRhi::D3D12, ¶ms)); +#elif defined(Q_OS_MACOS) || defined(Q_OS_IOS) + QRhiMetalInitParams params; + rhi.reset(QRhi::create(QRhi::Metal, ¶ms)); +#elif QT_CONFIG(vulkan) + QVulkanInstance inst; + inst.setExtensions(QRhiVulkanInitParams::preferredInstanceExtensions()); + if (inst.create()) { + QRhiVulkanInitParams params; + params.inst = &inst; + rhi.reset(QRhi::create(QRhi::Vulkan, ¶ms)); + } else { + qFatal("Failed to create Vulkan instance"); + } +#endif + if (rhi) + qDebug() << rhi->backendName() << rhi->driverInfo(); + else + qFatal("Failed to initialize RHI"); + + float rotation = 0.0f; + float opacity = 1.0f; + int opacityDir = 1; + + std::unique_ptr tex(rhi->newTexture(QRhiTexture::RGBA8, + QSize(1280, 720), + 1, + QRhiTexture::RenderTarget | QRhiTexture::UsedAsTransferSource)); + tex->create(); + std::unique_ptr rt(rhi->newTextureRenderTarget({ tex.get() })); + std::unique_ptr rp(rt->newCompatibleRenderPassDescriptor()); + rt->setRenderPassDescriptor(rp.get()); + rt->create(); + + QMatrix4x4 viewProjection = rhi->clipSpaceCorrMatrix(); + viewProjection.perspective(45.0f, 1280 / 720.f, 0.01f, 1000.0f); + viewProjection.translate(0, 0, -4); + + static float vertexData[] = { // Y up, CCW + 0.0f, 0.5f, 1.0f, 0.0f, 0.0f, + -0.5f, -0.5f, 0.0f, 1.0f, 0.0f, + 0.5f, -0.5f, 0.0f, 0.0f, 1.0f, + }; + + std::unique_ptr vbuf(rhi->newBuffer(QRhiBuffer::Immutable, + QRhiBuffer::VertexBuffer, + sizeof(vertexData))); + vbuf->create(); + + std::unique_ptr ubuf(rhi->newBuffer(QRhiBuffer::Dynamic, + QRhiBuffer::UniformBuffer, + 64 + 4)); + ubuf->create(); + + std::unique_ptr srb(rhi->newShaderResourceBindings()); + srb->setBindings({ + QRhiShaderResourceBinding::uniformBuffer(0, + QRhiShaderResourceBinding::VertexStage | QRhiShaderResourceBinding::FragmentStage, + ubuf.get()) + }); + srb->create(); + + std::unique_ptr ps(rhi->newGraphicsPipeline()); + QRhiGraphicsPipeline::TargetBlend premulAlphaBlend; + premulAlphaBlend.enable = true; + ps->setTargetBlends({ premulAlphaBlend }); + static auto getShader = [](const QString &name) { + QFile f(name); + return f.open(QIODevice::ReadOnly) ? QShader::fromSerialized(f.readAll()) : QShader(); + }; + ps->setShaderStages({ + { QRhiShaderStage::Vertex, getShader(QLatin1String("color.vert.qsb")) }, + { QRhiShaderStage::Fragment, getShader(QLatin1String("color.frag.qsb")) } + }); + QRhiVertexInputLayout inputLayout; + inputLayout.setBindings({ + { 5 * sizeof(float) } + }); + inputLayout.setAttributes({ + { 0, 0, QRhiVertexInputAttribute::Float2, 0 }, + { 0, 1, QRhiVertexInputAttribute::Float3, 2 * sizeof(float) } + }); + ps->setVertexInputLayout(inputLayout); + ps->setShaderResourceBindings(srb.get()); + ps->setRenderPassDescriptor(rp.get()); + ps->create(); + + QRhiCommandBuffer *cb; + for (int frame = 0; frame < 20; ++frame) { + rhi->beginOffscreenFrame(&cb); + + QRhiResourceUpdateBatch *u = rhi->nextResourceUpdateBatch(); + if (frame == 0) + u->uploadStaticBuffer(vbuf.get(), vertexData); + + QMatrix4x4 mvp = viewProjection; + mvp.rotate(rotation, 0, 1, 0); + u->updateDynamicBuffer(ubuf.get(), 0, 64, mvp.constData()); + rotation += 5.0f; + + u->updateDynamicBuffer(ubuf.get(), 64, 4, &opacity); + opacity += opacityDir * 0.2f; + if (opacity < 0.0f || opacity > 1.0f) { + opacityDir *= -1; + opacity = qBound(0.0f, opacity, 1.0f); + } + + cb->beginPass(rt.get(), Qt::green, { 1.0f, 0 }, u); + cb->setGraphicsPipeline(ps.get()); + cb->setViewport({ 0, 0, 1280, 720 }); + cb->setShaderResources(); + const QRhiCommandBuffer::VertexInput vbufBinding(vbuf.get(), 0); + cb->setVertexInput(0, 1, &vbufBinding); + cb->draw(3); + QRhiReadbackResult readbackResult; + u = rhi->nextResourceUpdateBatch(); + u->readBackTexture({ tex.get() }, &readbackResult); + cb->endPass(u); + + rhi->endOffscreenFrame(); + + QImage image(reinterpret_cast(readbackResult.data.constData()), + readbackResult.pixelSize.width(), + readbackResult.pixelSize.height(), + QImage::Format_RGBA8888_Premultiplied); + if (rhi->isYUpInFramebuffer()) + image = image.mirrored(); + image.save(QString::asprintf("frame%d.png", frame)); + } + + return 0; +} +//! [0] diff --git a/src/gui/doc/src/external-resources.qdoc b/src/gui/doc/src/external-resources.qdoc index cea63f59..67ba9f30 100644 --- a/src/gui/doc/src/external-resources.qdoc +++ b/src/gui/doc/src/external-resources.qdoc @@ -46,3 +46,13 @@ \externalpage https://www.lunarg.com/vulkan-sdk/ \title LunarG Vulkan SDK */ + +/*! + \externalpage https://developer.android.com/reference/androidx/core/content/FileProvider + \title Android: FileProvider +*/ + +/*! + \externalpage https://developer.android.com/training/secure-file-sharing/setup-sharing.html + \title Android: Setting up file sharing +*/ diff --git a/src/gui/doc/src/qt6-changes.qdoc b/src/gui/doc/src/qt6-changes.qdoc index f4ad7679..60e1bffb 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 e4052db5..8ba191d7 100644 --- a/src/gui/doc/src/qtgui-overview.qdoc +++ b/src/gui/doc/src/qtgui-overview.qdoc @@ -51,6 +51,56 @@ that prefer more low-level APIs to text and font handling can use classes like QRawFont and QGlyphRun. + \section1 RHI Graphics + + The Qt Rendering Hardware Interface is an abstraction for hardware accelerated + graphics APIs, such as, \l{https://www.khronos.org/opengl/}{OpenGL}, + \l{https://www.khronos.org/opengles/}{OpenGL ES}, + \l{https://docs.microsoft.com/en-us/windows/desktop/direct3d}{Direct3D}, + \l{https://developer.apple.com/metal/}{Metal}, and + \l{https://www.khronos.org/vulkan/}{Vulkan}. + + As an alternative to using OpenGL or Vulkan directly to render to a + QWindow, \l QRhi and the related classes provide a portable, cross-platform + 3D graphics and compute API complemented by a shader conditioning and + transpiling pipeline. This way applications can avoid directly depending on + a single, and, in some cases, vendor or platform-specific 3D API. + + Below is a list of the main RHI-related classes. These are complemented by + a number of additional classes and structs. + + \list + \li QRhi + \li QShader + \li QShaderDescription + \li QRhiCommandBuffer + \li QRhiResourceUpdateBatch + \li QRhiBuffer + \li QRhiRenderBuffer + \li QRhiTexture + \li QRhiSampler + \li QRhiTextureRenderTarget + \li QRhiShaderResourceBindings + \li QRhiGraphicsPipeline + \li QRhiComputePipeline + \li QRhiSwapChain + \endlist + + See the \l{RHI Window Example} for an introductory example of creating a + portable, cross-platform application that performs accelerated 3D rendering + onto a QWindow using QRhi. + + \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. + + \section1 3D Matrix and Vector Math + + The Qt GUI module also contains a few math classes to aid with the most + common mathematical operations related to 3D graphics. These classes + include \l {QMatrix4x4}, \l {QVector2D}, \l {QVector3D}, \l {QVector4D}, + and \l {QQuaternion}. + \section1 OpenGL and OpenGL ES Integration QWindow supports rendering using OpenGL and OpenGL ES, depending on what the @@ -86,10 +136,6 @@ For more information, see the \l {OpenGL Window Example}. - The Qt GUI module also contains a few math classes to aid with the most - common mathematical operations related to 3D graphics. These classes include - \l {QMatrix4x4}, \l {QVector4D}, and \l {QQuaternion}. - A \l {QWindow} created with the \l {QSurface::OpenGLSurface} can be used in combination with \l QPainter and \l QOpenGLPaintDevice to have OpenGL hardware-accelerated 2D graphics by sacrificing some of the visual quality. @@ -104,18 +150,21 @@ On Android, Vulkan headers were added in API level 24 of the NDK. - Relevant classes: + The main relevant classes for low-level Vulkan support are: \list - \li QVulkanDeviceFunctions - \li QVulkanExtension - \li QVulkanFunctions - \li QVulkanInfoVector \li QVulkanInstance - \li QVulkanWindow - \li QVulkanWindowRenderer + \li QVulkanFunctions + \li QVulkanDeviceFunctions \endlist + In addition, \l QVulkanWindow provides a convenience subclass of QWindow + that makes it easier to get started with implementing Vulkan-based + rendering targeting a QWindow. Using this helper class is completely + optional; applications with more advanced Vulkan-based renderers may + instead want to use a QWindow with the \l {QSurface::VulkanSurface} type + directly. + For more information, see the \l{Hello Vulkan Widget Example} and the \l {Hello Vulkan Triangle Example}. diff --git a/src/gui/doc/src/qtgui.qdoc b/src/gui/doc/src/qtgui.qdoc index d39fa722..4ea35232 100644 --- a/src/gui/doc/src/qtgui.qdoc +++ b/src/gui/doc/src/qtgui.qdoc @@ -54,6 +54,8 @@ \list \li \l {Application Windows} {Qt GUI Application Windows} \li \l {2D Graphics} {Qt GUI 2D Graphics} + \li \l {RHI Graphics} {Qt GUI Accelerated 2D and 3D Graphics using the Qt RHI} + \li \l {3D Matrix and Vector Math} {Qt GUI Matrix and Vector Math} \li \l {OpenGL and OpenGL ES Integration} {Qt GUI OpenGL and OpenGL ES Integration} \li \l {Vulkan Integration} {Qt GUI Vulkan Integration} diff --git a/src/gui/doc/src/richtext.qdoc b/src/gui/doc/src/richtext.qdoc index 75b85d3e..d6702856 100644 --- a/src/gui/doc/src/richtext.qdoc +++ b/src/gui/doc/src/richtext.qdoc @@ -11,7 +11,7 @@ \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 @@ -839,11 +839,7 @@ \section1 Supported Tags The following table lists the HTML tags supported by Qt's - \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. + \l{Rich Text Processing}{rich text} engine: \table 70% \header \li Tag diff --git a/src/gui/image/qbitmap.cpp b/src/gui/image/qbitmap.cpp index 96ff03ce..2208cca1 100644 --- a/src/gui/image/qbitmap.cpp +++ b/src/gui/image/qbitmap.cpp @@ -196,7 +196,7 @@ QBitmap QBitmap::fromImage(QImage &&image, Qt::ImageConversionFlags flags) Constructs a bitmap with the given \a size, and sets the contents to the \a bits supplied. - The bitmap data has to be byte aligned and provided in in the bit + The bitmap data has to be byte aligned and provided in the bit order specified by \a monoFormat. The mono format must be either QImage::Format_Mono or QImage::Format_MonoLSB. Use QImage::Format_Mono to specify data on the XBM format. diff --git a/src/gui/image/qicon.cpp b/src/gui/image/qicon.cpp index de4a2b99..f95b194f 100644 --- a/src/gui/image/qicon.cpp +++ b/src/gui/image/qicon.cpp @@ -1121,6 +1121,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) @@ -1129,20 +1133,14 @@ void QIcon::setThemeSearchPaths(const QStringList &paths) } /*! - \since 4.6 + \since 4.6 - Returns the search paths for icon themes. + Returns the search paths for icon themes. - The default value will depend on the platform: + The default search paths will be defined by the platform. + All platforms will also have the resource directory \c{:\icons} as a fallback. - On X11, the search path will use the XDG_DATA_DIRS environment - variable if available. - - 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. - - \sa setThemeSearchPaths(), fromTheme(), setThemeName() + \sa setThemeSearchPaths(), fromTheme(), setThemeName() */ QStringList QIcon::themeSearchPaths() { @@ -1154,12 +1152,13 @@ QStringList QIcon::themeSearchPaths() Returns the fallback search paths for icons. - The fallback search paths are used to look for standalone + The fallback search paths are consulted for standalone icon files if the \l{themeName()}{current icon theme} or \l{fallbackIconTheme()}{fallback icon theme} do not provide results for an icon lookup. - The default value will depend on the platform. + If not set, the fallback search paths will be defined + by the platform. \sa setFallbackSearchPaths(), themeSearchPaths() */ @@ -1173,12 +1172,12 @@ QStringList QIcon::fallbackSearchPaths() Sets the fallback search paths for icons to \a paths. - The fallback search paths are used to look for standalone + The fallback search paths are consulted for standalone icon files if the \l{themeName()}{current icon theme} or \l{fallbackIconTheme()}{fallback icon theme} do not provide results for an icon lookup. - \note To add some path without replacing existing ones: + For example: \snippet code/src_gui_image_qicon.cpp 5 @@ -1194,9 +1193,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(). + + 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. + + \externalpage http://standards.freedesktop.org/icon-theme-spec/icon-theme-spec-latest.html + \title Freedesktop Icon Theme Specification \sa themeSearchPaths(), themeName() */ @@ -1210,8 +1215,15 @@ 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. + + \externalpage https://www.freedesktop.org/ + \title Freedesktop \sa setThemeName(), themeSearchPaths(), fromTheme(), hasThemeIcon() @@ -1226,8 +1238,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() */ @@ -1241,16 +1257,16 @@ QString QIcon::fallbackThemeName() Sets the fallback icon theme to \a name. - The fallback icon theme is used for last resort lookup of icons - not provided by the \l{themeName()}{current icon theme}, - or if the \l{themeName()}{current icon theme} does not exist. + 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 a directory name in the - themeSearchPath() containing an index.theme - file describing its contents. + 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() */ @@ -1262,34 +1278,33 @@ 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{fallbackIconTheme()}{fallback icon theme} is consulted, + before falling back to looking up standalone icon files in the + \l{QIcon::fallbackSearchPaths()}{fallback icon search path}. 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(). + If an \l{themeName()}{icon theme} has not been explicitly + set via setThemeName() a platform defined icon theme will + be used. - \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}. + \note Platform icon themes is only implemented on + \l{Freedesktop} based systems at the moment, + following the \l{Freedesktop Icon Naming Specification}. + In order to use themed icons on other platforms, you will have + to bundle a \l{setThemeName()}{compliant theme} in one of your + themeSearchPaths(), and set the appropriate themeName(). - \note If an icon can't be found in the current theme, then it will be - searched in fallbackSearchPaths() as an unthemed icon. + \externalpage https://specifications.freedesktop.org/icon-naming-spec/icon-naming-spec-latest.html + \title Freedesktop Icon Naming Specification - \sa themeName(), setThemeName(), themeSearchPaths(), fallbackSearchPaths() + \sa themeName(), fallbackIconTheme(), setThemeName(), themeSearchPaths(), fallbackSearchPaths() */ QIcon QIcon::fromTheme(const QString &name) { @@ -1297,32 +1312,36 @@ 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{fallbackIconTheme()}{fallback icon theme} is consulted, + before falling back to looking up standalone icon files in the + \l{QIcon::fallbackSearchPaths()}{fallback icon search path}. + + 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 fallbackIconTheme(), fallbackSearchPaths() */ QIcon QIcon::fromTheme(const QString &name, const QIcon &fallback) { @@ -1338,7 +1357,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() */ @@ -1443,8 +1463,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/qiconengine.cpp b/src/gui/image/qiconengine.cpp index 49528c70..78273bde 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 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_p.h b/src/gui/image/qiconengine_p.h new file mode 100644 index 00000000..3cf09984 --- /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 + +#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 +#include + +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 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 fb702e6b..876c3e0f 100644 --- a/src/gui/image/qiconloader.cpp +++ b/src/gui/image/qiconloader.cpp @@ -133,9 +133,10 @@ void QIconLoader::updateSystemTheme() 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 @@ -150,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(); } @@ -242,7 +249,7 @@ QIconCacheGtkReader::QIconCacheGtkReader(const QString &dirName) : m_isValid(false) { QFileInfo info(dirName + "/icon-theme.cache"_L1); - if (!info.exists() || info.lastModified() < QFileInfo(dirName).lastModified()) + if (!info.exists() || info.lastModified(QTimeZone::UTC) < QFileInfo(dirName).lastModified(QTimeZone::UTC)) return; m_file.setFileName(info.absoluteFilePath()); if (!m_file.open(QFile::ReadOnly)) @@ -257,13 +264,13 @@ QIconCacheGtkReader::QIconCacheGtkReader(const QString &dirName) m_isValid = true; // Check that all the directories are older than the cache - auto lastModified = info.lastModified(); + const QDateTime lastModified = info.lastModified(QTimeZone::UTC); quint32 dirListOffset = read32(8); quint32 dirListLen = read32(dirListOffset); for (uint i = 0; i < dirListLen; ++i) { quint32 offset = read32(dirListOffset + 4 + 4 * i); if (!m_isValid || offset >= m_size || lastModified < QFileInfo(dirName + u'/' - + QString::fromUtf8(reinterpret_cast(m_data + offset))).lastModified()) { + + QString::fromUtf8(reinterpret_cast(m_data + offset))).lastModified(QTimeZone::UTC)) { m_isValid = false; return; } @@ -351,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) { @@ -407,9 +414,9 @@ QStringList QIconTheme::parents() const if (!fallback.isEmpty()) result.append(fallback); - // Ensure that all themes fall back to hicolor - if (!result.contains("hicolor"_L1)) - result.append("hicolor"_L1); + // Ensure that all themes fall back to hicolor as the last theme + result.removeAll("hicolor"_L1); + result.append("hicolor"_L1); return result; } @@ -425,7 +432,8 @@ QThemeIconInfo QIconLoader::findIconHelper(const QString &themeName, const QString &iconName, QStringList &visited) 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()); @@ -594,54 +602,136 @@ QThemeIconInfo QIconLoader::loadIcon(const QString &name) const 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(engine); + if (!engine->isNull()) + debug.quote() << ", " << engine->iconName(); + else + debug << ", null"; + debug << ")"; + } else { + debug << "QIconEngine(nullptr)"; + } + return debug; +} +#endif -// -------- Icon Loader Engine -------- // +QIconEngine *QIconLoader::iconEngine(const QString &iconName) const +{ + qCDebug(lcIconLoader) << "Resolving icon engine for icon" << iconName; + auto *platformTheme = QGuiApplicationPrivate::platformTheme(); + std::unique_ptr iconEngine; + if (!hasUserTheme() && platformTheme) + iconEngine.reset(platformTheme->createIconEngine(iconName)); + if (!iconEngine || iconEngine->isNull()) { + iconEngine.reset(new QIconLoaderEngine(iconName)); + } -QIconLoaderEngine::QIconLoaderEngine(const QString& iconName) - : m_iconName(iconName), m_key(0) + qCDebug(lcIconLoader) << "Resulting engine" << iconEngine.get(); + return iconEngine.release(); +} + +/*! + \internal + \class QThemeIconEngine + \inmodule QtGui + + \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; - -QIconLoaderEngine::QIconLoaderEngine(const QIconLoaderEngine &other) - : QIconEngine(other), - m_iconName(other.m_iconName), - m_key(0) +QThemeIconEngine::QThemeIconEngine(const QThemeIconEngine &other) + : QProxyIconEngine() + , m_iconName(other.m_iconName) { } -QIconEngine *QIconLoaderEngine::clone() const +QString QThemeIconEngine::key() const { - return new QIconLoaderEngine(*this); + // 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; } -bool QIconLoaderEngine::read(QDataStream &in) { +QIconEngine *QThemeIconEngine::clone() const +{ + return new QThemeIconEngine(*this); +} + +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; } +QIconEngine *QThemeIconEngine::proxiedEngine() const +{ + 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(); +} + +/*! + \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)) +{ +} + +QIconLoaderEngine::~QIconLoaderEngine() = default; + +QIconEngine *QIconLoaderEngine::clone() const +{ + Q_UNREACHABLE(); + return nullptr; // Cannot be cloned +} + bool QIconLoaderEngine::hasIcon() const { return !(m_info.entries.empty()); } -// Lazily load the icon -void QIconLoaderEngine::ensureLoaded() -{ - if (QIconLoader::instance()->themeKey() != m_key) { - m_info = QIconLoader::instance()->loadIcon(m_iconName); - m_key = QIconLoader::instance()->themeKey(); - } -} - void QIconLoaderEngine::paint(QPainter *painter, const QRect &rect, QIcon::Mode mode, QIcon::State state) { @@ -747,8 +837,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; @@ -817,8 +905,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); @@ -833,19 +919,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(); @@ -855,7 +938,7 @@ QList QIconLoaderEngine::availableSizes(QIcon::Mode mode, QIcon::State st { Q_UNUSED(mode); Q_UNUSED(state); - ensureLoaded(); + const qsizetype N = qsizetype(m_info.entries.size()); QList sizes; sizes.reserve(N); diff --git a/src/gui/image/qiconloader_p.h b/src/gui/image/qiconloader_p.h index 75bd06a5..816fb770 100644 --- a/src/gui/image/qiconloader_p.h +++ b/src/gui/image/qiconloader_p.h @@ -22,6 +22,7 @@ #include #include #include +#include #include #include #include @@ -86,6 +87,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 m_proxiedEngine; +}; + class QIconLoaderEngine : public QIconEngine { public: @@ -96,8 +118,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 +127,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; }; @@ -162,6 +181,8 @@ public: void ensureInitialized(); bool hasUserTheme() const { return !m_userTheme.isEmpty(); } + QIconEngine *iconEngine(const QString &iconName) const; + private: QThemeIconInfo findIconHelper(const QString &themeName, const QString &iconName, diff --git a/src/gui/image/qpicture.h b/src/gui/image/qpicture.h index c8b0966a..bc8be6c4 100644 --- a/src/gui/image/qpicture.h +++ b/src/gui/image/qpicture.h @@ -6,8 +6,8 @@ #include #include +#include #include -#include #include QT_BEGIN_NAMESPACE diff --git a/src/gui/image/qpixmap.cpp b/src/gui/image/qpixmap.cpp index 3efb3eba..8b7de7ac 100644 --- a/src/gui/image/qpixmap.cpp +++ b/src/gui/image/qpixmap.cpp @@ -717,7 +717,7 @@ bool QPixmap::load(const QString &fileName, const char *format, Qt::ImageConvers QString key = "qt_pixmap"_L1 % info.absoluteFilePath() - % HexString(info.lastModified().toSecsSinceEpoch()) + % HexString(info.lastModified(QTimeZone::UTC).toSecsSinceEpoch()) % HexString(info.size()) % HexString(data ? data->pixelType() : QPlatformPixmap::PixmapType); diff --git a/src/gui/image/qpixmap.h b/src/gui/image/qpixmap.h index b6fa5da8..5be98be1 100644 --- a/src/gui/image/qpixmap.h +++ b/src/gui/image/qpixmap.h @@ -8,8 +8,8 @@ #include #include #include +#include #include // char*->QString conversion -#include #include #include diff --git a/src/gui/image/qpixmap_win.cpp b/src/gui/image/qpixmap_win.cpp index 5a340aac..24af7636 100644 --- a/src/gui/image/qpixmap_win.cpp +++ b/src/gui/image/qpixmap_win.cpp @@ -324,7 +324,7 @@ HBITMAP qt_imageToWinHBITMAP(const QImage &imageIn, int hbitmapFormat) It is the caller's responsibility to free the \c HBITMAP data after use. - For usage with with standard GDI calls, such as \c BitBlt(), the image + For usage with standard GDI calls, such as \c BitBlt(), the image should have the format QImage::Format_RGB32. When using the resulting HBITMAP for the \c AlphaBlend() GDI function, diff --git a/src/gui/image/qpixmapcache.cpp b/src/gui/image/qpixmapcache.cpp index c70aba90..45c9743f 100644 --- a/src/gui/image/qpixmapcache.cpp +++ b/src/gui/image/qpixmapcache.cpp @@ -8,6 +8,8 @@ #include "qthread.h" #include "qcoreapplication.h" +using namespace std::chrono_literals; + QT_BEGIN_NAMESPACE /*! @@ -182,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); @@ -202,7 +203,8 @@ public: bool flushDetachedPixmaps(bool nt); private: - enum { soon_time = 10000, flush_time = 30000 }; + static constexpr auto soon_time = 10s; + static constexpr auto flush_time = 30s; int *keyArray; int theid; int ps; @@ -216,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() @@ -310,6 +317,7 @@ QPixmapCache::Key QPMCache::insert(const QPixmap &pixmap, int cost) { QPixmapCache::Key cacheKey = createKey(); // invalidated by ~QPixmapCacheEntry on failed insert bool success = QCache::insert(cacheKey, new QPixmapCacheEntry(cacheKey, pixmap), cost); + Q_ASSERT(success || !cacheKey.isValid()); if (success) { if (!theid) { theid = startTimer(flush_time); @@ -319,25 +327,6 @@ 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::remove(key); - - QPixmapCache::Key cacheKey = createKey(); - - bool success = QCache::insert(cacheKey, new QPixmapCacheEntry(cacheKey, pixmap), cost); - if (success) { - if (!theid) { - theid = startTimer(flush_time); - t = false; - } - const_cast(key) = cacheKey; - } - return success; -} - bool QPMCache::remove(const QString &key) { const auto cacheKey = cacheKeys.take(key); @@ -440,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) @@ -492,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)); } @@ -519,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). @@ -573,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 433890c6..72ee1b79 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) = 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 c9a90563..43c4d978 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: diff --git a/src/gui/itemmodels/qfileinfogatherer.cpp b/src/gui/itemmodels/qfileinfogatherer.cpp index 1b88b3e0..0e0a3b11 100644 --- a/src/gui/itemmodels/qfileinfogatherer.cpp +++ b/src/gui/itemmodels/qfileinfogatherer.cpp @@ -83,7 +83,7 @@ void QFileInfoGatherer::driveRemoved() const QFileInfoList driveInfoList = QDir::drives(); for (const QFileInfo &fi : driveInfoList) drives.append(translateDriveName(fi)); - newListOfFiles(QString(), drives); + emit newListOfFiles(QString(), drives); } bool QFileInfoGatherer::resolveSymlinks() const diff --git a/src/gui/itemmodels/qfileinfogatherer_p.h b/src/gui/itemmodels/qfileinfogatherer_p.h index c8a56a33..e4b2bc88 100644 --- a/src/gui/itemmodels/qfileinfogatherer_p.h +++ b/src/gui/itemmodels/qfileinfogatherer_p.h @@ -51,7 +51,7 @@ public: return mFileInfo == fileInfo.mFileInfo && displayType == fileInfo.displayType && permissions() == fileInfo.permissions() - && lastModified() == fileInfo.lastModified(); + && lastModified(QTimeZone::UTC) == fileInfo.lastModified(QTimeZone::UTC); } #ifndef QT_NO_FSFILEENGINE @@ -95,8 +95,8 @@ public: return mFileInfo; } - QDateTime lastModified() const { - return mFileInfo.lastModified(); + QDateTime lastModified(const QTimeZone &tz) const { + return mFileInfo.lastModified(tz); } qint64 size() const { diff --git a/src/gui/itemmodels/qfilesystemmodel.cpp b/src/gui/itemmodels/qfilesystemmodel.cpp index cf9afa21..3c52f27f 100644 --- a/src/gui/itemmodels/qfilesystemmodel.cpp +++ b/src/gui/itemmodels/qfilesystemmodel.cpp @@ -143,7 +143,7 @@ QFileInfo QFileSystemModel::fileInfo(const QModelIndex &index) const \fn void QFileSystemModel::fileRenamed(const QString &path, const QString &oldName, const QString &newName) This signal is emitted whenever a file with the \a oldName is successfully - renamed to \a newName. The file is located in in the directory \a path. + renamed to \a newName. The file is located in the directory \a path. */ /*! @@ -529,14 +529,38 @@ QString QFileSystemModel::type(const QModelIndex &index) const } /*! - Returns the date and time when \a index was last modified. + Returns the date and time (in local time) when \a index was last modified. + + This is an overloaded function, equivalent to calling: + \code + lastModified(index, QTimeZone::LocalTime); + \endcode + + If \a index is invalid, a default constructed QDateTime is returned. */ QDateTime QFileSystemModel::lastModified(const QModelIndex &index) const +{ + return lastModified(index, QTimeZone::LocalTime); +} + +/*! + \since 6.6 + Returns the date and time, in the time zone \a tz, when + \a index was last modified. + + Typical arguments for \a tz are \c QTimeZone::UTC or \c QTimeZone::LocalTime. + UTC does not require any conversion from the time returned by the native file + system API, therefore getting the time in UTC is potentially faster. LocalTime + is typically chosen if the time is shown to the user. + + If \a index is invalid, a default constructed QDateTime is returned. + */ +QDateTime QFileSystemModel::lastModified(const QModelIndex &index, const QTimeZone &tz) const { Q_D(const QFileSystemModel); if (!index.isValid()) return QDateTime(); - return d->node(index)->lastModified(); + return d->node(index)->lastModified(tz); } /*! @@ -765,7 +789,7 @@ QString QFileSystemModelPrivate::time(const QModelIndex &index) const if (!index.isValid()) return QString(); #if QT_CONFIG(datestring) - return QLocale::system().toString(node(index)->lastModified(), QLocale::ShortFormat); + return QLocale::system().toString(node(index)->lastModified(QTimeZone::LocalTime), QLocale::ShortFormat); #else Q_UNUSED(index); return QString(); @@ -1031,10 +1055,12 @@ public: } case QFileSystemModelPrivate::TimeColumn: { - if (l->lastModified() == r->lastModified()) + const QDateTime left = l->lastModified(QTimeZone::UTC); + const QDateTime right = r->lastModified(QTimeZone::UTC); + if (left == right) return naturalCompare.compare(l->fileName, r->fileName) < 0; - return l->lastModified() < r->lastModified(); + return left < right; } } Q_ASSERT(false); @@ -2059,8 +2085,8 @@ void QFileSystemModelPrivate::init() #if QT_CONFIG(filesystemwatcher) q->connect(&fileInfoGatherer, SIGNAL(newListOfFiles(QString,QStringList)), q, SLOT(_q_directoryChanged(QString,QStringList))); - q->connect(&fileInfoGatherer, SIGNAL(updates(QString, QList>)), q, - SLOT(_q_fileSystemChanged(QString, QList>))); + q->connect(&fileInfoGatherer, SIGNAL(updates(QString,QList>)), q, + SLOT(_q_fileSystemChanged(QString,QList>))); q->connect(&fileInfoGatherer, SIGNAL(nameResolved(QString,QString)), q, SLOT(_q_resolvedName(QString,QString))); q->connect(&fileInfoGatherer, SIGNAL(directoryLoaded(QString)), diff --git a/src/gui/itemmodels/qfilesystemmodel.h b/src/gui/itemmodels/qfilesystemmodel.h index d7bdf825..2aa81923 100644 --- a/src/gui/itemmodels/qfilesystemmodel.h +++ b/src/gui/itemmodels/qfilesystemmodel.h @@ -113,7 +113,9 @@ public: bool isDir(const QModelIndex &index) const; qint64 size(const QModelIndex &index) const; QString type(const QModelIndex &index) const; + QDateTime lastModified(const QModelIndex &index) const; + QDateTime lastModified(const QModelIndex &index, const QTimeZone &tz) const; QModelIndex mkdir(const QModelIndex &parent, const QString &name); bool rmdir(const QModelIndex &index); diff --git a/src/gui/itemmodels/qfilesystemmodel_p.h b/src/gui/itemmodels/qfilesystemmodel_p.h index cfcf63a6..9198c2f5 100644 --- a/src/gui/itemmodels/qfilesystemmodel_p.h +++ b/src/gui/itemmodels/qfilesystemmodel_p.h @@ -90,7 +90,7 @@ public: inline qint64 size() const { if (info && !info->isDir()) return info->size(); return 0; } inline QString type() const { if (info) return info->displayType; return QLatin1StringView(""); } - inline QDateTime lastModified() const { if (info) return info->lastModified(); return QDateTime(); } + inline QDateTime lastModified(const QTimeZone &tz) const { return info ? info->lastModified(tz) : QDateTime(); } inline QFile::Permissions permissions() const { if (info) return info->permissions(); return { }; } inline bool isReadable() const { return ((permissions() & QFile::ReadUser) != 0); } inline bool isWritable() const { return ((permissions() & QFile::WriteUser) != 0); } diff --git a/src/gui/kernel/qaction.cpp b/src/gui/kernel/qaction.cpp index dccdb1ac..c67cfd53 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/mdi/mainwindow.cpp qaction setup + 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/qevent.cpp b/src/gui/kernel/qevent.cpp index d2bf905e..19fdcdc9 100644 --- a/src/gui/kernel/qevent.cpp +++ b/src/gui/kernel/qevent.cpp @@ -1440,12 +1440,13 @@ Q_IMPL_EVENT_COMMON(QKeyEvent) Returns the Unicode text that this key generated. - Return values when modifier keys such as - Shift, Control, Alt, and Meta are pressed - differ among platforms and could return an empty string. + The text is not limited to the printable range of Unicode + code points, and may include control characters or characters + from other Unicode categories, including QChar::Other_PrivateUse. - \note \l key() will always return a valid value, - independent of modifier keys. + The text may also be empty, for example when modifier keys such as + Shift, Control, Alt, and Meta are pressed (depending on the platform). + The key() function will always return a valid value. \sa Qt::WA_KeyCompression */ @@ -3589,10 +3590,15 @@ Q_IMPL_EVENT_COMMON(QShowEvent) \snippet qfileopenevent/Info.plist Custom Info.plist - The following implementation of a QApplication subclass prints the path to - the file that was, for example, dropped on the Dock icon of the application. + The following implementation of a QApplication subclass shows how to handle + QFileOpenEvent to open the file that was, for example, dropped on the Dock + icon of the application. \snippet qfileopenevent/main.cpp QApplication subclass + + Note how \c{QFileOpenEvent::file()} is not guaranteed to be the name of a + local file that can be opened using QFile. The contents of the string depend + on the source application. */ /*! @@ -3620,19 +3626,23 @@ Q_IMPL_EVENT_COMMON(QFileOpenEvent) /*! \fn QString QFileOpenEvent::file() const - Returns the file that is being opened. + Returns the name of the file that the application should open. + + This is not guaranteed to be the path to a local file. */ /*! \fn QUrl QFileOpenEvent::url() const - Returns the url that is being opened. + Returns the url that the application should open. \since 4.6 */ +#if QT_DEPRECATED_SINCE(6, 6) /*! \fn bool QFileOpenEvent::openFile(QFile &file, QIODevice::OpenMode flags) const + \deprecated [6.6] interpret the string returned by file() Opens a QFile on the \a file referenced by this event in the mode specified by \a flags. Returns \c true if successful; otherwise returns \c false. @@ -3647,6 +3657,7 @@ bool QFileOpenEvent::openFile(QFile &file, QIODevice::OpenMode flags) const file.setFileName(m_file); return file.open(flags); } +#endif #ifndef QT_NO_TOOLBAR /*! diff --git a/src/gui/kernel/qevent.h b/src/gui/kernel/qevent.h index a3fd09d9..900524d2 100644 --- a/src/gui/kernel/qevent.h +++ b/src/gui/kernel/qevent.h @@ -852,7 +852,10 @@ public: inline QString file() const { return m_file; } QUrl url() const { return m_url; } +#if QT_DEPRECATED_SINCE(6, 6) + QT_DEPRECATED_VERSION_X_6_6("Interpret the string returned by file()") bool openFile(QFile &file, QIODevice::OpenMode flags) const; +#endif private: QString m_file; QUrl m_url; diff --git a/src/gui/kernel/qguiapplication.cpp b/src/gui/kernel/qguiapplication.cpp index fb973a96..c5f03719 100644 --- a/src/gui/kernel/qguiapplication.cpp +++ b/src/gui/kernel/qguiapplication.cpp @@ -1248,10 +1248,6 @@ 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."; @@ -1353,7 +1349,7 @@ static void init_platform(const QString &pluginNamesWithArguments, const QString const auto platformIntegration = QGuiApplicationPrivate::platformIntegration(); fontSmoothingGamma = platformIntegration->styleHint(QPlatformIntegration::FontSmoothingGamma).toReal(); QCoreApplication::setAttribute(Qt::AA_DontShowShortcutsInContextMenus, - !platformIntegration->styleHint(QPlatformIntegration::ShowShortcutsInContextMenus).toBool()); + !QGuiApplication::styleHints()->showShortcutsInContextMenus()); } static void init_plugins(const QList &pluginList) @@ -2091,6 +2087,9 @@ void Q_TRACE_INSTRUMENT(qtgui) QGuiApplicationPrivate::processWindowSystemEvent( case QWindowSystemInterfacePrivate::WindowScreenChanged: QGuiApplicationPrivate::processWindowScreenChangedEvent(static_cast(e)); break; + case QWindowSystemInterfacePrivate::WindowDevicePixelRatioChanged: + QGuiApplicationPrivate::processWindowDevicePixelRatioChangedEvent(static_cast(e)); + break; case QWindowSystemInterfacePrivate::SafeAreaMarginsChanged: QGuiApplicationPrivate::processSafeAreaMarginsChangedEvent(static_cast(e)); break; @@ -2592,28 +2591,28 @@ void QGuiApplicationPrivate::processWindowStateChangedEvent(QWindowSystemInterfa void QGuiApplicationPrivate::processWindowScreenChangedEvent(QWindowSystemInterfacePrivate::WindowScreenChangedEvent *wse) { - if (QWindow *window = wse->window.data()) { - if (window->screen() == wse->screen.data()) - 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); - } + QWindow *window = wse->window.data(); + if (!window) + 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 + if (window->screen() == wse->screen.data()) + 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); } } +void QGuiApplicationPrivate::processWindowDevicePixelRatioChangedEvent(QWindowSystemInterfacePrivate::WindowDevicePixelRatioChangedEvent *wde) +{ + if (wde->window.isNull()) + return; + QWindowPrivate::get(wde->window)->updateDevicePixelRatio(); +} + void QGuiApplicationPrivate::processSafeAreaMarginsChangedEvent(QWindowSystemInterfacePrivate::SafeAreaMarginsChangedEvent *wse) { if (wse->window.isNull()) @@ -3199,6 +3198,10 @@ void QGuiApplicationPrivate::processScreenLogicalDotsPerInchChange(QWindowSystem s->d_func()->updateGeometry(); } + for (QWindow *window : QGuiApplication::allWindows()) + if (window->screen() == e->screen) + QWindowPrivate::get(window)->updateDevicePixelRatio(); + resetCachedDevicePixelRatio(); } diff --git a/src/gui/kernel/qguiapplication_p.h b/src/gui/kernel/qguiapplication_p.h index 7abd65a1..c96cbdd0 100644 --- a/src/gui/kernel/qguiapplication_p.h +++ b/src/gui/kernel/qguiapplication_p.h @@ -116,6 +116,7 @@ public: static void processActivatedEvent(QWindowSystemInterfacePrivate::ActivatedWindowEvent *e); static void processWindowStateChangedEvent(QWindowSystemInterfacePrivate::WindowStateChangedEvent *e); static void processWindowScreenChangedEvent(QWindowSystemInterfacePrivate::WindowScreenChangedEvent *e); + static void processWindowDevicePixelRatioChangedEvent(QWindowSystemInterfacePrivate::WindowDevicePixelRatioChangedEvent *e); static void processSafeAreaMarginsChangedEvent(QWindowSystemInterfacePrivate::SafeAreaMarginsChangedEvent *e); @@ -419,7 +420,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/qhighdpiscaling.cpp b/src/gui/kernel/qhighdpiscaling.cpp index bbd8f8a8..a0e1b48d 100644 --- a/src/gui/kernel/qhighdpiscaling.cpp +++ b/src/gui/kernel/qhighdpiscaling.cpp @@ -494,7 +494,8 @@ void QHighDpiScaling::updateHighDpiScaling() qCDebug(lcHighDpi) << "Applying screen factors" << m_screenFactors; int i = -1; const auto screens = QGuiApplication::screens(); - for (const auto &[name, factor] : m_screenFactors) { + for (const auto &[name, rawFactor]: m_screenFactors) { + const qreal factor = roundScaleFactor(rawFactor); ++i; if (name.isNull()) { if (i < screens.size()) @@ -540,15 +541,17 @@ void QHighDpiScaling::setGlobalFactor(qreal factor) if (!QGuiApplication::allWindows().isEmpty()) qWarning("QHighDpiScaling::setFactor: Should only be called when no windows exist."); + const auto screens = QGuiApplication::screens(); + + std::vector updateEmitters; + for (QScreen *screen : screens) + updateEmitters.emplace_back(screen); + m_globalScalingActive = !qFuzzyCompare(factor, qreal(1)); m_factor = m_globalScalingActive ? factor : qreal(1); m_active = m_globalScalingActive || m_screenFactorSet || m_platformPluginDpiScalingActive ; - const auto screens = QGuiApplication::screens(); for (QScreen *screen : screens) screen->d_func()->updateGeometry(); - - // FIXME: The geometry has been updated based on the new scale factor, - // but we don't emit any geometry change signals for the screens. } static const char scaleFactorProperty[] = "_q_scaleFactor"; @@ -565,6 +568,8 @@ void QHighDpiScaling::setScreenFactor(QScreen *screen, qreal factor) m_active = true; } + QScreenPrivate::UpdateEmitter updateEmitter(screen); + // Prefer associating the factor with screen name over the object // since the screen object may be deleted on screen disconnects. const QString name = screen->name(); @@ -573,9 +578,7 @@ void QHighDpiScaling::setScreenFactor(QScreen *screen, qreal factor) else QHighDpiScaling::m_namedScreenScaleFactors.insert(name, factor); - // hack to force re-evaluation of screen geometry - if (screen->handle()) - screen->d_func()->setPlatformScreen(screen->handle()); // updates geometries based on scale factor + screen->d_func()->updateGeometry(); } QPoint QHighDpiScaling::mapPositionToNative(const QPoint &pos, const QPlatformScreen *platformScreen) diff --git a/src/gui/kernel/qopenglcontext.cpp b/src/gui/kernel/qopenglcontext.cpp index a018f867..1dab68e5 100644 --- a/src/gui/kernel/qopenglcontext.cpp +++ b/src/gui/kernel/qopenglcontext.cpp @@ -69,6 +69,7 @@ QOpenGLContext *qt_gl_global_share_context() /*! \class QOpenGLContext + \ingroup painting-3D \inmodule QtGui \since 5.0 \brief The QOpenGLContext class represents a native OpenGL context, enabling diff --git a/src/gui/kernel/qpalette.cpp b/src/gui/kernel/qpalette.cpp index 4abbcd5e..785572e7 100644 --- a/src/gui/kernel/qpalette.cpp +++ b/src/gui/kernel/qpalette.cpp @@ -16,12 +16,17 @@ static int qt_palette_count = 1; static constexpr QPalette::ResolveMask colorRoleOffset(QPalette::ColorGroup colorGroup) { - return qToUnderlying(QPalette::NColorRoles) * qToUnderlying(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); } @@ -100,6 +105,24 @@ static void qt_placeholder_from_text(QPalette &pal, int alpha = 50) } } +static void qt_ensure_default_accent_color(QPalette &pal) +{ + // have a lighter/darker factor handy, depending on dark/light heuristics + const int lighter = pal.base().color().lightness() > pal.text().color().lightness() ? 130 : 70; + + // 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(i); + 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::Accent, accentBrush); + } + } +} + static void qt_palette_from_color(QPalette &pal, const QColor &button) { int h, s, v; @@ -124,6 +147,7 @@ static void qt_palette_from_color(QPalette &pal, const QColor &button) whiteBrush, buttonBrush, buttonBrush); qt_placeholder_from_text(pal); + qt_ensure_default_accent_color(pal); } /*! @@ -324,6 +348,14 @@ static void qt_palette_from_color(QPalette &pal, const QColor &button) \sa ColorRole, brush() */ +/*! + \fn const QBrush & QPalette::accent() const + + Returns the accent brush of the current color group. + + \sa ColorRole, brush() +*/ + /*! \fn const QBrush & QPalette::link() const @@ -524,6 +556,13 @@ static void qt_palette_from_color(QPalette &pal, const QColor &button) item. By default, the highlight color is Qt::darkBlue. + \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. + Unless explicitly set, it defaults to Highlight. + \value HighlightedText A text color that contrasts with \c Highlight. By default, the highlighted text color is Qt::white. @@ -613,6 +652,7 @@ QPalette::QPalette(const QBrush &windowText, const QBrush &button, base, window); qt_placeholder_from_text(*this); + qt_ensure_default_accent_color(*this); } @@ -670,6 +710,7 @@ QPalette::QPalette(const QColor &button, const QColor &window) whiteBrush, baseBrush, windowBrush); qt_placeholder_from_text(*this); + qt_ensure_default_accent_color(*this); } /*! @@ -837,6 +878,10 @@ void QPalette::setBrush(ColorGroup cg, ColorRole cr, const QBrush &b) */ bool QPalette::isBrushSet(ColorGroup cg, ColorRole cr) const { + // NoRole has no resolve mask and should never be set anyway + if (cr == NoRole) + return false; + if (cg == Current) cg = currentGroup; @@ -885,8 +930,11 @@ void QPalette::detach() Returns \c true (usually quickly) if this palette is equal to \a p; otherwise returns \c false (slowly). - \note The current ColorGroup is not taken into account when - comparing palettes + \note The following is not taken into account when comparing palettes: + \list + \li the \c current ColorGroup + \li ColorRole NoRole \since 6.6 + \endlist \sa operator!=() */ @@ -896,6 +944,9 @@ bool QPalette::operator==(const QPalette &p) const return true; for(int grp = 0; grp < (int)NColorGroups; grp++) { for(int role = 0; role < (int)NColorRoles; role++) { + // Dont't verify NoRole, because it has no resolve bit + if (role == NoRole) + continue; if (d->data->br[grp][role] != p.d->data->br[grp][role]) return false; } @@ -979,6 +1030,10 @@ 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 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))))) { palette.d->data.detach(); @@ -1054,6 +1109,9 @@ QDataStream &operator<<(QDataStream &s, const QPalette &p) max = QPalette::AlternateBase + 1; else if (s.version() <= QDataStream::Qt_5_11) max = QPalette::ToolTipText + 1; + else if (s.version() <= QDataStream::Qt_6_5) + max = QPalette::PlaceholderText + 1; + for (int r = 0; r < max; r++) s << p.d->data->br[grp][r]; } @@ -1097,15 +1155,25 @@ QDataStream &operator>>(QDataStream &s, QPalette &p) } else if (s.version() <= QDataStream::Qt_5_11) { p = QPalette(); max = QPalette::ToolTipText + 1; + } else if (s.version() <= QDataStream::Qt_6_5) { + p = QPalette(); + max = QPalette::PlaceholderText + 1; } + QBrush tmp; for(int grp = 0; grp < (int)QPalette::NColorGroups; ++grp) { + const QPalette::ColorGroup group = static_cast(grp); for(int role = 0; role < max; ++role) { s >> tmp; - p.setBrush((QPalette::ColorGroup)grp, (QPalette::ColorRole)role, tmp); + p.setBrush(group, (QPalette::ColorRole)role, tmp); } + + // Accent defaults to Highlight for stream versions that don't have it. + if (s.version() < QDataStream::Qt_6_6) + p.setBrush(group, QPalette::Accent, p.brush(group, QPalette::Highlight)); } + } return s; } diff --git a/src/gui/kernel/qpalette.h b/src/gui/kernel/qpalette.h index 0a6dc0b3..a6162e10 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,7 +56,8 @@ public: NoRole, ToolTipBase, ToolTipText, PlaceholderText, - NColorRoles = PlaceholderText + 1, + Accent, + NColorRoles = Accent + 1, }; Q_ENUM(ColorRole) @@ -98,6 +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 &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/qplatformdialoghelper.cpp b/src/gui/kernel/qplatformdialoghelper.cpp index e1f84053..7153c90f 100644 --- a/src/gui/kernel/qplatformdialoghelper.cpp +++ b/src/gui/kernel/qplatformdialoghelper.cpp @@ -768,7 +768,7 @@ public: {} QString windowTitle; - QMessageDialogOptions::Icon icon; + QMessageDialogOptions::StandardIcon icon; QString text; QString informativeText; QString detailedText; @@ -778,6 +778,7 @@ public: QPixmap iconPixmap; QString checkBoxLabel; Qt::CheckState checkBoxState = Qt::Unchecked; + QMessageDialogOptions::Options options; }; QMessageDialogOptions::QMessageDialogOptions(QMessageDialogOptionsPrivate *dd) @@ -819,12 +820,12 @@ void QMessageDialogOptions::setWindowTitle(const QString &title) d->windowTitle = title; } -QMessageDialogOptions::Icon QMessageDialogOptions::icon() const +QMessageDialogOptions::StandardIcon QMessageDialogOptions::standardIcon() const { return d->icon; } -void QMessageDialogOptions::setIcon(Icon icon) +void QMessageDialogOptions::setStandardIcon(StandardIcon icon) { d->icon = icon; } @@ -924,6 +925,29 @@ Qt::CheckState QMessageDialogOptions::checkBoxState() const return d->checkBoxState; } +void QMessageDialogOptions::setOption(QMessageDialogOptions::Option option, bool on) +{ + if (!(d->options & option) != !on) + setOptions(d->options ^ option); +} + +bool QMessageDialogOptions::testOption(QMessageDialogOptions::Option option) const +{ + return d->options & option; +} + +void QMessageDialogOptions::setOptions(QMessageDialogOptions::Options options) +{ + if (options != d->options) + d->options = options; +} + +QMessageDialogOptions::Options QMessageDialogOptions::options() const +{ + return d->options; +} + + QPlatformDialogHelper::ButtonRole QPlatformDialogHelper::buttonRole(QPlatformDialogHelper::StandardButton button) { switch (button) { diff --git a/src/gui/kernel/qplatformdialoghelper.h b/src/gui/kernel/qplatformdialoghelper.h index fc2e85a8..161ea067 100644 --- a/src/gui/kernel/qplatformdialoghelper.h +++ b/src/gui/kernel/qplatformdialoghelper.h @@ -403,9 +403,16 @@ protected: ~QMessageDialogOptions(); public: + // Keep in sync with QMessageBox Option + enum class Option { + DontUseNativeDialog = 0x00000001, + }; + Q_DECLARE_FLAGS(Options, Option); + Q_FLAG(Options); + // Keep in sync with QMessageBox::Icon - enum Icon { NoIcon, Information, Warning, Critical, Question }; - Q_ENUM(Icon) + enum StandardIcon { NoIcon, Information, Warning, Critical, Question }; + Q_ENUM(StandardIcon) static QSharedPointer create(); QSharedPointer clone() const; @@ -413,8 +420,8 @@ public: QString windowTitle() const; void setWindowTitle(const QString &); - void setIcon(Icon icon); - Icon icon() const; + void setStandardIcon(StandardIcon icon); + StandardIcon standardIcon() const; void setIconPixmap(const QPixmap &pixmap); QPixmap iconPixmap() const; @@ -428,6 +435,11 @@ public: void setDetailedText(const QString &text); QString detailedText() const; + void setOption(Option option, bool on = true); + bool testOption(Option option) const; + void setOptions(Options options); + Options options() const; + void setStandardButtons(QPlatformDialogHelper::StandardButtons buttons); QPlatformDialogHelper::StandardButtons standardButtons() const; diff --git a/src/gui/kernel/qplatforminputcontext.cpp b/src/gui/kernel/qplatforminputcontext.cpp index b8a53685..985cefa1 100644 --- a/src/gui/kernel/qplatforminputcontext.cpp +++ b/src/gui/kernel/qplatforminputcontext.cpp @@ -95,7 +95,7 @@ void QPlatformInputContext::update(Qt::InputMethodQueries) } /*! - Called when when the word currently being composed in input item is tapped by + Called when the word currently being composed in the input item is tapped by the user. Input methods often use this information to offer more word suggestions to the user. */ diff --git a/src/gui/kernel/qplatformintegration.cpp b/src/gui/kernel/qplatformintegration.cpp index c95742b3..b7117b12 100644 --- a/src/gui/kernel/qplatformintegration.cpp +++ b/src/gui/kernel/qplatformintegration.cpp @@ -416,6 +416,8 @@ QVariant QPlatformIntegration::styleHint(StyleHint hint) const return QPlatformTheme::defaultThemeHint(QPlatformTheme::FlickMaximumVelocity); case FlickDeceleration: return QPlatformTheme::defaultThemeHint(QPlatformTheme::FlickDeceleration); + case UnderlineShortcut: + return true; } return 0; diff --git a/src/gui/kernel/qplatformintegration.h b/src/gui/kernel/qplatformintegration.h index 3d433957..39384501 100644 --- a/src/gui/kernel/qplatformintegration.h +++ b/src/gui/kernel/qplatformintegration.h @@ -164,7 +164,8 @@ public: MouseDoubleClickDistance, FlickStartDistance, FlickMaximumVelocity, - FlickDeceleration + FlickDeceleration, + UnderlineShortcut, }; virtual QVariant styleHint(StyleHint hint) const; diff --git a/src/gui/kernel/qplatformscreen.cpp b/src/gui/kernel/qplatformscreen.cpp index 94f31ac6..0369a0b1 100644 --- a/src/gui/kernel/qplatformscreen.cpp +++ b/src/gui/kernel/qplatformscreen.cpp @@ -25,10 +25,8 @@ QPlatformScreen::QPlatformScreen() QPlatformScreen::~QPlatformScreen() { Q_D(QPlatformScreen); - if (d->screen) { - qWarning("Manually deleting a QPlatformScreen. Call QWindowSystemInterface::handleScreenRemoved instead."); - delete d->screen; - } + Q_ASSERT_X(!d->screen, "QPlatformScreen", + "QPlatformScreens should be removed via QWindowSystemInterface::handleScreenRemoved()"); } /*! diff --git a/src/gui/kernel/qplatformscreen.h b/src/gui/kernel/qplatformscreen.h index 3ef5798a..de704f46 100644 --- a/src/gui/kernel/qplatformscreen.h +++ b/src/gui/kernel/qplatformscreen.h @@ -132,7 +132,7 @@ protected: QScopedPointer d_ptr; private: - friend class QScreenPrivate; + friend class QScreen; }; // Qt doesn't currently support running with no platform screen diff --git a/src/gui/kernel/qplatformtheme.cpp b/src/gui/kernel/qplatformtheme.cpp index 7d565813..f7f54709 100644 --- a/src/gui/kernel/qplatformtheme.cpp +++ b/src/gui/kernel/qplatformtheme.cpp @@ -374,6 +374,7 @@ Q_GUI_EXPORT QPalette qt_fusionPalette() const QColor button = backGround; const QColor shadow = dark.darker(135); const QColor disabledShadow = shadow.lighter(150); + const QColor disabledHighlight(145, 145, 145); QColor placeholder = text; placeholder.setAlpha(128); @@ -392,7 +393,11 @@ Q_GUI_EXPORT QPalette qt_fusionPalette() fusionPalette.setBrush(QPalette::Active, QPalette::Highlight, highlight); fusionPalette.setBrush(QPalette::Inactive, QPalette::Highlight, highlight); - fusionPalette.setBrush(QPalette::Disabled, QPalette::Highlight, QColor(145, 145, 145)); + fusionPalette.setBrush(QPalette::Disabled, QPalette::Highlight, 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); @@ -525,6 +530,8 @@ QVariant QPlatformTheme::themeHint(ThemeHint hint) const return QGuiApplicationPrivate::platformIntegration()->styleHint(QPlatformIntegration::FlickMaximumVelocity); case QPlatformTheme::FlickDeceleration: return QGuiApplicationPrivate::platformIntegration()->styleHint(QPlatformIntegration::FlickDeceleration); + case QPlatformTheme::UnderlineShortcut: + return QGuiApplicationPrivate::platformIntegration()->styleHint(QPlatformIntegration::UnderlineShortcut); default: return QPlatformTheme::defaultThemeHint(hint); } @@ -637,7 +644,10 @@ QVariant QPlatformTheme::defaultThemeHint(ThemeHint hint) return QVariant(QString()); case MouseCursorSize: return QVariant(QSize(16, 16)); + case UnderlineShortcut: + return true; } + return QVariant(); } @@ -820,28 +830,23 @@ QString QPlatformTheme::removeMnemonics(const QString &original) }; QString returnText(original.size(), u'\0'); int finalDest = 0; - int currPos = 0; - int l = original.size(); - while (l) { - if (original.at(currPos) == u'&') { - ++currPos; - --l; - if (l == 0) + QStringView text(original); + while (!text.isEmpty()) { + if (text.startsWith(u'&')) { + text = text.sliced(1); + if (text.isEmpty()) break; - } else if (l >= 4 && mnemonicInParentheses(QStringView{original}.sliced(currPos, 4))) { - // Also strip any leading space before the mnemonic: - int n = 0; - while (finalDest > n && returnText.at(finalDest - n - 1).isSpace()) - ++n; - finalDest -= n; - currPos += 4; - l -= 4; + } else if (text.size() >= 4 && mnemonicInParentheses(text.first(4))) { + // Advance over the matched mnemonic: + text = text.sliced(4); + // Also strip any leading space before it: + while (finalDest > 0 && returnText.at(finalDest - 1).isSpace()) + --finalDest; continue; } - returnText[finalDest] = original.at(currPos); - ++currPos; + returnText[finalDest] = text.front(); + text = text.sliced(1); ++finalDest; - --l; } returnText.truncate(finalDest); return returnText; diff --git a/src/gui/kernel/qplatformtheme.h b/src/gui/kernel/qplatformtheme.h index dcc0c8e0..c0193947 100644 --- a/src/gui/kernel/qplatformtheme.h +++ b/src/gui/kernel/qplatformtheme.h @@ -95,7 +95,8 @@ public: FlickDeceleration, MenuBarFocusOnAltPressRelease, MouseCursorTheme, - MouseCursorSize + MouseCursorSize, + UnderlineShortcut, }; Q_ENUM(ThemeHint) diff --git a/src/gui/kernel/qplatformwindow.cpp b/src/gui/kernel/qplatformwindow.cpp index d1bbbe5c..e2c9fc41 100644 --- a/src/gui/kernel/qplatformwindow.cpp +++ b/src/gui/kernel/qplatformwindow.cpp @@ -727,20 +727,29 @@ QRect QPlatformWindow::initialGeometry(const QWindow *w, const QRect &initialGeo should be delivered using QPlatformWindow::deliverUpdateRequest() to not get out of sync with the internal state of QWindow. - The default implementation posts an UpdateRequest event to the - window after 5 ms. The additional time is there to give the event - loop a bit of idle time to gather system events. + The default implementation posts an UpdateRequest event to the window after + an interval that is at most 5 ms. If the window's associated screen reports + a \l{QPlatformScreen::refreshRate()}{refresh rate} higher than 60 Hz, the + interval is scaled down to a valid smaller than 5. The additional time is + there to give the event loop a bit of idle time to gather system events. */ void QPlatformWindow::requestUpdate() { Q_D(QPlatformWindow); - static int updateInterval = []() { - bool ok = false; - int customUpdateInterval = qEnvironmentVariableIntValue("QT_QPA_UPDATE_IDLE_TIME", &ok); - return ok ? customUpdateInterval : 5; - }(); + static bool customUpdateIntervalValid = false; + static int customUpdateInterval = qEnvironmentVariableIntValue("QT_QPA_UPDATE_IDLE_TIME", + &customUpdateIntervalValid); + int updateInterval = customUpdateInterval; + if (!customUpdateIntervalValid) { + updateInterval = 5; + if (QPlatformScreen *currentScreen = screen()) { + const qreal refreshRate = currentScreen->refreshRate(); + if (refreshRate > 60.0) + updateInterval /= refreshRate / 60.0; + } + } Q_ASSERT(!d->updateTimer.isActive()); d->updateTimer.start(updateInterval, Qt::PreciseTimer, window()); diff --git a/src/gui/kernel/qplatformwindow_p.h b/src/gui/kernel/qplatformwindow_p.h index 65b8b4a2..a1c7b754 100644 --- a/src/gui/kernel/qplatformwindow_p.h +++ b/src/gui/kernel/qplatformwindow_p.h @@ -41,6 +41,15 @@ public: namespace QNativeInterface::Private { +#if defined(Q_OS_WASM) || defined(Q_QDOC) +struct Q_GUI_EXPORT QWasmWindow +{ + QT_DECLARE_NATIVE_INTERFACE(QWasmWindow, 1, QWindow) + virtual emscripten::val document() const = 0; + virtual emscripten::val clientArea() const = 0; +}; +#endif + #if defined(Q_OS_MACOS) || defined(Q_QDOC) struct Q_GUI_EXPORT QCocoaWindow { diff --git a/src/gui/kernel/qscreen.cpp b/src/gui/kernel/qscreen.cpp index 052b2a77..d598e4fc 100644 --- a/src/gui/kernel/qscreen.cpp +++ b/src/gui/kernel/qscreen.cpp @@ -37,29 +37,23 @@ QT_BEGIN_NAMESPACE \inmodule QtGui */ -QScreen::QScreen(QPlatformScreen *screen) +QScreen::QScreen(QPlatformScreen *platformScreen) : QObject(*new QScreenPrivate(), nullptr) { Q_D(QScreen); - d->setPlatformScreen(screen); -} -void QScreenPrivate::setPlatformScreen(QPlatformScreen *screen) -{ - Q_Q(QScreen); - platformScreen = screen; - platformScreen->d_func()->screen = q; - orientation = platformScreen->orientation(); + d->platformScreen = platformScreen; + platformScreen->d_func()->screen = this; - logicalDpi = QPlatformScreen::overrideDpi(platformScreen->logicalDpi()); - - refreshRate = platformScreen->refreshRate(); + d->orientation = platformScreen->orientation(); + d->logicalDpi = QPlatformScreen::overrideDpi(platformScreen->logicalDpi()); + d->refreshRate = platformScreen->refreshRate(); // safeguard ourselves against buggy platform behavior... - if (refreshRate < 1.0) - refreshRate = 60.0; + if (d->refreshRate < 1.0) + d->refreshRate = 60.0; - updateGeometry(); - updatePrimaryOrientation(); // derived from the geometry + d->updateGeometry(); + d->updatePrimaryOrientation(); // derived from the geometry } void QScreenPrivate::updateGeometry() @@ -72,45 +66,13 @@ void QScreenPrivate::updateGeometry() /*! Destroys the screen. + + \internal */ QScreen::~QScreen() { - // Remove screen - const bool wasPrimary = QGuiApplication::primaryScreen() == this; - QGuiApplicationPrivate::screen_list.removeOne(this); - QGuiApplicationPrivate::resetCachedDevicePixelRatio(); - - if (!qGuiApp) - return; - - QScreen *newPrimaryScreen = QGuiApplication::primaryScreen(); - if (wasPrimary && newPrimaryScreen) - emit qGuiApp->primaryScreenChanged(newPrimaryScreen); - - // Allow clients to manage windows that are affected by the screen going - // away, before we fall back to moving them to the primary screen. - emit qApp->screenRemoved(this); - - if (QGuiApplication::closingDown()) - return; - - bool movingFromVirtualSibling = newPrimaryScreen - && newPrimaryScreen->handle()->virtualSiblings().contains(handle()); - - // Move any leftover windows to the primary screen - const auto allWindows = QGuiApplication::allWindows(); - for (QWindow *window : allWindows) { - if (!window->isTopLevel() || window->screen() != this) - continue; - - const bool wasVisible = window->isVisible(); - window->setScreen(newPrimaryScreen); - - // Re-show window if moved from a virtual sibling screen. Otherwise - // leave it up to the application developer to show the window. - if (movingFromVirtualSibling) - window->setVisible(wasVisible); - } + Q_ASSERT_X(!QGuiApplicationPrivate::screen_list.contains(this), "QScreen", + "QScreens should be removed via QWindowSystemInterface::handleScreenRemoved()"); } /*! diff --git a/src/gui/kernel/qscreen_p.h b/src/gui/kernel/qscreen_p.h index b9e726a9..964bc1cc 100644 --- a/src/gui/kernel/qscreen_p.h +++ b/src/gui/kernel/qscreen_p.h @@ -40,7 +40,6 @@ class QScreenPrivate : public QObjectPrivate, public QScreenData { Q_DECLARE_PUBLIC(QScreen) public: - void setPlatformScreen(QPlatformScreen *screen); void updateGeometry(); void updatePrimaryOrientation(); diff --git a/src/gui/kernel/qshortcutmap.cpp b/src/gui/kernel/qshortcutmap.cpp index ed03f005..18494193 100644 --- a/src/gui/kernel/qshortcutmap.cpp +++ b/src/gui/kernel/qshortcutmap.cpp @@ -410,6 +410,7 @@ QKeySequence::SequenceMatch QShortcutMap::find(QKeyEvent *e, int ignoredModifier int 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); @@ -420,6 +421,8 @@ QKeySequence::SequenceMatch QShortcutMap::find(QKeyEvent *e, int ignoredModifier 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) { if ((*it).enabled) diff --git a/src/gui/kernel/qstylehints.cpp b/src/gui/kernel/qstylehints.cpp index 30a3be7d..5becae76 100644 --- a/src/gui/kernel/qstylehints.cpp +++ b/src/gui/kernel/qstylehints.cpp @@ -294,6 +294,7 @@ int QStyleHints::keyboardAutoRepeatRate() const /*! \property QStyleHints::keyboardAutoRepeatRateF + \since 6.5 \brief the rate, in events per second, in which additional repeated key presses will automatically be generated if a key is being held down. */ @@ -376,6 +377,8 @@ bool QStyleHints::showIsMaximized() const Since Qt 5.13, the setShowShortcutsInContextMenus() function can be used to override the platform default. + + \sa Qt::AA_DontShowShortcutsInContextMenus */ bool QStyleHints::showShortcutsInContextMenus() const { diff --git a/src/gui/kernel/qsurface.cpp b/src/gui/kernel/qsurface.cpp index b8083641..c00cf55a 100644 --- a/src/gui/kernel/qsurface.cpp +++ b/src/gui/kernel/qsurface.cpp @@ -30,13 +30,12 @@ QT_IMPL_METATYPE_EXTERN_TAGGED(QSurface*, QSurface_ptr) \value Offscreen The surface is an instance of QOffscreenSurface. */ - /*! \enum QSurface::SurfaceType The SurfaceType enum describes what type of surface this is. - \value RasterSurface The surface is is composed of pixels and can be rendered to using + \value RasterSurface The surface is composed of pixels and can be rendered to using a software rasterizer like Qt's raster paint engine. \value OpenGLSurface The surface is an OpenGL compatible surface and can be used in conjunction with QOpenGLContext. @@ -55,7 +54,6 @@ QT_IMPL_METATYPE_EXTERN_TAGGED(QSurface*, QSurface_ptr) surface type is only supported on Windows. */ - /*! \fn QSurfaceFormat QSurface::format() const diff --git a/src/gui/kernel/qtestsupport_gui.cpp b/src/gui/kernel/qtestsupport_gui.cpp index 47c69f8b..ac58ab44 100644 --- a/src/gui/kernel/qtestsupport_gui.cpp +++ b/src/gui/kernel/qtestsupport_gui.cpp @@ -102,7 +102,7 @@ bool QTouchEventSequence::commit(bool processEvents) { if (points.isEmpty()) return false; - QThread::msleep(1); + QThread::sleep(std::chrono::milliseconds{1}); bool ret = false; if (targetWindow) ret = qt_handleTouchEventv2(targetWindow, device, points.values()); diff --git a/src/gui/kernel/qwindow.cpp b/src/gui/kernel/qwindow.cpp index 90fa051b..0d524b86 100644 --- a/src/gui/kernel/qwindow.cpp +++ b/src/gui/kernel/qwindow.cpp @@ -213,8 +213,10 @@ void QWindowPrivate::init(QScreen *targetScreen) isWindow = true; parentWindow = static_cast(q->QObject::parent()); + QScreen *connectScreen = targetScreen ? targetScreen : QGuiApplication::primaryScreen(); + if (!parentWindow) - connectToScreen(targetScreen ? targetScreen : QGuiApplication::primaryScreen()); + connectToScreen(connectScreen); // If your application aborts here, you are probably creating a QWindow // before the screen list is populated. @@ -224,6 +226,20 @@ void QWindowPrivate::init(QScreen *targetScreen) QGuiApplicationPrivate::window_list.prepend(q); 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); + } +#endif + updateDevicePixelRatio(); + }); } /*! @@ -550,6 +566,8 @@ void QWindowPrivate::create(bool recursive, WId nativeHandle) QPlatformSurfaceEvent e(QPlatformSurfaceEvent::SurfaceCreated); QGuiApplication::sendEvent(q, &e); + updateDevicePixelRatio(); + if (needsUpdate) q->requestUpdate(); } @@ -668,7 +686,7 @@ bool QWindow::isVisible() const into an actual native surface. However, the window remains hidden until setVisible() is called. Note that it is not usually necessary to call this function directly, as it will be implicitly - called by show(), setVisible(), and other functions that require access to the platform + called by show(), setVisible(), winId(), and other functions that require access to the platform resources. Call destroy() to free the platform resources if necessary. @@ -1333,14 +1351,29 @@ Qt::ScreenOrientation QWindow::contentOrientation() const qreal QWindow::devicePixelRatio() const { Q_D(const QWindow); + return d->devicePixelRatio; +} + +/* + Updates the cached devicePixelRatio value by polling for a new value. + Sends QEvent::DevicePixelRatioChange to the window if the DPR has changed. +*/ +void QWindowPrivate::updateDevicePixelRatio() +{ + Q_Q(QWindow); // If there is no platform window use the associated screen's devicePixelRatio, // which typically is the primary screen and will be correct for single-display // systems (a very common case). - if (!d->platformWindow) - return screen()->devicePixelRatio(); + const qreal newDevicePixelRatio = platformWindow ? + platformWindow->devicePixelRatio() * QHighDpiScaling::factor(q) : q->screen()->devicePixelRatio(); - return d->platformWindow->devicePixelRatio() * QHighDpiScaling::factor(this); + if (newDevicePixelRatio == devicePixelRatio) + return; + + devicePixelRatio = newDevicePixelRatio; + QEvent dprChangeEvent(QEvent::DevicePixelRatioChange); + QGuiApplication::sendEvent(q, &dprChangeEvent); } Qt::WindowState QWindowPrivate::effectiveState(Qt::WindowStates state) @@ -2584,11 +2617,13 @@ bool QWindow::event(QEvent *ev) /*! Schedules a QEvent::UpdateRequest event to be delivered to this window. - The event is delivered in sync with the display vsync on platforms - where this is possible. Otherwise, the event is delivered after a - delay of 5 ms. The additional time is there to give the event loop - a bit of idle time to gather system events, and can be overridden - using the QT_QPA_UPDATE_IDLE_TIME environment variable. + The event is delivered in sync with the display vsync on platforms where + this is possible. Otherwise, the event is delivered after a delay of at + most 5 ms. If the window's associated screen reports a + \l{QScreen::refreshRate()}{refresh rate} higher than 60 Hz, the interval is + scaled down to a value smaller than 5. The additional time is there to give + the event loop a bit of idle time to gather system events, and can be + overridden using the QT_QPA_UPDATE_IDLE_TIME environment variable. When driving animations, this function should be called once after drawing has completed. Calling this function multiple times will result in a single @@ -3050,6 +3085,10 @@ void *QWindow::resolveInterface(const char *name, int revision) const QT_NATIVE_INTERFACE_RETURN_IF(QWaylandWindow, platformWindow); #endif +#if defined(Q_OS_WASM) + QT_NATIVE_INTERFACE_RETURN_IF(QWasmWindow, platformWindow); +#endif + return nullptr; } diff --git a/src/gui/kernel/qwindow_p.h b/src/gui/kernel/qwindow_p.h index ef633d09..96beb17b 100644 --- a/src/gui/kernel/qwindow_p.h +++ b/src/gui/kernel/qwindow_p.h @@ -89,6 +89,8 @@ public: void setAutomaticPositionAndResizeEnabled(bool a) { positionAutomatic = resizeAutomatic = a; } + void updateDevicePixelRatio(); + static QWindowPrivate *get(QWindow *window) { return window->d_func(); } static Qt::WindowState effectiveState(Qt::WindowStates); @@ -106,6 +108,7 @@ public: QString windowFilePath; QIcon windowIcon; QRect geometry; + qreal devicePixelRatio = 1.0; Qt::WindowStates windowState = Qt::WindowNoState; QWindow::Visibility visibility = QWindow::Hidden; bool resizeEventPending = true; diff --git a/src/gui/kernel/qwindowsysteminterface.cpp b/src/gui/kernel/qwindowsysteminterface.cpp index 48dc5fe7..be2157c8 100644 --- a/src/gui/kernel/qwindowsysteminterface.cpp +++ b/src/gui/kernel/qwindowsysteminterface.cpp @@ -259,6 +259,12 @@ QT_DEFINE_QPA_EVENT_HANDLER(void, handleWindowScreenChanged, QWindow *window, QS handleWindowSystemEvent(window, screen); } +QT_DEFINE_QPA_EVENT_HANDLER(void, handleWindowDevicePixelRatioChanged, QWindow *window) +{ + handleWindowSystemEvent(window); +} + + QT_DEFINE_QPA_EVENT_HANDLER(void, handleSafeAreaMarginsChanged, QWindow *window) { handleWindowSystemEvent(window); @@ -383,61 +389,34 @@ QT_DEFINE_QPA_EVENT_HANDLER(bool, handleMouseEvent, QWindow *window, ulong times Qt::MouseButton button, QEvent::Type type, Qt::KeyboardModifiers mods, Qt::MouseEventSource source) { - Q_ASSERT_X(type != QEvent::MouseButtonDblClick && type != QEvent::NonClientAreaMouseButtonDblClick, - "QWindowSystemInterface::handleMouseEvent", + + bool isNonClientArea = {}; + + switch (type) { + case QEvent::MouseButtonDblClick: + case QEvent::NonClientAreaMouseButtonDblClick: + Q_ASSERT_X(false, "QWindowSystemInterface::handleMouseEvent", "QTBUG-71263: Native double clicks are not implemented."); + return false; + case QEvent::MouseMove: + case QEvent::MouseButtonPress: + case QEvent::MouseButtonRelease: + isNonClientArea = false; + break; + case QEvent::NonClientAreaMouseMove: + case QEvent::NonClientAreaMouseButtonPress: + case QEvent::NonClientAreaMouseButtonRelease: + isNonClientArea = true; + break; + default: + Q_UNREACHABLE(); + } + auto localPos = QHighDpi::fromNativeLocalPosition(local, window); auto globalPos = QHighDpi::fromNativeGlobalPosition(global, window); return handleWindowSystemEvent(window, - timestamp, localPos, globalPos, state, mods, button, type, source, false, device); -} - -bool QWindowSystemInterface::handleFrameStrutMouseEvent(QWindow *window, - const QPointF &local, const QPointF &global, - Qt::MouseButtons state, - Qt::MouseButton button, QEvent::Type type, - Qt::KeyboardModifiers mods, - Qt::MouseEventSource source) -{ - const unsigned long time = QWindowSystemInterfacePrivate::eventTime.elapsed(); - return handleFrameStrutMouseEvent(window, time, local, global, state, button, type, mods, source); -} - -bool QWindowSystemInterface::handleFrameStrutMouseEvent(QWindow *window, const QPointingDevice *device, - const QPointF &local, const QPointF &global, - Qt::MouseButtons state, - Qt::MouseButton button, QEvent::Type type, - Qt::KeyboardModifiers mods, - Qt::MouseEventSource source) -{ - const unsigned long time = QWindowSystemInterfacePrivate::eventTime.elapsed(); - return handleFrameStrutMouseEvent(window, time, device, local, global, state, button, type, mods, source); -} - -bool QWindowSystemInterface::handleFrameStrutMouseEvent(QWindow *window, ulong timestamp, - const QPointF &local, const QPointF &global, - Qt::MouseButtons state, - Qt::MouseButton button, QEvent::Type type, - Qt::KeyboardModifiers mods, - Qt::MouseEventSource source) -{ - return handleFrameStrutMouseEvent(window, timestamp, QPointingDevice::primaryPointingDevice(), - local, global, state, button, type, mods, source); -} - -bool QWindowSystemInterface::handleFrameStrutMouseEvent(QWindow *window, ulong timestamp, const QPointingDevice *device, - const QPointF &local, const QPointF &global, - Qt::MouseButtons state, - Qt::MouseButton button, QEvent::Type type, - Qt::KeyboardModifiers mods, - Qt::MouseEventSource source) -{ - auto localPos = QHighDpi::fromNativeLocalPosition(local, window); - auto globalPos = QHighDpi::fromNativeGlobalPosition(global, window); - - return handleWindowSystemEvent(window, - timestamp, localPos, globalPos, state, mods, button, type, source, true, device); + timestamp, localPos, globalPos, state, mods, button, type, source, isNonClientArea, device); } bool QWindowSystemInterface::handleShortcutEvent(QWindow *window, ulong timestamp, int keyCode, Qt::KeyboardModifiers modifiers, quint32 nativeScanCode, @@ -732,9 +711,9 @@ QT_DEFINE_QPA_EVENT_HANDLER(bool, handleTouchCancelEvent, QWindow *window, ulong The screen should be deleted by calling QWindowSystemInterface::handleScreenRemoved(). */ -void QWindowSystemInterface::handleScreenAdded(QPlatformScreen *ps, bool isPrimary) +void QWindowSystemInterface::handleScreenAdded(QPlatformScreen *platformScreen, bool isPrimary) { - QScreen *screen = new QScreen(ps); + QScreen *screen = new QScreen(platformScreen); if (isPrimary) QGuiApplicationPrivate::screen_list.prepend(screen); @@ -761,9 +740,45 @@ void QWindowSystemInterface::handleScreenAdded(QPlatformScreen *ps, bool isPrima */ void QWindowSystemInterface::handleScreenRemoved(QPlatformScreen *platformScreen) { - // Important to keep this order since the QSceen doesn't own the platform screen. - // The QScreen destructor will take care changing the primary screen, so no need here. - delete platformScreen->screen(); + QScreen *screen = platformScreen->screen(); + + // Remove screen + const bool wasPrimary = QGuiApplication::primaryScreen() == screen; + QGuiApplicationPrivate::screen_list.removeOne(screen); + QGuiApplicationPrivate::resetCachedDevicePixelRatio(); + + if (qGuiApp) { + QScreen *newPrimaryScreen = QGuiApplication::primaryScreen(); + if (wasPrimary && newPrimaryScreen) + emit qGuiApp->primaryScreenChanged(newPrimaryScreen); + + // Allow clients to manage windows that are affected by the screen going + // away, before we fall back to moving them to the primary screen. + emit qApp->screenRemoved(screen); + + if (!QGuiApplication::closingDown()) { + bool movingFromVirtualSibling = newPrimaryScreen + && newPrimaryScreen->handle()->virtualSiblings().contains(platformScreen); + + // Move any leftover windows to the primary screen + const auto allWindows = QGuiApplication::allWindows(); + for (QWindow *window : allWindows) { + if (!window->isTopLevel() || window->screen() != screen) + continue; + + const bool wasVisible = window->isVisible(); + window->setScreen(newPrimaryScreen); + + // Re-show window if moved from a virtual sibling screen. Otherwise + // leave it up to the application developer to show the window. + if (movingFromVirtualSibling) + window->setVisible(wasVisible); + } + } + } + + // Important to keep this order since the QSceen doesn't own the platform screen + delete screen; delete platformScreen; } diff --git a/src/gui/kernel/qwindowsysteminterface.h b/src/gui/kernel/qwindowsysteminterface.h index f7857e16..29ecd656 100644 --- a/src/gui/kernel/qwindowsysteminterface.h +++ b/src/gui/kernel/qwindowsysteminterface.h @@ -64,31 +64,6 @@ public: Qt::KeyboardModifiers mods = Qt::NoModifier, Qt::MouseEventSource source = Qt::MouseEventNotSynthesized); - static bool handleFrameStrutMouseEvent(QWindow *window, const QPointF &local, - const QPointF &global, Qt::MouseButtons state, - Qt::MouseButton button, QEvent::Type type, - Qt::KeyboardModifiers mods = Qt::NoModifier, - Qt::MouseEventSource source = - Qt::MouseEventNotSynthesized); - static bool handleFrameStrutMouseEvent(QWindow *window, const QPointingDevice *device, - const QPointF &local, const QPointF &global, - Qt::MouseButtons state, - Qt::MouseButton button, QEvent::Type type, - Qt::KeyboardModifiers mods = Qt::NoModifier, - Qt::MouseEventSource source = - Qt::MouseEventNotSynthesized); - static bool handleFrameStrutMouseEvent(QWindow *window, ulong timestamp, const QPointF &local, - const QPointF &global, Qt::MouseButtons state, - Qt::MouseButton button, QEvent::Type type, - Qt::KeyboardModifiers mods = Qt::NoModifier, - Qt::MouseEventSource source = - Qt::MouseEventNotSynthesized); - static bool handleFrameStrutMouseEvent(QWindow *window, ulong timestamp, const QPointingDevice *device, - const QPointF &local, const QPointF &global, Qt::MouseButtons state, - Qt::MouseButton button, QEvent::Type type, - Qt::KeyboardModifiers mods = Qt::NoModifier, - Qt::MouseEventSource source = Qt::MouseEventNotSynthesized); - static bool handleShortcutEvent(QWindow *window, ulong timestamp, int k, Qt::KeyboardModifiers mods, quint32 nativeScanCode, quint32 nativeVirtualKey, quint32 nativeModifiers, const QString & text = QString(), bool autorep = false, ushort count = 1); @@ -190,6 +165,9 @@ public: template static void handleWindowScreenChanged(QWindow *window, QScreen *newScreen); + template + static void handleWindowDevicePixelRatioChanged(QWindow *window); + template static void handleSafeAreaMarginsChanged(QWindow *window); diff --git a/src/gui/kernel/qwindowsysteminterface_p.h b/src/gui/kernel/qwindowsysteminterface_p.h index 5aa4d17a..b08abc0a 100644 --- a/src/gui/kernel/qwindowsysteminterface_p.h +++ b/src/gui/kernel/qwindowsysteminterface_p.h @@ -68,7 +68,8 @@ public: WindowScreenChanged = 0x21, SafeAreaMarginsChanged = 0x22, ApplicationTermination = 0x23, - Paint = 0x24 + Paint = 0x24, + WindowDevicePixelRatioChanged = 0x25, }; class WindowSystemEvent { @@ -154,6 +155,15 @@ public: QPointer screen; }; + class WindowDevicePixelRatioChangedEvent : public WindowSystemEvent { + public: + WindowDevicePixelRatioChangedEvent(QWindow *w) + : WindowSystemEvent(WindowDevicePixelRatioChanged), window(w) + { } + + QPointer window; + }; + class SafeAreaMarginsChangedEvent : public WindowSystemEvent { public: SafeAreaMarginsChangedEvent(QWindow *w) diff --git a/src/gui/math3d/qvectornd.h b/src/gui/math3d/qvectornd.h index 0f3ce758..a8adf128 100644 --- a/src/gui/math3d/qvectornd.h +++ b/src/gui/math3d/qvectornd.h @@ -10,8 +10,13 @@ #include #include +#include +#include + QT_BEGIN_NAMESPACE +// QT_ENABLE_P0846_SEMANTICS_FOR(get) // from qpoint.h + class QVector2D; class QVector3D; class QVector4D; @@ -145,10 +150,10 @@ private: template = true, - std::enable_if_t, QVector2D>, bool> = true> + std::enable_if_t, QVector2D>, bool> = true> friend constexpr decltype(auto) get(V &&vec) noexcept { - return (std::forward(vec).v[I]); + return q23::forward_like(vec.v[I]); } }; @@ -304,10 +309,10 @@ private: template = true, - std::enable_if_t, QVector3D>, bool> = true> + std::enable_if_t, QVector3D>, bool> = true> friend constexpr decltype(auto) get(V &&vec) noexcept { - return (std::forward(vec).v[I]); + return q23::forward_like(vec.v[I]); } }; @@ -456,10 +461,10 @@ private: template = true, - std::enable_if_t, QVector4D>, bool> = true> + std::enable_if_t, QVector4D>, bool> = true> friend constexpr decltype(auto) get(V &&vec) noexcept { - return (std::forward(vec).v[I]); + return q23::forward_like(vec.v[I]); } }; diff --git a/src/gui/opengl/qopenglextensions_p.h b/src/gui/opengl/qopenglextensions_p.h index 2924754e..fdb9b51f 100644 --- a/src/gui/opengl/qopenglextensions_p.h +++ b/src/gui/opengl/qopenglextensions_p.h @@ -58,7 +58,8 @@ public: TextureSwizzle = 0x01000000, StandardDerivatives = 0x02000000, ASTCTextureCompression = 0x04000000, - ETC2TextureCompression = 0x08000000 + ETC2TextureCompression = 0x08000000, + HalfFloatVertex = 0x10000000 }; Q_DECLARE_FLAGS(OpenGLExtensions, OpenGLExtension) diff --git a/src/gui/opengl/qopenglfunctions.cpp b/src/gui/opengl/qopenglfunctions.cpp index d8c8e470..ee769875 100644 --- a/src/gui/opengl/qopenglfunctions.cpp +++ b/src/gui/opengl/qopenglfunctions.cpp @@ -346,6 +346,8 @@ static int qt_gl_resolve_extensions() extensions |= QOpenGLExtensions::TextureSwizzle; if (extensionMatcher.match("GL_OES_standard_derivatives")) extensions |= QOpenGLExtensions::StandardDerivatives; + if (extensionMatcher.match("GL_ARB_half_float_vertex")) + extensions |= QOpenGLExtensions::HalfFloatVertex; if (ctx->isOpenGLES()) { if (format.majorVersion() >= 2) @@ -360,7 +362,8 @@ static int qt_gl_resolve_extensions() | QOpenGLExtensions::FramebufferMultisample | QOpenGLExtensions::Sized8Formats | QOpenGLExtensions::StandardDerivatives - | QOpenGLExtensions::ETC2TextureCompression; + | QOpenGLExtensions::ETC2TextureCompression + | QOpenGLExtensions::HalfFloatVertex; #ifndef Q_OS_WASM // WebGL 2.0 specification explicitly does not support texture swizzles // https://www.khronos.org/registry/webgl/specs/latest/2.0/#5.19 diff --git a/src/gui/opengl/qopenglprogrambinarycache_p.h b/src/gui/opengl/qopenglprogrambinarycache_p.h index 0237c81f..c3850bde 100644 --- a/src/gui/opengl/qopenglprogrambinarycache_p.h +++ b/src/gui/opengl/qopenglprogrambinarycache_p.h @@ -20,7 +20,7 @@ #include #include #include -#include +#include QT_BEGIN_NAMESPACE diff --git a/src/gui/painting/qbackingstoredefaultcompositor.cpp b/src/gui/painting/qbackingstoredefaultcompositor.cpp index e1a21c2d..13a8bdb1 100644 --- a/src/gui/painting/qbackingstoredefaultcompositor.cpp +++ b/src/gui/painting/qbackingstoredefaultcompositor.cpp @@ -17,6 +17,7 @@ QBackingStoreDefaultCompositor::~QBackingStoreDefaultCompositor() void QBackingStoreDefaultCompositor::reset() { + m_rhi = nullptr; delete m_psNoBlend; m_psNoBlend = nullptr; delete m_psBlend; diff --git a/src/gui/painting/qbackingstoredefaultcompositor_p.h b/src/gui/painting/qbackingstoredefaultcompositor_p.h index 63ff0e3a..2f835e5d 100644 --- a/src/gui/painting/qbackingstoredefaultcompositor_p.h +++ b/src/gui/painting/qbackingstoredefaultcompositor_p.h @@ -16,7 +16,7 @@ // #include -#include +#include QT_BEGIN_NAMESPACE diff --git a/src/gui/painting/qbackingstorerhisupport.cpp b/src/gui/painting/qbackingstorerhisupport.cpp index 530de60e..3927367a 100644 --- a/src/gui/painting/qbackingstorerhisupport.cpp +++ b/src/gui/painting/qbackingstorerhisupport.cpp @@ -8,19 +8,9 @@ #if QT_CONFIG(opengl) #include #include -#include -#endif - -#ifdef Q_OS_WIN -#include -#endif - -#if defined(Q_OS_MACOS) || defined(Q_OS_IOS) -#include #endif #if QT_CONFIG(vulkan) -#include #include #endif @@ -66,6 +56,16 @@ bool QBackingStoreRhiSupport::create() QOffscreenSurface *surface = nullptr; QRhi::Flags flags; + // This must be the same env.var. Qt Quick uses, 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 (m_config.api() == QPlatformBackingStoreRhiConfig::Null) { + QRhiNullInitParams params; + rhi = QRhi::create(QRhi::Null, ¶ms, flags); + } + #if QT_CONFIG(opengl) if (!rhi && m_config.api() == QPlatformBackingStoreRhiConfig::OpenGL) { surface = QRhiGles2InitParams::newFallbackSurface(m_format); @@ -79,15 +79,21 @@ bool QBackingStoreRhiSupport::create() #endif #ifdef Q_OS_WIN - if (!rhi && m_config.api() == QPlatformBackingStoreRhiConfig::D3D11) { - QRhiD3D11InitParams params; - params.enableDebugLayer = m_config.isDebugLayerEnabled(); - rhi = QRhi::create(QRhi::D3D11, ¶ms, flags); - if (!rhi && !flags.testFlag(QRhi::PreferSoftwareRenderer)) { - qCDebug(lcQpaBackingStore, "Failed to create a D3D device with default settings; " - "attempting to get a software rasterizer backed device instead"); - flags |= QRhi::PreferSoftwareRenderer; + if (!rhi) { + if (m_config.api() == QPlatformBackingStoreRhiConfig::D3D11) { + QRhiD3D11InitParams params; + params.enableDebugLayer = m_config.isDebugLayerEnabled(); rhi = QRhi::create(QRhi::D3D11, ¶ms, flags); + if (!rhi && !flags.testFlag(QRhi::PreferSoftwareRenderer)) { + qCDebug(lcQpaBackingStore, "Failed to create a D3D device with default settings; " + "attempting to get a software rasterizer backed device instead"); + flags |= QRhi::PreferSoftwareRenderer; + rhi = QRhi::create(QRhi::D3D11, ¶ms, flags); + } + } else if (m_config.api() == QPlatformBackingStoreRhiConfig::D3D12) { + QRhiD3D12InitParams params; + params.enableDebugLayer = m_config.isDebugLayerEnabled(); + rhi = QRhi::create(QRhi::D3D12, ¶ms, flags); } } #endif @@ -201,6 +207,7 @@ QSurface::SurfaceType QBackingStoreRhiSupport::surfaceTypeForConfig(const QPlatf QSurface::SurfaceType type = QSurface::RasterSurface; switch (config.api()) { case QPlatformBackingStoreRhiConfig::D3D11: + case QPlatformBackingStoreRhiConfig::D3D12: type = QSurface::Direct3DSurface; break; case QPlatformBackingStoreRhiConfig::Vulkan: @@ -229,6 +236,8 @@ QRhi::Implementation QBackingStoreRhiSupport::apiToRhiBackend(QPlatformBackingSt return QRhi::Vulkan; case QPlatformBackingStoreRhiConfig::D3D11: return QRhi::D3D11; + case QPlatformBackingStoreRhiConfig::D3D12: + return QRhi::D3D12; case QPlatformBackingStoreRhiConfig::Null: return QRhi::Null; default: @@ -270,6 +279,8 @@ bool QBackingStoreRhiSupport::checkForceRhi(QPlatformBackingStoreRhiConfig *outC #ifdef Q_OS_WIN if (backend == QStringLiteral("d3d11") || backend == QStringLiteral("d3d")) config.setApi(QPlatformBackingStoreRhiConfig::D3D11); + if (backend == QStringLiteral("d3d12")) + config.setApi(QPlatformBackingStoreRhiConfig::D3D12); #endif #if defined(Q_OS_MACOS) || defined(Q_OS_IOS) if (backend == QStringLiteral("metal")) diff --git a/src/gui/painting/qbackingstorerhisupport_p.h b/src/gui/painting/qbackingstorerhisupport_p.h index c8aa8f46..39ce36c6 100644 --- a/src/gui/painting/qbackingstorerhisupport_p.h +++ b/src/gui/painting/qbackingstorerhisupport_p.h @@ -19,7 +19,7 @@ #include #include #include -#include +#include #include QT_BEGIN_NAMESPACE diff --git a/src/gui/painting/qpagelayout.h b/src/gui/painting/qpagelayout.h index 04f58bd4..29be2f97 100644 --- a/src/gui/painting/qpagelayout.h +++ b/src/gui/painting/qpagelayout.h @@ -5,7 +5,7 @@ #define QPAGELAYOUT_H #include -#include +#include #include #include diff --git a/src/gui/painting/qplatformbackingstore.cpp b/src/gui/painting/qplatformbackingstore.cpp index 82e7778b..f7c4df40 100644 --- a/src/gui/painting/qplatformbackingstore.cpp +++ b/src/gui/painting/qplatformbackingstore.cpp @@ -379,6 +379,7 @@ void QPlatformBackingStore::graphicsDeviceReportedLost() return; qWarning("Rhi backingstore: graphics device lost, attempting to reinitialize"); + d_ptr->compositor.reset(); d_ptr->rhiSupport.reset(); d_ptr->rhiSupport.create(); if (!d_ptr->rhiSupport.rhi()) diff --git a/src/gui/painting/qplatformbackingstore.h b/src/gui/painting/qplatformbackingstore.h index 40453574..d928af65 100644 --- a/src/gui/painting/qplatformbackingstore.h +++ b/src/gui/painting/qplatformbackingstore.h @@ -45,6 +45,7 @@ struct Q_GUI_EXPORT QPlatformBackingStoreRhiConfig Metal, Vulkan, D3D11, + D3D12, Null }; diff --git a/src/gui/painting/qregion.cpp b/src/gui/painting/qregion.cpp index 457c2cc2..94f1f31e 100644 --- a/src/gui/painting/qregion.cpp +++ b/src/gui/painting/qregion.cpp @@ -3191,8 +3191,7 @@ static void CreateETandAET(int count, const QPoint *pts, int iSLLBlock = 0; int dy; - if (count < 2) - return; + Q_ASSERT(count > 1); /* * initialize the Active Edge Table @@ -3538,7 +3537,7 @@ static QRegionPrivate *PolygonRegion(const QPoint *Pts, int Count, int rule) POINTBLOCK *tmpPtBlock; int numFullPtBlocks = 0; - Q_ASSUME(Count > 1); + Q_ASSERT(Count > 1); region = new QRegionPrivate; diff --git a/src/gui/painting/qrgbafloat.h b/src/gui/painting/qrgbafloat.h index d5ca4cd8..da74328f 100644 --- a/src/gui/painting/qrgbafloat.h +++ b/src/gui/painting/qrgbafloat.h @@ -19,7 +19,13 @@ class alignas(sizeof(F) * 4) QRgbaFloat static_assert(std::is_same::value || std::is_same::value); public: using Type = F; +#if defined(__AVX512FP16__) && QFLOAT16_IS_NATIVE + // AVX512FP16 has multiplication instructions + using FastType = F; +#else + // use FP32 for multiplications using FastType = float; +#endif F r; F g; F b; @@ -28,7 +34,7 @@ public: static constexpr QRgbaFloat fromRgba64(quint16 red, quint16 green, quint16 blue, quint16 alpha) { - constexpr FastType scale = 1.0f / 65535.0f; + constexpr FastType scale = FastType(1.0f / 65535.0f); return QRgbaFloat{ F(red * scale), F(green * scale), @@ -39,7 +45,7 @@ public: static constexpr QRgbaFloat fromRgba(quint8 red, quint8 green, quint8 blue, quint8 alpha) { - constexpr FastType scale = 1.0f / 255.0f; + constexpr FastType scale = FastType(1.0f / 255.0f); return QRgbaFloat{ F(red * scale), F(green * scale), @@ -52,36 +58,36 @@ public: return fromRgba(quint8(rgb >> 16), quint8(rgb >> 8), quint8(rgb), quint8(rgb >> 24)); } - constexpr bool isOpaque() const { return a >= 1.0f; } - constexpr bool isTransparent() const { return a <= 0.0f; } + 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 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 FastType redNormalized() const { return std::clamp(static_cast(r), 0.0f, 1.0f); } - constexpr FastType greenNormalized() const { return std::clamp(static_cast(g), 0.0f, 1.0f); } - constexpr FastType blueNormalized() const { return std::clamp(static_cast(b), 0.0f, 1.0f); } - constexpr FastType alphaNormalized() const { return std::clamp(static_cast(a), 0.0f, 1.0f); } + 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 std::lround(redNormalized() * 255.0f); } - constexpr quint8 green8() const { return std::lround(greenNormalized() * 255.0f); } - constexpr quint8 blue8() const { return std::lround(blueNormalized() * 255.0f); } - constexpr quint8 alpha8() const { return std::lround(alphaNormalized() * 255.0f); } + constexpr quint8 red8() const { return qRound(redNormalized() * FastType(255.0f)); } + constexpr quint8 green8() const { return qRound(greenNormalized() * FastType(255.0f)); } + constexpr quint8 blue8() const { return qRound(blueNormalized() * FastType(255.0f)); } + constexpr quint8 alpha8() const { return qRound(alphaNormalized() * FastType(255.0f)); } constexpr uint toArgb32() const { return uint((alpha8() << 24) | (red8() << 16) | (green8() << 8) | blue8()); } - constexpr quint16 red16() const { return std::lround(redNormalized() * 65535.0f); } - constexpr quint16 green16() const { return std::lround(greenNormalized() * 65535.0f); } - constexpr quint16 blue16() const { return std::lround(blueNormalized() * 65535.0f); } - constexpr quint16 alpha16() const { return std::lround(alphaNormalized() * 65535.0f); } + constexpr quint16 red16() const { return qRound(redNormalized() * FastType(65535.0f)); } + constexpr quint16 green16() const { return qRound(greenNormalized() * FastType(65535.0f)); } + constexpr quint16 blue16() const { return qRound(blueNormalized() * FastType(65535.0f)); } + constexpr quint16 alpha16() const { return qRound(alphaNormalized() * FastType(65535.0f)); } constexpr Q_ALWAYS_INLINE QRgbaFloat premultiplied() const { @@ -104,6 +110,12 @@ public: { return !(*this == f); } + +private: + constexpr static FastType clamp01(Type f) + { + return std::clamp(FastType(f), FastType(0.0f), FastType(1.0f)); + } }; typedef QRgbaFloat QRgbaFloat16; diff --git a/src/gui/painting/qtransform.cpp b/src/gui/painting/qtransform.cpp index 6c49cd35..b2720b27 100644 --- a/src/gui/painting/qtransform.cpp +++ b/src/gui/painting/qtransform.cpp @@ -358,7 +358,7 @@ QTransform &QTransform::translate(qreal dx, qreal dy) if (dx == 0 && dy == 0) return *this; #ifndef QT_NO_DEBUG - if (qIsNaN(dx) | qIsNaN(dy)) { + if (qIsNaN(dx) || qIsNaN(dy)) { nanWarning("translate"); return *this; } @@ -401,7 +401,7 @@ QTransform &QTransform::translate(qreal dx, qreal dy) QTransform QTransform::fromTranslate(qreal dx, qreal dy) { #ifndef QT_NO_DEBUG - if (qIsNaN(dx) | qIsNaN(dy)) { + if (qIsNaN(dx) || qIsNaN(dy)) { nanWarning("fromTranslate"); return QTransform(); } @@ -426,7 +426,7 @@ QTransform & QTransform::scale(qreal sx, qreal sy) if (sx == 1 && sy == 1) return *this; #ifndef QT_NO_DEBUG - if (qIsNaN(sx) | qIsNaN(sy)) { + if (qIsNaN(sx) || qIsNaN(sy)) { nanWarning("scale"); return *this; } @@ -467,7 +467,7 @@ QTransform & QTransform::scale(qreal sx, qreal sy) QTransform QTransform::fromScale(qreal sx, qreal sy) { #ifndef QT_NO_DEBUG - if (qIsNaN(sx) | qIsNaN(sy)) { + if (qIsNaN(sx) || qIsNaN(sy)) { nanWarning("fromScale"); return QTransform(); } @@ -492,7 +492,7 @@ QTransform & QTransform::shear(qreal sh, qreal sv) if (sh == 0 && sv == 0) return *this; #ifndef QT_NO_DEBUG - if (qIsNaN(sh) | qIsNaN(sv)) { + if (qIsNaN(sh) || qIsNaN(sv)) { nanWarning("shear"); return *this; } diff --git a/src/gui/platform/unix/qxkbcommon.cpp b/src/gui/platform/unix/qxkbcommon.cpp index fc014b38..d254aeec 100644 --- a/src/gui/platform/unix/qxkbcommon.cpp +++ b/src/gui/platform/unix/qxkbcommon.cpp @@ -488,11 +488,9 @@ 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. - if (!QXkbCommon::isLatin1(keysym)) { - xkb_keysym_t latinKeysym = QXkbCommon::lookupLatinKeysym(state, code); - if (latinKeysym != XKB_KEY_NoSymbol) - keysym = latinKeysym; - } + xkb_keysym_t latinKeysym = QXkbCommon::lookupLatinKeysym(state, code); + if (latinKeysym != XKB_KEY_NoSymbol) + keysym = latinKeysym; } return keysymToQtKey_internal(keysym, modifiers, state, code, superAsMeta, hyperAsMeta); @@ -735,12 +733,9 @@ xkb_keysym_t QXkbCommon::lookupLatinKeysym(xkb_state *state, xkb_keycode_t keyco xkb_keysym_t sym = XKB_KEY_NoSymbol; 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) @@ -751,34 +746,6 @@ 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+, - // 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+ from generating a ctrl+q - // shortcut in the above described setup. We don't want ctrl+ and ctrl+ 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; } diff --git a/src/gui/platform/unix/qxkbcommon_p.h b/src/gui/platform/unix/qxkbcommon_p.h index 96a209cb..8e6613d2 100644 --- a/src/gui/platform/unix/qxkbcommon_p.h +++ b/src/gui/platform/unix/qxkbcommon_p.h @@ -44,7 +44,7 @@ public: static int keysymToQtKey(xkb_keysym_t keysym, Qt::KeyboardModifiers modifiers); static int keysymToQtKey(xkb_keysym_t keysym, Qt::KeyboardModifiers modifiers, xkb_state *state, xkb_keycode_t code, - bool superAsMeta = false, bool hyperAsMeta = false); + bool superAsMeta = true, bool hyperAsMeta = true); // xkbcommon_* API is part of libxkbcommon internals, with modifications as // described in the header of the implementation file. diff --git a/src/gui/platform/wasm/qlocalfileapi.cpp b/src/gui/platform/wasm/qlocalfileapi.cpp index 54018898..76b99361 100644 --- a/src/gui/platform/wasm/qlocalfileapi.cpp +++ b/src/gui/platform/wasm/qlocalfileapi.cpp @@ -195,7 +195,7 @@ emscripten::val makeSaveFileOptions(const QStringList &filterList, const std::st if (!suggestedName.empty()) options.set("suggestedName", emscripten::val(suggestedName)); - if (auto typeList = LocalFileApi::qtFilterListToTypes(filterList)) + if (auto typeList = qtFilterListToTypes(filterList)) options.set("types", emscripten::val(std::move(*typeList))); return options; @@ -206,6 +206,6 @@ std::string makeFileInputAccept(const QStringList &filterList) return qtFilterListToFileInputAccept(filterList); } -} // namespace LocalFileApi +} // namespace LocalFileApi QT_END_NAMESPACE diff --git a/src/gui/platform/wasm/qlocalfileapi_p.h b/src/gui/platform/wasm/qlocalfileapi_p.h index 05e9a6c7..1398d674 100644 --- a/src/gui/platform/wasm/qlocalfileapi_p.h +++ b/src/gui/platform/wasm/qlocalfileapi_p.h @@ -24,7 +24,8 @@ QT_BEGIN_NAMESPACE namespace LocalFileApi { -class Q_CORE_EXPORT Type { +class Q_AUTOTEST_EXPORT Type +{ public: class Accept { public: @@ -50,6 +51,7 @@ public: void addExtension(Extension type); const std::vector &extensions() const { return m_extensions; } + private: std::vector m_extensions; }; @@ -79,9 +81,12 @@ private: std::optional m_accept; }; -Q_CORE_EXPORT emscripten::val makeOpenFileOptions(const QStringList &filterList, bool acceptMultiple); -Q_CORE_EXPORT emscripten::val makeSaveFileOptions(const QStringList &filterList, const std::string& suggestedName); -Q_CORE_EXPORT std::string makeFileInputAccept(const QStringList &filterList); +Q_AUTOTEST_EXPORT emscripten::val makeOpenFileOptions(const QStringList &filterList, + bool acceptMultiple); +Q_AUTOTEST_EXPORT emscripten::val makeSaveFileOptions(const QStringList &filterList, + const std::string &suggestedName); + +Q_AUTOTEST_EXPORT std::string makeFileInputAccept(const QStringList &filterList); } // namespace LocalFileApi QT_END_NAMESPACE diff --git a/src/gui/platform/wasm/qwasmlocalfileaccess.cpp b/src/gui/platform/wasm/qwasmlocalfileaccess.cpp index 0c613b11..a946cda0 100644 --- a/src/gui/platform/wasm/qwasmlocalfileaccess.cpp +++ b/src/gui/platform/wasm/qwasmlocalfileaccess.cpp @@ -141,7 +141,7 @@ QStringList makeFilterList(const std::string &qtAcceptList) auto filter = QString::fromStdString(qtAcceptList); if (filter.isEmpty()) return QStringList(); - auto sep = QStringLiteral(";;"); + QString sep(";;"); if (!filter.contains(sep) && filter.contains(u'\n')) sep = u'\n'; diff --git a/src/gui/platform/wasm/qwasmnativeinterface.cpp b/src/gui/platform/wasm/qwasmnativeinterface.cpp new file mode 100644 index 00000000..7313629a --- /dev/null +++ b/src/gui/platform/wasm/qwasmnativeinterface.cpp @@ -0,0 +1,17 @@ +// 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 +#include +#include + + +QT_BEGIN_NAMESPACE + +using namespace QNativeInterface::Private; + +QT_DEFINE_PRIVATE_NATIVE_INTERFACE(QWasmWindow); + +QT_END_NAMESPACE diff --git a/src/gui/rhi/MiniEngine_LICENSE.txt b/src/gui/rhi/MiniEngine_LICENSE.txt new file mode 100644 index 00000000..b8b569d7 --- /dev/null +++ b/src/gui/rhi/MiniEngine_LICENSE.txt @@ -0,0 +1,22 @@ +The MIT License (MIT) + +Copyright (c) 2015 Microsoft + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. + diff --git a/src/gui/rhi/cs_mipmap_p.h b/src/gui/rhi/cs_mipmap_p.h new file mode 100644 index 00000000..317cbe7b --- /dev/null +++ b/src/gui/rhi/cs_mipmap_p.h @@ -0,0 +1,939 @@ +// 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 CS_MIPMAP_P_H +#define CS_MIPMAP_P_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 + +#ifdef Q_OS_WIN + +#include + +#if 0 +// +// Generated by Microsoft (R) HLSL Shader Compiler 10.1 +// +// +// Buffer Definitions: +// +// cbuffer CB0 +// { +// +// uint SrcMipLevel; // Offset: 0 Size: 4 +// uint NumMipLevels; // Offset: 4 Size: 4 +// float2 TexelSize; // Offset: 8 Size: 8 +// +// } +// +// +// Resource Bindings: +// +// Name Type Format Dim HLSL Bind Count +// ------------------------------ ---------- ------- ----------- -------------- ------ +// BilinearClamp sampler NA NA s0 1 +// SrcMip texture float4 2d t0 1 +// OutMip1 UAV float4 2d u0 1 +// OutMip2 UAV float4 2d u1 1 +// OutMip3 UAV float4 2d u2 1 +// OutMip4 UAV float4 2d u3 1 +// CB0 cbuffer NA NA cb0 1 +// +// +// +// Input signature: +// +// Name Index Mask Register SysValue Format Used +// -------------------- ----- ------ -------- -------- ------- ------ +// no Input +// +// Output signature: +// +// Name Index Mask Register SysValue Format Used +// -------------------- ----- ------ -------- -------- ------- ------ +// no Output +cs_5_0 +dcl_globalFlags refactoringAllowed +dcl_constantbuffer CB0[1], immediateIndexed +dcl_sampler s0, mode_default +dcl_resource_texture2d (float,float,float,float) t0 +dcl_uav_typed_texture2d (float,float,float,float) u0 +dcl_uav_typed_texture2d (float,float,float,float) u1 +dcl_uav_typed_texture2d (float,float,float,float) u2 +dcl_uav_typed_texture2d (float,float,float,float) u3 +dcl_input vThreadIDInGroupFlattened +dcl_input vThreadID.xy +dcl_temps 6 +dcl_tgsm_structured g0, 4, 64 +dcl_tgsm_structured g1, 4, 64 +dcl_tgsm_structured g2, 4, 64 +dcl_tgsm_structured g3, 4, 64 +dcl_thread_group 8, 8, 1 +utof r0.xy, vThreadID.xyxx +add r0.xy, r0.xyxx, l(0.250000, 0.250000, 0.000000, 0.000000) +mul r0.zw, r0.xxxy, cb0[0].zzzw +utof r1.x, cb0[0].x +sample_l_indexable(texture2d)(float,float,float,float) r2.xyzw, r0.zwzz, t0.xyzw, s0, r1.x +mul r3.xyz, cb0[0].zwzz, l(0.500000, 0.500000, 0.500000, 0.000000) +mov r3.w, l(0) +mad r3.xyzw, cb0[0].zwzw, r0.xyxy, r3.zwxy +sample_l_indexable(texture2d)(float,float,float,float) r4.xyzw, r3.xyxx, t0.xyzw, s0, r1.x +add r2.xyzw, r2.xyzw, r4.xyzw +mov r3.x, l(0) +mul r3.y, cb0[0].w, l(0.500000) +mad r0.xy, cb0[0].zwzz, r0.xyxx, r3.xyxx +sample_l_indexable(texture2d)(float,float,float,float) r0.xyzw, r0.xyxx, t0.xyzw, s0, r1.x +add r0.xyzw, r0.xyzw, r2.xyzw +sample_l_indexable(texture2d)(float,float,float,float) r1.xyzw, r3.zwzz, t0.xyzw, s0, r1.x +add r0.xyzw, r0.xyzw, r1.xyzw +mul r1.xyzw, r0.xyzw, l(0.250000, 0.250000, 0.250000, 0.250000) +store_uav_typed u0.xyzw, vThreadID.xyyy, r1.xyzw +ieq r2.x, cb0[0].y, l(1) +if_nz r2.x + ret +endif +store_structured g0.x, vThreadIDInGroupFlattened.x, l(0), r1.x +store_structured g1.x, vThreadIDInGroupFlattened.x, l(0), r1.y +store_structured g2.x, vThreadIDInGroupFlattened.x, l(0), r1.z +store_structured g3.x, vThreadIDInGroupFlattened.x, l(0), r1.w +sync_g_t +and r2.x, vThreadIDInGroupFlattened.x, l(9) +if_z r2.x + iadd r2.xyz, vThreadIDInGroupFlattened.xxxx, l(1, 8, 9, 0) + ld_structured r3.x, r2.x, l(0), g0.xxxx + ld_structured r3.y, r2.x, l(0), g1.xxxx + ld_structured r3.z, r2.x, l(0), g2.xxxx + ld_structured r3.w, r2.x, l(0), g3.xxxx + ld_structured r4.x, r2.y, l(0), g0.xxxx + ld_structured r4.y, r2.y, l(0), g1.xxxx + ld_structured r4.z, r2.y, l(0), g2.xxxx + ld_structured r4.w, r2.y, l(0), g3.xxxx + ld_structured r5.x, r2.z, l(0), g0.xxxx + ld_structured r5.y, r2.z, l(0), g1.xxxx + ld_structured r5.z, r2.z, l(0), g2.xxxx + ld_structured r5.w, r2.z, l(0), g3.xxxx + mad r0.xyzw, r0.xyzw, l(0.250000, 0.250000, 0.250000, 0.250000), r3.xyzw + add r0.xyzw, r4.xyzw, r0.xyzw + add r0.xyzw, r5.xyzw, r0.xyzw + mul r1.xyzw, r0.xyzw, l(0.250000, 0.250000, 0.250000, 0.250000) + ushr r0.xyzw, vThreadID.xyyy, l(1, 1, 1, 1) + store_uav_typed u1.xyzw, r0.xyzw, r1.xyzw + store_structured g0.x, vThreadIDInGroupFlattened.x, l(0), r1.x + store_structured g1.x, vThreadIDInGroupFlattened.x, l(0), r1.y + store_structured g2.x, vThreadIDInGroupFlattened.x, l(0), r1.z + store_structured g3.x, vThreadIDInGroupFlattened.x, l(0), r1.w +endif +ieq r0.x, cb0[0].y, l(2) +if_nz r0.x + ret +endif +sync_g_t +and r0.x, vThreadIDInGroupFlattened.x, l(27) +if_z r0.x + iadd r0.xyz, vThreadIDInGroupFlattened.xxxx, l(2, 16, 18, 0) + ld_structured r2.x, r0.x, l(0), g0.xxxx + ld_structured r2.y, r0.x, l(0), g1.xxxx + ld_structured r2.z, r0.x, l(0), g2.xxxx + ld_structured r2.w, r0.x, l(0), g3.xxxx + ld_structured r3.x, r0.y, l(0), g0.xxxx + ld_structured r3.y, r0.y, l(0), g1.xxxx + ld_structured r3.z, r0.y, l(0), g2.xxxx + ld_structured r3.w, r0.y, l(0), g3.xxxx + ld_structured r4.x, r0.z, l(0), g0.xxxx + ld_structured r4.y, r0.z, l(0), g1.xxxx + ld_structured r4.z, r0.z, l(0), g2.xxxx + ld_structured r4.w, r0.z, l(0), g3.xxxx + add r0.xyzw, r1.xyzw, r2.xyzw + add r0.xyzw, r3.xyzw, r0.xyzw + add r0.xyzw, r4.xyzw, r0.xyzw + mul r1.xyzw, r0.xyzw, l(0.250000, 0.250000, 0.250000, 0.250000) + ushr r0.xyzw, vThreadID.xyyy, l(2, 2, 2, 2) + store_uav_typed u2.xyzw, r0.xyzw, r1.xyzw + store_structured g0.x, vThreadIDInGroupFlattened.x, l(0), r1.x + store_structured g1.x, vThreadIDInGroupFlattened.x, l(0), r1.y + store_structured g2.x, vThreadIDInGroupFlattened.x, l(0), r1.z + store_structured g3.x, vThreadIDInGroupFlattened.x, l(0), r1.w +endif +ieq r0.x, cb0[0].y, l(3) +if_nz r0.x + ret +endif +sync_g_t +if_z vThreadIDInGroupFlattened.x + ld_structured r0.x, l(4), l(0), g0.xxxx + ld_structured r0.y, l(4), l(0), g1.xxxx + ld_structured r0.z, l(4), l(0), g2.xxxx + ld_structured r0.w, l(4), l(0), g3.xxxx + ld_structured r2.x, l(32), l(0), g0.xxxx + ld_structured r2.y, l(32), l(0), g1.xxxx + ld_structured r2.z, l(32), l(0), g2.xxxx + ld_structured r2.w, l(32), l(0), g3.xxxx + ld_structured r3.x, l(36), l(0), g0.xxxx + ld_structured r3.y, l(36), l(0), g1.xxxx + ld_structured r3.z, l(36), l(0), g2.xxxx + ld_structured r3.w, l(36), l(0), g3.xxxx + add r0.xyzw, r0.xyzw, r1.xyzw + add r0.xyzw, r2.xyzw, r0.xyzw + add r0.xyzw, r3.xyzw, r0.xyzw + mul r0.xyzw, r0.xyzw, l(0.250000, 0.250000, 0.250000, 0.250000) + ushr r1.xyzw, vThreadID.xyyy, l(3, 3, 3, 3) + store_uav_typed u3.xyzw, r1.xyzw, r0.xyzw +endif +ret +// Approximately 111 instruction slots used +#endif + +inline constexpr BYTE g_csMipmap[] = +{ + 68, 88, 66, 67, 133, 122, + 5, 181, 163, 163, 140, 185, + 158, 179, 4, 65, 180, 238, + 158, 10, 1, 0, 0, 0, + 60, 17, 0, 0, 5, 0, + 0, 0, 52, 0, 0, 0, + 200, 2, 0, 0, 216, 2, + 0, 0, 232, 2, 0, 0, + 160, 16, 0, 0, 82, 68, + 69, 70, 140, 2, 0, 0, + 1, 0, 0, 0, 88, 1, + 0, 0, 7, 0, 0, 0, + 60, 0, 0, 0, 0, 5, + 83, 67, 0, 1, 0, 0, + 100, 2, 0, 0, 82, 68, + 49, 49, 60, 0, 0, 0, + 24, 0, 0, 0, 32, 0, + 0, 0, 40, 0, 0, 0, + 36, 0, 0, 0, 12, 0, + 0, 0, 0, 0, 0, 0, + 28, 1, 0, 0, 3, 0, + 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, + 1, 0, 0, 0, 1, 0, + 0, 0, 42, 1, 0, 0, + 2, 0, 0, 0, 5, 0, + 0, 0, 4, 0, 0, 0, + 255, 255, 255, 255, 0, 0, + 0, 0, 1, 0, 0, 0, + 13, 0, 0, 0, 49, 1, + 0, 0, 4, 0, 0, 0, + 5, 0, 0, 0, 4, 0, + 0, 0, 255, 255, 255, 255, + 0, 0, 0, 0, 1, 0, + 0, 0, 13, 0, 0, 0, + 57, 1, 0, 0, 4, 0, + 0, 0, 5, 0, 0, 0, + 4, 0, 0, 0, 255, 255, + 255, 255, 1, 0, 0, 0, + 1, 0, 0, 0, 13, 0, + 0, 0, 65, 1, 0, 0, + 4, 0, 0, 0, 5, 0, + 0, 0, 4, 0, 0, 0, + 255, 255, 255, 255, 2, 0, + 0, 0, 1, 0, 0, 0, + 13, 0, 0, 0, 73, 1, + 0, 0, 4, 0, 0, 0, + 5, 0, 0, 0, 4, 0, + 0, 0, 255, 255, 255, 255, + 3, 0, 0, 0, 1, 0, + 0, 0, 13, 0, 0, 0, + 81, 1, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, + 1, 0, 0, 0, 1, 0, + 0, 0, 66, 105, 108, 105, + 110, 101, 97, 114, 67, 108, + 97, 109, 112, 0, 83, 114, + 99, 77, 105, 112, 0, 79, + 117, 116, 77, 105, 112, 49, + 0, 79, 117, 116, 77, 105, + 112, 50, 0, 79, 117, 116, + 77, 105, 112, 51, 0, 79, + 117, 116, 77, 105, 112, 52, + 0, 67, 66, 48, 0, 171, + 171, 171, 81, 1, 0, 0, + 3, 0, 0, 0, 112, 1, + 0, 0, 16, 0, 0, 0, + 0, 0, 0, 0, 0, 0, + 0, 0, 232, 1, 0, 0, + 0, 0, 0, 0, 4, 0, + 0, 0, 2, 0, 0, 0, + 252, 1, 0, 0, 0, 0, + 0, 0, 255, 255, 255, 255, + 0, 0, 0, 0, 255, 255, + 255, 255, 0, 0, 0, 0, + 32, 2, 0, 0, 4, 0, + 0, 0, 4, 0, 0, 0, + 2, 0, 0, 0, 252, 1, + 0, 0, 0, 0, 0, 0, + 255, 255, 255, 255, 0, 0, + 0, 0, 255, 255, 255, 255, + 0, 0, 0, 0, 45, 2, + 0, 0, 8, 0, 0, 0, + 8, 0, 0, 0, 2, 0, + 0, 0, 64, 2, 0, 0, + 0, 0, 0, 0, 255, 255, + 255, 255, 0, 0, 0, 0, + 255, 255, 255, 255, 0, 0, + 0, 0, 83, 114, 99, 77, + 105, 112, 76, 101, 118, 101, + 108, 0, 100, 119, 111, 114, + 100, 0, 171, 171, 0, 0, + 19, 0, 1, 0, 1, 0, + 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, + 244, 1, 0, 0, 78, 117, + 109, 77, 105, 112, 76, 101, + 118, 101, 108, 115, 0, 84, + 101, 120, 101, 108, 83, 105, + 122, 101, 0, 102, 108, 111, + 97, 116, 50, 0, 171, 171, + 1, 0, 3, 0, 1, 0, + 2, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, + 0, 0, 55, 2, 0, 0, + 77, 105, 99, 114, 111, 115, + 111, 102, 116, 32, 40, 82, + 41, 32, 72, 76, 83, 76, + 32, 83, 104, 97, 100, 101, + 114, 32, 67, 111, 109, 112, + 105, 108, 101, 114, 32, 49, + 48, 46, 49, 0, 73, 83, + 71, 78, 8, 0, 0, 0, + 0, 0, 0, 0, 8, 0, + 0, 0, 79, 83, 71, 78, + 8, 0, 0, 0, 0, 0, + 0, 0, 8, 0, 0, 0, + 83, 72, 69, 88, 176, 13, + 0, 0, 80, 0, 5, 0, + 108, 3, 0, 0, 106, 8, + 0, 1, 89, 0, 0, 4, + 70, 142, 32, 0, 0, 0, + 0, 0, 1, 0, 0, 0, + 90, 0, 0, 3, 0, 96, + 16, 0, 0, 0, 0, 0, + 88, 24, 0, 4, 0, 112, + 16, 0, 0, 0, 0, 0, + 85, 85, 0, 0, 156, 24, + 0, 4, 0, 224, 17, 0, + 0, 0, 0, 0, 85, 85, + 0, 0, 156, 24, 0, 4, + 0, 224, 17, 0, 1, 0, + 0, 0, 85, 85, 0, 0, + 156, 24, 0, 4, 0, 224, + 17, 0, 2, 0, 0, 0, + 85, 85, 0, 0, 156, 24, + 0, 4, 0, 224, 17, 0, + 3, 0, 0, 0, 85, 85, + 0, 0, 95, 0, 0, 2, + 0, 64, 2, 0, 95, 0, + 0, 2, 50, 0, 2, 0, + 104, 0, 0, 2, 6, 0, + 0, 0, 160, 0, 0, 5, + 0, 240, 17, 0, 0, 0, + 0, 0, 4, 0, 0, 0, + 64, 0, 0, 0, 160, 0, + 0, 5, 0, 240, 17, 0, + 1, 0, 0, 0, 4, 0, + 0, 0, 64, 0, 0, 0, + 160, 0, 0, 5, 0, 240, + 17, 0, 2, 0, 0, 0, + 4, 0, 0, 0, 64, 0, + 0, 0, 160, 0, 0, 5, + 0, 240, 17, 0, 3, 0, + 0, 0, 4, 0, 0, 0, + 64, 0, 0, 0, 155, 0, + 0, 4, 8, 0, 0, 0, + 8, 0, 0, 0, 1, 0, + 0, 0, 86, 0, 0, 4, + 50, 0, 16, 0, 0, 0, + 0, 0, 70, 0, 2, 0, + 0, 0, 0, 10, 50, 0, + 16, 0, 0, 0, 0, 0, + 70, 0, 16, 0, 0, 0, + 0, 0, 2, 64, 0, 0, + 0, 0, 128, 62, 0, 0, + 128, 62, 0, 0, 0, 0, + 0, 0, 0, 0, 56, 0, + 0, 8, 194, 0, 16, 0, + 0, 0, 0, 0, 6, 4, + 16, 0, 0, 0, 0, 0, + 166, 142, 32, 0, 0, 0, + 0, 0, 0, 0, 0, 0, + 86, 0, 0, 6, 18, 0, + 16, 0, 1, 0, 0, 0, + 10, 128, 32, 0, 0, 0, + 0, 0, 0, 0, 0, 0, + 72, 0, 0, 141, 194, 0, + 0, 128, 67, 85, 21, 0, + 242, 0, 16, 0, 2, 0, + 0, 0, 230, 10, 16, 0, + 0, 0, 0, 0, 70, 126, + 16, 0, 0, 0, 0, 0, + 0, 96, 16, 0, 0, 0, + 0, 0, 10, 0, 16, 0, + 1, 0, 0, 0, 56, 0, + 0, 11, 114, 0, 16, 0, + 3, 0, 0, 0, 230, 138, + 32, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 2, 64, + 0, 0, 0, 0, 0, 63, + 0, 0, 0, 63, 0, 0, + 0, 63, 0, 0, 0, 0, + 54, 0, 0, 5, 130, 0, + 16, 0, 3, 0, 0, 0, + 1, 64, 0, 0, 0, 0, + 0, 0, 50, 0, 0, 10, + 242, 0, 16, 0, 3, 0, + 0, 0, 230, 142, 32, 0, + 0, 0, 0, 0, 0, 0, + 0, 0, 70, 4, 16, 0, + 0, 0, 0, 0, 230, 4, + 16, 0, 3, 0, 0, 0, + 72, 0, 0, 141, 194, 0, + 0, 128, 67, 85, 21, 0, + 242, 0, 16, 0, 4, 0, + 0, 0, 70, 0, 16, 0, + 3, 0, 0, 0, 70, 126, + 16, 0, 0, 0, 0, 0, + 0, 96, 16, 0, 0, 0, + 0, 0, 10, 0, 16, 0, + 1, 0, 0, 0, 0, 0, + 0, 7, 242, 0, 16, 0, + 2, 0, 0, 0, 70, 14, + 16, 0, 2, 0, 0, 0, + 70, 14, 16, 0, 4, 0, + 0, 0, 54, 0, 0, 5, + 18, 0, 16, 0, 3, 0, + 0, 0, 1, 64, 0, 0, + 0, 0, 0, 0, 56, 0, + 0, 8, 34, 0, 16, 0, + 3, 0, 0, 0, 58, 128, + 32, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 1, 64, + 0, 0, 0, 0, 0, 63, + 50, 0, 0, 10, 50, 0, + 16, 0, 0, 0, 0, 0, + 230, 138, 32, 0, 0, 0, + 0, 0, 0, 0, 0, 0, + 70, 0, 16, 0, 0, 0, + 0, 0, 70, 0, 16, 0, + 3, 0, 0, 0, 72, 0, + 0, 141, 194, 0, 0, 128, + 67, 85, 21, 0, 242, 0, + 16, 0, 0, 0, 0, 0, + 70, 0, 16, 0, 0, 0, + 0, 0, 70, 126, 16, 0, + 0, 0, 0, 0, 0, 96, + 16, 0, 0, 0, 0, 0, + 10, 0, 16, 0, 1, 0, + 0, 0, 0, 0, 0, 7, + 242, 0, 16, 0, 0, 0, + 0, 0, 70, 14, 16, 0, + 0, 0, 0, 0, 70, 14, + 16, 0, 2, 0, 0, 0, + 72, 0, 0, 141, 194, 0, + 0, 128, 67, 85, 21, 0, + 242, 0, 16, 0, 1, 0, + 0, 0, 230, 10, 16, 0, + 3, 0, 0, 0, 70, 126, + 16, 0, 0, 0, 0, 0, + 0, 96, 16, 0, 0, 0, + 0, 0, 10, 0, 16, 0, + 1, 0, 0, 0, 0, 0, + 0, 7, 242, 0, 16, 0, + 0, 0, 0, 0, 70, 14, + 16, 0, 0, 0, 0, 0, + 70, 14, 16, 0, 1, 0, + 0, 0, 56, 0, 0, 10, + 242, 0, 16, 0, 1, 0, + 0, 0, 70, 14, 16, 0, + 0, 0, 0, 0, 2, 64, + 0, 0, 0, 0, 128, 62, + 0, 0, 128, 62, 0, 0, + 128, 62, 0, 0, 128, 62, + 164, 0, 0, 6, 242, 224, + 17, 0, 0, 0, 0, 0, + 70, 5, 2, 0, 70, 14, + 16, 0, 1, 0, 0, 0, + 32, 0, 0, 8, 18, 0, + 16, 0, 2, 0, 0, 0, + 26, 128, 32, 0, 0, 0, + 0, 0, 0, 0, 0, 0, + 1, 64, 0, 0, 1, 0, + 0, 0, 31, 0, 4, 3, + 10, 0, 16, 0, 2, 0, + 0, 0, 62, 0, 0, 1, + 21, 0, 0, 1, 168, 0, + 0, 8, 18, 240, 17, 0, + 0, 0, 0, 0, 10, 64, + 2, 0, 1, 64, 0, 0, + 0, 0, 0, 0, 10, 0, + 16, 0, 1, 0, 0, 0, + 168, 0, 0, 8, 18, 240, + 17, 0, 1, 0, 0, 0, + 10, 64, 2, 0, 1, 64, + 0, 0, 0, 0, 0, 0, + 26, 0, 16, 0, 1, 0, + 0, 0, 168, 0, 0, 8, + 18, 240, 17, 0, 2, 0, + 0, 0, 10, 64, 2, 0, + 1, 64, 0, 0, 0, 0, + 0, 0, 42, 0, 16, 0, + 1, 0, 0, 0, 168, 0, + 0, 8, 18, 240, 17, 0, + 3, 0, 0, 0, 10, 64, + 2, 0, 1, 64, 0, 0, + 0, 0, 0, 0, 58, 0, + 16, 0, 1, 0, 0, 0, + 190, 24, 0, 1, 1, 0, + 0, 6, 18, 0, 16, 0, + 2, 0, 0, 0, 10, 64, + 2, 0, 1, 64, 0, 0, + 9, 0, 0, 0, 31, 0, + 0, 3, 10, 0, 16, 0, + 2, 0, 0, 0, 30, 0, + 0, 9, 114, 0, 16, 0, + 2, 0, 0, 0, 6, 64, + 2, 0, 2, 64, 0, 0, + 1, 0, 0, 0, 8, 0, + 0, 0, 9, 0, 0, 0, + 0, 0, 0, 0, 167, 0, + 0, 9, 18, 0, 16, 0, + 3, 0, 0, 0, 10, 0, + 16, 0, 2, 0, 0, 0, + 1, 64, 0, 0, 0, 0, + 0, 0, 6, 240, 17, 0, + 0, 0, 0, 0, 167, 0, + 0, 9, 34, 0, 16, 0, + 3, 0, 0, 0, 10, 0, + 16, 0, 2, 0, 0, 0, + 1, 64, 0, 0, 0, 0, + 0, 0, 6, 240, 17, 0, + 1, 0, 0, 0, 167, 0, + 0, 9, 66, 0, 16, 0, + 3, 0, 0, 0, 10, 0, + 16, 0, 2, 0, 0, 0, + 1, 64, 0, 0, 0, 0, + 0, 0, 6, 240, 17, 0, + 2, 0, 0, 0, 167, 0, + 0, 9, 130, 0, 16, 0, + 3, 0, 0, 0, 10, 0, + 16, 0, 2, 0, 0, 0, + 1, 64, 0, 0, 0, 0, + 0, 0, 6, 240, 17, 0, + 3, 0, 0, 0, 167, 0, + 0, 9, 18, 0, 16, 0, + 4, 0, 0, 0, 26, 0, + 16, 0, 2, 0, 0, 0, + 1, 64, 0, 0, 0, 0, + 0, 0, 6, 240, 17, 0, + 0, 0, 0, 0, 167, 0, + 0, 9, 34, 0, 16, 0, + 4, 0, 0, 0, 26, 0, + 16, 0, 2, 0, 0, 0, + 1, 64, 0, 0, 0, 0, + 0, 0, 6, 240, 17, 0, + 1, 0, 0, 0, 167, 0, + 0, 9, 66, 0, 16, 0, + 4, 0, 0, 0, 26, 0, + 16, 0, 2, 0, 0, 0, + 1, 64, 0, 0, 0, 0, + 0, 0, 6, 240, 17, 0, + 2, 0, 0, 0, 167, 0, + 0, 9, 130, 0, 16, 0, + 4, 0, 0, 0, 26, 0, + 16, 0, 2, 0, 0, 0, + 1, 64, 0, 0, 0, 0, + 0, 0, 6, 240, 17, 0, + 3, 0, 0, 0, 167, 0, + 0, 9, 18, 0, 16, 0, + 5, 0, 0, 0, 42, 0, + 16, 0, 2, 0, 0, 0, + 1, 64, 0, 0, 0, 0, + 0, 0, 6, 240, 17, 0, + 0, 0, 0, 0, 167, 0, + 0, 9, 34, 0, 16, 0, + 5, 0, 0, 0, 42, 0, + 16, 0, 2, 0, 0, 0, + 1, 64, 0, 0, 0, 0, + 0, 0, 6, 240, 17, 0, + 1, 0, 0, 0, 167, 0, + 0, 9, 66, 0, 16, 0, + 5, 0, 0, 0, 42, 0, + 16, 0, 2, 0, 0, 0, + 1, 64, 0, 0, 0, 0, + 0, 0, 6, 240, 17, 0, + 2, 0, 0, 0, 167, 0, + 0, 9, 130, 0, 16, 0, + 5, 0, 0, 0, 42, 0, + 16, 0, 2, 0, 0, 0, + 1, 64, 0, 0, 0, 0, + 0, 0, 6, 240, 17, 0, + 3, 0, 0, 0, 50, 0, + 0, 12, 242, 0, 16, 0, + 0, 0, 0, 0, 70, 14, + 16, 0, 0, 0, 0, 0, + 2, 64, 0, 0, 0, 0, + 128, 62, 0, 0, 128, 62, + 0, 0, 128, 62, 0, 0, + 128, 62, 70, 14, 16, 0, + 3, 0, 0, 0, 0, 0, + 0, 7, 242, 0, 16, 0, + 0, 0, 0, 0, 70, 14, + 16, 0, 4, 0, 0, 0, + 70, 14, 16, 0, 0, 0, + 0, 0, 0, 0, 0, 7, + 242, 0, 16, 0, 0, 0, + 0, 0, 70, 14, 16, 0, + 5, 0, 0, 0, 70, 14, + 16, 0, 0, 0, 0, 0, + 56, 0, 0, 10, 242, 0, + 16, 0, 1, 0, 0, 0, + 70, 14, 16, 0, 0, 0, + 0, 0, 2, 64, 0, 0, + 0, 0, 128, 62, 0, 0, + 128, 62, 0, 0, 128, 62, + 0, 0, 128, 62, 85, 0, + 0, 9, 242, 0, 16, 0, + 0, 0, 0, 0, 70, 5, + 2, 0, 2, 64, 0, 0, + 1, 0, 0, 0, 1, 0, + 0, 0, 1, 0, 0, 0, + 1, 0, 0, 0, 164, 0, + 0, 7, 242, 224, 17, 0, + 1, 0, 0, 0, 70, 14, + 16, 0, 0, 0, 0, 0, + 70, 14, 16, 0, 1, 0, + 0, 0, 168, 0, 0, 8, + 18, 240, 17, 0, 0, 0, + 0, 0, 10, 64, 2, 0, + 1, 64, 0, 0, 0, 0, + 0, 0, 10, 0, 16, 0, + 1, 0, 0, 0, 168, 0, + 0, 8, 18, 240, 17, 0, + 1, 0, 0, 0, 10, 64, + 2, 0, 1, 64, 0, 0, + 0, 0, 0, 0, 26, 0, + 16, 0, 1, 0, 0, 0, + 168, 0, 0, 8, 18, 240, + 17, 0, 2, 0, 0, 0, + 10, 64, 2, 0, 1, 64, + 0, 0, 0, 0, 0, 0, + 42, 0, 16, 0, 1, 0, + 0, 0, 168, 0, 0, 8, + 18, 240, 17, 0, 3, 0, + 0, 0, 10, 64, 2, 0, + 1, 64, 0, 0, 0, 0, + 0, 0, 58, 0, 16, 0, + 1, 0, 0, 0, 21, 0, + 0, 1, 32, 0, 0, 8, + 18, 0, 16, 0, 0, 0, + 0, 0, 26, 128, 32, 0, + 0, 0, 0, 0, 0, 0, + 0, 0, 1, 64, 0, 0, + 2, 0, 0, 0, 31, 0, + 4, 3, 10, 0, 16, 0, + 0, 0, 0, 0, 62, 0, + 0, 1, 21, 0, 0, 1, + 190, 24, 0, 1, 1, 0, + 0, 6, 18, 0, 16, 0, + 0, 0, 0, 0, 10, 64, + 2, 0, 1, 64, 0, 0, + 27, 0, 0, 0, 31, 0, + 0, 3, 10, 0, 16, 0, + 0, 0, 0, 0, 30, 0, + 0, 9, 114, 0, 16, 0, + 0, 0, 0, 0, 6, 64, + 2, 0, 2, 64, 0, 0, + 2, 0, 0, 0, 16, 0, + 0, 0, 18, 0, 0, 0, + 0, 0, 0, 0, 167, 0, + 0, 9, 18, 0, 16, 0, + 2, 0, 0, 0, 10, 0, + 16, 0, 0, 0, 0, 0, + 1, 64, 0, 0, 0, 0, + 0, 0, 6, 240, 17, 0, + 0, 0, 0, 0, 167, 0, + 0, 9, 34, 0, 16, 0, + 2, 0, 0, 0, 10, 0, + 16, 0, 0, 0, 0, 0, + 1, 64, 0, 0, 0, 0, + 0, 0, 6, 240, 17, 0, + 1, 0, 0, 0, 167, 0, + 0, 9, 66, 0, 16, 0, + 2, 0, 0, 0, 10, 0, + 16, 0, 0, 0, 0, 0, + 1, 64, 0, 0, 0, 0, + 0, 0, 6, 240, 17, 0, + 2, 0, 0, 0, 167, 0, + 0, 9, 130, 0, 16, 0, + 2, 0, 0, 0, 10, 0, + 16, 0, 0, 0, 0, 0, + 1, 64, 0, 0, 0, 0, + 0, 0, 6, 240, 17, 0, + 3, 0, 0, 0, 167, 0, + 0, 9, 18, 0, 16, 0, + 3, 0, 0, 0, 26, 0, + 16, 0, 0, 0, 0, 0, + 1, 64, 0, 0, 0, 0, + 0, 0, 6, 240, 17, 0, + 0, 0, 0, 0, 167, 0, + 0, 9, 34, 0, 16, 0, + 3, 0, 0, 0, 26, 0, + 16, 0, 0, 0, 0, 0, + 1, 64, 0, 0, 0, 0, + 0, 0, 6, 240, 17, 0, + 1, 0, 0, 0, 167, 0, + 0, 9, 66, 0, 16, 0, + 3, 0, 0, 0, 26, 0, + 16, 0, 0, 0, 0, 0, + 1, 64, 0, 0, 0, 0, + 0, 0, 6, 240, 17, 0, + 2, 0, 0, 0, 167, 0, + 0, 9, 130, 0, 16, 0, + 3, 0, 0, 0, 26, 0, + 16, 0, 0, 0, 0, 0, + 1, 64, 0, 0, 0, 0, + 0, 0, 6, 240, 17, 0, + 3, 0, 0, 0, 167, 0, + 0, 9, 18, 0, 16, 0, + 4, 0, 0, 0, 42, 0, + 16, 0, 0, 0, 0, 0, + 1, 64, 0, 0, 0, 0, + 0, 0, 6, 240, 17, 0, + 0, 0, 0, 0, 167, 0, + 0, 9, 34, 0, 16, 0, + 4, 0, 0, 0, 42, 0, + 16, 0, 0, 0, 0, 0, + 1, 64, 0, 0, 0, 0, + 0, 0, 6, 240, 17, 0, + 1, 0, 0, 0, 167, 0, + 0, 9, 66, 0, 16, 0, + 4, 0, 0, 0, 42, 0, + 16, 0, 0, 0, 0, 0, + 1, 64, 0, 0, 0, 0, + 0, 0, 6, 240, 17, 0, + 2, 0, 0, 0, 167, 0, + 0, 9, 130, 0, 16, 0, + 4, 0, 0, 0, 42, 0, + 16, 0, 0, 0, 0, 0, + 1, 64, 0, 0, 0, 0, + 0, 0, 6, 240, 17, 0, + 3, 0, 0, 0, 0, 0, + 0, 7, 242, 0, 16, 0, + 0, 0, 0, 0, 70, 14, + 16, 0, 1, 0, 0, 0, + 70, 14, 16, 0, 2, 0, + 0, 0, 0, 0, 0, 7, + 242, 0, 16, 0, 0, 0, + 0, 0, 70, 14, 16, 0, + 3, 0, 0, 0, 70, 14, + 16, 0, 0, 0, 0, 0, + 0, 0, 0, 7, 242, 0, + 16, 0, 0, 0, 0, 0, + 70, 14, 16, 0, 4, 0, + 0, 0, 70, 14, 16, 0, + 0, 0, 0, 0, 56, 0, + 0, 10, 242, 0, 16, 0, + 1, 0, 0, 0, 70, 14, + 16, 0, 0, 0, 0, 0, + 2, 64, 0, 0, 0, 0, + 128, 62, 0, 0, 128, 62, + 0, 0, 128, 62, 0, 0, + 128, 62, 85, 0, 0, 9, + 242, 0, 16, 0, 0, 0, + 0, 0, 70, 5, 2, 0, + 2, 64, 0, 0, 2, 0, + 0, 0, 2, 0, 0, 0, + 2, 0, 0, 0, 2, 0, + 0, 0, 164, 0, 0, 7, + 242, 224, 17, 0, 2, 0, + 0, 0, 70, 14, 16, 0, + 0, 0, 0, 0, 70, 14, + 16, 0, 1, 0, 0, 0, + 168, 0, 0, 8, 18, 240, + 17, 0, 0, 0, 0, 0, + 10, 64, 2, 0, 1, 64, + 0, 0, 0, 0, 0, 0, + 10, 0, 16, 0, 1, 0, + 0, 0, 168, 0, 0, 8, + 18, 240, 17, 0, 1, 0, + 0, 0, 10, 64, 2, 0, + 1, 64, 0, 0, 0, 0, + 0, 0, 26, 0, 16, 0, + 1, 0, 0, 0, 168, 0, + 0, 8, 18, 240, 17, 0, + 2, 0, 0, 0, 10, 64, + 2, 0, 1, 64, 0, 0, + 0, 0, 0, 0, 42, 0, + 16, 0, 1, 0, 0, 0, + 168, 0, 0, 8, 18, 240, + 17, 0, 3, 0, 0, 0, + 10, 64, 2, 0, 1, 64, + 0, 0, 0, 0, 0, 0, + 58, 0, 16, 0, 1, 0, + 0, 0, 21, 0, 0, 1, + 32, 0, 0, 8, 18, 0, + 16, 0, 0, 0, 0, 0, + 26, 128, 32, 0, 0, 0, + 0, 0, 0, 0, 0, 0, + 1, 64, 0, 0, 3, 0, + 0, 0, 31, 0, 4, 3, + 10, 0, 16, 0, 0, 0, + 0, 0, 62, 0, 0, 1, + 21, 0, 0, 1, 190, 24, + 0, 1, 31, 0, 0, 2, + 10, 64, 2, 0, 167, 0, + 0, 9, 18, 0, 16, 0, + 0, 0, 0, 0, 1, 64, + 0, 0, 4, 0, 0, 0, + 1, 64, 0, 0, 0, 0, + 0, 0, 6, 240, 17, 0, + 0, 0, 0, 0, 167, 0, + 0, 9, 34, 0, 16, 0, + 0, 0, 0, 0, 1, 64, + 0, 0, 4, 0, 0, 0, + 1, 64, 0, 0, 0, 0, + 0, 0, 6, 240, 17, 0, + 1, 0, 0, 0, 167, 0, + 0, 9, 66, 0, 16, 0, + 0, 0, 0, 0, 1, 64, + 0, 0, 4, 0, 0, 0, + 1, 64, 0, 0, 0, 0, + 0, 0, 6, 240, 17, 0, + 2, 0, 0, 0, 167, 0, + 0, 9, 130, 0, 16, 0, + 0, 0, 0, 0, 1, 64, + 0, 0, 4, 0, 0, 0, + 1, 64, 0, 0, 0, 0, + 0, 0, 6, 240, 17, 0, + 3, 0, 0, 0, 167, 0, + 0, 9, 18, 0, 16, 0, + 2, 0, 0, 0, 1, 64, + 0, 0, 32, 0, 0, 0, + 1, 64, 0, 0, 0, 0, + 0, 0, 6, 240, 17, 0, + 0, 0, 0, 0, 167, 0, + 0, 9, 34, 0, 16, 0, + 2, 0, 0, 0, 1, 64, + 0, 0, 32, 0, 0, 0, + 1, 64, 0, 0, 0, 0, + 0, 0, 6, 240, 17, 0, + 1, 0, 0, 0, 167, 0, + 0, 9, 66, 0, 16, 0, + 2, 0, 0, 0, 1, 64, + 0, 0, 32, 0, 0, 0, + 1, 64, 0, 0, 0, 0, + 0, 0, 6, 240, 17, 0, + 2, 0, 0, 0, 167, 0, + 0, 9, 130, 0, 16, 0, + 2, 0, 0, 0, 1, 64, + 0, 0, 32, 0, 0, 0, + 1, 64, 0, 0, 0, 0, + 0, 0, 6, 240, 17, 0, + 3, 0, 0, 0, 167, 0, + 0, 9, 18, 0, 16, 0, + 3, 0, 0, 0, 1, 64, + 0, 0, 36, 0, 0, 0, + 1, 64, 0, 0, 0, 0, + 0, 0, 6, 240, 17, 0, + 0, 0, 0, 0, 167, 0, + 0, 9, 34, 0, 16, 0, + 3, 0, 0, 0, 1, 64, + 0, 0, 36, 0, 0, 0, + 1, 64, 0, 0, 0, 0, + 0, 0, 6, 240, 17, 0, + 1, 0, 0, 0, 167, 0, + 0, 9, 66, 0, 16, 0, + 3, 0, 0, 0, 1, 64, + 0, 0, 36, 0, 0, 0, + 1, 64, 0, 0, 0, 0, + 0, 0, 6, 240, 17, 0, + 2, 0, 0, 0, 167, 0, + 0, 9, 130, 0, 16, 0, + 3, 0, 0, 0, 1, 64, + 0, 0, 36, 0, 0, 0, + 1, 64, 0, 0, 0, 0, + 0, 0, 6, 240, 17, 0, + 3, 0, 0, 0, 0, 0, + 0, 7, 242, 0, 16, 0, + 0, 0, 0, 0, 70, 14, + 16, 0, 0, 0, 0, 0, + 70, 14, 16, 0, 1, 0, + 0, 0, 0, 0, 0, 7, + 242, 0, 16, 0, 0, 0, + 0, 0, 70, 14, 16, 0, + 2, 0, 0, 0, 70, 14, + 16, 0, 0, 0, 0, 0, + 0, 0, 0, 7, 242, 0, + 16, 0, 0, 0, 0, 0, + 70, 14, 16, 0, 3, 0, + 0, 0, 70, 14, 16, 0, + 0, 0, 0, 0, 56, 0, + 0, 10, 242, 0, 16, 0, + 0, 0, 0, 0, 70, 14, + 16, 0, 0, 0, 0, 0, + 2, 64, 0, 0, 0, 0, + 128, 62, 0, 0, 128, 62, + 0, 0, 128, 62, 0, 0, + 128, 62, 85, 0, 0, 9, + 242, 0, 16, 0, 1, 0, + 0, 0, 70, 5, 2, 0, + 2, 64, 0, 0, 3, 0, + 0, 0, 3, 0, 0, 0, + 3, 0, 0, 0, 3, 0, + 0, 0, 164, 0, 0, 7, + 242, 224, 17, 0, 3, 0, + 0, 0, 70, 14, 16, 0, + 1, 0, 0, 0, 70, 14, + 16, 0, 0, 0, 0, 0, + 21, 0, 0, 1, 62, 0, + 0, 1, 83, 84, 65, 84, + 148, 0, 0, 0, 111, 0, + 0, 0, 6, 0, 0, 0, + 0, 0, 0, 0, 2, 0, + 0, 0, 22, 0, 0, 0, + 5, 0, 0, 0, 5, 0, + 0, 0, 4, 0, 0, 0, + 6, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 4, 0, + 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, + 0, 0, 2, 0, 0, 0, + 0, 0, 0, 0, 2, 0, + 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, + 0, 0, 3, 0, 0, 0, + 0, 0, 0, 0, 4, 0, + 0, 0 +}; + +#endif // Q_OS_WIN + +#endif // CS_MIPMAP_P_H diff --git a/src/gui/rhi/cs_tdr_p.h b/src/gui/rhi/cs_tdr_p.h deleted file mode 100644 index de444200..00000000 --- a/src/gui/rhi/cs_tdr_p.h +++ /dev/null @@ -1,192 +0,0 @@ -// 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 CS_TDR_P_H -#define CS_TDR_P_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 - -#ifdef Q_OS_WIN - -#include - -#if 0 -// -// Generated by Microsoft (R) HLSL Shader Compiler 10.1 -// -// -// Buffer Definitions: -// -// cbuffer ConstantBuffer -// { -// -// uint zero; // Offset: 0 Size: 4 -// -// } -// -// -// Resource Bindings: -// -// Name Type Format Dim HLSL Bind Count -// ------------------------------ ---------- ------- ----------- -------------- ------ -// uav UAV uint buf u0 1 -// ConstantBuffer cbuffer NA NA cb0 1 -// -// -// -// Input signature: -// -// Name Index Mask Register SysValue Format Used -// -------------------- ----- ------ -------- -------- ------- ------ -// no Input -// -// Output signature: -// -// Name Index Mask Register SysValue Format Used -// -------------------- ----- ------ -------- -------- ------- ------ -// no Output -cs_5_0 -dcl_globalFlags refactoringAllowed -dcl_constantbuffer CB0[1], immediateIndexed -dcl_uav_typed_buffer (uint,uint,uint,uint) u0 -dcl_input vThreadID.x -dcl_thread_group 256, 1, 1 -loop - breakc_nz cb0[0].x - store_uav_typed u0.xyzw, vThreadID.xxxx, cb0[0].xxxx -endloop -ret -// Approximately 5 instruction slots used -#endif - -inline constexpr BYTE g_killDeviceByTimingOut[] = -{ - 68, 88, 66, 67, 217, 62, - 220, 38, 136, 51, 86, 245, - 161, 96, 18, 35, 141, 17, - 26, 13, 1, 0, 0, 0, - 164, 2, 0, 0, 5, 0, - 0, 0, 52, 0, 0, 0, - 100, 1, 0, 0, 116, 1, - 0, 0, 132, 1, 0, 0, - 8, 2, 0, 0, 82, 68, - 69, 70, 40, 1, 0, 0, - 1, 0, 0, 0, 144, 0, - 0, 0, 2, 0, 0, 0, - 60, 0, 0, 0, 0, 5, - 83, 67, 0, 1, 0, 0, - 0, 1, 0, 0, 82, 68, - 49, 49, 60, 0, 0, 0, - 24, 0, 0, 0, 32, 0, - 0, 0, 40, 0, 0, 0, - 36, 0, 0, 0, 12, 0, - 0, 0, 0, 0, 0, 0, - 124, 0, 0, 0, 4, 0, - 0, 0, 4, 0, 0, 0, - 1, 0, 0, 0, 255, 255, - 255, 255, 0, 0, 0, 0, - 1, 0, 0, 0, 0, 0, - 0, 0, 128, 0, 0, 0, - 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, - 0, 0, 1, 0, 0, 0, - 0, 0, 0, 0, 117, 97, - 118, 0, 67, 111, 110, 115, - 116, 97, 110, 116, 66, 117, - 102, 102, 101, 114, 0, 171, - 128, 0, 0, 0, 1, 0, - 0, 0, 168, 0, 0, 0, - 16, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, - 208, 0, 0, 0, 0, 0, - 0, 0, 4, 0, 0, 0, - 2, 0, 0, 0, 220, 0, - 0, 0, 0, 0, 0, 0, - 255, 255, 255, 255, 0, 0, - 0, 0, 255, 255, 255, 255, - 0, 0, 0, 0, 122, 101, - 114, 111, 0, 100, 119, 111, - 114, 100, 0, 171, 0, 0, - 19, 0, 1, 0, 1, 0, - 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, - 213, 0, 0, 0, 77, 105, - 99, 114, 111, 115, 111, 102, - 116, 32, 40, 82, 41, 32, - 72, 76, 83, 76, 32, 83, - 104, 97, 100, 101, 114, 32, - 67, 111, 109, 112, 105, 108, - 101, 114, 32, 49, 48, 46, - 49, 0, 73, 83, 71, 78, - 8, 0, 0, 0, 0, 0, - 0, 0, 8, 0, 0, 0, - 79, 83, 71, 78, 8, 0, - 0, 0, 0, 0, 0, 0, - 8, 0, 0, 0, 83, 72, - 69, 88, 124, 0, 0, 0, - 80, 0, 5, 0, 31, 0, - 0, 0, 106, 8, 0, 1, - 89, 0, 0, 4, 70, 142, - 32, 0, 0, 0, 0, 0, - 1, 0, 0, 0, 156, 8, - 0, 4, 0, 224, 17, 0, - 0, 0, 0, 0, 68, 68, - 0, 0, 95, 0, 0, 2, - 18, 0, 2, 0, 155, 0, - 0, 4, 0, 1, 0, 0, - 1, 0, 0, 0, 1, 0, - 0, 0, 48, 0, 0, 1, - 3, 0, 4, 4, 10, 128, - 32, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 164, 0, - 0, 7, 242, 224, 17, 0, - 0, 0, 0, 0, 6, 0, - 2, 0, 6, 128, 32, 0, - 0, 0, 0, 0, 0, 0, - 0, 0, 22, 0, 0, 1, - 62, 0, 0, 1, 83, 84, - 65, 84, 148, 0, 0, 0, - 5, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, - 1, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 2, 0, - 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, - 1, 0, 0, 0 -}; - -#endif // Q_OS_WIN - -#endif // CS_TDR_P_H diff --git a/src/gui/rhi/mipmap.hlsl b/src/gui/rhi/mipmap.hlsl new file mode 100644 index 00000000..ac293e07 --- /dev/null +++ b/src/gui/rhi/mipmap.hlsl @@ -0,0 +1,117 @@ +// Copyright (c) Microsoft. All rights reserved. +// This code is licensed under the MIT License (MIT). +// THIS CODE IS PROVIDED *AS IS* WITHOUT WARRANTY OF +// ANY KIND, EITHER EXPRESS OR IMPLIED, INCLUDING ANY +// IMPLIED WARRANTIES OF FITNESS FOR A PARTICULAR +// PURPOSE, MERCHANTABILITY, OR NON-INFRINGEMENT. + +RWTexture2D OutMip1 : register(u0); +RWTexture2D OutMip2 : register(u1); +RWTexture2D OutMip3 : register(u2); +RWTexture2D OutMip4 : register(u3); +Texture2D SrcMip : register(t0); +SamplerState BilinearClamp : register(s0); + +cbuffer CB0 : register(b0) +{ + uint SrcMipLevel; // Texture level of source mip + uint NumMipLevels; // Number of OutMips to write: [1, 4] + float2 TexelSize; // 1.0 / OutMip1.Dimensions +} + +// The reason for separating channels is to reduce bank conflicts in the +// local data memory controller. A large stride will cause more threads +// to collide on the same memory bank. +groupshared float gs_R[64]; +groupshared float gs_G[64]; +groupshared float gs_B[64]; +groupshared float gs_A[64]; + +void StoreColor( uint Index, float4 Color ) +{ + gs_R[Index] = Color.r; + gs_G[Index] = Color.g; + gs_B[Index] = Color.b; + gs_A[Index] = Color.a; +} + +float4 LoadColor( uint Index ) +{ + return float4( gs_R[Index], gs_G[Index], gs_B[Index], gs_A[Index]); +} + +[numthreads( 8, 8, 1 )] +void csMain( uint GI : SV_GroupIndex, uint3 DTid : SV_DispatchThreadID ) +{ + // Use 4 bilinear samples to guarantee we don't undersample when downsizing by more than 2x + // in both directions. + float2 UV1 = TexelSize * (DTid.xy + float2(0.25, 0.25)); + float2 O = TexelSize * 0.5; + float4 Src1 = SrcMip.SampleLevel(BilinearClamp, UV1, SrcMipLevel); + Src1 += SrcMip.SampleLevel(BilinearClamp, UV1 + float2(O.x, 0.0), SrcMipLevel); + Src1 += SrcMip.SampleLevel(BilinearClamp, UV1 + float2(0.0, O.y), SrcMipLevel); + Src1 += SrcMip.SampleLevel(BilinearClamp, UV1 + float2(O.x, O.y), SrcMipLevel); + Src1 *= 0.25; + + OutMip1[DTid.xy] = Src1; + + // A scalar (constant) branch can exit all threads coherently. + if (NumMipLevels == 1) + return; + + // Without lane swizzle operations, the only way to share data with other + // threads is through LDS. + StoreColor(GI, Src1); + + // This guarantees all LDS writes are complete and that all threads have + // executed all instructions so far (and therefore have issued their LDS + // write instructions.) + GroupMemoryBarrierWithGroupSync(); + + // With low three bits for X and high three bits for Y, this bit mask + // (binary: 001001) checks that X and Y are even. + if ((GI & 0x9) == 0) + { + float4 Src2 = LoadColor(GI + 0x01); + float4 Src3 = LoadColor(GI + 0x08); + float4 Src4 = LoadColor(GI + 0x09); + Src1 = 0.25 * (Src1 + Src2 + Src3 + Src4); + + OutMip2[DTid.xy / 2] = Src1; + StoreColor(GI, Src1); + } + + if (NumMipLevels == 2) + return; + + GroupMemoryBarrierWithGroupSync(); + + // This bit mask (binary: 011011) checks that X and Y are multiples of four. + if ((GI & 0x1B) == 0) + { + float4 Src2 = LoadColor(GI + 0x02); + float4 Src3 = LoadColor(GI + 0x10); + float4 Src4 = LoadColor(GI + 0x12); + Src1 = 0.25 * (Src1 + Src2 + Src3 + Src4); + + OutMip3[DTid.xy / 4] = Src1; + StoreColor(GI, Src1); + } + + if (NumMipLevels == 3) + return; + + GroupMemoryBarrierWithGroupSync(); + + // This bit mask would be 111111 (X & Y multiples of 8), but only one + // thread fits that criteria. + if (GI == 0) + { + float4 Src2 = LoadColor(GI + 0x04); + float4 Src3 = LoadColor(GI + 0x20); + float4 Src4 = LoadColor(GI + 0x24); + Src1 = 0.25 * (Src1 + Src2 + Src3 + Src4); + + OutMip4[DTid.xy / 8] = Src1; + } +} diff --git a/src/gui/rhi/qrhi.cpp b/src/gui/rhi/qrhi.cpp index caa1ee17..294bcc69 100644 --- a/src/gui/rhi/qrhi.cpp +++ b/src/gui/rhi/qrhi.cpp @@ -1,22 +1,23 @@ -// Copyright (C) 2019 The Qt Company Ltd. +// 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 "qrhi_p_p.h" +#include "qrhi_p.h" #include #include -#include "qrhinull_p_p.h" +#include "qrhinull_p.h" #ifndef QT_NO_OPENGL -#include "qrhigles2_p_p.h" +#include "qrhigles2_p.h" #endif #if QT_CONFIG(vulkan) -#include "qrhivulkan_p_p.h" +#include "qrhivulkan_p.h" #endif #ifdef Q_OS_WIN -#include "qrhid3d11_p_p.h" +#include "qrhid3d11_p.h" +#include "qrhid3d12_p.h" #endif #if defined(Q_OS_MACOS) || defined(Q_OS_IOS) -#include "qrhimetal_p_p.h" +#include "qrhimetal_p.h" #endif #include @@ -27,8 +28,9 @@ Q_LOGGING_CATEGORY(QRHI_LOG_INFO, "qt.rhi.general") /*! \class QRhi - \internal + \ingroup painting-3D \inmodule QtGui + \since 6.6 \brief Accelerated 2D/3D graphics API abstraction. @@ -39,50 +41,53 @@ Q_LOGGING_CATEGORY(QRHI_LOG_INFO, "qt.rhi.general") \l{https://developer.apple.com/metal/}{Metal}, and \l{https://www.khronos.org/vulkan/}{Vulkan}. - Some of the main design goals are: - - \list - - \li Simple, minimal, understandable, extensible. Follow the proven path of the - Qt Quick scenegraph. - - \li Aim to be a product - and in the bigger picture, part of a product (Qt) - - that is usable out of the box both by internal (such as, Qt Quick) and, - eventually, external users. - - \li Not a complete 1:1 wrapper for any of the underlying APIs. The feature set - is tuned towards the needs of Qt's 2D and 3D offering (QPainter, Qt Quick, Qt - 3D Studio). Iterate and evolve in a sustainable manner. - - \li Intrinsically cross-platform, without reinventing: abstracting - cross-platform aspects of certain APIs (such as, OpenGL context creation and - windowing system interfaces, Vulkan instance and surface management) is not in - scope here. These are delegated to the existing QtGui facilities (QWindow, - QOpenGLContext, QVulkanInstance) and its backing QPA architecture. - - \endlist + \warning The QRhi family of classes in the Qt Gui module, including QShader + and QShaderDescription, offer limited compatibility guarantees. There are + no source or binary compatibility guarantees for these classes, meaning the + API is only guaranteed to work with the Qt version the application was + developed against. Source incompatible changes are however aimed to be kept + at a minimum and will only be made in minor releases (6.7, 6.8, and so on). + To use these classes in an application, link to + \c{Qt::GuiPrivate} (if using CMake), and include the headers with the \c + rhi prefix, for example \c{#include }. Each QRhi instance is backed by a backend for a specific graphics API. The selection of the backend is a run time choice and is up to the application or library that creates the QRhi instance. Some backends are available on multiple platforms (OpenGL, Vulkan, Null), while APIs specific to a given platform are only available when running on the platform in question (Metal - on macOS/iOS/tvOS, Direct3D on Windows). + on macOS/iOS, Direct3D on Windows). The available backends currently are: \list - \li OpenGL 2.1 or OpenGL ES 2.0 or newer. Some extensions are utilized when - present, for example to enable multisample framebuffers. + \li OpenGL 2.1 / OpenGL ES 2.0 or newer. Some extensions and newer core + specification features are utilized when present, for example to enable + multisample framebuffers or compute shaders. Operating in core profile + contexts is supported as well. If necessary, applications can query the + \l{QRhi::Feature}{feature flags} at runtime to check for features that are + not supported in the OpenGL context backing the QRhi. The OpenGL backend + builds on QOpenGLContext, QOpenGLFunctions, and the related cross-platform + infrastructure of the Qt GUI module. - \li Direct3D 11.1 + \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 Metal + \li Direct3D 12.0 or newer, with Shader Model 5.0 or newer. The D3D12 + device is by default created with specifying a minimum feature level of + \c{D3D_FEATURE_LEVEL_11_0}. - \li Vulkan 1.0, optionally with some extensions that are part of Vulkan 1.1 + \li Metal 1.2 or newer. - \li Null - A "dummy" backend that issues no graphics calls at all. + \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. \endlist @@ -92,15 +97,60 @@ Q_LOGGING_CATEGORY(QRHI_LOG_INFO, "qt.rhi.general") are then generated from that, together with reflection information (inputs, outputs, shader resources). This is then packed into easily and efficiently serializable QShader instances. The compilers and tools to generate such - shaders are not part of QRhi, but the core classes for using such shaders, - QShader and QShaderDescription, are. + shaders are not part of QRhi and the Qt GUI module, but the core classes + for using such shaders, QShader and QShaderDescription, are. The APIs and + tools for performing compilation and translation are part of the Qt Shader + Tools module. - \section2 Design Fundamentals + See the \l{RHI Window Example} for an introductory example of creating a + portable, cross-platform application that performs accelerated 3D rendering + onto a QWindow using QRhi. + + \section1 An Impression of the API + + To provide a quick look at the API with a short yet complete example that + does not involve window-related setup, the following is a complete, + runnable cross-platform application that renders 20 frames off-screen, and + then saves the generated images to files after reading back the texture + contents from the GPU. For an example that renders on-screen, which then + involves setting up a QWindow and a swapchain, refer to the + \l{RHI Window Example}. + + For brevity, the initialization of the QRhi is done based on the platform: + the sample code here chooses Direct 3D 12 on Windows, Metal on macOS and + iOS, and Vulkan otherwise. OpenGL and Direct 3D 11 are never used by this + application, but support for those could be introduced with a few + additional lines. + + \snippet rhioffscreen/main.cpp 0 + + The result of the application is 20 \c PNG images (frame0.png - + frame19.png). These contain a rotating triangle with varying opacity over a + green background. + + The vertex and fragment shaders are expected to be processed and packaged + into \c{.qsb} files. The Vulkan-compatible GLSL source code is the + following: + + \e color.vert + \snippet rhioffscreen/color.vert 0 + + \e color.frag + \snippet rhioffscreen/color.frag 0 + + To manually compile and transpile these shaders to a number of targets + (SPIR-V, HLSL, MSL, GLSL) and generate the \c{.qsb} files the application + loads at run time, run \c{qsb --qt6 color.vert -o color.vert.qsb} and + \c{qsb --qt6 color.frag -o color.frag.qsb}. Alternatively, the Qt Shader + Tools module offers build system integration for CMake, the + \c qt_add_shaders() CMake function, that can achieve the same at build time. + + \section1 Design Fundamentals A QRhi cannot be instantiated directly. Instead, use the create() function. Delete the QRhi instance normally to release the graphics device. - \section3 Resources + \section2 Resources Instances of classes deriving from QRhiResource, such as, QRhiBuffer, QRhiTexture, etc., encapsulate zero, one, or more native graphics @@ -108,10 +158,10 @@ Q_LOGGING_CATEGORY(QRHI_LOG_INFO, "qt.rhi.general") functions of the QRhi, such as, newBuffer(), newTexture(), newTextureRenderTarget(), newSwapChain(). - \badcode - vbuf = rhi->newBuffer(QRhiBuffer::Immutable, QRhiBuffer::VertexBuffer, sizeof(vertexData)); - if (!vbuf->create()) { error } - ... + \code + QRhiBuffer *vbuf = rhi->newBuffer(QRhiBuffer::Immutable, QRhiBuffer::VertexBuffer, sizeof(vertexData)); + if (!vbuf->create()) { error(); } + // ... delete vbuf; \endcode @@ -120,9 +170,10 @@ Q_LOGGING_CATEGORY(QRHI_LOG_INFO, "qt.rhi.general") \li The returned value from functions like newBuffer() is always owned by the caller. - \li Just creating a QRhiResource subclass never allocates or initializes any - native resources. That is only done when calling the \c create() function of a - subclass, for example, QRhiBuffer::create() or QRhiTexture::create(). + \li Just creating an instance of a QRhiResource subclass never allocates or + initializes any native resources. That is only done when calling the + \c create() function of a subclass, for example, QRhiBuffer::create() or + QRhiTexture::create(). \li The exceptions are QRhiTextureRenderTarget::newCompatibleRenderPassDescriptor(), @@ -146,15 +197,15 @@ Q_LOGGING_CATEGORY(QRHI_LOG_INFO, "qt.rhi.general") \li Note that this does not mean that a QRhiResource can freely be destroy()'ed or deleted within a frame (that is, in a - \l{QRhiCommandBuffer::beginFrame()}{beginFrame()} - - \l{QRhiCommandBuffer::endFrame()}{endFrame()} section). As a general rule, - all referenced QRhiResource objects must stay unchanged until the frame is - submitted by calling \l{QRhiCommandBuffer::endFrame()}{endFrame()}. To ease - this, QRhiResource::deleteLater() is provided as a convenience. + \l{QRhi::beginFrame()}{beginFrame()} - \l{QRhi::endFrame()}{endFrame()} + section). As a general rule, all referenced QRhiResource objects must stay + unchanged until the frame is submitted by calling + \l{QRhi::endFrame()}{endFrame()}. To ease this, + QRhiResource::deleteLater() is provided as a convenience. \endlist - \section3 Command buffers and deferred command execution + \section2 Command buffers and deferred command execution Regardless of the design and capabilities of the underlying graphics API, all QRhi backends implement some level of command buffers. No @@ -181,7 +232,7 @@ Q_LOGGING_CATEGORY(QRHI_LOG_INFO, "qt.rhi.general") As a general rule, all referenced QRhiResource objects must stay valid and unmodified until the frame is submitted by calling - \l{QRhiCommandBuffer::endFrame()}{endFrame()}. On the other hand, calling + \l{QRhi::endFrame()}{endFrame()}. On the other hand, calling \l{QRhiResource::destroy()}{destroy()} or deleting the QRhiResource are always safe once the frame is submitted, regardless of the status of the underlying native resources (which may still be in use by the GPU - but @@ -189,10 +240,20 @@ Q_LOGGING_CATEGORY(QRHI_LOG_INFO, "qt.rhi.general") Unlike APIs like OpenGL, upload and copy type of commands cannot be mixed with draw commands. The typical renderer will involve a sequence similar to - the following: \c{(re)create resources} - \c{begin frame} - \c{record - uploads and copies} - \c{start renderpass} - \c{record draw calls} - \c{end - renderpass} - \c{end frame}. Recording copy type of operations happens via - QRhiResourceUpdateBatch. Such operations are committed typically on + the following: + + \list + \li (re)create resources + \li begin frame + \li record/issue uploads and copies + \li start recording a render pass + \li record draw calls + \li end render pass + \li end frame + \endlist + + Recording copy type of operations happens via QRhiResourceUpdateBatch. Such + operations are committed typically on \l{QRhiCommandBuffer::beginPass()}{beginPass()}. When working with legacy rendering engines designed for OpenGL, the @@ -211,7 +272,7 @@ Q_LOGGING_CATEGORY(QRHI_LOG_INFO, "qt.rhi.general") remain the primary way of operating since this is what fits Qt's various UI technologies best. - \section3 Threading + \section2 Threading A QRhi instance and the associated resources can be created and used on any thread but all usage must be limited to that one single thread. When @@ -240,7 +301,7 @@ Q_LOGGING_CATEGORY(QRHI_LOG_INFO, "qt.rhi.general") (gui) thread, but becomes important when a separate, dedicated render thread is used. - \section3 Resource synchronization + \section2 Resource synchronization QRhi does not expose APIs for resource barriers or image layout transitions. Such synchronization is done implicitly by the backends, where @@ -260,7 +321,7 @@ Q_LOGGING_CATEGORY(QRHI_LOG_INFO, "qt.rhi.general") different access (one for load, one for store) is supported even within the same pass. - \section3 Resource reuse + \section2 Resource reuse From the user's point of view a QRhiResource is reusable immediately after calling QRhiResource::destroy(). With the exception of swapchains, calling @@ -280,24 +341,24 @@ Q_LOGGING_CATEGORY(QRHI_LOG_INFO, "qt.rhi.general") though there is a good chance that under the hood the QRhiBuffer is now backed by a whole new native buffer. - \badcode - ubuf = rhi->newBuffer(QRhiBuffer::Dynamic, QRhiBuffer::UniformBuffer, 256); + \code + QRhiBuffer *ubuf = rhi->newBuffer(QRhiBuffer::Dynamic, QRhiBuffer::UniformBuffer, 256); ubuf->create(); - srb = rhi->newShaderResourceBindings() + QRhiShaderResourceBindings *srb = rhi->newShaderResourceBindings() srb->setBindings({ QRhiShaderResourceBinding::uniformBuffer(0, QRhiShaderResourceBinding::VertexStage | QRhiShaderResourceBinding::FragmentStage, ubuf) }); srb->create(); - ... + // ... // now in a later frame we need to grow the buffer to a larger size ubuf->setSize(512); ubuf->create(); // same as ubuf->destroy(); ubuf->create(); - // That's it, srb needs no changes whatsoever, any references in it to - // ubuf stay valid. When it comes to internal details, such as that + // srb needs no changes whatsoever, any references in it to ubuf + // stay valid. When it comes to internal details, such as that // ubuf may now be backed by a completely different native buffer // resource, that is is recognized and handled automatically by the // next setShaderResources(). @@ -312,7 +373,7 @@ Q_LOGGING_CATEGORY(QRHI_LOG_INFO, "qt.rhi.general") resource underneath, without having to update the QRhiTextureRenderTarget as that will be done implicitly in beginPass(). - \section3 Pooled objects + \section2 Pooled objects In addition to resources, there are pooled objects as well, such as, QRhiResourceUpdateBatch. An instance is retrieved via a \c next function, @@ -322,24 +383,25 @@ Q_LOGGING_CATEGORY(QRHI_LOG_INFO, "qt.rhi.general") QRhiCommandBuffer::beginPass() or QRhiCommandBuffer::endPass(). These functions take care of returning the batch to the pool. Alternatively, a batch can be "canceled" and returned to the pool without processing by - calling QRhiResourceUpdateBatch::destroy(). + calling QRhiResourceUpdateBatch::release(). A typical pattern is thus: - \badcode + \code QRhiResourceUpdateBatch *resUpdates = rhi->nextResourceUpdateBatch(); - ... + // ... resUpdates->updateDynamicBuffer(ubuf, 0, 64, mvp.constData()); if (!image.isNull()) { resUpdates->uploadTexture(texture, image); image = QImage(); } - ... + // ... QRhiCommandBuffer *cb = m_sc->currentFrameCommandBuffer(); + // note the last argument cb->beginPass(swapchain->currentFrameRenderTarget(), clearCol, clearDs, resUpdates); \endcode - \section3 Swapchain specifics + \section2 Swapchain specifics QRhiSwapChain features some special semantics due to the peculiar nature of swapchains. @@ -370,30 +432,174 @@ Q_LOGGING_CATEGORY(QRHI_LOG_INFO, "qt.rhi.general") \endlist - \section3 Ownership + \section2 Ownership The general rule is no ownership transfer. Creating a QRhi with an already existing graphics device does not mean the QRhi takes ownership of the device object. Similarly, ownership is not given away when a device or texture object is "exported" via QRhi::nativeHandles() or - QRhiTexture::nativeHandles(). Most importantly, passing pointers in structs + QRhiTexture::nativeTexture(). Most importantly, passing pointers in structs and via setters does not transfer ownership. - \section2 Troubleshooting + \section1 Troubleshooting and Profiling - Errors are printed to the output via qWarning(). Additional debug messages - can be enabled via the following logging categories. Messages from these - categories are not printed by default unless explicitly enabled via - QLoggingCategory or the \c QT_LOGGING_RULES environment variable. + \section2 Error reporting + + Functions such as \l QRhi::create() and the resource classes' \c create() + member functions (e.g., \l QRhiBuffer::create()) indicate failure with the + return value (\nullptr or + \c false, respectively). When working with QShader, \l QShader::fromSerialized() + returns an invalid QShader (for which \l{QShader::isValid()}{isValid()} returns + \c false) when the data passed to the function cannot be successfully deserialized. + Some functions, beginFrame() in particular, may also sometimes report "soft failures", + such as \l FrameOpSwapChainOutOfDate, which do not indicate an unrecoverable error, + but rather should be seen as a "try again later" response. + + Warnings and errors may get printed at any time to the debug output via + qWarning(). It is therefore always advisable to inspect the output of the + application. + + Additional debug messages can be enabled via the following logging + categories. Messages from these categories are not printed by default + unless explicitly enabled via QLoggingCategory or the \c QT_LOGGING_RULES + environment variable. For better interoperation with Qt Quick, the + environment variable \c{QSG_INFO} also enables these debug prints. \list \li \c{qt.rhi.general} \endlist - It is strongly advised to inspect the output with the logging categories - (\c{qt.rhi.*}) enabled whenever a QRhi-based application is not behaving as - expected. For better interoperation with Qt Quick, the environment variable - \c{QSG_INFO} also enables these debug prints. + Additionally, applications can query the \l{QRhi::backendName()}{QRhi + backend name} and + \l{QRhi::driverInfo()}{graphics device information} from a successfully + initialized QRhi. This can then be printed to the user or stored in the + application logs even in production builds, if desired. + + \section2 Investigating rendering problems + + When the rendering results are not as expected, or the application is + experiencing problems, always consider checking with the the native 3D + APIs' debug and validation facilities. QRhi itself features limited error + checking since replicating the already existing, vast amount of + functionality in the underlying layers is not reasonable. + + \list + + \li For Vulkan, controlling the + \l{https://github.com/KhronosGroup/Vulkan-ValidationLayers}{Vulkan + Validation Layers} is not in the scope of the QRhi, but rather can be + achieved by configuring the \l QVulkanInstance with the appropriate layers. + For example, call \c{instance.setLayers({ "VK_LAYER_KHRONOS_validation" });} + before invoking \l{QVulkanInstance::create()}{create()} on the QVulkanInstance. + (note that this assumes that the validation layers are actually installed + and available, e.g. from the Vulkan SDK) By default, QVulkanInstance conveniently + redirects the Vulkan debug messages to qDebug, meaning the validation messages get + printed just like other Qt warnings. + + \li With Direct 3D 11 and 12, a graphics device with the debug layer + enabled can be requested by toggling the \c enableDebugLayer flag in the + appropriate \l{QRhiD3D11InitParams}{init params struct}. The messages appear on the + debug output, which is visible in Qt Creator's messages panel or via a tool + such as \l{https://learn.microsoft.com/en-us/sysinternals/downloads/debugview}{DebugView}. + + \li For Metal, controlling Metal Validation is outside of QRhi's scope. + Rather, to enable validation, run the application with the environment + variable \c{METAL_DEVICE_WRAPPER_TYPE=1} set, or run the application within + XCode. There may also be further settings and environment variable in modern + XCode and macOS versions. See for instance + \l{https://developer.apple.com/documentation/metal/diagnosing_metal_programming_issues_early}{this + page}. + + \endlist + + \section2 Frame captures and performance profiling + + A Qt application rendering with QRhi to a window while relying on a 3D API + under the hood, is, from the windowing and graphics pipeline perspective at + least, no different from any other (non-Qt) applications using the same 3D + API. This means that tools and practices for debugging and profiling + applications involving 3D graphics, such as games, all apply to such a Qt + application as well. + + A few examples of tools that can provide insights into the rendering + internals of Qt applications that use QRhi, which includes Qt Quick and Qt + Quick 3D based projects as well: + + \list + + \li \l{https://renderdoc.org/}{RenderDoc} allows taking frame captures and + introspecting the recorded commands and pipeline state on Windows and Linux + for applications using OpenGL, Vulkan, D3D11, or D3D12. When trying to + figure out why some parts of the 3D scene do not show up as expected, + RenderDoc is often a fast and efficient way to check the pipeline stages + and the related state and discover the missing or incorrect value. It is + also a tool that is actively used when developing Qt itself. + + \li For NVIDIA-based systems, + \l{https://developer.nvidia.com/nsight-graphics}{Nsight Graphics} provides + a graphics debugger tool on Windows and Linux. In addition to investigating the commands + in the frame and the pipeline, the vendor-specific tools allow looking at timings and + hardware performance information, which is not something simple frame captures can provide. + + \li For AMD-based systems, the \l{https://gpuopen.com/rgp/}{Radeon GPU + Profiler} can be used to gain deeper insights into the application's + rendering and its performance. + + \li As QRhi supports Direct 3D 12, using + \l{https://devblogs.microsoft.com/pix/download/}{PIX}, a performance tuning + and debugging tool for DirectX 12 games on Windows is an option as well. + + \li On macOS, + \l{https://developer.apple.com/documentation/metal/debugging_tools/viewing_your_gpu_workload_with_the_metal_debugger}{the + XCode Metal debugger} can be used to take and introspect frame + captures, to investigate performance details, and debug shaders. In macOS 13 it is also possible + to enable an overlay that displays frame rate and other information for any Metal-based window by + setting the environment variable \c{MTL_HUD_ENABLED=1}. + + \endlist + + On mobile and embedded platforms, there may be vendor and platform-specific + tools, provided by the GPU or SoC vendor, available to perform performance + profiling of application using OpenGL ES or Vulkan. + + When capturing frames, remember that objects and groups of commands can be + named via debug markers, as long as \l{QRhi::EnableDebugMarkers}{debug + markers were enabled} for the QRhi, and the graphics API in use supports + this. To annotate the command stream, call + \l{QRhiCommandBuffer::debugMarkBegin()}{debugMarkBegin()}, + \l{QRhiCommandBuffer::debugMarkEnd()}{debugMarkEnd()} and/or + \l{QRhiCommandBuffer::debugMarkMsg()}{debugMarkMsg()}. + This can be particularly useful in larger frames with multiple render passes. + Resources are named by calling \l{QRhiResource::setName()}{setName()} before create(). + + To perform basic timing measurements on the CPU and GPU side within the + application, \l QElapsedTimer and + \l QRhiCommandBuffer::lastCompletedGpuTime() can be used. The latter is + only available with select graphics APIs at the moment and requires opting + in via the \l QRhi::EnableTimestamps flag. + + \section2 Resource leak checking + + When destroying a QRhi object without properly destroying all buffers, + textures, and other resources created from it, warnings about this are + printed to the debug output whenever the application is a debug build, or + when the \c QT_RHI_LEAK_CHECK environment variable is set to a non-zero + value. This is a simple way to discover design issues around resource + handling within the application rendering logic. Note however that some + platforms and underlying graphics APIs may perform their own allocation and + resource leak detection as well, over which Qt will have no direct control. + For example, when using Vulkan, the memory allocator may raise failing + assertions in debug builds when resources that own graphics memory + allocations are not destroyed before the QRhi. In addition, the Vulkan + validation layer, when enabled, will issue warnings about native graphics + resources that were not released. Similarly, with Direct 3D warnings may + get printed about unreleased COM objects when the application does not + destroy the QRhi and its resources in the correct order. + + \sa {RHI Window Example}, QRhiCommandBuffer, QRhiResourceUpdateBatch, + QRhiShaderResourceBindings, QShader, QRhiBuffer, QRhiTexture, + QRhiRenderBuffer, QRhiSampler, QRhiTextureRenderTarget, + QRhiGraphicsPipeline, QRhiComputePipeline, QRhiSwapChain */ /*! @@ -404,6 +610,7 @@ Q_LOGGING_CATEGORY(QRHI_LOG_INFO, "qt.rhi.general") \value Vulkan \value OpenGLES2 \value D3D11 + \value D3D12 \value Metal */ @@ -411,13 +618,18 @@ Q_LOGGING_CATEGORY(QRHI_LOG_INFO, "qt.rhi.general") \enum QRhi::Flag Describes what special features to enable. - \value EnableProfiling This flag has currently no effect. - \value EnableDebugMarkers Enables debug marker groups. Without this frame debugging features like making debug groups and custom resource name visible in external GPU debugging tools will not be available and functions - like QRhiCommandBuffer::debugMarkBegin() will become a no-op. Avoid - enabling in production builds as it may involve a performance penalty. + like QRhiCommandBuffer::debugMarkBegin() will become no-ops. Avoid enabling + in production builds as it may involve a small performance impact. Has no + effect when the QRhi::DebugMarkers feature is not reported as supported. + + \value EnableTimestamps Enables GPU timestamp collection. When not set, + QRhiCommandBuffer::lastCompletedGpuTime() always returns 0. Enable this + only when needed since there may be a small amount of extra work involved + (e.g. timestamp queries), depending on the underlying graphics API. Has no + effect when the QRhi::Timestamps feature is not reported as supported. \value PreferSoftwareRenderer Indicates that backends should prefer choosing an adapter or physical device that renders in software on the CPU. @@ -439,7 +651,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 @@ -488,8 +700,9 @@ Q_LOGGING_CATEGORY(QRHI_LOG_INFO, "qt.rhi.general") QRhiCommandBuffer::debugMarkBegin()) are supported. \value Timestamps Indicates that command buffer timestamps are supported. - Relevant for addGpuFrameTimeCallback(). Can be expected to be supported on - D3D11 and Vulkan, assuming the underlying implementation supports it. + 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. \value Instancing Indicates that instanced drawing is supported. In practice this feature will be unsupported with OpenGL ES 2.0 and OpenGL @@ -559,7 +772,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. @@ -587,7 +800,7 @@ Q_LOGGING_CATEGORY(QRHI_LOG_INFO, "qt.rhi.general") \value TriangleFanTopology Indicates that QRhiGraphicsPipeline::setTopology() supports QRhiGraphicsPipeline::TriangleFan. In practice this feature will be - unsupported with Metal and Direct 3D 11. + unsupported with Metal and Direct 3D 11/12. \value ReadBackNonUniformBuffer Indicates that \l{QRhiResourceUpdateBatch::readBackBuffer()}{reading buffer contents} is @@ -720,7 +933,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 @@ -731,11 +944,29 @@ Q_LOGGING_CATEGORY(QRHI_LOG_INFO, "qt.rhi.general") \value OneDimensionalTextures Indicates that 1D textures are supported. In practice this feature will be unsupported on OpenGL ES. - \value OneDimensionalTextureMipmaps Indicates that 1D texture mipmaps and - 1D texture render targets are supported. In practice this feature will be - unsupported on backends that do not report support for + \value OneDimensionalTextureMipmaps Indicates that generating 1D texture + mipmaps are supported. In practice this feature will be unsupported on + backends that do not report support for + \l{OneDimensionalTextures}, Metal, and Direct 3D 12. + + \value HalfAttributes Indicates that specifying input attributes with half + precision (16bit) floating point types for a shader pipeline is supported. + 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 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. + + \value RenderToOneDimensionalTexture Indicates that 1D texture render + targets are supported. In practice this feature will be unsupported on + backends that do not report support for \l{OneDimensionalTextures}, and Metal. + \value ThreeDimensionalTextureMipmaps Indicates that generating 3D texture + mipmaps are supported. In practice this feature will be unsupported with + Direct 3D 12. */ /*! @@ -847,22 +1078,28 @@ Q_LOGGING_CATEGORY(QRHI_LOG_INFO, "qt.rhi.general") /*! \class QRhiInitParams - \internal \inmodule QtGui + \since 6.6 \brief Base class for backend-specific initialization parameters. Contains fields that are relevant to all backends. + + \note This is a RHI API with limited compatibility guarantees, see \l QRhi + for details. */ /*! \class QRhiDepthStencilClearValue - \internal \inmodule QtGui + \since 6.6 \brief Specifies clear values for a depth or stencil buffer. + + \note This is a RHI API with limited compatibility guarantees, see \l QRhi + for details. */ /*! - \fn QRhiDepthStencilClearValue::QRhiDepthStencilClearValue() + \fn QRhiDepthStencilClearValue::QRhiDepthStencilClearValue() = default Constructs a depth/stencil clear value with depth clear value 1.0f and stencil clear value 0. @@ -879,37 +1116,45 @@ QRhiDepthStencilClearValue::QRhiDepthStencilClearValue(float d, quint32 s) } /*! - \return \c true if the values in the two QRhiDepthStencilClearValue objects - \a a and \a b are equal. - - \relates QRhiDepthStencilClearValue + \fn float QRhiDepthStencilClearValue::depthClearValue() const + \return the depth clear value. In most cases this is 1.0f. */ -bool operator==(const QRhiDepthStencilClearValue &a, const QRhiDepthStencilClearValue &b) noexcept -{ - return a.depthClearValue() == b.depthClearValue() - && a.stencilClearValue() == b.stencilClearValue(); -} /*! + \fn void QRhiDepthStencilClearValue::setDepthClearValue(float d) + Sets the depth clear value to \a d. + */ + +/*! + \fn quint32 QRhiDepthStencilClearValue::stencilClearValue() const + \return the stencil clear value. In most cases this is 0. + */ + +/*! + \fn void QRhiDepthStencilClearValue::setStencilClearValue(quint32 s) + Sets the stencil clear value to \a s. + */ + +/*! + \fn bool QRhiDepthStencilClearValue::operator==(const QRhiDepthStencilClearValue &a, const QRhiDepthStencilClearValue &b) noexcept + + \return \c true if the values in the two QRhiDepthStencilClearValue objects + \a a and \a b are equal. + */ + +/*! + \fn bool QRhiDepthStencilClearValue::operator!=(const QRhiDepthStencilClearValue &a, const QRhiDepthStencilClearValue &b) noexcept + \return \c false if the values in the two QRhiDepthStencilClearValue objects \a a and \a b are equal; otherwise returns \c true. - \relates QRhiDepthStencilClearValue */ -bool operator!=(const QRhiDepthStencilClearValue &a, const QRhiDepthStencilClearValue &b) noexcept -{ - return !(a == b); -} /*! - \return the hash value for \a v, using \a seed to seed the calculation. + \fn size_t QRhiDepthStencilClearValue::qHash(const QRhiDepthStencilClearValue &v, size_t seed = 0) noexcept - \relates QRhiDepthStencilClearValue + \return the hash value for \a v, using \a seed to seed the calculation. */ -size_t qHash(const QRhiDepthStencilClearValue &v, size_t seed) noexcept -{ - return seed * (uint(qFloor(qreal(v.depthClearValue()) * 100)) + v.stencilClearValue()); -} #ifndef QT_NO_DEBUG_STREAM QDebug operator<<(QDebug dbg, const QRhiDepthStencilClearValue &v) @@ -924,8 +1169,8 @@ QDebug operator<<(QDebug dbg, const QRhiDepthStencilClearValue &v) /*! \class QRhiViewport - \internal \inmodule QtGui + \since 6.6 \brief Specifies a viewport rectangle. Used with QRhiCommandBuffer::setViewport(). @@ -935,20 +1180,23 @@ QDebug operator<<(QDebug dbg, const QRhiDepthStencilClearValue &v) Typical usage is like the following: - \badcode + \code const QSize outputSizeInPixels = swapchain->currentPixelSize(); const QRhiViewport viewport(0, 0, outputSizeInPixels.width(), outputSizeInPixels.height()); - cb->beginPass(swapchain->currentFrameRenderTarget(), { 0, 0, 0, 1 }, { 1, 0 }); + cb->beginPass(swapchain->currentFrameRenderTarget(), Qt::black, { 1.0f, 0 }); cb->setGraphicsPipeline(ps); cb->setViewport(viewport); - ... + // ... \endcode + \note This is a RHI API with limited compatibility guarantees, see \l QRhi + for details. + \sa QRhiCommandBuffer::setViewport(), QRhi::clipSpaceCorrMatrix(), QRhiScissor */ /*! - \fn QRhiViewport::QRhiViewport() + \fn QRhiViewport::QRhiViewport() = default Constructs a viewport description with an empty rectangle and a depth range of 0.0f - 1.0f. @@ -974,40 +1222,59 @@ QRhiViewport::QRhiViewport(float x, float y, float w, float h, float minDepth, f } /*! + \fn std::array QRhiViewport::viewport() const + \return the viewport x, y, width, and height. + */ + +/*! + \fn void QRhiViewport::setViewport(float x, float y, float w, float h) + Sets the viewport's position and size to \a x, \a y, \a w, and \a h. + + \note Viewports are specified in a coordinate system that has its origin in + the bottom-left. + */ + +/*! + \fn float QRhiViewport::minDepth() const + \return the minDepth value of the depth range of the viewport. + */ + +/*! + \fn void QRhiViewport::setMinDepth(float minDepth) + Sets the \a minDepth of the depth range of the viewport. + By default this is set to 0.0f. + */ + +/*! + \fn float QRhiViewport::maxDepth() const + \return the maxDepth value of the depth range of the viewport. + */ + +/*! + \fn void QRhiViewport::setMaxDepth(float maxDepth) + Sets the \a maxDepth of the depth range of the viewport. + By default this is set to 1.0f. + */ + +/*! + \fn bool QRhiViewport::operator==(const QRhiViewport &a, const QRhiViewport &b) noexcept + \return \c true if the values in the two QRhiViewport objects \a a and \a b are equal. - - \relates QRhiViewport */ -bool operator==(const QRhiViewport &a, const QRhiViewport &b) noexcept -{ - return a.viewport() == b.viewport() - && a.minDepth() == b.minDepth() - && a.maxDepth() == b.maxDepth(); -} /*! + \fn bool QRhiViewport::operator!=(const QRhiViewport &a, const QRhiViewport &b) noexcept + \return \c false if the values in the two QRhiViewport objects \a a and \a b are equal; otherwise returns \c true. - - \relates QRhiViewport */ -bool operator!=(const QRhiViewport &a, const QRhiViewport &b) noexcept -{ - return !(a == b); -} /*! - \return the hash value for \a v, using \a seed to seed the calculation. + \fn size_t QRhiViewport::qHash(const QRhiViewport &v, size_t seed = 0) noexcept - \relates QRhiViewport + \return the hash value for \a v, using \a seed to seed the calculation. */ -size_t qHash(const QRhiViewport &v, size_t seed) noexcept -{ - const std::array r = v.viewport(); - return seed + uint(r[0]) + uint(r[1]) + uint(r[2]) + uint(r[3]) - + uint(qFloor(qreal(v.minDepth()) * 100)) + uint(qFloor(qreal(v.maxDepth()) * 100)); -} #ifndef QT_NO_DEBUG_STREAM QDebug operator<<(QDebug dbg, const QRhiViewport &v) @@ -1027,8 +1294,8 @@ QDebug operator<<(QDebug dbg, const QRhiViewport &v) /*! \class QRhiScissor - \internal \inmodule QtGui + \since 6.6 \brief Specifies a scissor rectangle. Used with QRhiCommandBuffer::setScissor(). Setting a scissor rectangle is @@ -1042,11 +1309,14 @@ 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 is a RHI API with limited compatibility guarantees, see \l QRhi + for details. + \sa QRhiCommandBuffer::setScissor(), QRhiViewport */ /*! - \fn QRhiScissor::QRhiScissor() + \fn QRhiScissor::QRhiScissor() = default Constructs an empty scissor. */ @@ -1067,37 +1337,37 @@ QRhiScissor::QRhiScissor(int x, int y, int w, int h) } /*! + \fn std::array QRhiScissor::scissor() const + \return the scissor position and size. + */ + +/*! + \fn void QRhiScissor::setScissor(int x, int y, int w, int h) + Sets the scissor position and size to \a x, \a y, \a w, \a h. + + \note The position is always expected to be specified in a coordinate + system that has its origin in the bottom-left corner, like OpenGL. + */ + +/*! + \fn bool QRhiScissor::operator==(const QRhiScissor &a, const QRhiScissor &b) noexcept + \return \c true if the values in the two QRhiScissor objects \a a and \a b are equal. - - \relates QRhiScissor */ -bool operator==(const QRhiScissor &a, const QRhiScissor &b) noexcept -{ - return a.scissor() == b.scissor(); -} /*! + \fn bool QRhiScissor::operator!=(const QRhiScissor &a, const QRhiScissor &b) noexcept + \return \c false if the values in the two QRhiScissor objects \a a and \a b are equal; otherwise returns \c true. - - \relates QRhiScissor */ -bool operator!=(const QRhiScissor &a, const QRhiScissor &b) noexcept -{ - return !(a == b); -} /*! - \return the hash value for \a v, using \a seed to seed the calculation. + \fn size_t QRhiScissor::qHash(const QRhiScissor &v, size_t seed = 0) noexcept - \relates QRhiScissor + \return the hash value for \a v, using \a seed to seed the calculation. */ -size_t qHash(const QRhiScissor &v, size_t seed) noexcept -{ - const std::array r = v.scissor(); - return seed + uint(r[0]) + uint(r[1]) + uint(r[2]) + uint(r[3]); -} #ifndef QT_NO_DEBUG_STREAM QDebug operator<<(QDebug dbg, const QRhiScissor &s) @@ -1115,8 +1385,8 @@ QDebug operator<<(QDebug dbg, const QRhiScissor &s) /*! \class QRhiVertexInputBinding - \internal \inmodule QtGui + \since 6.6 \brief Describes a vertex input binding. Specifies the stride (in bytes, must be a multiple of 4), the @@ -1134,7 +1404,7 @@ QDebug operator<<(QDebug dbg, const QRhiScissor &s) format in a buffer (or separate buffers even). Defining two bindings could then be done like this: - \badcode + \code QRhiVertexInputLayout inputLayout; inputLayout.setBindings({ { 3 * sizeof(float) }, @@ -1151,7 +1421,7 @@ QDebug operator<<(QDebug dbg, const QRhiScissor &s) vertices, assuming we have a single buffer with first the positions and then the texture coordinates: - \badcode + \code const QRhiCommandBuffer::VertexInput vbufBindings[] = { { cubeBuf, 0 }, { cubeBuf, 36 * 3 * sizeof(float) } @@ -1167,6 +1437,9 @@ QDebug operator<<(QDebug dbg, const QRhiScissor &s) \note the stride must always be a multiple of 4. + \note This is a RHI API with limited compatibility guarantees, see \l QRhi + for details. + \sa QRhiCommandBuffer::setVertexInput() */ @@ -1179,7 +1452,7 @@ QDebug operator<<(QDebug dbg, const QRhiScissor &s) */ /*! - \fn QRhiVertexInputBinding::QRhiVertexInputBinding() + \fn QRhiVertexInputBinding::QRhiVertexInputBinding() = default Constructs a default vertex input binding description. */ @@ -1199,38 +1472,54 @@ QRhiVertexInputBinding::QRhiVertexInputBinding(quint32 stride, Classification cl } /*! + \fn quint32 QRhiVertexInputBinding::stride() const + \return the stride in bytes. + */ + +/*! + \fn void QRhiVertexInputBinding::setStride(quint32 s) + Sets the stride to \a s. + */ + +/*! + \fn QRhiVertexInputBinding::Classification QRhiVertexInputBinding::classification() const + \return the input data classification. + */ + +/*! + \fn void QRhiVertexInputBinding::setClassification(Classification c) + Sets the input data classification \a c. By default this is set to PerVertex. + */ + +/*! + \fn quint32 QRhiVertexInputBinding::instanceStepRate() const + \return the instance step rate. + */ + +/*! + \fn void QRhiVertexInputBinding::setInstanceStepRate(quint32 rate) + Sets the instance step \a rate. By default this is set to 1. + */ + +/*! + \fn bool QRhiVertexInputBinding::operator==(const QRhiVertexInputBinding &a, const QRhiVertexInputBinding &b) noexcept + \return \c true if the values in the two QRhiVertexInputBinding objects \a a and \a b are equal. - - \relates QRhiVertexInputBinding */ -bool operator==(const QRhiVertexInputBinding &a, const QRhiVertexInputBinding &b) noexcept -{ - return a.stride() == b.stride() - && a.classification() == b.classification() - && a.instanceStepRate() == b.instanceStepRate(); -} /*! + \fn bool QRhiVertexInputBinding::operator!=(const QRhiVertexInputBinding &a, const QRhiVertexInputBinding &b) noexcept + \return \c false if the values in the two QRhiVertexInputBinding objects \a a and \a b are equal; otherwise returns \c true. - - \relates QRhiVertexInputBinding */ -bool operator!=(const QRhiVertexInputBinding &a, const QRhiVertexInputBinding &b) noexcept -{ - return !(a == b); -} /*! - \return the hash value for \a v, using \a seed to seed the calculation. + \fn size_t QRhiVertexInputBinding::qHash(const QRhiVertexInputBinding &v, size_t seed = 0) noexcept - \relates QRhiVertexInputBinding + \return the hash value for \a v, using \a seed to seed the calculation. */ -size_t qHash(const QRhiVertexInputBinding &v, size_t seed) noexcept -{ - return seed + v.stride() + v.classification(); -} #ifndef QT_NO_DEBUG_STREAM QDebug operator<<(QDebug dbg, const QRhiVertexInputBinding &b) @@ -1246,14 +1535,15 @@ QDebug operator<<(QDebug dbg, const QRhiVertexInputBinding &b) /*! \class QRhiVertexInputAttribute - \internal \inmodule QtGui + \since 6.6 \brief Describes a single vertex input element. The members specify the binding number, location, format, and offset for a single vertex input element. - \note For HLSL it is assumed that the vertex shader uses + \note For HLSL it is assumed that the vertex shader translated from SPIR-V + uses \c{TEXCOORD} as the semantic for each input. Hence no separate semantic name and index. @@ -1269,7 +1559,7 @@ QDebug operator<<(QDebug dbg, const QRhiVertexInputBinding &b) non-interleaved format in a buffer (or separate buffers even). Once two bindings are defined, the attributes could be specified as: - \badcode + \code QRhiVertexInputLayout inputLayout; inputLayout.setBindings({ { 3 * sizeof(float) }, @@ -1286,7 +1576,7 @@ QDebug operator<<(QDebug dbg, const QRhiVertexInputBinding &b) vertices, assuming we have a single buffer with first the positions and then the texture coordinates: - \badcode + \code const QRhiCommandBuffer::VertexInput vbufBindings[] = { { cubeBuf, 0 }, { cubeBuf, 36 * 3 * sizeof(float) } @@ -1298,7 +1588,7 @@ QDebug operator<<(QDebug dbg, const QRhiVertexInputBinding &b) binding, with multiple attributes referring to that same buffer binding point: - \badcode + \code QRhiVertexInputLayout inputLayout; inputLayout.setBindings({ { 5 * sizeof(float) } @@ -1311,11 +1601,14 @@ QDebug operator<<(QDebug dbg, const QRhiVertexInputBinding &b) and then: - \badcode + \code const QRhiCommandBuffer::VertexInput vbufBinding(interleavedCubeBuf, 0); cb->setVertexInput(0, 1, &vbufBinding); \endcode + \note This is a RHI API with limited compatibility guarantees, see \l QRhi + for details. + \sa QRhiCommandBuffer::setVertexInput() */ @@ -1338,10 +1631,20 @@ QDebug operator<<(QDebug dbg, const QRhiVertexInputBinding &b) \value SInt3 Three component signed integer vector \value SInt2 Two component signed integer vector \value SInt Signed integer + \value Half4 Four component half precision (16 bit) float vector + \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 + + \note Support for half precision floating point attributes is indicated at + run time by the QRhi::Feature::HalfAttributes feature flag. Note that + Direct3D 11/12 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. */ /*! - \fn QRhiVertexInputAttribute::QRhiVertexInputAttribute() + \fn QRhiVertexInputAttribute::QRhiVertexInputAttribute() = default Constructs a default vertex input attribute description. */ @@ -1366,39 +1669,85 @@ QRhiVertexInputAttribute::QRhiVertexInputAttribute(int binding, int location, Fo } /*! + \fn int QRhiVertexInputAttribute::binding() const + \return the binding point index. + */ + +/*! + \fn void QRhiVertexInputAttribute::setBinding(int b) + Sets the binding point index to \a b. + By default this is set to 0. + */ + +/*! + \fn int QRhiVertexInputAttribute::location() const + \return the location of the vertex input element. + */ + +/*! + \fn void QRhiVertexInputAttribute::setLocation(int loc) + Sets the location of the vertex input element to \a loc. + By default this is set to 0. + */ + +/*! + \fn QRhiVertexInputAttribute::Format QRhiVertexInputAttribute::format() const + \return the format of the vertex input element. + */ + +/*! + \fn void QRhiVertexInputAttribute::setFormat(Format f) + Sets the format of the vertex input element to \a f. + By default this is set to Float4. + */ + +/*! + \fn quint32 QRhiVertexInputAttribute::offset() const + \return the byte offset for the input element. + */ + +/*! + \fn void QRhiVertexInputAttribute::setOffset(quint32 ofs) + Sets the byte offset for the input element to \a ofs. + */ + +/*! + \fn int QRhiVertexInputAttribute::matrixSlice() const + + \return the matrix slice if the input element corresponds to a row or + column of a matrix, or -1 if not relevant. + */ + +/*! + \fn void QRhiVertexInputAttribute::setMatrixSlice(int slice) + + Sets the matrix \a slice. By default this is set to -1, and should be set + to a >= 0 value only when this attribute corresponds to a row or column of + a matrix (for example, a 4x4 matrix becomes 4 vec4s, consuming 4 + consecutive vertex input locations), in which case it is the index of the + row or column. \c{location - matrixSlice} must always be equal to the \c + location for the first row or column of the unrolled matrix. + */ + +/*! + \fn bool QRhiVertexInputAttribute::operator==(const QRhiVertexInputAttribute &a, const QRhiVertexInputAttribute &b) noexcept + \return \c true if the values in the two QRhiVertexInputAttribute objects \a a and \a b are equal. - - \relates QRhiVertexInputAttribute */ -bool operator==(const QRhiVertexInputAttribute &a, const QRhiVertexInputAttribute &b) noexcept -{ - return a.binding() == b.binding() - && a.location() == b.location() - && a.format() == b.format() - && a.offset() == b.offset(); -} /*! + \fn bool QRhiVertexInputAttribute::operator!=(const QRhiVertexInputAttribute &a, const QRhiVertexInputAttribute &b) noexcept + \return \c false if the values in the two QRhiVertexInputAttribute objects \a a and \a b are equal; otherwise returns \c true. - - \relates QRhiVertexInputAttribute */ -bool operator!=(const QRhiVertexInputAttribute &a, const QRhiVertexInputAttribute &b) noexcept -{ - return !(a == b); -} /*! - \return the hash value for \a v, using \a seed to seed the calculation. + \fn size_t QRhiVertexInputAttribute::qHash(const QRhiVertexInputAttribute &v, size_t seed = 0) noexcept - \relates QRhiVertexInputAttribute + \return the hash value for \a v, using \a seed to seed the calculation. */ -size_t qHash(const QRhiVertexInputAttribute &v, size_t seed) noexcept -{ - return seed + uint(v.binding()) + uint(v.location()) + uint(v.format()) + v.offset(); -} #ifndef QT_NO_DEBUG_STREAM QDebug operator<<(QDebug dbg, const QRhiVertexInputAttribute &a) @@ -1443,6 +1792,15 @@ QRhiVertexInputAttribute::Format QRhiImplementation::shaderDescVariableFormatToV case QShaderDescription::Uint: return QRhiVertexInputAttribute::UInt; + case QShaderDescription::Half4: + return QRhiVertexInputAttribute::Half4; + case QShaderDescription::Half3: + return QRhiVertexInputAttribute::Half3; + case QShaderDescription::Half2: + return QRhiVertexInputAttribute::Half2; + case QShaderDescription::Half: + return QRhiVertexInputAttribute::Half; + default: Q_UNREACHABLE_RETURN(QRhiVertexInputAttribute::Float); } @@ -1485,6 +1843,15 @@ quint32 QRhiImplementation::byteSizePerVertexForVertexInputFormat(QRhiVertexInpu case QRhiVertexInputAttribute::SInt: return sizeof(qint32); + case QRhiVertexInputAttribute::Half4: + return 4 * sizeof(qfloat16); + case QRhiVertexInputAttribute::Half3: + return 4 * sizeof(qfloat16); // half3 still takes 8 bytes + case QRhiVertexInputAttribute::Half2: + return 2 * sizeof(qfloat16); + case QRhiVertexInputAttribute::Half: + return sizeof(qfloat16); + default: Q_UNREACHABLE_RETURN(1); } @@ -1492,51 +1859,120 @@ quint32 QRhiImplementation::byteSizePerVertexForVertexInputFormat(QRhiVertexInpu /*! \class QRhiVertexInputLayout - \internal \inmodule QtGui + \since 6.6 \brief Describes the layout of vertex inputs consumed by a vertex shader. The vertex input layout is defined by the collections of QRhiVertexInputBinding and QRhiVertexInputAttribute. + + As an example, let's assume that we have a single buffer with 3 component + vertex positions and 2 component UV coordinates interleaved (\c x, \c y, \c + z, \c u, \c v), that the position and UV are expected at input locations 0 + and 1 by the vertex shader, and that the vertex buffer will be bound at + binding point 0 using + \l{QRhiCommandBuffer::setVertexInput()}{setVertexInput()} later on: + + \code + QRhiVertexInputLayout inputLayout; + inputLayout.setBindings({ + { 5 * sizeof(float) } + }); + inputLayout.setAttributes({ + { 0, 0, QRhiVertexInputAttribute::Float3, 0 }, + { 0, 1, QRhiVertexInputAttribute::Float2, 3 * sizeof(float) } + }); + \endcode + + \note This is a RHI API with limited compatibility guarantees, see \l QRhi + for details. */ /*! - \fn QRhiVertexInputLayout::QRhiVertexInputLayout() + \fn QRhiVertexInputLayout::QRhiVertexInputLayout() = default Constructs an empty vertex input layout description. */ /*! + \fn void QRhiVertexInputLayout::setBindings(std::initializer_list list) + Sets the bindings from the specified \a list. + */ + +/*! + \fn template void QRhiVertexInputLayout::setBindings(InputIterator first, InputIterator last) + Sets the bindings using the iterators \a first and \a last. + */ + +/*! + \fn const QRhiVertexInputBinding *QRhiVertexInputLayout::cbeginBindings() const + \return a const iterator pointing to the first item in the binding list. + */ + +/*! + \fn const QRhiVertexInputBinding *QRhiVertexInputLayout::cendBindings() const + \return a const iterator pointing just after the last item in the binding list. + */ + +/*! + \fn const QRhiVertexInputBinding *QRhiVertexInputLayout::bindingAt(qsizetype index) const + \return the binding at the given \a index. + */ + +/*! + \fn qsizetype QRhiVertexInputLayout::bindingCount() const + \return the number of bindings. + */ + +/*! + \fn void QRhiVertexInputLayout::setAttributes(std::initializer_list list) + Sets the attributes from the specified \a list. + */ + +/*! + \fn template void QRhiVertexInputLayout::setAttributes(InputIterator first, InputIterator last) + Sets the attributes using the iterators \a first and \a last. + */ + +/*! + \fn const QRhiVertexInputAttribute *QRhiVertexInputLayout::cbeginAttributes() const + \return a const iterator pointing to the first item in the attribute list. + */ + +/*! + \fn const QRhiVertexInputAttribute *QRhiVertexInputLayout::cendAttributes() const + \return a const iterator pointing just after the last item in the attribute list. + */ + +/*! + \fn const QRhiVertexInputAttribute *QRhiVertexInputLayout::attributeAt(qsizetype index) const + \return the attribute at the given \a index. + */ + +/*! + \fn qsizetype QRhiVertexInputLayout::attributeCount() const + \return the number of attributes. + */ + +/*! + \fn bool QRhiVertexInputLayout::operator==(const QRhiVertexInputLayout &a, const QRhiVertexInputLayout &b) noexcept + \return \c true if the values in the two QRhiVertexInputLayout objects \a a and \a b are equal. - - \relates QRhiVertexInputLayout */ -bool operator==(const QRhiVertexInputLayout &a, const QRhiVertexInputLayout &b) noexcept -{ - return a.m_bindings == b.m_bindings && a.m_attributes == b.m_attributes; -} /*! + \fn bool QRhiVertexInputLayout::operator!=(const QRhiVertexInputLayout &a, const QRhiVertexInputLayout &b) noexcept + \return \c false if the values in the two QRhiVertexInputLayout objects \a a and \a b are equal; otherwise returns \c true. - - \relates QRhiVertexInputLayout */ -bool operator!=(const QRhiVertexInputLayout &a, const QRhiVertexInputLayout &b) noexcept -{ - return !(a == b); -} /*! - \return the hash value for \a v, using \a seed to seed the calculation. + \fn size_t QRhiVertexInputLayout::qHash(const QRhiVertexInputLayout &v, size_t seed = 0) noexcept - \relates QRhiVertexInputLayout + \return the hash value for \a v, using \a seed to seed the calculation. */ -size_t qHash(const QRhiVertexInputLayout &v, size_t seed) noexcept -{ - return qHash(v.m_bindings, seed) + qHash(v.m_attributes, seed); -} #ifndef QT_NO_DEBUG_STREAM QDebug operator<<(QDebug dbg, const QRhiVertexInputLayout &v) @@ -1551,9 +1987,39 @@ QDebug operator<<(QDebug dbg, const QRhiVertexInputLayout &v) /*! \class QRhiShaderStage - \internal \inmodule QtGui + \since 6.6 \brief Specifies the type and the shader code for a shader stage in the pipeline. + + When setting up a QRhiGraphicsPipeline, a collection of shader stages are + specified. The QRhiShaderStage contains a QShader and some associated + metadata, such as the graphics pipeline stage, and the + \l{QShader::Variant}{shader variant} to select. There is no need to specify + the shader language or version because the QRhi backend in use at runtime + will take care of choosing the appropriate shader version from the + collection within the QShader. + + The typical usage is in combination with + QRhiGraphicsPipeline::setShaderStages(), shown here with a simple approach + to load the QShader from \c{.qsb} files generated offline or at build time: + + \code + QShader getShader(const QString &name) + { + QFile f(name); + return f.open(QIODevice::ReadOnly) ? QShader::fromSerialized(f.readAll()) : QShader(); + } + + QShader vs = getShader("material.vert.qsb"); + QShader fs = getShader("material.frag.qsb"); + pipeline->setShaderStages({ + { QRhiShaderStage::Vertex, vs }, + { QRhiShaderStage::Fragment, fs } + }); + \endcode + + \note This is a RHI API with limited compatibility guarantees, see \l QRhi + for details. */ /*! @@ -1562,10 +2028,10 @@ QDebug operator<<(QDebug dbg, const QRhiVertexInputLayout &v) \value Vertex Vertex stage - \value TessellationControlStage Tessellation control (hull shader) stage. - Must be used only when the QRhi::Tessellation feature is supported. + \value TessellationControl Tessellation control (hull shader) stage. Must + be used only when the QRhi::Tessellation feature is supported. - \value TessellationEvaluationStage Tessellation evaluation (domain shader) + \value TessellationEvaluation Tessellation evaluation (domain shader) stage. Must be used only when the QRhi::Tessellation feature is supported. \value Fragment Fragment (pixel shader) stage @@ -1578,12 +2044,45 @@ QDebug operator<<(QDebug dbg, const QRhiVertexInputLayout &v) */ /*! - \fn QRhiShaderStage::QRhiShaderStage() + \fn QRhiShaderStage::QRhiShaderStage() = default Constructs a shader stage description for the vertex stage with an empty QShader. */ +/*! + \fn QRhiShaderStage::Type QRhiShaderStage::type() const + \return the type of the stage. + */ + +/*! + \fn void QRhiShaderStage::setType(Type t) + + Sets the type of the stage to \a t. Setters should rarely be needed in + pratice. Most applications will likely use the QRhiShaderStage constructor + in most cases. + */ + +/*! + \fn QShader QRhiShaderStage::shader() const + \return the QShader to be used for this stage in the graphics pipeline. + */ + +/*! + \fn void QRhiShaderStage::setShader(const QShader &s) + Sets the shader collection \a s. + */ + +/*! + \fn QShader::Variant QRhiShaderStage::shaderVariant() const + \return the requested shader variant. + */ + +/*! + \fn void QRhiShaderStage::setShaderVariant(QShader::Variant v) + Sets the requested shader variant \a v. + */ + /*! Constructs a shader stage description with the \a type of the stage and the \a shader. @@ -1601,38 +2100,24 @@ QRhiShaderStage::QRhiShaderStage(Type type, const QShader &shader, QShader::Vari } /*! + \fn bool QRhiShaderStage::operator==(const QRhiShaderStage &a, const QRhiShaderStage &b) noexcept + \return \c true if the values in the two QRhiShaderStage objects \a a and \a b are equal. - - \relates QRhiShaderStage */ -bool operator==(const QRhiShaderStage &a, const QRhiShaderStage &b) noexcept -{ - return a.type() == b.type() - && a.shader() == b.shader() - && a.shaderVariant() == b.shaderVariant(); -} /*! + \fn bool QRhiShaderStage::operator!=(const QRhiShaderStage &a, const QRhiShaderStage &b) noexcept + \return \c false if the values in the two QRhiShaderStage objects \a a and \a b are equal; otherwise returns \c true. - - \relates QRhiShaderStage */ -bool operator!=(const QRhiShaderStage &a, const QRhiShaderStage &b) noexcept -{ - return !(a == b); -} /*! - \return the hash value for \a v, using \a seed to seed the calculation. + \fn size_t QRhiShaderStage::qHash(const QRhiShaderStage &v, size_t seed = 0) noexcept - \relates QRhiShaderStage + \return the hash value for \a v, using \a seed to seed the calculation. */ -size_t qHash(const QRhiShaderStage &v, size_t seed) noexcept -{ - return v.type() + qHash(v.shader(), seed) + v.shaderVariant(); -} #ifndef QT_NO_DEBUG_STREAM QDebug operator<<(QDebug dbg, const QRhiShaderStage &s) @@ -1648,12 +2133,14 @@ QDebug operator<<(QDebug dbg, const QRhiShaderStage &s) /*! \class QRhiColorAttachment - \internal \inmodule QtGui + \since 6.6 \brief Describes the a single color attachment of a render target. A color attachment is either a QRhiTexture or a QRhiRenderBuffer. The - former, when texture() is set, is used in most cases. + former, i.e. when texture() is set, is used in most cases. + QRhiColorAttachment is commonly used in combination with + QRhiTextureRenderTargetDescription. \note texture() and renderBuffer() cannot be both set (be non-null at the same time). @@ -1680,10 +2167,15 @@ QDebug operator<<(QDebug dbg, const QRhiShaderStage &s) \note when resolving is enabled, the multisample data may not be written out at all. This means that the multisample texture() must not be used afterwards with shaders for sampling when resolveTexture() is set. + + \note This is a RHI API with limited compatibility guarantees, see \l QRhi + for details. + + \sa QRhiTextureRenderTargetDescription */ /*! - \fn QRhiColorAttachment::QRhiColorAttachment() + \fn QRhiColorAttachment::QRhiColorAttachment() = default Constructs an empty color attachment description. */ @@ -1706,10 +2198,106 @@ QRhiColorAttachment::QRhiColorAttachment(QRhiRenderBuffer *renderBuffer) { } +/*! + \fn QRhiTexture *QRhiColorAttachment::texture() const + + \return the texture this attachment description references, or \nullptr if + there is none. + */ + +/*! + \fn void QRhiColorAttachment::setTexture(QRhiTexture *tex) + + Sets the texture \a tex. + + \note texture() and renderBuffer() cannot be both set (be non-null at the + same time). + */ + +/*! + \fn QRhiRenderBuffer *QRhiColorAttachment::renderBuffer() const + + \return the renderbuffer this attachment description references, or + \nullptr if there is none. + + In practice associating a QRhiRenderBuffer with a QRhiColorAttachment makes + the most sense when setting up multisample rendering via a multisample + \l{QRhiRenderBuffer::Type}{color} renderbuffer that is then resolved into a + non-multisample texture at the end of the render pass. + */ + +/*! + \fn void QRhiColorAttachment::setRenderBuffer(QRhiRenderBuffer *rb) + + Sets the renderbuffer \a rb. + + \note texture() and renderBuffer() cannot be both set (be non-null at the + same time). + */ + +/*! + \fn int QRhiColorAttachment::layer() const + \return the layer index (cubemap face or array layer). 0 by default. + */ + +/*! + \fn void QRhiColorAttachment::setLayer(int layer) + Sets the \a layer index. + */ + +/*! + \fn int QRhiColorAttachment::level() const + \return the mip level. 0 by default. + */ + +/*! + \fn void QRhiColorAttachment::setLevel(int level) + Sets the mip \a level. + */ + +/*! + \fn QRhiTexture *QRhiColorAttachment::resolveTexture() const + + \return the resolve texture this attachment description references, or + \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. + */ + +/*! + \fn void QRhiColorAttachment::setResolveTexture(QRhiTexture *tex) + Sets the resolve texture \a tex. + */ + +/*! + \fn int QRhiColorAttachment::resolveLayer() const + \return the currently set resolve texture layer. Defaults to 0. + */ + +/*! + \fn void QRhiColorAttachment::setResolveLayer(int layer) + Sets the resolve texture \a layer to use. + */ + +/*! + \fn int QRhiColorAttachment::resolveLevel() const + \return the currently set resolve texture mip level. Defaults to 0. + */ + +/*! + \fn void QRhiColorAttachment::setResolveLevel(int level) + Sets the resolve texture mip \a level to use. + */ + /*! \class QRhiTextureRenderTargetDescription - \internal \inmodule QtGui + \since 6.6 \brief Describes the color and depth or depth/stencil attachments of a render target. A texture render target has zero or more textures as color attachments, @@ -1718,10 +2306,78 @@ QRhiColorAttachment::QRhiColorAttachment(QRhiRenderBuffer *renderBuffer) \note depthStencilBuffer() and depthTexture() cannot be both set (cannot be non-null at the same time). + + Let's look at some example usages in combination with + QRhiTextureRenderTarget. + + Due to the constructors, the targeting a texture (and no depth/stencil + buffer) is simple: + + \code + QRhiTexture *texture = rhi->newTexture(QRhiTexture::RGBA8, QSize(256, 256), 1, QRhiTexture::RenderTarget); + texture->create(); + QRhiTextureRenderTarget *rt = rhi->newTextureRenderTarget({ texture })); + \endcode + + The following creates a texture render target that is set up to target mip + level #2 of a texture: + + \code + QRhiTexture *texture = rhi->newTexture(QRhiTexture::RGBA8, QSize(512, 512), 1, QRhiTexture::RenderTarget | QRhiTexture::MipMapped); + texture->create(); + QRhiColorAttachment colorAtt(texture); + colorAtt.setLevel(2); + QRhiTextureRenderTarget *rt = rhi->newTextureRenderTarget({ colorAtt }); + \endcode + + Another example, this time to render into a depth texture: + + \code + QRhiTexture *shadowMap = rhi->newTexture(QRhiTexture::D32F, QSize(1024, 1024), 1, QRhiTexture::RenderTarget); + shadowMap->create(); + QRhiTextureRenderTargetDescription rtDesc; + rtDesc.setDepthTexture(shadowMap); + QRhiTextureRenderTarget *rt = rhi->newTextureRenderTarget(rtDesc); + \endcode + + A very common case, having a texture as the color attachment and a + renderbuffer as depth/stencil to enable depth testing: + + \code + QRhiTexture *texture = rhi->newTexture(QRhiTexture::RGBA8, QSize(512, 512), 1. QRhiTexture::RenderTarget); + texture->create(); + QRhiRenderBuffer *depthStencil = rhi->newRenderBuffer(QRhiRenderBuffer::DepthStencil, QSize(512, 512)); + depthStencil->create(); + QRhiTextureRenderTargetDescription rtDesc({ texture }, depthStencil); + QRhiTextureRenderTarget *rt = rhi->newTextureRenderTarget(rtDesc); + \endcode + + Finally, to enable multisample rendering in a portable manner (so also + supporting OpenGL ES 3.0), using a QRhiRenderBuffer as the (multisample) + color buffer and then resolving into a regular (non-multisample) 2D + texture. To enable depth testing, a depth-stencil buffer, which also must + use the same sample count, is used as well: + + \code + QRhiRenderBuffer *colorBuffer = rhi->newRenderBuffer(QRhiRenderBuffer::Color, QSize(512, 512), 4); // 4x MSAA + colorBuffer->create(); + QRhiRenderBuffer *depthStencil = rhi->newRenderBuffer(QRhiRenderBuffer::DepthStencil, QSize(512, 512), 4); + depthStencil->create(); + QRhiTexture *texture = rhi->newTexture(QRhiTexture::RGBA8, QSize(512, 512), 1, QRhiTexture::RenderTarget); + texture->create(); + QRhiColorAttachment colorAtt(colorBuffer); + colorAtt.setResolveTexture(texture); + QRhiTextureRenderTarget *rt = rhi->newTextureRenderTarget({ colorAtt, depthStencil }); + \endcode + + \note This is a RHI API with limited compatibility guarantees, see \l QRhi + for details. + + \sa QRhiColorAttachment, QRhiTextureRenderTarget */ /*! - \fn QRhiTextureRenderTargetDescription::QRhiTextureRenderTargetDescription() + \fn QRhiTextureRenderTargetDescription::QRhiTextureRenderTargetDescription() = default Constructs an empty texture render target description. */ @@ -1762,10 +2418,73 @@ QRhiTextureRenderTargetDescription::QRhiTextureRenderTargetDescription(const QRh m_colorAttachments.append(colorAttachment); } +/*! + \fn void QRhiTextureRenderTargetDescription::setColorAttachments(std::initializer_list list) + Sets the \a list of color attachments. + */ + +/*! + \fn template void QRhiTextureRenderTargetDescription::setColorAttachments(InputIterator first, InputIterator last) + Sets the list of color attachments via the iterators \a first and \a last. + */ + +/*! + \fn const QRhiColorAttachment *QRhiTextureRenderTargetDescription::cbeginColorAttachments() const + \return a const iterator pointing to the first item in the attachment list. + */ + +/*! + \fn const QRhiColorAttachment *QRhiTextureRenderTargetDescription::cendColorAttachments() const + \return a const iterator pointing just after the last item in the attachment list. + */ + +/*! + \fn const QRhiColorAttachment *QRhiTextureRenderTargetDescription::colorAttachmentAt(qsizetype index) const + \return the color attachment at the specified \a index. + */ + +/*! + \fn qsizetype QRhiTextureRenderTargetDescription::colorAttachmentCount() const + \return the number of currently set color attachments. + */ + +/*! + \fn QRhiRenderBuffer *QRhiTextureRenderTargetDescription::depthStencilBuffer() const + \return the renderbuffer used as depth-stencil buffer, or \nullptr if none was set. + */ + +/*! + \fn void QRhiTextureRenderTargetDescription::setDepthStencilBuffer(QRhiRenderBuffer *renderBuffer) + + Sets the \a renderBuffer for depth-stencil. Not mandatory, e.g. when no + depth test/write or stencil-related features are used within any graphics + pipelines in any of the render passes for this render target, it can be + left set to \nullptr. + + \note depthStencilBuffer() and depthTexture() cannot be both set (cannot be + non-null at the same time). + */ + +/*! + \fn QRhiTexture *QRhiTextureRenderTargetDescription::depthTexture() const + \return the currently referenced depth texture, or \nullptr if none was set. + */ + +/*! + \fn void QRhiTextureRenderTargetDescription::setDepthTexture(QRhiTexture *texture) + + Sets the \a texture for depth-stencil. This is an alternative to + setDepthStencilBuffer(), where instead of a QRhiRenderBuffer a QRhiTexture + with a suitable type (e.g., QRhiTexture::D32F) is provided. + + \note depthStencilBuffer() and depthTexture() cannot be both set (cannot be + non-null at the same time). + */ + /*! \class QRhiTextureSubresourceUploadDescription - \internal \inmodule QtGui + \since 6.6 \brief Describes the source for one mip level in a layer in a texture upload operation. The source content is specified either as a QImage or as a raw blob. The @@ -1822,10 +2541,15 @@ QRhiTextureRenderTargetDescription::QRhiTextureRenderTargetDescription(const QRh in order to be portable across all backends) If this cannot be ensured, the caller is strongly encouraged to call QImage::detach() on the image before passing it to uploadTexture(). + + \note This is a RHI API with limited compatibility guarantees, see \l QRhi + for details. + + \sa QRhiTextureUploadDescription */ /*! - \fn QRhiTextureSubresourceUploadDescription::QRhiTextureSubresourceUploadDescription() + \fn QRhiTextureSubresourceUploadDescription::QRhiTextureSubresourceUploadDescription() = default Constructs an empty subresource description. @@ -1872,13 +2596,109 @@ QRhiTextureSubresourceUploadDescription::QRhiTextureSubresourceUploadDescription { } +/*! + \fn QImage QRhiTextureSubresourceUploadDescription::image() const + \return the currently set QImage. + */ + +/*! + \fn void QRhiTextureSubresourceUploadDescription::setImage(const QImage &image) + + Sets \a image. + + \note image() and data() cannot be both set at the same time. + */ + +/*! + \fn QByteArray QRhiTextureSubresourceUploadDescription::data() const + \return the currently set raw pixel data. + */ + +/*! + \fn void QRhiTextureSubresourceUploadDescription::setData(const QByteArray &data) + + Sets \a data. + + \note image() and data() cannot be both set at the same time. + */ + +/*! + \fn quint32 QRhiTextureSubresourceUploadDescription::dataStride() const + \return the currently set data stride. + */ + +/*! + \fn void QRhiTextureSubresourceUploadDescription::setDataStride(quint32 stride) + + Sets the data \a stride in bytes. By default this is 0 and not always + relevant. When providing raw data(), and the stride is not specified via + setDataStride(), the stride (row pitch, row length in bytes) of the + provided data must be equal to \c{width * pixelSize} where \c pixelSize is + the number of bytes used for one pixel, and there must be no additional + padding between rows. Otherwise, if there is additional space between the + lines, set a non-zero \a stride. All this is applicable only when raw image + data is provided, and is not necessary when working QImage since that has + its own \l{QImage::bytesPerLine()}{stride} value. + + \note Setting the stride via setDataStride() is only functional when + QRhi::ImageDataStride is reported as + \l{QRhi::isFeatureSupported()}{supported}. + + \note When a QImage is given, the stride returned from + QImage::bytesPerLine() is taken into account automatically and therefore + there is no need to set the data stride manually. + */ + +/*! + \fn QPoint QRhiTextureSubresourceUploadDescription::destinationTopLeft() const + \return the currently set destination top-left position. Defaults to (0, 0). + */ + +/*! + \fn void QRhiTextureSubresourceUploadDescription::setDestinationTopLeft(const QPoint &p) + Sets the destination top-left position \a p. + */ + +/*! + \fn QSize QRhiTextureSubresourceUploadDescription::sourceSize() const + + \return the source size in pixels. Defaults to a default-constructed QSize, + which indicates the entire subresource. + */ + +/*! + \fn void QRhiTextureSubresourceUploadDescription::setSourceSize(const QSize &size) + + Sets the source \a size in pixels. + + \note Setting sourceSize() or sourceTopLeft() may trigger a QImage copy + internally, depending on the format and the backend. + */ + +/*! + \fn QPoint QRhiTextureSubresourceUploadDescription::sourceTopLeft() const + \return the currently set source top-left position. Defaults to (0, 0). + */ + +/*! + \fn void QRhiTextureSubresourceUploadDescription::setSourceTopLeft(const QPoint &p) + + Sets the source top-left position \a p. + + \note Setting sourceSize() or sourceTopLeft() may trigger a QImage copy + internally, depending on the format and the backend. + */ + /*! \class QRhiTextureUploadEntry - \internal \inmodule QtGui + \since 6.6 \brief Describes one layer (face for cubemaps, slice for 3D textures, element for texture arrays) in a texture upload operation. + + \note This is a RHI API with limited compatibility guarantees, see \l QRhi + for details. */ /*! @@ -1903,19 +2723,62 @@ QRhiTextureUploadEntry::QRhiTextureUploadEntry(int layer, int level, { } +/*! + \fn int QRhiTextureUploadEntry::layer() const + \return the currently set layer index (cubemap face, array layer). Defaults to 0. + */ + +/*! + \fn void QRhiTextureUploadEntry::setLayer(int layer) + Sets the \a layer. + */ + +/*! + \fn int QRhiTextureUploadEntry::level() const + \return the currently set mip level. Defaults to 0. + */ + +/*! + \fn void QRhiTextureUploadEntry::setLevel(int level) + Sets the mip \a level. + */ + +/*! + \fn QRhiTextureSubresourceUploadDescription QRhiTextureUploadEntry::description() const + \return the currently set subresource description. + */ + +/*! + \fn void QRhiTextureUploadEntry::setDescription(const QRhiTextureSubresourceUploadDescription &desc) + Sets the subresource description \a desc. + */ + /*! \class QRhiTextureUploadDescription - \internal \inmodule QtGui + \since 6.6 \brief Describes a texture upload operation. Used with QRhiResourceUpdateBatch::uploadTexture(). That function has two variants: one taking a QImage and one taking a QRhiTextureUploadDescription. The former is a convenience version, internally creating a QRhiTextureUploadDescription with a single image - targeting level 0 for layer 0. However, when cubemaps, pre-generated mip - images, or compressed textures are involved, applications will have to work - directly with this class instead. + targeting level 0 for layer 0. + + An example of the the common, simple case of wanting to upload the contents + of a QImage to a QRhiTexture with a matching pixel size: + + \code + QImage image(256, 256, QImage::Format_RGBA8888); + image.fill(Qt::green); // or could use a QPainter targeting image + QRhiTexture *texture = rhi->newTexture(QRhiTexture::RGBA8, QSize(256, 256)); + texture->create(); + QRhiResourceUpdateBatch *u = rhi->nextResourceUpdateBatch(); + u->uploadTexture(texture, image); + \endcode + + When cubemaps, pre-generated mip images, compressed textures, or partial + uploads are involved, applications will have to use this class instead. QRhiTextureUploadDescription also enables specifying batched uploads, which are useful for example when generating an atlas or glyph cache texture: @@ -1931,10 +2794,10 @@ QRhiTextureUploadEntry::QRhiTextureUploadEntry(int layer, int level, For example, specifying the faces of a cubemap could look like the following: - \badcode + \code QImage faces[6]; - ... - QList entries; + // ... + QVarLengthArray entries; for (int i = 0; i < 6; ++i) entries.append(QRhiTextureUploadEntry(i, 0, faces[i])); QRhiTextureUploadDescription desc; @@ -1944,7 +2807,7 @@ QRhiTextureUploadEntry::QRhiTextureUploadEntry(int layer, int level, Another example that specifies mip images for a compressed texture: - \badcode + \code QList entries; const int mipCount = rhi->mipLevelsForSize(compressedTexture->pixelSize()); for (int level = 0; level < mipCount; ++level) { @@ -1959,7 +2822,7 @@ QRhiTextureUploadEntry::QRhiTextureUploadEntry(int layer, int level, With partial uploads targeting the same subresource, it is recommended to batch them into a single upload request, whenever possible: - \badcode + \code QRhiTextureSubresourceUploadDescription subresDesc(image); subresDesc.setSourceSize(QSize(10, 10)); subResDesc.setDestinationTopLeft(QPoint(50, 40)); @@ -1973,6 +2836,11 @@ QRhiTextureUploadEntry::QRhiTextureUploadEntry(int layer, int level, QRhiTextureUploadDescription desc({ entry, entry2}); resourceUpdates->uploadTexture(texture, desc); \endcode + + \note This is a RHI API with limited compatibility guarantees, see \l QRhi + for details. + + \sa QRhiResourceUpdateBatch */ /*! @@ -2004,10 +2872,40 @@ QRhiTextureUploadDescription::QRhiTextureUploadDescription(std::initializer_list { } +/*! + \fn void QRhiTextureUploadDescription::setEntries(std::initializer_list list) + Sets the \a list of entries. + */ + +/*! + \fn template void QRhiTextureUploadDescription::setEntries(InputIterator first, InputIterator last) + Sets the list of entries using the iterators \a first and \a last. + */ + +/*! + \fn const QRhiTextureUploadEntry *QRhiTextureUploadDescription::cbeginEntries() const + \return a const iterator pointing to the first item in the entry list. + */ + +/*! + \fn const QRhiTextureUploadEntry *QRhiTextureUploadDescription::cendEntries() const + \return a const iterator pointing just after the last item in the entry list. + */ + +/*! + \fn const QRhiTextureUploadEntry *QRhiTextureUploadDescription::entryAt(qsizetype index) const + \return the entry at \a index. + */ + +/*! + \fn qsizetype QRhiTextureUploadDescription::entryCount() const + \return the number of entries. + */ + /*! \class QRhiTextureCopyDescription - \internal \inmodule QtGui + \since 6.6 \brief Describes a texture-to-texture copy operation. An empty pixelSize() indicates that the entire subresource is to be copied. @@ -2027,6 +2925,9 @@ QRhiTextureUploadDescription::QRhiTextureUploadDescription(std::initializer_list copied at a time. The source and destination layer and mip level indices can 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 is a RHI API with limited compatibility guarantees, see \l QRhi + for details. */ /*! @@ -2035,10 +2936,84 @@ QRhiTextureUploadDescription::QRhiTextureUploadDescription(std::initializer_list Constructs an empty texture copy description. */ +/*! + \fn QSize QRhiTextureCopyDescription::pixelSize() const + \return the size of the region to copy. + + \note An empty pixelSize() indicates that the entire subresource is to be + copied. A default constructed copy description therefore leads to copying + the entire subresource at level 0 of layer 0. + */ + +/*! + \fn void QRhiTextureCopyDescription::setPixelSize(const QSize &sz) + Sets the size of the region to copy to \a sz. + */ + +/*! + \fn int QRhiTextureCopyDescription::sourceLayer() const + \return the source array layer (cubemap face or array layer index). Defaults to 0. + */ + +/*! + \fn void QRhiTextureCopyDescription::setSourceLayer(int layer) + Sets the source array \a layer. + */ + +/*! + \fn int QRhiTextureCopyDescription::sourceLevel() const + \return the source mip level. Defaults to 0. + */ + +/*! + \fn void QRhiTextureCopyDescription::setSourceLevel(int level) + Sets the source mip \a level. + */ + +/*! + \fn QPoint QRhiTextureCopyDescription::sourceTopLeft() const + \return the source top-left position (in pixels). Defaults to (0, 0). + */ + +/*! + \fn void QRhiTextureCopyDescription::setSourceTopLeft(const QPoint &p) + Sets the source top-left position to \a p. + */ + +/*! + \fn int QRhiTextureCopyDescription::destinationLayer() const + \return the destination array layer (cubemap face or array layer index). Default to 0. + */ + +/*! + \fn void QRhiTextureCopyDescription::setDestinationLayer(int layer) + Sets the destination array \a layer. + */ + +/*! + \fn int QRhiTextureCopyDescription::destinationLevel() const + \return the destionation mip level. Defaults to 0. + */ + +/*! + \fn void QRhiTextureCopyDescription::setDestinationLevel(int level) + Sets the destination mip \a level. + */ + +/*! + \fn QPoint QRhiTextureCopyDescription::destinationTopLeft() const + \return the destionation top-left position in pixels. Defaults to (0, 0). + */ + +/*! + \fn void QRhiTextureCopyDescription::setDestinationTopLeft(const QPoint &p) + Sets the destination top-left position \a p. + */ + /*! \class QRhiReadbackDescription - \internal \inmodule QtGui + \since 6.6 \brief Describes a readback (reading back texture contents from possibly GPU-only memory) operation. The source of the readback operation is either a QRhiTexture or the @@ -2056,10 +3031,13 @@ QRhiTextureUploadDescription::QRhiTextureUploadDescription(std::initializer_list \note Multisample textures cannot be read back. Readbacks are supported for multisample swapchain buffers however. + + \note This is a RHI API with limited compatibility guarantees, see \l QRhi + for details. */ /*! - \fn QRhiReadbackDescription::QRhiReadbackDescription() + \fn QRhiReadbackDescription::QRhiReadbackDescription() = default Constructs an empty texture readback description. @@ -2082,33 +3060,141 @@ QRhiReadbackDescription::QRhiReadbackDescription(QRhiTexture *texture) { } +/*! + \fn QRhiTexture *QRhiReadbackDescription::texture() const + + \return the QRhiTexture that is read back. Can be left set to \nullptr + which indicates that the backbuffer of the current swapchain is to be used + instead. + */ + +/*! + \fn void QRhiReadbackDescription::setTexture(QRhiTexture *tex) + + Sets the texture \a tex as the source of the readback operation. + + Setting \nullptr is valid too, in which case the current swapchain's + current backbuffer is used. (but then the readback cannot be issued in a + non-swapchain-based frame) + + \note Multisample textures cannot be read back. Readbacks are supported for + multisample swapchain buffers however. + + \note Textures used in readbacks must be created with + QRhiTexture::UsedAsTransferSource. + + \note Swapchains used in readbacks must be created with + QRhiSwapChain::UsedAsTransferSource. + */ + +/*! + \fn int QRhiReadbackDescription::layer() const + + \return the currently set array layer (cubemap face, array index). Defaults to 0. + + Applicable only when the source of the readback is a QRhiTexture. + */ + +/*! + \fn void QRhiReadbackDescription::setLayer(int layer) + Sets the array \a layer to read back. + */ + +/*! + \fn int QRhiReadbackDescription::level() const + + \return the currently set mip level. Defaults to 0. + + Applicable only when the source of the readback is a QRhiTexture. + */ + +/*! + \fn void QRhiReadbackDescription::setLevel(int level) + Sets the mip \a level to read back. + */ + /*! \class QRhiReadbackResult - \internal \inmodule QtGui - \brief Describes the results of a potentially asynchronous readback operation. + \since 6.6 + \brief Describes the results of a potentially asynchronous buffer or texture readback operation. When \l completed is set, the function is invoked when the \l data is available. \l format and \l pixelSize are set upon completion together with \l data. + + \note This is a RHI API with limited compatibility guarantees, see \l QRhi + for details. */ +/*! + \variable QRhiReadbackResult::completed + + Callback that is invoked upon completion, on the thread the QRhi operates + on. Can be left set to \nullptr, in which case no callback is invoked. + */ + +/*! + \variable QRhiReadbackResult::format + + Valid only for textures, the texture format. + */ + +/*! + \variable QRhiReadbackResult::pixelSize + + Valid only for textures, the size in pixels. + */ + +/*! + \variable QRhiReadbackResult::data + + The buffer or image data. + + \sa QRhiResourceUpdateBatch::readBackTexture(), QRhiResourceUpdateBatch::readBackBuffer() + */ + + /*! \class QRhiNativeHandles - \internal \inmodule QtGui + \since 6.6 \brief Base class for classes exposing backend-specific collections of native resource objects. + + \note This is a RHI API with limited compatibility guarantees, see \l QRhi + for details. */ /*! \class QRhiResource - \internal \inmodule QtGui + \since 6.6 \brief Base class for classes encapsulating native resource objects. + + \note This is a RHI API with limited compatibility guarantees, see \l QRhi + for details. */ /*! - \fn QRhiResource::Type QRhiResource::resourceType() const + \enum QRhiResource::Type + Specifies type of the resource. + + \value Buffer + \value Texture + \value Sampler + \value RenderBuffer + \value RenderPassDescriptor + \value SwapChainRenderTarget + \value TextureRenderTarget + \value ShaderResourceBindings + \value GraphicsPipeline + \value SwapChain + \value ComputePipeline + \value CommandBuffer + */ + +/*! + \fn virtual QRhiResource::Type QRhiResource::resourceType() const = 0 \return the type of the resource. */ @@ -2140,7 +3226,7 @@ QRhiResource::~QRhiResource() } /*! - \fn void QRhiResource::destroy() + \fn virtual void QRhiResource::destroy() = 0 Releases (or requests deferred releasing of) the underlying native graphics resources. Safe to call multiple times, subsequent invocations will be a @@ -2154,7 +3240,7 @@ QRhiResource::~QRhiResource() released until the frame is submitted by QRhi::endFrame(). The QRhiResource destructor also performs the same task, so calling this - function is not necessary before destroying a QRhiResource. + function is not necessary before deleting a QRhiResource. \sa deleteLater() */ @@ -2170,6 +3256,31 @@ QRhiResource::~QRhiResource() If the QRhi that created this object is already destroyed, the object is deleted immediately. + Using deleteLater() can be a useful convenience in many cases, and it + complements the low-level guarantee (that the underlying native graphics + objects are never destroyed until it is safe to do so and it is known for + sure that they are not used by the GPU in an still in-flight frame), by + offering a way to make sure the C++ object instances (of QRhiBuffer, + QRhiTexture, etc.) themselves also stay valid until the end of the current + frame. + + The following example shows a convenient way of creating a throwaway buffer + that is only used in one frame and gets automatically released in + endFrame(). (when it comes to the underlying native buffer(s), the usual + guarantee applies: the QRhi backend defers the releasing of those until it + is guaranteed that the frame in which the buffer is accessed by the GPU has + completed) + + \code + rhi->beginFrame(swapchain); + QRhiBuffer *buf = rhi->newBuffer(QRhiBuffer::Immutable, QRhiBuffer::VertexBuffer, 256); + buf->deleteLater(); // ! + u = rhi->nextResourceUpdateBatch(); + u->uploadStaticBuffer(buf, data); + // ... draw with buf + rhi->endFrame(); + \endcode + \sa destroy() */ void QRhiResource::deleteLater() @@ -2191,7 +3302,7 @@ QByteArray QRhiResource::name() const /*! Sets a \a name for the object. - This has two uses: to get descriptive names for the native graphics + This allows getting descriptive names for the native graphics resources visible in graphics debugging tools, such as \l{https://renderdoc.org/}{RenderDoc} and \l{https://developer.apple.com/xcode/}{XCode}. @@ -2237,9 +3348,129 @@ QRhi *QRhiResource::rhi() const /*! \class QRhiBuffer - \internal \inmodule QtGui + \since 6.6 \brief Vertex, index, or uniform (constant) buffer resource. + + \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 + a \c VkBuffer or \c MTLBuffer). With some graphics APIs and backends + certain types of buffers may not use a native buffer object at all (e.g. + OpenGL if uniform buffer objects are not used), but this is transparent to + the user of the QRhiBuffer API. Similarly, the fact that some types of + buffers may use two or three native buffers underneath, in order to allow + efficient per-frame content update without stalling the GPU pipeline, is + mostly invisible to the applications and libraries. + + A QRhiBuffer instance is always created by calling + \l{QRhi::newBuffer()}{the QRhi's newBuffer() function}. This creates no + native graphics resources. To do that, call create() after setting the + appropriate options, such as the type, usage flags, size, although in most cases these + are already set based on the arguments passed to + \l{QRhi::newBuffer()}{newBuffer()}. + + \section2 Example usage + + To create a uniform buffer for a shader where the GLSL uniform block + contains a single \c mat4 member, and update the contents: + + \code + QRhiBuffer *ubuf = rhi->newBuffer(QRhiBuffer::Dynamic, QRhiBuffer::UniformBuffer, 64); + if (!ubuf->create()) { error(); } + QRhiResourceUpdateBatch *batch = rhi->nextResourceUpdateBatch(); + QMatrix4x4 mvp; + // ... set up the modelview-projection matrix + batch->updateDynamicBuffer(ubuf, 0, 64, mvp.constData()); + // ... + commandBuffer->resourceUpdate(batch); // or, alternatively, pass 'batch' to a beginPass() call + \endcode + + An example of creating a buffer with vertex data: + + \code + const float vertices[] = { -1.0f, -1.0f, 1.0f, -1.0f, 0.0f, 1.0f }; + QRhiBuffer *vbuf = rhi->newBuffer(QRhiBuffer::Immutable, QRhiBuffer::VertexBuffer, sizeof(vertices)); + if (!vbuf->create()) { error(); } + QRhiResourceUpdateBatch *batch = rhi->nextResourceUpdateBatch(); + batch->uploadStaticBuffer(vbuf, vertices); + // ... + commandBuffer->resourceUpdate(batch); // or, alternatively, pass 'batch' to a beginPass() call + \endcode + + An index buffer: + + \code + static const quint16 indices[] = { 0, 1, 2 }; + QRhiBuffer *ibuf = rhi->newBuffer(QRhiBuffer::Immutable, QRhiBuffer::IndexBuffer, sizeof(indices)); + if (!ibuf->create()) { error(); } + QRhiResourceUpdateBatch *batch = rhi->nextResourceUpdateBatch(); + batch->uploadStaticBuffer(ibuf, indices); + // ... + commandBuffer->resourceUpdate(batch); // or, alternatively, pass 'batch' to a beginPass() call + \endcode + + \section2 Common patterns + + A call to create() destroys any existing native resources if create() was + successfully called before. If those native resources are still in use by + an in-flight frame (i.e., there's a chance they are still read by the GPU), + the destroying of those resources is deferred automatically. Thus a very + common and convenient pattern to safely increase the size of an already + initialized buffer is the following. In practice this drops and creates a + whole new set of native resources underneath, so it is not necessarily a + cheap operation, but is more convenient and still faster than the + alternatives, because by not destroying the \c buf object itself, all + references to it stay valid in other data structures (e.g., in any + QRhiShaderResourceBinding the QRhiBuffer is referenced from). + + \code + if (buf->size() < newSize) { + buf->setSize(newSize); + if (!buf->create()) { error(); } + } + // continue using buf, fill it with new data + \endcode + + When working with uniform buffers, it will sometimes be necessary to + combine data for multiple draw calls into a single buffer for efficiency + reasons. Be aware of the aligment requirements: with some graphics APIs + offsets for a uniform buffer must be aligned to 256 bytes. This applies + both to QRhiShaderResourceBinding and to the dynamic offsets passed to + \l{QRhiCommandBuffer::setShaderResources()}{setShaderResources()}. Use the + \l{QRhi::ubufAlignment()}{ubufAlignment()} and + \l{QRhi::ubufAligned()}{ubufAligned()} functions to create portable code. + As an example, the following is an outline for issuing multiple (\c N) draw + calls with the same pipeline and geometry, but with a different data in the + uniform buffers exposed at binding point 0. This assumes the buffer is + exposed via + \l{QRhiShaderResourceBinding::uniformBufferWithDynamicOffset()}{uniformBufferWithDynamicOffset()} + which allows passing a QRhiCommandBuffer::DynamicOffset list to + \l{QRhiCommandBuffer::setShaderResources()}{setShaderResources()}. + + \code + const int N = 2; + const int UB_SIZE = 64 + 4; // assuming a uniform block with { mat4 matrix; float opacity; } + const int ONE_UBUF_SIZE = rhi->ubufAligned(UB_SIZE); + const int TOTAL_UBUF_SIZE = N * ONE_UBUF_SIZE; + QRhiBuffer *ubuf = rhi->newBuffer(QRhiBuffer::Dynamic, QRhiBuffer::UniformBuffer, TOTAL_UBUF_SIZE); + if (!ubuf->create()) { error(); } + 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); + } + // ... + // beginPass(), set pipeline, etc., and then: + for (int i = 0; i < N; ++i) { + QRhiCommandBuffer::DynamicOffset dynOfs[] = { { 0, i * ONE_UBUF_SIZE } }; + cb->setShaderResources(srb, 1, dynOfs); + cb->draw(36); + } + \endcode + + \sa QRhiResourceUpdateBatch, QRhi, QRhiCommandBuffer */ /*! @@ -2275,42 +3506,29 @@ QRhi *QRhiResource::rhi() const Flag values to specify how the buffer is going to be used. \value VertexBuffer Vertex buffer. This allows the QRhiBuffer to be used in - \l{setVertexInput()}{QRhiCommandBuffer::setVertexInput()}. + \l{QRhiCommandBuffer::setVertexInput()}{setVertexInput()}. \value IndexBuffer Index buffer. This allows the QRhiBuffer to be used in - \l{setVertexInput()}{QRhiCommandBuffer::setVertexInput()}. + \l{QRhiCommandBuffer::setVertexInput()}{setVertexInput()}. \value UniformBuffer Uniform buffer (also called constant buffer). This allows the QRhiBuffer to be used in combination with - \l{UniformBuffer}{QRhiShaderResourceBinding::UniformBuffer}. When + \l{QRhiShaderResourceBinding::UniformBuffer}{UniformBuffer}. When \l{QRhi::NonDynamicUniformBuffers}{NonDynamicUniformBuffers} is reported as not supported, this usage can only be combined with the type Dynamic. \value StorageBuffer Storage buffer. This allows the QRhiBuffer to be used - in combination with \l{BufferLoad}{QRhiShaderResourceBinding::BufferLoad}, - \l{BufferStore}{QRhiShaderResourceBinding::BufferStore}, or - \l{BufferLoadStore}{QRhiShaderResourceBinding::BufferLoadStore}. This usage + in combination with \l{QRhiShaderResourceBinding::BufferLoad}{BufferLoad}, + \l{QRhiShaderResourceBinding::BufferStore}{BufferStore}, or + \l{QRhiShaderResourceBinding::BufferLoadStore}{BufferLoadStore}. This usage can only be combined with the types Immutable or Static, and is only available when the \l{QRhi::Compute}{Compute feature} is reported as supported. */ -/*! - \fn void QRhiBuffer::setSize(int sz) - - Sets the size of the buffer in bytes. The size is normally specified in - QRhi::newBuffer() so this function is only used when the size has to be - changed. As with other setters, the size only takes effect when calling - create(), and for already created buffers this involves releasing the previous - native resource and creating new ones under the hood. - - Backends may choose to allocate buffers bigger than \a sz in order to - fulfill alignment requirements. This is hidden from the applications and - size() will always report the size requested in \a sz. - */ - /*! \class QRhiBuffer::NativeBuffer + \inmodule QtGui \brief Contains information about the underlying native resources of a buffer. */ @@ -2322,10 +3540,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). */ /*! @@ -2361,7 +3582,7 @@ QRhiResource::Type QRhiBuffer::resourceType() const } /*! - \fn bool QRhiBuffer::create() + \fn virtual bool QRhiBuffer::create() = 0 Creates the corresponding native graphics resources. If there are already resources present due to an earlier create() with no corresponding @@ -2371,6 +3592,50 @@ QRhiResource::Type QRhiBuffer::resourceType() const Regardless of the return value, calling destroy() is always safe. */ +/*! + \fn QRhiBuffer::Type QRhiBuffer::type() const + \return the buffer type. + */ + +/*! + \fn void QRhiBuffer::setType(Type t) + Sets the buffer's type to \a t. + */ + +/*! + \fn QRhiBuffer::UsageFlags QRhiBuffer::usage() const + \return the buffer's usage flags. + */ + +/*! + \fn void QRhiBuffer::setUsage(UsageFlags u) + Sets the buffer's usage flags to \a u. + */ + +/*! + \fn quint32 QRhiBuffer::size() const + + \return the buffer's size in bytes. + + This is always the value that was passed to setSize() or QRhi::newBuffer(). + Internally, the native buffers may be bigger if that is required by the + underlying graphics API. + */ + +/*! + \fn void QRhiBuffer::setSize(quint32 sz) + + Sets the size of the buffer in bytes. The size is normally specified in + QRhi::newBuffer() so this function is only used when the size has to be + changed. As with other setters, the size only takes effect when calling + create(), and for already created buffers this involves releasing the previous + native resource and creating new ones under the hood. + + Backends may choose to allocate buffers bigger than \a sz in order to + fulfill alignment requirements. This is hidden from the applications and + size() will always report the size requested in \a sz. + */ + /*! \return the underlying native resources for this buffer. The returned value will be empty if exposing the underlying native resources is not supported by @@ -2459,18 +3724,18 @@ void QRhiBuffer::endFullDynamicBufferUpdateForCurrentFrame() /*! \class QRhiRenderBuffer - \internal \inmodule QtGui + \since 6.6 \brief Renderbuffer resource. Renderbuffers cannot be sampled or read but have some benefits over textures in some cases: - A DepthStencil renderbuffer may be lazily allocated and be backed by + A \l DepthStencil renderbuffer may be lazily allocated and be backed by transient memory with some APIs. On some platforms this may mean the depth/stencil buffer uses no physical backing at all. - Color renderbuffers are useful since QRhi::MultisampleRenderBuffer may be + \l Color renderbuffers are useful since QRhi::MultisampleRenderBuffer may be supported even when QRhi::MultisampleTexture is not. How the renderbuffer is implemented by a backend is not exposed to the @@ -2484,6 +3749,9 @@ void QRhiBuffer::endFullDynamicBufferUpdateForCurrentFrame() QRhi provides automatic sizing behavior to match the color buffers, which means calling setPixelSize() and create() are not necessary for such renderbuffers. + + \note This is a RHI API with limited compatibility guarantees, see \l QRhi + for details. */ /*! @@ -2494,6 +3762,22 @@ void QRhiBuffer::endFullDynamicBufferUpdateForCurrentFrame() \value Color Color */ +/*! + \struct QRhiRenderBuffer::NativeRenderBuffer + \inmodule QtGui + \brief Wraps a native renderbuffer object. + */ + +/*! + \variable QRhiRenderBuffer::NativeRenderBuffer::object + \brief 64-bit integer containing the native object handle. + + Used with QRhiRenderBuffer::createFrom(). + + With OpenGL the native handle is a GLuint value. \c object is expected to + be a valid OpenGL renderbuffer object ID. + */ + /*! \enum QRhiRenderBuffer::Flag Flag values for flags() and setFlags() @@ -2531,7 +3815,7 @@ QRhiResource::Type QRhiRenderBuffer::resourceType() const } /*! - \fn bool QRhiRenderBuffer::create() + \fn virtual bool QRhiRenderBuffer::create() = 0 Creates the corresponding native graphics resources. If there are already resources present due to an earlier create() with no corresponding @@ -2580,16 +3864,121 @@ bool QRhiRenderBuffer::createFrom(NativeRenderBuffer src) } /*! - \fn QRhiTexture::Format QRhiRenderBuffer::backingFormat() const + \fn QRhiRenderBuffer::Type QRhiRenderBuffer::type() const + \return the renderbuffer type. + */ + +/*! + \fn void QRhiRenderBuffer::setType(Type t) + Sets the type to \a t. + */ + +/*! + \fn QSize QRhiRenderBuffer::pixelSize() const + \return the pixel size. + */ + +/*! + \fn void QRhiRenderBuffer::setPixelSize(const QSize &sz) + Sets the size (in pixels) to \a sz. + */ + +/*! + \fn int QRhiRenderBuffer::sampleCount() const + \return the sample count. 1 means no multisample antialiasing. + */ + +/*! + \fn void QRhiRenderBuffer::setSampleCount(int s) + Sets the sample count to \a s. + */ + +/*! + \fn QRhiRenderBuffer::Flags QRhiRenderBuffer::flags() const + \return the flags. + */ + +/*! + \fn void QRhiRenderBuffer::setFlags(Flags f) + Sets the flags to \a f. + */ + +/*! + \fn virtual QRhiTexture::Format QRhiRenderBuffer::backingFormat() const = 0 \internal */ /*! \class QRhiTexture - \internal \inmodule QtGui + \since 6.6 \brief Texture resource. + + A QRhiTexture encapsulates a native texture object, such as a \c VkImage or + \c MTLTexture. + + A QRhiTexture instance is always created by calling + \l{QRhi::newTexture()}{the QRhi's newTexture() function}. This creates no + native graphics resources. To do that, call create() after setting the + appropriate options, such as the format and size, although in most cases + these are already set based on the arguments passed to + \l{QRhi::newTexture()}{newTexture()}. + + Setting the \l{QRhiTexture::Flags}{flags} correctly is essential, otherwise + various errors can occur depending on the underlying QRhi backend and + graphics API. For example, when a texture will be rendered into from a + render pass via QRhiTextureRenderTarget, the texture must be created with + the \l RenderTarget flag set. Similarly, when the texture is going to be + \l{QRhiResourceUpdateBatch::readBackTexture()}{read back}, the \l + UsedAsTransferSource flag must be set upfront. Mipmapped textures must have + the MipMapped flag set. And so on. It is not possible to change the flags + once create() has succeeded. To release the existing and create a new + native texture object with the changed settings, call the setters and call + create() again. This then might be a potentially expensive operation. + + \section2 Example usage + + To create a 2D texture with a size of 512x512 pixels and set its contents to all green: + + \code + QRhiTexture *texture = rhi->newTexture(QRhiTexture::RGBA8, QSize(512, 512)); + if (!texture->create()) { error(); } + QRhiResourceUpdateBatch *batch = rhi->nextResourceUpdateBatch(); + QImage image(512, 512, QImage::Format_RGBA8888); + image.fill(Qt::green); + batch->uploadTexture(texture, image); + // ... + commandBuffer->resourceUpdate(batch); // or, alternatively, pass 'batch' to a beginPass() call + \endcode + + \section2 Common patterns + + A call to create() destroys any existing native resources if create() was + successfully called before. If those native resources are still in use by + an in-flight frame (i.e., there's a chance they are still read by the GPU), + the destroying of those resources is deferred automatically. Thus a very + common and convenient pattern to safely change the size of an already + existing texture is the following. In practice this drops and creates a + whole new native texture resource underneath, so it is not necessarily a + cheap operation, but is more convenient and still faster than the + alternatives, because by not destroying the \c texture object itself, all + references to it stay valid in other data structures (e.g., in any + QShaderResourceBinding the QRhiTexture is referenced from). + + \code + // determine newSize, e.g. based on the swapchain's output size or other factors + if (texture->pixelSize() != newSize) { + texture->setPixelSize(newSize); + if (!texture->create()) { error(); } + } + // continue using texture, fill it with new data + \endcode + + \note This is a RHI API with limited compatibility guarantees, see \l QRhi + for details. + + \sa QRhiResourceUpdateBatch, QRhi, QRhiTextureRenderTarget */ /*! @@ -2656,6 +4045,14 @@ bool QRhiRenderBuffer::createFrom(NativeRenderBuffer src) QRhi::TextureArrays feature. When rendering into, or uploading data to a texture array, the \c layer specified in the render target's color attachment or the upload description selects a single element in the array. + + \value OneDimensional The texture is a 1D texture. Such textures can be + created by passing a 0 height and depth to QRhi::newTexture(). Note that + there can be limitations on one dimensional textures depending on the + underlying graphics API. For example, rendering to them or using them with + mipmap-based filtering may be unsupported. This is indicated by the + QRhi::OneDimensionalTextures and QRhi::OneDimensionalTextureMipmaps + feature flags. */ /*! @@ -2689,7 +4086,7 @@ bool QRhiRenderBuffer::createFrom(NativeRenderBuffer src) \value R32F One component, 32-bit float. - \value RGBA10A2 Four components, unsigned normalized 10 bit R, G, and B, + \value RGB10A2 Four components, unsigned normalized 10 bit R, G, and B, 2-bit alpha. This is a packed format so native endianness applies. Note that there is no BGR10A2. This is because RGB10A2 maps to DXGI_FORMAT_R10G10B10A2_UNORM with D3D, MTLPixelFormatRGB10A2Unorm with @@ -2735,7 +4132,8 @@ bool QRhiRenderBuffer::createFrom(NativeRenderBuffer src) */ /*! - \class QRhiTexture::NativeTexture + \struct QRhiTexture::NativeTexture + \inmodule QtGui \brief Contains information about the underlying native resources of a texture. */ @@ -2744,9 +4142,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. */ /*! @@ -2776,7 +4175,7 @@ QRhiResource::Type QRhiTexture::resourceType() const } /*! - \fn bool QRhiTexture::create() + \fn virtual bool QRhiTexture::create() = 0 Creates the corresponding native graphics resources. If there are already resources present due to an earlier create() with no corresponding @@ -2799,13 +4198,16 @@ QRhiTexture::NativeTexture QRhiTexture::nativeTexture() } /*! - Similar to create() except that no new native textures are created. Instead, - the native texture resources specified by \a src is used. + Similar to create(), except that no new native textures are created. + Instead, the native texture resources specified by \a src is used. This allows importing an existing native texture object (which must belong to the same device or sharing context, depending on the graphics API) from an external graphics engine. + \return true if the specified existing native texture object has been + successfully wrapped as a non-owning QRhiTexture. + \note format(), pixelSize(), sampleCount(), and flags() must still be set correctly. Passing incorrect sizes and other values to QRhi::newTexture() and then following it with a createFrom() expecting that the native texture @@ -2832,7 +4234,7 @@ bool QRhiTexture::createFrom(QRhiTexture::NativeTexture src) /*! With some graphics APIs, such as Vulkan, integrating custom rendering code that uses the graphics API directly needs special care when it comes to - image layouts. This function allows communicating the expected layout the + image layouts. This function allows communicating the expected \a layout the image backing the QRhiTexture is in after the native rendering commands. For example, consider rendering into a QRhiTexture's VkImage directly with @@ -2848,17 +4250,125 @@ bool QRhiTexture::createFrom(QRhiTexture::NativeTexture src) This function has no effect with QRhi backends where the underlying graphics API does not expose a concept of image layouts. + + \note With Vulkan \a layout is a \c VkImageLayout. With Direct 3D 12 \a + layout is a value composed of the bits from \c D3D12_RESOURCE_STATES. */ void QRhiTexture::setNativeLayout(int layout) { Q_UNUSED(layout); } +/*! + \fn QRhiTexture::Format QRhiTexture::format() const + \return the texture format. + */ + +/*! + \fn void QRhiTexture::setFormat(QRhiTexture::Format fmt) + + Sets the requested texture format to \a fmt. + + \note The value set is only taken into account upon the next call to + create(), i.e. when the underlying graphics resource are (re)created. + Setting a new value is futile otherwise and must be avoided since it can + lead to inconsistent state. + */ + +/*! + \fn QSize QRhiTexture::pixelSize() const + \return the size in pixels. + */ + +/*! + \fn void QRhiTexture::setPixelSize(const QSize &sz) + + Sets the texture size, specified in pixels, to \a sz. + + \note The value set is only taken into account upon the next call to + create(), i.e. when the underlying graphics resource are (re)created. + Setting a new value is futile otherwise and must be avoided since it can + lead to inconsistent state. The same applies to all other setters as well. + */ + +/*! + \fn int QRhiTexture::depth() const + \return the depth for 3D textures. + */ + +/*! + \fn void QRhiTexture::setDepth(int depth) + Sets the \a depth for a 3D texture. + */ + +/*! + \fn int QRhiTexture::arraySize() const + \return the texture array size. + */ + +/*! + \fn void QRhiTexture::setArraySize(int arraySize) + Sets the texture \a arraySize. + */ + +/*! + \fn int QRhiTexture::arrayRangeStart() const + + \return the first array layer when setArrayRange() was called. + + \sa setArrayRange() + */ + +/*! + \fn int QRhiTexture::arrayRangeLength() const + + \return the exposed array range size when setArrayRange() was called. + + \sa setArrayRange() +*/ + +/*! + \fn void QRhiTexture::setArrayRange(int startIndex, int count) + + Normally all array layers are exposed and it is up to the shader to select + the layer via the third coordinate passed to the \c{texture()} GLSL + function when sampling the \c sampler2DArray. When QRhi::TextureArrayRange + is reported as supported, calling setArrayRange() before create() or + createFrom() requests selecting only the specified range, \a count elements + starting from \a startIndex. The shader logic can then be written with this + in mind. + + \sa QRhi::TextureArrayRange + */ + +/*! + \fn Flags QRhiTexture::flags() const + \return the texture flags. + */ + +/*! + \fn void QRhiTexture::setFlags(Flags f) + Sets the texture flags to \a f. + */ + +/*! + \fn int QRhiTexture::sampleCount() const + \return the sample count. 1 means no multisample antialiasing. + */ + +/*! + \fn void QRhiTexture::setSampleCount(int s) + Sets the sample count to \a s. + */ + /*! \class QRhiSampler - \internal \inmodule QtGui + \since 6.6 \brief Sampler resource. + + \note This is a RHI API with limited compatibility guarantees, see \l QRhi + for details. */ /*! @@ -2914,15 +4424,92 @@ QRhiResource::Type QRhiSampler::resourceType() const return Sampler; } +/*! + \fn QRhiSampler::Filter QRhiSampler::magFilter() const + \return the magnification filter mode. + */ + +/*! + \fn void QRhiSampler::setMagFilter(Filter f) + Sets the magnification filter mode to \a f. + */ + +/*! + \fn QRhiSampler::Filter QRhiSampler::minFilter() const + \return the minification filter mode. + */ + +/*! + \fn void QRhiSampler::setMinFilter(Filter f) + Sets the minification filter mode to \a f. + */ + +/*! + \fn QRhiSampler::Filter QRhiSampler::mipmapMode() const + \return the mipmap filter mode. + */ + +/*! + \fn void QRhiSampler::setMipmapMode(Filter f) + + Sets the mipmap filter mode to \a f. + + Leave this set to None when the texture has no mip levels, or when the mip + levels are not to be taken into account. + */ + +/*! + \fn QRhiSampler::AddressMode QRhiSampler::addressU() const + \return the horizontal wrap mode. + */ + +/*! + \fn void QRhiSampler::setAddressU(AddressMode mode) + Sets the horizontal wrap \a mode. + */ + +/*! + \fn QRhiSampler::AddressMode QRhiSampler::addressV() const + \return the vertical wrap mode. + */ + +/*! + \fn void QRhiSampler::setAddressV(AddressMode mode) + Sets the vertical wrap \a mode. + */ + +/*! + \fn QRhiSampler::AddressMode QRhiSampler::addressW() const + \return the depth wrap mode. + */ + +/*! + \fn void QRhiSampler::setAddressW(AddressMode mode) + Sets the depth wrap \a mode. + */ + +/*! + \fn QRhiSampler::CompareOp QRhiSampler::textureCompareOp() const + \return the texture comparison function. + */ + +/*! + \fn void QRhiSampler::setTextureCompareOp(CompareOp op) + Sets the texture comparison function \a op. + */ + /*! \class QRhiRenderPassDescriptor - \internal \inmodule QtGui + \since 6.6 \brief Render pass resource. A render pass, if such a concept exists in the underlying graphics API, is a collection of attachments (color, depth, stencil) and describes how those attachments are used. + + \note This is a RHI API with limited compatibility guarantees, see \l QRhi + for details. */ /*! @@ -2942,7 +4529,7 @@ QRhiResource::Type QRhiRenderPassDescriptor::resourceType() const } /*! - \fn bool QRhiRenderPassDescriptor::isCompatible(const QRhiRenderPassDescriptor *other) const + \fn virtual bool QRhiRenderPassDescriptor::isCompatible(const QRhiRenderPassDescriptor *other) const = 0 \return true if the \a other QRhiRenderPassDescriptor is compatible with this one, meaning \c this and \a other can be used interchangebly in @@ -2977,7 +4564,7 @@ QRhiResource::Type QRhiRenderPassDescriptor::resourceType() const */ /*! - \fn QRhiRenderPassDescriptor *QRhiRenderPassDescriptor::newCompatibleRenderPassDescriptor() const + \fn virtual QRhiRenderPassDescriptor *QRhiRenderPassDescriptor::newCompatibleRenderPassDescriptor() const = 0 \return a new QRhiRenderPassDescriptor that is \l{isCompatible()}{compatible} with this one. @@ -2998,7 +4585,7 @@ QRhiResource::Type QRhiRenderPassDescriptor::resourceType() const */ /*! - \fn QVector QRhiRenderPassDescriptor::serializedFormat() const + \fn virtual QVector QRhiRenderPassDescriptor::serializedFormat() const = 0 \return a vector of integers containing an opaque blob describing the data relevant for \l{isCompatible()}{compatibility}. @@ -3029,10 +4616,20 @@ const QRhiNativeHandles *QRhiRenderPassDescriptor::nativeHandles() /*! \class QRhiRenderTarget - \internal \inmodule QtGui + \since 6.6 \brief Represents an onscreen (swapchain) or offscreen (texture) render target. + Applications do not create an instance of this class directly. Rather, it + is the subclass QRhiTextureRenderTarget that is instantiable by clients of + the API via \l{QRhi::newTextureRenderTarget()}{newTextureRenderTarget()}. + The other subclass is QRhiSwapChainRenderTarget, which is the type + QRhiSwapChain returns when calling + \l{QRhiSwapChain::currentFrameRenderTarget()}{currentFrameRenderTarget()}. + + \note This is a RHI API with limited compatibility guarantees, see \l QRhi + for details. + \sa QRhiSwapChainRenderTarget, QRhiTextureRenderTarget */ @@ -3045,7 +4642,7 @@ QRhiRenderTarget::QRhiRenderTarget(QRhiImplementation *rhi) } /*! - \fn QSize QRhiRenderTarget::pixelSize() const + \fn virtual QSize QRhiRenderTarget::pixelSize() const = 0 \return the size in pixels. @@ -3063,7 +4660,7 @@ QRhiRenderTarget::QRhiRenderTarget(QRhiImplementation *rhi) */ /*! - \fn float QRhiRenderTarget::devicePixelRatio() const + \fn virtual float QRhiRenderTarget::devicePixelRatio() const = 0 \return the device pixel ratio. For QRhiTextureRenderTarget this is always 1. For targets retrieved from a QRhiSwapChain the value reflects the @@ -3071,6 +4668,25 @@ QRhiRenderTarget::QRhiRenderTarget(QRhiImplementation *rhi) QWindow. */ +/*! + \fn virtual int QRhiRenderTarget::sampleCount() const = 0 + + \return the sample count or 1 if multisample antialiasing is not relevant for + this render target. + */ + +/*! + \fn QRhiRenderPassDescriptor *QRhiRenderTarget::renderPassDescriptor() const + + \return the associated QRhiRenderPassDescriptor. + */ + +/*! + \fn void QRhiRenderTarget::setRenderPassDescriptor(QRhiRenderPassDescriptor *desc) + + Sets the QRhiRenderPassDescriptor \a desc for use with this render target. + */ + /*! \internal */ @@ -3082,14 +4698,17 @@ QRhiSwapChainRenderTarget::QRhiSwapChainRenderTarget(QRhiImplementation *rhi, QR /*! \class QRhiSwapChainRenderTarget - \internal \inmodule QtGui + \since 6.6 \brief Swapchain render target resource. When targeting the color buffers of a swapchain, active render target is a QRhiSwapChainRenderTarget. This is what QRhiSwapChain::currentFrameRenderTarget() returns. + \note This is a RHI API with limited compatibility guarantees, see \l QRhi + for details. + \sa QRhiSwapChain */ @@ -3109,8 +4728,8 @@ QRhiResource::Type QRhiSwapChainRenderTarget::resourceType() const /*! \class QRhiTextureRenderTarget - \internal \inmodule QtGui + \since 6.6 \brief Texture render target resource. A texture render target allows rendering into one or more textures, @@ -3126,15 +4745,18 @@ QRhiResource::Type QRhiSwapChainRenderTarget::resourceType() const The simplest example of creating a render target with a texture as its single color attachment: - \badcode - texture = rhi->newTexture(QRhiTexture::RGBA8, size, 1, QRhiTexture::RenderTarget); + \code + QRhiTexture *texture = rhi->newTexture(QRhiTexture::RGBA8, size, 1, QRhiTexture::RenderTarget); texture->create(); - rt = rhi->newTextureRenderTarget({ texture }); + QRhiTextureRenderTarget *rt = rhi->newTextureRenderTarget({ texture }); rp = rt->newCompatibleRenderPassDescriptor(); rt->setRenderPassDescriptor(rt); rt->create(); // rt can now be used with beginPass() \endcode + + \note This is a RHI API with limited compatibility guarantees, see \l QRhi + for details. */ /*! @@ -3180,7 +4802,7 @@ QRhiResource::Type QRhiTextureRenderTarget::resourceType() const } /*! - \fn QRhiRenderPassDescriptor *QRhiTextureRenderTarget::newCompatibleRenderPassDescriptor() + \fn virtual QRhiRenderPassDescriptor *QRhiTextureRenderTarget::newCompatibleRenderPassDescriptor() = 0 \return a new QRhiRenderPassDescriptor that is compatible with this render target. @@ -3190,7 +4812,8 @@ QRhiResource::Type QRhiTextureRenderTarget::resourceType() const QRhiGraphicsPipeline::setRenderPassDescriptor(). A render pass descriptor describes the attachments (color, depth/stencil) and the load/store behavior that can be affected by flags(). A QRhiGraphicsPipeline can only - be used in combination with a render target that has the same + be used in combination with a render target that has a + \l{QRhiRenderPassDescriptor::isCompatible()}{compatible} QRhiRenderPassDescriptor set. Two QRhiTextureRenderTarget instances can share the same render pass @@ -3206,7 +4829,7 @@ QRhiResource::Type QRhiTextureRenderTarget::resourceType() const */ /*! - \fn bool QRhiTextureRenderTarget::create() + \fn virtual bool QRhiTextureRenderTarget::create() = 0 Creates the corresponding native graphics resources. If there are already resources present due to an earlier create() with no corresponding @@ -3229,10 +4852,30 @@ QRhiResource::Type QRhiTextureRenderTarget::resourceType() const Regardless of the return value, calling destroy() is always safe. */ +/*! + \fn QRhiTextureRenderTargetDescription QRhiTextureRenderTarget::description() const + \return the render target description. + */ + +/*! + \fn void QRhiTextureRenderTarget::setDescription(const QRhiTextureRenderTargetDescription &desc) + Sets the render target description \a desc. + */ + +/*! + \fn QRhiTextureRenderTarget::Flags QRhiTextureRenderTarget::flags() const + \return the currently set flags. + */ + +/*! + \fn void QRhiTextureRenderTarget::setFlags(Flags f) + Sets the flags to \a f. + */ + /*! \class QRhiShaderResourceBindings - \internal \inmodule QtGui + \since 6.6 \brief Encapsulates resources for making buffer, texture, sampler resources visible to shaders. A QRhiShaderResourceBindings is a collection of QRhiShaderResourceBinding @@ -3253,19 +4896,19 @@ QRhiResource::Type QRhiTextureRenderTarget::resourceType() const QRhiShaderResourceBindings could be created and then passed to QRhiGraphicsPipeline::setShaderResourceBindings(): - \badcode - srb = rhi->newShaderResourceBindings(); + \code + QRhiShaderResourceBindings *srb = rhi->newShaderResourceBindings(); srb->setBindings({ QRhiShaderResourceBinding::uniformBuffer(0, QRhiShaderResourceBinding::VertexStage | QRhiShaderResourceBinding::FragmentStage, ubuf), QRhiShaderResourceBinding::sampledTexture(1, QRhiShaderResourceBinding::FragmentStage, texture, sampler) }); srb->create(); - ... - ps = rhi->newGraphicsPipeline(); - ... + // ... + QRhiGraphicsPipeline *ps = rhi->newGraphicsPipeline(); + // ... ps->setShaderResourceBindings(srb); ps->create(); - ... + // ... cb->setGraphicsPipeline(ps); cb->setShaderResources(); // binds srb \endcode @@ -3284,14 +4927,17 @@ QRhiResource::Type QRhiTextureRenderTarget::resourceType() const srb argument. As long as the layouts (so the number of bindings and the binding points) match between two QRhiShaderResourceBindings, they can both be used with the same pipeline, assuming the pipeline was created with one of - them in the first place. + them in the first place. See isLayoutCompatible() for more details. - \badcode - srb2 = rhi->newShaderResourceBindings(); - ... + \code + QRhiShaderResourceBindings *srb2 = rhi->newShaderResourceBindings(); + // ... cb->setGraphicsPipeline(ps); cb->setShaderResources(srb2); // binds srb2 \endcode + + \note This is a RHI API with limited compatibility guarantees, see \l QRhi + for details. */ /*! @@ -3357,7 +5003,7 @@ bool QRhiShaderResourceBindings::isLayoutCompatible(const QRhiShaderResourceBind \l{isLayoutCompatible()}{layout compatibility tests}. Given two objects \c srb1 and \c srb2, if the data returned from this - function is identical, then \c{srb1->isLayoutCompatible(srb2), and vice + function is identical, then \c{srb1->isLayoutCompatible(srb2)}, and vice versa hold true as well. \note The returned data is meant to be used for storing in memory and @@ -3374,22 +5020,55 @@ void QRhiImplementation::updateLayoutDesc(QRhiShaderResourceBindings *srb) srb->m_layoutDesc.clear(); auto layoutDescAppender = std::back_inserter(srb->m_layoutDesc); for (const QRhiShaderResourceBinding &b : std::as_const(srb->m_bindings)) { - const QRhiShaderResourceBinding::Data *d = b.data(); + const QRhiShaderResourceBinding::Data *d = &b.d; srb->m_layoutDescHash ^= uint(d->binding) ^ uint(d->stage) ^ uint(d->type) ^ uint(d->arraySize()); layoutDescAppender = d->serialize(layoutDescAppender); } } +/*! + \fn void QRhiShaderResourceBindings::setBindings(std::initializer_list list) + Sets the \a list of bindings. + */ + +/*! + \fn template void QRhiShaderResourceBindings::setBindings(InputIterator first, InputIterator last) + Sets the list of bindings from the iterators \a first and \a last. + */ + +/*! + \fn const QRhiShaderResourceBinding *QRhiShaderResourceBindings::cbeginBindings() const + \return a const iterator pointing to the first item in the binding list. + */ + +/*! + \fn const QRhiShaderResourceBinding *QRhiShaderResourceBindings::cendBindings() const + \return a const iterator pointing just after the last item in the binding list. + */ + +/*! + \fn const QRhiShaderResourceBinding *QRhiShaderResourceBindings::bindingAt(qsizetype index) const + \return the binding at the specified \a index. + */ + +/*! + \fn qsizetype QRhiShaderResourceBindings::bindingCount() const + \return the number of bindings. + */ + /*! \class QRhiShaderResourceBinding - \internal \inmodule QtGui + \since 6.6 \brief Describes the shader resource for a single binding point. A QRhiShaderResourceBinding cannot be constructed directly. Instead, use the static functions such as uniformBuffer() or sampledTexture() to get an instance. + + \note This is a RHI API with limited compatibility guarantees, see \l QRhi + for details. */ /*! @@ -3398,7 +5077,15 @@ void QRhiImplementation::updateLayoutDesc(QRhiShaderResourceBindings *srb) \value UniformBuffer Uniform buffer - \value SampledTexture Combined image sampler + \value SampledTexture Combined image sampler (a texture and sampler pair). + Even when the shading language associated with the underlying 3D API has no + support for this concept (e.g. D3D and HLSL), this is still supported + because the shader translation layer takes care of the appropriate + translation and remapping of binding points or shader registers. + + \value Texture Texture (separate) + + \value Sampler Sampler (separate) \value ImageLoad Image load (with GLSL this maps to doing imageLoad() on a single level - and either one or all layers - of a texture exposed to the @@ -3438,7 +5125,7 @@ void QRhiImplementation::updateLayoutDesc(QRhiShaderResourceBindings *srb) For example, \c a and \c b below are not equal, but are compatible layout-wise: - \badcode + \code auto a = QRhiShaderResourceBinding::uniformBuffer(0, QRhiShaderResourceBinding::VertexStage, buffer); auto b = QRhiShaderResourceBinding::uniformBuffer(0, QRhiShaderResourceBinding::VertexStage, someOtherBuffer, 256); \endcode @@ -3573,6 +5260,14 @@ QRhiShaderResourceBinding QRhiShaderResourceBinding::uniformBufferWithDynamicOff together with another, layout compatible QRhiShaderResourceBindings with resources present passed to QRhiCommandBuffer::setShaderResources(). + \note A shader may not be able to consume more than 16 textures/samplers, + depending on the underlying graphics API. This hard limit must be kept in + mind in renderer design. This does not apply to texture arrays which + consume a single binding point (shader register) and can contain 256-2048 + textures, depending on the underlying graphics API. Arrays of textures (see + sampledTextures()) are however no different in this regard than using the + same number of individual textures. + \sa sampledTextures() */ QRhiShaderResourceBinding QRhiShaderResourceBinding::sampledTexture( @@ -3658,6 +5353,14 @@ QRhiShaderResourceBinding QRhiShaderResourceBinding::sampledTextures( Vulkan-compatible GLSL code separate textures are declared as \c texture2D as opposed to \c sampler2D: \c{layout(binding = 1) uniform texture2D tex;} + \note A shader may not be able to consume more than 16 textures, depending + on the underlying graphics API. This hard limit must be kept in mind in + renderer design. This does not apply to texture arrays which consume a + single binding point (shader register) and can contain 256-2048 textures, + depending on the underlying graphics API. Arrays of textures (see + sampledTextures()) are however no different in this regard than using the + same number of individual textures. + \sa textures(), sampler() */ QRhiShaderResourceBinding QRhiShaderResourceBinding::texture(int binding, StageFlags stage, QRhiTexture *tex) @@ -3730,6 +5433,10 @@ QRhiShaderResourceBinding QRhiShaderResourceBinding::textures(int binding, Stage to sample the texture: \c{fragColor = texture(sampler2D(tex, samp), texcoord);}. + \note A shader may not be able to consume more than 16 samplers, depending + on the underlying graphics API. This hard limit must be kept in mind in + renderer design. + \sa texture() */ QRhiShaderResourceBinding QRhiShaderResourceBinding::sampler(int binding, StageFlags stage, QRhiSampler *sampler) @@ -4070,8 +5777,8 @@ QRhiShaderResourceBinding QRhiShaderResourceBinding::bufferLoadStore( */ bool operator==(const QRhiShaderResourceBinding &a, const QRhiShaderResourceBinding &b) noexcept { - const QRhiShaderResourceBinding::Data *da = a.data(); - const QRhiShaderResourceBinding::Data *db = b.data(); + const QRhiShaderResourceBinding::Data *da = QRhiImplementation::shaderResourceBindingData(a); + const QRhiShaderResourceBinding::Data *db = QRhiImplementation::shaderResourceBindingData(b); if (da == db) return true; @@ -4160,41 +5867,44 @@ bool operator!=(const QRhiShaderResourceBinding &a, const QRhiShaderResourceBind */ size_t qHash(const QRhiShaderResourceBinding &b, size_t seed) noexcept { - const QRhiShaderResourceBinding::Data *d = b.data(); - size_t h = uint(d->binding) ^ uint(d->stage) ^ uint(d->type) ^ seed; + const QRhiShaderResourceBinding::Data *d = QRhiImplementation::shaderResourceBindingData(b); + QtPrivate::QHashCombine hash; + seed = hash(seed, d->binding); + seed = hash(seed, d->stage); + seed = hash(seed, d->type); switch (d->type) { case QRhiShaderResourceBinding::UniformBuffer: - h ^= qHash(reinterpret_cast(d->u.ubuf.buf)); + seed = hash(seed, reinterpret_cast(d->u.ubuf.buf)); break; case QRhiShaderResourceBinding::SampledTexture: - h ^= qHash(reinterpret_cast(d->u.stex.texSamplers[0].tex)); - h ^= qHash(reinterpret_cast(d->u.stex.texSamplers[0].sampler)); + seed = hash(seed, reinterpret_cast(d->u.stex.texSamplers[0].tex)); + seed = hash(seed, reinterpret_cast(d->u.stex.texSamplers[0].sampler)); break; case QRhiShaderResourceBinding::Texture: - h ^= qHash(reinterpret_cast(d->u.stex.texSamplers[0].tex)); + seed = hash(seed, reinterpret_cast(d->u.stex.texSamplers[0].tex)); break; case QRhiShaderResourceBinding::Sampler: - h ^= qHash(reinterpret_cast(d->u.stex.texSamplers[0].sampler)); + seed = hash(seed, reinterpret_cast(d->u.stex.texSamplers[0].sampler)); break; case QRhiShaderResourceBinding::ImageLoad: case QRhiShaderResourceBinding::ImageStore: case QRhiShaderResourceBinding::ImageLoadStore: - h ^= qHash(reinterpret_cast(d->u.simage.tex)); + seed = hash(seed, reinterpret_cast(d->u.simage.tex)); break; case QRhiShaderResourceBinding::BufferLoad: case QRhiShaderResourceBinding::BufferStore: case QRhiShaderResourceBinding::BufferLoadStore: - h ^= qHash(reinterpret_cast(d->u.sbuf.buf)); + seed = hash(seed, reinterpret_cast(d->u.sbuf.buf)); break; } - return h; + return seed; } #ifndef QT_NO_DEBUG_STREAM QDebug operator<<(QDebug dbg, const QRhiShaderResourceBinding &b) { QDebugStateSaver saver(dbg); - const QRhiShaderResourceBinding::Data *d = b.data(); + const QRhiShaderResourceBinding::Data *d = QRhiImplementation::shaderResourceBindingData(b); dbg.nospace() << "QRhiShaderResourceBinding(" << "binding=" << d->binding << " stage=" << d->stage @@ -4289,18 +5999,44 @@ QDebug operator<<(QDebug dbg, const QRhiShaderResourceBindings &srb) /*! \class QRhiGraphicsPipeline - \internal \inmodule QtGui + \since 6.6 \brief Graphics pipeline state resource. + Represents a graphics pipeline. What exactly this map to in the underlying + native graphics API, varies. Where there is a concept of pipeline objects, + for example with Vulkan, the QRhi backend will create such an object upon + calling create(). Elsewhere, for example with OpenGL, the + QRhiGraphicsPipeline may merely collect the various state, and create()'s + main task is to set up the corresponding shader program, but deferring + looking at any of the requested state to a later point. + + As with all QRhiResource subclasses, the two-phased initialization pattern + applies: setting any values via the setters, for example setDepthTest(), is + only effective after calling create(). Avoid changing any values once the + QRhiGraphicsPipeline has been initialized via create(). To change some + state, set the new value and call create() again. However, that will + effectively release all underlying native resources and create new ones. As + a result, it may be a heavy, expensive operation. Rather, prefer creating + multiple pipelines with the different states, and + \l{QRhiCommandBuffer::setGraphicsPipeline()}{switch between them} when + recording the render pass. + \note Setting the shader stages is mandatory. There must be at least one stage, and there must be a vertex stage. \note Setting the shader resource bindings is mandatory. The referenced QRhiShaderResourceBindings must already have create() called on it by the time create() is called. Associating with a QRhiShaderResourceBindings that - has no bindings is also valid, as long as no shader in any stage expects - any resources. + has no bindings is also valid, as long as no shader in any stage expects any + resources. Using a QRhiShaderResourceBindings object that does not specify + any actual resources (i.e., the buffers, textures, etc. for the binding + points are set to \nullptr) is valid as well, as long as a + \l{QRhiShaderResourceBindings::isLayoutCompatible()}{layout-compatible} + QRhiShaderResourceBindings, that specifies resources for all the bindings, + is going to be set via + \l{QRhiCommandBuffer::setShaderResources()}{setShaderResources()} when + recording the render pass. \note Setting the render pass descriptor is mandatory. To obtain a QRhiRenderPassDescriptor that can be passed to setRenderPassDescriptor(), @@ -4313,20 +6049,49 @@ QDebug operator<<(QDebug dbg, const QRhiShaderResourceBindings &srb) render target's color and depth stencil attachments. \note The depth test, depth write, and stencil test are disabled by - default. + default. The face culling mode defaults to no culling. \note stencilReadMask() and stencilWriteMask() apply to both faces. They both default to 0xFF. - */ -/*! - \fn void QRhiGraphicsPipeline::setTargetBlends(const QList &blends) + \section2 Example usage - Sets the blend specification for color attachments. Each element in \a - blends corresponds to a color attachment of the render target. + All settings of a graphics pipeline have defaults which might be suitable + to many applications. Therefore a minimal example of creating a graphics + pipeline could be the following. This assumes that the vertex shader takes + a single \c{vec3 position} input at the input location 0. With the + QRhiShaderResourceBindings and QRhiRenderPassDescriptor objects, plus the + QShader collections for the vertex and fragment stages, a pipeline could be + created like this: - By default no blends are set, which is a shortcut to disabling blending and - enabling color write for all four channels. + \code + QRhiShaderResourceBindings *srb; + QRhiRenderPassDescriptor *rpDesc; + QShader vs, fs; + // ... + + QRhiVertexInputLayout inputLayout; + inputLayout.setBindings({ { 3 * sizeof(float) } }); + inputLayout.setAttributes({ { 0, 0, QRhiVertexInputAttribute::Float3, 0 } }); + + QRhiGraphicsPipeline *ps = rhi->newGraphicsPipeline(); + ps->setShaderStages({ { QRhiShaderStage::Vertex, vs }, { QRhiShaderStage::Fragment, fs } }); + ps->setVertexInputLayout(inputLayout); + ps->setShaderResourceBindings(srb); + ps->setRenderPassDescriptor(rpDesc); + if (!ps->create()) { error(); } + \endcode + + The above code creates a pipeline object that uses the defaults for many + settings and states. For example, it will use a \l Triangles topology, no + backface culling, blending is disabled but color write is enabled for all + four channels, depth test/write are disabled, stencil operations are + disabled. + + \note This is a RHI API with limited compatibility guarantees, see \l QRhi + for details. + + \sa QRhiCommandBuffer, QRhi */ /*! @@ -4485,21 +6250,85 @@ QDebug operator<<(QDebug dbg, const QRhiShaderResourceBindings &srb) */ /*! - \class QRhiGraphicsPipeline::TargetBlend - \internal + \struct QRhiGraphicsPipeline::TargetBlend \inmodule QtGui + \since 6.6 \brief Describes the blend state for one color attachment. Defaults to color write enabled, blending disabled. The blend values are set up for pre-multiplied alpha (One, OneMinusSrcAlpha, One, - OneMinusSrcAlpha) by default. + OneMinusSrcAlpha) by default. This means that to get the alpha blending + mode Qt Quick uses, it is enough to set the \c enable flag to true while + leaving other values at their defaults. + + \note This is a RHI API with limited compatibility guarantees, see \l QRhi + for details. */ /*! - \class QRhiGraphicsPipeline::StencilOpState - \internal + \variable QRhiGraphicsPipeline::TargetBlend::colorWrite + */ + +/*! + \variable QRhiGraphicsPipeline::TargetBlend::enable + */ + +/*! + \variable QRhiGraphicsPipeline::TargetBlend::srcColor + */ + +/*! + \variable QRhiGraphicsPipeline::TargetBlend::dstColor + */ + +/*! + \variable QRhiGraphicsPipeline::TargetBlend::opColor + */ + +/*! + \variable QRhiGraphicsPipeline::TargetBlend::srcAlpha + */ + +/*! + \variable QRhiGraphicsPipeline::TargetBlend::dstAlpha + */ + +/*! + \variable QRhiGraphicsPipeline::TargetBlend::opAlpha + */ + +/*! + \struct QRhiGraphicsPipeline::StencilOpState \inmodule QtGui + \since 6.6 \brief Describes the stencil operation state. + + The default-constructed StencilOpState has the following set: + \list + \li failOp - \l Keep + \li depthFailOp - \l Keep + \li passOp - \l Keep + \li compareOp \l Always + \endlist + + \note This is a RHI API with limited compatibility guarantees, see \l QRhi + for details. + */ + +/*! + \variable QRhiGraphicsPipeline::StencilOpState::failOp + */ + +/*! + \variable QRhiGraphicsPipeline::StencilOpState::depthFailOp + */ + +/*! + \variable QRhiGraphicsPipeline::StencilOpState::passOp + */ + +/*! + \variable QRhiGraphicsPipeline::StencilOpState::compareOp */ /*! @@ -4519,7 +6348,7 @@ QRhiResource::Type QRhiGraphicsPipeline::resourceType() const } /*! - \fn bool QRhiGraphicsPipeline::create() + \fn virtual bool QRhiGraphicsPipeline::create() = 0 Creates the corresponding native graphics resources. If there are already resources present due to an earlier create() with no corresponding @@ -4527,23 +6356,132 @@ QRhiResource::Type QRhiGraphicsPipeline::resourceType() const \return \c true when successful, \c false when a graphics operation failed. Regardless of the return value, calling destroy() is always safe. + + \note This may be, depending on the underlying graphics API, an expensive + operation, especially when shaders get compiled/optimized from source or + from an intermediate bytecode format to the GPU's own instruction set. + Where applicable, the QRhi backend automatically sets up the relevant + non-persistent facilities to accelerate this, for example the Vulkan + backend automatically creates a \c VkPipelineCache to improve data reuse + during the lifetime of the application. + + \note Drivers may also employ various persistent (disk-based) caching + strategies for shader and pipeline data, which is hidden to and is outside + of Qt's control. In some cases, depending on the graphics API and the QRhi + backend, there are facilities within QRhi for manually managing such a + cache, allowing the retrieval of a serializable blob that can then be + reloaded in the future runs of the application to ensure faster pipeline + creation times. See QRhi::pipelineCacheData() and + QRhi::setPipelineCacheData() for details. Note also that when working with + a QRhi instance managed by a higher level Qt framework, such as Qt Quick, + it is possible that such disk-based caching is taken care of automatically, + for example QQuickWindow uses a disk-based pipeline cache by default (which + comes in addition to any driver-level caching). + */ + +/*! + \fn QRhiGraphicsPipeline::Flags QRhiGraphicsPipeline::flags() const + \return the currently set flags. + */ + +/*! + \fn void QRhiGraphicsPipeline::setFlags(Flags f) + Sets the flags \a f. + */ + +/*! + \fn QRhiGraphicsPipeline::Topology QRhiGraphicsPipeline::topology() const + \return the currently set primitive topology. + */ + +/*! + \fn void QRhiGraphicsPipeline::setTopology(Topology t) + Sets the primitive topology \a t. + */ + +/*! + \fn QRhiGraphicsPipeline::CullMode QRhiGraphicsPipeline::cullMode() const + \return the currently set face culling mode. + */ + +/*! + \fn void QRhiGraphicsPipeline::setCullMode(CullMode mode) + Sets the specified face culling \a mode. + */ + +/*! + \fn QRhiGraphicsPipeline::FrontFace QRhiGraphicsPipeline::frontFace() const + \return the currently set front face mode. + */ + +/*! + \fn void QRhiGraphicsPipeline::setFrontFace(FrontFace f) + Sets the front face mode \a f. + */ + +/*! + \fn void QRhiGraphicsPipeline::setTargetBlends(std::initializer_list list) + + Sets the \a list of render target blend settings. This is a list because + when multiple render targets are used (i.e., a QRhiTextureRenderTarget with + more than one QRhiColorAttachment), there needs to be a TargetBlend + structure per render target (color attachment). + + By default there is one default-constructed TargetBlend set. + + \sa QRhi::MaxColorAttachments + */ + +/*! + \fn template void QRhiGraphicsPipeline::setTargetBlends(InputIterator first, InputIterator last) + Sets the list of render target blend settings from the iterators \a first and \a last. + */ + +/*! + \fn const QRhiGraphicsPipeline::TargetBlend *QRhiGraphicsPipeline::cbeginTargetBlends() const + \return a const iterator pointing to the first item in the render target blend setting list. + */ + +/*! + \fn const QRhiGraphicsPipeline::TargetBlend *QRhiGraphicsPipeline::cendTargetBlends() const + \return a const iterator pointing just after the last item in the render target blend setting list. + */ + +/*! + \fn const QRhiGraphicsPipeline::TargetBlend *QRhiGraphicsPipeline::targetBlendAt(qsizetype index) const + \return the render target blend setting at the specified \a index. + */ + +/*! + \fn qsizetype QRhiGraphicsPipeline::targetBlendCount() const + \return the number of render target blend settings. + */ + +/*! + \fn bool QRhiGraphicsPipeline::hasDepthTest() const + \return true if depth testing is enabled. */ /*! \fn void QRhiGraphicsPipeline::setDepthTest(bool enable) - Enables or disables depth testing. Both depth test and the writing out of - depth data are disabled by default. + Enables or disables depth testing based on \a enable. Both depth test and + the writing out of depth data are disabled by default. \sa setDepthWrite() */ +/*! + \fn bool QRhiGraphicsPipeline::hasDepthWrite() const + \return true if depth write is enabled. + */ + /*! \fn void QRhiGraphicsPipeline::setDepthWrite(bool enable) - Controls the writing out of depth data into the depth buffer. By default - this is disabled. Depth write is typically enabled together with the depth - test. + Controls the writing out of depth data into the depth buffer based on + \a enable. By default this is disabled. Depth write is typically enabled + together with the depth test. \note Enabling depth write without having depth testing enabled may not lead to the desired result, and should be avoided. @@ -4551,10 +6489,211 @@ QRhiResource::Type QRhiGraphicsPipeline::resourceType() const \sa setDepthTest() */ +/*! + \fn QRhiGraphicsPipeline::CompareOp QRhiGraphicsPipeline::depthOp() const + \return the depth comparison function. + */ + +/*! + \fn void QRhiGraphicsPipeline::setDepthOp(CompareOp op) + Sets the depth comparison function \a op. + */ + +/*! + \fn bool QRhiGraphicsPipeline::hasStencilTest() const + \return true if stencil testing is enabled. + */ + +/*! + \fn void QRhiGraphicsPipeline::setStencilTest(bool enable) + Enables or disables stencil tests based on \a enable. + By default this is disabled. + */ + +/*! + \fn QRhiGraphicsPipeline::StencilOpState QRhiGraphicsPipeline::stencilFront() const + \return the current stencil test state for front faces. + */ + +/*! + \fn void QRhiGraphicsPipeline::setStencilFront(const StencilOpState &state) + Sets the stencil test \a state for front faces. + */ + +/*! + \fn QRhiGraphicsPipeline::StencilOpState QRhiGraphicsPipeline::stencilBack() const + \return the current stencil test state for back faces. + */ + +/*! + \fn void QRhiGraphicsPipeline::setStencilBack(const StencilOpState &state) + Sets the stencil test \a state for back faces. + */ + +/*! + \fn quint32 QRhiGraphicsPipeline::stencilReadMask() const + \return the currrent stencil read mask. + */ + +/*! + \fn void QRhiGraphicsPipeline::setStencilReadMask(quint32 mask) + Sets the stencil read \a mask. The default value is 0xFF. + */ + +/*! + \fn quint32 QRhiGraphicsPipeline::stencilWriteMask() const + \return the current stencil write mask. + */ + +/*! + \fn void QRhiGraphicsPipeline::setStencilWriteMask(quint32 mask) + Sets the stencil write \a mask. The default value is 0xFF. + */ + +/*! + \fn int QRhiGraphicsPipeline::sampleCount() const + \return the currently set sample count. 1 means no multisample antialiasing. + */ + +/*! + \fn void QRhiGraphicsPipeline::setSampleCount(int s) + + Sets the sample count. Typical values for \a s are 1, 4, or 8. The pipeline + must always be compatible with the render target, i.e. the sample counts + must match. + + \sa QRhi::supportedSampleCounts() + */ + +/*! + \fn float QRhiGraphicsPipeline::lineWidth() const + \return the currently set line width. The default is 1.0f. + */ + +/*! + \fn void QRhiGraphicsPipeline::setLineWidth(float width) + + Sets the line \a width. If the QRhi::WideLines feature is reported as + unsupported at runtime, values other than 1.0f are ignored. + */ + +/*! + \fn int QRhiGraphicsPipeline::depthBias() const + \return the currently set depth bias. + */ + +/*! + \fn void QRhiGraphicsPipeline::setDepthBias(int bias) + Sets the depth \a bias. The default value is 0. + */ + +/*! + \fn float QRhiGraphicsPipeline::slopeScaledDepthBias() const + \return the currently set slope scaled depth bias. + */ + +/*! + \fn void QRhiGraphicsPipeline::setSlopeScaledDepthBias(float bias) + Sets the slope scaled depth \a bias. The default value is 0. + */ + +/*! + \fn void QRhiGraphicsPipeline::setShaderStages(std::initializer_list list) + Sets the \a list of shader stages. + */ + +/*! + \fn template void QRhiGraphicsPipeline::setShaderStages(InputIterator first, InputIterator last) + Sets the list of shader stages from the iterators \a first and \a last. + */ + +/*! + \fn const QRhiShaderStage *QRhiGraphicsPipeline::cbeginShaderStages() const + \return a const iterator pointing to the first item in the shader stage list. + */ + +/*! + \fn const QRhiShaderStage *QRhiGraphicsPipeline::cendShaderStages() const + \return a const iterator pointing just after the last item in the shader stage list. + */ + +/*! + \fn const QRhiShaderStage *QRhiGraphicsPipeline::shaderStageAt(qsizetype index) const + \return the shader stage at the specified \a index. + */ + +/*! + \fn qsizetype QRhiGraphicsPipeline::shaderStageCount() const + \return the number of shader stages in this pipeline. + */ + +/*! + \fn QRhiVertexInputLayout QRhiGraphicsPipeline::vertexInputLayout() const + \return the currently set vertex input layout specification. + */ + +/*! + \fn void QRhiGraphicsPipeline::setVertexInputLayout(const QRhiVertexInputLayout &layout) + Specifies the vertex input \a layout. + */ + +/*! + \fn QRhiShaderResourceBindings *QRhiGraphicsPipeline::shaderResourceBindings() const + \return the currently associated QRhiShaderResourceBindings object. + */ + +/*! + \fn void QRhiGraphicsPipeline::setShaderResourceBindings(QRhiShaderResourceBindings *srb) + + Associates with \a srb describing the resource binding layout and the + resources (QRhiBuffer, QRhiTexture) themselves. The latter is optional, + because only the layout matters during pipeline creation. Therefore, the \a + srb passed in here can leave the actual buffer or texture objects + unspecified (\nullptr) as long as there is another, + \l{QRhiShaderResourceBindings::isLayoutCompatible()}{layout-compatible} + QRhiShaderResourceBindings bound via + \l{QRhiCommandBuffer::setShaderResources()}{setShaderResources()} before + recording the draw calls. + */ + +/*! + \fn QRhiRenderPassDescriptor *QRhiGraphicsPipeline::renderPassDescriptor() const + \return the currently set QRhiRenderPassDescriptor. + */ + +/*! + \fn void QRhiGraphicsPipeline::setRenderPassDescriptor(QRhiRenderPassDescriptor *desc) + Associates with the specified QRhiRenderPassDescriptor \a desc. + */ + +/*! + \fn int QRhiGraphicsPipeline::patchControlPointCount() const + \return the currently set patch control point count. + */ + +/*! + \fn void QRhiGraphicsPipeline::setPatchControlPointCount(int count) + + Sets the number of patch control points to \a count. The default value is + 3. This is used only when the topology is set to \l Patches. + */ + +/*! + \fn QRhiGraphicsPipeline::PolygonMode QRhiGraphicsPipeline::polygonMode() const + \return the polygon mode. + */ + +/*! + \fn void QRhiGraphicsPipeline::setPolygonMode(PolygonMode mode) + Sets the polygon \a mode. The default is Fill. + + \sa QRhi::NonFillPolygonMode + */ + /*! \class QRhiSwapChain - \internal \inmodule QtGui + \since 6.6 \brief Swapchain resource. A swapchain enables presenting rendering results to a surface. A swapchain @@ -4564,7 +6703,7 @@ QRhiResource::Type QRhiGraphicsPipeline::resourceType() const Below is a typical pattern for creating and managing a swapchain and some associated resources in order to render onto a QWindow: - \badcode + \code void init() { sc = rhi->newSwapChain(); @@ -4610,7 +6749,7 @@ QRhiResource::Type QRhiGraphicsPipeline::resourceType() const Releasing the swapchain must happen while the QWindow and the underlying native window is fully up and running. Building on the previous example: - \badcode + \code void releaseSwapChain() { if (hasSwapChain) { @@ -4642,7 +6781,7 @@ QRhiResource::Type QRhiGraphicsPipeline::resourceType() const events. QExposeEvent is a loosely specified event that is sent whenever a window gets mapped, obscured, and resized, depending on the platform. - \badcode + \code void Window::exposeEvent(QExposeEvent *) { // initialize and start rendering when the window becomes usable for graphics purposes @@ -4688,6 +6827,9 @@ QRhiResource::Type QRhiGraphicsPipeline::resourceType() const command at the end of a frame. For OpenGL, it is necessary to request the appropriate sample count also via QSurfaceFormat, by calling QSurfaceFormat::setDefaultFormat() before initializing the QRhi. + + \note This is a RHI API with limited compatibility guarantees, see \l QRhi + for details. */ /*! @@ -4746,6 +6888,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 @@ -4806,7 +6956,7 @@ QRhiResource::Type QRhiSwapChain::resourceType() const */ /*! - \fn QSize QRhiSwapChain::surfacePixelSize() + \fn virtual QSize QRhiSwapChain::surfacePixelSize() = 0 \return The size of the window's associated surface or layer. @@ -4814,13 +6964,13 @@ QRhiResource::Type QRhiSwapChain::resourceType() const QWindow::devicePixelRatio()}. With some graphics APIs and windowing system interfaces (for example, Vulkan) there is a theoretical possibility for a surface to assume a size different from the associated window. To support - these cases, rendering logic must always base size-derived calculations + these cases, \b{rendering logic must always base size-derived calculations (such as, viewports) on the size reported from QRhiSwapChain, and never on - the size queried from QWindow. + the size queried from QWindow}. - \note Can also be called before createOrResize(), if at least window() is - already set) This in combination with currentPixelSize() allows to detect - when a swapchain needs to be resized. However, watch out for the fact that + \note \b{Can also be called before createOrResize(), if at least window() is + already set. This in combination with currentPixelSize() allows to detect + when a swapchain needs to be resized.} However, watch out for the fact that the size of the underlying native object (surface, layer, or similar) is "live", so whenever this function is called, it returns the latest value reported by the underlying implementation, without any atomicity guarantee. @@ -4841,9 +6991,9 @@ QRhiResource::Type QRhiSwapChain::resourceType() const */ /*! - \fn bool QRhiSwapChain::isFormatSuported(Format f) + \fn virtual bool QRhiSwapChain::isFormatSupported(Format f) = 0 - \return true if the given swapchain format is supported. SDR is always + \return true if the given swapchain format \a f is supported. SDR is always supported. \note Can be called independently of createOrResize(), but window() must @@ -4854,20 +7004,45 @@ 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() */ /*! - \fn QRhiCommandBuffer *QRhiSwapChain::currentFrameCommandBuffer() + \fn virtual QRhiCommandBuffer *QRhiSwapChain::currentFrameCommandBuffer() = 0 - \return a command buffer on which rendering commands can be recorded. Only - valid within a QRhi::beginFrame() - QRhi::endFrame() block where - beginFrame() was called with this swapchain. + \return a command buffer on which rendering commands and resource updates + can be recorded within a \l{QRhi::beginFrame()}{beginFrame} - + \l{QRhi::endFrame()}{endFrame} block, assuming beginFrame() was called with + this swapchain. - \note the value must not be cached and reused between frames + \note The returned object is valid also after endFrame(), up until the next + beginFrame(), but the returned command buffer should not be used to record + any commands then. Rather, it can be used to query data collected during + the frame (or previous frames), for example by calling + \l{QRhiCommandBuffer::lastCompletedGpuTime()}{lastCompletedGpuTime()}. + + \note The value must not be cached and reused between frames. The caller + should not hold on to the returned object once + \l{QRhi::beginFrame()}{beginFrame()} is called again. Instead, the command + buffer object should be queried again by calling this function. */ /*! - \fn QRhiRenderTarget *QRhiSwapChain::currentFrameRenderTarget() + \fn virtual QRhiRenderTarget *QRhiSwapChain::currentFrameRenderTarget() = 0 \return a render target that can used with beginPass() in order to render the swapchain's current backbuffer. Only valid within a @@ -4907,7 +7082,7 @@ QRhiRenderTarget *QRhiSwapChain::currentFrameRenderTarget(StereoTargetBuffer tar } /*! - \fn bool QRhiSwapChain::createOrResize() + \fn virtual bool QRhiSwapChain::createOrResize() = 0 Creates the swapchain if not already done and resizes the swapchain buffers to match the current size of the targeted surface. Call this whenever the @@ -4922,10 +7097,112 @@ QRhiRenderTarget *QRhiSwapChain::currentFrameRenderTarget(StereoTargetBuffer tar Regardless of the return value, calling destroy() is always safe. */ +/*! + \fn QWindow *QRhiSwapChain::window() const + \return the currently set window. + */ + +/*! + \fn void QRhiSwapChain::setWindow(QWindow *window) + Sets the \a window. + */ + +/*! + \fn QRhiSwapChainProxyData QRhiSwapChain::proxyData() const + \return the currently set proxy data. + */ + +/*! + \fn void QRhiSwapChain::setProxyData(const QRhiSwapChainProxyData &d) + Sets the proxy data \a d. + + \sa QRhi::updateSwapChainProxyData() + */ + +/*! + \fn QRhiSwapChain::Flags QRhiSwapChain::flags() const + \return the currently set flags. + */ + +/*! + \fn void QRhiSwapChain::setFlags(Flags f) + Sets the flags \a f. + */ + +/*! + \fn QRhiSwapChain::Format QRhiSwapChain::format() const + \return the currently set format. + */ + +/*! + \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. + */ + +/*! + \fn QRhiRenderBuffer *QRhiSwapChain::depthStencil() const + \return the currently associated renderbuffer for depth-stencil. + */ + +/*! + \fn void QRhiSwapChain::setDepthStencil(QRhiRenderBuffer *ds) + Sets the renderbuffer \a ds for use as a depth-stencil buffer. + */ + +/*! + \fn int QRhiSwapChain::sampleCount() const + \return the currently set sample count. 1 means no multisample antialiasing. + */ + +/*! + \fn void QRhiSwapChain::setSampleCount(int samples) + + Sets the sample count. Common values for \a samples are 1 (no MSAA), 4 (4x + MSAA), or 8 (8x MSAA). + + \sa QRhi::supportedSampleCounts() + */ + +/*! + \fn QRhiRenderPassDescriptor *QRhiSwapChain::renderPassDescriptor() const + \return the currently associated QRhiRenderPassDescriptor object. + */ + +/*! + \fn void QRhiSwapChain::setRenderPassDescriptor(QRhiRenderPassDescriptor *desc) + Associates with the QRhiRenderPassDescriptor \a desc. + */ + +/*! + \fn virtual QRhiRenderPassDescriptor *QRhiSwapChain::newCompatibleRenderPassDescriptor() = 0; + + \return a new QRhiRenderPassDescriptor that is compatible with this swapchain. + + The returned value is used in two ways: it can be passed to + setRenderPassDescriptor() and + QRhiGraphicsPipeline::setRenderPassDescriptor(). A render pass descriptor + describes the attachments (color, depth/stencil) and the load/store + behavior that can be affected by flags(). A QRhiGraphicsPipeline can only + be used in combination with a swapchain that has a + \l{QRhiRenderPassDescriptor::isCompatible()}{compatible} + QRhiRenderPassDescriptor set. + + \sa createOrResize() + */ + /*! \struct QRhiSwapChainHdrInfo - \internal \inmodule QtGui + \since 6.6 \brief Describes the high dynamic range related information of the swapchain's associated output. @@ -4958,9 +7235,75 @@ QRhiRenderTarget *QRhiSwapChain::currentFrameRenderTarget(StereoTargetBuffer tar values are always the built-in defaults and \c isHardCodedDefaults is always true. + \note This is a RHI API with limited compatibility guarantees, see \l QRhi + for details. + \sa QRhiSwapChain::hdrInfo() */ +/*! + \enum QRhiSwapChainHdrInfo::LimitsType + + \value LuminanceInNits Indicates that the \l limits union has its + \c luminanceInNits struct set + + \value ColorComponentValue Indicates that the \l limits union has its + \c colorComponentValue struct set +*/ + +/*! + \variable QRhiSwapChainHdrInfo::isHardCodedDefaults + + 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) + + \sa QRhiSwapChain::hdrInfo() +*/ + +/*! + \variable QRhiSwapChainHdrInfo::limitsType + + 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. This value indicates what kind of + information is available in \l limits. + + \sa QRhiSwapChain::hdrInfo() +*/ + +/*! + \variable QRhiSwapChainHdrInfo::limits + + Contains the actual values queried from the graphics API or the platform. + The type of data is indicated by \l limitsType. This is therefore a union. + There are currently two options: + + Luminance values in nits: + + \code + struct { + float minLuminance; + float maxLuminance; + } luminanceInNits; + \endcode + + Whereas for macOS/iOS, the current maximum and potential maximum color + component values are provided: + + \code + struct { + float maxColorComponentValue; + float maxPotentialColorComponentValue; + } colorComponentValue; + \endcode + + \sa QRhiSwapChain::hdrInfo() +*/ + /*! \return the HDR information for the associated display. @@ -4999,6 +7342,7 @@ 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; } dbg.nospace() << ')'; @@ -5008,8 +7352,8 @@ QDebug operator<<(QDebug dbg, const QRhiSwapChainHdrInfo &info) /*! \class QRhiComputePipeline - \internal \inmodule QtGui + \since 6.6 \brief Compute pipeline state resource. \note Setting the shader resource bindings is mandatory. The referenced @@ -5017,6 +7361,9 @@ QDebug operator<<(QDebug dbg, const QRhiSwapChainHdrInfo &info) time create() is called. \note Setting the shader is mandatory. + + \note This is a RHI API with limited compatibility guarantees, see \l QRhi + for details. */ /*! @@ -5045,16 +7392,60 @@ QRhiComputePipeline::QRhiComputePipeline(QRhiImplementation *rhi) { } +/*! + \fn QRhiComputePipeline::Flags QRhiComputePipeline::flags() const + \return the currently set flags. + */ + +/*! + \fn void QRhiComputePipeline::setFlags(Flags f) + Sets the flags \a f. + */ + +/*! + \fn QRhiShaderStage QRhiComputePipeline::shaderStage() const + \return the currently set shader. + */ + +/*! + \fn void QRhiComputePipeline::setShaderStage(const QRhiShaderStage &stage) + + Sets the shader to use. \a stage can only refer to the + \l{QRhiShaderStage::Compute}{compute stage}. + */ + +/*! + \fn QRhiShaderResourceBindings *QRhiComputePipeline::shaderResourceBindings() const + \return the currently associated QRhiShaderResourceBindings object. + */ + +/*! + \fn void QRhiComputePipeline::setShaderResourceBindings(QRhiShaderResourceBindings *srb) + + Associates with \a srb describing the resource binding layout and the + resources (QRhiBuffer, QRhiTexture) themselves. The latter is optional. As + with graphics pipelines, the \a srb passed in here can leave the actual + buffer or texture objects unspecified (\nullptr) as long as there is + another, + \l{QRhiShaderResourceBindings::isLayoutCompatible()}{layout-compatible} + QRhiShaderResourceBindings bound via + \l{QRhiCommandBuffer::setShaderResources()}{setShaderResources()} before + recording the dispatch call. + */ + /*! \class QRhiCommandBuffer - \internal \inmodule QtGui + \since 6.6 \brief Command buffer resource. Not creatable by applications at the moment. The only ways to obtain a valid QRhiCommandBuffer are to get it from the targeted swapchain via QRhiSwapChain::currentFrameCommandBuffer(), or, in case of rendering completely offscreen, initializing one via QRhi::beginOffscreenFrame(). + + \note This is a RHI API with limited compatibility guarantees, see \l QRhi + for details. */ /*! @@ -5420,7 +7811,7 @@ bool QRhiImplementation::sanityCheckShaderResourceBindings(QRhiShaderResourceBin const int CHECKED_BINDINGS_COUNT = 64; bool bindingSeen[CHECKED_BINDINGS_COUNT] = {}; for (auto it = srb->cbeginBindings(), end = srb->cendBindings(); it != end; ++it) { - const int binding = it->data()->binding; + const int binding = shaderResourceBindingData(*it)->binding; if (binding >= CHECKED_BINDINGS_COUNT) continue; if (binding < 0) { @@ -5428,7 +7819,7 @@ bool QRhiImplementation::sanityCheckShaderResourceBindings(QRhiShaderResourceBin bindingsOk = false; continue; } - switch (it->data()->type) { + switch (shaderResourceBindingData(*it)->type) { case QRhiShaderResourceBinding::UniformBuffer: if (!bindingSeen[binding]) { bindingSeen[binding] = true; @@ -5482,7 +7873,7 @@ bool QRhiImplementation::sanityCheckShaderResourceBindings(QRhiShaderResourceBin } break; default: - qWarning("Unknown binding type %d", int(it->data()->type)); + qWarning("Unknown binding type %d", int(shaderResourceBindingData(*it)->type)); bindingsOk = false; break; } @@ -5522,14 +7913,30 @@ QRhi::~QRhi() delete d; } +void QRhiImplementation::prepareForCreate(QRhi *rhi, QRhi::Implementation impl, QRhi::Flags flags) +{ + q = rhi; + + // Play nice with QSG_INFO since that is still the most commonly used + // way to get graphics info printed from Qt Quick apps, and the Quick + // scenegraph is our primary user. + if (qEnvironmentVariableIsSet("QSG_INFO")) + const_cast(QRHI_LOG_INFO()).setEnabled(QtDebugMsg, true); + + debugMarkers = flags.testFlag(QRhi::EnableDebugMarkers); + + implType = impl; + implThread = QThread::currentThread(); +} + /*! \return a new QRhi instance with a backend for the graphics API specified by \a impl with the specified \a flags. \a params must point to an instance of one of the backend-specific subclasses of QRhiInitParams, such as, QRhiVulkanInitParams, - QRhiMetalInitParams, QRhiD3D11InitParams, QRhiGles2InitParams. See these - classes for examples on creating a QRhi. + QRhiMetalInitParams, QRhiD3D11InitParams, QRhiD3D12InitParams, + QRhiGles2InitParams. See these classes for examples on creating a QRhi. QRhi by design does not implement any fallback logic: if the specified API cannot be initialized, create() will fail, with warnings printed on the @@ -5543,6 +7950,13 @@ QRhi::~QRhi() initialization of the infrastructure and is wasteful if that QRhi instance is then thrown immediately away. + \a importDevice allows using an already existing graphics device, without + QRhi creating its own. When not null, this parameter must point to an + instance of one of the subclasses of QRhiNativeHandles: + QRhiVulkanNativeHandles, QRhiD3D11NativeHandles, QRhiD3D12NativeHandles, + QRhiMetalNativeHandles, QRhiGles2NativeHandles. The exact details and + semantics depend on the backand and the underlying graphics API. + \sa probe() */ QRhi *QRhi::create(Implementation impl, QRhiInitParams *params, Flags flags, QRhiNativeHandles *importDevice) @@ -5589,25 +8003,22 @@ QRhi *QRhi::create(Implementation impl, QRhiInitParams *params, Flags flags, QRh #else qWarning("This platform has no Metal support"); break; +#endif + case D3D12: +#ifdef Q_OS_WIN + r->d = new QRhiD3D12(static_cast(params), + static_cast(importDevice)); + break; +#else + qWarning("This platform has no Direct3D 12 support"); + break; #endif } if (r->d) { - r->d->q = r.get(); - - // Play nice with QSG_INFO since that is still the most commonly used - // way to get graphics info printed from Qt Quick apps, and the Quick - // scenegraph is our primary user. - if (qEnvironmentVariableIsSet("QSG_INFO")) - const_cast(QRHI_LOG_INFO()).setEnabled(QtDebugMsg, true); - - r->d->debugMarkers = flags.testFlag(EnableDebugMarkers); - - if (r->d->create(flags)) { - r->d->implType = impl; - r->d->implThread = QThread::currentThread(); + r->d->prepareForCreate(r.get(), impl, flags); + if (r->d->create(flags)) return r.release(); - } } return nullptr; @@ -5649,8 +8060,15 @@ bool QRhi::probe(QRhi::Implementation impl, QRhiInitParams *params) /*! \struct QRhiSwapChainProxyData - \internal \inmodule QtGui + \since 6.6 + + \brief Opaque data describing native objects needed to set up a swapchain. + + \note This is a RHI API with limited compatibility guarantees, see \l QRhi + for details. + + \sa QRhi::updateSwapChainProxyData() */ /*! @@ -5715,6 +8133,8 @@ const char *QRhi::backendName(Implementation impl) return "D3D11"; case QRhi::Metal: return "Metal"; + case QRhi::D3D12: + return "D3D12"; } Q_UNREACHABLE_RETURN("Unknown"); @@ -5744,9 +8164,8 @@ const char *QRhi::backendName() const /*! \struct QRhiDriverInfo - \internal \inmodule QtGui - \since 6.1 + \since 6.6 \brief Describes the physical device, adapter, or graphics API implementation that is used by an initialized QRhi. @@ -5758,8 +8177,35 @@ const char *QRhi::backendName() const \c{GL_VERSION}. The deviceId is always 0 for OpenGL. vendorId is always 0 for OpenGL and Metal. deviceType is always UnknownDevice for OpenGL and Direct 3D. + + \note This is a RHI API with limited compatibility guarantees, see \l QRhi + for details. */ +/*! + \variable QRhiDriverInfo::deviceName + + \sa QRhi::driverInfo() +*/ + +/*! + \variable QRhiDriverInfo::deviceId + + \sa QRhi::driverInfo() +*/ + +/*! + \variable QRhiDriverInfo::vendorId + + \sa QRhi::driverInfo() +*/ + +/*! + \variable QRhiDriverInfo::deviceType + + \sa QRhi::driverInfo(), QRhiDriverInfo::DeviceType +*/ + #ifndef QT_NO_DEBUG_STREAM static inline const char *deviceTypeStr(QRhiDriverInfo::DeviceType type) { @@ -5842,40 +8288,10 @@ void QRhi::runCleanup() d->cleanupCallbacks.clear(); } -/*! - Registers a \a callback that is called with an elapsed time calculated from - GPU timestamps asynchronously after a timestamp becomes available at some - point after presenting a frame. - - The callback is called with a float value that is meant to be in - milliseconds and represents the elapsed time on the GPU side for a given - frame. Care must be exercised with the interpretation of the value, as what - it exactly is is not controlled by Qt and depends on the underlying - graphics API and its implementation. In particular, comparing the values - between different graphics APIs is discouraged and may be meaningless. - - The timing values become available asynchronously, sometimes several frames - after the frame has been submitted in endFrame(). There is currently no way - to identify the frame. The callback is invoked whenever the timestamp - queries complete. - - \note This is only supported when the Timestamp feature is reported as - supported from isFeatureSupported(). Otherwise the \a callback is never - called. - - The \a callback is always called on the thread the QRhi lives and operates - on. While not guaranteed, it is typical that the callback is invoked from - within beginFrame(). - */ -void QRhi::addGpuFrameTimeCallback(const GpuFrameTimeCallback &callback) -{ - d->addGpuFrameTimeCallback(callback); -} - /*! \class QRhiResourceUpdateBatch - \internal \inmodule QtGui + \since 6.6 \brief Records upload and copy type of operations. With QRhi it is no longer possible to perform copy type of operations at @@ -5891,6 +8307,9 @@ void QRhi::addGpuFrameTimeCallback(const GpuFrameTimeCallback &callback) To get an available, empty batch from the pool, call QRhi::nextResourceUpdateBatch(). + + \note This is a RHI API with limited compatibility guarantees, see \l QRhi + for details. */ /*! @@ -5934,29 +8353,26 @@ void QRhiResourceUpdateBatch::release() that is then merged into another when starting to first render pass later on: - \badcode - void init() - { - ... - initialUpdates = rhi->nextResourceUpdateBatch(); - initialUpdates->uploadStaticBuffer(vbuf, vertexData); - initialUpdates->uploadStaticBuffer(ibuf, indexData); - ... - } - - void render() - { - ... - QRhiResourceUpdateBatch *resUpdates = rhi->nextResourceUpdateBatch(); - if (initialUpdates) { - resUpdates->merge(initialUpdates); - initialUpdates->release(); - initialUpdates = nullptr; + \code + void init() + { + initialUpdates = rhi->nextResourceUpdateBatch(); + initialUpdates->uploadStaticBuffer(vbuf, vertexData); + initialUpdates->uploadStaticBuffer(ibuf, indexData); + // ... + } + + void render() + { + QRhiResourceUpdateBatch *resUpdates = rhi->nextResourceUpdateBatch(); + if (initialUpdates) { + resUpdates->merge(initialUpdates); + initialUpdates->release(); + initialUpdates = nullptr; + } + // resUpdates->updateDynamicBuffer(...); + cb->beginPass(rt, clearCol, clearDs, resUpdates); } - resUpdates->updateDynamicBuffer(...); - ... - cb->beginPass(rt, clearCol, clearDs, resUpdates); - } \endcode */ void QRhiResourceUpdateBatch::merge(QRhiResourceUpdateBatch *other) @@ -5998,8 +8414,8 @@ bool QRhiResourceUpdateBatch::hasOptimalCapacity() const \note QRhi transparently manages double buffering in order to prevent stalling the graphics pipeline. The fact that a QRhiBuffer may have - multiple native underneath can be safely ignored when using the QRhi and - QRhiResourceUpdateBatch. + multiple native buffer objects underneath can be safely ignored when using + the QRhi and QRhiResourceUpdateBatch. */ void QRhiResourceUpdateBatch::updateDynamicBuffer(QRhiBuffer *buf, quint32 offset, quint32 size, const void *data) { @@ -6054,7 +8470,7 @@ void QRhiResourceUpdateBatch::uploadStaticBuffer(QRhiBuffer *buf, const void *da A readback is asynchronous. \a result contains a callback that is invoked when the operation has completed. The data is provided in - QRhiBufferReadbackResult::data. Upon successful completion that QByteArray + QRhiReadbackResult::data. Upon successful completion that QByteArray will have a size equal to \a size. On failure the QByteArray will be empty. \note Reading buffers with a usage different than QRhiBuffer::UniformBuffer @@ -6071,7 +8487,7 @@ void QRhiResourceUpdateBatch::uploadStaticBuffer(QRhiBuffer *buf, const void *da \sa readBackTexture(), QRhi::isFeatureSupported(), QRhi::resourceLimit() */ -void QRhiResourceUpdateBatch::readBackBuffer(QRhiBuffer *buf, quint32 offset, quint32 size, QRhiBufferReadbackResult *result) +void QRhiResourceUpdateBatch::readBackBuffer(QRhiBuffer *buf, quint32 offset, quint32 size, QRhiReadbackResult *result) { const int idx = d->activeBufferOpCount++; if (idx < d->bufferOps.size()) @@ -6145,10 +8561,10 @@ void QRhiResourceUpdateBatch::copyTexture(QRhiTexture *dst, QRhiTexture *src, co application. Therefore, \a result provides not just the data but also a callback as operations on the batch are asynchronous by nature: - \badcode - beginFrame(sc); - beginPass - ... + \code + rhi->beginFrame(swapchain); + cb->beginPass(swapchain->currentFrameRenderTarget(), colorClear, dsClear); + // ... QRhiReadbackResult *rbResult = new QRhiReadbackResult; rbResult->completed = [rbResult] { { @@ -6159,11 +8575,11 @@ void QRhiResourceUpdateBatch::copyTexture(QRhiTexture *dst, QRhiTexture *src, co } delete rbResult; }; - u = nextResourceUpdateBatch(); + QRhiResourceUpdateBatch *u = nextResourceUpdateBatch(); QRhiReadbackDescription rb; // no texture -> uses the current backbuffer of sc u->readBackTexture(rb, rbResult); - endPass(u); - endFrame(sc); + cb->endPass(u); + rhi->endFrame(swapchain); \endcode \note The texture must be created with QRhiTexture::UsedAsTransferSource. @@ -6240,8 +8656,25 @@ void QRhiResourceUpdateBatch::generateMips(QRhiTexture *tex) \note Can be called outside beginFrame() - endFrame() as well since a batch instance just collects data on its own, it does not perform any operations. - \warning The maximum number of batches is 64. When this limit is reached, - the function will return null until a batch is returned to the pool. + Due to not being tied to a frame being recorded, the following sequence is + valid for example: + + \code + rhi->beginFrame(swapchain); + QRhiResourceUpdateBatch *u = rhi->nextResourceUpdateBatch(); + u->uploadStaticBuffer(buf, data); + // ... do not commit the batch + rhi->endFrame(); + // u stays valid (assuming buf stays valid as well) + rhi->beginFrame(swapchain); + swapchain->currentFrameCommandBuffer()->resourceUpdate(u); + // ... draw with buf + rhi->endFrame(); + \endcode + + \warning The maximum number of batches per QRhi is 64. When this limit is + reached, the function will return null until a batch is returned to the + pool. */ QRhiResourceUpdateBatch *QRhi::nextResourceUpdateBatch() { @@ -6254,6 +8687,11 @@ QRhiResourceUpdateBatch *QRhi::nextResourceUpdateBatch() // then reused in subsequent frames. This comes at the expense of using // more memory, but has proven good results when (CPU) profiling typical // Quick/Quick3D apps. + // + // Prefering memory over performance means that we always pick the first + // free batch, and triggering the aggressive deallocating of all backing + // memory (see trimOpLists) before returning it. + static const bool preferMemoryOverPerformance = qEnvironmentVariableIntValue("QT_RHI_MINIMIZE_POOLS"); auto nextFreeBatch = [this]() -> QRhiResourceUpdateBatch * { auto isFree = [this](int i) -> QRhiResourceUpdateBatch * { @@ -6262,7 +8700,8 @@ QRhiResourceUpdateBatch *QRhi::nextResourceUpdateBatch() d->resUpdPoolMap |= mask; QRhiResourceUpdateBatch *u = d->resUpdPool[i]; QRhiResourceUpdateBatchPrivate::get(u)->poolIndex = i; - d->lastResUpdIdx = i; + if (!preferMemoryOverPerformance) + d->lastResUpdIdx = i; return u; } return nullptr; @@ -6291,6 +8730,9 @@ QRhiResourceUpdateBatch *QRhi::nextResourceUpdateBatch() qWarning("Resource update batch pool exhausted (max is 64)"); } + if (preferMemoryOverPerformance && u) + u->d->trimOpLists(); + return u; } @@ -6341,8 +8783,6 @@ bool QRhiResourceUpdateBatchPrivate::hasOptimalCapacity() const void QRhiResourceUpdateBatchPrivate::trimOpLists() { - Q_ASSERT(poolIndex == -1); // must not be in use - // Unlike free(), this is expected to aggressively deallocate all memory // used by both the buffer and texture operation lists. (i.e. using // squeeze() to only keep the stack prealloc of the QVLAs) @@ -6361,9 +8801,10 @@ void QRhiResourceUpdateBatchPrivate::trimOpLists() } /*! - Sometimes committing resource updates is necessary without starting a - render pass. Not often needed, updates should typically be passed to - beginPass (or endPass, in case of readbacks) instead. + Sometimes committing resource updates is necessary or just more convenient + without starting a render pass. Calling this function with \a + resourceUpdates is an alternative to passing \a resourceUpdates to a + beginPass() call (or endPass(), which would be typical in case of readbacks). \note Cannot be called inside a pass. */ @@ -6411,16 +8852,20 @@ void QRhiCommandBuffer::resourceUpdate(QRhiResourceUpdateBatch *resourceUpdates) made on \a rt. Therefore, if \a rt has a QRhiTexture color attachment \c texture, and one needs to make the texture a different size, the following is then valid: - \badcode - rt = rhi->newTextureRenderTarget({ { texture } }); + \code + QRhiTextureRenderTarget *rt = rhi->newTextureRenderTarget({ { texture } }); rt->create(); - ... + // ... texture->setPixelSize(new_size); texture->create(); - cb->beginPass(rt, ...); // this is ok, no explicit rt->create() is required before + cb->beginPass(rt, colorClear, dsClear); // this is ok, no explicit rt->create() is required before \endcode - \sa endPass() + \a flags allow controlling certain advanced functionality. One commonly used + flag is \c ExternalContents. This should be specified whenever + beginExternal() will be called within the pass started by this function. + + \sa endPass(), BeginPassFlags */ void QRhiCommandBuffer::beginPass(QRhiRenderTarget *rt, const QColor &colorClearValue, @@ -6558,7 +9003,7 @@ void QRhiCommandBuffer::setShaderResources(QRhiShaderResourceBindings *srb, floats for position (so 5 floats per vertex: x, y, r, g, b). A QRhiGraphicsPipeline for this shader can then be created using the input layout: - \badcode + \code QRhiVertexInputLayout inputLayout; inputLayout.setBindings({ { 5 * sizeof(float) } @@ -6571,11 +9016,10 @@ void QRhiCommandBuffer::setShaderResources(QRhiShaderResourceBindings *srb, Here there is one buffer binding (binding number 0), with two inputs referencing it. When recording the pass, once the pipeline is set, the - vertex bindings can be specified simply like the following (using C++11 - initializer syntax), assuming vbuf is the QRhiBuffer with all the - interleaved position+color data: + vertex bindings can be specified simply like the following, assuming vbuf + is the QRhiBuffer with all the interleaved position+color data: - \badcode + \code const QRhiCommandBuffer::VertexInput vbufBinding(vbuf, 0); cb->setVertexInput(0, 1, &vbufBinding); \endcode @@ -6715,8 +9159,9 @@ void QRhiCommandBuffer::drawIndexed(quint32 indexCount, } /*! - Records a named debug group on the command buffer. This is shown in - graphics debugging tools such as \l{https://renderdoc.org/}{RenderDoc} and + Records a named debug group on the command buffer with the specified \a + name. This is shown in graphics debugging tools such as + \l{https://renderdoc.org/}{RenderDoc} and \l{https://developer.apple.com/xcode/}{XCode}. The end of the grouping is indicated by debugMarkEnd(). @@ -6774,6 +9219,8 @@ void QRhiCommandBuffer::debugMarkMsg(const QByteArray &msg) \note Compute is only available when the \l{QRhi::Compute}{Compute} feature is reported as supported. + + \a flags is not currently used. */ void QRhiCommandBuffer::beginComputePass(QRhiResourceUpdateBatch *resourceUpdates, BeginPassFlags flags) { @@ -6857,9 +9304,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 @@ -6906,6 +9353,41 @@ void QRhiCommandBuffer::endExternal() m_rhi->endExternal(this); } +/*! + \return the last available timestamp, in seconds. The value indicates the + elapsed time on the GPU during the last completed frame. + + 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 + underlying graphics API and its implementation. In particular, comparing + 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. + + \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. + */ +double QRhiCommandBuffer::lastCompletedGpuTime() +{ + return m_rhi->lastCompletedGpuTime(this); +} + /*! \return the value (typically an offset) \a v aligned to the uniform buffer alignment given by by ubufAlignment(). @@ -6919,7 +9401,7 @@ int QRhi::ubufAligned(int v) const /*! \return the number of mip levels for a given \a size. */ -int QRhi::mipLevelsForSize(const QSize &size) const +int QRhi::mipLevelsForSize(const QSize &size) { return qFloor(std::log2(qMax(size.width(), size.height()))) + 1; } @@ -6928,7 +9410,7 @@ int QRhi::mipLevelsForSize(const QSize &size) const \return the texture image size for a given \a mipLevel, calculated based on the level 0 size given in \a baseLevelSize. */ -QSize QRhi::sizeForMipLevel(int mipLevel, const QSize &baseLevelSize) const +QSize QRhi::sizeForMipLevel(int mipLevel, const QSize &baseLevelSize) { const int w = qMax(1, baseLevelSize.width() >> mipLevel); const int h = qMax(1, baseLevelSize.height() >> mipLevel); @@ -7042,7 +9524,8 @@ int QRhi::resourceLimit(ResourceLimit limit) const for the device, context, and similar concepts used by the backend. Cast to QRhiVulkanNativeHandles, QRhiD3D11NativeHandles, - QRhiGles2NativeHandles, QRhiMetalNativeHandles as appropriate. + QRhiD3D12NativeHandles, QRhiGles2NativeHandles, or QRhiMetalNativeHandles + as appropriate. \note No ownership is transferred, neither for the returned pointer nor for any native objects. @@ -7143,7 +9626,7 @@ bool QRhi::isDeviceLost() const } /*! - \return a binary \a data blob with data collected from the + \return a binary data blob with data collected from the QRhiGraphicsPipeline and QRhiComputePipeline successfully created during the lifetime of this QRhi. @@ -7242,12 +9725,70 @@ void QRhi::setPipelineCacheData(const QByteArray &data) /*! \struct QRhiStats - \internal \inmodule QtGui + \since 6.6 \brief Statistics provided from the underlying memory allocator. + + \note This is a RHI API with limited compatibility guarantees, see \l QRhi + for details. */ +/*! + \variable QRhiStats::totalPipelineCreationTime + + The total time in milliseconds spent in graphics and compute pipeline + creation, which usually involves shader compilation or cache lookups, and + potentially expensive processing. + + \note The value should not be compared between different backends since the + concept of "pipelines" and what exactly happens under the hood during, for + instance, a call to QRhiGraphicsPipeline::create(), differ greatly between + graphics APIs and their implementations. + + \sa QRhi::statistics() +*/ + +/*! + \variable QRhiStats::blockCount + + Statistic reported from the Vulkan or D3D12 memory allocator. + + \sa QRhi::statistics() +*/ + +/*! + \variable QRhiStats::allocCount + + Statistic reported from the Vulkan or D3D12 memory allocator. + + \sa QRhi::statistics() +*/ + +/*! + \variable QRhiStats::usedBytes + + Statistic reported from the Vulkan or D3D12 memory allocator. + + \sa QRhi::statistics() +*/ + +/*! + \variable QRhiStats::unusedBytes + + Statistic reported from the Vulkan or D3D12 memory allocator. + + \sa QRhi::statistics() +*/ + +/*! + \variable QRhiStats::totalUsageBytes + + Valid only with D3D12 currently. Matches IDXGIAdapter3::QueryVideoMemoryInfo(). + + \sa QRhi::statistics() +*/ + #ifndef QT_NO_DEBUG_STREAM QDebug operator<<(QDebug dbg, const QRhiStats &info) { @@ -7258,6 +9799,7 @@ QDebug operator<<(QDebug dbg, const QRhiStats &info) << " allocCount=" << info.allocCount << " usedBytes=" << info.usedBytes << " unusedBytes=" << info.unusedBytes + << " totalUsageBytes=" << info.totalUsageBytes << ')'; return dbg; } @@ -7276,6 +9818,15 @@ QDebug operator<<(QDebug dbg, const QRhiStats &info) from the underlying memory allocator library. This gives an insight into the memory requirements of the active buffers and textures. + The same is true for Direct 3D 12. In addition to the memory allocator + library's statistics, here the result also includes a \c totalUsageBytes + field which reports the total size including additional resources that are + not under the memory allocator library's control (swapchain buffers, + descriptor heaps, etc.), as reported by DXGI. + + The values correspond to all types of memory used, combined. (i.e. video + + system in case of a discreet GPU) + Additional data, such as the total time in milliseconds spent in graphics and compute pipeline creation (which usually involves shader compilation or cache lookups, and potentially expensive processing) is available with most @@ -7497,6 +10048,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, @@ -7700,26 +10259,29 @@ int QRhi::currentFrameSlot() const beginOffscreenFrame, endOffscreenFrame, beginFrame, ...) is possible too but it does reduce parallelism so it should be done only infrequently. - Offscreen frames do not let the CPU - potentially - generate another frame + Offscreen frames do not let the CPU potentially generate another frame while the GPU is still processing the previous one. This has the side effect that if readbacks are scheduled, the results are guaranteed to be available once endOffscreenFrame() returns. That is not the case with - frames targeting a swapchain. + frames targeting a swapchain: there the GPU is potentially better utilized, + but working with readback operations needs more care from the application + because endFrame(), unlike endOffscreenFrame(), does not guarantee that the + results from the readback are available at that point. The skeleton of rendering a frame without a swapchain and then reading the frame contents back could look like the following: - \badcode - QRhiReadbackResult rbResult; - QRhiCommandBuffer *cb; - beginOffscreenFrame(&cb); - beginPass - ... - u = nextResourceUpdateBatch(); - u->readBackTexture(rb, &rbResult); - endPass(u); - endOffscreenFrame(); - // image data available in rbResult + \code + QRhiReadbackResult rbResult; + QRhiCommandBuffer *cb; + rhi->beginOffscreenFrame(&cb); + cb->beginPass(rt, colorClear, dsClear); + // ... + u = nextResourceUpdateBatch(); + u->readBackTexture(rb, &rbResult); + cb->endPass(u); + rhi->endOffscreenFrame(); + // image data available in rbResult \endcode \sa endOffscreenFrame(), beginFrame() @@ -7737,7 +10299,9 @@ QRhi::FrameOpResult QRhi::beginOffscreenFrame(QRhiCommandBuffer **cb, BeginFrame } /*! - Ends and waits for the offscreen frame. + Ends, submits, and waits for the offscreen frame. + + \a flags is not currently used. \sa beginOffscreenFrame() */ @@ -7778,6 +10342,9 @@ QRhi::FrameOpResult QRhi::finish() With some backend this list of supported values is fixed in advance, while with some others the (physical) device properties indicate what is supported at run time. + + \sa QRhiRenderBuffer::setSampleCount(), QRhiTexture::setSampleCount(), + QRhiGraphicsPipeline::setSampleCount(), QRhiSwapChain::setSampleCount() */ QList QRhi::supportedSampleCounts() const { diff --git a/src/gui/rhi/qrhi.h b/src/gui/rhi/qrhi.h new file mode 100644 index 00000000..eb300f58 --- /dev/null +++ b/src/gui/rhi/qrhi.h @@ -0,0 +1,1975 @@ +// 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 QRHI_H +#define QRHI_H + +// +// W A R N I N G +// ------------- +// +// This file is part of the RHI API, with limited compatibility guarantees. +// Usage of this API may make your code source and binary incompatible with +// future versions of Qt. +// + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +QT_BEGIN_NAMESPACE + +class QWindow; +class QRhi; +class QRhiImplementation; +class QRhiBuffer; +class QRhiRenderBuffer; +class QRhiTexture; +class QRhiSampler; +class QRhiCommandBuffer; +class QRhiResourceUpdateBatch; +class QRhiResourceUpdateBatchPrivate; +class QRhiSwapChain; + +class Q_GUI_EXPORT QRhiDepthStencilClearValue +{ +public: + QRhiDepthStencilClearValue() = default; + QRhiDepthStencilClearValue(float d, quint32 s); + + float depthClearValue() const { return m_d; } + void setDepthClearValue(float d) { m_d = d; } + + quint32 stencilClearValue() const { return m_s; } + void setStencilClearValue(quint32 s) { m_s = s; } + +private: + float m_d = 1.0f; + quint32 m_s = 0; + + friend bool operator==(const QRhiDepthStencilClearValue &a, const QRhiDepthStencilClearValue &b) noexcept + { + return a.m_d == b.m_d && a.m_s == b.m_s; + } + + friend bool operator!=(const QRhiDepthStencilClearValue &a, const QRhiDepthStencilClearValue &b) noexcept + { + return !(a == b); + } + + friend size_t qHash(const QRhiDepthStencilClearValue &v, size_t seed = 0) noexcept + { + QtPrivate::QHashCombine hash; + seed = hash(seed, v.m_d); + seed = hash(seed, v.m_s); + return seed; + } +}; + +Q_DECLARE_TYPEINFO(QRhiDepthStencilClearValue, Q_RELOCATABLE_TYPE); + +#ifndef QT_NO_DEBUG_STREAM +Q_GUI_EXPORT QDebug operator<<(QDebug, const QRhiDepthStencilClearValue &); +#endif + +class Q_GUI_EXPORT QRhiViewport +{ +public: + QRhiViewport() = default; + QRhiViewport(float x, float y, float w, float h, float minDepth = 0.0f, float maxDepth = 1.0f); + + std::array viewport() const { return m_rect; } + void setViewport(float x, float y, float w, float h) { + m_rect[0] = x; m_rect[1] = y; m_rect[2] = w; m_rect[3] = h; + } + + float minDepth() const { return m_minDepth; } + void setMinDepth(float minDepth) { m_minDepth = minDepth; } + + float maxDepth() const { return m_maxDepth; } + void setMaxDepth(float maxDepth) { m_maxDepth = maxDepth; } + +private: + std::array m_rect { { 0.0f, 0.0f, 0.0f, 0.0f } }; + float m_minDepth = 0.0f; + float m_maxDepth = 1.0f; + + friend bool operator==(const QRhiViewport &a, const QRhiViewport &b) noexcept + { + return a.m_rect == b.m_rect + && a.m_minDepth == b.m_minDepth + && a.m_maxDepth == b.m_maxDepth; + } + + friend bool operator!=(const QRhiViewport &a, const QRhiViewport &b) noexcept + { + return !(a == b); + } + + friend size_t qHash(const QRhiViewport &v, size_t seed = 0) noexcept + { + QtPrivate::QHashCombine hash; + seed = hash(seed, v.m_rect[0]); + seed = hash(seed, v.m_rect[1]); + seed = hash(seed, v.m_rect[2]); + seed = hash(seed, v.m_rect[3]); + seed = hash(seed, v.m_minDepth); + seed = hash(seed, v.m_maxDepth); + return seed; + } +}; + +Q_DECLARE_TYPEINFO(QRhiViewport, Q_RELOCATABLE_TYPE); + +#ifndef QT_NO_DEBUG_STREAM +Q_GUI_EXPORT QDebug operator<<(QDebug, const QRhiViewport &); +#endif + +class Q_GUI_EXPORT QRhiScissor +{ +public: + QRhiScissor() = default; + QRhiScissor(int x, int y, int w, int h); + + std::array scissor() const { return m_rect; } + void setScissor(int x, int y, int w, int h) { + m_rect[0] = x; m_rect[1] = y; m_rect[2] = w; m_rect[3] = h; + } + +private: + std::array m_rect { { 0, 0, 0, 0 } }; + + friend bool operator==(const QRhiScissor &a, const QRhiScissor &b) noexcept + { + return a.m_rect == b.m_rect; + } + + friend bool operator!=(const QRhiScissor &a, const QRhiScissor &b) noexcept + { + return !(a == b); + } + + friend size_t qHash(const QRhiScissor &v, size_t seed = 0) noexcept + { + QtPrivate::QHashCombine hash; + seed = hash(seed, v.m_rect[0]); + seed = hash(seed, v.m_rect[1]); + seed = hash(seed, v.m_rect[2]); + seed = hash(seed, v.m_rect[3]); + return seed; + } +}; + +Q_DECLARE_TYPEINFO(QRhiScissor, Q_RELOCATABLE_TYPE); + +#ifndef QT_NO_DEBUG_STREAM +Q_GUI_EXPORT QDebug operator<<(QDebug, const QRhiScissor &); +#endif + +class Q_GUI_EXPORT QRhiVertexInputBinding +{ +public: + enum Classification { + PerVertex, + PerInstance + }; + + QRhiVertexInputBinding() = default; + QRhiVertexInputBinding(quint32 stride, Classification cls = PerVertex, quint32 stepRate = 1); + + quint32 stride() const { return m_stride; } + void setStride(quint32 s) { m_stride = s; } + + Classification classification() const { return m_classification; } + void setClassification(Classification c) { m_classification = c; } + + quint32 instanceStepRate() const { return m_instanceStepRate; } + void setInstanceStepRate(quint32 rate) { m_instanceStepRate = rate; } + +private: + quint32 m_stride = 0; + Classification m_classification = PerVertex; + quint32 m_instanceStepRate = 1; + + friend bool operator==(const QRhiVertexInputBinding &a, const QRhiVertexInputBinding &b) noexcept + { + return a.m_stride == b.m_stride + && a.m_classification == b.m_classification + && a.m_instanceStepRate == b.m_instanceStepRate; + } + + friend bool operator!=(const QRhiVertexInputBinding &a, const QRhiVertexInputBinding &b) noexcept + { + return !(a == b); + } + + friend size_t qHash(const QRhiVertexInputBinding &v, size_t seed = 0) noexcept + { + QtPrivate::QHashCombine hash; + seed = hash(seed, v.m_stride); + seed = hash(seed, v.m_classification); + seed = hash(seed, v.m_instanceStepRate); + return seed; + } +}; + +Q_DECLARE_TYPEINFO(QRhiVertexInputBinding, Q_RELOCATABLE_TYPE); + +#ifndef QT_NO_DEBUG_STREAM +Q_GUI_EXPORT QDebug operator<<(QDebug, const QRhiVertexInputBinding &); +#endif + +class Q_GUI_EXPORT QRhiVertexInputAttribute +{ +public: + enum Format { + Float4, + Float3, + Float2, + Float, + UNormByte4, + UNormByte2, + UNormByte, + UInt4, + UInt3, + UInt2, + UInt, + SInt4, + SInt3, + SInt2, + SInt, + Half4, + Half3, + Half2, + Half + }; + + QRhiVertexInputAttribute() = default; + QRhiVertexInputAttribute(int binding, int location, Format format, quint32 offset, int matrixSlice = -1); + + int binding() const { return m_binding; } + void setBinding(int b) { m_binding = b; } + + int location() const { return m_location; } + void setLocation(int loc) { m_location = loc; } + + Format format() const { return m_format; } + void setFormat(Format f) { m_format = f; } + + quint32 offset() const { return m_offset; } + void setOffset(quint32 ofs) { m_offset = ofs; } + + int matrixSlice() const { return m_matrixSlice; } + void setMatrixSlice(int slice) { m_matrixSlice = slice; } + +private: + int m_binding = 0; + int m_location = 0; + Format m_format = Float4; + quint32 m_offset = 0; + int m_matrixSlice = -1; + + friend bool operator==(const QRhiVertexInputAttribute &a, const QRhiVertexInputAttribute &b) noexcept + { + return a.m_binding == b.m_binding + && a.m_location == b.m_location + && a.m_format == b.m_format + && a.m_offset == b.m_offset; + // matrixSlice excluded intentionally + } + + friend bool operator!=(const QRhiVertexInputAttribute &a, const QRhiVertexInputAttribute &b) noexcept + { + return !(a == b); + } + + friend size_t qHash(const QRhiVertexInputAttribute &v, size_t seed = 0) noexcept + { + QtPrivate::QHashCombine hash; + seed = hash(seed, v.m_binding); + seed = hash(seed, v.m_location); + seed = hash(seed, v.m_format); + seed = hash(seed, v.m_offset); + return seed; + } +}; + +Q_DECLARE_TYPEINFO(QRhiVertexInputAttribute, Q_RELOCATABLE_TYPE); + +#ifndef QT_NO_DEBUG_STREAM +Q_GUI_EXPORT QDebug operator<<(QDebug, const QRhiVertexInputAttribute &); +#endif + +class Q_GUI_EXPORT QRhiVertexInputLayout +{ +public: + QRhiVertexInputLayout() = default; + + void setBindings(std::initializer_list list) { m_bindings = list; } + template + void setBindings(InputIterator first, InputIterator last) + { + m_bindings.clear(); + std::copy(first, last, std::back_inserter(m_bindings)); + } + const QRhiVertexInputBinding *cbeginBindings() const { return m_bindings.cbegin(); } + const QRhiVertexInputBinding *cendBindings() const { return m_bindings.cend(); } + const QRhiVertexInputBinding *bindingAt(qsizetype index) const { return &m_bindings.at(index); } + qsizetype bindingCount() const { return m_bindings.count(); } + + void setAttributes(std::initializer_list list) { m_attributes = list; } + template + void setAttributes(InputIterator first, InputIterator last) + { + m_attributes.clear(); + std::copy(first, last, std::back_inserter(m_attributes)); + } + const QRhiVertexInputAttribute *cbeginAttributes() const { return m_attributes.cbegin(); } + const QRhiVertexInputAttribute *cendAttributes() const { return m_attributes.cend(); } + const QRhiVertexInputAttribute *attributeAt(qsizetype index) const { return &m_attributes.at(index); } + qsizetype attributeCount() const { return m_attributes.count(); } + +private: + QVarLengthArray m_bindings; + QVarLengthArray m_attributes; + + friend bool operator==(const QRhiVertexInputLayout &a, const QRhiVertexInputLayout &b) noexcept + { + return a.m_bindings == b.m_bindings && a.m_attributes == b.m_attributes; + } + + friend bool operator!=(const QRhiVertexInputLayout &a, const QRhiVertexInputLayout &b) noexcept + { + return !(a == b); + } + + friend size_t qHash(const QRhiVertexInputLayout &v, size_t seed = 0) noexcept + { + QtPrivate::QHashCombine hash; + seed = hash(seed, v.m_bindings); + seed = hash(seed, v.m_attributes); + return seed; + } + + friend Q_GUI_EXPORT QDebug operator<<(QDebug, const QRhiVertexInputLayout &); +}; + +#ifndef QT_NO_DEBUG_STREAM +Q_GUI_EXPORT QDebug operator<<(QDebug, const QRhiVertexInputLayout &); +#endif + +class Q_GUI_EXPORT QRhiShaderStage +{ +public: + enum Type { + Vertex, + TessellationControl, + TessellationEvaluation, + Geometry, + Fragment, + Compute + }; + + QRhiShaderStage() = default; + QRhiShaderStage(Type type, const QShader &shader, + QShader::Variant v = QShader::StandardShader); + + Type type() const { return m_type; } + void setType(Type t) { m_type = t; } + + QShader shader() const { return m_shader; } + void setShader(const QShader &s) { m_shader = s; } + + QShader::Variant shaderVariant() const { return m_shaderVariant; } + void setShaderVariant(QShader::Variant v) { m_shaderVariant = v; } + +private: + Type m_type = Vertex; + QShader m_shader; + QShader::Variant m_shaderVariant = QShader::StandardShader; + + friend bool operator==(const QRhiShaderStage &a, const QRhiShaderStage &b) noexcept + { + return a.m_type == b.m_type + && a.m_shader == b.m_shader + && a.m_shaderVariant == b.m_shaderVariant; + } + + friend bool operator!=(const QRhiShaderStage &a, const QRhiShaderStage &b) noexcept + { + return !(a == b); + } + + friend size_t qHash(const QRhiShaderStage &v, size_t seed = 0) noexcept + { + QtPrivate::QHashCombine hash; + seed = hash(seed, v.m_type); + seed = hash(seed, v.m_shader); + seed = hash(seed, v.m_shaderVariant); + return seed; + } +}; + +Q_DECLARE_TYPEINFO(QRhiShaderStage, Q_RELOCATABLE_TYPE); + +#ifndef QT_NO_DEBUG_STREAM +Q_GUI_EXPORT QDebug operator<<(QDebug, const QRhiShaderStage &); +#endif + +using QRhiGraphicsShaderStage = QRhiShaderStage; + +class Q_GUI_EXPORT QRhiShaderResourceBinding +{ +public: + enum Type { + UniformBuffer, + SampledTexture, + Texture, + Sampler, + ImageLoad, + ImageStore, + ImageLoadStore, + BufferLoad, + BufferStore, + BufferLoadStore + }; + + enum StageFlag { + VertexStage = 1 << 0, + TessellationControlStage = 1 << 1, + TessellationEvaluationStage = 1 << 2, + GeometryStage = 1 << 3, + FragmentStage = 1 << 4, + ComputeStage = 1 << 5 + }; + Q_DECLARE_FLAGS(StageFlags, StageFlag) + + QRhiShaderResourceBinding() = default; + + bool isLayoutCompatible(const QRhiShaderResourceBinding &other) const; + + static QRhiShaderResourceBinding uniformBuffer(int binding, StageFlags stage, QRhiBuffer *buf); + static QRhiShaderResourceBinding uniformBuffer(int binding, StageFlags stage, QRhiBuffer *buf, quint32 offset, quint32 size); + static QRhiShaderResourceBinding uniformBufferWithDynamicOffset(int binding, StageFlags stage, QRhiBuffer *buf, quint32 size); + + static QRhiShaderResourceBinding sampledTexture(int binding, StageFlags stage, QRhiTexture *tex, QRhiSampler *sampler); + + struct TextureAndSampler { + QRhiTexture *tex; + QRhiSampler *sampler; + }; + static QRhiShaderResourceBinding sampledTextures(int binding, StageFlags stage, int count, const TextureAndSampler *texSamplers); + + static QRhiShaderResourceBinding texture(int binding, StageFlags stage, QRhiTexture *tex); + static QRhiShaderResourceBinding textures(int binding, StageFlags stage, int count, QRhiTexture **tex); + static QRhiShaderResourceBinding sampler(int binding, StageFlags stage, QRhiSampler *sampler); + + static QRhiShaderResourceBinding imageLoad(int binding, StageFlags stage, QRhiTexture *tex, int level); + static QRhiShaderResourceBinding imageStore(int binding, StageFlags stage, QRhiTexture *tex, int level); + static QRhiShaderResourceBinding imageLoadStore(int binding, StageFlags stage, QRhiTexture *tex, int level); + + static QRhiShaderResourceBinding bufferLoad(int binding, StageFlags stage, QRhiBuffer *buf); + static QRhiShaderResourceBinding bufferLoad(int binding, StageFlags stage, QRhiBuffer *buf, quint32 offset, quint32 size); + static QRhiShaderResourceBinding bufferStore(int binding, StageFlags stage, QRhiBuffer *buf); + static QRhiShaderResourceBinding bufferStore(int binding, StageFlags stage, QRhiBuffer *buf, quint32 offset, quint32 size); + static QRhiShaderResourceBinding bufferLoadStore(int binding, StageFlags stage, QRhiBuffer *buf); + static QRhiShaderResourceBinding bufferLoadStore(int binding, StageFlags stage, QRhiBuffer *buf, quint32 offset, quint32 size); + + struct Data + { + int binding; + QRhiShaderResourceBinding::StageFlags stage; + QRhiShaderResourceBinding::Type type; + struct UniformBufferData { + QRhiBuffer *buf; + quint32 offset; + quint32 maybeSize; + bool hasDynamicOffset; + }; + static const int MAX_TEX_SAMPLER_ARRAY_SIZE = 16; + struct TextureAndOrSamplerData { + int count; + TextureAndSampler texSamplers[MAX_TEX_SAMPLER_ARRAY_SIZE]; + }; + struct StorageImageData { + QRhiTexture *tex; + int level; + }; + struct StorageBufferData { + QRhiBuffer *buf; + quint32 offset; + quint32 maybeSize; + }; + union { + UniformBufferData ubuf; + TextureAndOrSamplerData stex; + StorageImageData simage; + StorageBufferData sbuf; + } u; + + int arraySize() const + { + return type == QRhiShaderResourceBinding::SampledTexture || type == QRhiShaderResourceBinding::Texture + ? u.stex.count + : 1; + } + + template + Output serialize(Output dst) const + { + // must write out exactly LAYOUT_DESC_ENTRIES_PER_BINDING elements here + *dst++ = quint32(binding); + *dst++ = quint32(stage); + *dst++ = quint32(type); + *dst++ = quint32(arraySize()); + return dst; + } + }; + + static const int LAYOUT_DESC_ENTRIES_PER_BINDING = 4; + + template + static void serializeLayoutDescription(const QRhiShaderResourceBinding *first, + const QRhiShaderResourceBinding *last, + Output dst) + { + while (first != last) { + dst = first->d.serialize(dst); + ++first; + } + } + +private: + Data d; + friend class QRhiImplementation; +}; + +Q_DECLARE_OPERATORS_FOR_FLAGS(QRhiShaderResourceBinding::StageFlags) + +Q_DECLARE_TYPEINFO(QRhiShaderResourceBinding, Q_PRIMITIVE_TYPE); + +Q_GUI_EXPORT bool operator==(const QRhiShaderResourceBinding &a, const QRhiShaderResourceBinding &b) noexcept; +Q_GUI_EXPORT bool operator!=(const QRhiShaderResourceBinding &a, const QRhiShaderResourceBinding &b) noexcept; +Q_GUI_EXPORT size_t qHash(const QRhiShaderResourceBinding &b, size_t seed = 0) noexcept; +#ifndef QT_NO_DEBUG_STREAM +Q_GUI_EXPORT QDebug operator<<(QDebug, const QRhiShaderResourceBinding &); +#endif + +class Q_GUI_EXPORT QRhiColorAttachment +{ +public: + QRhiColorAttachment() = default; + QRhiColorAttachment(QRhiTexture *texture); + QRhiColorAttachment(QRhiRenderBuffer *renderBuffer); + + QRhiTexture *texture() const { return m_texture; } + void setTexture(QRhiTexture *tex) { m_texture = tex; } + + QRhiRenderBuffer *renderBuffer() const { return m_renderBuffer; } + void setRenderBuffer(QRhiRenderBuffer *rb) { m_renderBuffer = rb; } + + int layer() const { return m_layer; } + void setLayer(int layer) { m_layer = layer; } + + int level() const { return m_level; } + void setLevel(int level) { m_level = level; } + + QRhiTexture *resolveTexture() const { return m_resolveTexture; } + void setResolveTexture(QRhiTexture *tex) { m_resolveTexture = tex; } + + int resolveLayer() const { return m_resolveLayer; } + void setResolveLayer(int layer) { m_resolveLayer = layer; } + + int resolveLevel() const { return m_resolveLevel; } + void setResolveLevel(int level) { m_resolveLevel = level; } + +private: + QRhiTexture *m_texture = nullptr; + QRhiRenderBuffer *m_renderBuffer = nullptr; + int m_layer = 0; + int m_level = 0; + QRhiTexture *m_resolveTexture = nullptr; + int m_resolveLayer = 0; + int m_resolveLevel = 0; +}; + +Q_DECLARE_TYPEINFO(QRhiColorAttachment, Q_RELOCATABLE_TYPE); + +class Q_GUI_EXPORT QRhiTextureRenderTargetDescription +{ +public: + QRhiTextureRenderTargetDescription() = default; + QRhiTextureRenderTargetDescription(const QRhiColorAttachment &colorAttachment); + QRhiTextureRenderTargetDescription(const QRhiColorAttachment &colorAttachment, QRhiRenderBuffer *depthStencilBuffer); + QRhiTextureRenderTargetDescription(const QRhiColorAttachment &colorAttachment, QRhiTexture *depthTexture); + + void setColorAttachments(std::initializer_list list) { m_colorAttachments = list; } + template + void setColorAttachments(InputIterator first, InputIterator last) + { + m_colorAttachments.clear(); + std::copy(first, last, std::back_inserter(m_colorAttachments)); + } + const QRhiColorAttachment *cbeginColorAttachments() const { return m_colorAttachments.cbegin(); } + const QRhiColorAttachment *cendColorAttachments() const { return m_colorAttachments.cend(); } + const QRhiColorAttachment *colorAttachmentAt(qsizetype index) const { return &m_colorAttachments.at(index); } + qsizetype colorAttachmentCount() const { return m_colorAttachments.count(); } + + QRhiRenderBuffer *depthStencilBuffer() const { return m_depthStencilBuffer; } + void setDepthStencilBuffer(QRhiRenderBuffer *renderBuffer) { m_depthStencilBuffer = renderBuffer; } + + QRhiTexture *depthTexture() const { return m_depthTexture; } + void setDepthTexture(QRhiTexture *texture) { m_depthTexture = texture; } + +private: + QVarLengthArray m_colorAttachments; + QRhiRenderBuffer *m_depthStencilBuffer = nullptr; + QRhiTexture *m_depthTexture = nullptr; +}; + +class Q_GUI_EXPORT QRhiTextureSubresourceUploadDescription +{ +public: + QRhiTextureSubresourceUploadDescription() = default; + explicit QRhiTextureSubresourceUploadDescription(const QImage &image); + QRhiTextureSubresourceUploadDescription(const void *data, quint32 size); + explicit QRhiTextureSubresourceUploadDescription(const QByteArray &data); + + QImage image() const { return m_image; } + void setImage(const QImage &image) { m_image = image; } + + QByteArray data() const { return m_data; } + void setData(const QByteArray &data) { m_data = data; } + + quint32 dataStride() const { return m_dataStride; } + void setDataStride(quint32 stride) { m_dataStride = stride; } + + QPoint destinationTopLeft() const { return m_destinationTopLeft; } + void setDestinationTopLeft(const QPoint &p) { m_destinationTopLeft = p; } + + QSize sourceSize() const { return m_sourceSize; } + void setSourceSize(const QSize &size) { m_sourceSize = size; } + + QPoint sourceTopLeft() const { return m_sourceTopLeft; } + void setSourceTopLeft(const QPoint &p) { m_sourceTopLeft = p; } + +private: + QImage m_image; + QByteArray m_data; + quint32 m_dataStride = 0; + QPoint m_destinationTopLeft; + QSize m_sourceSize; + QPoint m_sourceTopLeft; +}; + +Q_DECLARE_TYPEINFO(QRhiTextureSubresourceUploadDescription, Q_RELOCATABLE_TYPE); + +class Q_GUI_EXPORT QRhiTextureUploadEntry +{ +public: + QRhiTextureUploadEntry() = default; + QRhiTextureUploadEntry(int layer, int level, const QRhiTextureSubresourceUploadDescription &desc); + + int layer() const { return m_layer; } + void setLayer(int layer) { m_layer = layer; } + + int level() const { return m_level; } + void setLevel(int level) { m_level = level; } + + QRhiTextureSubresourceUploadDescription description() const { return m_desc; } + void setDescription(const QRhiTextureSubresourceUploadDescription &desc) { m_desc = desc; } + +private: + int m_layer = 0; + int m_level = 0; + QRhiTextureSubresourceUploadDescription m_desc; +}; + +Q_DECLARE_TYPEINFO(QRhiTextureUploadEntry, Q_RELOCATABLE_TYPE); + +class Q_GUI_EXPORT QRhiTextureUploadDescription +{ +public: + QRhiTextureUploadDescription() = default; + QRhiTextureUploadDescription(const QRhiTextureUploadEntry &entry); + QRhiTextureUploadDescription(std::initializer_list list); + + void setEntries(std::initializer_list list) { m_entries = list; } + template + void setEntries(InputIterator first, InputIterator last) + { + m_entries.clear(); + std::copy(first, last, std::back_inserter(m_entries)); + } + const QRhiTextureUploadEntry *cbeginEntries() const { return m_entries.cbegin(); } + const QRhiTextureUploadEntry *cendEntries() const { return m_entries.cend(); } + const QRhiTextureUploadEntry *entryAt(qsizetype index) const { return &m_entries.at(index); } + qsizetype entryCount() const { return m_entries.count(); } + +private: + QVarLengthArray m_entries; +}; + +class Q_GUI_EXPORT QRhiTextureCopyDescription +{ +public: + QRhiTextureCopyDescription() = default; + + QSize pixelSize() const { return m_pixelSize; } + void setPixelSize(const QSize &sz) { m_pixelSize = sz; } + + int sourceLayer() const { return m_sourceLayer; } + void setSourceLayer(int layer) { m_sourceLayer = layer; } + + int sourceLevel() const { return m_sourceLevel; } + void setSourceLevel(int level) { m_sourceLevel = level; } + + QPoint sourceTopLeft() const { return m_sourceTopLeft; } + void setSourceTopLeft(const QPoint &p) { m_sourceTopLeft = p; } + + int destinationLayer() const { return m_destinationLayer; } + void setDestinationLayer(int layer) { m_destinationLayer = layer; } + + int destinationLevel() const { return m_destinationLevel; } + void setDestinationLevel(int level) { m_destinationLevel = level; } + + QPoint destinationTopLeft() const { return m_destinationTopLeft; } + void setDestinationTopLeft(const QPoint &p) { m_destinationTopLeft = p; } + +private: + QSize m_pixelSize; + int m_sourceLayer = 0; + int m_sourceLevel = 0; + QPoint m_sourceTopLeft; + int m_destinationLayer = 0; + int m_destinationLevel = 0; + QPoint m_destinationTopLeft; +}; + +Q_DECLARE_TYPEINFO(QRhiTextureCopyDescription, Q_RELOCATABLE_TYPE); + +class Q_GUI_EXPORT QRhiReadbackDescription +{ +public: + QRhiReadbackDescription() = default; + QRhiReadbackDescription(QRhiTexture *texture); + + QRhiTexture *texture() const { return m_texture; } + void setTexture(QRhiTexture *tex) { m_texture = tex; } + + int layer() const { return m_layer; } + void setLayer(int layer) { m_layer = layer; } + + int level() const { return m_level; } + void setLevel(int level) { m_level = level; } + +private: + QRhiTexture *m_texture = nullptr; + int m_layer = 0; + int m_level = 0; +}; + +Q_DECLARE_TYPEINFO(QRhiReadbackDescription, Q_RELOCATABLE_TYPE); + +struct Q_GUI_EXPORT QRhiNativeHandles +{ +}; + +class Q_GUI_EXPORT QRhiResource +{ +public: + enum Type { + Buffer, + Texture, + Sampler, + RenderBuffer, + RenderPassDescriptor, + SwapChainRenderTarget, + TextureRenderTarget, + ShaderResourceBindings, + GraphicsPipeline, + SwapChain, + ComputePipeline, + CommandBuffer + }; + + virtual ~QRhiResource(); + + virtual Type resourceType() const = 0; + + virtual void destroy() = 0; + + void deleteLater(); + + QByteArray name() const; + void setName(const QByteArray &name); + + quint64 globalResourceId() const; + + QRhi *rhi() const; + +protected: + QRhiResource(QRhiImplementation *rhi); + Q_DISABLE_COPY(QRhiResource) + friend class QRhiImplementation; + QRhiImplementation *m_rhi = nullptr; + quint64 m_id; + QByteArray m_objectName; +}; + +class Q_GUI_EXPORT QRhiBuffer : public QRhiResource +{ +public: + enum Type { + Immutable, + Static, + Dynamic + }; + + enum UsageFlag { + VertexBuffer = 1 << 0, + IndexBuffer = 1 << 1, + UniformBuffer = 1 << 2, + StorageBuffer = 1 << 3 + }; + Q_DECLARE_FLAGS(UsageFlags, UsageFlag) + + struct NativeBuffer { + const void *objects[3]; + int slotCount; + }; + + QRhiResource::Type resourceType() const override; + + Type type() const { return m_type; } + void setType(Type t) { m_type = t; } + + UsageFlags usage() const { return m_usage; } + void setUsage(UsageFlags u) { m_usage = u; } + + quint32 size() const { return m_size; } + void setSize(quint32 sz) { m_size = sz; } + + virtual bool create() = 0; + + virtual NativeBuffer nativeBuffer(); + + virtual char *beginFullDynamicBufferUpdateForCurrentFrame(); + virtual void endFullDynamicBufferUpdateForCurrentFrame(); + +protected: + QRhiBuffer(QRhiImplementation *rhi, Type type_, UsageFlags usage_, quint32 size_); + Type m_type; + UsageFlags m_usage; + quint32 m_size; +}; + +Q_DECLARE_OPERATORS_FOR_FLAGS(QRhiBuffer::UsageFlags) + +class Q_GUI_EXPORT QRhiTexture : public QRhiResource +{ +public: + enum Flag { + RenderTarget = 1 << 0, + CubeMap = 1 << 2, + MipMapped = 1 << 3, + sRGB = 1 << 4, + UsedAsTransferSource = 1 << 5, + UsedWithGenerateMips = 1 << 6, + UsedWithLoadStore = 1 << 7, + UsedAsCompressedAtlas = 1 << 8, + ExternalOES = 1 << 9, + ThreeDimensional = 1 << 10, + TextureRectangleGL = 1 << 11, + TextureArray = 1 << 12, + OneDimensional = 1 << 13 + }; + Q_DECLARE_FLAGS(Flags, Flag) + + enum Format { + UnknownFormat, + + RGBA8, + BGRA8, + R8, + RG8, + R16, + RG16, + RED_OR_ALPHA8, + + RGBA16F, + RGBA32F, + R16F, + R32F, + + RGB10A2, + + D16, + D24, + D24S8, + D32F, + + BC1, + BC2, + BC3, + BC4, + BC5, + BC6H, + BC7, + + ETC2_RGB8, + ETC2_RGB8A1, + ETC2_RGBA8, + + ASTC_4x4, + ASTC_5x4, + ASTC_5x5, + ASTC_6x5, + ASTC_6x6, + ASTC_8x5, + ASTC_8x6, + ASTC_8x8, + ASTC_10x5, + ASTC_10x6, + ASTC_10x8, + ASTC_10x10, + ASTC_12x10, + ASTC_12x12 + }; + + struct NativeTexture { + quint64 object; + int layout; // or state + }; + + QRhiResource::Type resourceType() const override; + + Format format() const { return m_format; } + void setFormat(Format fmt) { m_format = fmt; } + + QSize pixelSize() const { return m_pixelSize; } + void setPixelSize(const QSize &sz) { m_pixelSize = sz; } + + int depth() const { return m_depth; } + void setDepth(int depth) { m_depth = depth; } + + int arraySize() const { return m_arraySize; } + void setArraySize(int arraySize) { m_arraySize = arraySize; } + + int arrayRangeStart() const { return m_arrayRangeStart; } + int arrayRangeLength() const { return m_arrayRangeLength; } + void setArrayRange(int startIndex, int count) + { + m_arrayRangeStart = startIndex; + m_arrayRangeLength = count; + } + + Flags flags() const { return m_flags; } + void setFlags(Flags f) { m_flags = f; } + + int sampleCount() const { return m_sampleCount; } + void setSampleCount(int s) { m_sampleCount = s; } + + virtual bool create() = 0; + virtual NativeTexture nativeTexture(); + virtual bool createFrom(NativeTexture src); + virtual void setNativeLayout(int layout); + +protected: + QRhiTexture(QRhiImplementation *rhi, Format format_, const QSize &pixelSize_, int depth_, + int arraySize_, int sampleCount_, Flags flags_); + Format m_format; + QSize m_pixelSize; + int m_depth; + int m_arraySize; + int m_sampleCount; + Flags m_flags; + int m_arrayRangeStart = -1; + int m_arrayRangeLength = -1; +}; + +Q_DECLARE_OPERATORS_FOR_FLAGS(QRhiTexture::Flags) + +class Q_GUI_EXPORT QRhiSampler : public QRhiResource +{ +public: + enum Filter { + None, + Nearest, + Linear + }; + + enum AddressMode { + Repeat, + ClampToEdge, + Mirror, + }; + + enum CompareOp { + Never, + Less, + Equal, + LessOrEqual, + Greater, + NotEqual, + GreaterOrEqual, + Always + }; + + QRhiResource::Type resourceType() const override; + + Filter magFilter() const { return m_magFilter; } + void setMagFilter(Filter f) { m_magFilter = f; } + + Filter minFilter() const { return m_minFilter; } + void setMinFilter(Filter f) { m_minFilter = f; } + + Filter mipmapMode() const { return m_mipmapMode; } + void setMipmapMode(Filter f) { m_mipmapMode = f; } + + AddressMode addressU() const { return m_addressU; } + void setAddressU(AddressMode mode) { m_addressU = mode; } + + AddressMode addressV() const { return m_addressV; } + void setAddressV(AddressMode mode) { m_addressV = mode; } + + AddressMode addressW() const { return m_addressW; } + void setAddressW(AddressMode mode) { m_addressW = mode; } + + CompareOp textureCompareOp() const { return m_compareOp; } + void setTextureCompareOp(CompareOp op) { m_compareOp = op; } + + virtual bool create() = 0; + +protected: + QRhiSampler(QRhiImplementation *rhi, + Filter magFilter_, Filter minFilter_, Filter mipmapMode_, + AddressMode u_, AddressMode v_, AddressMode w_); + Filter m_magFilter; + Filter m_minFilter; + Filter m_mipmapMode; + AddressMode m_addressU; + AddressMode m_addressV; + AddressMode m_addressW; + CompareOp m_compareOp; +}; + +class Q_GUI_EXPORT QRhiRenderBuffer : public QRhiResource +{ +public: + enum Type { + DepthStencil, + Color + }; + + enum Flag { + UsedWithSwapChainOnly = 1 << 0 + }; + Q_DECLARE_FLAGS(Flags, Flag) + + struct NativeRenderBuffer { + quint64 object; + }; + + QRhiResource::Type resourceType() const override; + + Type type() const { return m_type; } + void setType(Type t) { m_type = t; } + + QSize pixelSize() const { return m_pixelSize; } + void setPixelSize(const QSize &sz) { m_pixelSize = sz; } + + int sampleCount() const { return m_sampleCount; } + void setSampleCount(int s) { m_sampleCount = s; } + + Flags flags() const { return m_flags; } + void setFlags(Flags f) { m_flags = f; } + + virtual bool create() = 0; + virtual bool createFrom(NativeRenderBuffer src); + + virtual QRhiTexture::Format backingFormat() const = 0; + +protected: + QRhiRenderBuffer(QRhiImplementation *rhi, Type type_, const QSize &pixelSize_, + int sampleCount_, Flags flags_, QRhiTexture::Format backingFormatHint_); + Type m_type; + QSize m_pixelSize; + int m_sampleCount; + Flags m_flags; + QRhiTexture::Format m_backingFormatHint; +}; + +Q_DECLARE_OPERATORS_FOR_FLAGS(QRhiRenderBuffer::Flags) + +class Q_GUI_EXPORT QRhiRenderPassDescriptor : public QRhiResource +{ +public: + QRhiResource::Type resourceType() const override; + + virtual bool isCompatible(const QRhiRenderPassDescriptor *other) const = 0; + virtual const QRhiNativeHandles *nativeHandles(); + + virtual QRhiRenderPassDescriptor *newCompatibleRenderPassDescriptor() const = 0; + + virtual QVector serializedFormat() const = 0; + +protected: + QRhiRenderPassDescriptor(QRhiImplementation *rhi); +}; + +class Q_GUI_EXPORT QRhiRenderTarget : public QRhiResource +{ +public: + virtual QSize pixelSize() const = 0; + virtual float devicePixelRatio() const = 0; + virtual int sampleCount() const = 0; + + QRhiRenderPassDescriptor *renderPassDescriptor() const { return m_renderPassDesc; } + void setRenderPassDescriptor(QRhiRenderPassDescriptor *desc) { m_renderPassDesc = desc; } + +protected: + QRhiRenderTarget(QRhiImplementation *rhi); + QRhiRenderPassDescriptor *m_renderPassDesc = nullptr; +}; + +class Q_GUI_EXPORT QRhiSwapChainRenderTarget : public QRhiRenderTarget +{ +public: + QRhiResource::Type resourceType() const override; + QRhiSwapChain *swapChain() const { return m_swapchain; } + +protected: + QRhiSwapChainRenderTarget(QRhiImplementation *rhi, QRhiSwapChain *swapchain_); + QRhiSwapChain *m_swapchain; +}; + +class Q_GUI_EXPORT QRhiTextureRenderTarget : public QRhiRenderTarget +{ +public: + enum Flag { + PreserveColorContents = 1 << 0, + PreserveDepthStencilContents = 1 << 1 + }; + Q_DECLARE_FLAGS(Flags, Flag) + + QRhiResource::Type resourceType() const override; + + QRhiTextureRenderTargetDescription description() const { return m_desc; } + void setDescription(const QRhiTextureRenderTargetDescription &desc) { m_desc = desc; } + + Flags flags() const { return m_flags; } + void setFlags(Flags f) { m_flags = f; } + + virtual QRhiRenderPassDescriptor *newCompatibleRenderPassDescriptor() = 0; + + virtual bool create() = 0; + +protected: + QRhiTextureRenderTarget(QRhiImplementation *rhi, const QRhiTextureRenderTargetDescription &desc_, Flags flags_); + QRhiTextureRenderTargetDescription m_desc; + Flags m_flags; +}; + +Q_DECLARE_OPERATORS_FOR_FLAGS(QRhiTextureRenderTarget::Flags) + +class Q_GUI_EXPORT QRhiShaderResourceBindings : public QRhiResource +{ +public: + QRhiResource::Type resourceType() const override; + + void setBindings(std::initializer_list list) { m_bindings = list; } + template + void setBindings(InputIterator first, InputIterator last) + { + m_bindings.clear(); + std::copy(first, last, std::back_inserter(m_bindings)); + } + const QRhiShaderResourceBinding *cbeginBindings() const { return m_bindings.cbegin(); } + const QRhiShaderResourceBinding *cendBindings() const { return m_bindings.cend(); } + const QRhiShaderResourceBinding *bindingAt(qsizetype index) const { return &m_bindings.at(index); } + qsizetype bindingCount() const { return m_bindings.count(); } + + bool isLayoutCompatible(const QRhiShaderResourceBindings *other) const; + + QVector serializedLayoutDescription() const { return m_layoutDesc; } + + virtual bool create() = 0; + + enum UpdateFlag { + BindingsAreSorted = 0x01 + }; + Q_DECLARE_FLAGS(UpdateFlags, UpdateFlag) + + virtual void updateResources(UpdateFlags flags = {}) = 0; + +protected: + static const int BINDING_PREALLOC = 12; + QRhiShaderResourceBindings(QRhiImplementation *rhi); + QVarLengthArray m_bindings; + size_t m_layoutDescHash = 0; + // Intentionally not using QVLA for m_layoutDesc: clients like Qt Quick are much + // better served with an implicitly shared container here, because they will likely + // throw this directly into structs serving as cache keys. + QVector m_layoutDesc; + friend class QRhiImplementation; +#ifndef QT_NO_DEBUG_STREAM + friend Q_GUI_EXPORT QDebug operator<<(QDebug, const QRhiShaderResourceBindings &); +#endif +}; + +Q_DECLARE_OPERATORS_FOR_FLAGS(QRhiShaderResourceBindings::UpdateFlags) + +#ifndef QT_NO_DEBUG_STREAM +Q_GUI_EXPORT QDebug operator<<(QDebug, const QRhiShaderResourceBindings &); +#endif + +class Q_GUI_EXPORT QRhiGraphicsPipeline : public QRhiResource +{ +public: + enum Flag { + UsesBlendConstants = 1 << 0, + UsesStencilRef = 1 << 1, + UsesScissor = 1 << 2, + CompileShadersWithDebugInfo = 1 << 3 + }; + Q_DECLARE_FLAGS(Flags, Flag) + + enum Topology { + Triangles, + TriangleStrip, + TriangleFan, + Lines, + LineStrip, + Points, + Patches + }; + + enum CullMode { + None, + Front, + Back + }; + + enum FrontFace { + CCW, + CW + }; + + enum ColorMaskComponent { + R = 1 << 0, + G = 1 << 1, + B = 1 << 2, + A = 1 << 3 + }; + Q_DECLARE_FLAGS(ColorMask, ColorMaskComponent) + + enum BlendFactor { + Zero, + One, + SrcColor, + OneMinusSrcColor, + DstColor, + OneMinusDstColor, + SrcAlpha, + OneMinusSrcAlpha, + DstAlpha, + OneMinusDstAlpha, + ConstantColor, + OneMinusConstantColor, + ConstantAlpha, + OneMinusConstantAlpha, + SrcAlphaSaturate, + Src1Color, + OneMinusSrc1Color, + Src1Alpha, + OneMinusSrc1Alpha + }; + + enum BlendOp { + Add, + Subtract, + ReverseSubtract, + Min, + Max + }; + + struct TargetBlend { + ColorMask colorWrite = ColorMask(0xF); // R | G | B | A + bool enable = false; + BlendFactor srcColor = One; + BlendFactor dstColor = OneMinusSrcAlpha; + BlendOp opColor = Add; + BlendFactor srcAlpha = One; + BlendFactor dstAlpha = OneMinusSrcAlpha; + BlendOp opAlpha = Add; + }; + + enum CompareOp { + Never, + Less, + Equal, + LessOrEqual, + Greater, + NotEqual, + GreaterOrEqual, + Always + }; + + enum StencilOp { + StencilZero, + Keep, + Replace, + IncrementAndClamp, + DecrementAndClamp, + Invert, + IncrementAndWrap, + DecrementAndWrap + }; + + struct StencilOpState { + StencilOp failOp = Keep; + StencilOp depthFailOp = Keep; + StencilOp passOp = Keep; + CompareOp compareOp = Always; + }; + + enum PolygonMode { + Fill, + Line + }; + + QRhiResource::Type resourceType() const override; + + Flags flags() const { return m_flags; } + void setFlags(Flags f) { m_flags = f; } + + Topology topology() const { return m_topology; } + void setTopology(Topology t) { m_topology = t; } + + CullMode cullMode() const { return m_cullMode; } + void setCullMode(CullMode mode) { m_cullMode = mode; } + + FrontFace frontFace() const { return m_frontFace; } + void setFrontFace(FrontFace f) { m_frontFace = f; } + + void setTargetBlends(std::initializer_list list) { m_targetBlends = list; } + template + void setTargetBlends(InputIterator first, InputIterator last) + { + m_targetBlends.clear(); + std::copy(first, last, std::back_inserter(m_targetBlends)); + } + const TargetBlend *cbeginTargetBlends() const { return m_targetBlends.cbegin(); } + const TargetBlend *cendTargetBlends() const { return m_targetBlends.cend(); } + const TargetBlend *targetBlendAt(qsizetype index) const { return &m_targetBlends.at(index); } + qsizetype targetBlendCount() const { return m_targetBlends.count(); } + + bool hasDepthTest() const { return m_depthTest; } + void setDepthTest(bool enable) { m_depthTest = enable; } + + bool hasDepthWrite() const { return m_depthWrite; } + void setDepthWrite(bool enable) { m_depthWrite = enable; } + + CompareOp depthOp() const { return m_depthOp; } + void setDepthOp(CompareOp op) { m_depthOp = op; } + + bool hasStencilTest() const { return m_stencilTest; } + void setStencilTest(bool enable) { m_stencilTest = enable; } + + StencilOpState stencilFront() const { return m_stencilFront; } + void setStencilFront(const StencilOpState &state) { m_stencilFront = state; } + + StencilOpState stencilBack() const { return m_stencilBack; } + void setStencilBack(const StencilOpState &state) { m_stencilBack = state; } + + quint32 stencilReadMask() const { return m_stencilReadMask; } + void setStencilReadMask(quint32 mask) { m_stencilReadMask = mask; } + + quint32 stencilWriteMask() const { return m_stencilWriteMask; } + void setStencilWriteMask(quint32 mask) { m_stencilWriteMask = mask; } + + int sampleCount() const { return m_sampleCount; } + void setSampleCount(int s) { m_sampleCount = s; } + + float lineWidth() const { return m_lineWidth; } + void setLineWidth(float width) { m_lineWidth = width; } + + int depthBias() const { return m_depthBias; } + void setDepthBias(int bias) { m_depthBias = bias; } + + float slopeScaledDepthBias() const { return m_slopeScaledDepthBias; } + void setSlopeScaledDepthBias(float bias) { m_slopeScaledDepthBias = bias; } + + void setShaderStages(std::initializer_list list) { m_shaderStages = list; } + template + void setShaderStages(InputIterator first, InputIterator last) + { + m_shaderStages.clear(); + std::copy(first, last, std::back_inserter(m_shaderStages)); + } + const QRhiShaderStage *cbeginShaderStages() const { return m_shaderStages.cbegin(); } + const QRhiShaderStage *cendShaderStages() const { return m_shaderStages.cend(); } + const QRhiShaderStage *shaderStageAt(qsizetype index) const { return &m_shaderStages.at(index); } + qsizetype shaderStageCount() const { return m_shaderStages.count(); } + + QRhiVertexInputLayout vertexInputLayout() const { return m_vertexInputLayout; } + void setVertexInputLayout(const QRhiVertexInputLayout &layout) { m_vertexInputLayout = layout; } + + QRhiShaderResourceBindings *shaderResourceBindings() const { return m_shaderResourceBindings; } + void setShaderResourceBindings(QRhiShaderResourceBindings *srb) { m_shaderResourceBindings = srb; } + + QRhiRenderPassDescriptor *renderPassDescriptor() const { return m_renderPassDesc; } + void setRenderPassDescriptor(QRhiRenderPassDescriptor *desc) { m_renderPassDesc = desc; } + + int patchControlPointCount() const { return m_patchControlPointCount; } + void setPatchControlPointCount(int count) { m_patchControlPointCount = count; } + + PolygonMode polygonMode() const {return m_polygonMode; } + void setPolygonMode(PolygonMode mode) {m_polygonMode = mode; } + + virtual bool create() = 0; + +protected: + QRhiGraphicsPipeline(QRhiImplementation *rhi); + Flags m_flags; + Topology m_topology = Triangles; + CullMode m_cullMode = None; + FrontFace m_frontFace = CCW; + QVarLengthArray m_targetBlends; + bool m_depthTest = false; + bool m_depthWrite = false; + CompareOp m_depthOp = Less; + bool m_stencilTest = false; + StencilOpState m_stencilFront; + StencilOpState m_stencilBack; + quint32 m_stencilReadMask = 0xFF; + quint32 m_stencilWriteMask = 0xFF; + int m_sampleCount = 1; + float m_lineWidth = 1.0f; + int m_depthBias = 0; + float m_slopeScaledDepthBias = 0.0f; + int m_patchControlPointCount = 3; + PolygonMode m_polygonMode = Fill; + QVarLengthArray m_shaderStages; + QRhiVertexInputLayout m_vertexInputLayout; + QRhiShaderResourceBindings *m_shaderResourceBindings = nullptr; + QRhiRenderPassDescriptor *m_renderPassDesc = nullptr; +}; + +Q_DECLARE_OPERATORS_FOR_FLAGS(QRhiGraphicsPipeline::Flags) +Q_DECLARE_OPERATORS_FOR_FLAGS(QRhiGraphicsPipeline::ColorMask) +Q_DECLARE_TYPEINFO(QRhiGraphicsPipeline::TargetBlend, Q_RELOCATABLE_TYPE); + +struct QRhiSwapChainHdrInfo +{ + bool isHardCodedDefaults; + enum LimitsType { + LuminanceInNits, + ColorComponentValue + }; + LimitsType limitsType; + union { + struct { + float minLuminance; + float maxLuminance; + } luminanceInNits; + struct { + float maxColorComponentValue; + float maxPotentialColorComponentValue; + } colorComponentValue; + } limits; +}; + +Q_DECLARE_TYPEINFO(QRhiSwapChainHdrInfo, Q_RELOCATABLE_TYPE); + +#ifndef QT_NO_DEBUG_STREAM +Q_GUI_EXPORT QDebug operator<<(QDebug, const QRhiSwapChainHdrInfo &); +#endif + +struct QRhiSwapChainProxyData +{ + void *reserved[2] = {}; +}; + +class Q_GUI_EXPORT QRhiSwapChain : public QRhiResource +{ +public: + enum Flag { + SurfaceHasPreMulAlpha = 1 << 0, + SurfaceHasNonPreMulAlpha = 1 << 1, + sRGB = 1 << 2, + UsedAsTransferSource = 1 << 3, + NoVSync = 1 << 4, + MinimalBufferCount = 1 << 5 + }; + Q_DECLARE_FLAGS(Flags, Flag) + + enum Format { + SDR, + HDRExtendedSrgbLinear, + HDR10 + }; + + enum StereoTargetBuffer { + LeftBuffer, + RightBuffer + }; + + QRhiResource::Type resourceType() const override; + + QWindow *window() const { return m_window; } + void setWindow(QWindow *window) { m_window = window; } + + QRhiSwapChainProxyData proxyData() const { return m_proxyData; } + void setProxyData(const QRhiSwapChainProxyData &d) { m_proxyData = d; } + + Flags flags() const { return m_flags; } + void setFlags(Flags f) { m_flags = f; } + + Format format() const { return m_format; } + void setFormat(Format f) { m_format = f; } + + QRhiRenderBuffer *depthStencil() const { return m_depthStencil; } + void setDepthStencil(QRhiRenderBuffer *ds) { m_depthStencil = ds; } + + int sampleCount() const { return m_sampleCount; } + void setSampleCount(int samples) { m_sampleCount = samples; } + + QRhiRenderPassDescriptor *renderPassDescriptor() const { return m_renderPassDesc; } + void setRenderPassDescriptor(QRhiRenderPassDescriptor *desc) { m_renderPassDesc = desc; } + + QSize currentPixelSize() const { return m_currentPixelSize; } + + virtual QRhiCommandBuffer *currentFrameCommandBuffer() = 0; + virtual QRhiRenderTarget *currentFrameRenderTarget() = 0; + virtual QRhiRenderTarget *currentFrameRenderTarget(StereoTargetBuffer targetBuffer); + virtual QSize surfacePixelSize() = 0; + virtual bool isFormatSupported(Format f) = 0; + virtual QRhiRenderPassDescriptor *newCompatibleRenderPassDescriptor() = 0; + virtual bool createOrResize() = 0; + virtual QRhiSwapChainHdrInfo hdrInfo(); + +protected: + QRhiSwapChain(QRhiImplementation *rhi); + QWindow *m_window = nullptr; + Flags m_flags; + Format m_format = SDR; + QRhiRenderBuffer *m_depthStencil = nullptr; + int m_sampleCount = 1; + QRhiRenderPassDescriptor *m_renderPassDesc = nullptr; + QSize m_currentPixelSize; + QRhiSwapChainProxyData m_proxyData; +}; + +Q_DECLARE_OPERATORS_FOR_FLAGS(QRhiSwapChain::Flags) + +class Q_GUI_EXPORT QRhiComputePipeline : public QRhiResource +{ +public: + enum Flag { + CompileShadersWithDebugInfo = 1 << 0 + }; + Q_DECLARE_FLAGS(Flags, Flag) + + QRhiResource::Type resourceType() const override; + virtual bool create() = 0; + + Flags flags() const { return m_flags; } + void setFlags(Flags f) { m_flags = f; } + + QRhiShaderStage shaderStage() const { return m_shaderStage; } + void setShaderStage(const QRhiShaderStage &stage) { m_shaderStage = stage; } + + QRhiShaderResourceBindings *shaderResourceBindings() const { return m_shaderResourceBindings; } + void setShaderResourceBindings(QRhiShaderResourceBindings *srb) { m_shaderResourceBindings = srb; } + +protected: + QRhiComputePipeline(QRhiImplementation *rhi); + Flags m_flags; + QRhiShaderStage m_shaderStage; + QRhiShaderResourceBindings *m_shaderResourceBindings = nullptr; +}; + +Q_DECLARE_OPERATORS_FOR_FLAGS(QRhiComputePipeline::Flags) + +class Q_GUI_EXPORT QRhiCommandBuffer : public QRhiResource +{ +public: + enum IndexFormat { + IndexUInt16, + IndexUInt32 + }; + + enum BeginPassFlag { + ExternalContent = 0x01, + DoNotTrackResourcesForCompute = 0x02 + }; + Q_DECLARE_FLAGS(BeginPassFlags, BeginPassFlag) + + QRhiResource::Type resourceType() const override; + + void resourceUpdate(QRhiResourceUpdateBatch *resourceUpdates); + + void beginPass(QRhiRenderTarget *rt, + const QColor &colorClearValue, + const QRhiDepthStencilClearValue &depthStencilClearValue, + QRhiResourceUpdateBatch *resourceUpdates = nullptr, + BeginPassFlags flags = {}); + void endPass(QRhiResourceUpdateBatch *resourceUpdates = nullptr); + + void setGraphicsPipeline(QRhiGraphicsPipeline *ps); + using DynamicOffset = QPair; // binding, offset + void setShaderResources(QRhiShaderResourceBindings *srb = nullptr, + int dynamicOffsetCount = 0, + const DynamicOffset *dynamicOffsets = nullptr); + using VertexInput = QPair; // buffer, offset + void setVertexInput(int startBinding, int bindingCount, const VertexInput *bindings, + QRhiBuffer *indexBuf = nullptr, quint32 indexOffset = 0, + IndexFormat indexFormat = IndexUInt16); + + void setViewport(const QRhiViewport &viewport); + void setScissor(const QRhiScissor &scissor); + void setBlendConstants(const QColor &c); + void setStencilRef(quint32 refValue); + + void draw(quint32 vertexCount, + quint32 instanceCount = 1, + quint32 firstVertex = 0, + quint32 firstInstance = 0); + + void drawIndexed(quint32 indexCount, + quint32 instanceCount = 1, + quint32 firstIndex = 0, + qint32 vertexOffset = 0, + quint32 firstInstance = 0); + + void debugMarkBegin(const QByteArray &name); + void debugMarkEnd(); + void debugMarkMsg(const QByteArray &msg); + + void beginComputePass(QRhiResourceUpdateBatch *resourceUpdates = nullptr, BeginPassFlags flags = {}); + void endComputePass(QRhiResourceUpdateBatch *resourceUpdates = nullptr); + void setComputePipeline(QRhiComputePipeline *ps); + void dispatch(int x, int y, int z); + + const QRhiNativeHandles *nativeHandles(); + void beginExternal(); + void endExternal(); + + double lastCompletedGpuTime(); + +protected: + QRhiCommandBuffer(QRhiImplementation *rhi); +}; + +Q_DECLARE_OPERATORS_FOR_FLAGS(QRhiCommandBuffer::BeginPassFlags) + +struct Q_GUI_EXPORT QRhiReadbackResult +{ + std::function completed = nullptr; + QRhiTexture::Format format; + QSize pixelSize; + QByteArray data; +}; + +class Q_GUI_EXPORT QRhiResourceUpdateBatch +{ +public: + ~QRhiResourceUpdateBatch(); + + void release(); + + void merge(QRhiResourceUpdateBatch *other); + bool hasOptimalCapacity() const; + + void updateDynamicBuffer(QRhiBuffer *buf, quint32 offset, quint32 size, const void *data); + void uploadStaticBuffer(QRhiBuffer *buf, quint32 offset, quint32 size, const void *data); + void uploadStaticBuffer(QRhiBuffer *buf, const void *data); + void readBackBuffer(QRhiBuffer *buf, quint32 offset, quint32 size, QRhiReadbackResult *result); + void uploadTexture(QRhiTexture *tex, const QRhiTextureUploadDescription &desc); + void uploadTexture(QRhiTexture *tex, const QImage &image); + void copyTexture(QRhiTexture *dst, QRhiTexture *src, const QRhiTextureCopyDescription &desc = QRhiTextureCopyDescription()); + void readBackTexture(const QRhiReadbackDescription &rb, QRhiReadbackResult *result); + void generateMips(QRhiTexture *tex); + +private: + QRhiResourceUpdateBatch(QRhiImplementation *rhi); + Q_DISABLE_COPY(QRhiResourceUpdateBatch) + QRhiResourceUpdateBatchPrivate *d; + friend class QRhiResourceUpdateBatchPrivate; + friend class QRhi; +}; + +struct Q_GUI_EXPORT QRhiDriverInfo +{ + enum DeviceType { + UnknownDevice, + IntegratedDevice, + DiscreteDevice, + ExternalDevice, + VirtualDevice, + CpuDevice + }; + + QByteArray deviceName; + quint64 deviceId = 0; + quint64 vendorId = 0; + DeviceType deviceType = UnknownDevice; +}; + +Q_DECLARE_TYPEINFO(QRhiDriverInfo, Q_RELOCATABLE_TYPE); + +#ifndef QT_NO_DEBUG_STREAM +Q_GUI_EXPORT QDebug operator<<(QDebug, const QRhiDriverInfo &); +#endif + +struct Q_GUI_EXPORT QRhiStats +{ + qint64 totalPipelineCreationTime = 0; + // Vulkan or D3D12 memory allocator statistics + quint32 blockCount = 0; + quint32 allocCount = 0; + quint64 usedBytes = 0; + quint64 unusedBytes = 0; + // D3D12 only, from IDXGIAdapter3::QueryVideoMemoryInfo(), incl. all resources + quint64 totalUsageBytes = 0; +}; + +Q_DECLARE_TYPEINFO(QRhiStats, Q_RELOCATABLE_TYPE); + +#ifndef QT_NO_DEBUG_STREAM +Q_GUI_EXPORT QDebug operator<<(QDebug, const QRhiStats &); +#endif + +struct Q_GUI_EXPORT QRhiInitParams +{ +}; + +class Q_GUI_EXPORT QRhi +{ +public: + enum Implementation { + Null, + Vulkan, + OpenGLES2, + D3D11, + Metal, + D3D12 + }; + + enum Flag { + EnableDebugMarkers = 1 << 0, + PreferSoftwareRenderer = 1 << 1, + EnablePipelineCacheDataSave = 1 << 2, + EnableTimestamps = 1 << 3 + }; + Q_DECLARE_FLAGS(Flags, Flag) + + enum FrameOpResult { + FrameOpSuccess = 0, + FrameOpError, + FrameOpSwapChainOutOfDate, + FrameOpDeviceLost + }; + + enum Feature { + MultisampleTexture = 1, + MultisampleRenderBuffer, + DebugMarkers, + Timestamps, + Instancing, + CustomInstanceStepRate, + PrimitiveRestart, + NonDynamicUniformBuffers, + NonFourAlignedEffectiveIndexBufferOffset, + NPOTTextureRepeat, + RedOrAlpha8IsRed, + ElementIndexUint, + Compute, + WideLines, + VertexShaderPointSize, + BaseVertex, + BaseInstance, + TriangleFanTopology, + ReadBackNonUniformBuffer, + ReadBackNonBaseMipLevel, + TexelFetch, + RenderToNonBaseMipLevel, + IntAttributes, + ScreenSpaceDerivatives, + ReadBackAnyTextureFormat, + PipelineCacheDataLoadSave, + ImageDataStride, + RenderBufferImport, + ThreeDimensionalTextures, + RenderTo3DTextureSlice, + TextureArrays, + Tessellation, + GeometryShader, + TextureArrayRange, + NonFillPolygonMode, + OneDimensionalTextures, + OneDimensionalTextureMipmaps, + HalfAttributes, + RenderToOneDimensionalTexture, + ThreeDimensionalTextureMipmaps + }; + + enum BeginFrameFlag { + }; + Q_DECLARE_FLAGS(BeginFrameFlags, BeginFrameFlag) + + enum EndFrameFlag { + SkipPresent = 1 << 0 + }; + Q_DECLARE_FLAGS(EndFrameFlags, EndFrameFlag) + + enum ResourceLimit { + TextureSizeMin = 1, + TextureSizeMax, + MaxColorAttachments, + FramesInFlight, + MaxAsyncReadbackFrames, + MaxThreadGroupsPerDimension, + MaxThreadsPerThreadGroup, + MaxThreadGroupX, + MaxThreadGroupY, + MaxThreadGroupZ, + TextureArraySizeMax, + MaxUniformBufferRange, + MaxVertexInputs, + MaxVertexOutputs + }; + + ~QRhi(); + + static QRhi *create(Implementation impl, + QRhiInitParams *params, + Flags flags = {}, + QRhiNativeHandles *importDevice = nullptr); + static bool probe(Implementation impl, QRhiInitParams *params); + + Implementation backend() const; + const char *backendName() const; + static const char *backendName(Implementation impl); + QRhiDriverInfo driverInfo() const; + QThread *thread() const; + + using CleanupCallback = std::function; + void addCleanupCallback(const CleanupCallback &callback); + void runCleanup(); + + QRhiGraphicsPipeline *newGraphicsPipeline(); + QRhiComputePipeline *newComputePipeline(); + QRhiShaderResourceBindings *newShaderResourceBindings(); + + QRhiBuffer *newBuffer(QRhiBuffer::Type type, + QRhiBuffer::UsageFlags usage, + quint32 size); + + QRhiRenderBuffer *newRenderBuffer(QRhiRenderBuffer::Type type, + const QSize &pixelSize, + int sampleCount = 1, + QRhiRenderBuffer::Flags flags = {}, + QRhiTexture::Format backingFormatHint = QRhiTexture::UnknownFormat); + + QRhiTexture *newTexture(QRhiTexture::Format format, + const QSize &pixelSize, + int sampleCount = 1, + QRhiTexture::Flags flags = {}); + + QRhiTexture *newTexture(QRhiTexture::Format format, + int width, int height, int depth, + int sampleCount = 1, + QRhiTexture::Flags flags = {}); + + QRhiTexture *newTextureArray(QRhiTexture::Format format, + int arraySize, + const QSize &pixelSize, + int sampleCount = 1, + QRhiTexture::Flags flags = {}); + + QRhiSampler *newSampler(QRhiSampler::Filter magFilter, + QRhiSampler::Filter minFilter, + QRhiSampler::Filter mipmapMode, + QRhiSampler::AddressMode addressU, + QRhiSampler::AddressMode addressV, + QRhiSampler::AddressMode addressW = QRhiSampler::Repeat); + + QRhiTextureRenderTarget *newTextureRenderTarget(const QRhiTextureRenderTargetDescription &desc, + QRhiTextureRenderTarget::Flags flags = {}); + + QRhiSwapChain *newSwapChain(); + FrameOpResult beginFrame(QRhiSwapChain *swapChain, BeginFrameFlags flags = {}); + FrameOpResult endFrame(QRhiSwapChain *swapChain, EndFrameFlags flags = {}); + bool isRecordingFrame() const; + int currentFrameSlot() const; + + FrameOpResult beginOffscreenFrame(QRhiCommandBuffer **cb, BeginFrameFlags flags = {}); + FrameOpResult endOffscreenFrame(EndFrameFlags flags = {}); + + QRhi::FrameOpResult finish(); + + QRhiResourceUpdateBatch *nextResourceUpdateBatch(); + + QList supportedSampleCounts() const; + + int ubufAlignment() const; + int ubufAligned(int v) const; + + static int mipLevelsForSize(const QSize &size); + static QSize sizeForMipLevel(int mipLevel, const QSize &baseLevelSize); + + bool isYUpInFramebuffer() const; + bool isYUpInNDC() const; + bool isClipDepthZeroToOne() const; + + QMatrix4x4 clipSpaceCorrMatrix() const; + + bool isTextureFormatSupported(QRhiTexture::Format format, QRhiTexture::Flags flags = {}) const; + bool isFeatureSupported(QRhi::Feature feature) const; + int resourceLimit(ResourceLimit limit) const; + + const QRhiNativeHandles *nativeHandles(); + bool makeThreadLocalNativeContextCurrent(); + + static const int MAX_MIP_LEVELS = 16; // -> max width or height is 65536 + + void releaseCachedResources(); + + bool isDeviceLost() const; + + QByteArray pipelineCacheData(); + void setPipelineCacheData(const QByteArray &data); + + QRhiStats statistics() const; + + static QRhiSwapChainProxyData updateSwapChainProxyData(Implementation impl, QWindow *window); + +protected: + QRhi(); + +private: + Q_DISABLE_COPY(QRhi) + QRhiImplementation *d = nullptr; +}; + +Q_DECLARE_OPERATORS_FOR_FLAGS(QRhi::Flags) +Q_DECLARE_OPERATORS_FOR_FLAGS(QRhi::BeginFrameFlags) +Q_DECLARE_OPERATORS_FOR_FLAGS(QRhi::EndFrameFlags) + +QT_END_NAMESPACE + +#include + +#endif diff --git a/src/gui/rhi/qrhi_p.h b/src/gui/rhi/qrhi_p.h index de322d37..05df169a 100644 --- a/src/gui/rhi/qrhi_p.h +++ b/src/gui/rhi/qrhi_p.h @@ -1,8 +1,8 @@ -// Copyright (C) 2019 The Qt Company Ltd. +// 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 QRHI_H -#define QRHI_H +#ifndef QRHI_P_H +#define QRHI_P_H // // W A R N I N G @@ -15,1823 +15,789 @@ // We mean it. // -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include +#include +#include +#include +#include +#include +#include +#include QT_BEGIN_NAMESPACE -class QWindow; -class QRhi; -class QRhiImplementation; -class QRhiBuffer; -class QRhiRenderBuffer; -class QRhiTexture; -class QRhiSampler; -class QRhiCommandBuffer; -class QRhiResourceUpdateBatch; -class QRhiResourceUpdateBatchPrivate; -class QRhiSwapChain; +#define QRHI_RES(t, x) static_cast(x) +#define QRHI_RES_RHI(t) t *rhiD = static_cast(m_rhi) -class Q_GUI_EXPORT QRhiDepthStencilClearValue +Q_DECLARE_LOGGING_CATEGORY(QRHI_LOG_INFO) + +class QRhiImplementation { public: - QRhiDepthStencilClearValue() = default; - QRhiDepthStencilClearValue(float d, quint32 s); - - float depthClearValue() const { return m_d; } - void setDepthClearValue(float d) { m_d = d; } - - quint32 stencilClearValue() const { return m_s; } - void setStencilClearValue(quint32 s) { m_s = s; } - -private: - float m_d = 1.0f; - quint32 m_s = 0; -}; - -Q_DECLARE_TYPEINFO(QRhiDepthStencilClearValue, Q_RELOCATABLE_TYPE); - -Q_GUI_EXPORT bool operator==(const QRhiDepthStencilClearValue &a, const QRhiDepthStencilClearValue &b) noexcept; -Q_GUI_EXPORT bool operator!=(const QRhiDepthStencilClearValue &a, const QRhiDepthStencilClearValue &b) noexcept; -Q_GUI_EXPORT size_t qHash(const QRhiDepthStencilClearValue &v, size_t seed = 0) noexcept; -#ifndef QT_NO_DEBUG_STREAM -Q_GUI_EXPORT QDebug operator<<(QDebug, const QRhiDepthStencilClearValue &); -#endif - -class Q_GUI_EXPORT QRhiViewport -{ -public: - QRhiViewport() = default; - QRhiViewport(float x, float y, float w, float h, float minDepth = 0.0f, float maxDepth = 1.0f); - - std::array viewport() const { return m_rect; } - void setViewport(float x, float y, float w, float h) { - m_rect[0] = x; m_rect[1] = y; m_rect[2] = w; m_rect[3] = h; - } - - float minDepth() const { return m_minDepth; } - void setMinDepth(float minDepth) { m_minDepth = minDepth; } - - float maxDepth() const { return m_maxDepth; } - void setMaxDepth(float maxDepth) { m_maxDepth = maxDepth; } - -private: - std::array m_rect { { 0.0f, 0.0f, 0.0f, 0.0f } }; - float m_minDepth = 0.0f; - float m_maxDepth = 1.0f; -}; - -Q_DECLARE_TYPEINFO(QRhiViewport, Q_RELOCATABLE_TYPE); - -Q_GUI_EXPORT bool operator==(const QRhiViewport &a, const QRhiViewport &b) noexcept; -Q_GUI_EXPORT bool operator!=(const QRhiViewport &a, const QRhiViewport &b) noexcept; -Q_GUI_EXPORT size_t qHash(const QRhiViewport &v, size_t seed = 0) noexcept; -#ifndef QT_NO_DEBUG_STREAM -Q_GUI_EXPORT QDebug operator<<(QDebug, const QRhiViewport &); -#endif - -class Q_GUI_EXPORT QRhiScissor -{ -public: - QRhiScissor() = default; - QRhiScissor(int x, int y, int w, int h); - - std::array scissor() const { return m_rect; } - void setScissor(int x, int y, int w, int h) { - m_rect[0] = x; m_rect[1] = y; m_rect[2] = w; m_rect[3] = h; - } - -private: - std::array m_rect { { 0, 0, 0, 0 } }; -}; - -Q_DECLARE_TYPEINFO(QRhiScissor, Q_RELOCATABLE_TYPE); - -Q_GUI_EXPORT bool operator==(const QRhiScissor &a, const QRhiScissor &b) noexcept; -Q_GUI_EXPORT bool operator!=(const QRhiScissor &a, const QRhiScissor &b) noexcept; -Q_GUI_EXPORT size_t qHash(const QRhiScissor &v, size_t seed = 0) noexcept; -#ifndef QT_NO_DEBUG_STREAM -Q_GUI_EXPORT QDebug operator<<(QDebug, const QRhiScissor &); -#endif - -class Q_GUI_EXPORT QRhiVertexInputBinding -{ -public: - enum Classification { - PerVertex, - PerInstance - }; - - QRhiVertexInputBinding() = default; - QRhiVertexInputBinding(quint32 stride, Classification cls = PerVertex, quint32 stepRate = 1); - - quint32 stride() const { return m_stride; } - void setStride(quint32 s) { m_stride = s; } - - Classification classification() const { return m_classification; } - void setClassification(Classification c) { m_classification = c; } - - quint32 instanceStepRate() const { return m_instanceStepRate; } - void setInstanceStepRate(quint32 rate) { m_instanceStepRate = rate; } - -private: - quint32 m_stride = 0; - Classification m_classification = PerVertex; - quint32 m_instanceStepRate = 1; -}; - -Q_DECLARE_TYPEINFO(QRhiVertexInputBinding, Q_RELOCATABLE_TYPE); - -Q_GUI_EXPORT bool operator==(const QRhiVertexInputBinding &a, const QRhiVertexInputBinding &b) noexcept; -Q_GUI_EXPORT bool operator!=(const QRhiVertexInputBinding &a, const QRhiVertexInputBinding &b) noexcept; -Q_GUI_EXPORT size_t qHash(const QRhiVertexInputBinding &v, size_t seed = 0) noexcept; -#ifndef QT_NO_DEBUG_STREAM -Q_GUI_EXPORT QDebug operator<<(QDebug, const QRhiVertexInputBinding &); -#endif - -class Q_GUI_EXPORT QRhiVertexInputAttribute -{ -public: - enum Format { - Float4, - Float3, - Float2, - Float, - UNormByte4, - UNormByte2, - UNormByte, - UInt4, - UInt3, - UInt2, - UInt, - SInt4, - SInt3, - SInt2, - SInt - }; - - QRhiVertexInputAttribute() = default; - QRhiVertexInputAttribute(int binding, int location, Format format, quint32 offset, int matrixSlice = -1); - - int binding() const { return m_binding; } - void setBinding(int b) { m_binding = b; } - - int location() const { return m_location; } - void setLocation(int loc) { m_location = loc; } - - Format format() const { return m_format; } - void setFormat(Format f) { m_format = f; } - - quint32 offset() const { return m_offset; } - void setOffset(quint32 ofs) { m_offset = ofs; } - - int matrixSlice() const { return m_matrixSlice; } - void setMatrixSlice(int slice) { m_matrixSlice = slice; } - -private: - int m_binding = 0; - int m_location = 0; - Format m_format = Float4; - quint32 m_offset = 0; - int m_matrixSlice = -1; -}; - -Q_DECLARE_TYPEINFO(QRhiVertexInputAttribute, Q_RELOCATABLE_TYPE); - -Q_GUI_EXPORT bool operator==(const QRhiVertexInputAttribute &a, const QRhiVertexInputAttribute &b) noexcept; -Q_GUI_EXPORT bool operator!=(const QRhiVertexInputAttribute &a, const QRhiVertexInputAttribute &b) noexcept; -Q_GUI_EXPORT size_t qHash(const QRhiVertexInputAttribute &v, size_t seed = 0) noexcept; -#ifndef QT_NO_DEBUG_STREAM -Q_GUI_EXPORT QDebug operator<<(QDebug, const QRhiVertexInputAttribute &); -#endif - -class Q_GUI_EXPORT QRhiVertexInputLayout -{ -public: - QRhiVertexInputLayout() = default; - - void setBindings(std::initializer_list list) { m_bindings = list; } - template - void setBindings(InputIterator first, InputIterator last) - { - m_bindings.clear(); - std::copy(first, last, std::back_inserter(m_bindings)); - } - const QRhiVertexInputBinding *cbeginBindings() const { return m_bindings.cbegin(); } - const QRhiVertexInputBinding *cendBindings() const { return m_bindings.cend(); } - const QRhiVertexInputBinding *bindingAt(int index) const { return &m_bindings.at(index); } - - void setAttributes(std::initializer_list list) { m_attributes = list; } - template - void setAttributes(InputIterator first, InputIterator last) - { - m_attributes.clear(); - std::copy(first, last, std::back_inserter(m_attributes)); - } - const QRhiVertexInputAttribute *cbeginAttributes() const { return m_attributes.cbegin(); } - const QRhiVertexInputAttribute *cendAttributes() const { return m_attributes.cend(); } - -private: - QVarLengthArray m_bindings; - QVarLengthArray m_attributes; - - friend Q_GUI_EXPORT bool operator==(const QRhiVertexInputLayout &a, const QRhiVertexInputLayout &b) noexcept; - friend Q_GUI_EXPORT size_t qHash(const QRhiVertexInputLayout &v, size_t seed) noexcept; - friend Q_GUI_EXPORT QDebug operator<<(QDebug, const QRhiVertexInputLayout &); -}; - -Q_GUI_EXPORT bool operator==(const QRhiVertexInputLayout &a, const QRhiVertexInputLayout &b) noexcept; -Q_GUI_EXPORT bool operator!=(const QRhiVertexInputLayout &a, const QRhiVertexInputLayout &b) noexcept; -Q_GUI_EXPORT size_t qHash(const QRhiVertexInputLayout &v, size_t seed = 0) noexcept; -#ifndef QT_NO_DEBUG_STREAM -Q_GUI_EXPORT QDebug operator<<(QDebug, const QRhiVertexInputLayout &); -#endif - -class Q_GUI_EXPORT QRhiShaderStage -{ -public: - enum Type { - Vertex, - TessellationControl, - TessellationEvaluation, - Geometry, - Fragment, - Compute - }; - - QRhiShaderStage() = default; - QRhiShaderStage(Type type, const QShader &shader, - QShader::Variant v = QShader::StandardShader); - - Type type() const { return m_type; } - void setType(Type t) { m_type = t; } - - QShader shader() const { return m_shader; } - void setShader(const QShader &s) { m_shader = s; } - - QShader::Variant shaderVariant() const { return m_shaderVariant; } - void setShaderVariant(QShader::Variant v) { m_shaderVariant = v; } - -private: - Type m_type = Vertex; - QShader m_shader; - QShader::Variant m_shaderVariant = QShader::StandardShader; -}; - -Q_DECLARE_TYPEINFO(QRhiShaderStage, Q_RELOCATABLE_TYPE); - -Q_GUI_EXPORT bool operator==(const QRhiShaderStage &a, const QRhiShaderStage &b) noexcept; -Q_GUI_EXPORT bool operator!=(const QRhiShaderStage &a, const QRhiShaderStage &b) noexcept; -Q_GUI_EXPORT size_t qHash(const QRhiShaderStage &s, size_t seed = 0) noexcept; -#ifndef QT_NO_DEBUG_STREAM -Q_GUI_EXPORT QDebug operator<<(QDebug, const QRhiShaderStage &); -#endif - -using QRhiGraphicsShaderStage = QRhiShaderStage; - -class Q_GUI_EXPORT QRhiShaderResourceBinding -{ -public: - enum Type { - UniformBuffer, - SampledTexture, - Texture, - Sampler, - ImageLoad, - ImageStore, - ImageLoadStore, - BufferLoad, - BufferStore, - BufferLoadStore - }; - - enum StageFlag { - VertexStage = 1 << 0, - TessellationControlStage = 1 << 1, - TessellationEvaluationStage = 1 << 2, - GeometryStage = 1 << 3, - FragmentStage = 1 << 4, - ComputeStage = 1 << 5 - }; - Q_DECLARE_FLAGS(StageFlags, StageFlag) - - QRhiShaderResourceBinding() = default; - - bool isLayoutCompatible(const QRhiShaderResourceBinding &other) const; - - static QRhiShaderResourceBinding uniformBuffer(int binding, StageFlags stage, QRhiBuffer *buf); - static QRhiShaderResourceBinding uniformBuffer(int binding, StageFlags stage, QRhiBuffer *buf, quint32 offset, quint32 size); - static QRhiShaderResourceBinding uniformBufferWithDynamicOffset(int binding, StageFlags stage, QRhiBuffer *buf, quint32 size); - - static QRhiShaderResourceBinding sampledTexture(int binding, StageFlags stage, QRhiTexture *tex, QRhiSampler *sampler); - - struct TextureAndSampler { - QRhiTexture *tex; - QRhiSampler *sampler; - }; - static QRhiShaderResourceBinding sampledTextures(int binding, StageFlags stage, int count, const TextureAndSampler *texSamplers); - - static QRhiShaderResourceBinding texture(int binding, StageFlags stage, QRhiTexture *tex); - static QRhiShaderResourceBinding textures(int binding, StageFlags stage, int count, QRhiTexture **tex); - static QRhiShaderResourceBinding sampler(int binding, StageFlags stage, QRhiSampler *sampler); - - static QRhiShaderResourceBinding imageLoad(int binding, StageFlags stage, QRhiTexture *tex, int level); - static QRhiShaderResourceBinding imageStore(int binding, StageFlags stage, QRhiTexture *tex, int level); - static QRhiShaderResourceBinding imageLoadStore(int binding, StageFlags stage, QRhiTexture *tex, int level); - - static QRhiShaderResourceBinding bufferLoad(int binding, StageFlags stage, QRhiBuffer *buf); - static QRhiShaderResourceBinding bufferLoad(int binding, StageFlags stage, QRhiBuffer *buf, quint32 offset, quint32 size); - static QRhiShaderResourceBinding bufferStore(int binding, StageFlags stage, QRhiBuffer *buf); - static QRhiShaderResourceBinding bufferStore(int binding, StageFlags stage, QRhiBuffer *buf, quint32 offset, quint32 size); - static QRhiShaderResourceBinding bufferLoadStore(int binding, StageFlags stage, QRhiBuffer *buf); - static QRhiShaderResourceBinding bufferLoadStore(int binding, StageFlags stage, QRhiBuffer *buf, quint32 offset, quint32 size); - - struct Data - { - int binding; - QRhiShaderResourceBinding::StageFlags stage; - QRhiShaderResourceBinding::Type type; - struct UniformBufferData { - QRhiBuffer *buf; - quint32 offset; - quint32 maybeSize; - bool hasDynamicOffset; - }; - static const int MAX_TEX_SAMPLER_ARRAY_SIZE = 16; - struct TextureAndOrSamplerData { - int count; - TextureAndSampler texSamplers[MAX_TEX_SAMPLER_ARRAY_SIZE]; - }; - struct StorageImageData { - QRhiTexture *tex; - int level; - }; - struct StorageBufferData { - QRhiBuffer *buf; - quint32 offset; - quint32 maybeSize; - }; - union { - UniformBufferData ubuf; - TextureAndOrSamplerData stex; - StorageImageData simage; - StorageBufferData sbuf; - } u; - - int arraySize() const - { - return type == QRhiShaderResourceBinding::SampledTexture || type == QRhiShaderResourceBinding::Texture - ? u.stex.count - : 1; - } - - template - Output serialize(Output dst) const - { - // must write out exactly LAYOUT_DESC_ENTRIES_PER_BINDING elements here - *dst++ = quint32(binding); - *dst++ = quint32(stage); - *dst++ = quint32(type); - *dst++ = quint32(arraySize()); - return dst; - } - }; - - Data *data() { return &d; } - const Data *data() const { return &d; } - - static const int LAYOUT_DESC_ENTRIES_PER_BINDING = 4; - - template - static void serializeLayoutDescription(const QRhiShaderResourceBinding *first, - const QRhiShaderResourceBinding *last, - Output dst) - { - while (first != last) { - dst = first->data()->serialize(dst); - ++first; - } - } - -private: - Data d; -}; - -Q_DECLARE_OPERATORS_FOR_FLAGS(QRhiShaderResourceBinding::StageFlags) - -Q_DECLARE_TYPEINFO(QRhiShaderResourceBinding, Q_PRIMITIVE_TYPE); - -Q_GUI_EXPORT bool operator==(const QRhiShaderResourceBinding &a, const QRhiShaderResourceBinding &b) noexcept; -Q_GUI_EXPORT bool operator!=(const QRhiShaderResourceBinding &a, const QRhiShaderResourceBinding &b) noexcept; -Q_GUI_EXPORT size_t qHash(const QRhiShaderResourceBinding &b, size_t seed = 0) noexcept; -#ifndef QT_NO_DEBUG_STREAM -Q_GUI_EXPORT QDebug operator<<(QDebug, const QRhiShaderResourceBinding &); -#endif - -class Q_GUI_EXPORT QRhiColorAttachment -{ -public: - QRhiColorAttachment() = default; - QRhiColorAttachment(QRhiTexture *texture); - QRhiColorAttachment(QRhiRenderBuffer *renderBuffer); - - QRhiTexture *texture() const { return m_texture; } - void setTexture(QRhiTexture *tex) { m_texture = tex; } - - QRhiRenderBuffer *renderBuffer() const { return m_renderBuffer; } - void setRenderBuffer(QRhiRenderBuffer *rb) { m_renderBuffer = rb; } - - int layer() const { return m_layer; } - void setLayer(int layer) { m_layer = layer; } - - int level() const { return m_level; } - void setLevel(int level) { m_level = level; } - - QRhiTexture *resolveTexture() const { return m_resolveTexture; } - void setResolveTexture(QRhiTexture *tex) { m_resolveTexture = tex; } - - int resolveLayer() const { return m_resolveLayer; } - void setResolveLayer(int layer) { m_resolveLayer = layer; } - - int resolveLevel() const { return m_resolveLevel; } - void setResolveLevel(int level) { m_resolveLevel = level; } - -private: - QRhiTexture *m_texture = nullptr; - QRhiRenderBuffer *m_renderBuffer = nullptr; - int m_layer = 0; - int m_level = 0; - QRhiTexture *m_resolveTexture = nullptr; - int m_resolveLayer = 0; - int m_resolveLevel = 0; -}; - -Q_DECLARE_TYPEINFO(QRhiColorAttachment, Q_RELOCATABLE_TYPE); - -class Q_GUI_EXPORT QRhiTextureRenderTargetDescription -{ -public: - QRhiTextureRenderTargetDescription() = default; - QRhiTextureRenderTargetDescription(const QRhiColorAttachment &colorAttachment); - QRhiTextureRenderTargetDescription(const QRhiColorAttachment &colorAttachment, QRhiRenderBuffer *depthStencilBuffer); - QRhiTextureRenderTargetDescription(const QRhiColorAttachment &colorAttachment, QRhiTexture *depthTexture); - - void setColorAttachments(std::initializer_list list) { m_colorAttachments = list; } - template - void setColorAttachments(InputIterator first, InputIterator last) - { - m_colorAttachments.clear(); - std::copy(first, last, std::back_inserter(m_colorAttachments)); - } - const QRhiColorAttachment *cbeginColorAttachments() const { return m_colorAttachments.cbegin(); } - const QRhiColorAttachment *cendColorAttachments() const { return m_colorAttachments.cend(); } - const QRhiColorAttachment *colorAttachmentAt(int index) const { return &m_colorAttachments.at(index); } - - QRhiRenderBuffer *depthStencilBuffer() const { return m_depthStencilBuffer; } - void setDepthStencilBuffer(QRhiRenderBuffer *renderBuffer) { m_depthStencilBuffer = renderBuffer; } - - QRhiTexture *depthTexture() const { return m_depthTexture; } - void setDepthTexture(QRhiTexture *texture) { m_depthTexture = texture; } - -private: - QVarLengthArray m_colorAttachments; - QRhiRenderBuffer *m_depthStencilBuffer = nullptr; - QRhiTexture *m_depthTexture = nullptr; -}; - -class Q_GUI_EXPORT QRhiTextureSubresourceUploadDescription -{ -public: - QRhiTextureSubresourceUploadDescription() = default; - explicit QRhiTextureSubresourceUploadDescription(const QImage &image); - QRhiTextureSubresourceUploadDescription(const void *data, quint32 size); - explicit QRhiTextureSubresourceUploadDescription(const QByteArray &data); - - QImage image() const { return m_image; } - void setImage(const QImage &image) { m_image = image; } - - QByteArray data() const { return m_data; } - void setData(const QByteArray &data) { m_data = data; } - - quint32 dataStride() const { return m_dataStride; } - void setDataStride(quint32 stride) { m_dataStride = stride; } - - QPoint destinationTopLeft() const { return m_destinationTopLeft; } - void setDestinationTopLeft(const QPoint &p) { m_destinationTopLeft = p; } - - QSize sourceSize() const { return m_sourceSize; } - void setSourceSize(const QSize &size) { m_sourceSize = size; } - - QPoint sourceTopLeft() const { return m_sourceTopLeft; } - void setSourceTopLeft(const QPoint &p) { m_sourceTopLeft = p; } - -private: - QImage m_image; - QByteArray m_data; - quint32 m_dataStride = 0; - QPoint m_destinationTopLeft; - QSize m_sourceSize; - QPoint m_sourceTopLeft; -}; - -Q_DECLARE_TYPEINFO(QRhiTextureSubresourceUploadDescription, Q_RELOCATABLE_TYPE); - -class Q_GUI_EXPORT QRhiTextureUploadEntry -{ -public: - QRhiTextureUploadEntry() = default; - QRhiTextureUploadEntry(int layer, int level, const QRhiTextureSubresourceUploadDescription &desc); - - int layer() const { return m_layer; } - void setLayer(int layer) { m_layer = layer; } - - int level() const { return m_level; } - void setLevel(int level) { m_level = level; } - - QRhiTextureSubresourceUploadDescription description() const { return m_desc; } - void setDescription(const QRhiTextureSubresourceUploadDescription &desc) { m_desc = desc; } - -private: - int m_layer = 0; - int m_level = 0; - QRhiTextureSubresourceUploadDescription m_desc; -}; - -Q_DECLARE_TYPEINFO(QRhiTextureUploadEntry, Q_RELOCATABLE_TYPE); - -class Q_GUI_EXPORT QRhiTextureUploadDescription -{ -public: - QRhiTextureUploadDescription() = default; - QRhiTextureUploadDescription(const QRhiTextureUploadEntry &entry); - QRhiTextureUploadDescription(std::initializer_list list); - - void setEntries(std::initializer_list list) { m_entries = list; } - template - void setEntries(InputIterator first, InputIterator last) - { - m_entries.clear(); - std::copy(first, last, std::back_inserter(m_entries)); - } - const QRhiTextureUploadEntry *cbeginEntries() const { return m_entries.cbegin(); } - const QRhiTextureUploadEntry *cendEntries() const { return m_entries.cend(); } - -private: - QVarLengthArray m_entries; -}; - -class Q_GUI_EXPORT QRhiTextureCopyDescription -{ -public: - QRhiTextureCopyDescription() = default; - - QSize pixelSize() const { return m_pixelSize; } - void setPixelSize(const QSize &sz) { m_pixelSize = sz; } - - int sourceLayer() const { return m_sourceLayer; } - void setSourceLayer(int layer) { m_sourceLayer = layer; } - - int sourceLevel() const { return m_sourceLevel; } - void setSourceLevel(int level) { m_sourceLevel = level; } - - QPoint sourceTopLeft() const { return m_sourceTopLeft; } - void setSourceTopLeft(const QPoint &p) { m_sourceTopLeft = p; } - - int destinationLayer() const { return m_destinationLayer; } - void setDestinationLayer(int layer) { m_destinationLayer = layer; } - - int destinationLevel() const { return m_destinationLevel; } - void setDestinationLevel(int level) { m_destinationLevel = level; } - - QPoint destinationTopLeft() const { return m_destinationTopLeft; } - void setDestinationTopLeft(const QPoint &p) { m_destinationTopLeft = p; } - -private: - QSize m_pixelSize; - int m_sourceLayer = 0; - int m_sourceLevel = 0; - QPoint m_sourceTopLeft; - int m_destinationLayer = 0; - int m_destinationLevel = 0; - QPoint m_destinationTopLeft; -}; - -Q_DECLARE_TYPEINFO(QRhiTextureCopyDescription, Q_RELOCATABLE_TYPE); - -class Q_GUI_EXPORT QRhiReadbackDescription -{ -public: - QRhiReadbackDescription() = default; - QRhiReadbackDescription(QRhiTexture *texture); - - QRhiTexture *texture() const { return m_texture; } - void setTexture(QRhiTexture *tex) { m_texture = tex; } - - int layer() const { return m_layer; } - void setLayer(int layer) { m_layer = layer; } - - int level() const { return m_level; } - void setLevel(int level) { m_level = level; } - -private: - QRhiTexture *m_texture = nullptr; - int m_layer = 0; - int m_level = 0; -}; - -Q_DECLARE_TYPEINFO(QRhiReadbackDescription, Q_RELOCATABLE_TYPE); - -struct Q_GUI_EXPORT QRhiNativeHandles -{ -}; - -class Q_GUI_EXPORT QRhiResource -{ -public: - enum Type { - Buffer, - Texture, - Sampler, - RenderBuffer, - RenderPassDescriptor, - SwapChainRenderTarget, - TextureRenderTarget, - ShaderResourceBindings, - GraphicsPipeline, - SwapChain, - ComputePipeline, - CommandBuffer - }; - - virtual ~QRhiResource(); - - virtual Type resourceType() const = 0; + virtual ~QRhiImplementation(); + virtual bool create(QRhi::Flags flags) = 0; virtual void destroy() = 0; - void deleteLater(); + virtual QRhiGraphicsPipeline *createGraphicsPipeline() = 0; + virtual QRhiComputePipeline *createComputePipeline() = 0; + virtual QRhiShaderResourceBindings *createShaderResourceBindings() = 0; + virtual QRhiBuffer *createBuffer(QRhiBuffer::Type type, + QRhiBuffer::UsageFlags usage, + quint32 size) = 0; + virtual QRhiRenderBuffer *createRenderBuffer(QRhiRenderBuffer::Type type, + const QSize &pixelSize, + int sampleCount, + QRhiRenderBuffer::Flags flags, + QRhiTexture::Format backingFormatHint) = 0; + virtual QRhiTexture *createTexture(QRhiTexture::Format format, + const QSize &pixelSize, + int depth, + int arraySize, + int sampleCount, + QRhiTexture::Flags flags) = 0; + virtual QRhiSampler *createSampler(QRhiSampler::Filter magFilter, + QRhiSampler::Filter minFilter, + QRhiSampler::Filter mipmapMode, + QRhiSampler:: AddressMode u, + QRhiSampler::AddressMode v, + QRhiSampler::AddressMode w) = 0; - QByteArray name() const; - void setName(const QByteArray &name); + virtual QRhiTextureRenderTarget *createTextureRenderTarget(const QRhiTextureRenderTargetDescription &desc, + QRhiTextureRenderTarget::Flags flags) = 0; - quint64 globalResourceId() const; + virtual QRhiSwapChain *createSwapChain() = 0; + virtual QRhi::FrameOpResult beginFrame(QRhiSwapChain *swapChain, QRhi::BeginFrameFlags flags) = 0; + virtual QRhi::FrameOpResult endFrame(QRhiSwapChain *swapChain, QRhi::EndFrameFlags flags) = 0; + virtual QRhi::FrameOpResult beginOffscreenFrame(QRhiCommandBuffer **cb, QRhi::BeginFrameFlags flags) = 0; + virtual QRhi::FrameOpResult endOffscreenFrame(QRhi::EndFrameFlags flags) = 0; + virtual QRhi::FrameOpResult finish() = 0; - QRhi *rhi() const; + virtual void resourceUpdate(QRhiCommandBuffer *cb, QRhiResourceUpdateBatch *resourceUpdates) = 0; -protected: - QRhiResource(QRhiImplementation *rhi); - Q_DISABLE_COPY(QRhiResource) - friend class QRhiImplementation; - QRhiImplementation *m_rhi = nullptr; - quint64 m_id; - QByteArray m_objectName; -}; + virtual void beginPass(QRhiCommandBuffer *cb, + QRhiRenderTarget *rt, + const QColor &colorClearValue, + const QRhiDepthStencilClearValue &depthStencilClearValue, + QRhiResourceUpdateBatch *resourceUpdates, + QRhiCommandBuffer::BeginPassFlags flags) = 0; + virtual void endPass(QRhiCommandBuffer *cb, QRhiResourceUpdateBatch *resourceUpdates) = 0; -class Q_GUI_EXPORT QRhiBuffer : public QRhiResource -{ -public: - enum Type { - Immutable, - Static, - Dynamic - }; + virtual void setGraphicsPipeline(QRhiCommandBuffer *cb, + QRhiGraphicsPipeline *ps) = 0; - enum UsageFlag { - VertexBuffer = 1 << 0, - IndexBuffer = 1 << 1, - UniformBuffer = 1 << 2, - StorageBuffer = 1 << 3 - }; - Q_DECLARE_FLAGS(UsageFlags, UsageFlag) + virtual void setShaderResources(QRhiCommandBuffer *cb, + QRhiShaderResourceBindings *srb, + int dynamicOffsetCount, + const QRhiCommandBuffer::DynamicOffset *dynamicOffsets) = 0; - struct NativeBuffer { - const void *objects[3]; - int slotCount; - }; + virtual void setVertexInput(QRhiCommandBuffer *cb, + int startBinding, int bindingCount, const QRhiCommandBuffer::VertexInput *bindings, + QRhiBuffer *indexBuf, quint32 indexOffset, + QRhiCommandBuffer::IndexFormat indexFormat) = 0; - QRhiResource::Type resourceType() const override; + virtual void setViewport(QRhiCommandBuffer *cb, const QRhiViewport &viewport) = 0; + virtual void setScissor(QRhiCommandBuffer *cb, const QRhiScissor &scissor) = 0; + virtual void setBlendConstants(QRhiCommandBuffer *cb, const QColor &c) = 0; + virtual void setStencilRef(QRhiCommandBuffer *cb, quint32 refValue) = 0; - Type type() const { return m_type; } - void setType(Type t) { m_type = t; } + virtual void draw(QRhiCommandBuffer *cb, quint32 vertexCount, + quint32 instanceCount, quint32 firstVertex, quint32 firstInstance) = 0; + virtual void drawIndexed(QRhiCommandBuffer *cb, quint32 indexCount, + quint32 instanceCount, quint32 firstIndex, + qint32 vertexOffset, quint32 firstInstance) = 0; - UsageFlags usage() const { return m_usage; } - void setUsage(UsageFlags u) { m_usage = u; } + virtual void debugMarkBegin(QRhiCommandBuffer *cb, const QByteArray &name) = 0; + virtual void debugMarkEnd(QRhiCommandBuffer *cb) = 0; + virtual void debugMarkMsg(QRhiCommandBuffer *cb, const QByteArray &msg) = 0; - quint32 size() const { return m_size; } - void setSize(quint32 sz) { m_size = sz; } + virtual void beginComputePass(QRhiCommandBuffer *cb, + QRhiResourceUpdateBatch *resourceUpdates, + QRhiCommandBuffer::BeginPassFlags flags) = 0; + virtual void endComputePass(QRhiCommandBuffer *cb, QRhiResourceUpdateBatch *resourceUpdates) = 0; + virtual void setComputePipeline(QRhiCommandBuffer *cb, QRhiComputePipeline *ps) = 0; + virtual void dispatch(QRhiCommandBuffer *cb, int x, int y, int z) = 0; - virtual bool create() = 0; + virtual const QRhiNativeHandles *nativeHandles(QRhiCommandBuffer *cb) = 0; + virtual void beginExternal(QRhiCommandBuffer *cb) = 0; + virtual void endExternal(QRhiCommandBuffer *cb) = 0; + virtual double lastCompletedGpuTime(QRhiCommandBuffer *cb) = 0; - virtual NativeBuffer nativeBuffer(); + virtual QList supportedSampleCounts() const = 0; + virtual int ubufAlignment() const = 0; + virtual bool isYUpInFramebuffer() const = 0; + virtual bool isYUpInNDC() const = 0; + virtual bool isClipDepthZeroToOne() const = 0; + virtual QMatrix4x4 clipSpaceCorrMatrix() const = 0; + virtual bool isTextureFormatSupported(QRhiTexture::Format format, QRhiTexture::Flags flags) const = 0; + virtual bool isFeatureSupported(QRhi::Feature feature) const = 0; + virtual int resourceLimit(QRhi::ResourceLimit limit) const = 0; + virtual const QRhiNativeHandles *nativeHandles() = 0; + virtual QRhiDriverInfo driverInfo() const = 0; + virtual QRhiStats statistics() = 0; + virtual bool makeThreadLocalNativeContextCurrent() = 0; + virtual void releaseCachedResources() = 0; + virtual bool isDeviceLost() const = 0; - virtual char *beginFullDynamicBufferUpdateForCurrentFrame(); - virtual void endFullDynamicBufferUpdateForCurrentFrame(); + virtual QByteArray pipelineCacheData() = 0; + virtual void setPipelineCacheData(const QByteArray &data) = 0; -protected: - QRhiBuffer(QRhiImplementation *rhi, Type type_, UsageFlags usage_, quint32 size_); - Type m_type; - UsageFlags m_usage; - quint32 m_size; -}; + void prepareForCreate(QRhi *rhi, QRhi::Implementation impl, QRhi::Flags flags); -Q_DECLARE_OPERATORS_FOR_FLAGS(QRhiBuffer::UsageFlags) + bool isCompressedFormat(QRhiTexture::Format format) const; + void compressedFormatInfo(QRhiTexture::Format format, const QSize &size, + quint32 *bpl, quint32 *byteSize, + QSize *blockDim) const; + void textureFormatInfo(QRhiTexture::Format format, const QSize &size, + quint32 *bpl, quint32 *byteSize, quint32 *bytesPerPixel) const; -class Q_GUI_EXPORT QRhiTexture : public QRhiResource -{ -public: - enum Flag { - RenderTarget = 1 << 0, - CubeMap = 1 << 2, - MipMapped = 1 << 3, - sRGB = 1 << 4, - UsedAsTransferSource = 1 << 5, - UsedWithGenerateMips = 1 << 6, - UsedWithLoadStore = 1 << 7, - UsedAsCompressedAtlas = 1 << 8, - ExternalOES = 1 << 9, - ThreeDimensional = 1 << 10, - TextureRectangleGL = 1 << 11, - TextureArray = 1 << 12, - OneDimensional = 1 << 13 - }; - Q_DECLARE_FLAGS(Flags, Flag) - - enum Format { - UnknownFormat, - - RGBA8, - BGRA8, - R8, - RG8, - R16, - RG16, - RED_OR_ALPHA8, - - RGBA16F, - RGBA32F, - R16F, - R32F, - - RGB10A2, - - D16, - D24, - D24S8, - D32F, - - BC1, - BC2, - BC3, - BC4, - BC5, - BC6H, - BC7, - - ETC2_RGB8, - ETC2_RGB8A1, - ETC2_RGBA8, - - ASTC_4x4, - ASTC_5x4, - ASTC_5x5, - ASTC_6x5, - ASTC_6x6, - ASTC_8x5, - ASTC_8x6, - ASTC_8x8, - ASTC_10x5, - ASTC_10x6, - ASTC_10x8, - ASTC_10x10, - ASTC_12x10, - ASTC_12x12 - }; - - struct NativeTexture { - quint64 object; - int layout; - }; - - QRhiResource::Type resourceType() const override; - - Format format() const { return m_format; } - void setFormat(Format fmt) { m_format = fmt; } - - QSize pixelSize() const { return m_pixelSize; } - void setPixelSize(const QSize &sz) { m_pixelSize = sz; } - - int depth() const { return m_depth; } - void setDepth(int depth) { m_depth = depth; } - - int arraySize() const { return m_arraySize; } - void setArraySize(int arraySize) { m_arraySize = arraySize; } - - int arrayRangeStart() const { return m_arrayRangeStart; } - int arrayRangeLength() const { return m_arrayRangeLength; } - void setArrayRange(int startIndex, int count) + void registerResource(QRhiResource *res, bool ownsNativeResources = true) { - m_arrayRangeStart = startIndex; - m_arrayRangeLength = count; + // The ownsNativeResources is relevant for the (graphics resource) leak + // check in ~QRhiImplementation; when false, the registration's sole + // purpose is to automatically null out the resource's m_rhi pointer in + // case the rhi goes away first. (which should not happen in + // well-written applications but we try to be graceful) + resources.insert(res, ownsNativeResources); } - Flags flags() const { return m_flags; } - void setFlags(Flags f) { m_flags = f; } - - int sampleCount() const { return m_sampleCount; } - void setSampleCount(int s) { m_sampleCount = s; } - - virtual bool create() = 0; - virtual NativeTexture nativeTexture(); - virtual bool createFrom(NativeTexture src); - virtual void setNativeLayout(int layout); - -protected: - QRhiTexture(QRhiImplementation *rhi, Format format_, const QSize &pixelSize_, int depth_, - int arraySize_, int sampleCount_, Flags flags_); - Format m_format; - QSize m_pixelSize; - int m_depth; - int m_arraySize; - int m_sampleCount; - Flags m_flags; - int m_arrayRangeStart = -1; - int m_arrayRangeLength = -1; -}; - -Q_DECLARE_OPERATORS_FOR_FLAGS(QRhiTexture::Flags) - -class Q_GUI_EXPORT QRhiSampler : public QRhiResource -{ -public: - enum Filter { - None, - Nearest, - Linear - }; - - enum AddressMode { - Repeat, - ClampToEdge, - Mirror, - }; - - enum CompareOp { - Never, - Less, - Equal, - LessOrEqual, - Greater, - NotEqual, - GreaterOrEqual, - Always - }; - - QRhiResource::Type resourceType() const override; - - Filter magFilter() const { return m_magFilter; } - void setMagFilter(Filter f) { m_magFilter = f; } - - Filter minFilter() const { return m_minFilter; } - void setMinFilter(Filter f) { m_minFilter = f; } - - Filter mipmapMode() const { return m_mipmapMode; } - void setMipmapMode(Filter f) { m_mipmapMode = f; } - - AddressMode addressU() const { return m_addressU; } - void setAddressU(AddressMode mode) { m_addressU = mode; } - - AddressMode addressV() const { return m_addressV; } - void setAddressV(AddressMode mode) { m_addressV = mode; } - - AddressMode addressW() const { return m_addressW; } - void setAddressW(AddressMode mode) { m_addressW = mode; } - - CompareOp textureCompareOp() const { return m_compareOp; } - void setTextureCompareOp(CompareOp op) { m_compareOp = op; } - - virtual bool create() = 0; - -protected: - QRhiSampler(QRhiImplementation *rhi, - Filter magFilter_, Filter minFilter_, Filter mipmapMode_, - AddressMode u_, AddressMode v_, AddressMode w_); - Filter m_magFilter; - Filter m_minFilter; - Filter m_mipmapMode; - AddressMode m_addressU; - AddressMode m_addressV; - AddressMode m_addressW; - CompareOp m_compareOp; -}; - -class Q_GUI_EXPORT QRhiRenderBuffer : public QRhiResource -{ -public: - enum Type { - DepthStencil, - Color - }; - - enum Flag { - UsedWithSwapChainOnly = 1 << 0 - }; - Q_DECLARE_FLAGS(Flags, Flag) - - struct NativeRenderBuffer { - quint64 object; - }; - - QRhiResource::Type resourceType() const override; - - Type type() const { return m_type; } - void setType(Type t) { m_type = t; } - - QSize pixelSize() const { return m_pixelSize; } - void setPixelSize(const QSize &sz) { m_pixelSize = sz; } - - int sampleCount() const { return m_sampleCount; } - void setSampleCount(int s) { m_sampleCount = s; } - - Flags flags() const { return m_flags; } - void setFlags(Flags h) { m_flags = h; } - - virtual bool create() = 0; - virtual bool createFrom(NativeRenderBuffer src); - - virtual QRhiTexture::Format backingFormat() const = 0; - -protected: - QRhiRenderBuffer(QRhiImplementation *rhi, Type type_, const QSize &pixelSize_, - int sampleCount_, Flags flags_, QRhiTexture::Format backingFormatHint_); - Type m_type; - QSize m_pixelSize; - int m_sampleCount; - Flags m_flags; - QRhiTexture::Format m_backingFormatHint; -}; - -Q_DECLARE_OPERATORS_FOR_FLAGS(QRhiRenderBuffer::Flags) - -class Q_GUI_EXPORT QRhiRenderPassDescriptor : public QRhiResource -{ -public: - QRhiResource::Type resourceType() const override; - - virtual bool isCompatible(const QRhiRenderPassDescriptor *other) const = 0; - virtual const QRhiNativeHandles *nativeHandles(); - - virtual QRhiRenderPassDescriptor *newCompatibleRenderPassDescriptor() const = 0; - - virtual QVector serializedFormat() const = 0; - -protected: - QRhiRenderPassDescriptor(QRhiImplementation *rhi); -}; - -class Q_GUI_EXPORT QRhiRenderTarget : public QRhiResource -{ -public: - virtual QSize pixelSize() const = 0; - virtual float devicePixelRatio() const = 0; - virtual int sampleCount() const = 0; - - QRhiRenderPassDescriptor *renderPassDescriptor() const { return m_renderPassDesc; } - void setRenderPassDescriptor(QRhiRenderPassDescriptor *desc) { m_renderPassDesc = desc; } - -protected: - QRhiRenderTarget(QRhiImplementation *rhi); - QRhiRenderPassDescriptor *m_renderPassDesc = nullptr; -}; - -class Q_GUI_EXPORT QRhiSwapChainRenderTarget : public QRhiRenderTarget -{ -public: - QRhiResource::Type resourceType() const override; - QRhiSwapChain *swapChain() const { return m_swapchain; } - -protected: - QRhiSwapChainRenderTarget(QRhiImplementation *rhi, QRhiSwapChain *swapchain_); - QRhiSwapChain *m_swapchain; -}; - -class Q_GUI_EXPORT QRhiTextureRenderTarget : public QRhiRenderTarget -{ -public: - enum Flag { - PreserveColorContents = 1 << 0, - PreserveDepthStencilContents = 1 << 1 - }; - Q_DECLARE_FLAGS(Flags, Flag) - - QRhiResource::Type resourceType() const override; - - QRhiTextureRenderTargetDescription description() const { return m_desc; } - void setDescription(const QRhiTextureRenderTargetDescription &desc) { m_desc = desc; } - - Flags flags() const { return m_flags; } - void setFlags(Flags f) { m_flags = f; } - - virtual QRhiRenderPassDescriptor *newCompatibleRenderPassDescriptor() = 0; - - virtual bool create() = 0; - -protected: - QRhiTextureRenderTarget(QRhiImplementation *rhi, const QRhiTextureRenderTargetDescription &desc_, Flags flags_); - QRhiTextureRenderTargetDescription m_desc; - Flags m_flags; -}; - -Q_DECLARE_OPERATORS_FOR_FLAGS(QRhiTextureRenderTarget::Flags) - -class Q_GUI_EXPORT QRhiShaderResourceBindings : public QRhiResource -{ -public: - QRhiResource::Type resourceType() const override; - - void setBindings(std::initializer_list list) { m_bindings = list; } - - template - void setBindings(InputIterator first, InputIterator last) + void unregisterResource(QRhiResource *res) { - m_bindings.clear(); - std::copy(first, last, std::back_inserter(m_bindings)); + resources.remove(res); } - const QRhiShaderResourceBinding *cbeginBindings() const { return m_bindings.cbegin(); } - const QRhiShaderResourceBinding *cendBindings() const { return m_bindings.cend(); } - - bool isLayoutCompatible(const QRhiShaderResourceBindings *other) const; - - QVector serializedLayoutDescription() const { return m_layoutDesc; } - - virtual bool create() = 0; - - enum UpdateFlag { - BindingsAreSorted = 0x01 - }; - Q_DECLARE_FLAGS(UpdateFlags, UpdateFlag) - - virtual void updateResources(UpdateFlags flags = {}) = 0; - -protected: - static const int BINDING_PREALLOC = 12; - QRhiShaderResourceBindings(QRhiImplementation *rhi); - QVarLengthArray m_bindings; - size_t m_layoutDescHash = 0; - // Intentionally not using QVLA for m_layoutDesc: clients like Qt Quick are much - // better served with an implicitly shared container here, because they will likely - // throw this directly into structs serving as cache keys. - QVector m_layoutDesc; - friend class QRhiImplementation; -#ifndef QT_NO_DEBUG_STREAM - friend Q_GUI_EXPORT QDebug operator<<(QDebug, const QRhiShaderResourceBindings &); -#endif -}; - -Q_DECLARE_OPERATORS_FOR_FLAGS(QRhiShaderResourceBindings::UpdateFlags) - -#ifndef QT_NO_DEBUG_STREAM -Q_GUI_EXPORT QDebug operator<<(QDebug, const QRhiShaderResourceBindings &); -#endif - -class Q_GUI_EXPORT QRhiGraphicsPipeline : public QRhiResource -{ -public: - enum Flag { - UsesBlendConstants = 1 << 0, - UsesStencilRef = 1 << 1, - UsesScissor = 1 << 2, - CompileShadersWithDebugInfo = 1 << 3 - }; - Q_DECLARE_FLAGS(Flags, Flag) - - enum Topology { - Triangles, - TriangleStrip, - TriangleFan, - Lines, - LineStrip, - Points, - Patches - }; - - enum CullMode { - None, - Front, - Back - }; - - enum FrontFace { - CCW, - CW - }; - - enum ColorMaskComponent { - R = 1 << 0, - G = 1 << 1, - B = 1 << 2, - A = 1 << 3 - }; - Q_DECLARE_FLAGS(ColorMask, ColorMaskComponent) - - enum BlendFactor { - Zero, - One, - SrcColor, - OneMinusSrcColor, - DstColor, - OneMinusDstColor, - SrcAlpha, - OneMinusSrcAlpha, - DstAlpha, - OneMinusDstAlpha, - ConstantColor, - OneMinusConstantColor, - ConstantAlpha, - OneMinusConstantAlpha, - SrcAlphaSaturate, - Src1Color, - OneMinusSrc1Color, - Src1Alpha, - OneMinusSrc1Alpha - }; - - enum BlendOp { - Add, - Subtract, - ReverseSubtract, - Min, - Max - }; - - struct TargetBlend { - ColorMask colorWrite = ColorMask(0xF); // R | G | B | A - bool enable = false; - BlendFactor srcColor = One; - BlendFactor dstColor = OneMinusSrcAlpha; - BlendOp opColor = Add; - BlendFactor srcAlpha = One; - BlendFactor dstAlpha = OneMinusSrcAlpha; - BlendOp opAlpha = Add; - }; - - enum CompareOp { - Never, - Less, - Equal, - LessOrEqual, - Greater, - NotEqual, - GreaterOrEqual, - Always - }; - - enum StencilOp { - StencilZero, - Keep, - Replace, - IncrementAndClamp, - DecrementAndClamp, - Invert, - IncrementAndWrap, - DecrementAndWrap - }; - - struct StencilOpState { - StencilOp failOp = Keep; - StencilOp depthFailOp = Keep; - StencilOp passOp = Keep; - CompareOp compareOp = Always; - }; - - enum PolygonMode { - Fill, - Line - }; - - QRhiResource::Type resourceType() const override; - - Flags flags() const { return m_flags; } - void setFlags(Flags f) { m_flags = f; } - - Topology topology() const { return m_topology; } - void setTopology(Topology t) { m_topology = t; } - - CullMode cullMode() const { return m_cullMode; } - void setCullMode(CullMode mode) { m_cullMode = mode; } - - FrontFace frontFace() const { return m_frontFace; } - void setFrontFace(FrontFace f) { m_frontFace = f; } - - void setTargetBlends(std::initializer_list list) { m_targetBlends = list; } - template - void setTargetBlends(InputIterator first, InputIterator last) + void addDeleteLater(QRhiResource *res) { - m_targetBlends.clear(); - std::copy(first, last, std::back_inserter(m_targetBlends)); + if (inFrame) + pendingDeleteResources.insert(res); + else + delete res; } - const TargetBlend *cbeginTargetBlends() const { return m_targetBlends.cbegin(); } - const TargetBlend *cendTargetBlends() const { return m_targetBlends.cend(); } - bool hasDepthTest() const { return m_depthTest; } - void setDepthTest(bool enable) { m_depthTest = enable; } - - bool hasDepthWrite() const { return m_depthWrite; } - void setDepthWrite(bool enable) { m_depthWrite = enable; } - - CompareOp depthOp() const { return m_depthOp; } - void setDepthOp(CompareOp op) { m_depthOp = op; } - - bool hasStencilTest() const { return m_stencilTest; } - void setStencilTest(bool enable) { m_stencilTest = enable; } - - StencilOpState stencilFront() const { return m_stencilFront; } - void setStencilFront(const StencilOpState &state) { m_stencilFront = state; } - - StencilOpState stencilBack() const { return m_stencilBack; } - void setStencilBack(const StencilOpState &state) { m_stencilBack = state; } - - quint32 stencilReadMask() const { return m_stencilReadMask; } - void setStencilReadMask(quint32 mask) { m_stencilReadMask = mask; } - - quint32 stencilWriteMask() const { return m_stencilWriteMask; } - void setStencilWriteMask(quint32 mask) { m_stencilWriteMask = mask; } - - int sampleCount() const { return m_sampleCount; } - void setSampleCount(int s) { m_sampleCount = s; } - - float lineWidth() const { return m_lineWidth; } - void setLineWidth(float width) { m_lineWidth = width; } - - int depthBias() const { return m_depthBias; } - void setDepthBias(int bias) { m_depthBias = bias; } - - float slopeScaledDepthBias() const { return m_slopeScaledDepthBias; } - void setSlopeScaledDepthBias(float bias) { m_slopeScaledDepthBias = bias; } - - void setShaderStages(std::initializer_list list) { m_shaderStages = list; } - template - void setShaderStages(InputIterator first, InputIterator last) + void addCleanupCallback(const QRhi::CleanupCallback &callback) { - m_shaderStages.clear(); - std::copy(first, last, std::back_inserter(m_shaderStages)); + cleanupCallbacks.append(callback); } - const QRhiShaderStage *cbeginShaderStages() const { return m_shaderStages.cbegin(); } - const QRhiShaderStage *cendShaderStages() const { return m_shaderStages.cend(); } - QRhiVertexInputLayout vertexInputLayout() const { return m_vertexInputLayout; } - void setVertexInputLayout(const QRhiVertexInputLayout &layout) { m_vertexInputLayout = layout; } + bool sanityCheckGraphicsPipeline(QRhiGraphicsPipeline *ps); + bool sanityCheckShaderResourceBindings(QRhiShaderResourceBindings *srb); + void updateLayoutDesc(QRhiShaderResourceBindings *srb); - QRhiShaderResourceBindings *shaderResourceBindings() const { return m_shaderResourceBindings; } - void setShaderResourceBindings(QRhiShaderResourceBindings *srb) { m_shaderResourceBindings = srb; } + quint32 pipelineCacheRhiId() const + { + const quint32 ver = (QT_VERSION_MAJOR << 16) | (QT_VERSION_MINOR << 8) | (QT_VERSION_PATCH); + return (quint32(implType) << 24) | ver; + } - QRhiRenderPassDescriptor *renderPassDescriptor() const { return m_renderPassDesc; } - void setRenderPassDescriptor(QRhiRenderPassDescriptor *desc) { m_renderPassDesc = desc; } + void pipelineCreationStart() + { + pipelineCreationTimer.start(); + } - int patchControlPointCount() const { return m_patchControlPointCount; } - void setPatchControlPointCount(int count) { m_patchControlPointCount = count; } + void pipelineCreationEnd() + { + accumulatedPipelineCreationTime += pipelineCreationTimer.elapsed(); + } - PolygonMode polygonMode() const {return m_polygonMode; } - void setPolygonMode(PolygonMode mode) {m_polygonMode = mode; } + qint64 totalPipelineCreationTime() const + { + return accumulatedPipelineCreationTime; + } - virtual bool create() = 0; + QRhiVertexInputAttribute::Format shaderDescVariableFormatToVertexInputFormat(QShaderDescription::VariableType type) const; + quint32 byteSizePerVertexForVertexInputFormat(QRhiVertexInputAttribute::Format format) const; -protected: - QRhiGraphicsPipeline(QRhiImplementation *rhi); - Flags m_flags; - Topology m_topology = Triangles; - CullMode m_cullMode = None; - FrontFace m_frontFace = CCW; - QVarLengthArray m_targetBlends; - bool m_depthTest = false; - bool m_depthWrite = false; - CompareOp m_depthOp = Less; - bool m_stencilTest = false; - StencilOpState m_stencilFront; - StencilOpState m_stencilBack; - quint32 m_stencilReadMask = 0xFF; - quint32 m_stencilWriteMask = 0xFF; - int m_sampleCount = 1; - float m_lineWidth = 1.0f; - int m_depthBias = 0; - float m_slopeScaledDepthBias = 0.0f; - int m_patchControlPointCount = 3; - PolygonMode m_polygonMode = Fill; - QVarLengthArray m_shaderStages; - QRhiVertexInputLayout m_vertexInputLayout; - QRhiShaderResourceBindings *m_shaderResourceBindings = nullptr; - QRhiRenderPassDescriptor *m_renderPassDesc = nullptr; -}; + static const QRhiShaderResourceBinding::Data *shaderResourceBindingData(const QRhiShaderResourceBinding &binding) + { + return &binding.d; + } -Q_DECLARE_OPERATORS_FOR_FLAGS(QRhiGraphicsPipeline::Flags) -Q_DECLARE_OPERATORS_FOR_FLAGS(QRhiGraphicsPipeline::ColorMask) -Q_DECLARE_TYPEINFO(QRhiGraphicsPipeline::TargetBlend, Q_RELOCATABLE_TYPE); + static QRhiShaderResourceBinding::Data *shaderResourceBindingData(QRhiShaderResourceBinding &binding) + { + return &binding.d; + } -struct QRhiSwapChainHdrInfo -{ - bool isHardCodedDefaults; - enum LimitsType { - LuminanceInNits, - ColorComponentValue - }; - LimitsType limitsType; - union { - struct { - float minLuminance; - float maxLuminance; - } luminanceInNits; - struct { - float maxColorComponentValue; - } colorComponentValue; - } limits; -}; + static bool sortedBindingLessThan(const QRhiShaderResourceBinding &a, const QRhiShaderResourceBinding &b) + { + return a.d.binding < b.d.binding; + } -Q_DECLARE_TYPEINFO(QRhiSwapChainHdrInfo, Q_RELOCATABLE_TYPE); + QRhi *q; -#ifndef QT_NO_DEBUG_STREAM -Q_GUI_EXPORT QDebug operator<<(QDebug, const QRhiSwapChainHdrInfo &); -#endif + static const int MAX_SHADER_CACHE_ENTRIES = 128; -struct QRhiSwapChainProxyData -{ - void *reserved[2] = {}; -}; - -class Q_GUI_EXPORT QRhiSwapChain : public QRhiResource -{ -public: - enum Flag { - SurfaceHasPreMulAlpha = 1 << 0, - SurfaceHasNonPreMulAlpha = 1 << 1, - sRGB = 1 << 2, - UsedAsTransferSource = 1 << 3, - NoVSync = 1 << 4, - MinimalBufferCount = 1 << 5 - }; - Q_DECLARE_FLAGS(Flags, Flag) - - enum Format { - SDR, - HDRExtendedSrgbLinear, - HDR10 - }; - - enum StereoTargetBuffer { - LeftBuffer, - RightBuffer - }; - - QRhiResource::Type resourceType() const override; - - QWindow *window() const { return m_window; } - void setWindow(QWindow *window) { m_window = window; } - - QRhiSwapChainProxyData proxyData() const { return m_proxyData; } - void setProxyData(const QRhiSwapChainProxyData &d) { m_proxyData = d; } - - Flags flags() const { return m_flags; } - void setFlags(Flags f) { m_flags = f; } - - Format format() const { return m_format; } - void setFormat(Format f) { m_format = f; } - - QRhiRenderBuffer *depthStencil() const { return m_depthStencil; } - void setDepthStencil(QRhiRenderBuffer *ds) { m_depthStencil = ds; } - - int sampleCount() const { return m_sampleCount; } - void setSampleCount(int samples) { m_sampleCount = samples; } - - QRhiRenderPassDescriptor *renderPassDescriptor() const { return m_renderPassDesc; } - void setRenderPassDescriptor(QRhiRenderPassDescriptor *desc) { m_renderPassDesc = desc; } - - QSize currentPixelSize() const { return m_currentPixelSize; } - - virtual QRhiCommandBuffer *currentFrameCommandBuffer() = 0; - virtual QRhiRenderTarget *currentFrameRenderTarget() = 0; - virtual QRhiRenderTarget *currentFrameRenderTarget(StereoTargetBuffer targetBuffer); - virtual QSize surfacePixelSize() = 0; - virtual bool isFormatSupported(Format f) = 0; - virtual QRhiRenderPassDescriptor *newCompatibleRenderPassDescriptor() = 0; - virtual bool createOrResize() = 0; - virtual QRhiSwapChainHdrInfo hdrInfo(); - -protected: - QRhiSwapChain(QRhiImplementation *rhi); - QWindow *m_window = nullptr; - Flags m_flags; - Format m_format = SDR; - QRhiRenderBuffer *m_depthStencil = nullptr; - int m_sampleCount = 1; - QRhiRenderPassDescriptor *m_renderPassDesc = nullptr; - QSize m_currentPixelSize; - QRhiSwapChainProxyData m_proxyData; -}; - -Q_DECLARE_OPERATORS_FOR_FLAGS(QRhiSwapChain::Flags) - -class Q_GUI_EXPORT QRhiComputePipeline : public QRhiResource -{ -public: - enum Flag { - CompileShadersWithDebugInfo = 1 << 0 - }; - Q_DECLARE_FLAGS(Flags, Flag) - - QRhiResource::Type resourceType() const override; - virtual bool create() = 0; - - Flags flags() const { return m_flags; } - void setFlags(Flags f) { m_flags = f; } - - QRhiShaderStage shaderStage() const { return m_shaderStage; } - void setShaderStage(const QRhiShaderStage &stage) { m_shaderStage = stage; } - - QRhiShaderResourceBindings *shaderResourceBindings() const { return m_shaderResourceBindings; } - void setShaderResourceBindings(QRhiShaderResourceBindings *srb) { m_shaderResourceBindings = srb; } - -protected: - QRhiComputePipeline(QRhiImplementation *rhi); - Flags m_flags; - QRhiShaderStage m_shaderStage; - QRhiShaderResourceBindings *m_shaderResourceBindings = nullptr; -}; - -Q_DECLARE_OPERATORS_FOR_FLAGS(QRhiComputePipeline::Flags) - -class Q_GUI_EXPORT QRhiCommandBuffer : public QRhiResource -{ -public: - enum IndexFormat { - IndexUInt16, - IndexUInt32 - }; - - enum BeginPassFlag { - ExternalContent = 0x01, - DoNotTrackResourcesForCompute = 0x02 - }; - Q_DECLARE_FLAGS(BeginPassFlags, BeginPassFlag) - - QRhiResource::Type resourceType() const override; - - void resourceUpdate(QRhiResourceUpdateBatch *resourceUpdates); - - void beginPass(QRhiRenderTarget *rt, - const QColor &colorClearValue, - const QRhiDepthStencilClearValue &depthStencilClearValue, - QRhiResourceUpdateBatch *resourceUpdates = nullptr, - BeginPassFlags flags = {}); - void endPass(QRhiResourceUpdateBatch *resourceUpdates = nullptr); - - void setGraphicsPipeline(QRhiGraphicsPipeline *ps); - using DynamicOffset = QPair; // binding, offset - void setShaderResources(QRhiShaderResourceBindings *srb = nullptr, - int dynamicOffsetCount = 0, - const DynamicOffset *dynamicOffsets = nullptr); - using VertexInput = QPair; // buffer, offset - void setVertexInput(int startBinding, int bindingCount, const VertexInput *bindings, - QRhiBuffer *indexBuf = nullptr, quint32 indexOffset = 0, - IndexFormat indexFormat = IndexUInt16); - - void setViewport(const QRhiViewport &viewport); - void setScissor(const QRhiScissor &scissor); - void setBlendConstants(const QColor &c); - void setStencilRef(quint32 refValue); - - void draw(quint32 vertexCount, - quint32 instanceCount = 1, - quint32 firstVertex = 0, - quint32 firstInstance = 0); - - void drawIndexed(quint32 indexCount, - quint32 instanceCount = 1, - quint32 firstIndex = 0, - qint32 vertexOffset = 0, - quint32 firstInstance = 0); - - void debugMarkBegin(const QByteArray &name); - void debugMarkEnd(); - void debugMarkMsg(const QByteArray &msg); - - void beginComputePass(QRhiResourceUpdateBatch *resourceUpdates = nullptr, BeginPassFlags flags = {}); - void endComputePass(QRhiResourceUpdateBatch *resourceUpdates = nullptr); - void setComputePipeline(QRhiComputePipeline *ps); - void dispatch(int x, int y, int z); - - const QRhiNativeHandles *nativeHandles(); - void beginExternal(); - void endExternal(); - -protected: - QRhiCommandBuffer(QRhiImplementation *rhi); -}; - -Q_DECLARE_OPERATORS_FOR_FLAGS(QRhiCommandBuffer::BeginPassFlags) - -struct Q_GUI_EXPORT QRhiReadbackResult -{ - std::function completed = nullptr; - QRhiTexture::Format format; - QSize pixelSize; - QByteArray data; -}; - -struct Q_GUI_EXPORT QRhiBufferReadbackResult -{ - std::function completed = nullptr; - QByteArray data; -}; - -class Q_GUI_EXPORT QRhiResourceUpdateBatch -{ -public: - ~QRhiResourceUpdateBatch(); - - void release(); - - void merge(QRhiResourceUpdateBatch *other); - bool hasOptimalCapacity() const; - - void updateDynamicBuffer(QRhiBuffer *buf, quint32 offset, quint32 size, const void *data); - void uploadStaticBuffer(QRhiBuffer *buf, quint32 offset, quint32 size, const void *data); - void uploadStaticBuffer(QRhiBuffer *buf, const void *data); - void readBackBuffer(QRhiBuffer *buf, quint32 offset, quint32 size, QRhiBufferReadbackResult *result); - void uploadTexture(QRhiTexture *tex, const QRhiTextureUploadDescription &desc); - void uploadTexture(QRhiTexture *tex, const QImage &image); - void copyTexture(QRhiTexture *dst, QRhiTexture *src, const QRhiTextureCopyDescription &desc = QRhiTextureCopyDescription()); - void readBackTexture(const QRhiReadbackDescription &rb, QRhiReadbackResult *result); - void generateMips(QRhiTexture *tex); + bool debugMarkers = false; + int currentFrameSlot = 0; // for vk, mtl, and similar. unused by gl and d3d11. + bool inFrame = false; private: - QRhiResourceUpdateBatch(QRhiImplementation *rhi); - Q_DISABLE_COPY(QRhiResourceUpdateBatch) - QRhiResourceUpdateBatchPrivate *d; - friend class QRhiResourceUpdateBatchPrivate; + QRhi::Implementation implType; + QThread *implThread; + QVarLengthArray resUpdPool; + quint64 resUpdPoolMap = 0; + int lastResUpdIdx = -1; + QHash resources; + QSet pendingDeleteResources; + QVarLengthArray cleanupCallbacks; + QElapsedTimer pipelineCreationTimer; + qint64 accumulatedPipelineCreationTime = 0; + friend class QRhi; + friend class QRhiResourceUpdateBatchPrivate; }; -struct Q_GUI_EXPORT QRhiDriverInfo +enum QRhiTargetRectBoundMode { - enum DeviceType { - UnknownDevice, - IntegratedDevice, - DiscreteDevice, - ExternalDevice, - VirtualDevice, - CpuDevice - }; - - QByteArray deviceName; - quint64 deviceId = 0; - quint64 vendorId = 0; - DeviceType deviceType = UnknownDevice; + UnBounded, + Bounded }; -Q_DECLARE_TYPEINFO(QRhiDriverInfo, Q_RELOCATABLE_TYPE); - -#ifndef QT_NO_DEBUG_STREAM -Q_GUI_EXPORT QDebug operator<<(QDebug, const QRhiDriverInfo &); -#endif - -struct Q_GUI_EXPORT QRhiStats +template +bool qrhi_toTopLeftRenderTargetRect(const QSize &outputSize, const std::array &r, + T *x, T *y, T *w, T *h) { - qint64 totalPipelineCreationTime = 0; - quint32 blockCount = 0; - quint32 allocCount = 0; - quint64 usedBytes = 0; - quint64 unusedBytes = 0; -}; + // x,y are bottom-left in QRhiScissor and QRhiViewport but top-left in + // Vulkan/Metal/D3D. Our input is an OpenGL-style scissor rect where both + // negative x or y, and partly or completely out of bounds rects are + // allowed. The only thing the input here cannot have is a negative width + // or height. We must handle all other input gracefully, clamping to a zero + // width or height rect in the worst case, and ensuring the resulting rect + // is inside the rendertarget's bounds because some APIs' validation/debug + // layers are allergic to out of bounds scissor rects. -Q_DECLARE_TYPEINFO(QRhiStats, Q_RELOCATABLE_TYPE); + const T outputWidth = outputSize.width(); + const T outputHeight = outputSize.height(); + const T inputWidth = r[2]; + const T inputHeight = r[3]; -#ifndef QT_NO_DEBUG_STREAM -Q_GUI_EXPORT QDebug operator<<(QDebug, const QRhiStats &); -#endif + if (inputWidth < 0 || inputHeight < 0) + return false; -struct Q_GUI_EXPORT QRhiInitParams + *x = r[0]; + *y = outputHeight - (r[1] + inputHeight); + *w = inputWidth; + *h = inputHeight; + + if (boundingMode == Bounded) { + const T widthOffset = *x < 0 ? -*x : 0; + const T heightOffset = *y < 0 ? -*y : 0; + *w = *x < outputWidth ? qMax(0, inputWidth - widthOffset) : 0; + *h = *y < outputHeight ? qMax(0, inputHeight - heightOffset) : 0; + + if (outputWidth > 0) + *x = qBound(0, *x, outputWidth - 1); + if (outputHeight > 0) + *y = qBound(0, *y, outputHeight - 1); + + if (*x + *w > outputWidth) + *w = qMax(0, outputWidth - *x); + if (*y + *h > outputHeight) + *h = qMax(0, outputHeight - *y); + } + return true; +} + +struct QRhiBufferDataPrivate { + Q_DISABLE_COPY_MOVE(QRhiBufferDataPrivate) + QRhiBufferDataPrivate() { } + ~QRhiBufferDataPrivate() { delete[] largeData; } + int ref = 1; + quint32 size = 0; + quint32 largeAlloc = 0; + char *largeData = nullptr; + static constexpr quint32 SMALL_DATA_SIZE = 1024; + char data[SMALL_DATA_SIZE]; }; -class Q_GUI_EXPORT QRhi +// no detach-with-contents, no atomic refcount, no shrink +class QRhiBufferData { public: - enum Implementation { - Null, - Vulkan, - OpenGLES2, - D3D11, - Metal - }; - - enum Flag { - EnableProfiling = 1 << 0, - EnableDebugMarkers = 1 << 1, - PreferSoftwareRenderer = 1 << 2, - EnablePipelineCacheDataSave = 1 << 3 - }; - Q_DECLARE_FLAGS(Flags, Flag) - - enum FrameOpResult { - FrameOpSuccess = 0, - FrameOpError, - FrameOpSwapChainOutOfDate, - FrameOpDeviceLost - }; - - enum Feature { - MultisampleTexture = 1, - MultisampleRenderBuffer, - DebugMarkers, - Timestamps, - Instancing, - CustomInstanceStepRate, - PrimitiveRestart, - NonDynamicUniformBuffers, - NonFourAlignedEffectiveIndexBufferOffset, - NPOTTextureRepeat, - RedOrAlpha8IsRed, - ElementIndexUint, - Compute, - WideLines, - VertexShaderPointSize, - BaseVertex, - BaseInstance, - TriangleFanTopology, - ReadBackNonUniformBuffer, - ReadBackNonBaseMipLevel, - TexelFetch, - RenderToNonBaseMipLevel, - IntAttributes, - ScreenSpaceDerivatives, - ReadBackAnyTextureFormat, - PipelineCacheDataLoadSave, - ImageDataStride, - RenderBufferImport, - ThreeDimensionalTextures, - RenderTo3DTextureSlice, - TextureArrays, - Tessellation, - GeometryShader, - TextureArrayRange, - NonFillPolygonMode, - OneDimensionalTextures, - OneDimensionalTextureMipmaps - }; - - enum BeginFrameFlag { - }; - Q_DECLARE_FLAGS(BeginFrameFlags, BeginFrameFlag) - - enum EndFrameFlag { - SkipPresent = 1 << 0 - }; - Q_DECLARE_FLAGS(EndFrameFlags, EndFrameFlag) - - enum ResourceLimit { - TextureSizeMin = 1, - TextureSizeMax, - MaxColorAttachments, - FramesInFlight, - MaxAsyncReadbackFrames, - MaxThreadGroupsPerDimension, - MaxThreadsPerThreadGroup, - MaxThreadGroupX, - MaxThreadGroupY, - MaxThreadGroupZ, - TextureArraySizeMax, - MaxUniformBufferRange, - MaxVertexInputs, - MaxVertexOutputs - }; - - ~QRhi(); - - static QRhi *create(Implementation impl, - QRhiInitParams *params, - Flags flags = {}, - QRhiNativeHandles *importDevice = nullptr); - static bool probe(Implementation impl, QRhiInitParams *params); - - Implementation backend() const; - const char *backendName() const; - static const char *backendName(Implementation impl); - QRhiDriverInfo driverInfo() const; - QThread *thread() const; - - using CleanupCallback = std::function; - void addCleanupCallback(const CleanupCallback &callback); - void runCleanup(); - - using GpuFrameTimeCallback = std::function; - void addGpuFrameTimeCallback(const GpuFrameTimeCallback &callback); - - QRhiGraphicsPipeline *newGraphicsPipeline(); - QRhiComputePipeline *newComputePipeline(); - QRhiShaderResourceBindings *newShaderResourceBindings(); - - QRhiBuffer *newBuffer(QRhiBuffer::Type type, - QRhiBuffer::UsageFlags usage, - quint32 size); - - QRhiRenderBuffer *newRenderBuffer(QRhiRenderBuffer::Type type, - const QSize &pixelSize, - int sampleCount = 1, - QRhiRenderBuffer::Flags flags = {}, - QRhiTexture::Format backingFormatHint = QRhiTexture::UnknownFormat); - - QRhiTexture *newTexture(QRhiTexture::Format format, - const QSize &pixelSize, - int sampleCount = 1, - QRhiTexture::Flags flags = {}); - - QRhiTexture *newTexture(QRhiTexture::Format format, - int width, int height, int depth, - int sampleCount = 1, - QRhiTexture::Flags flags = {}); - - QRhiTexture *newTextureArray(QRhiTexture::Format format, - int arraySize, - const QSize &pixelSize, - int sampleCount = 1, - QRhiTexture::Flags flags = {}); - - QRhiSampler *newSampler(QRhiSampler::Filter magFilter, - QRhiSampler::Filter minFilter, - QRhiSampler::Filter mipmapMode, - QRhiSampler::AddressMode addressU, - QRhiSampler::AddressMode addressV, - QRhiSampler::AddressMode addressW = QRhiSampler::Repeat); - - QRhiTextureRenderTarget *newTextureRenderTarget(const QRhiTextureRenderTargetDescription &desc, - QRhiTextureRenderTarget::Flags flags = {}); - - QRhiSwapChain *newSwapChain(); - FrameOpResult beginFrame(QRhiSwapChain *swapChain, BeginFrameFlags flags = {}); - FrameOpResult endFrame(QRhiSwapChain *swapChain, EndFrameFlags flags = {}); - bool isRecordingFrame() const; - int currentFrameSlot() const; - - FrameOpResult beginOffscreenFrame(QRhiCommandBuffer **cb, BeginFrameFlags flags = {}); - FrameOpResult endOffscreenFrame(EndFrameFlags flags = {}); - - QRhi::FrameOpResult finish(); - - QRhiResourceUpdateBatch *nextResourceUpdateBatch(); - - QList supportedSampleCounts() const; - - int ubufAlignment() const; - int ubufAligned(int v) const; - - int mipLevelsForSize(const QSize &size) const; - QSize sizeForMipLevel(int mipLevel, const QSize &baseLevelSize) const; - - bool isYUpInFramebuffer() const; - bool isYUpInNDC() const; - bool isClipDepthZeroToOne() const; - - QMatrix4x4 clipSpaceCorrMatrix() const; - - bool isTextureFormatSupported(QRhiTexture::Format format, QRhiTexture::Flags flags = {}) const; - bool isFeatureSupported(QRhi::Feature feature) const; - int resourceLimit(ResourceLimit limit) const; - - const QRhiNativeHandles *nativeHandles(); - bool makeThreadLocalNativeContextCurrent(); - - static const int MAX_MIP_LEVELS = 16; // a width and/or height of 65536 should be enough for everyone - - void releaseCachedResources(); - - bool isDeviceLost() const; - - QByteArray pipelineCacheData(); - void setPipelineCacheData(const QByteArray &data); - - QRhiStats statistics() const; - - static QRhiSwapChainProxyData updateSwapChainProxyData(Implementation impl, QWindow *window); - -protected: - QRhi(); - + QRhiBufferData() = default; + ~QRhiBufferData() + { + if (d && !--d->ref) + delete d; + } + QRhiBufferData(const QRhiBufferData &other) + : d(other.d) + { + if (d) + d->ref += 1; + } + QRhiBufferData &operator=(const QRhiBufferData &other) + { + if (d == other.d) + return *this; + if (other.d) + other.d->ref += 1; + if (d && !--d->ref) + delete d; + d = other.d; + return *this; + } + const char *constData() const + { + return d->size <= QRhiBufferDataPrivate::SMALL_DATA_SIZE ? d->data : d->largeData; + } + quint32 size() const + { + return d->size; + } + void assign(const char *s, quint32 size) + { + if (!d) { + d = new QRhiBufferDataPrivate; + } else if (d->ref != 1) { + d->ref -= 1; + d = new QRhiBufferDataPrivate; + } + d->size = size; + if (size <= QRhiBufferDataPrivate::SMALL_DATA_SIZE) { + memcpy(d->data, s, size); + } else { + if (d->largeAlloc < size) { + delete[] d->largeData; + d->largeAlloc = size; + d->largeData = new char[size]; + } + memcpy(d->largeData, s, size); + } + } private: - Q_DISABLE_COPY(QRhi) - QRhiImplementation *d = nullptr; + QRhiBufferDataPrivate *d = nullptr; }; -Q_DECLARE_OPERATORS_FOR_FLAGS(QRhi::Flags) -Q_DECLARE_OPERATORS_FOR_FLAGS(QRhi::BeginFrameFlags) -Q_DECLARE_OPERATORS_FOR_FLAGS(QRhi::EndFrameFlags) +Q_DECLARE_TYPEINFO(QRhiBufferData, Q_RELOCATABLE_TYPE); + +class QRhiResourceUpdateBatchPrivate +{ +public: + struct BufferOp { + enum Type { + DynamicUpdate, + StaticUpload, + Read + }; + Type type; + QRhiBuffer *buf; + quint32 offset; + QRhiBufferData data; + quint32 readSize; + QRhiReadbackResult *result; + + static BufferOp dynamicUpdate(QRhiBuffer *buf, quint32 offset, quint32 size, const void *data) + { + BufferOp op = {}; + op.type = DynamicUpdate; + op.buf = buf; + op.offset = offset; + const int effectiveSize = size ? size : buf->size(); + op.data.assign(reinterpret_cast(data), effectiveSize); + return op; + } + + static void changeToDynamicUpdate(BufferOp *op, QRhiBuffer *buf, quint32 offset, quint32 size, const void *data) + { + op->type = DynamicUpdate; + op->buf = buf; + op->offset = offset; + const int effectiveSize = size ? size : buf->size(); + op->data.assign(reinterpret_cast(data), effectiveSize); + } + + static BufferOp staticUpload(QRhiBuffer *buf, quint32 offset, quint32 size, const void *data) + { + BufferOp op = {}; + op.type = StaticUpload; + op.buf = buf; + op.offset = offset; + const int effectiveSize = size ? size : buf->size(); + op.data.assign(reinterpret_cast(data), effectiveSize); + return op; + } + + static void changeToStaticUpload(BufferOp *op, QRhiBuffer *buf, quint32 offset, quint32 size, const void *data) + { + op->type = StaticUpload; + op->buf = buf; + op->offset = offset; + const int effectiveSize = size ? size : buf->size(); + op->data.assign(reinterpret_cast(data), effectiveSize); + } + + static BufferOp read(QRhiBuffer *buf, quint32 offset, quint32 size, QRhiReadbackResult *result) + { + BufferOp op = {}; + op.type = Read; + op.buf = buf; + op.offset = offset; + op.readSize = size; + op.result = result; + return op; + } + }; + + struct TextureOp { + enum Type { + Upload, + Copy, + Read, + GenMips + }; + Type type; + QRhiTexture *dst; + // Specifying multiple uploads for a subresource must be supported. + // In the backend this can then end up, where applicable, as a + // single, batched copy operation with only one set of barriers. + // This helps when doing for example glyph cache fills. + using MipLevelUploadList = std::array, QRhi::MAX_MIP_LEVELS>; + QVarLengthArray subresDesc; + QRhiTexture *src; + QRhiTextureCopyDescription desc; + QRhiReadbackDescription rb; + QRhiReadbackResult *result; + + static TextureOp upload(QRhiTexture *tex, const QRhiTextureUploadDescription &desc) + { + TextureOp op = {}; + op.type = Upload; + op.dst = tex; + int maxLayer = -1; + for (auto it = desc.cbeginEntries(), itEnd = desc.cendEntries(); it != itEnd; ++it) { + if (it->layer() > maxLayer) + maxLayer = it->layer(); + } + op.subresDesc.resize(maxLayer + 1); + for (auto it = desc.cbeginEntries(), itEnd = desc.cendEntries(); it != itEnd; ++it) + op.subresDesc[it->layer()][it->level()].append(it->description()); + return op; + } + + static TextureOp copy(QRhiTexture *dst, QRhiTexture *src, const QRhiTextureCopyDescription &desc) + { + TextureOp op = {}; + op.type = Copy; + op.dst = dst; + op.src = src; + op.desc = desc; + return op; + } + + static TextureOp read(const QRhiReadbackDescription &rb, QRhiReadbackResult *result) + { + TextureOp op = {}; + op.type = Read; + op.rb = rb; + op.result = result; + return op; + } + + static TextureOp genMips(QRhiTexture *tex) + { + TextureOp op = {}; + op.type = GenMips; + op.dst = tex; + return op; + } + }; + + int activeBufferOpCount = 0; // this is the real number of used elements in bufferOps, not bufferOps.count() + static const int BUFFER_OPS_STATIC_ALLOC = 1024; + QVarLengthArray bufferOps; + + int activeTextureOpCount = 0; // this is the real number of used elements in textureOps, not textureOps.count() + static const int TEXTURE_OPS_STATIC_ALLOC = 256; + QVarLengthArray textureOps; + + QRhiResourceUpdateBatch *q = nullptr; + QRhiImplementation *rhi = nullptr; + int poolIndex = -1; + + void free(); + void merge(QRhiResourceUpdateBatchPrivate *other); + bool hasOptimalCapacity() const; + void trimOpLists(); + + static QRhiResourceUpdateBatchPrivate *get(QRhiResourceUpdateBatch *b) { return b->d; } +}; + +template +struct QRhiBatchedBindings +{ + void feed(int binding, T resource) { // binding must be strictly increasing + if (curBinding == -1 || binding > curBinding + 1) { + finish(); + curBatch.startBinding = binding; + curBatch.resources.clear(); + curBatch.resources.append(resource); + } else { + Q_ASSERT(binding == curBinding + 1); + curBatch.resources.append(resource); + } + curBinding = binding; + } + + bool finish() { + if (!curBatch.resources.isEmpty()) + batches.append(curBatch); + return !batches.isEmpty(); + } + + void clear() { + batches.clear(); + curBatch.resources.clear(); + curBinding = -1; + } + + struct Batch { + uint startBinding; + QVarLengthArray resources; + + bool operator==(const Batch &other) const + { + return startBinding == other.startBinding && resources == other.resources; + } + + bool operator!=(const Batch &other) const + { + return !operator==(other); + } + }; + + QVarLengthArray batches; // sorted by startBinding + + bool operator==(const QRhiBatchedBindings &other) const + { + return batches == other.batches; + } + + bool operator!=(const QRhiBatchedBindings &other) const + { + return !operator==(other); + } + +private: + Batch curBatch; + int curBinding = -1; +}; + +class QRhiGlobalObjectIdGenerator +{ +public: +#ifdef Q_ATOMIC_INT64_IS_SUPPORTED + using Type = quint64; +#else + using Type = quint32; +#endif + static Type newId(); +}; + +class QRhiPassResourceTracker +{ +public: + bool isEmpty() const; + void reset(); + + struct UsageState { + int layout; + int access; + int stage; + }; + + enum BufferStage { + BufVertexInputStage, + BufVertexStage, + BufTCStage, + BufTEStage, + BufFragmentStage, + BufComputeStage, + BufGeometryStage + }; + + enum BufferAccess { + BufVertexInput, + BufIndexRead, + BufUniformRead, + BufStorageLoad, + BufStorageStore, + BufStorageLoadStore + }; + + void registerBuffer(QRhiBuffer *buf, int slot, BufferAccess *access, BufferStage *stage, + const UsageState &state); + + enum TextureStage { + TexVertexStage, + TexTCStage, + TexTEStage, + TexFragmentStage, + TexColorOutputStage, + TexDepthOutputStage, + TexComputeStage, + TexGeometryStage + }; + + enum TextureAccess { + TexSample, + TexColorOutput, + TexDepthOutput, + TexStorageLoad, + TexStorageStore, + TexStorageLoadStore + }; + + void registerTexture(QRhiTexture *tex, TextureAccess *access, TextureStage *stage, + const UsageState &state); + + struct Buffer { + int slot; + BufferAccess access; + BufferStage stage; + UsageState stateAtPassBegin; + }; + + using BufferIterator = QHash::const_iterator; + BufferIterator cbeginBuffers() const { return m_buffers.cbegin(); } + BufferIterator cendBuffers() const { return m_buffers.cend(); } + + struct Texture { + TextureAccess access; + TextureStage stage; + UsageState stateAtPassBegin; + }; + + using TextureIterator = QHash::const_iterator; + TextureIterator cbeginTextures() const { return m_textures.cbegin(); } + TextureIterator cendTextures() const { return m_textures.cend(); } + + static BufferStage toPassTrackerBufferStage(QRhiShaderResourceBinding::StageFlags stages); + static TextureStage toPassTrackerTextureStage(QRhiShaderResourceBinding::StageFlags stages); + +private: + QHash m_buffers; + QHash m_textures; +}; + +Q_DECLARE_TYPEINFO(QRhiPassResourceTracker::Buffer, Q_RELOCATABLE_TYPE); +Q_DECLARE_TYPEINFO(QRhiPassResourceTracker::Texture, Q_RELOCATABLE_TYPE); + +template +class QRhiBackendCommandList +{ +public: + QRhiBackendCommandList() = default; + ~QRhiBackendCommandList() { delete[] v; } + inline void reset() { p = 0; } + inline bool isEmpty() const { return p == 0; } + inline T &get() { + if (p == a) { + a += GROW; + T *nv = new T[a]; + if (v) { + memcpy(nv, v, p * sizeof(T)); + delete[] v; + } + v = nv; + } + return v[p++]; + } + inline void unget() { --p; } + inline T *cbegin() const { return v; } + inline T *cend() const { return v + p; } + inline T *begin() { return v; } + inline T *end() { return v + p; } +private: + Q_DISABLE_COPY(QRhiBackendCommandList) + T *v = nullptr; + int a = 0; + int p = 0; +}; + +struct QRhiRenderTargetAttachmentTracker +{ + struct ResId { quint64 id; uint generation; }; + using ResIdList = QVarLengthArray; // color, resolve, ds + + template + static void updateResIdList(const QRhiTextureRenderTargetDescription &desc, ResIdList *dst); + + template + static bool isUpToDate(const QRhiTextureRenderTargetDescription &desc, const ResIdList ¤tResIdList); +}; + +inline bool operator==(const QRhiRenderTargetAttachmentTracker::ResId &a, const QRhiRenderTargetAttachmentTracker::ResId &b) +{ + return a.id == b.id && a.generation == b.generation; +} + +inline bool operator!=(const QRhiRenderTargetAttachmentTracker::ResId &a, const QRhiRenderTargetAttachmentTracker::ResId &b) +{ + return !(a == b); +} + +template +void QRhiRenderTargetAttachmentTracker::updateResIdList(const QRhiTextureRenderTargetDescription &desc, ResIdList *dst) +{ + const bool hasDepthStencil = desc.depthStencilBuffer() || desc.depthTexture(); + dst->resize(desc.colorAttachmentCount() * 2 + (hasDepthStencil ? 1 : 0)); + int n = 0; + for (auto it = desc.cbeginColorAttachments(), itEnd = desc.cendColorAttachments(); it != itEnd; ++it, ++n) { + const QRhiColorAttachment &colorAtt(*it); + if (colorAtt.texture()) { + TexType *texD = QRHI_RES(TexType, colorAtt.texture()); + (*dst)[n] = { texD->globalResourceId(), texD->generation }; + } else if (colorAtt.renderBuffer()) { + RenderBufferType *rbD = QRHI_RES(RenderBufferType, colorAtt.renderBuffer()); + (*dst)[n] = { rbD->globalResourceId(), rbD->generation }; + } else { + (*dst)[n] = { 0, 0 }; + } + ++n; + if (colorAtt.resolveTexture()) { + TexType *texD = QRHI_RES(TexType, colorAtt.resolveTexture()); + (*dst)[n] = { texD->globalResourceId(), texD->generation }; + } else { + (*dst)[n] = { 0, 0 }; + } + } + if (hasDepthStencil) { + if (desc.depthTexture()) { + TexType *depthTexD = QRHI_RES(TexType, desc.depthTexture()); + (*dst)[n] = { depthTexD->globalResourceId(), depthTexD->generation }; + } else if (desc.depthStencilBuffer()) { + RenderBufferType *depthRbD = QRHI_RES(RenderBufferType, desc.depthStencilBuffer()); + (*dst)[n] = { depthRbD->globalResourceId(), depthRbD->generation }; + } else { + (*dst)[n] = { 0, 0 }; + } + } +} + +template +bool QRhiRenderTargetAttachmentTracker::isUpToDate(const QRhiTextureRenderTargetDescription &desc, const ResIdList ¤tResIdList) +{ + // Just as setShaderResources() recognizes if an srb's referenced + // resources have been rebuilt (got a create() since the srb's + // create()), we should do the same for the textures and renderbuffers + // referenced from the rendertarget. It is not uncommon that a texture + // or ds buffer gets resized due to following a window size in some + // form, which involves a create() on them. It is then nice if the + // render target auto-rebuilds in beginPass(). + + ResIdList resIdList; + updateResIdList(desc, &resIdList); + return resIdList == currentResIdList; +} + +template +inline T *qrhi_objectFromProxyData(QRhiSwapChainProxyData *pd, QWindow *window, QRhi::Implementation impl, uint objectIndex) +{ + Q_ASSERT(objectIndex < std::size(pd->reserved)); + if (!pd->reserved[objectIndex]) // // was not set, no other choice, do it here, whatever thread this is + *pd = QRhi::updateSwapChainProxyData(impl, window); + return static_cast(pd->reserved[objectIndex]); +} QT_END_NAMESPACE diff --git a/src/gui/rhi/qrhi_p_p.h b/src/gui/rhi/qrhi_p_p.h deleted file mode 100644 index da29a24d..00000000 --- a/src/gui/rhi/qrhi_p_p.h +++ /dev/null @@ -1,802 +0,0 @@ -// Copyright (C) 2019 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 QRHI_P_H -#define QRHI_P_H - -// -// W A R N I N G -// ------------- -// -// This file is not part of the Qt API. It exists purely as an -// implementation detail. This header file may change from version to -// version without notice, or even be removed. -// -// We mean it. -// - -#include "qrhi_p.h" -#include -#include -#include -#include -#include -#include - -QT_BEGIN_NAMESPACE - -#define QRHI_RES(t, x) static_cast(x) -#define QRHI_RES_RHI(t) t *rhiD = static_cast(m_rhi) - -Q_DECLARE_LOGGING_CATEGORY(QRHI_LOG_INFO) - -class QRhiImplementation -{ -public: - virtual ~QRhiImplementation(); - - virtual bool create(QRhi::Flags flags) = 0; - virtual void destroy() = 0; - - virtual QRhiGraphicsPipeline *createGraphicsPipeline() = 0; - virtual QRhiComputePipeline *createComputePipeline() = 0; - virtual QRhiShaderResourceBindings *createShaderResourceBindings() = 0; - virtual QRhiBuffer *createBuffer(QRhiBuffer::Type type, - QRhiBuffer::UsageFlags usage, - quint32 size) = 0; - virtual QRhiRenderBuffer *createRenderBuffer(QRhiRenderBuffer::Type type, - const QSize &pixelSize, - int sampleCount, - QRhiRenderBuffer::Flags flags, - QRhiTexture::Format backingFormatHint) = 0; - virtual QRhiTexture *createTexture(QRhiTexture::Format format, - const QSize &pixelSize, - int depth, - int arraySize, - int sampleCount, - QRhiTexture::Flags flags) = 0; - virtual QRhiSampler *createSampler(QRhiSampler::Filter magFilter, - QRhiSampler::Filter minFilter, - QRhiSampler::Filter mipmapMode, - QRhiSampler:: AddressMode u, - QRhiSampler::AddressMode v, - QRhiSampler::AddressMode w) = 0; - - virtual QRhiTextureRenderTarget *createTextureRenderTarget(const QRhiTextureRenderTargetDescription &desc, - QRhiTextureRenderTarget::Flags flags) = 0; - - virtual QRhiSwapChain *createSwapChain() = 0; - virtual QRhi::FrameOpResult beginFrame(QRhiSwapChain *swapChain, QRhi::BeginFrameFlags flags) = 0; - virtual QRhi::FrameOpResult endFrame(QRhiSwapChain *swapChain, QRhi::EndFrameFlags flags) = 0; - virtual QRhi::FrameOpResult beginOffscreenFrame(QRhiCommandBuffer **cb, QRhi::BeginFrameFlags flags) = 0; - virtual QRhi::FrameOpResult endOffscreenFrame(QRhi::EndFrameFlags flags) = 0; - virtual QRhi::FrameOpResult finish() = 0; - - virtual void resourceUpdate(QRhiCommandBuffer *cb, QRhiResourceUpdateBatch *resourceUpdates) = 0; - - virtual void beginPass(QRhiCommandBuffer *cb, - QRhiRenderTarget *rt, - const QColor &colorClearValue, - const QRhiDepthStencilClearValue &depthStencilClearValue, - QRhiResourceUpdateBatch *resourceUpdates, - QRhiCommandBuffer::BeginPassFlags flags) = 0; - virtual void endPass(QRhiCommandBuffer *cb, QRhiResourceUpdateBatch *resourceUpdates) = 0; - - virtual void setGraphicsPipeline(QRhiCommandBuffer *cb, - QRhiGraphicsPipeline *ps) = 0; - - virtual void setShaderResources(QRhiCommandBuffer *cb, - QRhiShaderResourceBindings *srb, - int dynamicOffsetCount, - const QRhiCommandBuffer::DynamicOffset *dynamicOffsets) = 0; - - virtual void setVertexInput(QRhiCommandBuffer *cb, - int startBinding, int bindingCount, const QRhiCommandBuffer::VertexInput *bindings, - QRhiBuffer *indexBuf, quint32 indexOffset, - QRhiCommandBuffer::IndexFormat indexFormat) = 0; - - virtual void setViewport(QRhiCommandBuffer *cb, const QRhiViewport &viewport) = 0; - virtual void setScissor(QRhiCommandBuffer *cb, const QRhiScissor &scissor) = 0; - virtual void setBlendConstants(QRhiCommandBuffer *cb, const QColor &c) = 0; - virtual void setStencilRef(QRhiCommandBuffer *cb, quint32 refValue) = 0; - - virtual void draw(QRhiCommandBuffer *cb, quint32 vertexCount, - quint32 instanceCount, quint32 firstVertex, quint32 firstInstance) = 0; - virtual void drawIndexed(QRhiCommandBuffer *cb, quint32 indexCount, - quint32 instanceCount, quint32 firstIndex, - qint32 vertexOffset, quint32 firstInstance) = 0; - - virtual void debugMarkBegin(QRhiCommandBuffer *cb, const QByteArray &name) = 0; - virtual void debugMarkEnd(QRhiCommandBuffer *cb) = 0; - virtual void debugMarkMsg(QRhiCommandBuffer *cb, const QByteArray &msg) = 0; - - virtual void beginComputePass(QRhiCommandBuffer *cb, - QRhiResourceUpdateBatch *resourceUpdates, - QRhiCommandBuffer::BeginPassFlags flags) = 0; - virtual void endComputePass(QRhiCommandBuffer *cb, QRhiResourceUpdateBatch *resourceUpdates) = 0; - virtual void setComputePipeline(QRhiCommandBuffer *cb, QRhiComputePipeline *ps) = 0; - virtual void dispatch(QRhiCommandBuffer *cb, int x, int y, int z) = 0; - - virtual const QRhiNativeHandles *nativeHandles(QRhiCommandBuffer *cb) = 0; - virtual void beginExternal(QRhiCommandBuffer *cb) = 0; - virtual void endExternal(QRhiCommandBuffer *cb) = 0; - - virtual QList supportedSampleCounts() const = 0; - virtual int ubufAlignment() const = 0; - virtual bool isYUpInFramebuffer() const = 0; - virtual bool isYUpInNDC() const = 0; - virtual bool isClipDepthZeroToOne() const = 0; - virtual QMatrix4x4 clipSpaceCorrMatrix() const = 0; - virtual bool isTextureFormatSupported(QRhiTexture::Format format, QRhiTexture::Flags flags) const = 0; - virtual bool isFeatureSupported(QRhi::Feature feature) const = 0; - virtual int resourceLimit(QRhi::ResourceLimit limit) const = 0; - virtual const QRhiNativeHandles *nativeHandles() = 0; - virtual QRhiDriverInfo driverInfo() const = 0; - virtual QRhiStats statistics() = 0; - virtual bool makeThreadLocalNativeContextCurrent() = 0; - virtual void releaseCachedResources() = 0; - virtual bool isDeviceLost() const = 0; - - virtual QByteArray pipelineCacheData() = 0; - virtual void setPipelineCacheData(const QByteArray &data) = 0; - - bool isCompressedFormat(QRhiTexture::Format format) const; - void compressedFormatInfo(QRhiTexture::Format format, const QSize &size, - quint32 *bpl, quint32 *byteSize, - QSize *blockDim) const; - void textureFormatInfo(QRhiTexture::Format format, const QSize &size, - quint32 *bpl, quint32 *byteSize, quint32 *bytesPerPixel) const; - - void registerResource(QRhiResource *res, bool ownsNativeResources = true) - { - // The ownsNativeResources is relevant for the (graphics resource) leak - // check in ~QRhiImplementation; when false, the registration's sole - // purpose is to automatically null out the resource's m_rhi pointer in - // case the rhi goes away first. (which should not happen in - // well-written applications but we try to be graceful) - resources.insert(res, ownsNativeResources); - } - - void unregisterResource(QRhiResource *res) - { - resources.remove(res); - } - - void addDeleteLater(QRhiResource *res) - { - if (inFrame) - pendingDeleteResources.insert(res); - else - delete res; - } - - void addCleanupCallback(const QRhi::CleanupCallback &callback) - { - cleanupCallbacks.append(callback); - } - - void addGpuFrameTimeCallback(const QRhi::GpuFrameTimeCallback &callback) - { - gpuFrameTimeCallbacks.append(callback); - } - - bool hasGpuFrameTimeCallback() const - { - return !gpuFrameTimeCallbacks.isEmpty(); - } - - void runGpuFrameTimeCallbacks(float t) - { - for (const QRhi::GpuFrameTimeCallback &f : std::as_const(gpuFrameTimeCallbacks)) - f(t); - } - - bool sanityCheckGraphicsPipeline(QRhiGraphicsPipeline *ps); - bool sanityCheckShaderResourceBindings(QRhiShaderResourceBindings *srb); - void updateLayoutDesc(QRhiShaderResourceBindings *srb); - - quint32 pipelineCacheRhiId() const - { - const quint32 ver = (QT_VERSION_MAJOR << 16) | (QT_VERSION_MINOR << 8) | (QT_VERSION_PATCH); - return (quint32(implType) << 24) | ver; - } - - void pipelineCreationStart() - { - pipelineCreationTimer.start(); - } - - void pipelineCreationEnd() - { - accumulatedPipelineCreationTime += pipelineCreationTimer.elapsed(); - } - - qint64 totalPipelineCreationTime() const - { - return accumulatedPipelineCreationTime; - } - - QRhiVertexInputAttribute::Format shaderDescVariableFormatToVertexInputFormat(QShaderDescription::VariableType type) const; - quint32 byteSizePerVertexForVertexInputFormat(QRhiVertexInputAttribute::Format format) const; - - QRhi *q; - - static const int MAX_SHADER_CACHE_ENTRIES = 128; - - bool debugMarkers = false; - int currentFrameSlot = 0; // for vk, mtl, and similar. unused by gl and d3d11. - bool inFrame = false; - -private: - QRhi::Implementation implType; - QThread *implThread; - QVarLengthArray resUpdPool; - quint64 resUpdPoolMap = 0; - int lastResUpdIdx = -1; - QHash resources; - QSet pendingDeleteResources; - QVarLengthArray cleanupCallbacks; - QVarLengthArray gpuFrameTimeCallbacks; - QElapsedTimer pipelineCreationTimer; - qint64 accumulatedPipelineCreationTime = 0; - - friend class QRhi; - friend class QRhiResourceUpdateBatchPrivate; -}; - -enum QRhiTargetRectBoundMode -{ - UnBounded, - Bounded -}; - -template -bool qrhi_toTopLeftRenderTargetRect(const QSize &outputSize, const std::array &r, - T *x, T *y, T *w, T *h) -{ - // x,y are bottom-left in QRhiScissor and QRhiViewport but top-left in - // Vulkan/Metal/D3D. Our input is an OpenGL-style scissor rect where both - // negative x or y, and partly or completely out of bounds rects are - // allowed. The only thing the input here cannot have is a negative width - // or height. We must handle all other input gracefully, clamping to a zero - // width or height rect in the worst case, and ensuring the resulting rect - // is inside the rendertarget's bounds because some APIs' validation/debug - // layers are allergic to out of bounds scissor rects. - - const T outputWidth = outputSize.width(); - const T outputHeight = outputSize.height(); - const T inputWidth = r[2]; - const T inputHeight = r[3]; - - if (inputWidth < 0 || inputHeight < 0) - return false; - - *x = r[0]; - *y = outputHeight - (r[1] + inputHeight); - *w = inputWidth; - *h = inputHeight; - - if (boundingMode == Bounded) { - const T widthOffset = *x < 0 ? -*x : 0; - const T heightOffset = *y < 0 ? -*y : 0; - *w = *x < outputWidth ? qMax(0, inputWidth - widthOffset) : 0; - *h = *y < outputHeight ? qMax(0, inputHeight - heightOffset) : 0; - - *x = qBound(0, *x, outputWidth - 1); - *y = qBound(0, *y, outputHeight - 1); - - if (*x + *w > outputWidth) - *w = qMax(0, outputWidth - *x); - if (*y + *h > outputHeight) - *h = qMax(0, outputHeight - *y); - } - return true; -} - -struct QRhiBufferDataPrivate -{ - Q_DISABLE_COPY_MOVE(QRhiBufferDataPrivate) - QRhiBufferDataPrivate() { } - ~QRhiBufferDataPrivate() { delete[] largeData; } - int ref = 1; - quint32 size = 0; - quint32 largeAlloc = 0; - char *largeData = nullptr; - static constexpr quint32 SMALL_DATA_SIZE = 1024; - char data[SMALL_DATA_SIZE]; -}; - -// no detach-with-contents, no atomic refcount, no shrink -class QRhiBufferData -{ -public: - QRhiBufferData() = default; - ~QRhiBufferData() - { - if (d && !--d->ref) - delete d; - } - QRhiBufferData(const QRhiBufferData &other) - : d(other.d) - { - if (d) - d->ref += 1; - } - QRhiBufferData &operator=(const QRhiBufferData &other) - { - if (d == other.d) - return *this; - if (other.d) - other.d->ref += 1; - if (d && !--d->ref) - delete d; - d = other.d; - return *this; - } - const char *constData() const - { - return d->size <= QRhiBufferDataPrivate::SMALL_DATA_SIZE ? d->data : d->largeData; - } - quint32 size() const - { - return d->size; - } - void assign(const char *s, quint32 size) - { - if (!d) { - d = new QRhiBufferDataPrivate; - } else if (d->ref != 1) { - d->ref -= 1; - d = new QRhiBufferDataPrivate; - } - d->size = size; - if (size <= QRhiBufferDataPrivate::SMALL_DATA_SIZE) { - memcpy(d->data, s, size); - } else { - if (d->largeAlloc < size) { - delete[] d->largeData; - d->largeAlloc = size; - d->largeData = new char[size]; - } - memcpy(d->largeData, s, size); - } - } -private: - QRhiBufferDataPrivate *d = nullptr; -}; - -Q_DECLARE_TYPEINFO(QRhiBufferData, Q_RELOCATABLE_TYPE); - -class QRhiResourceUpdateBatchPrivate -{ -public: - struct BufferOp { - enum Type { - DynamicUpdate, - StaticUpload, - Read - }; - Type type; - QRhiBuffer *buf; - quint32 offset; - QRhiBufferData data; - quint32 readSize; - QRhiBufferReadbackResult *result; - - static BufferOp dynamicUpdate(QRhiBuffer *buf, quint32 offset, quint32 size, const void *data) - { - BufferOp op = {}; - op.type = DynamicUpdate; - op.buf = buf; - op.offset = offset; - const int effectiveSize = size ? size : buf->size(); - op.data.assign(reinterpret_cast(data), effectiveSize); - return op; - } - - static void changeToDynamicUpdate(BufferOp *op, QRhiBuffer *buf, quint32 offset, quint32 size, const void *data) - { - op->type = DynamicUpdate; - op->buf = buf; - op->offset = offset; - const int effectiveSize = size ? size : buf->size(); - op->data.assign(reinterpret_cast(data), effectiveSize); - } - - static BufferOp staticUpload(QRhiBuffer *buf, quint32 offset, quint32 size, const void *data) - { - BufferOp op = {}; - op.type = StaticUpload; - op.buf = buf; - op.offset = offset; - const int effectiveSize = size ? size : buf->size(); - op.data.assign(reinterpret_cast(data), effectiveSize); - return op; - } - - static void changeToStaticUpload(BufferOp *op, QRhiBuffer *buf, quint32 offset, quint32 size, const void *data) - { - op->type = StaticUpload; - op->buf = buf; - op->offset = offset; - const int effectiveSize = size ? size : buf->size(); - op->data.assign(reinterpret_cast(data), effectiveSize); - } - - static BufferOp read(QRhiBuffer *buf, quint32 offset, quint32 size, QRhiBufferReadbackResult *result) - { - BufferOp op = {}; - op.type = Read; - op.buf = buf; - op.offset = offset; - op.readSize = size; - op.result = result; - return op; - } - }; - - struct TextureOp { - enum Type { - Upload, - Copy, - Read, - GenMips - }; - Type type; - QRhiTexture *dst; - // Specifying multiple uploads for a subresource must be supported. - // In the backend this can then end up, where applicable, as a - // single, batched copy operation with only one set of barriers. - // This helps when doing for example glyph cache fills. - using MipLevelUploadList = std::array, QRhi::MAX_MIP_LEVELS>; - QVarLengthArray subresDesc; - QRhiTexture *src; - QRhiTextureCopyDescription desc; - QRhiReadbackDescription rb; - QRhiReadbackResult *result; - - static TextureOp upload(QRhiTexture *tex, const QRhiTextureUploadDescription &desc) - { - TextureOp op = {}; - op.type = Upload; - op.dst = tex; - int maxLayer = -1; - for (auto it = desc.cbeginEntries(), itEnd = desc.cendEntries(); it != itEnd; ++it) { - if (it->layer() > maxLayer) - maxLayer = it->layer(); - } - op.subresDesc.resize(maxLayer + 1); - for (auto it = desc.cbeginEntries(), itEnd = desc.cendEntries(); it != itEnd; ++it) - op.subresDesc[it->layer()][it->level()].append(it->description()); - return op; - } - - static TextureOp copy(QRhiTexture *dst, QRhiTexture *src, const QRhiTextureCopyDescription &desc) - { - TextureOp op = {}; - op.type = Copy; - op.dst = dst; - op.src = src; - op.desc = desc; - return op; - } - - static TextureOp read(const QRhiReadbackDescription &rb, QRhiReadbackResult *result) - { - TextureOp op = {}; - op.type = Read; - op.rb = rb; - op.result = result; - return op; - } - - static TextureOp genMips(QRhiTexture *tex) - { - TextureOp op = {}; - op.type = GenMips; - op.dst = tex; - return op; - } - }; - - int activeBufferOpCount = 0; // this is the real number of used elements in bufferOps, not bufferOps.count() - static const int BUFFER_OPS_STATIC_ALLOC = 1024; - QVarLengthArray bufferOps; - - int activeTextureOpCount = 0; // this is the real number of used elements in textureOps, not textureOps.count() - static const int TEXTURE_OPS_STATIC_ALLOC = 256; - QVarLengthArray textureOps; - - QRhiResourceUpdateBatch *q = nullptr; - QRhiImplementation *rhi = nullptr; - int poolIndex = -1; - - void free(); - void merge(QRhiResourceUpdateBatchPrivate *other); - bool hasOptimalCapacity() const; - void trimOpLists(); - - static QRhiResourceUpdateBatchPrivate *get(QRhiResourceUpdateBatch *b) { return b->d; } -}; - -template -struct QRhiBatchedBindings -{ - void feed(int binding, T resource) { // binding must be strictly increasing - if (curBinding == -1 || binding > curBinding + 1) { - finish(); - curBatch.startBinding = binding; - curBatch.resources.clear(); - curBatch.resources.append(resource); - } else { - Q_ASSERT(binding == curBinding + 1); - curBatch.resources.append(resource); - } - curBinding = binding; - } - - bool finish() { - if (!curBatch.resources.isEmpty()) - batches.append(curBatch); - return !batches.isEmpty(); - } - - void clear() { - batches.clear(); - curBatch.resources.clear(); - curBinding = -1; - } - - struct Batch { - uint startBinding; - QVarLengthArray resources; - - bool operator==(const Batch &other) const - { - return startBinding == other.startBinding && resources == other.resources; - } - - bool operator!=(const Batch &other) const - { - return !operator==(other); - } - }; - - QVarLengthArray batches; // sorted by startBinding - - bool operator==(const QRhiBatchedBindings &other) const - { - return batches == other.batches; - } - - bool operator!=(const QRhiBatchedBindings &other) const - { - return !operator==(other); - } - -private: - Batch curBatch; - int curBinding = -1; -}; - -class QRhiGlobalObjectIdGenerator -{ -public: -#ifdef Q_ATOMIC_INT64_IS_SUPPORTED - using Type = quint64; -#else - using Type = quint32; -#endif - static Type newId(); -}; - -class QRhiPassResourceTracker -{ -public: - bool isEmpty() const; - void reset(); - - struct UsageState { - int layout; - int access; - int stage; - }; - - enum BufferStage { - BufVertexInputStage, - BufVertexStage, - BufTCStage, - BufTEStage, - BufFragmentStage, - BufComputeStage, - BufGeometryStage - }; - - enum BufferAccess { - BufVertexInput, - BufIndexRead, - BufUniformRead, - BufStorageLoad, - BufStorageStore, - BufStorageLoadStore - }; - - void registerBuffer(QRhiBuffer *buf, int slot, BufferAccess *access, BufferStage *stage, - const UsageState &state); - - enum TextureStage { - TexVertexStage, - TexTCStage, - TexTEStage, - TexFragmentStage, - TexColorOutputStage, - TexDepthOutputStage, - TexComputeStage, - TexGeometryStage - }; - - enum TextureAccess { - TexSample, - TexColorOutput, - TexDepthOutput, - TexStorageLoad, - TexStorageStore, - TexStorageLoadStore - }; - - void registerTexture(QRhiTexture *tex, TextureAccess *access, TextureStage *stage, - const UsageState &state); - - struct Buffer { - int slot; - BufferAccess access; - BufferStage stage; - UsageState stateAtPassBegin; - }; - - using BufferIterator = QHash::const_iterator; - BufferIterator cbeginBuffers() const { return m_buffers.cbegin(); } - BufferIterator cendBuffers() const { return m_buffers.cend(); } - - struct Texture { - TextureAccess access; - TextureStage stage; - UsageState stateAtPassBegin; - }; - - using TextureIterator = QHash::const_iterator; - TextureIterator cbeginTextures() const { return m_textures.cbegin(); } - TextureIterator cendTextures() const { return m_textures.cend(); } - - static BufferStage toPassTrackerBufferStage(QRhiShaderResourceBinding::StageFlags stages); - static TextureStage toPassTrackerTextureStage(QRhiShaderResourceBinding::StageFlags stages); - -private: - QHash m_buffers; - QHash m_textures; -}; - -Q_DECLARE_TYPEINFO(QRhiPassResourceTracker::Buffer, Q_RELOCATABLE_TYPE); -Q_DECLARE_TYPEINFO(QRhiPassResourceTracker::Texture, Q_RELOCATABLE_TYPE); - -template -class QRhiBackendCommandList -{ -public: - QRhiBackendCommandList() = default; - ~QRhiBackendCommandList() { delete[] v; } - inline void reset() { p = 0; } - inline bool isEmpty() const { return p == 0; } - inline T &get() { - if (p == a) { - a += GROW; - T *nv = new T[a]; - if (v) { - memcpy(nv, v, p * sizeof(T)); - delete[] v; - } - v = nv; - } - return v[p++]; - } - inline void unget() { --p; } - inline T *cbegin() const { return v; } - inline T *cend() const { return v + p; } - inline T *begin() { return v; } - inline T *end() { return v + p; } -private: - Q_DISABLE_COPY(QRhiBackendCommandList) - T *v = nullptr; - int a = 0; - int p = 0; -}; - -struct QRhiRenderTargetAttachmentTracker -{ - struct ResId { quint64 id; uint generation; }; - using ResIdList = QVarLengthArray; // color, resolve, ds - - template - static void updateResIdList(const QRhiTextureRenderTargetDescription &desc, ResIdList *dst); - - template - static bool isUpToDate(const QRhiTextureRenderTargetDescription &desc, const ResIdList ¤tResIdList); -}; - -inline bool operator==(const QRhiRenderTargetAttachmentTracker::ResId &a, const QRhiRenderTargetAttachmentTracker::ResId &b) -{ - return a.id == b.id && a.generation == b.generation; -} - -inline bool operator!=(const QRhiRenderTargetAttachmentTracker::ResId &a, const QRhiRenderTargetAttachmentTracker::ResId &b) -{ - return !(a == b); -} - -template -void QRhiRenderTargetAttachmentTracker::updateResIdList(const QRhiTextureRenderTargetDescription &desc, ResIdList *dst) -{ - const quintptr colorAttCount = desc.cendColorAttachments() - desc.cbeginColorAttachments(); - const bool hasDepthStencil = desc.depthStencilBuffer() || desc.depthTexture(); - dst->resize(colorAttCount * 2 + (hasDepthStencil ? 1 : 0)); - int n = 0; - for (auto it = desc.cbeginColorAttachments(), itEnd = desc.cendColorAttachments(); it != itEnd; ++it, ++n) { - const QRhiColorAttachment &colorAtt(*it); - if (colorAtt.texture()) { - TexType *texD = QRHI_RES(TexType, colorAtt.texture()); - (*dst)[n] = { texD->globalResourceId(), texD->generation }; - } else if (colorAtt.renderBuffer()) { - RenderBufferType *rbD = QRHI_RES(RenderBufferType, colorAtt.renderBuffer()); - (*dst)[n] = { rbD->globalResourceId(), rbD->generation }; - } else { - (*dst)[n] = { 0, 0 }; - } - ++n; - if (colorAtt.resolveTexture()) { - TexType *texD = QRHI_RES(TexType, colorAtt.resolveTexture()); - (*dst)[n] = { texD->globalResourceId(), texD->generation }; - } else { - (*dst)[n] = { 0, 0 }; - } - } - if (hasDepthStencil) { - if (desc.depthTexture()) { - TexType *depthTexD = QRHI_RES(TexType, desc.depthTexture()); - (*dst)[n] = { depthTexD->globalResourceId(), depthTexD->generation }; - } else if (desc.depthStencilBuffer()) { - RenderBufferType *depthRbD = QRHI_RES(RenderBufferType, desc.depthStencilBuffer()); - (*dst)[n] = { depthRbD->globalResourceId(), depthRbD->generation }; - } else { - (*dst)[n] = { 0, 0 }; - } - } -} - -template -bool QRhiRenderTargetAttachmentTracker::isUpToDate(const QRhiTextureRenderTargetDescription &desc, const ResIdList ¤tResIdList) -{ - // Just as setShaderResources() recognizes if an srb's referenced - // resources have been rebuilt (got a create() since the srb's - // create()), we should do the same for the textures and renderbuffers - // referenced from the rendertarget. It is not uncommon that a texture - // or ds buffer gets resized due to following a window size in some - // form, which involves a create() on them. It is then nice if the - // render target auto-rebuilds in beginPass(). - - ResIdList resIdList; - updateResIdList(desc, &resIdList); - return resIdList == currentResIdList; -} - -template -inline T *qrhi_objectFromProxyData(QRhiSwapChainProxyData *pd, QWindow *window, QRhi::Implementation impl, uint objectIndex) -{ - Q_ASSERT(objectIndex < std::size(pd->reserved)); - if (!pd->reserved[objectIndex]) // // was not set, no other choice, do it here, whatever thread this is - *pd = QRhi::updateSwapChainProxyData(impl, window); - return static_cast(pd->reserved[objectIndex]); -} - -QT_END_NAMESPACE - -#endif diff --git a/src/gui/rhi/qrhi_platform.h b/src/gui/rhi/qrhi_platform.h new file mode 100644 index 00000000..e5069738 --- /dev/null +++ b/src/gui/rhi/qrhi_platform.h @@ -0,0 +1,175 @@ +// 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 QRHIPLATFORM_H +#define QRHIPLATFORM_H + +// +// W A R N I N G +// ------------- +// +// This file is part of the RHI API, with limited compatibility guarantees. +// Usage of this API may make your code source and binary incompatible with +// future versions of Qt. +// + +#include + +#if QT_CONFIG(opengl) +#include +#endif + +#if QT_CONFIG(vulkan) +#include +#endif + +#if defined(Q_OS_MACOS) || defined(Q_OS_IOS) || defined(Q_QDOC) +Q_FORWARD_DECLARE_OBJC_CLASS(MTLDevice); +Q_FORWARD_DECLARE_OBJC_CLASS(MTLCommandQueue); +Q_FORWARD_DECLARE_OBJC_CLASS(MTLCommandBuffer); +Q_FORWARD_DECLARE_OBJC_CLASS(MTLRenderCommandEncoder); +#endif + +QT_BEGIN_NAMESPACE + +struct Q_GUI_EXPORT QRhiNullInitParams : public QRhiInitParams +{ +}; + +struct Q_GUI_EXPORT QRhiNullNativeHandles : public QRhiNativeHandles +{ +}; + +#if QT_CONFIG(opengl) || defined(Q_QDOC) + +class QOpenGLContext; +class QOffscreenSurface; +class QSurface; +class QWindow; + +struct Q_GUI_EXPORT QRhiGles2InitParams : public QRhiInitParams +{ + QRhiGles2InitParams(); + + QSurfaceFormat format; + QSurface *fallbackSurface = nullptr; + QWindow *window = nullptr; + QOpenGLContext *shareContext = nullptr; + + static QOffscreenSurface *newFallbackSurface(const QSurfaceFormat &format = QSurfaceFormat::defaultFormat()); +}; + +struct Q_GUI_EXPORT QRhiGles2NativeHandles : public QRhiNativeHandles +{ + QOpenGLContext *context = nullptr; +}; + +#endif // opengl/qdoc + +#if (QT_CONFIG(vulkan) && __has_include()) || defined(Q_QDOC) + +struct Q_GUI_EXPORT QRhiVulkanInitParams : public QRhiInitParams +{ + QVulkanInstance *inst = nullptr; + QWindow *window = nullptr; + QByteArrayList deviceExtensions; + + static QByteArrayList preferredInstanceExtensions(); + static QByteArrayList preferredExtensionsForImportedDevice(); +}; + +struct Q_GUI_EXPORT QRhiVulkanNativeHandles : public QRhiNativeHandles +{ + // to import a physical device (always required) + VkPhysicalDevice physDev = VK_NULL_HANDLE; + // to import a device and queue + VkDevice dev = VK_NULL_HANDLE; + quint32 gfxQueueFamilyIdx = 0; + quint32 gfxQueueIdx = 0; + // and optionally, the mem allocator + void *vmemAllocator = nullptr; + + // only for querying (rhi->nativeHandles()) + VkQueue gfxQueue = VK_NULL_HANDLE; + QVulkanInstance *inst = nullptr; +}; + +struct Q_GUI_EXPORT QRhiVulkanCommandBufferNativeHandles : public QRhiNativeHandles +{ + VkCommandBuffer commandBuffer = VK_NULL_HANDLE; +}; + +struct Q_GUI_EXPORT QRhiVulkanRenderPassNativeHandles : public QRhiNativeHandles +{ + VkRenderPass renderPass = VK_NULL_HANDLE; +}; + +#endif // vulkan/qdoc + +#if defined(Q_OS_WIN) || defined(Q_QDOC) + +// no d3d includes here, to prevent precompiled header mess due to COM, hence the void pointers + +struct Q_GUI_EXPORT QRhiD3D11InitParams : public QRhiInitParams +{ + bool enableDebugLayer = false; +}; + +struct Q_GUI_EXPORT QRhiD3D11NativeHandles : public QRhiNativeHandles +{ + // to import a device and a context + void *dev = nullptr; + void *context = nullptr; + // alternatively, to specify the device feature level and/or the adapter to use + int featureLevel = 0; + quint32 adapterLuidLow = 0; + qint32 adapterLuidHigh = 0; +}; + +struct Q_GUI_EXPORT QRhiD3D12InitParams : public QRhiInitParams +{ + bool enableDebugLayer = false; +}; + +struct Q_GUI_EXPORT QRhiD3D12NativeHandles : public QRhiNativeHandles +{ + // to import a device + void *dev = nullptr; + int minimumFeatureLevel = 0; + // to just specify the adapter to use, set these and leave dev set to null + quint32 adapterLuidLow = 0; + qint32 adapterLuidHigh = 0; + // in addition, can specify the command queue to use + void *commandQueue = nullptr; +}; + +struct Q_GUI_EXPORT QRhiD3D12CommandBufferNativeHandles : public QRhiNativeHandles +{ + void *commandList = nullptr; // ID3D12GraphicsCommandList +}; + +#endif // WIN/QDOC + +#if defined(Q_OS_MACOS) || defined(Q_OS_IOS) || defined(Q_QDOC) + +struct Q_GUI_EXPORT QRhiMetalInitParams : public QRhiInitParams +{ +}; + +struct Q_GUI_EXPORT QRhiMetalNativeHandles : public QRhiNativeHandles +{ + MTLDevice *dev = nullptr; + MTLCommandQueue *cmdQueue = nullptr; +}; + +struct Q_GUI_EXPORT QRhiMetalCommandBufferNativeHandles : public QRhiNativeHandles +{ + MTLCommandBuffer *commandBuffer = nullptr; + MTLRenderCommandEncoder *encoder = nullptr; +}; + +#endif // MACOS/IOS/QDOC + +QT_END_NAMESPACE + +#endif diff --git a/src/gui/rhi/qrhid3d11.cpp b/src/gui/rhi/qrhid3d11.cpp index 1e325b4d..ba126049 100644 --- a/src/gui/rhi/qrhid3d11.cpp +++ b/src/gui/rhi/qrhid3d11.cpp @@ -1,19 +1,14 @@ // Copyright (C) 2019 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 "qrhid3d11_p_p.h" -#include "qshader_p.h" +#include "qrhid3d11_p.h" +#include "qshader.h" #include "vs_test_p.h" -#include "cs_tdr_p.h" #include #include -#include #include #include - -#include - -#include +#include "qrhid3dhelpers_p.h" QT_BEGIN_NAMESPACE @@ -31,10 +26,13 @@ using namespace Qt::StringLiterals; /*! \class QRhiD3D11InitParams - \internal \inmodule QtGui + \since 6.6 \brief Direct3D 11 specific initialization parameters. + \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 desired, enableDebugLayer can be set to \c true to enable the Direct3D debug layer. This can be useful during development, but should be avoided @@ -47,9 +45,7 @@ using namespace Qt::StringLiterals; \endcode \note QRhiSwapChain should only be used in combination with QWindow - instances that have their surface type set to QSurface::OpenGLSurface. - There are currently no Direct3D specifics in the Windows platform support - of Qt and therefore there is no separate QSurface type available. + instances that have their surface type set to QSurface::Direct3DSurface. \section2 Working with existing Direct3D 11 devices @@ -74,17 +70,47 @@ using namespace Qt::StringLiterals; Initialization will fail otherwise. */ +/*! + \variable QRhiD3D11InitParams::enableDebugLayer + + When set to true, a debug device is created, assuming the debug layer is + available. The default value is false. +*/ + /*! \class QRhiD3D11NativeHandles - \internal \inmodule QtGui + \since 6.6 \brief Holds the D3D device and device context used by the QRhi. \note The class uses \c{void *} as the type since including the COM-based \c{d3d11.h} headers is not acceptable here. The actual types are \c{ID3D11Device *} and \c{ID3D11DeviceContext *}. + + \note This is a RHI API with limited compatibility guarantees, see \l QRhi + for details. */ +/*! + \variable QRhiD3D11NativeHandles::dev +*/ + +/*! + \variable QRhiD3D11NativeHandles::context +*/ + +/*! + \variable QRhiD3D11NativeHandles::featureLevel +*/ + +/*! + \variable QRhiD3D11NativeHandles::adapterLuidLow +*/ + +/*! + \variable QRhiD3D11NativeHandles::adapterLuidHigh +*/ + // help mingw with its ancient sdk headers #ifndef DXGI_ADAPTER_FLAG_SOFTWARE #define DXGI_ADAPTER_FLAG_SOFTWARE 2 @@ -99,14 +125,10 @@ using namespace Qt::StringLiterals; #endif QRhiD3D11::QRhiD3D11(QRhiD3D11InitParams *params, QRhiD3D11NativeHandles *importParams) - : ofr(this), - deviceCurse(this) + : ofr(this) { debugLayer = params->enableDebugLayer; - deviceCurse.framesToActivate = params->framesUntilKillingDeviceViaTdr; - deviceCurse.permanent = params->repeatDeviceKill; - if (importParams) { if (importParams->dev && importParams->context) { dev = reinterpret_cast(importParams->dev); @@ -133,22 +155,13 @@ inline Int aligned(Int v, Int byteAlign) static IDXGIFactory1 *createDXGIFactory2() { - typedef HRESULT(WINAPI* CreateDXGIFactory2Func) (UINT flags, REFIID riid, void** factory); - static CreateDXGIFactory2Func myCreateDXGIFactory2 = - (CreateDXGIFactory2Func)::GetProcAddress(::GetModuleHandle(L"dxgi"), "CreateDXGIFactory2"); - IDXGIFactory1 *result = nullptr; - - if (myCreateDXGIFactory2) - { - const HRESULT hr = myCreateDXGIFactory2(0, __uuidof(IDXGIFactory2), reinterpret_cast(&result)); - if (FAILED(hr)) { - qWarning("CreateDXGIFactory2() failed to create DXGI factory: %s", - qPrintable(QSystemError::windowsComString(hr))); - result = nullptr; - } + const HRESULT hr = CreateDXGIFactory2(0, __uuidof(IDXGIFactory2), reinterpret_cast(&result)); + if (FAILED(hr)) { + qWarning("CreateDXGIFactory2() failed to create DXGI factory: %s", + qPrintable(QSystemError::windowsComString(hr))); + result = nullptr; } - return result; } @@ -333,6 +346,11 @@ bool QRhiD3D11::create(QRhi::Flags flags) if (FAILED(context->QueryInterface(__uuidof(ID3DUserDefinedAnnotation), reinterpret_cast(&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; @@ -341,9 +359,6 @@ bool QRhiD3D11::create(QRhi::Flags flags) nativeHandlesStruct.adapterLuidLow = adapterLuid.LowPart; nativeHandlesStruct.adapterLuidHigh = adapterLuid.HighPart; - if (deviceCurse.framesToActivate > 0) - deviceCurse.initResources(); - return true; } @@ -361,7 +376,7 @@ void QRhiD3D11::destroy() clearShaderCache(); - deviceCurse.releaseResources(); + ofr.timestamps.destroy(); if (annotations) { annotations->Release(); @@ -565,6 +580,12 @@ bool QRhiD3D11::isFeatureSupported(QRhi::Feature feature) const return true; case QRhi::OneDimensionalTextureMipmaps: return true; + case QRhi::HalfAttributes: + return true; + case QRhi::RenderToOneDimensionalTexture: + return true; + case QRhi::ThreeDimensionalTextureMipmaps: + return true; default: Q_UNREACHABLE(); return false; @@ -889,7 +910,7 @@ void QRhiD3D11::setShaderResources(QRhiCommandBuffer *cb, QRhiShaderResourceBind bool srbUpdate = false; for (int i = 0, ie = srbD->sortedBindings.count(); i != ie; ++i) { - const QRhiShaderResourceBinding::Data *b = srbD->sortedBindings.at(i).data(); + const QRhiShaderResourceBinding::Data *b = shaderResourceBindingData(srbD->sortedBindings.at(i)); QD3D11ShaderResourceBindings::BoundResourceData &bd(srbD->boundResourceData[i]); switch (b->type) { case QRhiShaderResourceBinding::UniformBuffer: @@ -1252,6 +1273,12 @@ void QRhiD3D11::endExternal(QRhiCommandBuffer *cb) } } +double QRhiD3D11::lastCompletedGpuTime(QRhiCommandBuffer *cb) +{ + QD3D11CommandBuffer *cbD = QRHI_RES(QD3D11CommandBuffer, cb); + return cbD->lastGpuTime; +} + QRhi::FrameOpResult QRhiD3D11::beginFrame(QRhiSwapChain *swapChain, QRhi::BeginFrameFlags flags) { Q_UNUSED(flags); @@ -1260,30 +1287,6 @@ QRhi::FrameOpResult QRhiD3D11::beginFrame(QRhiSwapChain *swapChain, QRhi::BeginF contextState.currentSwapChain = swapChainD; const int currentFrameSlot = swapChainD->currentFrameSlot; - if (swapChainD->timestampActive[currentFrameSlot]) { - ID3D11Query *tsDisjoint = swapChainD->timestampDisjointQuery[currentFrameSlot]; - const int tsIdx = QD3D11SwapChain::BUFFER_COUNT * currentFrameSlot; - ID3D11Query *tsStart = swapChainD->timestampQuery[tsIdx]; - ID3D11Query *tsEnd = swapChainD->timestampQuery[tsIdx + 1]; - quint64 timestamps[2]; - D3D11_QUERY_DATA_TIMESTAMP_DISJOINT dj; - bool ok = true; - ok &= context->GetData(tsDisjoint, &dj, sizeof(dj), D3D11_ASYNC_GETDATA_DONOTFLUSH) == S_OK; - ok &= context->GetData(tsEnd, ×tamps[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, ×tamps[0], sizeof(quint64), D3D11_ASYNC_GETDATA_DONOTFLUSH) == S_OK; - if (ok) { - if (!dj.Disjoint && dj.Frequency) { - const float elapsedMs = (timestamps[1] - timestamps[0]) / float(dj.Frequency) * 1000.0f; - runGpuFrameTimeCallbacks(elapsedMs); - } - swapChainD->timestampActive[currentFrameSlot] = false; - } // else leave timestampActive set to true, will retry in a subsequent beginFrame - } - swapChainD->cb.resetState(); swapChainD->rt.d.rtv[0] = swapChainD->sampleDesc.Count > 1 ? @@ -1292,6 +1295,12 @@ QRhi::FrameOpResult QRhiD3D11::beginFrame(QRhiSwapChain *swapChain, QRhi::BeginF finishActiveReadbacks(); + if (swapChainD->timestamps.active[currentFrameSlot]) { + double elapsedSec = 0; + if (swapChainD->timestamps.tryQueryTimestamps(currentFrameSlot, context, &elapsedSec)) + swapChainD->cb.lastGpuTime = elapsedSec; + } + return QRhi::FrameOpSuccess; } @@ -1301,11 +1310,11 @@ QRhi::FrameOpResult QRhiD3D11::endFrame(QRhiSwapChain *swapChain, QRhi::EndFrame Q_ASSERT(contextState.currentSwapChain = swapChainD); const int currentFrameSlot = swapChainD->currentFrameSlot; - ID3D11Query *tsDisjoint = swapChainD->timestampDisjointQuery[currentFrameSlot]; + ID3D11Query *tsDisjoint = swapChainD->timestamps.disjointQuery[currentFrameSlot]; const int tsIdx = QD3D11SwapChain::BUFFER_COUNT * currentFrameSlot; - ID3D11Query *tsStart = swapChainD->timestampQuery[tsIdx]; - ID3D11Query *tsEnd = swapChainD->timestampQuery[tsIdx + 1]; - const bool recordTimestamps = tsDisjoint && tsStart && tsEnd && !swapChainD->timestampActive[currentFrameSlot]; + ID3D11Query *tsStart = swapChainD->timestamps.query[tsIdx]; + ID3D11Query *tsEnd = swapChainD->timestamps.query[tsIdx + 1]; + const bool recordTimestamps = tsDisjoint && tsStart && tsEnd && !swapChainD->timestamps.active[currentFrameSlot]; // send all commands to the context if (recordTimestamps) @@ -1323,7 +1332,7 @@ QRhi::FrameOpResult QRhiD3D11::endFrame(QRhiSwapChain *swapChain, QRhi::EndFrame if (recordTimestamps) { context->End(tsEnd); context->End(tsDisjoint); - swapChainD->timestampActive[currentFrameSlot] = true; + swapChainD->timestamps.active[currentFrameSlot] = true; } if (!flags.testFlag(QRhi::SkipPresent)) { @@ -1353,19 +1362,6 @@ QRhi::FrameOpResult QRhiD3D11::endFrame(QRhiSwapChain *swapChain, QRhi::EndFrame swapChainD->frameCount += 1; contextState.currentSwapChain = nullptr; - if (deviceCurse.framesToActivate > 0) { - deviceCurse.framesLeft -= 1; - if (deviceCurse.framesLeft == 0) { - deviceCurse.framesLeft = deviceCurse.framesToActivate; - if (!deviceCurse.permanent) - deviceCurse.framesToActivate = -1; - - deviceCurse.activate(); - } else if (deviceCurse.framesLeft % 100 == 0) { - qDebug("Impending doom: %d frames left", deviceCurse.framesLeft); - } - } - return QRhi::FrameOpSuccess; } @@ -1377,6 +1373,12 @@ 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; + } + return QRhi::FrameOpSuccess; } @@ -1385,11 +1387,26 @@ 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 + } + executeCommandBuffer(&ofr.cbWrapper); + context->Flush(); finishActiveReadbacks(); - context->Flush(); + if (recordTimestamps) { + context->End(tsEnd); + context->End(tsDisjoint); + ofr.timestamps.active[ofr.timestampIdx] = true; + ofr.timestampIdx = (ofr.timestampIdx + 1) % 2; + } return QRhi::FrameOpSuccess; } @@ -1658,6 +1675,8 @@ void QRhiD3D11::enqueueResourceUpdates(QRhiCommandBuffer *cb, QRhiResourceUpdate if (bufD->m_type == QRhiBuffer::Dynamic) { u.result->data.resize(u.readSize); memcpy(u.result->data.data(), bufD->dynBuf + u.offset, size_t(u.readSize)); + if (u.result->completed) + u.result->completed(); } else { BufferReadback readback; readback.result = u.result; @@ -1693,8 +1712,6 @@ void QRhiD3D11::enqueueResourceUpdates(QRhiCommandBuffer *cb, QRhiResourceUpdate activeBufferReadbacks.append(readback); } - if (u.result->completed) - u.result->completed(); } } for (int opIdx = 0; opIdx < ud->activeTextureOpCount; ++opIdx) { @@ -2193,7 +2210,7 @@ void QRhiD3D11::updateShaderResourceBindings(QD3D11ShaderResourceBindings *srbD, } res[RBM_SUPPORTED_STAGES]; for (int i = 0, ie = srbD->sortedBindings.count(); i != ie; ++i) { - const QRhiShaderResourceBinding::Data *b = srbD->sortedBindings.at(i).data(); + const QRhiShaderResourceBinding::Data *b = shaderResourceBindingData(srbD->sortedBindings.at(i)); QD3D11ShaderResourceBindings::BoundResourceData &bd(srbD->boundResourceData[i]); switch (b->type) { case QRhiShaderResourceBinding::UniformBuffer: @@ -2639,10 +2656,10 @@ void QRhiD3D11::executeCommandBuffer(QD3D11CommandBuffer *cbD, QD3D11SwapChain * if (timestampSwapChain) { const int currentFrameSlot = timestampSwapChain->currentFrameSlot; - ID3D11Query *tsDisjoint = timestampSwapChain->timestampDisjointQuery[currentFrameSlot]; + ID3D11Query *tsDisjoint = timestampSwapChain->timestamps.disjointQuery[currentFrameSlot]; const int tsIdx = QD3D11SwapChain::BUFFER_COUNT * currentFrameSlot; - ID3D11Query *tsStart = timestampSwapChain->timestampQuery[tsIdx]; - if (tsDisjoint && tsStart && !timestampSwapChain->timestampActive[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 @@ -3216,12 +3233,10 @@ bool QD3D11Texture::prepareCreate(QSize *adjustedSize) qWarning("Texture cannot be both 1D and 3D"); return false; } - m_depth = qMax(1, m_depth); if (m_depth > 1 && !is3D) { qWarning("Texture cannot have a depth of %d when it is not 3D", m_depth); return false; } - m_arraySize = qMax(0, m_arraySize); if (m_arraySize > 0 && !isArray) { qWarning("Texture cannot have an array size of %d when it is not an array", m_arraySize); return false; @@ -3261,7 +3276,7 @@ bool QD3D11Texture::finishCreate() srvDesc.Texture1DArray.ArraySize = UINT(m_arrayRangeLength); } else { srvDesc.Texture1DArray.FirstArraySlice = 0; - srvDesc.Texture1DArray.ArraySize = UINT(m_arraySize); + srvDesc.Texture1DArray.ArraySize = UINT(qMax(0, m_arraySize)); } } else { srvDesc.ViewDimension = D3D11_SRV_DIMENSION_TEXTURE1D; @@ -3275,7 +3290,7 @@ bool QD3D11Texture::finishCreate() srvDesc.Texture2DMSArray.ArraySize = UINT(m_arrayRangeLength); } else { srvDesc.Texture2DMSArray.FirstArraySlice = 0; - srvDesc.Texture2DMSArray.ArraySize = UINT(m_arraySize); + srvDesc.Texture2DMSArray.ArraySize = UINT(qMax(0, m_arraySize)); } } else { srvDesc.ViewDimension = D3D11_SRV_DIMENSION_TEXTURE2DARRAY; @@ -3285,7 +3300,7 @@ bool QD3D11Texture::finishCreate() srvDesc.Texture2DArray.ArraySize = UINT(m_arrayRangeLength); } else { srvDesc.Texture2DArray.FirstArraySlice = 0; - srvDesc.Texture2DArray.ArraySize = UINT(m_arraySize); + srvDesc.Texture2DArray.ArraySize = UINT(qMax(0, m_arraySize)); } } } else { @@ -3348,7 +3363,7 @@ bool QD3D11Texture::create() D3D11_TEXTURE1D_DESC desc = {}; desc.Width = UINT(size.width()); desc.MipLevels = mipLevelCount; - desc.ArraySize = isArray ? UINT(m_arraySize) : 1; + desc.ArraySize = isArray ? UINT(qMax(0, m_arraySize)) : 1; desc.Format = dxgiFormat; desc.Usage = D3D11_USAGE_DEFAULT; desc.BindFlags = bindFlags; @@ -3368,7 +3383,7 @@ bool QD3D11Texture::create() desc.Width = UINT(size.width()); desc.Height = UINT(size.height()); desc.MipLevels = mipLevelCount; - desc.ArraySize = isCube ? 6 : (isArray ? UINT(m_arraySize) : 1); + desc.ArraySize = isCube ? 6 : (isArray ? UINT(qMax(0, m_arraySize)) : 1); desc.Format = dxgiFormat; desc.SampleDesc = sampleDesc; desc.Usage = D3D11_USAGE_DEFAULT; @@ -3387,7 +3402,7 @@ bool QD3D11Texture::create() D3D11_TEXTURE3D_DESC desc = {}; desc.Width = UINT(size.width()); desc.Height = UINT(size.height()); - desc.Depth = UINT(m_depth); + desc.Depth = UINT(qMax(1, m_depth)); desc.MipLevels = mipLevelCount; desc.Format = dxgiFormat; desc.Usage = D3D11_USAGE_DEFAULT; @@ -3460,7 +3475,7 @@ ID3D11UnorderedAccessView *QD3D11Texture::unorderedAccessViewForLevel(int level) desc.ViewDimension = D3D11_UAV_DIMENSION_TEXTURE2DARRAY; desc.Texture2DArray.MipSlice = UINT(level); desc.Texture2DArray.FirstArraySlice = 0; - desc.Texture2DArray.ArraySize = UINT(m_arraySize); + desc.Texture2DArray.ArraySize = UINT(qMax(0, m_arraySize)); } else if (is3D) { desc.ViewDimension = D3D11_UAV_DIMENSION_TEXTURE3D; desc.Texture3D.MipSlice = UINT(level); @@ -3729,8 +3744,7 @@ bool QD3D11TextureRenderTarget::create() if (rtv[0] || dsv) destroy(); - const bool hasColorAttachments = m_desc.cbeginColorAttachments() != m_desc.cendColorAttachments(); - Q_ASSERT(hasColorAttachments || m_desc.depthTexture()); + Q_ASSERT(m_desc.colorAttachmentCount() > 0 || m_desc.depthTexture()); Q_ASSERT(!m_desc.depthStencilBuffer() || !m_desc.depthTexture()); const bool hasDepthStencil = m_desc.depthStencilBuffer() || m_desc.depthTexture(); @@ -3818,6 +3832,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", @@ -3904,11 +3939,7 @@ bool QD3D11ShaderResourceBindings::create() rhiD->updateLayoutDesc(this); std::copy(m_bindings.cbegin(), m_bindings.cend(), std::back_inserter(sortedBindings)); - std::sort(sortedBindings.begin(), sortedBindings.end(), - [](const QRhiShaderResourceBinding &a, const QRhiShaderResourceBinding &b) - { - return a.data()->binding < b.data()->binding; - }); + std::sort(sortedBindings.begin(), sortedBindings.end(), QRhiImplementation::sortedBindingLessThan); boundResourceData.resize(sortedBindings.count()); @@ -3917,7 +3948,7 @@ bool QD3D11ShaderResourceBindings::create() hasDynamicOffset = false; for (const QRhiShaderResourceBinding &b : sortedBindings) { - const QRhiShaderResourceBinding::Data *bd = b.data(); + const QRhiShaderResourceBinding::Data *bd = QRhiImplementation::shaderResourceBindingData(b); if (bd->type == QRhiShaderResourceBinding::UniformBuffer && bd->u.ubuf.hasDynamicOffset) { hasDynamicOffset = true; break; @@ -3933,13 +3964,8 @@ void QD3D11ShaderResourceBindings::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(), - [](const QRhiShaderResourceBinding &a, const QRhiShaderResourceBinding &b) - { - return a.data()->binding < b.data()->binding; - }); - } + if (!flags.testFlag(BindingsAreSorted)) + std::sort(sortedBindings.begin(), sortedBindings.end(), QRhiImplementation::sortedBindingLessThan); Q_ASSERT(boundResourceData.count() == sortedBindings.count()); for (BoundResourceData &bd : boundResourceData) @@ -4113,6 +4139,14 @@ static inline DXGI_FORMAT toD3DAttributeFormat(QRhiVertexInputAttribute::Format return DXGI_FORMAT_R32G32_SINT; case QRhiVertexInputAttribute::SInt: return DXGI_FORMAT_R32_SINT; + case QRhiVertexInputAttribute::Half4: + // Note: D3D does not support half3. Pass through half3 as half4. + case QRhiVertexInputAttribute::Half3: + return DXGI_FORMAT_R16G16B16A16_FLOAT; + case QRhiVertexInputAttribute::Half2: + return DXGI_FORMAT_R16G16_FLOAT; + case QRhiVertexInputAttribute::Half: + return DXGI_FORMAT_R16_FLOAT; default: Q_UNREACHABLE(); return DXGI_FORMAT_R32G32B32A32_FLOAT; @@ -4225,18 +4259,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(symbol); - } - } - return nullptr; -} - static inline QByteArray sourceHash(const QByteArray &source) { // taken from the GL backend, use the same mechanism to get a key @@ -4302,7 +4324,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(); @@ -4670,6 +4692,92 @@ void QD3D11CommandBuffer::destroy() // nothing to do here } +bool QD3D11Timestamps::prepare(int pairCount, 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) { + if (!disjointQuery[i]) { + queryDesc.Query = D3D11_QUERY_TIMESTAMP_DISJOINT; + HRESULT hr = rhiD->dev->CreateQuery(&queryDesc, &disjointQuery[i]); + if (FAILED(hr)) { + qWarning("Failed to create timestamp disjoint query: %s", + qPrintable(QSystemError::windowsComString(hr))); + return false; + } + } + queryDesc.Query = D3D11_QUERY_TIMESTAMP; + for (int j = 0; j < 2; ++j) { + const int idx = pairCount * i + j; + if (!query[idx]) { + HRESULT hr = rhiD->dev->CreateQuery(&queryDesc, &query[idx]); + if (FAILED(hr)) { + qWarning("Failed to create timestamp query: %s", + qPrintable(QSystemError::windowsComString(hr))); + return false; + } + } + } + } + this->pairCount = pairCount; + return true; +} + +void QD3D11Timestamps::destroy() +{ + for (int i = 0; i < MAX_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; + if (query[idx]) { + query[idx]->Release(); + query[idx] = nullptr; + } + } + } +} + +bool QD3D11Timestamps::tryQueryTimestamps(int idx, ID3D11DeviceContext *context, double *elapsedSec) +{ + bool result = false; + if (!active[idx]) + return result; + + ID3D11Query *tsDisjoint = disjointQuery[idx]; + const int tsIdx = pairCount * idx; + ID3D11Query *tsStart = query[tsIdx]; + ID3D11Query *tsEnd = query[tsIdx + 1]; + quint64 timestamps[2]; + D3D11_QUERY_DATA_TIMESTAMP_DISJOINT dj; + + bool ok = true; + ok &= context->GetData(tsDisjoint, &dj, sizeof(dj), D3D11_ASYNC_GETDATA_DONOTFLUSH) == S_OK; + ok &= context->GetData(tsEnd, ×tamps[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, ×tamps[0], sizeof(quint64), D3D11_ASYNC_GETDATA_DONOTFLUSH) == S_OK; + + if (ok) { + if (!dj.Disjoint && dj.Frequency) { + const float elapsedMs = (timestamps[1] - timestamps[0]) / float(dj.Frequency) * 1000.0f; + *elapsedSec = elapsedMs / 1000.0; + result = true; + } + active[idx] = false; + } // else leave active set, will retry in a subsequent beginFrame or similar + + return result; +} + QD3D11SwapChain::QD3D11SwapChain(QRhiImplementation *rhi) : QRhiSwapChain(rhi), rt(rhi, this), @@ -4680,10 +4788,6 @@ QD3D11SwapChain::QD3D11SwapChain(QRhiImplementation *rhi) for (int i = 0; i < BUFFER_COUNT; ++i) { msaaTex[i] = nullptr; msaaRtv[i] = nullptr; - timestampActive[i] = false; - timestampDisjointQuery[i] = nullptr; - timestampQuery[2 * i] = nullptr; - timestampQuery[2 * i + 1] = nullptr; } } @@ -4721,19 +4825,7 @@ void QD3D11SwapChain::destroy() releaseBuffers(); - for (int i = 0; i < BUFFER_COUNT; ++i) { - if (timestampDisjointQuery[i]) { - timestampDisjointQuery[i]->Release(); - timestampDisjointQuery[i] = nullptr; - } - for (int j = 0; j < 2; ++j) { - const int idx = BUFFER_COUNT * i + j; - if (timestampQuery[idx]) { - timestampQuery[idx]->Release(); - timestampQuery[idx] = nullptr; - } - } - } + timestamps.destroy(); swapChain->Release(); swapChain = nullptr; @@ -4830,7 +4922,7 @@ bool QD3D11SwapChain::isFormatSupported(Format f) QRhiSwapChainHdrInfo QD3D11SwapChain::hdrInfo() { QRhiSwapChainHdrInfo info = QRhiSwapChain::hdrInfo(); - if (m_format != QRhiSwapChain::SDR && m_window) { + if (m_window) { QRHI_RES_RHI(QRhiD3D11); DXGI_OUTPUT_DESC1 hdrOutputDesc; if (outputDesc1ForWindow(m_window, rhiD->activeAdapter, &hdrOutputDesc)) { @@ -4887,36 +4979,13 @@ bool QD3D11SwapChain::newColorBuffer(const QSize &size, DXGI_FORMAT format, DXGI return true; } -static IDCompositionDevice *createDirectCompositionDevice() -{ - QSystemLibrary dcomplib(QStringLiteral("dcomp")); - typedef HRESULT (__stdcall *DCompositionCreateDeviceFuncPtr)( - _In_opt_ IDXGIDevice *dxgiDevice, - _In_ REFIID iid, - _Outptr_ void **dcompositionDevice); - DCompositionCreateDeviceFuncPtr func = reinterpret_cast( - 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(&device)); - if (FAILED(hr)) { - qWarning("Failed to Direct Composition device: %s", - qPrintable(QSystemError::windowsComString(hr))); - return nullptr; - } - return device; -} - bool QRhiD3D11::ensureDirectCompositionDevice() { if (dcompDevice) return true; qCDebug(QRHI_LOG_INFO, "Creating Direct Composition device (needed for semi-transparent windows)"); - dcompDevice = createDirectCompositionDevice(); + dcompDevice = QRhiD3D::createDirectCompositionDevice(); return dcompDevice ? true : false; } @@ -4925,15 +4994,6 @@ static const DXGI_FORMAT DEFAULT_SRGB_FORMAT = DXGI_FORMAT_R8G8B8A8_UNORM_SRGB; bool QD3D11SwapChain::createOrResize() { - if (IsWindows10OrGreater()) - { - // continue - } - else - { - return createOrResizeWin7(); - } - // Can be called multiple times due to window resizes - that is not the // same as a simple destroy+create (as with other resources). Just need to // resize the buffers then. @@ -5097,6 +5157,9 @@ 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)) { @@ -5104,7 +5167,6 @@ bool QD3D11SwapChain::createOrResize() qPrintable(QSystemError::windowsComString(hr))); 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 @@ -5189,31 +5251,8 @@ bool QD3D11SwapChain::createOrResize() rtD->d.colorAttCount = 1; rtD->d.dsAttCount = m_depthStencil ? 1 : 0; - if (rhiD->hasGpuFrameTimeCallback()) { - D3D11_QUERY_DESC queryDesc = {}; - for (int i = 0; i < BUFFER_COUNT; ++i) { - if (!timestampDisjointQuery[i]) { - queryDesc.Query = D3D11_QUERY_TIMESTAMP_DISJOINT; - hr = rhiD->dev->CreateQuery(&queryDesc, ×tampDisjointQuery[i]); - if (FAILED(hr)) { - qWarning("Failed to create timestamp disjoint query: %s", - qPrintable(QSystemError::windowsComString(hr))); - break; - } - } - queryDesc.Query = D3D11_QUERY_TIMESTAMP; - for (int j = 0; j < 2; ++j) { - const int idx = BUFFER_COUNT * i + j; // one pair per buffer (frame) - if (!timestampQuery[idx]) { - hr = rhiD->dev->CreateQuery(&queryDesc, ×tampQuery[idx]); - if (FAILED(hr)) { - qWarning("Failed to create timestamp query: %s", - qPrintable(QSystemError::windowsComString(hr))); - break; - } - } - } - } + if (rhiD->rhiFlags.testFlag(QRhi::EnableTimestamps)) { + timestamps.prepare(BUFFER_COUNT, rhiD); // timestamp queries are optional so we can go on even if they failed } @@ -5223,324 +5262,4 @@ bool QD3D11SwapChain::createOrResize() return true; } -bool QD3D11SwapChain::createOrResizeWin7() -{ - // Can be called multiple times due to window resizes - that is not the - // same as a simple destroy+create (as with other resources). Just need to - // resize the buffers then. - - const bool needsRegistration = !window || window != m_window; - - // except if the window actually changes - if (window && window != m_window) - destroy(); - - window = m_window; - m_currentPixelSize = surfacePixelSize(); - pixelSize = m_currentPixelSize; - - if (pixelSize.isEmpty()) - return false; - - QRHI_RES_RHI(QRhiD3D11); - bool useFlipModel = rhiD->supportsFlipSwapchain; - - // Take a shortcut for alpha: whatever the platform plugin does to enable - // transparency for our QWindow will be sufficient on the legacy (DISCARD) - // path. For FLIP_* we'd need to use DirectComposition (create a - // IDCompositionDevice/Target/Visual), avoid that for now. (this though - // means HDR and semi-transparent windows cannot be combined) - if (m_flags.testFlag(SurfaceHasPreMulAlpha) || m_flags.testFlag(SurfaceHasNonPreMulAlpha)) { - useFlipModel = false; - if (window->requestedFormat().alphaBufferSize() <= 0) - qWarning("Swapchain says surface has alpha but the window has no alphaBufferSize set. " - "This may lead to problems."); - } - - swapInterval = m_flags.testFlag(QRhiSwapChain::NoVSync) ? 0 : 1; - swapChainFlags = 0; - - // A non-flip swapchain can do Present(0) as expected without - // ALLOW_TEARING, and ALLOW_TEARING is not compatible with it at all so the - // flag must not be set then. Whereas for flip we should use it, if - // supported, to get better results for 'unthrottled' presentation. - if (swapInterval == 0 && useFlipModel && rhiD->supportsAllowTearing) - swapChainFlags |= DXGI_SWAP_CHAIN_FLAG_ALLOW_TEARING; - - if (!swapChain) { - HWND hwnd = reinterpret_cast(window->winId()); - sampleDesc = rhiD->effectiveSampleCount(m_sampleCount); - colorFormat = DEFAULT_FORMAT; - srgbAdjustedColorFormat = m_flags.testFlag(sRGB) ? DEFAULT_SRGB_FORMAT : DEFAULT_FORMAT; - - HRESULT hr; - if (useFlipModel) { - 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) { - // 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) { - case HDRExtendedSrgbLinear: - colorFormat = DXGI_FORMAT_R16G16B16A16_FLOAT; - hdrColorSpace = DXGI_COLOR_SPACE_RGB_FULL_G10_NONE_P709; - srgbAdjustedColorFormat = colorFormat; - break; - case HDR10: - colorFormat = DXGI_FORMAT_R10G10B10A2_UNORM; - hdrColorSpace = DXGI_COLOR_SPACE_RGB_FULL_G2084_NONE_P2020; - srgbAdjustedColorFormat = colorFormat; - break; - default: - break; - } - } else { - // This happens also when Use HDR is set to Off in the Windows - // Display settings. Show a helpful warning, but continue with the - // default non-HDR format. - qWarning("The output associated with the window is not HDR capable " - "(or Use HDR is Off in the Display Settings), ignoring HDR format request"); - } - } - - // We use a FLIP model swapchain which implies a buffer count of 2 - // (as opposed to the old DISCARD with back buffer count == 1). - // This makes no difference for the rest of the stuff except that - // automatic MSAA is unsupported and needs to be implemented via a - // custom multisample render target and an explicit resolve. - - DXGI_SWAP_CHAIN_DESC1 desc; - memset(&desc, 0, sizeof(desc)); - desc.Width = UINT(pixelSize.width()); - desc.Height = UINT(pixelSize.height()); - desc.Format = colorFormat; - desc.SampleDesc.Count = 1; - desc.BufferUsage = DXGI_USAGE_RENDER_TARGET_OUTPUT; - desc.BufferCount = BUFFER_COUNT; - - // Normally we'd want FLIP_DISCARD, but that comes with the default - // SCALING_STRETCH, as SCALING_NONE is documented to be only - // available for FLIP_SEQUENTIAl. The problem with stretch is that - // Qt Quick and similar apps typically running in resizable windows - // will not like how that looks in practice: the content will - // appear to be "jumping" around during a window resize. So choose - // sequential/none by default. - if (rhiD->forceFlipDiscard) { - desc.Scaling = DXGI_SCALING_STRETCH; - desc.SwapEffect = DXGI_SWAP_EFFECT_FLIP_DISCARD; - } else { - desc.Scaling = DXGI_SCALING_NONE; - desc.SwapEffect = DXGI_SWAP_EFFECT_FLIP_SEQUENTIAL; - } - - // Do not bother with AlphaMode, if won't work unless we go through - // DirectComposition. Instead, we just take the other (DISCARD) - // path for now when alpha is requested. - desc.Flags = swapChainFlags; - - IDXGIFactory2 *fac = static_cast(rhiD->dxgiFactory); - IDXGISwapChain1 *sc1; - hr = fac->CreateSwapChainForHwnd(rhiD->dev, hwnd, &desc, nullptr, nullptr, &sc1); - - // If failed and we tried a HDR format, then try with SDR. This - // matches other backends, such as Vulkan where if the format is - // not supported, the default one is used instead. - if (FAILED(hr) && m_format != SDR) { - colorFormat = DEFAULT_FORMAT; - desc.Format = DEFAULT_FORMAT; - hr = fac->CreateSwapChainForHwnd(rhiD->dev, hwnd, &desc, nullptr, nullptr, &sc1); - } - - if (SUCCEEDED(hr)) { - swapChain = sc1; - if (m_format != SDR) { - IDXGISwapChain3 *sc3 = nullptr; - if (SUCCEEDED(sc1->QueryInterface(__uuidof(IDXGISwapChain3), reinterpret_cast(&sc3)))) { - hr = sc3->SetColorSpace1(hdrColorSpace); - if (FAILED(hr)) - qWarning("Failed to set color space on swapchain: %s", - qPrintable(QSystemError::windowsComString(hr))); - sc3->Release(); - } else { - qWarning("IDXGISwapChain3 not available, HDR swapchain will not work as expected"); - } - } - } - } else { - // Fallback: use DISCARD mode. Regardless, keep on using our manual - // resolve for symmetry with the FLIP_* code path when MSAA is - // requested. This has no HDR support. - - DXGI_SWAP_CHAIN_DESC desc; - memset(&desc, 0, sizeof(desc)); - desc.BufferDesc.Width = UINT(pixelSize.width()); - desc.BufferDesc.Height = UINT(pixelSize.height()); - desc.BufferDesc.RefreshRate.Numerator = 60; - desc.BufferDesc.RefreshRate.Denominator = 1; - desc.BufferDesc.Format = colorFormat; - desc.SampleDesc.Count = 1; - desc.BufferUsage = DXGI_USAGE_RENDER_TARGET_OUTPUT; - desc.BufferCount = 1; - desc.OutputWindow = hwnd; - desc.Windowed = true; - desc.SwapEffect = DXGI_SWAP_EFFECT_DISCARD; - desc.Flags = swapChainFlags; - - hr = rhiD->dxgiFactory->CreateSwapChain(rhiD->dev, &desc, &swapChain); - } - if (FAILED(hr)) { - qWarning("Failed to create D3D11 swapchain: %s", - qPrintable(QSystemError::windowsComString(hr))); - return false; - } - rhiD->dxgiFactory->MakeWindowAssociation(hwnd, DXGI_MWA_NO_WINDOW_CHANGES); - } else { - releaseBuffers(); - const UINT count = useFlipModel ? BUFFER_COUNT : 1; - HRESULT hr = swapChain->ResizeBuffers(count, UINT(pixelSize.width()), UINT(pixelSize.height()), - colorFormat, swapChainFlags); - if (hr == DXGI_ERROR_DEVICE_REMOVED || hr == DXGI_ERROR_DEVICE_RESET) { - qWarning("Device loss detected in ResizeBuffers()"); - rhiD->deviceLost = true; - return false; - } else if (FAILED(hr)) { - qWarning("Failed to resize D3D11 swapchain: %s", - qPrintable(QSystemError::windowsComString(hr))); - return false; - } - } - - // This looks odd (for FLIP_*, esp. compared with backends for Vulkan - // & co.) but the backbuffer is always at index 0, with magic underneath. - // Some explanation from - // https://docs.microsoft.com/en-us/windows/win32/direct3ddxgi/dxgi-1-4-improvements - // - // "In Direct3D 11, applications could call GetBuffer( 0, … ) only once. - // Every call to Present implicitly changed the resource identity of the - // returned interface. Direct3D 12 no longer supports that implicit - // resource identity change, due to the CPU overhead required and the - // flexible resource descriptor design. As a result, the application must - // manually call GetBuffer for every each buffer created with the - // swapchain." - - // So just query index 0 once (per resize) and be done with it. - HRESULT hr = swapChain->GetBuffer(0, __uuidof(ID3D11Texture2D), reinterpret_cast(&backBufferTex)); - if (FAILED(hr)) { - qWarning("Failed to query swapchain backbuffer: %s", - qPrintable(QSystemError::windowsComString(hr))); - return false; - } - D3D11_RENDER_TARGET_VIEW_DESC rtvDesc = {}; - rtvDesc.Format = srgbAdjustedColorFormat; - rtvDesc.ViewDimension = D3D11_RTV_DIMENSION_TEXTURE2D; - hr = rhiD->dev->CreateRenderTargetView(backBufferTex, &rtvDesc, &backBufferRtv); - if (FAILED(hr)) { - qWarning("Failed to create rtv for swapchain backbuffer: %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) { - if (!newColorBuffer(pixelSize, srgbAdjustedColorFormat, sampleDesc, &msaaTex[i], &msaaRtv[i])) - return false; - } - } - - if (m_depthStencil && m_depthStencil->sampleCount() != m_sampleCount) { - qWarning("Depth-stencil buffer's sampleCount (%d) does not match color buffers' sample count (%d). Expect problems.", - m_depthStencil->sampleCount(), m_sampleCount); - } - if (m_depthStencil && m_depthStencil->pixelSize() != pixelSize) { - if (m_depthStencil->flags().testFlag(QRhiRenderBuffer::UsedWithSwapChainOnly)) { - m_depthStencil->setPixelSize(pixelSize); - if (!m_depthStencil->create()) - qWarning("Failed to rebuild swapchain's associated depth-stencil buffer for size %dx%d", - pixelSize.width(), pixelSize.height()); - } else { - qWarning("Depth-stencil buffer's size (%dx%d) does not match the surface size (%dx%d). Expect problems.", - m_depthStencil->pixelSize().width(), m_depthStencil->pixelSize().height(), - pixelSize.width(), pixelSize.height()); - } - } - - currentFrameSlot = 0; - frameCount = 0; - ds = m_depthStencil ? QRHI_RES(QD3D11RenderBuffer, m_depthStencil) : nullptr; - - rt.setRenderPassDescriptor(m_renderPassDesc); // for the public getter in QRhiRenderTarget - QD3D11SwapChainRenderTarget *rtD = QRHI_RES(QD3D11SwapChainRenderTarget, &rt); - 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; - - if (rhiD->hasGpuFrameTimeCallback()) { - D3D11_QUERY_DESC queryDesc = {}; - for (int i = 0; i < BUFFER_COUNT; ++i) { - if (!timestampDisjointQuery[i]) { - queryDesc.Query = D3D11_QUERY_TIMESTAMP_DISJOINT; - hr = rhiD->dev->CreateQuery(&queryDesc, ×tampDisjointQuery[i]); - if (FAILED(hr)) { - qWarning("Failed to create timestamp disjoint query: %s", - qPrintable(QSystemError::windowsComString(hr))); - break; - } - } - queryDesc.Query = D3D11_QUERY_TIMESTAMP; - for (int j = 0; j < 2; ++j) { - const int idx = BUFFER_COUNT * i + j; // one pair per buffer (frame) - if (!timestampQuery[idx]) { - hr = rhiD->dev->CreateQuery(&queryDesc, ×tampQuery[idx]); - if (FAILED(hr)) { - qWarning("Failed to create timestamp query: %s", - qPrintable(QSystemError::windowsComString(hr))); - break; - } - } - } - } - // timestamp queries are optional so we can go on even if they failed - } - - if (needsRegistration) - rhiD->registerResource(this); - - return true; -} - -void QRhiD3D11::DeviceCurse::initResources() -{ - framesLeft = framesToActivate; - - HRESULT hr = q->dev->CreateComputeShader(g_killDeviceByTimingOut, sizeof(g_killDeviceByTimingOut), nullptr, &cs); - if (FAILED(hr)) { - qWarning("Failed to create compute shader: %s", - qPrintable(QSystemError::windowsComString(hr))); - return; - } -} - -void QRhiD3D11::DeviceCurse::releaseResources() -{ - if (cs) { - cs->Release(); - cs = nullptr; - } -} - -void QRhiD3D11::DeviceCurse::activate() -{ - if (!cs) - return; - - qDebug("Activating Curse. Goodbye Cruel World."); - - q->context->CSSetShader(cs, nullptr, 0); - q->context->Dispatch(256, 1, 1); -} - QT_END_NAMESPACE diff --git a/src/gui/rhi/qrhid3d11_p.h b/src/gui/rhi/qrhid3d11_p.h index 31aa58a6..0120df36 100644 --- a/src/gui/rhi/qrhid3d11_p.h +++ b/src/gui/rhi/qrhid3d11_p.h @@ -1,8 +1,8 @@ -// Copyright (C) 2019 The Qt Company Ltd. +// 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 QRHID3D11_H -#define QRHID3D11_H +#ifndef QRHID3D11_P_H +#define QRHID3D11_P_H // // W A R N I N G @@ -15,31 +15,838 @@ // We mean it. // -#include +#include "qrhi_p.h" +#include +#include -// no d3d includes here, to prevent precompiled header mess due to COM +#include +#include +#include QT_BEGIN_NAMESPACE -struct Q_GUI_EXPORT QRhiD3D11InitParams : public QRhiInitParams -{ - bool enableDebugLayer = false; +class QRhiD3D11; - int framesUntilKillingDeviceViaTdr = -1; - bool repeatDeviceKill = false; +struct QD3D11Buffer : public QRhiBuffer +{ + QD3D11Buffer(QRhiImplementation *rhi, Type type, UsageFlags usage, quint32 size); + ~QD3D11Buffer(); + void destroy() override; + bool create() override; + QRhiBuffer::NativeBuffer nativeBuffer() override; + char *beginFullDynamicBufferUpdateForCurrentFrame() override; + void endFullDynamicBufferUpdateForCurrentFrame() override; + + ID3D11UnorderedAccessView *unorderedAccessView(quint32 offset); + + ID3D11Buffer *buffer = nullptr; + char *dynBuf = nullptr; + bool hasPendingDynamicUpdates = false; + QHash uavs; + uint generation = 0; + friend class QRhiD3D11; }; -struct Q_GUI_EXPORT QRhiD3D11NativeHandles : public QRhiNativeHandles +struct QD3D11RenderBuffer : public QRhiRenderBuffer { - // to import a device and a context - void *dev = nullptr; - void *context = nullptr; - // alternatively, to specify the device feature level and/or the adapter to use - int featureLevel = 0; - quint32 adapterLuidLow = 0; - qint32 adapterLuidHigh = 0; + QD3D11RenderBuffer(QRhiImplementation *rhi, Type type, const QSize &pixelSize, + int sampleCount, QRhiRenderBuffer::Flags flags, + QRhiTexture::Format backingFormatHint); + ~QD3D11RenderBuffer(); + void destroy() override; + bool create() override; + QRhiTexture::Format backingFormat() const override; + + ID3D11Texture2D *tex = nullptr; + ID3D11DepthStencilView *dsv = nullptr; + ID3D11RenderTargetView *rtv = nullptr; + DXGI_FORMAT dxgiFormat; + DXGI_SAMPLE_DESC sampleDesc; + uint generation = 0; + friend class QRhiD3D11; }; +struct QD3D11Texture : public QRhiTexture +{ + QD3D11Texture(QRhiImplementation *rhi, Format format, const QSize &pixelSize, int depth, + int arraySize, int sampleCount, Flags flags); + ~QD3D11Texture(); + void destroy() override; + bool create() override; + bool createFrom(NativeTexture src) override; + NativeTexture nativeTexture() override; + + bool prepareCreate(QSize *adjustedSize = nullptr); + bool finishCreate(); + ID3D11UnorderedAccessView *unorderedAccessViewForLevel(int level); + ID3D11Resource *textureResource() const + { + if (tex) + return tex; + else if (tex1D) + return tex1D; + return tex3D; + } + + ID3D11Texture2D *tex = nullptr; + ID3D11Texture3D *tex3D = nullptr; + ID3D11Texture1D *tex1D = nullptr; + bool owns = true; + ID3D11ShaderResourceView *srv = nullptr; + DXGI_FORMAT dxgiFormat; + uint mipLevelCount = 0; + DXGI_SAMPLE_DESC sampleDesc; + ID3D11UnorderedAccessView *perLevelViews[QRhi::MAX_MIP_LEVELS]; + uint generation = 0; + friend class QRhiD3D11; +}; + +struct QD3D11Sampler : public QRhiSampler +{ + QD3D11Sampler(QRhiImplementation *rhi, Filter magFilter, Filter minFilter, Filter mipmapMode, + AddressMode u, AddressMode v, AddressMode w); + ~QD3D11Sampler(); + void destroy() override; + bool create() override; + + ID3D11SamplerState *samplerState = nullptr; + uint generation = 0; + friend class QRhiD3D11; +}; + +struct QD3D11RenderPassDescriptor : public QRhiRenderPassDescriptor +{ + QD3D11RenderPassDescriptor(QRhiImplementation *rhi); + ~QD3D11RenderPassDescriptor(); + void destroy() override; + bool isCompatible(const QRhiRenderPassDescriptor *other) const override; + QRhiRenderPassDescriptor *newCompatibleRenderPassDescriptor() const override; + QVector serializedFormat() const override; +}; + +struct QD3D11RenderTargetData +{ + QD3D11RenderTargetData(QRhiImplementation *) + { + for (int i = 0; i < MAX_COLOR_ATTACHMENTS; ++i) + rtv[i] = nullptr; + } + + QD3D11RenderPassDescriptor *rp = nullptr; + QSize pixelSize; + float dpr = 1; + int sampleCount = 1; + int colorAttCount = 0; + int dsAttCount = 0; + + static const int MAX_COLOR_ATTACHMENTS = 8; + ID3D11RenderTargetView *rtv[MAX_COLOR_ATTACHMENTS]; + ID3D11DepthStencilView *dsv = nullptr; + + QRhiRenderTargetAttachmentTracker::ResIdList currentResIdList; +}; + +struct QD3D11SwapChainRenderTarget : public QRhiSwapChainRenderTarget +{ + QD3D11SwapChainRenderTarget(QRhiImplementation *rhi, QRhiSwapChain *swapchain); + ~QD3D11SwapChainRenderTarget(); + void destroy() override; + + QSize pixelSize() const override; + float devicePixelRatio() const override; + int sampleCount() const override; + + QD3D11RenderTargetData d; +}; + +struct QD3D11TextureRenderTarget : public QRhiTextureRenderTarget +{ + QD3D11TextureRenderTarget(QRhiImplementation *rhi, const QRhiTextureRenderTargetDescription &desc, Flags flags); + ~QD3D11TextureRenderTarget(); + void destroy() override; + + QSize pixelSize() const override; + float devicePixelRatio() const override; + int sampleCount() const override; + + QRhiRenderPassDescriptor *newCompatibleRenderPassDescriptor() override; + bool create() override; + + QD3D11RenderTargetData d; + bool ownsRtv[QD3D11RenderTargetData::MAX_COLOR_ATTACHMENTS]; + ID3D11RenderTargetView *rtv[QD3D11RenderTargetData::MAX_COLOR_ATTACHMENTS]; + bool ownsDsv = false; + ID3D11DepthStencilView *dsv = nullptr; + friend class QRhiD3D11; +}; + +struct QD3D11ShaderResourceBindings : public QRhiShaderResourceBindings +{ + QD3D11ShaderResourceBindings(QRhiImplementation *rhi); + ~QD3D11ShaderResourceBindings(); + void destroy() override; + bool create() override; + void updateResources(UpdateFlags flags) override; + + bool hasDynamicOffset = false; + QVarLengthArray sortedBindings; + uint generation = 0; + + // Keep track of the generation number of each referenced QRhi* to be able + // to detect that the batched bindings are out of date. + struct BoundUniformBufferData { + quint64 id; + uint generation; + }; + struct BoundSampledTextureData { + int count; + struct { + quint64 texId; + uint texGeneration; + quint64 samplerId; + uint samplerGeneration; + } d[QRhiShaderResourceBinding::Data::MAX_TEX_SAMPLER_ARRAY_SIZE]; + }; + struct BoundStorageImageData { + quint64 id; + uint generation; + }; + struct BoundStorageBufferData { + quint64 id; + uint generation; + }; + struct BoundResourceData { + union { + BoundUniformBufferData ubuf; + BoundSampledTextureData stex; + BoundStorageImageData simage; + BoundStorageBufferData sbuf; + }; + }; + QVarLengthArray boundResourceData; + + struct StageUniformBufferBatches { + bool present = false; + QRhiBatchedBindings ubufs; + QRhiBatchedBindings ubuforigbindings; + QRhiBatchedBindings ubufoffsets; + QRhiBatchedBindings ubufsizes; + void finish() { + present = ubufs.finish(); + ubuforigbindings.finish(); + ubufoffsets.finish(); + ubufsizes.finish(); + } + void clear() { + ubufs.clear(); + ubuforigbindings.clear(); + ubufoffsets.clear(); + ubufsizes.clear(); + } + }; + + struct StageSamplerBatches { + bool present = false; + QRhiBatchedBindings samplers; + QRhiBatchedBindings shaderresources; + void finish() { + present = samplers.finish(); + shaderresources.finish(); + } + void clear() { + samplers.clear(); + shaderresources.clear(); + } + }; + + struct StageUavBatches { + bool present = false; + QRhiBatchedBindings uavs; + void finish() { + present = uavs.finish(); + } + void clear() { + uavs.clear(); + } + }; + + StageUniformBufferBatches vsUniformBufferBatches; + StageUniformBufferBatches hsUniformBufferBatches; + StageUniformBufferBatches dsUniformBufferBatches; + StageUniformBufferBatches gsUniformBufferBatches; + StageUniformBufferBatches fsUniformBufferBatches; + StageUniformBufferBatches csUniformBufferBatches; + + StageSamplerBatches vsSamplerBatches; + StageSamplerBatches hsSamplerBatches; + StageSamplerBatches dsSamplerBatches; + StageSamplerBatches gsSamplerBatches; + StageSamplerBatches fsSamplerBatches; + StageSamplerBatches csSamplerBatches; + + StageUavBatches csUavBatches; + + friend class QRhiD3D11; +}; + +Q_DECLARE_TYPEINFO(QD3D11ShaderResourceBindings::BoundResourceData, Q_RELOCATABLE_TYPE); + +struct QD3D11GraphicsPipeline : public QRhiGraphicsPipeline +{ + QD3D11GraphicsPipeline(QRhiImplementation *rhi); + ~QD3D11GraphicsPipeline(); + void destroy() override; + bool create() override; + + ID3D11DepthStencilState *dsState = nullptr; + ID3D11BlendState *blendState = nullptr; + struct { + ID3D11VertexShader *shader = nullptr; + QShader::NativeResourceBindingMap nativeResourceBindingMap; + } vs; + struct { + ID3D11HullShader *shader = nullptr; + QShader::NativeResourceBindingMap nativeResourceBindingMap; + } hs; + struct { + ID3D11DomainShader *shader = nullptr; + QShader::NativeResourceBindingMap nativeResourceBindingMap; + } ds; + struct { + ID3D11GeometryShader *shader = nullptr; + QShader::NativeResourceBindingMap nativeResourceBindingMap; + } gs; + struct { + ID3D11PixelShader *shader = nullptr; + QShader::NativeResourceBindingMap nativeResourceBindingMap; + } fs; + ID3D11InputLayout *inputLayout = nullptr; + D3D11_PRIMITIVE_TOPOLOGY d3dTopology = D3D11_PRIMITIVE_TOPOLOGY_TRIANGLELIST; + ID3D11RasterizerState *rastState = nullptr; + uint generation = 0; + friend class QRhiD3D11; +}; + +struct QD3D11ComputePipeline : public QRhiComputePipeline +{ + QD3D11ComputePipeline(QRhiImplementation *rhi); + ~QD3D11ComputePipeline(); + void destroy() override; + bool create() override; + + struct { + ID3D11ComputeShader *shader = nullptr; + QShader::NativeResourceBindingMap nativeResourceBindingMap; + } cs; + uint generation = 0; + friend class QRhiD3D11; +}; + +struct QD3D11SwapChain; + +struct QD3D11CommandBuffer : public QRhiCommandBuffer +{ + QD3D11CommandBuffer(QRhiImplementation *rhi); + ~QD3D11CommandBuffer(); + void destroy() override; + + // these must be kept at a reasonably low value otherwise sizeof Command explodes + static const int MAX_DYNAMIC_OFFSET_COUNT = 8; + static const int MAX_VERTEX_BUFFER_BINDING_COUNT = 8; + + struct Command { + enum Cmd { + ResetShaderResources, + SetRenderTarget, + Clear, + Viewport, + Scissor, + BindVertexBuffers, + BindIndexBuffer, + BindGraphicsPipeline, + BindShaderResources, + StencilRef, + BlendConstants, + Draw, + DrawIndexed, + UpdateSubRes, + CopySubRes, + ResolveSubRes, + GenMip, + DebugMarkBegin, + DebugMarkEnd, + DebugMarkMsg, + BindComputePipeline, + Dispatch + }; + enum ClearFlag { Color = 1, Depth = 2, Stencil = 4 }; + Cmd cmd; + + // QRhi*/QD3D11* references should be kept at minimum (so no + // QRhiTexture/Buffer/etc. pointers). + union Args { + struct { + QRhiRenderTarget *rt; + } setRenderTarget; + struct { + QRhiRenderTarget *rt; + int mask; + float c[4]; + float d; + quint32 s; + } clear; + struct { + float x, y, w, h; + float d0, d1; + } viewport; + struct { + int x, y, w, h; + } scissor; + struct { + int startSlot; + int slotCount; + ID3D11Buffer *buffers[MAX_VERTEX_BUFFER_BINDING_COUNT]; + UINT offsets[MAX_VERTEX_BUFFER_BINDING_COUNT]; + UINT strides[MAX_VERTEX_BUFFER_BINDING_COUNT]; + } bindVertexBuffers; + struct { + ID3D11Buffer *buffer; + quint32 offset; + DXGI_FORMAT format; + } bindIndexBuffer; + struct { + QD3D11GraphicsPipeline *ps; + } bindGraphicsPipeline; + struct { + QD3D11ShaderResourceBindings *srb; + bool offsetOnlyChange; + int dynamicOffsetCount; + uint dynamicOffsetPairs[MAX_DYNAMIC_OFFSET_COUNT * 2]; // binding, offsetInConstants + } bindShaderResources; + struct { + QD3D11GraphicsPipeline *ps; + quint32 ref; + } stencilRef; + struct { + QD3D11GraphicsPipeline *ps; + float c[4]; + } blendConstants; + struct { + QD3D11GraphicsPipeline *ps; + quint32 vertexCount; + quint32 instanceCount; + quint32 firstVertex; + quint32 firstInstance; + } draw; + struct { + QD3D11GraphicsPipeline *ps; + quint32 indexCount; + quint32 instanceCount; + quint32 firstIndex; + qint32 vertexOffset; + quint32 firstInstance; + } drawIndexed; + struct { + ID3D11Resource *dst; + UINT dstSubRes; + bool hasDstBox; + D3D11_BOX dstBox; + const void *src; // must come from retain*() + UINT srcRowPitch; + } updateSubRes; + struct { + ID3D11Resource *dst; + UINT dstSubRes; + UINT dstX; + UINT dstY; + UINT dstZ; + ID3D11Resource *src; + UINT srcSubRes; + bool hasSrcBox; + D3D11_BOX srcBox; + } copySubRes; + struct { + ID3D11Resource *dst; + UINT dstSubRes; + ID3D11Resource *src; + UINT srcSubRes; + DXGI_FORMAT format; + } resolveSubRes; + struct { + ID3D11ShaderResourceView *srv; + } genMip; + struct { + char s[64]; + } debugMark; + struct { + QD3D11ComputePipeline *ps; + } bindComputePipeline; + struct { + UINT x; + UINT y; + UINT z; + } dispatch; + } args; + }; + + enum PassType { + NoPass, + RenderPass, + ComputePass + }; + + QRhiBackendCommandList commands; + PassType recordingPass; + double lastGpuTime = 0; + QRhiRenderTarget *currentTarget; + QRhiGraphicsPipeline *currentGraphicsPipeline; + QRhiComputePipeline *currentComputePipeline; + uint currentPipelineGeneration; + QRhiShaderResourceBindings *currentGraphicsSrb; + QRhiShaderResourceBindings *currentComputeSrb; + uint currentSrbGeneration; + ID3D11Buffer *currentIndexBuffer; + quint32 currentIndexOffset; + DXGI_FORMAT currentIndexFormat; + ID3D11Buffer *currentVertexBuffers[D3D11_IA_VERTEX_INPUT_RESOURCE_SLOT_COUNT]; + quint32 currentVertexOffsets[D3D11_IA_VERTEX_INPUT_RESOURCE_SLOT_COUNT]; + + QVarLengthArray dataRetainPool; + QVarLengthArray bufferDataRetainPool; + QVarLengthArray imageRetainPool; + + // relies heavily on implicit sharing (no copies of the actual data will be made) + const uchar *retainData(const QByteArray &data) { + dataRetainPool.append(data); + return reinterpret_cast(dataRetainPool.last().constData()); + } + const uchar *retainBufferData(const QRhiBufferData &data) { + bufferDataRetainPool.append(data); + return reinterpret_cast(bufferDataRetainPool.last().constData()); + } + const uchar *retainImage(const QImage &image) { + imageRetainPool.append(image); + return imageRetainPool.last().constBits(); + } + void resetCommands() { + commands.reset(); + dataRetainPool.clear(); + bufferDataRetainPool.clear(); + imageRetainPool.clear(); + } + void resetState() { + recordingPass = NoPass; + // do not zero lastGpuTime + currentTarget = nullptr; + resetCommands(); + resetCachedState(); + } + void resetCachedState() { + currentGraphicsPipeline = nullptr; + currentComputePipeline = nullptr; + currentPipelineGeneration = 0; + currentGraphicsSrb = nullptr; + currentComputeSrb = nullptr; + currentSrbGeneration = 0; + currentIndexBuffer = nullptr; + currentIndexOffset = 0; + currentIndexFormat = DXGI_FORMAT_R16_UINT; + memset(currentVertexBuffers, 0, sizeof(currentVertexBuffers)); + memset(currentVertexOffsets, 0, sizeof(currentVertexOffsets)); + } +}; + +static const int QD3D11_SWAPCHAIN_BUFFER_COUNT = 2; + +struct QD3D11Timestamps +{ + 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; + + bool prepare(int pairCount, QRhiD3D11 *rhiD); + void destroy(); + bool tryQueryTimestamps(int idx, ID3D11DeviceContext *context, double *elapsedSec); +}; + +struct QD3D11SwapChain : public QRhiSwapChain +{ + QD3D11SwapChain(QRhiImplementation *rhi); + ~QD3D11SwapChain(); + void destroy() override; + + QRhiCommandBuffer *currentFrameCommandBuffer() override; + QRhiRenderTarget *currentFrameRenderTarget() override; + + QSize surfacePixelSize() override; + bool isFormatSupported(Format f) override; + QRhiSwapChainHdrInfo hdrInfo() override; + + QRhiRenderPassDescriptor *newCompatibleRenderPassDescriptor() override; + bool createOrResize() override; + + void releaseBuffers(); + bool newColorBuffer(const QSize &size, DXGI_FORMAT format, DXGI_SAMPLE_DESC sampleDesc, + ID3D11Texture2D **tex, ID3D11RenderTargetView **rtv) const; + + QWindow *window = nullptr; + QSize pixelSize; + QD3D11SwapChainRenderTarget rt; + QD3D11CommandBuffer cb; + DXGI_FORMAT colorFormat; + DXGI_FORMAT srgbAdjustedColorFormat; + IDXGISwapChain *swapChain = nullptr; + UINT swapChainFlags = 0; + ID3D11Texture2D *backBufferTex; + ID3D11RenderTargetView *backBufferRtv; + static const int BUFFER_COUNT = QD3D11_SWAPCHAIN_BUFFER_COUNT; + ID3D11Texture2D *msaaTex[BUFFER_COUNT]; + ID3D11RenderTargetView *msaaRtv[BUFFER_COUNT]; + DXGI_SAMPLE_DESC sampleDesc; + int currentFrameSlot = 0; + int frameCount = 0; + QD3D11RenderBuffer *ds = nullptr; + UINT swapInterval = 1; + IDCompositionTarget *dcompTarget = nullptr; + IDCompositionVisual *dcompVisual = nullptr; + QD3D11Timestamps timestamps; +}; + +class QRhiD3D11 : public QRhiImplementation +{ +public: + QRhiD3D11(QRhiD3D11InitParams *params, QRhiD3D11NativeHandles *importDevice = nullptr); + + bool create(QRhi::Flags flags) override; + void destroy() override; + + QRhiGraphicsPipeline *createGraphicsPipeline() override; + QRhiComputePipeline *createComputePipeline() override; + QRhiShaderResourceBindings *createShaderResourceBindings() override; + QRhiBuffer *createBuffer(QRhiBuffer::Type type, + QRhiBuffer::UsageFlags usage, + quint32 size) override; + QRhiRenderBuffer *createRenderBuffer(QRhiRenderBuffer::Type type, + const QSize &pixelSize, + int sampleCount, + QRhiRenderBuffer::Flags flags, + QRhiTexture::Format backingFormatHint) override; + QRhiTexture *createTexture(QRhiTexture::Format format, + const QSize &pixelSize, + int depth, + int arraySize, + int sampleCount, + QRhiTexture::Flags flags) override; + QRhiSampler *createSampler(QRhiSampler::Filter magFilter, + QRhiSampler::Filter minFilter, + QRhiSampler::Filter mipmapMode, + QRhiSampler:: AddressMode u, + QRhiSampler::AddressMode v, + QRhiSampler::AddressMode w) override; + + QRhiTextureRenderTarget *createTextureRenderTarget(const QRhiTextureRenderTargetDescription &desc, + QRhiTextureRenderTarget::Flags flags) override; + + QRhiSwapChain *createSwapChain() override; + QRhi::FrameOpResult beginFrame(QRhiSwapChain *swapChain, QRhi::BeginFrameFlags flags) override; + QRhi::FrameOpResult endFrame(QRhiSwapChain *swapChain, QRhi::EndFrameFlags flags) override; + QRhi::FrameOpResult beginOffscreenFrame(QRhiCommandBuffer **cb, QRhi::BeginFrameFlags flags) override; + QRhi::FrameOpResult endOffscreenFrame(QRhi::EndFrameFlags flags) override; + QRhi::FrameOpResult finish() override; + + void resourceUpdate(QRhiCommandBuffer *cb, QRhiResourceUpdateBatch *resourceUpdates) override; + + void beginPass(QRhiCommandBuffer *cb, + QRhiRenderTarget *rt, + const QColor &colorClearValue, + const QRhiDepthStencilClearValue &depthStencilClearValue, + QRhiResourceUpdateBatch *resourceUpdates, + QRhiCommandBuffer::BeginPassFlags flags) override; + void endPass(QRhiCommandBuffer *cb, QRhiResourceUpdateBatch *resourceUpdates) override; + + void setGraphicsPipeline(QRhiCommandBuffer *cb, + QRhiGraphicsPipeline *ps) override; + + void setShaderResources(QRhiCommandBuffer *cb, + QRhiShaderResourceBindings *srb, + int dynamicOffsetCount, + const QRhiCommandBuffer::DynamicOffset *dynamicOffsets) override; + + void setVertexInput(QRhiCommandBuffer *cb, + int startBinding, int bindingCount, const QRhiCommandBuffer::VertexInput *bindings, + QRhiBuffer *indexBuf, quint32 indexOffset, + QRhiCommandBuffer::IndexFormat indexFormat) override; + + void setViewport(QRhiCommandBuffer *cb, const QRhiViewport &viewport) override; + void setScissor(QRhiCommandBuffer *cb, const QRhiScissor &scissor) override; + void setBlendConstants(QRhiCommandBuffer *cb, const QColor &c) override; + void setStencilRef(QRhiCommandBuffer *cb, quint32 refValue) override; + + void draw(QRhiCommandBuffer *cb, quint32 vertexCount, + quint32 instanceCount, quint32 firstVertex, quint32 firstInstance) override; + + void drawIndexed(QRhiCommandBuffer *cb, quint32 indexCount, + quint32 instanceCount, quint32 firstIndex, + qint32 vertexOffset, quint32 firstInstance) override; + + void debugMarkBegin(QRhiCommandBuffer *cb, const QByteArray &name) override; + void debugMarkEnd(QRhiCommandBuffer *cb) override; + void debugMarkMsg(QRhiCommandBuffer *cb, const QByteArray &msg) override; + + void beginComputePass(QRhiCommandBuffer *cb, + QRhiResourceUpdateBatch *resourceUpdates, + QRhiCommandBuffer::BeginPassFlags flags) override; + void endComputePass(QRhiCommandBuffer *cb, QRhiResourceUpdateBatch *resourceUpdates) override; + void setComputePipeline(QRhiCommandBuffer *cb, QRhiComputePipeline *ps) override; + void dispatch(QRhiCommandBuffer *cb, int x, int y, int z) override; + + const QRhiNativeHandles *nativeHandles(QRhiCommandBuffer *cb) override; + void beginExternal(QRhiCommandBuffer *cb) override; + void endExternal(QRhiCommandBuffer *cb) override; + double lastCompletedGpuTime(QRhiCommandBuffer *cb) override; + + QList supportedSampleCounts() const override; + int ubufAlignment() const override; + bool isYUpInFramebuffer() const override; + bool isYUpInNDC() const override; + bool isClipDepthZeroToOne() const override; + QMatrix4x4 clipSpaceCorrMatrix() const override; + bool isTextureFormatSupported(QRhiTexture::Format format, QRhiTexture::Flags flags) const override; + bool isFeatureSupported(QRhi::Feature feature) const override; + int resourceLimit(QRhi::ResourceLimit limit) const override; + const QRhiNativeHandles *nativeHandles() override; + QRhiDriverInfo driverInfo() const override; + QRhiStats statistics() override; + bool makeThreadLocalNativeContextCurrent() override; + void releaseCachedResources() override; + bool isDeviceLost() const override; + + QByteArray pipelineCacheData() override; + void setPipelineCacheData(const QByteArray &data) override; + + void enqueueSubresUpload(QD3D11Texture *texD, QD3D11CommandBuffer *cbD, + int layer, int level, const QRhiTextureSubresourceUploadDescription &subresDesc); + void enqueueResourceUpdates(QRhiCommandBuffer *cb, QRhiResourceUpdateBatch *resourceUpdates); + void updateShaderResourceBindings(QD3D11ShaderResourceBindings *srbD, + const QShader::NativeResourceBindingMap *nativeResourceBindingMaps[]); + void executeBufferHostWrites(QD3D11Buffer *bufD); + void bindShaderResources(QD3D11ShaderResourceBindings *srbD, + const uint *dynOfsPairs, int dynOfsPairCount, + bool offsetOnlyChange); + void resetShaderResources(); + void executeCommandBuffer(QD3D11CommandBuffer *cbD, QD3D11SwapChain *timestampSwapChain = nullptr); + DXGI_SAMPLE_DESC effectiveSampleCount(int sampleCount) const; + void finishActiveReadbacks(); + void reportLiveObjects(ID3D11Device *device); + void clearShaderCache(); + QByteArray compileHlslShaderSource(const QShader &shader, QShader::Variant shaderVariant, uint flags, + QString *error, QShaderKey *usedShaderKey); + bool ensureDirectCompositionDevice(); + + QRhi::Flags rhiFlags; + bool debugLayer = false; + bool importedDeviceAndContext = false; + ID3D11Device *dev = nullptr; + ID3D11DeviceContext1 *context = nullptr; + D3D_FEATURE_LEVEL featureLevel = D3D_FEATURE_LEVEL(0); + LUID adapterLuid = {}; + ID3DUserDefinedAnnotation *annotations = nullptr; + IDXGIAdapter1 *activeAdapter = nullptr; + IDXGIFactory1 *dxgiFactory = nullptr; + IDCompositionDevice *dcompDevice = nullptr; + bool supportsAllowTearing = false; + bool deviceLost = false; + QRhiD3D11NativeHandles nativeHandlesStruct; + QRhiDriverInfo driverInfoStruct; + + struct { + int vsHighestActiveVertexBufferBinding = -1; + bool vsHasIndexBufferBound = false; + int vsHighestActiveSrvBinding = -1; + int hsHighestActiveSrvBinding = -1; + int dsHighestActiveSrvBinding = -1; + int gsHighestActiveSrvBinding = -1; + int fsHighestActiveSrvBinding = -1; + int csHighestActiveSrvBinding = -1; + int csHighestActiveUavBinding = -1; + QD3D11SwapChain *currentSwapChain = nullptr; + } contextState; + + struct OffscreenFrame { + OffscreenFrame(QRhiImplementation *rhi) : cbWrapper(rhi) { } + bool active = false; + QD3D11CommandBuffer cbWrapper; + QD3D11Timestamps timestamps; + int timestampIdx = 0; + } ofr; + + struct TextureReadback { + QRhiReadbackDescription desc; + QRhiReadbackResult *result; + ID3D11Texture2D *stagingTex; + quint32 byteSize; + quint32 bpl; + QSize pixelSize; + QRhiTexture::Format format; + }; + QVarLengthArray activeTextureReadbacks; + struct BufferReadback { + QRhiReadbackResult *result; + quint32 byteSize; + ID3D11Buffer *stagingBuf; + }; + QVarLengthArray activeBufferReadbacks; + + struct Shader { + Shader() = default; + Shader(IUnknown *s, const QByteArray &bytecode, const QShader::NativeResourceBindingMap &rbm) + : s(s), bytecode(bytecode), nativeResourceBindingMap(rbm) { } + IUnknown *s; + QByteArray bytecode; + QShader::NativeResourceBindingMap nativeResourceBindingMap; + }; + QHash m_shaderCache; + + // This is what gets exposed as the "pipeline cache", not that that concept + // applies anyway. Here we are just storing the DX bytecode for a shader so + // we can skip the HLSL->DXBC compilation when the QShader has HLSL source + // code and the same shader source has already been compiled before. + // m_shaderCache seemingly does the same, but this here does not care about + // the ID3D11*Shader, this is just about the bytecode and about allowing + // the data to be serialized to persistent storage and then reloaded in + // future runs of the app, or when creating another QRhi, etc. + struct BytecodeCacheKey { + QByteArray sourceHash; + QByteArray target; + QByteArray entryPoint; + uint compileFlags; + }; + QHash m_bytecodeCache; +}; + +Q_DECLARE_TYPEINFO(QRhiD3D11::TextureReadback, Q_RELOCATABLE_TYPE); +Q_DECLARE_TYPEINFO(QRhiD3D11::BufferReadback, Q_RELOCATABLE_TYPE); + +inline bool operator==(const QRhiD3D11::BytecodeCacheKey &a, const QRhiD3D11::BytecodeCacheKey &b) noexcept +{ + return a.sourceHash == b.sourceHash + && a.target == b.target + && a.entryPoint == b.entryPoint + && a.compileFlags == b.compileFlags; +} + +inline bool operator!=(const QRhiD3D11::BytecodeCacheKey &a, const QRhiD3D11::BytecodeCacheKey &b) noexcept +{ + return !(a == b); +} + +inline size_t qHash(const QRhiD3D11::BytecodeCacheKey &k, size_t seed = 0) noexcept +{ + return qHash(k.sourceHash, seed) ^ qHash(k.target) ^ qHash(k.entryPoint) ^ k.compileFlags; +} + QT_END_NAMESPACE #endif diff --git a/src/gui/rhi/qrhid3d11_p_p.h b/src/gui/rhi/qrhid3d11_p_p.h deleted file mode 100644 index e1d7559f..00000000 --- a/src/gui/rhi/qrhid3d11_p_p.h +++ /dev/null @@ -1,850 +0,0 @@ -// Copyright (C) 2019 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 QRHID3D11_P_H -#define QRHID3D11_P_H - -// -// W A R N I N G -// ------------- -// -// This file is not part of the Qt API. It exists purely as an -// implementation detail. This header file may change from version to -// version without notice, or even be removed. -// -// We mean it. -// - -#include "qrhid3d11_p.h" -#include "qrhi_p_p.h" -#include "qshaderdescription_p.h" -#include - -#include -#include -#include - -QT_BEGIN_NAMESPACE - -struct QD3D11Buffer : public QRhiBuffer -{ - QD3D11Buffer(QRhiImplementation *rhi, Type type, UsageFlags usage, quint32 size); - ~QD3D11Buffer(); - void destroy() override; - bool create() override; - QRhiBuffer::NativeBuffer nativeBuffer() override; - char *beginFullDynamicBufferUpdateForCurrentFrame() override; - void endFullDynamicBufferUpdateForCurrentFrame() override; - - ID3D11UnorderedAccessView *unorderedAccessView(quint32 offset); - - ID3D11Buffer *buffer = nullptr; - char *dynBuf = nullptr; - bool hasPendingDynamicUpdates = false; - QHash uavs; - uint generation = 0; - friend class QRhiD3D11; -}; - -struct QD3D11RenderBuffer : public QRhiRenderBuffer -{ - QD3D11RenderBuffer(QRhiImplementation *rhi, Type type, const QSize &pixelSize, - int sampleCount, QRhiRenderBuffer::Flags flags, - QRhiTexture::Format backingFormatHint); - ~QD3D11RenderBuffer(); - void destroy() override; - bool create() override; - QRhiTexture::Format backingFormat() const override; - - ID3D11Texture2D *tex = nullptr; - ID3D11DepthStencilView *dsv = nullptr; - ID3D11RenderTargetView *rtv = nullptr; - DXGI_FORMAT dxgiFormat; - DXGI_SAMPLE_DESC sampleDesc; - uint generation = 0; - friend class QRhiD3D11; -}; - -struct QD3D11Texture : public QRhiTexture -{ - QD3D11Texture(QRhiImplementation *rhi, Format format, const QSize &pixelSize, int depth, - int arraySize, int sampleCount, Flags flags); - ~QD3D11Texture(); - void destroy() override; - bool create() override; - bool createFrom(NativeTexture src) override; - NativeTexture nativeTexture() override; - - bool prepareCreate(QSize *adjustedSize = nullptr); - bool finishCreate(); - ID3D11UnorderedAccessView *unorderedAccessViewForLevel(int level); - ID3D11Resource *textureResource() const - { - if (tex) - return tex; - else if (tex1D) - return tex1D; - return tex3D; - } - - ID3D11Texture2D *tex = nullptr; - ID3D11Texture3D *tex3D = nullptr; - ID3D11Texture1D *tex1D = nullptr; - bool owns = true; - ID3D11ShaderResourceView *srv = nullptr; - DXGI_FORMAT dxgiFormat; - uint mipLevelCount = 0; - DXGI_SAMPLE_DESC sampleDesc; - ID3D11UnorderedAccessView *perLevelViews[QRhi::MAX_MIP_LEVELS]; - uint generation = 0; - friend class QRhiD3D11; -}; - -struct QD3D11Sampler : public QRhiSampler -{ - QD3D11Sampler(QRhiImplementation *rhi, Filter magFilter, Filter minFilter, Filter mipmapMode, - AddressMode u, AddressMode v, AddressMode w); - ~QD3D11Sampler(); - void destroy() override; - bool create() override; - - ID3D11SamplerState *samplerState = nullptr; - uint generation = 0; - friend class QRhiD3D11; -}; - -struct QD3D11RenderPassDescriptor : public QRhiRenderPassDescriptor -{ - QD3D11RenderPassDescriptor(QRhiImplementation *rhi); - ~QD3D11RenderPassDescriptor(); - void destroy() override; - bool isCompatible(const QRhiRenderPassDescriptor *other) const override; - QRhiRenderPassDescriptor *newCompatibleRenderPassDescriptor() const override; - QVector serializedFormat() const override; -}; - -struct QD3D11RenderTargetData -{ - QD3D11RenderTargetData(QRhiImplementation *) - { - for (int i = 0; i < MAX_COLOR_ATTACHMENTS; ++i) - rtv[i] = nullptr; - } - - QD3D11RenderPassDescriptor *rp = nullptr; - QSize pixelSize; - float dpr = 1; - int sampleCount = 1; - int colorAttCount = 0; - int dsAttCount = 0; - - static const int MAX_COLOR_ATTACHMENTS = 8; - ID3D11RenderTargetView *rtv[MAX_COLOR_ATTACHMENTS]; - ID3D11DepthStencilView *dsv = nullptr; - - QRhiRenderTargetAttachmentTracker::ResIdList currentResIdList; -}; - -struct QD3D11SwapChainRenderTarget : public QRhiSwapChainRenderTarget -{ - QD3D11SwapChainRenderTarget(QRhiImplementation *rhi, QRhiSwapChain *swapchain); - ~QD3D11SwapChainRenderTarget(); - void destroy() override; - - QSize pixelSize() const override; - float devicePixelRatio() const override; - int sampleCount() const override; - - QD3D11RenderTargetData d; -}; - -struct QD3D11TextureRenderTarget : public QRhiTextureRenderTarget -{ - QD3D11TextureRenderTarget(QRhiImplementation *rhi, const QRhiTextureRenderTargetDescription &desc, Flags flags); - ~QD3D11TextureRenderTarget(); - void destroy() override; - - QSize pixelSize() const override; - float devicePixelRatio() const override; - int sampleCount() const override; - - QRhiRenderPassDescriptor *newCompatibleRenderPassDescriptor() override; - bool create() override; - - QD3D11RenderTargetData d; - bool ownsRtv[QD3D11RenderTargetData::MAX_COLOR_ATTACHMENTS]; - ID3D11RenderTargetView *rtv[QD3D11RenderTargetData::MAX_COLOR_ATTACHMENTS]; - bool ownsDsv = false; - ID3D11DepthStencilView *dsv = nullptr; - friend class QRhiD3D11; -}; - -struct QD3D11ShaderResourceBindings : public QRhiShaderResourceBindings -{ - QD3D11ShaderResourceBindings(QRhiImplementation *rhi); - ~QD3D11ShaderResourceBindings(); - void destroy() override; - bool create() override; - void updateResources(UpdateFlags flags) override; - - bool hasDynamicOffset = false; - QVarLengthArray sortedBindings; - uint generation = 0; - - // Keep track of the generation number of each referenced QRhi* to be able - // to detect that the batched bindings are out of date. - struct BoundUniformBufferData { - quint64 id; - uint generation; - }; - struct BoundSampledTextureData { - int count; - struct { - quint64 texId; - uint texGeneration; - quint64 samplerId; - uint samplerGeneration; - } d[QRhiShaderResourceBinding::Data::MAX_TEX_SAMPLER_ARRAY_SIZE]; - }; - struct BoundStorageImageData { - quint64 id; - uint generation; - }; - struct BoundStorageBufferData { - quint64 id; - uint generation; - }; - struct BoundResourceData { - union { - BoundUniformBufferData ubuf; - BoundSampledTextureData stex; - BoundStorageImageData simage; - BoundStorageBufferData sbuf; - }; - }; - QVarLengthArray boundResourceData; - - struct StageUniformBufferBatches { - bool present = false; - QRhiBatchedBindings ubufs; - QRhiBatchedBindings ubuforigbindings; - QRhiBatchedBindings ubufoffsets; - QRhiBatchedBindings ubufsizes; - void finish() { - present = ubufs.finish(); - ubuforigbindings.finish(); - ubufoffsets.finish(); - ubufsizes.finish(); - } - void clear() { - ubufs.clear(); - ubuforigbindings.clear(); - ubufoffsets.clear(); - ubufsizes.clear(); - } - }; - - struct StageSamplerBatches { - bool present = false; - QRhiBatchedBindings samplers; - QRhiBatchedBindings shaderresources; - void finish() { - present = samplers.finish(); - shaderresources.finish(); - } - void clear() { - samplers.clear(); - shaderresources.clear(); - } - }; - - struct StageUavBatches { - bool present = false; - QRhiBatchedBindings uavs; - void finish() { - present = uavs.finish(); - } - void clear() { - uavs.clear(); - } - }; - - StageUniformBufferBatches vsUniformBufferBatches; - StageUniformBufferBatches hsUniformBufferBatches; - StageUniformBufferBatches dsUniformBufferBatches; - StageUniformBufferBatches gsUniformBufferBatches; - StageUniformBufferBatches fsUniformBufferBatches; - StageUniformBufferBatches csUniformBufferBatches; - - StageSamplerBatches vsSamplerBatches; - StageSamplerBatches hsSamplerBatches; - StageSamplerBatches dsSamplerBatches; - StageSamplerBatches gsSamplerBatches; - StageSamplerBatches fsSamplerBatches; - StageSamplerBatches csSamplerBatches; - - StageUavBatches csUavBatches; - - friend class QRhiD3D11; -}; - -Q_DECLARE_TYPEINFO(QD3D11ShaderResourceBindings::BoundResourceData, Q_RELOCATABLE_TYPE); - -struct QD3D11GraphicsPipeline : public QRhiGraphicsPipeline -{ - QD3D11GraphicsPipeline(QRhiImplementation *rhi); - ~QD3D11GraphicsPipeline(); - void destroy() override; - bool create() override; - - ID3D11DepthStencilState *dsState = nullptr; - ID3D11BlendState *blendState = nullptr; - struct { - ID3D11VertexShader *shader = nullptr; - QShader::NativeResourceBindingMap nativeResourceBindingMap; - } vs; - struct { - ID3D11HullShader *shader = nullptr; - QShader::NativeResourceBindingMap nativeResourceBindingMap; - } hs; - struct { - ID3D11DomainShader *shader = nullptr; - QShader::NativeResourceBindingMap nativeResourceBindingMap; - } ds; - struct { - ID3D11GeometryShader *shader = nullptr; - QShader::NativeResourceBindingMap nativeResourceBindingMap; - } gs; - struct { - ID3D11PixelShader *shader = nullptr; - QShader::NativeResourceBindingMap nativeResourceBindingMap; - } fs; - ID3D11InputLayout *inputLayout = nullptr; - D3D11_PRIMITIVE_TOPOLOGY d3dTopology = D3D11_PRIMITIVE_TOPOLOGY_TRIANGLELIST; - ID3D11RasterizerState *rastState = nullptr; - uint generation = 0; - friend class QRhiD3D11; -}; - -struct QD3D11ComputePipeline : public QRhiComputePipeline -{ - QD3D11ComputePipeline(QRhiImplementation *rhi); - ~QD3D11ComputePipeline(); - void destroy() override; - bool create() override; - - struct { - ID3D11ComputeShader *shader = nullptr; - QShader::NativeResourceBindingMap nativeResourceBindingMap; - } cs; - uint generation = 0; - friend class QRhiD3D11; -}; - -struct QD3D11SwapChain; - -struct QD3D11CommandBuffer : public QRhiCommandBuffer -{ - QD3D11CommandBuffer(QRhiImplementation *rhi); - ~QD3D11CommandBuffer(); - void destroy() override; - - // these must be kept at a reasonably low value otherwise sizeof Command explodes - static const int MAX_DYNAMIC_OFFSET_COUNT = 8; - static const int MAX_VERTEX_BUFFER_BINDING_COUNT = 8; - - struct Command { - enum Cmd { - ResetShaderResources, - SetRenderTarget, - Clear, - Viewport, - Scissor, - BindVertexBuffers, - BindIndexBuffer, - BindGraphicsPipeline, - BindShaderResources, - StencilRef, - BlendConstants, - Draw, - DrawIndexed, - UpdateSubRes, - CopySubRes, - ResolveSubRes, - GenMip, - DebugMarkBegin, - DebugMarkEnd, - DebugMarkMsg, - BindComputePipeline, - Dispatch - }; - enum ClearFlag { Color = 1, Depth = 2, Stencil = 4 }; - Cmd cmd; - - // QRhi*/QD3D11* references should be kept at minimum (so no - // QRhiTexture/Buffer/etc. pointers). - union Args { - struct { - QRhiRenderTarget *rt; - } setRenderTarget; - struct { - QRhiRenderTarget *rt; - int mask; - float c[4]; - float d; - quint32 s; - } clear; - struct { - float x, y, w, h; - float d0, d1; - } viewport; - struct { - int x, y, w, h; - } scissor; - struct { - int startSlot; - int slotCount; - ID3D11Buffer *buffers[MAX_VERTEX_BUFFER_BINDING_COUNT]; - UINT offsets[MAX_VERTEX_BUFFER_BINDING_COUNT]; - UINT strides[MAX_VERTEX_BUFFER_BINDING_COUNT]; - } bindVertexBuffers; - struct { - ID3D11Buffer *buffer; - quint32 offset; - DXGI_FORMAT format; - } bindIndexBuffer; - struct { - QD3D11GraphicsPipeline *ps; - } bindGraphicsPipeline; - struct { - QD3D11ShaderResourceBindings *srb; - bool offsetOnlyChange; - int dynamicOffsetCount; - uint dynamicOffsetPairs[MAX_DYNAMIC_OFFSET_COUNT * 2]; // binding, offsetInConstants - } bindShaderResources; - struct { - QD3D11GraphicsPipeline *ps; - quint32 ref; - } stencilRef; - struct { - QD3D11GraphicsPipeline *ps; - float c[4]; - } blendConstants; - struct { - QD3D11GraphicsPipeline *ps; - quint32 vertexCount; - quint32 instanceCount; - quint32 firstVertex; - quint32 firstInstance; - } draw; - struct { - QD3D11GraphicsPipeline *ps; - quint32 indexCount; - quint32 instanceCount; - quint32 firstIndex; - qint32 vertexOffset; - quint32 firstInstance; - } drawIndexed; - struct { - ID3D11Resource *dst; - UINT dstSubRes; - bool hasDstBox; - D3D11_BOX dstBox; - const void *src; // must come from retain*() - UINT srcRowPitch; - } updateSubRes; - struct { - ID3D11Resource *dst; - UINT dstSubRes; - UINT dstX; - UINT dstY; - UINT dstZ; - ID3D11Resource *src; - UINT srcSubRes; - bool hasSrcBox; - D3D11_BOX srcBox; - } copySubRes; - struct { - ID3D11Resource *dst; - UINT dstSubRes; - ID3D11Resource *src; - UINT srcSubRes; - DXGI_FORMAT format; - } resolveSubRes; - struct { - ID3D11ShaderResourceView *srv; - } genMip; - struct { - char s[64]; - } debugMark; - struct { - QD3D11ComputePipeline *ps; - } bindComputePipeline; - struct { - UINT x; - UINT y; - UINT z; - } dispatch; - } args; - }; - - enum PassType { - NoPass, - RenderPass, - ComputePass - }; - - QRhiBackendCommandList commands; - PassType recordingPass; - QRhiRenderTarget *currentTarget; - QRhiGraphicsPipeline *currentGraphicsPipeline; - QRhiComputePipeline *currentComputePipeline; - uint currentPipelineGeneration; - QRhiShaderResourceBindings *currentGraphicsSrb; - QRhiShaderResourceBindings *currentComputeSrb; - uint currentSrbGeneration; - ID3D11Buffer *currentIndexBuffer; - quint32 currentIndexOffset; - DXGI_FORMAT currentIndexFormat; - ID3D11Buffer *currentVertexBuffers[D3D11_IA_VERTEX_INPUT_RESOURCE_SLOT_COUNT]; - quint32 currentVertexOffsets[D3D11_IA_VERTEX_INPUT_RESOURCE_SLOT_COUNT]; - - QVarLengthArray dataRetainPool; - QVarLengthArray bufferDataRetainPool; - QVarLengthArray imageRetainPool; - - // relies heavily on implicit sharing (no copies of the actual data will be made) - const uchar *retainData(const QByteArray &data) { - dataRetainPool.append(data); - return reinterpret_cast(dataRetainPool.last().constData()); - } - const uchar *retainBufferData(const QRhiBufferData &data) { - bufferDataRetainPool.append(data); - return reinterpret_cast(bufferDataRetainPool.last().constData()); - } - const uchar *retainImage(const QImage &image) { - imageRetainPool.append(image); - return imageRetainPool.last().constBits(); - } - void resetCommands() { - commands.reset(); - dataRetainPool.clear(); - bufferDataRetainPool.clear(); - imageRetainPool.clear(); - } - void resetState() { - recordingPass = NoPass; - currentTarget = nullptr; - resetCommands(); - resetCachedState(); - } - void resetCachedState() { - currentGraphicsPipeline = nullptr; - currentComputePipeline = nullptr; - currentPipelineGeneration = 0; - currentGraphicsSrb = nullptr; - currentComputeSrb = nullptr; - currentSrbGeneration = 0; - currentIndexBuffer = nullptr; - currentIndexOffset = 0; - currentIndexFormat = DXGI_FORMAT_R16_UINT; - memset(currentVertexBuffers, 0, sizeof(currentVertexBuffers)); - memset(currentVertexOffsets, 0, sizeof(currentVertexOffsets)); - } -}; - -struct QD3D11SwapChain : public QRhiSwapChain -{ - QD3D11SwapChain(QRhiImplementation *rhi); - ~QD3D11SwapChain(); - void destroy() override; - - QRhiCommandBuffer *currentFrameCommandBuffer() override; - QRhiRenderTarget *currentFrameRenderTarget() override; - - QSize surfacePixelSize() override; - bool isFormatSupported(Format f) override; - QRhiSwapChainHdrInfo hdrInfo() override; - - QRhiRenderPassDescriptor *newCompatibleRenderPassDescriptor() override; - bool createOrResize() override; - - bool createOrResizeWin7(); - - void releaseBuffers(); - bool newColorBuffer(const QSize &size, DXGI_FORMAT format, DXGI_SAMPLE_DESC sampleDesc, - ID3D11Texture2D **tex, ID3D11RenderTargetView **rtv) const; - - QWindow *window = nullptr; - QSize pixelSize; - QD3D11SwapChainRenderTarget rt; - QD3D11CommandBuffer cb; - DXGI_FORMAT colorFormat; - DXGI_FORMAT srgbAdjustedColorFormat; - IDXGISwapChain *swapChain = nullptr; - UINT swapChainFlags = 0; - static const int BUFFER_COUNT = 2; - ID3D11Texture2D *backBufferTex; - ID3D11RenderTargetView *backBufferRtv; - ID3D11Texture2D *msaaTex[BUFFER_COUNT]; - ID3D11RenderTargetView *msaaRtv[BUFFER_COUNT]; - DXGI_SAMPLE_DESC sampleDesc; - int currentFrameSlot = 0; - int frameCount = 0; - QD3D11RenderBuffer *ds = nullptr; - bool timestampActive[BUFFER_COUNT]; - ID3D11Query *timestampDisjointQuery[BUFFER_COUNT]; - ID3D11Query *timestampQuery[BUFFER_COUNT * 2]; - UINT swapInterval = 1; - IDCompositionTarget *dcompTarget = nullptr; - IDCompositionVisual *dcompVisual = nullptr; -}; - -class QRhiD3D11 : public QRhiImplementation -{ -public: - QRhiD3D11(QRhiD3D11InitParams *params, QRhiD3D11NativeHandles *importDevice = nullptr); - - bool create(QRhi::Flags flags) override; - void destroy() override; - - QRhiGraphicsPipeline *createGraphicsPipeline() override; - QRhiComputePipeline *createComputePipeline() override; - QRhiShaderResourceBindings *createShaderResourceBindings() override; - QRhiBuffer *createBuffer(QRhiBuffer::Type type, - QRhiBuffer::UsageFlags usage, - quint32 size) override; - QRhiRenderBuffer *createRenderBuffer(QRhiRenderBuffer::Type type, - const QSize &pixelSize, - int sampleCount, - QRhiRenderBuffer::Flags flags, - QRhiTexture::Format backingFormatHint) override; - QRhiTexture *createTexture(QRhiTexture::Format format, - const QSize &pixelSize, - int depth, - int arraySize, - int sampleCount, - QRhiTexture::Flags flags) override; - QRhiSampler *createSampler(QRhiSampler::Filter magFilter, - QRhiSampler::Filter minFilter, - QRhiSampler::Filter mipmapMode, - QRhiSampler:: AddressMode u, - QRhiSampler::AddressMode v, - QRhiSampler::AddressMode w) override; - - QRhiTextureRenderTarget *createTextureRenderTarget(const QRhiTextureRenderTargetDescription &desc, - QRhiTextureRenderTarget::Flags flags) override; - - QRhiSwapChain *createSwapChain() override; - QRhi::FrameOpResult beginFrame(QRhiSwapChain *swapChain, QRhi::BeginFrameFlags flags) override; - QRhi::FrameOpResult endFrame(QRhiSwapChain *swapChain, QRhi::EndFrameFlags flags) override; - QRhi::FrameOpResult beginOffscreenFrame(QRhiCommandBuffer **cb, QRhi::BeginFrameFlags flags) override; - QRhi::FrameOpResult endOffscreenFrame(QRhi::EndFrameFlags flags) override; - QRhi::FrameOpResult finish() override; - - void resourceUpdate(QRhiCommandBuffer *cb, QRhiResourceUpdateBatch *resourceUpdates) override; - - void beginPass(QRhiCommandBuffer *cb, - QRhiRenderTarget *rt, - const QColor &colorClearValue, - const QRhiDepthStencilClearValue &depthStencilClearValue, - QRhiResourceUpdateBatch *resourceUpdates, - QRhiCommandBuffer::BeginPassFlags flags) override; - void endPass(QRhiCommandBuffer *cb, QRhiResourceUpdateBatch *resourceUpdates) override; - - void setGraphicsPipeline(QRhiCommandBuffer *cb, - QRhiGraphicsPipeline *ps) override; - - void setShaderResources(QRhiCommandBuffer *cb, - QRhiShaderResourceBindings *srb, - int dynamicOffsetCount, - const QRhiCommandBuffer::DynamicOffset *dynamicOffsets) override; - - void setVertexInput(QRhiCommandBuffer *cb, - int startBinding, int bindingCount, const QRhiCommandBuffer::VertexInput *bindings, - QRhiBuffer *indexBuf, quint32 indexOffset, - QRhiCommandBuffer::IndexFormat indexFormat) override; - - void setViewport(QRhiCommandBuffer *cb, const QRhiViewport &viewport) override; - void setScissor(QRhiCommandBuffer *cb, const QRhiScissor &scissor) override; - void setBlendConstants(QRhiCommandBuffer *cb, const QColor &c) override; - void setStencilRef(QRhiCommandBuffer *cb, quint32 refValue) override; - - void draw(QRhiCommandBuffer *cb, quint32 vertexCount, - quint32 instanceCount, quint32 firstVertex, quint32 firstInstance) override; - - void drawIndexed(QRhiCommandBuffer *cb, quint32 indexCount, - quint32 instanceCount, quint32 firstIndex, - qint32 vertexOffset, quint32 firstInstance) override; - - void debugMarkBegin(QRhiCommandBuffer *cb, const QByteArray &name) override; - void debugMarkEnd(QRhiCommandBuffer *cb) override; - void debugMarkMsg(QRhiCommandBuffer *cb, const QByteArray &msg) override; - - void beginComputePass(QRhiCommandBuffer *cb, - QRhiResourceUpdateBatch *resourceUpdates, - QRhiCommandBuffer::BeginPassFlags flags) override; - void endComputePass(QRhiCommandBuffer *cb, QRhiResourceUpdateBatch *resourceUpdates) override; - void setComputePipeline(QRhiCommandBuffer *cb, QRhiComputePipeline *ps) override; - void dispatch(QRhiCommandBuffer *cb, int x, int y, int z) override; - - const QRhiNativeHandles *nativeHandles(QRhiCommandBuffer *cb) override; - void beginExternal(QRhiCommandBuffer *cb) override; - void endExternal(QRhiCommandBuffer *cb) override; - - QList supportedSampleCounts() const override; - int ubufAlignment() const override; - bool isYUpInFramebuffer() const override; - bool isYUpInNDC() const override; - bool isClipDepthZeroToOne() const override; - QMatrix4x4 clipSpaceCorrMatrix() const override; - bool isTextureFormatSupported(QRhiTexture::Format format, QRhiTexture::Flags flags) const override; - bool isFeatureSupported(QRhi::Feature feature) const override; - int resourceLimit(QRhi::ResourceLimit limit) const override; - const QRhiNativeHandles *nativeHandles() override; - QRhiDriverInfo driverInfo() const override; - QRhiStats statistics() override; - bool makeThreadLocalNativeContextCurrent() override; - void releaseCachedResources() override; - bool isDeviceLost() const override; - - QByteArray pipelineCacheData() override; - void setPipelineCacheData(const QByteArray &data) override; - - void enqueueSubresUpload(QD3D11Texture *texD, QD3D11CommandBuffer *cbD, - int layer, int level, const QRhiTextureSubresourceUploadDescription &subresDesc); - void enqueueResourceUpdates(QRhiCommandBuffer *cb, QRhiResourceUpdateBatch *resourceUpdates); - void updateShaderResourceBindings(QD3D11ShaderResourceBindings *srbD, - const QShader::NativeResourceBindingMap *nativeResourceBindingMaps[]); - void executeBufferHostWrites(QD3D11Buffer *bufD); - void bindShaderResources(QD3D11ShaderResourceBindings *srbD, - const uint *dynOfsPairs, int dynOfsPairCount, - bool offsetOnlyChange); - void resetShaderResources(); - void executeCommandBuffer(QD3D11CommandBuffer *cbD, QD3D11SwapChain *timestampSwapChain = nullptr); - DXGI_SAMPLE_DESC effectiveSampleCount(int sampleCount) const; - void finishActiveReadbacks(); - void reportLiveObjects(ID3D11Device *device); - void clearShaderCache(); - QByteArray compileHlslShaderSource(const QShader &shader, QShader::Variant shaderVariant, uint flags, - QString *error, QShaderKey *usedShaderKey); - bool ensureDirectCompositionDevice(); - - QRhi::Flags rhiFlags; - bool debugLayer = false; - bool importedDeviceAndContext = false; - ID3D11Device *dev = nullptr; - ID3D11DeviceContext1 *context = nullptr; - D3D_FEATURE_LEVEL featureLevel = D3D_FEATURE_LEVEL(0); - LUID adapterLuid = {}; - ID3DUserDefinedAnnotation *annotations = nullptr; - IDXGIAdapter1 *activeAdapter = nullptr; - IDXGIFactory1 *dxgiFactory = nullptr; - IDCompositionDevice *dcompDevice = nullptr; - bool supportsAllowTearing = false; - bool supportsFlipSwapchain = false; // win7 - bool forceFlipDiscard = false; // win7 - bool deviceLost = false; - QRhiD3D11NativeHandles nativeHandlesStruct; - QRhiDriverInfo driverInfoStruct; - - struct { - int vsHighestActiveVertexBufferBinding = -1; - bool vsHasIndexBufferBound = false; - int vsHighestActiveSrvBinding = -1; - int hsHighestActiveSrvBinding = -1; - int dsHighestActiveSrvBinding = -1; - int gsHighestActiveSrvBinding = -1; - int fsHighestActiveSrvBinding = -1; - int csHighestActiveSrvBinding = -1; - int csHighestActiveUavBinding = -1; - QD3D11SwapChain *currentSwapChain = nullptr; - } contextState; - - struct OffscreenFrame { - OffscreenFrame(QRhiImplementation *rhi) : cbWrapper(rhi) { } - bool active = false; - QD3D11CommandBuffer cbWrapper; - } ofr; - - struct TextureReadback { - QRhiReadbackDescription desc; - QRhiReadbackResult *result; - ID3D11Texture2D *stagingTex; - quint32 byteSize; - quint32 bpl; - QSize pixelSize; - QRhiTexture::Format format; - }; - QVarLengthArray activeTextureReadbacks; - struct BufferReadback { - QRhiBufferReadbackResult *result; - quint32 byteSize; - ID3D11Buffer *stagingBuf; - }; - QVarLengthArray activeBufferReadbacks; - - struct Shader { - Shader() = default; - Shader(IUnknown *s, const QByteArray &bytecode, const QShader::NativeResourceBindingMap &rbm) - : s(s), bytecode(bytecode), nativeResourceBindingMap(rbm) { } - IUnknown *s; - QByteArray bytecode; - QShader::NativeResourceBindingMap nativeResourceBindingMap; - }; - QHash m_shaderCache; - - struct DeviceCurse { - DeviceCurse(QRhiD3D11 *impl) : q(impl) { } - QRhiD3D11 *q; - int framesToActivate = -1; - bool permanent = false; - int framesLeft = 0; - ID3D11ComputeShader *cs = nullptr; - - void initResources(); - void releaseResources(); - void activate(); - } deviceCurse; - - // This is what gets exposed as the "pipeline cache", not that that concept - // applies anyway. Here we are just storing the DX bytecode for a shader so - // we can skip the HLSL->DXBC compilation when the QShader has HLSL source - // code and the same shader source has already been compiled before. - // m_shaderCache seemingly does the same, but this here does not care about - // the ID3D11*Shader, this is just about the bytecode and about allowing - // the data to be serialized to persistent storage and then reloaded in - // future runs of the app, or when creating another QRhi, etc. - struct BytecodeCacheKey { - QByteArray sourceHash; - QByteArray target; - QByteArray entryPoint; - uint compileFlags; - }; - QHash m_bytecodeCache; -}; - -Q_DECLARE_TYPEINFO(QRhiD3D11::TextureReadback, Q_RELOCATABLE_TYPE); -Q_DECLARE_TYPEINFO(QRhiD3D11::BufferReadback, Q_RELOCATABLE_TYPE); - -inline bool operator==(const QRhiD3D11::BytecodeCacheKey &a, const QRhiD3D11::BytecodeCacheKey &b) noexcept -{ - return a.sourceHash == b.sourceHash - && a.target == b.target - && a.entryPoint == b.entryPoint - && a.compileFlags == b.compileFlags; -} - -inline bool operator!=(const QRhiD3D11::BytecodeCacheKey &a, const QRhiD3D11::BytecodeCacheKey &b) noexcept -{ - return !(a == b); -} - -inline size_t qHash(const QRhiD3D11::BytecodeCacheKey &k, size_t seed = 0) noexcept -{ - return qHash(k.sourceHash, seed) ^ qHash(k.target) ^ qHash(k.entryPoint) ^ k.compileFlags; -} - -QT_END_NAMESPACE - -#endif diff --git a/src/gui/rhi/qrhid3d12.cpp b/src/gui/rhi/qrhid3d12.cpp new file mode 100644 index 00000000..38e4fbd2 --- /dev/null +++ b/src/gui/rhi/qrhid3d12.cpp @@ -0,0 +1,6166 @@ +// 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 "qrhid3d12_p.h" +#include "qshader.h" +#include +#include +#include +#include +#include +#include "qrhid3dhelpers_p.h" +#include "cs_mipmap_p.h" + +#if __has_include() +#include +#define QRHI_D3D12_HAS_OLD_PIX +#endif + +QT_BEGIN_NAMESPACE + +/* + Direct 3D 12 backend. +*/ + +/*! + \class QRhiD3D12InitParams + \inmodule QtGui + \brief Direct3D 12 specific initialization parameters. + + \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 + desired, enableDebugLayer can be set to \c true to enable the Direct3D + debug layer. This can be useful during development, but should be avoided + in production builds. + + \badcode + QRhiD3D12InitParams params; + params.enableDebugLayer = true; + rhi = QRhi::create(QRhi::D3D12, ¶ms); + \endcode + + \note QRhiSwapChain should only be used in combination with QWindow + instances that have their surface type set to QSurface::Direct3DSurface. + + \section2 Working with existing Direct3D 12 devices + + When interoperating with another graphics engine, it may be necessary to + get a QRhi instance that uses the same Direct3D device. This can be + achieved by passing a pointer to a QRhiD3D12NativeHandles to + QRhi::create(). QRhi does not take ownership of any of the external + objects. + + Sometimes, for example when using QRhi in combination with OpenXR, one will + want to specify which adapter to use, and optionally, which feature level + to request on the device, while leaving the device creation to QRhi. This + is achieved by leaving the device pointer set to null, while specifying the + adapter LUID and feature level. + + Optionally the ID3D12CommandQueue can be specified as well, by setting \c + commandQueue to a non-null value. + */ + +/*! + \variable QRhiD3D12InitParams::enableDebugLayer + + When set to true, the debug layer is enabled, if installed and available. + The default value is false. +*/ + +/*! + \class QRhiD3D12NativeHandles + \inmodule QtGui + \brief Holds the D3D12 device used by the QRhi. + + \note The class uses \c{void *} as the type since including the COM-based + \c{d3d12.h} headers is not acceptable here. The actual types are + \c{ID3D12Device *} and \c{ID3D12CommandQueue *}. + + \note This is a RHI API with limited compatibility guarantees, see \l QRhi + for details. + */ + +/*! + \variable QRhiD3D12NativeHandles::dev +*/ + +/*! + \variable QRhiD3D12NativeHandles::minimumFeatureLevel +*/ + +/*! + \variable QRhiD3D12NativeHandles::adapterLuidLow +*/ + +/*! + \variable QRhiD3D12NativeHandles::adapterLuidHigh +*/ + +/*! + \variable QRhiD3D12NativeHandles::commandQueue +*/ + +/*! + \class QRhiD3D12CommandBufferNativeHandles + \inmodule QtGui + \brief Holds the ID3D12GraphicsCommandList 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 + \l{QRhi::beginFrame()}{beginFrame()} - \l{QRhi::endFrame()}{endFrame()} or + \l{QRhi::beginOffscreenFrame()}{beginOffscreenFrame()} - + \l{QRhi::endOffscreenFrame()}{endOffscreenFrame()} pair. + + \note This is a RHI API with limited compatibility guarantees, see \l QRhi + for details. + */ + +/*! + \variable QRhiD3D12CommandBufferNativeHandles::commandList +*/ + +// https://learn.microsoft.com/en-us/windows/win32/direct3d12/hardware-feature-levels +static const D3D_FEATURE_LEVEL MIN_FEATURE_LEVEL = D3D_FEATURE_LEVEL_11_0; + +QRhiD3D12::QRhiD3D12(QRhiD3D12InitParams *params, QRhiD3D12NativeHandles *importParams) +{ + debugLayer = params->enableDebugLayer; + if (importParams) { + if (importParams->dev) { + dev = reinterpret_cast(importParams->dev); + importedDevice = true; + } + if (importParams->commandQueue) { + cmdQueue = reinterpret_cast(importParams->commandQueue); + importedCommandQueue = true; + } + minimumFeatureLevel = D3D_FEATURE_LEVEL(importParams->minimumFeatureLevel); + adapterLuid.LowPart = importParams->adapterLuidLow; + adapterLuid.HighPart = importParams->adapterLuidHigh; + } +} + +template +inline Int aligned(Int v, Int byteAlign) +{ + return (v + byteAlign - 1) & ~(byteAlign - 1); +} + +static inline UINT calcSubresource(UINT mipSlice, UINT arraySlice, UINT mipLevels) +{ + return mipSlice + arraySlice * mipLevels; +} + +static inline QD3D12RenderTargetData *rtData(QRhiRenderTarget *rt) +{ + switch (rt->resourceType()) { + case QRhiResource::SwapChainRenderTarget: + return &QRHI_RES(QD3D12SwapChainRenderTarget, rt)->d; + case QRhiResource::TextureRenderTarget: + return &QRHI_RES(QD3D12TextureRenderTarget, rt)->d; + break; + default: + break; + } + Q_UNREACHABLE_RETURN(nullptr); +} + +bool QRhiD3D12::create(QRhi::Flags flags) +{ + rhiFlags = flags; + + UINT factoryFlags = 0; + if (debugLayer) + factoryFlags |= DXGI_CREATE_FACTORY_DEBUG; + HRESULT hr = CreateDXGIFactory2(factoryFlags, __uuidof(IDXGIFactory2), reinterpret_cast(&dxgiFactory)); + if (FAILED(hr)) { + qWarning("CreateDXGIFactory2() failed to create DXGI factory: %s", + qPrintable(QSystemError::windowsComString(hr))); + return false; + } + + supportsAllowTearing = false; + IDXGIFactory5 *factory5 = nullptr; + if (SUCCEEDED(dxgiFactory->QueryInterface(__uuidof(IDXGIFactory5), reinterpret_cast(&factory5)))) { + BOOL allowTearing = false; + if (SUCCEEDED(factory5->CheckFeatureSupport(DXGI_FEATURE_PRESENT_ALLOW_TEARING, &allowTearing, sizeof(allowTearing)))) + supportsAllowTearing = allowTearing; + factory5->Release(); + } + + if (debugLayer) { + ID3D12Debug1 *debug = nullptr; + if (SUCCEEDED(D3D12GetDebugInterface(__uuidof(ID3D12Debug1), reinterpret_cast(&debug)))) { + qCDebug(QRHI_LOG_INFO, "Enabling D3D12 debug layer"); + debug->EnableDebugLayer(); + debug->Release(); + } + } + + if (!importedDevice) { + IDXGIAdapter1 *adapter; + int requestedAdapterIndex = -1; + if (qEnvironmentVariableIsSet("QT_D3D_ADAPTER_INDEX")) + requestedAdapterIndex = qEnvironmentVariableIntValue("QT_D3D_ADAPTER_INDEX"); + + // The importParams may specify an adapter by the luid, take that into account. + if (requestedAdapterIndex < 0 && (adapterLuid.LowPart || adapterLuid.HighPart)) { + 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) + { + requestedAdapterIndex = adapterIndex; + break; + } + } + } + + if (requestedAdapterIndex < 0 && flags.testFlag(QRhi::PreferSoftwareRenderer)) { + 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.Flags & DXGI_ADAPTER_FLAG_SOFTWARE) { + requestedAdapterIndex = adapterIndex; + break; + } + } + } + + activeAdapter = nullptr; + for (int adapterIndex = 0; dxgiFactory->EnumAdapters1(UINT(adapterIndex), &adapter) != DXGI_ERROR_NOT_FOUND; ++adapterIndex) { + DXGI_ADAPTER_DESC1 desc; + adapter->GetDesc1(&desc); + const QString name = QString::fromUtf16(reinterpret_cast(desc.Description)); + qCDebug(QRHI_LOG_INFO, "Adapter %d: '%s' (vendor 0x%X device 0x%X flags 0x%X)", + adapterIndex, + qPrintable(name), + desc.VendorId, + desc.DeviceId, + desc.Flags); + if (!activeAdapter && (requestedAdapterIndex < 0 || requestedAdapterIndex == adapterIndex)) { + activeAdapter = adapter; + adapterLuid = desc.AdapterLuid; + driverInfoStruct.deviceName = name.toUtf8(); + driverInfoStruct.deviceId = desc.DeviceId; + driverInfoStruct.vendorId = desc.VendorId; + qCDebug(QRHI_LOG_INFO, " using this adapter"); + } else { + adapter->Release(); + } + } + if (!activeAdapter) { + qWarning("No adapter"); + return false; + } + + if (minimumFeatureLevel == 0) + minimumFeatureLevel = MIN_FEATURE_LEVEL; + + hr = D3D12CreateDevice(activeAdapter, + minimumFeatureLevel, + __uuidof(ID3D12Device), + reinterpret_cast(&dev)); + if (FAILED(hr)) { + qWarning("Failed to create D3D12 device: %s", qPrintable(QSystemError::windowsComString(hr))); + return false; + } + } else { + Q_ASSERT(dev); + // cannot just get a IDXGIDevice from the ID3D12Device anymore, look up the adapter instead + adapterLuid = dev->GetAdapterLuid(); + IDXGIAdapter1 *adapter; + 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(desc.Description)).toUtf8(); + driverInfoStruct.deviceId = desc.DeviceId; + driverInfoStruct.vendorId = desc.VendorId; + break; + } + } + qCDebug(QRHI_LOG_INFO, "Using imported device %p", dev); + } + + if (debugLayer) { + ID3D12InfoQueue *infoQueue; + if (SUCCEEDED(dev->QueryInterface(__uuidof(ID3D12InfoQueue), reinterpret_cast(&infoQueue)))) { + if (qEnvironmentVariableIntValue("QT_D3D_DEBUG_BREAK")) { + infoQueue->SetBreakOnSeverity(D3D12_MESSAGE_SEVERITY_CORRUPTION, true); + infoQueue->SetBreakOnSeverity(D3D12_MESSAGE_SEVERITY_ERROR, true); + infoQueue->SetBreakOnSeverity(D3D12_MESSAGE_SEVERITY_WARNING, true); + } + D3D12_INFO_QUEUE_FILTER filter = {}; + D3D12_MESSAGE_ID suppressedMessages[2] = { + // there is no way of knowing the clear color upfront + D3D12_MESSAGE_ID_CLEARRENDERTARGETVIEW_MISMATCHINGCLEARVALUE, + // we have no control over viewport and scissor rects + D3D12_MESSAGE_ID_DRAW_EMPTY_SCISSOR_RECTANGLE + }; + filter.DenyList.NumIDs = 2; + filter.DenyList.pIDList = suppressedMessages; + // Setting the filter would enable Info messages (e.g. about + // resource creation) which we don't need. + D3D12_MESSAGE_SEVERITY infoSev = D3D12_MESSAGE_SEVERITY_INFO; + filter.DenyList.NumSeverities = 1; + filter.DenyList.pSeverityList = &infoSev; + infoQueue->PushStorageFilter(&filter); + infoQueue->Release(); + } + } + + if (!importedCommandQueue) { + D3D12_COMMAND_QUEUE_DESC queueDesc = {}; + queueDesc.Type = D3D12_COMMAND_LIST_TYPE_DIRECT; + queueDesc.Priority = D3D12_COMMAND_QUEUE_PRIORITY_NORMAL; + hr = dev->CreateCommandQueue(&queueDesc, __uuidof(ID3D12CommandQueue), reinterpret_cast(&cmdQueue)); + if (FAILED(hr)) { + qWarning("Failed to create command queue: %s", qPrintable(QSystemError::windowsComString(hr))); + return false; + } + } + + hr = dev->CreateFence(0, D3D12_FENCE_FLAG_NONE, __uuidof(ID3D12Fence), reinterpret_cast(&fullFence)); + if (FAILED(hr)) { + qWarning("Failed to create fence: %s", qPrintable(QSystemError::windowsComString(hr))); + return false; + } + fullFenceEvent = CreateEvent(nullptr, FALSE, FALSE, nullptr); + fullFenceCounter = 0; + + for (int i = 0; i < QD3D12_FRAMES_IN_FLIGHT; ++i) { + hr = dev->CreateCommandAllocator(D3D12_COMMAND_LIST_TYPE_DIRECT, + __uuidof(ID3D12CommandAllocator), + reinterpret_cast(&cmdAllocators[i])); + if (FAILED(hr)) { + qWarning("Failed to create command allocator: %s", qPrintable(QSystemError::windowsComString(hr))); + return false; + } + } + + if (!vma.create(dev, activeAdapter)) { + qWarning("Failed to initialize graphics memory suballocator"); + return false; + } + + if (!rtvPool.create(dev, D3D12_DESCRIPTOR_HEAP_TYPE_RTV, "main RTV pool")) { + qWarning("Could not create RTV pool"); + return false; + } + + if (!dsvPool.create(dev, D3D12_DESCRIPTOR_HEAP_TYPE_DSV, "main DSV pool")) { + qWarning("Could not create DSV pool"); + return false; + } + + if (!cbvSrvUavPool.create(dev, D3D12_DESCRIPTOR_HEAP_TYPE_CBV_SRV_UAV, "main CBV-SRV-UAV pool")) { + qWarning("Could not create CBV-SRV-UAV pool"); + return false; + } + + resourcePool.create("main resource pool"); + pipelinePool.create("main pipeline pool"); + rootSignaturePool.create("main root signature pool"); + releaseQueue.create(&resourcePool, &pipelinePool, &rootSignaturePool); + barrierGen.create(&resourcePool); + + if (!samplerMgr.create(dev)) { + qWarning("Could not create sampler pool and shader-visible sampler heap"); + return false; + } + + if (!mipmapGen.create(this)) { + qWarning("Could not initialize mipmap generator"); + return false; + } + + const qint32 smallStagingSize = aligned(SMALL_STAGING_AREA_BYTES_PER_FRAME, QD3D12StagingArea::ALIGNMENT); + for (int i = 0; i < QD3D12_FRAMES_IN_FLIGHT; ++i) { + if (!smallStagingAreas[i].create(this, smallStagingSize, D3D12_HEAP_TYPE_UPLOAD)) { + qWarning("Could not create host-visible staging area"); + return false; + } + } + + if (!shaderVisibleCbvSrvUavHeap.create(dev, + D3D12_DESCRIPTOR_HEAP_TYPE_CBV_SRV_UAV, + SHADER_VISIBLE_CBV_SRV_UAV_HEAP_PER_FRAME_START_SIZE)) + { + qWarning("Could not create first shader-visible CBV/SRV/UAV heap"); + return false; + } + + deviceLost = false; + offscreenActive = false; + + nativeHandlesStruct.dev = dev; + nativeHandlesStruct.minimumFeatureLevel = minimumFeatureLevel; + nativeHandlesStruct.adapterLuidLow = adapterLuid.LowPart; + nativeHandlesStruct.adapterLuidHigh = adapterLuid.HighPart; + nativeHandlesStruct.commandQueue = cmdQueue; + + return true; +} + +void QRhiD3D12::destroy() +{ + if (!deviceLost && fullFence && fullFenceEvent) + waitGpu(); + + releaseQueue.releaseAll(); + + for (int i = 0; i < QD3D12_FRAMES_IN_FLIGHT; ++i) { + if (offscreenCb[i]) { + if (offscreenCb[i]->cmdList) + offscreenCb[i]->cmdList->Release(); + delete offscreenCb[i]; + offscreenCb[i] = nullptr; + } + } + + shaderVisibleCbvSrvUavHeap.destroy(); + + for (int i = 0; i < QD3D12_FRAMES_IN_FLIGHT; ++i) + smallStagingAreas[i].destroy(); + + mipmapGen.destroy(); + samplerMgr.destroy(); + resourcePool.destroy(); + pipelinePool.destroy(); + rootSignaturePool.destroy(); + rtvPool.destroy(); + dsvPool.destroy(); + cbvSrvUavPool.destroy(); + + for (int i = 0; i < QD3D12_FRAMES_IN_FLIGHT; ++i) { + cmdAllocators[i]->Release(); + cmdAllocators[i] = nullptr; + } + + if (fullFenceEvent) { + CloseHandle(fullFenceEvent); + fullFenceEvent = nullptr; + } + + if (fullFence) { + fullFence->Release(); + fullFence = nullptr; + } + + if (!importedCommandQueue) { + if (cmdQueue) { + cmdQueue->Release(); + cmdQueue = nullptr; + } + } + + vma.destroy(); + + if (!importedDevice) { + if (dev) { + dev->Release(); + dev = nullptr; + } + } + + if (dcompDevice) { + dcompDevice->Release(); + dcompDevice = nullptr; + } + + if (activeAdapter) { + activeAdapter->Release(); + activeAdapter = nullptr; + } + + if (dxgiFactory) { + dxgiFactory->Release(); + dxgiFactory = nullptr; + } +} + +QList QRhiD3D12::supportedSampleCounts() const +{ + return { 1, 2, 4, 8 }; +} + +QRhiSwapChain *QRhiD3D12::createSwapChain() +{ + return new QD3D12SwapChain(this); +} + +QRhiBuffer *QRhiD3D12::createBuffer(QRhiBuffer::Type type, QRhiBuffer::UsageFlags usage, quint32 size) +{ + return new QD3D12Buffer(this, type, usage, size); +} + +int QRhiD3D12::ubufAlignment() const +{ + return D3D12_CONSTANT_BUFFER_DATA_PLACEMENT_ALIGNMENT; // 256 +} + +bool QRhiD3D12::isYUpInFramebuffer() const +{ + return false; +} + +bool QRhiD3D12::isYUpInNDC() const +{ + return true; +} + +bool QRhiD3D12::isClipDepthZeroToOne() const +{ + return true; +} + +QMatrix4x4 QRhiD3D12::clipSpaceCorrMatrix() const +{ + // Like with Vulkan, but Y is already good. + + static QMatrix4x4 m; + if (m.isIdentity()) { + // NB the ctor takes row-major + m = QMatrix4x4(1.0f, 0.0f, 0.0f, 0.0f, + 0.0f, 1.0f, 0.0f, 0.0f, + 0.0f, 0.0f, 0.5f, 0.5f, + 0.0f, 0.0f, 0.0f, 1.0f); + } + return m; +} + +bool QRhiD3D12::isTextureFormatSupported(QRhiTexture::Format format, QRhiTexture::Flags flags) const +{ + Q_UNUSED(flags); + + if (format >= QRhiTexture::ETC2_RGB8 && format <= QRhiTexture::ASTC_12x12) + return false; + + return true; +} + +bool QRhiD3D12::isFeatureSupported(QRhi::Feature feature) const +{ + switch (feature) { + case QRhi::MultisampleTexture: + return true; + case QRhi::MultisampleRenderBuffer: + return true; + case QRhi::DebugMarkers: +#ifdef QRHI_D3D12_HAS_OLD_PIX + return true; +#else + return false; +#endif + case QRhi::Timestamps: + return false; // ### + case QRhi::Instancing: + return true; + case QRhi::CustomInstanceStepRate: + return true; + case QRhi::PrimitiveRestart: + return true; + case QRhi::NonDynamicUniformBuffers: + return false; + case QRhi::NonFourAlignedEffectiveIndexBufferOffset: + return true; + case QRhi::NPOTTextureRepeat: + return true; + case QRhi::RedOrAlpha8IsRed: + return true; + case QRhi::ElementIndexUint: + return true; + case QRhi::Compute: + return true; + case QRhi::WideLines: + return false; + case QRhi::VertexShaderPointSize: + return false; + case QRhi::BaseVertex: + return true; + case QRhi::BaseInstance: + return true; + case QRhi::TriangleFanTopology: + return false; + case QRhi::ReadBackNonUniformBuffer: + return true; + case QRhi::ReadBackNonBaseMipLevel: + return true; + case QRhi::TexelFetch: + return true; + case QRhi::RenderToNonBaseMipLevel: + return true; + case QRhi::IntAttributes: + return true; + case QRhi::ScreenSpaceDerivatives: + return true; + case QRhi::ReadBackAnyTextureFormat: + return true; + case QRhi::PipelineCacheDataLoadSave: + return false; // ### + case QRhi::ImageDataStride: + return true; + case QRhi::RenderBufferImport: + return false; + case QRhi::ThreeDimensionalTextures: + return true; + case QRhi::RenderTo3DTextureSlice: + return true; + case QRhi::TextureArrays: + return true; + case QRhi::Tessellation: + return true; + case QRhi::GeometryShader: + return true; + case QRhi::TextureArrayRange: + return true; + case QRhi::NonFillPolygonMode: + return true; + case QRhi::OneDimensionalTextures: + return true; + case QRhi::OneDimensionalTextureMipmaps: + return false; // we generate mipmaps ourselves with compute and this is not implemented + case QRhi::HalfAttributes: + return true; + case QRhi::RenderToOneDimensionalTexture: + return true; + case QRhi::ThreeDimensionalTextureMipmaps: + return false; // we generate mipmaps ourselves with compute and this is not implemented + } + return false; +} + +int QRhiD3D12::resourceLimit(QRhi::ResourceLimit limit) const +{ + switch (limit) { + case QRhi::TextureSizeMin: + return 1; + case QRhi::TextureSizeMax: + return 16384; + case QRhi::MaxColorAttachments: + return 8; + case QRhi::FramesInFlight: + return QD3D12_FRAMES_IN_FLIGHT; + case QRhi::MaxAsyncReadbackFrames: + return QD3D12_FRAMES_IN_FLIGHT; + case QRhi::MaxThreadGroupsPerDimension: + return 65535; + case QRhi::MaxThreadsPerThreadGroup: + return 1024; + case QRhi::MaxThreadGroupX: + return 1024; + case QRhi::MaxThreadGroupY: + return 1024; + case QRhi::MaxThreadGroupZ: + return 1024; + case QRhi::TextureArraySizeMax: + return 2048; + case QRhi::MaxUniformBufferRange: + return 65536; + case QRhi::MaxVertexInputs: + return 32; + case QRhi::MaxVertexOutputs: + return 32; + } + return 0; +} + +const QRhiNativeHandles *QRhiD3D12::nativeHandles() +{ + return &nativeHandlesStruct; +} + +QRhiDriverInfo QRhiD3D12::driverInfo() const +{ + return driverInfoStruct; +} + +QRhiStats QRhiD3D12::statistics() +{ + QRhiStats result; + result.totalPipelineCreationTime = totalPipelineCreationTime(); + + D3D12MA::Budget budgets[2]; // [gpu, system] with discreet GPU or [shared, nothing] with UMA + vma.getBudget(&budgets[0], &budgets[1]); + for (int i = 0; i < 2; ++i) { + const D3D12MA::Statistics &stats(budgets[i].Stats); + result.blockCount += stats.BlockCount; + result.allocCount += stats.AllocationCount; + result.usedBytes += stats.AllocationBytes; + result.unusedBytes += stats.BlockBytes - stats.AllocationBytes; + result.totalUsageBytes += budgets[i].UsageBytes; + } + + return result; +} + +bool QRhiD3D12::makeThreadLocalNativeContextCurrent() +{ + // not applicable + return false; +} + +void QRhiD3D12::releaseCachedResources() +{ + shaderBytecodeCache.data.clear(); +} + +bool QRhiD3D12::isDeviceLost() const +{ + return deviceLost; +} + +QByteArray QRhiD3D12::pipelineCacheData() +{ + return {}; +} + +void QRhiD3D12::setPipelineCacheData(const QByteArray &data) +{ + Q_UNUSED(data); +} + +QRhiRenderBuffer *QRhiD3D12::createRenderBuffer(QRhiRenderBuffer::Type type, const QSize &pixelSize, + int sampleCount, QRhiRenderBuffer::Flags flags, + QRhiTexture::Format backingFormatHint) +{ + return new QD3D12RenderBuffer(this, type, pixelSize, sampleCount, flags, backingFormatHint); +} + +QRhiTexture *QRhiD3D12::createTexture(QRhiTexture::Format format, + const QSize &pixelSize, int depth, int arraySize, + int sampleCount, QRhiTexture::Flags flags) +{ + return new QD3D12Texture(this, format, pixelSize, depth, arraySize, sampleCount, flags); +} + +QRhiSampler *QRhiD3D12::createSampler(QRhiSampler::Filter magFilter, QRhiSampler::Filter minFilter, + QRhiSampler::Filter mipmapMode, + QRhiSampler::AddressMode u, QRhiSampler::AddressMode v, QRhiSampler::AddressMode w) +{ + return new QD3D12Sampler(this, magFilter, minFilter, mipmapMode, u, v, w); +} + +QRhiTextureRenderTarget *QRhiD3D12::createTextureRenderTarget(const QRhiTextureRenderTargetDescription &desc, + QRhiTextureRenderTarget::Flags flags) +{ + return new QD3D12TextureRenderTarget(this, desc, flags); +} + +QRhiGraphicsPipeline *QRhiD3D12::createGraphicsPipeline() +{ + return new QD3D12GraphicsPipeline(this); +} + +QRhiComputePipeline *QRhiD3D12::createComputePipeline() +{ + return new QD3D12ComputePipeline(this); +} + +QRhiShaderResourceBindings *QRhiD3D12::createShaderResourceBindings() +{ + return new QD3D12ShaderResourceBindings(this); +} + +void QRhiD3D12::setGraphicsPipeline(QRhiCommandBuffer *cb, QRhiGraphicsPipeline *ps) +{ + QD3D12CommandBuffer *cbD = QRHI_RES(QD3D12CommandBuffer, cb); + Q_ASSERT(cbD->recordingPass == QD3D12CommandBuffer::RenderPass); + QD3D12GraphicsPipeline *psD = QRHI_RES(QD3D12GraphicsPipeline, ps); + const bool pipelineChanged = cbD->currentGraphicsPipeline != psD || cbD->currentPipelineGeneration != psD->generation; + + if (pipelineChanged) { + cbD->currentGraphicsPipeline = psD; + cbD->currentComputePipeline = nullptr; + cbD->currentPipelineGeneration = psD->generation; + + if (QD3D12Pipeline *pipeline = pipelinePool.lookupRef(psD->handle)) { + Q_ASSERT(pipeline->type == QD3D12Pipeline::Graphics); + cbD->cmdList->SetPipelineState(pipeline->pso); + if (QD3D12RootSignature *rs = rootSignaturePool.lookupRef(psD->rootSigHandle)) + cbD->cmdList->SetGraphicsRootSignature(rs->rootSig); + } + + cbD->cmdList->IASetPrimitiveTopology(psD->topology); + } +} + +void QRhiD3D12::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; + if (d.hasDynamicOffset) { + for (int i = 0; i < dynamicOffsetCount; ++i) { + const QRhiCommandBuffer::DynamicOffset &dynOfs(dynamicOffsets[i]); + if (dynOfs.first == binding) { + Q_ASSERT(aligned(dynOfs.second, 256u) == dynOfs.second); + offset += dynOfs.second; + } + } + } + visitorData.cbufs[s].append({ bufD->handles[currentFrameSlot], offset }); +} + +void QRhiD3D12::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) +{ + 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) +{ + QD3D12Buffer *bufD = QRHI_RES(QD3D12Buffer, d.buf); + // SPIRV-Cross generated HLSL uses RWByteAddressBuffer + D3D12_UNORDERED_ACCESS_VIEW_DESC uavDesc = {}; + uavDesc.Format = DXGI_FORMAT_R32_TYPELESS; + uavDesc.ViewDimension = D3D12_UAV_DIMENSION_BUFFER; + uavDesc.Buffer.FirstElement = d.offset / 4; + uavDesc.Buffer.NumElements = aligned(bufD->m_size - d.offset, 4u) / 4; + uavDesc.Buffer.Flags = D3D12_BUFFER_UAV_FLAG_RAW; + visitorData.uavs[s].append({ bufD->handles[0], uavDesc }); +} + +void QRhiD3D12::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; + if (isCube) { + uavDesc.ViewDimension = D3D12_UAV_DIMENSION_TEXTURE2DARRAY; + uavDesc.Texture2DArray.MipSlice = UINT(d.level); + uavDesc.Texture2DArray.FirstArraySlice = 0; + uavDesc.Texture2DArray.ArraySize = 6; + } else if (isArray) { + uavDesc.ViewDimension = D3D12_UAV_DIMENSION_TEXTURE2DARRAY; + uavDesc.Texture2DArray.MipSlice = UINT(d.level); + uavDesc.Texture2DArray.FirstArraySlice = 0; + uavDesc.Texture2DArray.ArraySize = UINT(qMax(0, texD->m_arraySize)); + } else if (is3D) { + uavDesc.ViewDimension = D3D12_UAV_DIMENSION_TEXTURE3D; + uavDesc.Texture3D.MipSlice = UINT(d.level); + } else { + uavDesc.ViewDimension = D3D12_UAV_DIMENSION_TEXTURE2D; + uavDesc.Texture2D.MipSlice = UINT(d.level); + } + visitorData.uavs[s].append({ texD->handle, uavDesc }); +} + +void QRhiD3D12::setShaderResources(QRhiCommandBuffer *cb, QRhiShaderResourceBindings *srb, + int dynamicOffsetCount, + const QRhiCommandBuffer::DynamicOffset *dynamicOffsets) +{ + QD3D12CommandBuffer *cbD = QRHI_RES(QD3D12CommandBuffer, cb); + Q_ASSERT(cbD->recordingPass != QD3D12CommandBuffer::NoPass); + QD3D12GraphicsPipeline *gfxPsD = QRHI_RES(QD3D12GraphicsPipeline, cbD->currentGraphicsPipeline); + QD3D12ComputePipeline *compPsD = QRHI_RES(QD3D12ComputePipeline, cbD->currentComputePipeline); + + if (!srb) { + if (gfxPsD) + srb = gfxPsD->m_shaderResourceBindings; + else + srb = compPsD->m_shaderResourceBindings; + } + + 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]); + switch (b->type) { + case QRhiShaderResourceBinding::UniformBuffer: + { + QD3D12Buffer *bufD = QRHI_RES(QD3D12Buffer, b->u.ubuf.buf); + Q_ASSERT(bufD->m_usage.testFlag(QRhiBuffer::UniformBuffer)); + Q_ASSERT(bufD->m_type == QRhiBuffer::Dynamic); + bufD->executeHostWritesForFrameSlot(currentFrameSlot); + } + break; + case QRhiShaderResourceBinding::SampledTexture: + case QRhiShaderResourceBinding::Texture: + case QRhiShaderResourceBinding::Sampler: + { + const QRhiShaderResourceBinding::Data::TextureAndOrSamplerData *data = &b->u.stex; + for (int elem = 0; elem < data->count; ++elem) { + QD3D12Texture *texD = QRHI_RES(QD3D12Texture, data->texSamplers[elem].tex); + QD3D12Sampler *samplerD = QRHI_RES(QD3D12Sampler, data->texSamplers[elem].sampler); + // We use the same code path for both combined and separate + // images and samplers, so tex or sampler (but not both) can be + // null here. + Q_ASSERT(texD || samplerD); + if (texD) { + UINT state = 0; + if (b->stage == QRhiShaderResourceBinding::FragmentStage) { + state = D3D12_RESOURCE_STATE_PIXEL_SHADER_RESOURCE; + } else if (b->stage.testFlag(QRhiShaderResourceBinding::FragmentStage)) { + state = D3D12_RESOURCE_STATE_PIXEL_SHADER_RESOURCE | D3D12_RESOURCE_STATE_NON_PIXEL_SHADER_RESOURCE; + } else { + state = D3D12_RESOURCE_STATE_NON_PIXEL_SHADER_RESOURCE; + } + barrierGen.addTransitionBarrier(texD->handle, D3D12_RESOURCE_STATES(state)); + barrierGen.enqueueBufferedTransitionBarriers(cbD); + } + } + } + break; + case QRhiShaderResourceBinding::ImageLoad: + case QRhiShaderResourceBinding::ImageStore: + case QRhiShaderResourceBinding::ImageLoadStore: + { + QD3D12Texture *texD = QRHI_RES(QD3D12Texture, b->u.simage.tex); + if (QD3D12Resource *res = resourcePool.lookupRef(texD->handle)) { + if (res->uavUsage) { + if (res->uavUsage & QD3D12Resource::UavUsageWrite) { + // RaW or WaW + barrierGen.enqueueUavBarrier(cbD, texD->handle); + } else { + if (b->type == QRhiShaderResourceBinding::ImageStore + || b->type == QRhiShaderResourceBinding::ImageLoadStore) + { + // WaR or WaW + barrierGen.enqueueUavBarrier(cbD, texD->handle); + } + } + } + res->uavUsage = 0; + if (b->type == QRhiShaderResourceBinding::ImageLoad || b->type == QRhiShaderResourceBinding::ImageLoadStore) + res->uavUsage |= QD3D12Resource::UavUsageRead; + if (b->type == QRhiShaderResourceBinding::ImageStore || b->type == QRhiShaderResourceBinding::ImageLoadStore) + res->uavUsage |= QD3D12Resource::UavUsageWrite; + barrierGen.addTransitionBarrier(texD->handle, D3D12_RESOURCE_STATE_UNORDERED_ACCESS); + barrierGen.enqueueBufferedTransitionBarriers(cbD); + } + } + break; + case QRhiShaderResourceBinding::BufferLoad: + case QRhiShaderResourceBinding::BufferStore: + case QRhiShaderResourceBinding::BufferLoadStore: + { + QD3D12Buffer *bufD = QRHI_RES(QD3D12Buffer, b->u.sbuf.buf); + Q_ASSERT(bufD->m_usage.testFlag(QRhiBuffer::StorageBuffer)); + Q_ASSERT(bufD->m_type != QRhiBuffer::Dynamic); + if (QD3D12Resource *res = resourcePool.lookupRef(bufD->handles[0])) { + if (res->uavUsage) { + if (res->uavUsage & QD3D12Resource::UavUsageWrite) { + // RaW or WaW + barrierGen.enqueueUavBarrier(cbD, bufD->handles[0]); + } else { + if (b->type == QRhiShaderResourceBinding::BufferStore + || b->type == QRhiShaderResourceBinding::BufferLoadStore) + { + // WaR or WaW + barrierGen.enqueueUavBarrier(cbD, bufD->handles[0]); + } + } + } + res->uavUsage = 0; + if (b->type == QRhiShaderResourceBinding::BufferLoad || b->type == QRhiShaderResourceBinding::BufferLoadStore) + res->uavUsage |= QD3D12Resource::UavUsageRead; + if (b->type == QRhiShaderResourceBinding::BufferStore || b->type == QRhiShaderResourceBinding::BufferLoadStore) + res->uavUsage |= QD3D12Resource::UavUsageWrite; + barrierGen.addTransitionBarrier(bufD->handles[0], D3D12_RESOURCE_STATE_UNORDERED_ACCESS); + barrierGen.enqueueBufferedTransitionBarriers(cbD); + } + } + break; + } + } + + const bool srbChanged = gfxPsD ? (cbD->currentGraphicsSrb != srb) : (cbD->currentComputeSrb != srb); + const bool srbRebuilt = cbD->currentSrbGeneration != srbD->generation; + + if (srbChanged || srbRebuilt || srbD->hasDynamicOffset) { + const QD3D12ShaderStageData *stageData = gfxPsD ? gfxPsD->stageData.data() : &compPsD->stageData; + + // The order of root parameters must match + // QD3D12ShaderResourceBindings::createRootSignature(), meaning the + // logic below must mirror that function (uniform buffers first etc.) + + QD3D12ShaderResourceVisitor visitor(srbD, stageData, gfxPsD ? 5 : 1); + + 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.visit(); + + quint32 cbvSrvUavCount = 0; + for (int s = 0; s < 6; ++s) { + // CBs use root constant buffer views, no need to count them here + cbvSrvUavCount += visitorData.srvs[s].count(); + cbvSrvUavCount += visitorData.uavs[s].count(); + } + + bool gotNewHeap = false; + if (!ensureShaderVisibleDescriptorHeapCapacity(&shaderVisibleCbvSrvUavHeap, + D3D12_DESCRIPTOR_HEAP_TYPE_CBV_SRV_UAV, + currentFrameSlot, + cbvSrvUavCount, + &gotNewHeap)) + { + return; + } + if (gotNewHeap) { + qCDebug(QRHI_LOG_INFO, "Created new shader-visible CBV/SRV/UAV descriptor heap," + " per-frame slice size is now %u," + " if this happens frequently then that's not great.", + shaderVisibleCbvSrvUavHeap.perFrameHeapSlice[0].capacity); + bindShaderVisibleHeaps(cbD); + } + + int rootParamIndex = 0; + for (int s = 0; s < 6; ++s) { + if (!visitorData.cbufs[s].isEmpty()) { + for (int i = 0, count = visitorData.cbufs[s].count(); i < count; ++i) { + const auto &cbuf(visitorData.cbufs[s][i]); + if (QD3D12Resource *res = resourcePool.lookupRef(cbuf.first)) { + quint32 offset = cbuf.second; + D3D12_GPU_VIRTUAL_ADDRESS gpuAddr = res->resource->GetGPUVirtualAddress() + offset; + if (cbD->currentGraphicsPipeline) + cbD->cmdList->SetGraphicsRootConstantBufferView(rootParamIndex, gpuAddr); + else + cbD->cmdList->SetComputeRootConstantBufferView(rootParamIndex, gpuAddr); + } + rootParamIndex += 1; + } + } + } + for (int s = 0; s < 6; ++s) { + if (!visitorData.srvs[s].isEmpty()) { + QD3D12DescriptorHeap &gpuSrvHeap(shaderVisibleCbvSrvUavHeap.perFrameHeapSlice[currentFrameSlot]); + QD3D12Descriptor startDesc = gpuSrvHeap.get(visitorData.srvs[s].count()); + for (int i = 0, count = visitorData.srvs[s].count(); i < count; ++i) { + const auto &srv(visitorData.srvs[s][i]); + dev->CopyDescriptorsSimple(1, gpuSrvHeap.incremented(startDesc, i).cpuHandle, srv.cpuHandle, + D3D12_DESCRIPTOR_HEAP_TYPE_CBV_SRV_UAV); + } + + if (cbD->currentGraphicsPipeline) + cbD->cmdList->SetGraphicsRootDescriptorTable(rootParamIndex, startDesc.gpuHandle); + else if (cbD->currentComputePipeline) + cbD->cmdList->SetComputeRootDescriptorTable(rootParamIndex, startDesc.gpuHandle); + + rootParamIndex += 1; + } + } + for (int s = 0; s < 6; ++s) { + // Samplers are one parameter / descriptor table each, and the + // descriptor is from the shader visible sampler heap already. + for (const QD3D12Descriptor &samplerDescriptor : visitorData.samplers[s]) { + if (cbD->currentGraphicsPipeline) + cbD->cmdList->SetGraphicsRootDescriptorTable(rootParamIndex, samplerDescriptor.gpuHandle); + else if (cbD->currentComputePipeline) + cbD->cmdList->SetComputeRootDescriptorTable(rootParamIndex, samplerDescriptor.gpuHandle); + + rootParamIndex += 1; + } + } + for (int s = 0; s < 6; ++s) { + if (!visitorData.uavs[s].isEmpty()) { + QD3D12DescriptorHeap &gpuUavHeap(shaderVisibleCbvSrvUavHeap.perFrameHeapSlice[currentFrameSlot]); + QD3D12Descriptor startDesc = gpuUavHeap.get(visitorData.uavs[s].count()); + for (int i = 0, count = visitorData.uavs[s].count(); i < count; ++i) { + const auto &uav(visitorData.uavs[s][i]); + if (QD3D12Resource *res = resourcePool.lookupRef(uav.first)) { + dev->CreateUnorderedAccessView(res->resource, nullptr, &uav.second, + gpuUavHeap.incremented(startDesc, i).cpuHandle); + } else { + dev->CreateUnorderedAccessView(nullptr, nullptr, nullptr, + gpuUavHeap.incremented(startDesc, i).cpuHandle); + } + } + + if (cbD->currentGraphicsPipeline) + cbD->cmdList->SetGraphicsRootDescriptorTable(rootParamIndex, startDesc.gpuHandle); + else if (cbD->currentComputePipeline) + cbD->cmdList->SetComputeRootDescriptorTable(rootParamIndex, startDesc.gpuHandle); + + rootParamIndex += 1; + } + } + + if (gfxPsD) { + cbD->currentGraphicsSrb = srb; + cbD->currentComputeSrb = nullptr; + } else { + cbD->currentGraphicsSrb = nullptr; + cbD->currentComputeSrb = srb; + } + cbD->currentSrbGeneration = srbD->generation; + } +} + +void QRhiD3D12::setVertexInput(QRhiCommandBuffer *cb, + int startBinding, int bindingCount, const QRhiCommandBuffer::VertexInput *bindings, + QRhiBuffer *indexBuf, quint32 indexOffset, QRhiCommandBuffer::IndexFormat indexFormat) +{ + QD3D12CommandBuffer *cbD = QRHI_RES(QD3D12CommandBuffer, cb); + Q_ASSERT(cbD->recordingPass == QD3D12CommandBuffer::RenderPass); + + bool needsBindVBuf = false; + for (int i = 0; i < bindingCount; ++i) { + const int inputSlot = startBinding + i; + QD3D12Buffer *bufD = QRHI_RES(QD3D12Buffer, bindings[i].first); + Q_ASSERT(bufD->m_usage.testFlag(QRhiBuffer::VertexBuffer)); + const bool isDynamic = bufD->m_type == QRhiBuffer::Dynamic; + if (isDynamic) + bufD->executeHostWritesForFrameSlot(currentFrameSlot); + + if (cbD->currentVertexBuffers[inputSlot] != bufD->handles[isDynamic ? currentFrameSlot : 0] + || cbD->currentVertexOffsets[inputSlot] != bindings[i].second) + { + needsBindVBuf = true; + cbD->currentVertexBuffers[inputSlot] = bufD->handles[isDynamic ? currentFrameSlot : 0]; + cbD->currentVertexOffsets[inputSlot] = bindings[i].second; + } + } + + if (needsBindVBuf) { + QVarLengthArray vbv; + vbv.reserve(bindingCount); + + QD3D12GraphicsPipeline *psD = cbD->currentGraphicsPipeline; + const QRhiVertexInputLayout &inputLayout(psD->m_vertexInputLayout); + const int inputBindingCount = inputLayout.cendBindings() - inputLayout.cbeginBindings(); + + for (int i = 0, ie = qMin(bindingCount, inputBindingCount); i != ie; ++i) { + QD3D12Buffer *bufD = QRHI_RES(QD3D12Buffer, bindings[i].first); + const QD3D12ObjectHandle handle = bufD->handles[bufD->m_type == QRhiBuffer::Dynamic ? currentFrameSlot : 0]; + const quint32 offset = bindings[i].second; + const quint32 stride = inputLayout.bindingAt(i)->stride(); + + if (bufD->m_type != QRhiBuffer::Dynamic) { + barrierGen.addTransitionBarrier(handle, D3D12_RESOURCE_STATE_VERTEX_AND_CONSTANT_BUFFER); + barrierGen.enqueueBufferedTransitionBarriers(cbD); + } + + if (QD3D12Resource *res = resourcePool.lookupRef(handle)) { + vbv.append({ + res->resource->GetGPUVirtualAddress() + offset, + UINT(res->desc.Width - offset), + stride + }); + } + } + + cbD->cmdList->IASetVertexBuffers(UINT(startBinding), vbv.count(), vbv.constData()); + } + + if (indexBuf) { + QD3D12Buffer *ibufD = QRHI_RES(QD3D12Buffer, indexBuf); + Q_ASSERT(ibufD->m_usage.testFlag(QRhiBuffer::IndexBuffer)); + const bool isDynamic = ibufD->m_type == QRhiBuffer::Dynamic; + if (isDynamic) + ibufD->executeHostWritesForFrameSlot(currentFrameSlot); + + const DXGI_FORMAT dxgiFormat = indexFormat == QRhiCommandBuffer::IndexUInt16 ? DXGI_FORMAT_R16_UINT + : DXGI_FORMAT_R32_UINT; + if (cbD->currentIndexBuffer != ibufD->handles[isDynamic ? currentFrameSlot : 0] + || cbD->currentIndexOffset != indexOffset + || cbD->currentIndexFormat != dxgiFormat) + { + cbD->currentIndexBuffer = ibufD->handles[isDynamic ? currentFrameSlot : 0]; + cbD->currentIndexOffset = indexOffset; + cbD->currentIndexFormat = dxgiFormat; + + if (ibufD->m_type != QRhiBuffer::Dynamic) { + barrierGen.addTransitionBarrier(cbD->currentIndexBuffer, D3D12_RESOURCE_STATE_INDEX_BUFFER); + barrierGen.enqueueBufferedTransitionBarriers(cbD); + } + + if (QD3D12Resource *res = resourcePool.lookupRef(cbD->currentIndexBuffer)) { + const D3D12_INDEX_BUFFER_VIEW ibv = { + res->resource->GetGPUVirtualAddress() + indexOffset, + UINT(res->desc.Width - indexOffset), + dxgiFormat + }; + cbD->cmdList->IASetIndexBuffer(&ibv); + } + } + } +} + +void QRhiD3D12::setViewport(QRhiCommandBuffer *cb, const QRhiViewport &viewport) +{ + QD3D12CommandBuffer *cbD = QRHI_RES(QD3D12CommandBuffer, cb); + Q_ASSERT(cbD->recordingPass == QD3D12CommandBuffer::RenderPass); + Q_ASSERT(cbD->currentTarget); + const QSize outputSize = cbD->currentTarget->pixelSize(); + + // D3D expects top-left, QRhiViewport is bottom-left + float x, y, w, h; + if (!qrhi_toTopLeftRenderTargetRect(outputSize, viewport.viewport(), &x, &y, &w, &h)) + return; + + D3D12_VIEWPORT v; + v.TopLeftX = x; + v.TopLeftY = y; + v.Width = w; + v.Height = h; + v.MinDepth = viewport.minDepth(); + v.MaxDepth = viewport.maxDepth(); + cbD->cmdList->RSSetViewports(1, &v); + + if (cbD->currentGraphicsPipeline + && !cbD->currentGraphicsPipeline->flags().testFlag(QRhiGraphicsPipeline::UsesScissor)) + { + qrhi_toTopLeftRenderTargetRect(outputSize, viewport.viewport(), &x, &y, &w, &h); + D3D12_RECT r; + r.left = x; + r.top = y; + // right and bottom are exclusive + r.right = x + w; + r.bottom = y + h; + cbD->cmdList->RSSetScissorRects(1, &r); + } +} + +void QRhiD3D12::setScissor(QRhiCommandBuffer *cb, const QRhiScissor &scissor) +{ + QD3D12CommandBuffer *cbD = QRHI_RES(QD3D12CommandBuffer, cb); + Q_ASSERT(cbD->recordingPass == QD3D12CommandBuffer::RenderPass); + Q_ASSERT(cbD->currentTarget); + const QSize outputSize = cbD->currentTarget->pixelSize(); + + // D3D expects top-left, QRhiScissor is bottom-left + int x, y, w, h; + if (!qrhi_toTopLeftRenderTargetRect(outputSize, scissor.scissor(), &x, &y, &w, &h)) + return; + + D3D12_RECT r; + r.left = x; + r.top = y; + // right and bottom are exclusive + r.right = x + w; + r.bottom = y + h; + cbD->cmdList->RSSetScissorRects(1, &r); +} + +void QRhiD3D12::setBlendConstants(QRhiCommandBuffer *cb, const QColor &c) +{ + QD3D12CommandBuffer *cbD = QRHI_RES(QD3D12CommandBuffer, cb); + Q_ASSERT(cbD->recordingPass == QD3D12CommandBuffer::RenderPass); + float v[4] = { c.redF(), c.greenF(), c.blueF(), c.alphaF() }; + cbD->cmdList->OMSetBlendFactor(v); +} + +void QRhiD3D12::setStencilRef(QRhiCommandBuffer *cb, quint32 refValue) +{ + QD3D12CommandBuffer *cbD = QRHI_RES(QD3D12CommandBuffer, cb); + Q_ASSERT(cbD->recordingPass == QD3D12CommandBuffer::RenderPass); + cbD->cmdList->OMSetStencilRef(refValue); +} + +void QRhiD3D12::draw(QRhiCommandBuffer *cb, quint32 vertexCount, + quint32 instanceCount, quint32 firstVertex, quint32 firstInstance) +{ + QD3D12CommandBuffer *cbD = QRHI_RES(QD3D12CommandBuffer, cb); + Q_ASSERT(cbD->recordingPass == QD3D12CommandBuffer::RenderPass); + cbD->cmdList->DrawInstanced(vertexCount, instanceCount, firstVertex, firstInstance); +} + +void QRhiD3D12::drawIndexed(QRhiCommandBuffer *cb, quint32 indexCount, + quint32 instanceCount, quint32 firstIndex, qint32 vertexOffset, quint32 firstInstance) +{ + QD3D12CommandBuffer *cbD = QRHI_RES(QD3D12CommandBuffer, cb); + Q_ASSERT(cbD->recordingPass == QD3D12CommandBuffer::RenderPass); + cbD->cmdList->DrawIndexedInstanced(indexCount, instanceCount, + firstIndex, vertexOffset, + firstInstance); +} + +void QRhiD3D12::debugMarkBegin(QRhiCommandBuffer *cb, const QByteArray &name) +{ + if (!debugMarkers) + return; + + QD3D12CommandBuffer *cbD = QRHI_RES(QD3D12CommandBuffer, cb); +#ifdef QRHI_D3D12_HAS_OLD_PIX + PIXBeginEvent(cbD->cmdList, PIX_COLOR_DEFAULT, reinterpret_cast(QString::fromLatin1(name).utf16())); +#else + Q_UNUSED(cbD); + Q_UNUSED(name); +#endif +} + +void QRhiD3D12::debugMarkEnd(QRhiCommandBuffer *cb) +{ + if (!debugMarkers) + return; + + QD3D12CommandBuffer *cbD = QRHI_RES(QD3D12CommandBuffer, cb); +#ifdef QRHI_D3D12_HAS_OLD_PIX + PIXEndEvent(cbD->cmdList); +#else + Q_UNUSED(cbD); +#endif +} + +void QRhiD3D12::debugMarkMsg(QRhiCommandBuffer *cb, const QByteArray &msg) +{ + if (!debugMarkers) + return; + + QD3D12CommandBuffer *cbD = QRHI_RES(QD3D12CommandBuffer, cb); +#ifdef QRHI_D3D12_HAS_OLD_PIX + PIXSetMarker(cbD->cmdList, PIX_COLOR_DEFAULT, reinterpret_cast(QString::fromLatin1(msg).utf16())); +#else + Q_UNUSED(cbD); + Q_UNUSED(msg); +#endif +} + +const QRhiNativeHandles *QRhiD3D12::nativeHandles(QRhiCommandBuffer *cb) +{ + return QRHI_RES(QD3D12CommandBuffer, cb)->nativeHandles(); +} + +void QRhiD3D12::beginExternal(QRhiCommandBuffer *cb) +{ + Q_UNUSED(cb); +} + +void QRhiD3D12::endExternal(QRhiCommandBuffer *cb) +{ + QD3D12CommandBuffer *cbD = QRHI_RES(QD3D12CommandBuffer, cb); + cbD->resetPerPassState(); + bindShaderVisibleHeaps(cbD); + if (cbD->currentTarget) { // could be compute, no rendertarget then + QD3D12RenderTargetData *rtD = rtData(cbD->currentTarget); + cbD->cmdList->OMSetRenderTargets(UINT(rtD->colorAttCount), + rtD->rtv, + TRUE, + rtD->dsAttCount ? &rtD->dsv : nullptr); + } +} + +double QRhiD3D12::lastCompletedGpuTime(QRhiCommandBuffer *cb) +{ + Q_UNUSED(cb); + return 0; +} + +QRhi::FrameOpResult QRhiD3D12::beginFrame(QRhiSwapChain *swapChain, QRhi::BeginFrameFlags flags) +{ + Q_UNUSED(flags); + + QD3D12SwapChain *swapChainD = QRHI_RES(QD3D12SwapChain, swapChain); + currentSwapChain = swapChainD; + currentFrameSlot = swapChainD->currentFrameSlot; + QD3D12SwapChain::FrameResources &fr(swapChainD->frameRes[currentFrameSlot]); + + // We could do smarter things but mirror the Vulkan backend for now: Make + // sure the previous commands for this same frame slot have finished. Do + // this also for any other swapchain's commands with the same frame slot. + // While this reduces concurrency in render-to-swapchain-A, + // render-to-swapchain-B, repeat kind of scenarios, it keeps resource usage + // safe: swapchain A starting its frame 0, followed by swapchain B starting + // its own frame 0 will make B wait for A's frame 0 commands. If a resource + // is written in B's frame or when B checks for pending resource releases, + // that won't mess up A's in-flight commands (as they are guaranteed not to + // 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(currentFrameSlot); // note: swapChainD->currentFrameSlot, not sc's + + HRESULT hr = cmdAllocators[currentFrameSlot]->Reset(); + if (FAILED(hr)) { + qWarning("Failed to reset command allocator: %s", + qPrintable(QSystemError::windowsComString(hr))); + return QRhi::FrameOpError; + } + + if (!startCommandListForCurrentFrameSlot(&fr.cmdList)) + return QRhi::FrameOpError; + + QD3D12CommandBuffer *cbD = &swapChainD->cbWrapper; + cbD->cmdList = fr.cmdList; + + swapChainD->rtWrapper.d.rtv[0] = swapChainD->sampleDesc.Count > 1 + ? swapChainD->msaaRtvs[swapChainD->currentBackBufferIndex].cpuHandle + : swapChainD->rtvs[swapChainD->currentBackBufferIndex].cpuHandle; + + swapChainD->rtWrapper.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. + releaseQueue.executeDeferredReleases(currentFrameSlot); + + // Full reset of the command buffer data. + cbD->resetState(); + + // Move the head back to zero for the per-frame shader-visible descriptor heap work areas. + shaderVisibleCbvSrvUavHeap.perFrameHeapSlice[currentFrameSlot].head = 0; + // Same for the small staging area. + smallStagingAreas[currentFrameSlot].head = 0; + + bindShaderVisibleHeaps(cbD); + + finishActiveReadbacks(); // last, in case the readback-completed callback issues rhi calls + + return QRhi::FrameOpSuccess; +} + +QRhi::FrameOpResult QRhiD3D12::endFrame(QRhiSwapChain *swapChain, QRhi::EndFrameFlags flags) +{ + QD3D12SwapChain *swapChainD = QRHI_RES(QD3D12SwapChain, swapChain); + Q_ASSERT(currentSwapChain == swapChainD); + QD3D12CommandBuffer *cbD = &swapChainD->cbWrapper; + + QD3D12ObjectHandle backBufferResourceHandle = swapChainD->colorBuffers[swapChainD->currentBackBufferIndex]; + if (swapChainD->sampleDesc.Count > 1) { + QD3D12ObjectHandle msaaBackBufferResourceHandle = swapChainD->msaaBuffers[swapChainD->currentBackBufferIndex]; + barrierGen.addTransitionBarrier(msaaBackBufferResourceHandle, D3D12_RESOURCE_STATE_RESOLVE_SOURCE); + barrierGen.addTransitionBarrier(backBufferResourceHandle, D3D12_RESOURCE_STATE_RESOLVE_DEST); + barrierGen.enqueueBufferedTransitionBarriers(cbD); + const QD3D12Resource *src = resourcePool.lookupRef(msaaBackBufferResourceHandle); + const QD3D12Resource *dst = resourcePool.lookupRef(backBufferResourceHandle); + if (src && dst) + cbD->cmdList->ResolveSubresource(dst->resource, 0, src->resource, 0, swapChainD->colorFormat); + } + + barrierGen.addTransitionBarrier(backBufferResourceHandle, D3D12_RESOURCE_STATE_PRESENT); + barrierGen.enqueueBufferedTransitionBarriers(cbD); + + ID3D12GraphicsCommandList *cmdList = cbD->cmdList; + HRESULT hr = cmdList->Close(); + if (FAILED(hr)) { + qWarning("Failed to close command list: %s", + qPrintable(QSystemError::windowsComString(hr))); + return QRhi::FrameOpError; + } + + ID3D12CommandList *execList[] = { cmdList }; + cmdQueue->ExecuteCommandLists(1, execList); + + 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; + } + 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()"); + deviceLost = true; + return QRhi::FrameOpDeviceLost; + } else if (FAILED(hr)) { + qWarning("Failed to present: %s", qPrintable(QSystemError::windowsComString(hr))); + return QRhi::FrameOpError; + } + + if (dcompDevice && swapChainD->dcompTarget && swapChainD->dcompVisual) + dcompDevice->Commit(); + } + + swapChainD->addCommandCompletionSignalForCurrentFrameSlot(); + + // NB! The deferred-release mechanism here differs from the older QRhi + // backends. There is no lastActiveFrameSlot tracking. Instead, + // currentFrameSlot is written to the registered entries now, and so the + // resources will get released in the frames_in_flight'th beginFrame() + // counting starting from now. + releaseQueue.activatePendingDeferredReleaseRequests(currentFrameSlot); + + if (!flags.testFlag(QRhi::SkipPresent)) { + // Only move to the next slot if we presented. Otherwise will block and + // wait for completion in the next beginFrame already, but SkipPresent + // should be infrequent anyway. + swapChainD->currentFrameSlot = (swapChainD->currentFrameSlot + 1) % QD3D12_FRAMES_IN_FLIGHT; + swapChainD->currentBackBufferIndex = swapChainD->swapChain->GetCurrentBackBufferIndex(); + } + + currentSwapChain = nullptr; + return QRhi::FrameOpSuccess; +} + +QRhi::FrameOpResult QRhiD3D12::beginOffscreenFrame(QRhiCommandBuffer **cb, QRhi::BeginFrameFlags flags) +{ + Q_UNUSED(flags); + + // Switch to the next slot manually. Swapchains do not know about this + // which is good. So for example an onscreen, onscreen, offscreen, + // onscreen, onscreen, onscreen sequence of frames leads to 0, 1, 0, 0, 1, + // 0. (no strict alternation anymore) But this is not different from what + // happens when multiple swapchains are involved. Offscreen frames are + // synchronous anyway in the sense that they wait for execution to complete + // in endOffscreenFrame, so no resources used in that frame are busy + // anymore in the next frame. + + currentFrameSlot = (currentFrameSlot + 1) % QD3D12_FRAMES_IN_FLIGHT; + + for (QD3D12SwapChain *sc : std::as_const(swapchains)) + sc->waitCommandCompletionForFrameSlot(currentFrameSlot); // note: not sc's currentFrameSlot + + if (!offscreenCb[currentFrameSlot]) + offscreenCb[currentFrameSlot] = new QD3D12CommandBuffer(this); + QD3D12CommandBuffer *cbD = offscreenCb[currentFrameSlot]; + if (!startCommandListForCurrentFrameSlot(&cbD->cmdList)) + return QRhi::FrameOpError; + + releaseQueue.executeDeferredReleases(currentFrameSlot); + cbD->resetState(); + shaderVisibleCbvSrvUavHeap.perFrameHeapSlice[currentFrameSlot].head = 0; + smallStagingAreas[currentFrameSlot].head = 0; + + bindShaderVisibleHeaps(cbD); + + offscreenActive = true; + *cb = cbD; + + return QRhi::FrameOpSuccess; +} + +QRhi::FrameOpResult QRhiD3D12::endOffscreenFrame(QRhi::EndFrameFlags flags) +{ + Q_UNUSED(flags); + Q_ASSERT(offscreenActive); + offscreenActive = false; + + QD3D12CommandBuffer *cbD = offscreenCb[currentFrameSlot]; + ID3D12GraphicsCommandList *cmdList = cbD->cmdList; + HRESULT hr = cmdList->Close(); + if (FAILED(hr)) { + qWarning("Failed to close command list: %s", + qPrintable(QSystemError::windowsComString(hr))); + return QRhi::FrameOpError; + } + + ID3D12CommandList *execList[] = { cmdList }; + cmdQueue->ExecuteCommandLists(1, execList); + + releaseQueue.activatePendingDeferredReleaseRequests(currentFrameSlot); + + // wait for completion + waitGpu(); + + // Here we know that executing the host-side reads for this (or any + // previous) frame is safe since we waited for completion above. + finishActiveReadbacks(true); + + return QRhi::FrameOpSuccess; +} + +QRhi::FrameOpResult QRhiD3D12::finish() +{ + if (!inFrame) + return QRhi::FrameOpSuccess; + + QD3D12CommandBuffer *cbD = nullptr; + if (offscreenActive) { + Q_ASSERT(!currentSwapChain); + cbD = offscreenCb[currentFrameSlot]; + } else { + Q_ASSERT(currentSwapChain); + cbD = ¤tSwapChain->cbWrapper; + } + if (!cbD) + return QRhi::FrameOpError; + + Q_ASSERT(cbD->recordingPass == QD3D12CommandBuffer::NoPass); + + ID3D12GraphicsCommandList *cmdList = cbD->cmdList; + HRESULT hr = cmdList->Close(); + if (FAILED(hr)) { + qWarning("Failed to close command list: %s", + qPrintable(QSystemError::windowsComString(hr))); + return QRhi::FrameOpError; + } + + ID3D12CommandList *execList[] = { cmdList }; + cmdQueue->ExecuteCommandLists(1, execList); + + releaseQueue.activatePendingDeferredReleaseRequests(currentFrameSlot); + + // full blocking wait for everything, frame slots do not matter now + waitGpu(); + + hr = cmdAllocators[currentFrameSlot]->Reset(); + if (FAILED(hr)) { + qWarning("Failed to reset command allocator: %s", + qPrintable(QSystemError::windowsComString(hr))); + return QRhi::FrameOpError; + } + + if (!startCommandListForCurrentFrameSlot(&cmdList)) + return QRhi::FrameOpError; + + cbD->resetState(); + + shaderVisibleCbvSrvUavHeap.perFrameHeapSlice[currentFrameSlot].head = 0; + smallStagingAreas[currentFrameSlot].head = 0; + + bindShaderVisibleHeaps(cbD); + + releaseQueue.executeDeferredReleases(currentFrameSlot); + + finishActiveReadbacks(true); + + return QRhi::FrameOpSuccess; +} + +void QRhiD3D12::resourceUpdate(QRhiCommandBuffer *cb, QRhiResourceUpdateBatch *resourceUpdates) +{ + QD3D12CommandBuffer *cbD = QRHI_RES(QD3D12CommandBuffer, cb); + Q_ASSERT(cbD->recordingPass == QD3D12CommandBuffer::NoPass); + enqueueResourceUpdates(cbD, resourceUpdates); +} + +void QRhiD3D12::beginPass(QRhiCommandBuffer *cb, + QRhiRenderTarget *rt, + const QColor &colorClearValue, + const QRhiDepthStencilClearValue &depthStencilClearValue, + QRhiResourceUpdateBatch *resourceUpdates, + QRhiCommandBuffer::BeginPassFlags) +{ + QD3D12CommandBuffer *cbD = QRHI_RES(QD3D12CommandBuffer, cb); + Q_ASSERT(cbD->recordingPass == QD3D12CommandBuffer::NoPass); + + if (resourceUpdates) + enqueueResourceUpdates(cbD, resourceUpdates); + + QD3D12RenderTargetData *rtD = rtData(rt); + bool wantsColorClear = true; + bool wantsDsClear = true; + if (rt->resourceType() == QRhiRenderTarget::TextureRenderTarget) { + QD3D12TextureRenderTarget *rtTex = QRHI_RES(QD3D12TextureRenderTarget, rt); + wantsColorClear = !rtTex->m_flags.testFlag(QRhiTextureRenderTarget::PreserveColorContents); + wantsDsClear = !rtTex->m_flags.testFlag(QRhiTextureRenderTarget::PreserveDepthStencilContents); + if (!QRhiRenderTargetAttachmentTracker::isUpToDate(rtTex->description(), rtD->currentResIdList)) + rtTex->create(); + + for (auto it = rtTex->m_desc.cbeginColorAttachments(), itEnd = rtTex->m_desc.cendColorAttachments(); it != itEnd; ++it) { + QD3D12Texture *texD = QRHI_RES(QD3D12Texture, it->texture()); + QD3D12Texture *resolveTexD = QRHI_RES(QD3D12Texture, it->resolveTexture()); + QD3D12RenderBuffer *rbD = QRHI_RES(QD3D12RenderBuffer, it->renderBuffer()); + if (texD) + barrierGen.addTransitionBarrier(texD->handle, D3D12_RESOURCE_STATE_RENDER_TARGET); + else if (rbD) + barrierGen.addTransitionBarrier(rbD->handle, D3D12_RESOURCE_STATE_RENDER_TARGET); + if (resolveTexD) + barrierGen.addTransitionBarrier(resolveTexD->handle, D3D12_RESOURCE_STATE_RENDER_TARGET); + } + if (rtTex->m_desc.depthStencilBuffer()) { + QD3D12RenderBuffer *rbD = QRHI_RES(QD3D12RenderBuffer, rtTex->m_desc.depthStencilBuffer()); + Q_ASSERT(rbD->m_type == QRhiRenderBuffer::DepthStencil); + barrierGen.addTransitionBarrier(rbD->handle, D3D12_RESOURCE_STATE_DEPTH_WRITE); + } else if (rtTex->m_desc.depthTexture()) { + QD3D12Texture *depthTexD = QRHI_RES(QD3D12Texture, rtTex->m_desc.depthTexture()); + barrierGen.addTransitionBarrier(depthTexD->handle, D3D12_RESOURCE_STATE_DEPTH_WRITE); + } + barrierGen.enqueueBufferedTransitionBarriers(cbD); + } else { + Q_ASSERT(currentSwapChain); + barrierGen.addTransitionBarrier(currentSwapChain->sampleDesc.Count > 1 + ? currentSwapChain->msaaBuffers[currentSwapChain->currentBackBufferIndex] + : currentSwapChain->colorBuffers[currentSwapChain->currentBackBufferIndex], + D3D12_RESOURCE_STATE_RENDER_TARGET); + barrierGen.enqueueBufferedTransitionBarriers(cbD); + } + + cbD->cmdList->OMSetRenderTargets(UINT(rtD->colorAttCount), + rtD->rtv, + TRUE, + rtD->dsAttCount ? &rtD->dsv : nullptr); + + if (rtD->colorAttCount && wantsColorClear) { + float clearColor[4] = { + colorClearValue.redF(), + colorClearValue.greenF(), + colorClearValue.blueF(), + colorClearValue.alphaF() + }; + for (int i = 0; i < rtD->colorAttCount; ++i) + cbD->cmdList->ClearRenderTargetView(rtD->rtv[i], clearColor, 0, nullptr); + } + if (rtD->dsAttCount && wantsDsClear) { + cbD->cmdList->ClearDepthStencilView(rtD->dsv, + D3D12_CLEAR_FLAGS(D3D12_CLEAR_FLAG_DEPTH | D3D12_CLEAR_FLAG_STENCIL), + depthStencilClearValue.depthClearValue(), + UINT8(depthStencilClearValue.stencilClearValue()), + 0, + nullptr); + } + + cbD->recordingPass = QD3D12CommandBuffer::RenderPass; + cbD->currentTarget = rt; + + cbD->resetPerPassState(); +} + +void QRhiD3D12::endPass(QRhiCommandBuffer *cb, QRhiResourceUpdateBatch *resourceUpdates) +{ + QD3D12CommandBuffer *cbD = QRHI_RES(QD3D12CommandBuffer, cb); + Q_ASSERT(cbD->recordingPass == QD3D12CommandBuffer::RenderPass); + + if (cbD->currentTarget->resourceType() == QRhiResource::TextureRenderTarget) { + QD3D12TextureRenderTarget *rtTex = QRHI_RES(QD3D12TextureRenderTarget, cbD->currentTarget); + for (auto it = rtTex->m_desc.cbeginColorAttachments(), itEnd = rtTex->m_desc.cendColorAttachments(); + it != itEnd; ++it) + { + const QRhiColorAttachment &colorAtt(*it); + if (!colorAtt.resolveTexture()) + continue; + + QD3D12Texture *dstTexD = QRHI_RES(QD3D12Texture, colorAtt.resolveTexture()); + QD3D12Resource *dstRes = resourcePool.lookupRef(dstTexD->handle); + if (!dstRes) + continue; + + QD3D12Texture *srcTexD = QRHI_RES(QD3D12Texture, colorAtt.texture()); + QD3D12RenderBuffer *srcRbD = QRHI_RES(QD3D12RenderBuffer, colorAtt.renderBuffer()); + Q_ASSERT(srcTexD || srcRbD); + QD3D12Resource *srcRes = resourcePool.lookupRef(srcTexD ? srcTexD->handle : srcRbD->handle); + if (!srcRes) + continue; + + if (srcTexD) { + if (srcTexD->dxgiFormat != dstTexD->dxgiFormat) { + qWarning("Resolve source (%d) and destination (%d) formats do not match", + int(srcTexD->dxgiFormat), int(dstTexD->dxgiFormat)); + continue; + } + if (srcTexD->sampleDesc.Count <= 1) { + qWarning("Cannot resolve a non-multisample texture"); + continue; + } + if (srcTexD->m_pixelSize != dstTexD->m_pixelSize) { + qWarning("Resolve source and destination sizes do not match"); + continue; + } + } else { + if (srcRbD->dxgiFormat != dstTexD->dxgiFormat) { + qWarning("Resolve source (%d) and destination (%d) formats do not match", + int(srcRbD->dxgiFormat), int(dstTexD->dxgiFormat)); + continue; + } + if (srcRbD->m_pixelSize != dstTexD->m_pixelSize) { + qWarning("Resolve source and destination sizes do not match"); + continue; + } + } + + barrierGen.addTransitionBarrier(srcTexD ? srcTexD->handle : srcRbD->handle, D3D12_RESOURCE_STATE_RESOLVE_SOURCE); + 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); + } + + } + + cbD->recordingPass = QD3D12CommandBuffer::NoPass; + cbD->currentTarget = nullptr; + + if (resourceUpdates) + enqueueResourceUpdates(cbD, resourceUpdates); +} + +void QRhiD3D12::beginComputePass(QRhiCommandBuffer *cb, + QRhiResourceUpdateBatch *resourceUpdates, + QRhiCommandBuffer::BeginPassFlags) +{ + QD3D12CommandBuffer *cbD = QRHI_RES(QD3D12CommandBuffer, cb); + Q_ASSERT(cbD->recordingPass == QD3D12CommandBuffer::NoPass); + + if (resourceUpdates) + enqueueResourceUpdates(cbD, resourceUpdates); + + cbD->recordingPass = QD3D12CommandBuffer::ComputePass; + + cbD->resetPerPassState(); +} + +void QRhiD3D12::endComputePass(QRhiCommandBuffer *cb, QRhiResourceUpdateBatch *resourceUpdates) +{ + QD3D12CommandBuffer *cbD = QRHI_RES(QD3D12CommandBuffer, cb); + Q_ASSERT(cbD->recordingPass == QD3D12CommandBuffer::ComputePass); + + cbD->recordingPass = QD3D12CommandBuffer::NoPass; + + if (resourceUpdates) + enqueueResourceUpdates(cbD, resourceUpdates); +} + +void QRhiD3D12::setComputePipeline(QRhiCommandBuffer *cb, QRhiComputePipeline *ps) +{ + QD3D12CommandBuffer *cbD = QRHI_RES(QD3D12CommandBuffer, cb); + Q_ASSERT(cbD->recordingPass == QD3D12CommandBuffer::ComputePass); + QD3D12ComputePipeline *psD = QRHI_RES(QD3D12ComputePipeline, ps); + const bool pipelineChanged = cbD->currentComputePipeline != psD || cbD->currentPipelineGeneration != psD->generation; + + if (pipelineChanged) { + cbD->currentGraphicsPipeline = nullptr; + cbD->currentComputePipeline = psD; + cbD->currentPipelineGeneration = psD->generation; + + if (QD3D12Pipeline *pipeline = pipelinePool.lookupRef(psD->handle)) { + Q_ASSERT(pipeline->type == QD3D12Pipeline::Compute); + cbD->cmdList->SetPipelineState(pipeline->pso); + if (QD3D12RootSignature *rs = rootSignaturePool.lookupRef(psD->rootSigHandle)) + cbD->cmdList->SetComputeRootSignature(rs->rootSig); + } + } +} + +void QRhiD3D12::dispatch(QRhiCommandBuffer *cb, int x, int y, int z) +{ + QD3D12CommandBuffer *cbD = QRHI_RES(QD3D12CommandBuffer, cb); + Q_ASSERT(cbD->recordingPass == QD3D12CommandBuffer::ComputePass); + cbD->cmdList->Dispatch(UINT(x), UINT(y), UINT(z)); +} + +bool QD3D12DescriptorHeap::create(ID3D12Device *device, + quint32 descriptorCount, + D3D12_DESCRIPTOR_HEAP_TYPE heapType, + D3D12_DESCRIPTOR_HEAP_FLAGS heapFlags) +{ + head = 0; + capacity = descriptorCount; + this->heapType = heapType; + this->heapFlags = heapFlags; + + D3D12_DESCRIPTOR_HEAP_DESC heapDesc = {}; + heapDesc.Type = heapType; + heapDesc.NumDescriptors = capacity; + heapDesc.Flags = D3D12_DESCRIPTOR_HEAP_FLAGS(heapFlags); + + HRESULT hr = device->CreateDescriptorHeap(&heapDesc, __uuidof(ID3D12DescriptorHeap), reinterpret_cast(&heap)); + if (FAILED(hr)) { + qWarning("Failed to create descriptor heap: %s", qPrintable(QSystemError::windowsComString(hr))); + heap = nullptr; + capacity = descriptorByteSize = 0; + return false; + } + + descriptorByteSize = device->GetDescriptorHandleIncrementSize(heapType); + heapStart.cpuHandle = heap->GetCPUDescriptorHandleForHeapStart(); + if (heapFlags & D3D12_DESCRIPTOR_HEAP_FLAG_SHADER_VISIBLE) + heapStart.gpuHandle = heap->GetGPUDescriptorHandleForHeapStart(); + + return true; +} + +void QD3D12DescriptorHeap::createWithExisting(const QD3D12DescriptorHeap &other, + quint32 offsetInDescriptors, + quint32 descriptorCount) +{ + heap = nullptr; + head = 0; + capacity = descriptorCount; + heapType = other.heapType; + heapFlags = other.heapFlags; + descriptorByteSize = other.descriptorByteSize; + heapStart = incremented(other.heapStart, offsetInDescriptors); +} + +void QD3D12DescriptorHeap::destroy() +{ + if (heap) { + heap->Release(); + heap = nullptr; + } + capacity = 0; +} + +void QD3D12DescriptorHeap::destroyWithDeferredRelease(QD3D12ReleaseQueue *releaseQueue) +{ + if (heap) { + releaseQueue->deferredReleaseDescriptorHeap(heap); + heap = nullptr; + } + capacity = 0; +} + +QD3D12Descriptor QD3D12DescriptorHeap::get(quint32 count) +{ + Q_ASSERT(count > 0); + if (head + count > capacity) { + qWarning("Cannot get %u descriptors as that would exceed capacity %u", count, capacity); + return {}; + } + head += count; + return at(head - count); +} + +QD3D12Descriptor QD3D12DescriptorHeap::at(quint32 index) const +{ + const quint32 startOffset = index * descriptorByteSize; + QD3D12Descriptor result; + result.cpuHandle.ptr = heapStart.cpuHandle.ptr + startOffset; + if (heapStart.gpuHandle.ptr != 0) + result.gpuHandle.ptr = heapStart.gpuHandle.ptr + startOffset; + return result; +} + +bool QD3D12CpuDescriptorPool::create(ID3D12Device *device, D3D12_DESCRIPTOR_HEAP_TYPE heapType, const char *debugName) +{ + QD3D12DescriptorHeap firstHeap; + if (!firstHeap.create(device, DESCRIPTORS_PER_HEAP, heapType, D3D12_DESCRIPTOR_HEAP_FLAG_NONE)) + return false; + heaps.append(HeapWithMap::init(firstHeap, DESCRIPTORS_PER_HEAP)); + descriptorByteSize = heaps[0].heap.descriptorByteSize; + this->device = device; + this->debugName = debugName; + return true; +} + +void QD3D12CpuDescriptorPool::destroy() +{ +#ifndef QT_NO_DEBUG + // debug builds: just do it always + static bool leakCheck = true; +#else + // release builds: opt-in + static bool leakCheck = qEnvironmentVariableIntValue("QT_RHI_LEAK_CHECK"); +#endif + if (leakCheck) { + for (HeapWithMap &heap : heaps) { + const int leakedDescriptorCount = heap.map.count(true); + if (leakedDescriptorCount > 0) { + qWarning("QD3D12CpuDescriptorPool::destroy(): " + "Heap %p for descriptor pool %p '%s' has %d unreleased descriptors", + &heap.heap, this, debugName, leakedDescriptorCount); + } + } + } + for (HeapWithMap &heap : heaps) + heap.heap.destroy(); + heaps.clear(); +} + +QD3D12Descriptor QD3D12CpuDescriptorPool::allocate(quint32 count) +{ + Q_ASSERT(count > 0 && count <= DESCRIPTORS_PER_HEAP); + + HeapWithMap &last(heaps.last()); + if (last.heap.head + count <= last.heap.capacity) { + quint32 firstIndex = last.heap.head; + for (quint32 i = 0; i < count; ++i) + last.map.setBit(firstIndex + i); + return last.heap.get(count); + } + + for (HeapWithMap &heap : heaps) { + quint32 freeCount = 0; + for (quint32 i = 0; i < DESCRIPTORS_PER_HEAP; ++i) { + if (heap.map.testBit(i)) { + freeCount = 0; + } else { + freeCount += 1; + if (freeCount == count) { + quint32 firstIndex = i - (freeCount - 1); + for (quint32 j = 0; j < count; ++j) { + heap.map.setBit(firstIndex + j); + return heap.heap.at(firstIndex); + } + } + } + } + } + + QD3D12DescriptorHeap newHeap; + if (!newHeap.create(device, DESCRIPTORS_PER_HEAP, last.heap.heapType, last.heap.heapFlags)) + return {}; + + heaps.append(HeapWithMap::init(newHeap, DESCRIPTORS_PER_HEAP)); + + for (quint32 i = 0; i < count; ++i) + heaps.last().map.setBit(i); + + return heaps.last().heap.get(count); +} + +void QD3D12CpuDescriptorPool::release(const QD3D12Descriptor &descriptor, quint32 count) +{ + Q_ASSERT(count > 0 && count <= DESCRIPTORS_PER_HEAP); + if (!descriptor.isValid()) + return; + + const SIZE_T addr = descriptor.cpuHandle.ptr; + for (HeapWithMap &heap : heaps) { + const SIZE_T begin = heap.heap.heapStart.cpuHandle.ptr; + const SIZE_T end = begin + heap.heap.descriptorByteSize * heap.heap.capacity; + if (addr >= begin && addr < end) { + quint32 firstIndex = (addr - begin) / heap.heap.descriptorByteSize; + for (quint32 i = 0; i < count; ++i) + heap.map.setBit(firstIndex + i, false); + return; + } + } + + qWarning("QD3D12CpuDescriptorPool::release: Descriptor with address %llu is not in any heap", + quint64(descriptor.cpuHandle.ptr)); +} + +bool QD3D12StagingArea::create(QRhiD3D12 *rhi, quint32 capacity, D3D12_HEAP_TYPE heapType) +{ + Q_ASSERT(heapType == D3D12_HEAP_TYPE_UPLOAD || heapType == D3D12_HEAP_TYPE_READBACK); + D3D12_RESOURCE_DESC resourceDesc = {}; + resourceDesc.Dimension = D3D12_RESOURCE_DIMENSION_BUFFER; + resourceDesc.Width = capacity; + resourceDesc.Height = 1; + resourceDesc.DepthOrArraySize = 1; + resourceDesc.MipLevels = 1; + resourceDesc.Format = DXGI_FORMAT_UNKNOWN; + resourceDesc.SampleDesc = { 1, 0 }; + resourceDesc.Layout = D3D12_TEXTURE_LAYOUT_ROW_MAJOR; + resourceDesc.Flags = D3D12_RESOURCE_FLAG_NONE; + UINT state = heapType == D3D12_HEAP_TYPE_UPLOAD ? D3D12_RESOURCE_STATE_GENERIC_READ : D3D12_RESOURCE_STATE_COPY_DEST; + HRESULT hr = rhi->vma.createResource(heapType, + &resourceDesc, + D3D12_RESOURCE_STATES(state), + nullptr, + &allocation, + __uuidof(ID3D12Resource), + reinterpret_cast(&resource)); + if (FAILED(hr)) { + qWarning("Failed to create buffer for staging area: %s", + qPrintable(QSystemError::windowsComString(hr))); + return false; + } + void *p = nullptr; + hr = resource->Map(0, nullptr, &p); + if (FAILED(hr)) { + qWarning("Failed to map buffer for staging area: %s", + qPrintable(QSystemError::windowsComString(hr))); + destroy(); + return false; + } + + mem.p = static_cast(p); + mem.gpuAddr = resource->GetGPUVirtualAddress(); + mem.buffer = resource; + mem.bufferOffset = 0; + + this->capacity = capacity; + head = 0; + + return true; +} + +void QD3D12StagingArea::destroy() +{ + if (resource) { + resource->Release(); + resource = nullptr; + } + if (allocation) { + allocation->Release(); + allocation = nullptr; + } + mem = {}; +} + +void QD3D12StagingArea::destroyWithDeferredRelease(QD3D12ReleaseQueue *releaseQueue) +{ + if (resource) + releaseQueue->deferredReleaseResourceAndAllocation(resource, allocation); + mem = {}; +} + +QD3D12StagingArea::Allocation QD3D12StagingArea::get(quint32 byteSize) +{ + const quint32 allocSize = aligned(byteSize, ALIGNMENT); + if (head + allocSize > capacity) { + qWarning("Failed to allocate %u (%u) bytes from staging area of size %u with %u bytes left", + allocSize, byteSize, capacity, remainingCapacity()); + return {}; + } + const quint32 offset = head; + head += allocSize; + return { + mem.p + offset, + mem.gpuAddr + offset, + mem.buffer, + offset + }; +} + +// Can be called inside and outside of begin-endFrame. Removes from the pool +// and releases the underlying native resource only in the frames_in_flight'th +// beginFrame() counted starting from the next endFrame(). +void QD3D12ReleaseQueue::deferredReleaseResource(const QD3D12ObjectHandle &handle) +{ + DeferredReleaseEntry e; + e.handle = handle; + queue.append(e); +} + +void QD3D12ReleaseQueue::deferredReleaseResourceWithViews(const QD3D12ObjectHandle &handle, + QD3D12CpuDescriptorPool *pool, + const QD3D12Descriptor &viewsStart, + int viewCount) +{ + DeferredReleaseEntry e; + e.type = DeferredReleaseEntry::Resource; + e.handle = handle; + e.poolForViews = pool; + e.viewsStart = viewsStart; + e.viewCount = viewCount; + queue.append(e); +} + +void QD3D12ReleaseQueue::deferredReleasePipeline(const QD3D12ObjectHandle &handle) +{ + DeferredReleaseEntry e; + e.type = DeferredReleaseEntry::Pipeline; + e.handle = handle; + queue.append(e); +} + +void QD3D12ReleaseQueue::deferredReleaseRootSignature(const QD3D12ObjectHandle &handle) +{ + DeferredReleaseEntry e; + e.type = DeferredReleaseEntry::RootSignature; + e.handle = handle; + queue.append(e); +} + +void QD3D12ReleaseQueue::deferredReleaseCallback(std::function callback, void *userData) +{ + DeferredReleaseEntry e; + e.type = DeferredReleaseEntry::Callback; + e.callback = callback; + e.callbackUserData = userData; + queue.append(e); +} + +void QD3D12ReleaseQueue::deferredReleaseResourceAndAllocation(ID3D12Resource *resource, + D3D12MA::Allocation *allocation) +{ + DeferredReleaseEntry e; + e.type = DeferredReleaseEntry::ResourceAndAllocation; + e.resourceAndAllocation = { resource, allocation }; + queue.append(e); +} + +void QD3D12ReleaseQueue::deferredReleaseDescriptorHeap(ID3D12DescriptorHeap *heap) +{ + DeferredReleaseEntry e; + e.type = DeferredReleaseEntry::DescriptorHeap; + e.descriptorHeap = heap; + queue.append(e); +} + +void QD3D12ReleaseQueue::deferredReleaseViews(QD3D12CpuDescriptorPool *pool, + const QD3D12Descriptor &viewsStart, + int viewCount) +{ + DeferredReleaseEntry e; + e.type = DeferredReleaseEntry::Views; + e.poolForViews = pool; + e.viewsStart = viewsStart; + e.viewCount = viewCount; + queue.append(e); +} + +void QD3D12ReleaseQueue::activatePendingDeferredReleaseRequests(int frameSlot) +{ + for (DeferredReleaseEntry &e : queue) { + if (!e.frameSlotToBeReleasedIn.has_value()) + e.frameSlotToBeReleasedIn = frameSlot; + } +} + +void QD3D12ReleaseQueue::executeDeferredReleases(int frameSlot, bool forced) +{ + for (int i = queue.count() - 1; i >= 0; --i) { + const DeferredReleaseEntry &e(queue[i]); + if (forced || (e.frameSlotToBeReleasedIn.has_value() && e.frameSlotToBeReleasedIn.value() == frameSlot)) { + switch (e.type) { + case DeferredReleaseEntry::Resource: + resourcePool->remove(e.handle); + if (e.poolForViews && e.viewsStart.isValid() && e.viewCount > 0) + e.poolForViews->release(e.viewsStart, e.viewCount); + break; + case DeferredReleaseEntry::Pipeline: + pipelinePool->remove(e.handle); + break; + case DeferredReleaseEntry::RootSignature: + rootSignaturePool->remove(e.handle); + break; + case DeferredReleaseEntry::Callback: + e.callback(e.callbackUserData); + break; + case DeferredReleaseEntry::ResourceAndAllocation: + // order matters: resource first, then the allocation (which + // may be null) + e.resourceAndAllocation.first->Release(); + if (e.resourceAndAllocation.second) + e.resourceAndAllocation.second->Release(); + break; + case DeferredReleaseEntry::DescriptorHeap: + e.descriptorHeap->Release(); + break; + case DeferredReleaseEntry::Views: + e.poolForViews->release(e.viewsStart, e.viewCount); + break; + } + queue.removeAt(i); + } + } +} + +void QD3D12ReleaseQueue::releaseAll() +{ + executeDeferredReleases(0, true); +} + +void QD3D12ResourceBarrierGenerator::addTransitionBarrier(const QD3D12ObjectHandle &resourceHandle, + D3D12_RESOURCE_STATES stateAfter) +{ + if (QD3D12Resource *res = resourcePool->lookupRef(resourceHandle)) { + if (stateAfter != res->state) { + transitionResourceBarriers.append({ resourceHandle, res->state, stateAfter }); + res->state = stateAfter; + } + } +} + +void QD3D12ResourceBarrierGenerator::enqueueBufferedTransitionBarriers(QD3D12CommandBuffer *cbD) +{ + QVarLengthArray barriers; + for (const TransitionResourceBarrier &trb : transitionResourceBarriers) { + if (QD3D12Resource *res = resourcePool->lookupRef(trb.resourceHandle)) { + D3D12_RESOURCE_BARRIER barrier = {}; + barrier.Type = D3D12_RESOURCE_BARRIER_TYPE_TRANSITION; + barrier.Flags = D3D12_RESOURCE_BARRIER_FLAG_NONE; + barrier.Transition.pResource = res->resource; + barrier.Transition.Subresource = D3D12_RESOURCE_BARRIER_ALL_SUBRESOURCES; + barrier.Transition.StateBefore = trb.stateBefore; + barrier.Transition.StateAfter = trb.stateAfter; + barriers.append(barrier); + } + } + transitionResourceBarriers.clear(); + if (!barriers.isEmpty()) + cbD->cmdList->ResourceBarrier(barriers.count(), barriers.constData()); +} + +void QD3D12ResourceBarrierGenerator::enqueueSubresourceTransitionBarrier(QD3D12CommandBuffer *cbD, + const QD3D12ObjectHandle &resourceHandle, + UINT subresource, + D3D12_RESOURCE_STATES stateBefore, + D3D12_RESOURCE_STATES stateAfter) +{ + if (QD3D12Resource *res = resourcePool->lookupRef(resourceHandle)) { + D3D12_RESOURCE_BARRIER barrier = {}; + barrier.Type = D3D12_RESOURCE_BARRIER_TYPE_TRANSITION; + barrier.Flags = D3D12_RESOURCE_BARRIER_FLAG_NONE; + barrier.Transition.pResource = res->resource; + barrier.Transition.Subresource = subresource; + barrier.Transition.StateBefore = stateBefore; + barrier.Transition.StateAfter = stateAfter; + cbD->cmdList->ResourceBarrier(1, &barrier); + } +} + +void QD3D12ResourceBarrierGenerator::enqueueUavBarrier(QD3D12CommandBuffer *cbD, + const QD3D12ObjectHandle &resourceHandle) +{ + if (QD3D12Resource *res = resourcePool->lookupRef(resourceHandle)) { + D3D12_RESOURCE_BARRIER barrier = {}; + barrier.Type = D3D12_RESOURCE_BARRIER_TYPE_UAV; + barrier.Flags = D3D12_RESOURCE_BARRIER_FLAG_NONE; + barrier.UAV.pResource = res->resource; + cbD->cmdList->ResourceBarrier(1, &barrier); + } +} + +void QD3D12ShaderBytecodeCache::insertWithCapacityLimit(const QRhiShaderStage &key, const Shader &s) +{ + if (data.count() >= QRhiD3D12::MAX_SHADER_CACHE_ENTRIES) + data.clear(); + data.insert(key, s); +} + +bool QD3D12ShaderVisibleDescriptorHeap::create(ID3D12Device *device, + D3D12_DESCRIPTOR_HEAP_TYPE type, + quint32 perFrameDescriptorCount) +{ + Q_ASSERT(type == D3D12_DESCRIPTOR_HEAP_TYPE_CBV_SRV_UAV || type == D3D12_DESCRIPTOR_HEAP_TYPE_SAMPLER); + + quint32 size = perFrameDescriptorCount * QD3D12_FRAMES_IN_FLIGHT; + + // https://learn.microsoft.com/en-us/windows/win32/direct3d12/hardware-support + const quint32 CBV_SRV_UAV_MAX = 1000000; + const quint32 SAMPLER_MAX = 2048; + if (type == D3D12_DESCRIPTOR_HEAP_TYPE_CBV_SRV_UAV) + size = qMin(size, CBV_SRV_UAV_MAX); + else if (type == D3D12_DESCRIPTOR_HEAP_TYPE_SAMPLER) + size = qMin(size, SAMPLER_MAX); + + if (!heap.create(device, size, type, D3D12_DESCRIPTOR_HEAP_FLAG_SHADER_VISIBLE)) { + qWarning("Failed to create shader-visible descriptor heap of size %u", size); + return false; + } + + perFrameDescriptorCount = size / QD3D12_FRAMES_IN_FLIGHT; + quint32 currentOffsetInDescriptors = 0; + for (int i = 0; i < QD3D12_FRAMES_IN_FLIGHT; ++i) { + perFrameHeapSlice[i].createWithExisting(heap, currentOffsetInDescriptors, perFrameDescriptorCount); + currentOffsetInDescriptors += perFrameDescriptorCount; + } + + return true; +} + +void QD3D12ShaderVisibleDescriptorHeap::destroy() +{ + heap.destroy(); +} + +void QD3D12ShaderVisibleDescriptorHeap::destroyWithDeferredRelease(QD3D12ReleaseQueue *releaseQueue) +{ + heap.destroyWithDeferredRelease(releaseQueue); +} + +static inline QPair mapBinding(int binding, const QShader::NativeResourceBindingMap &map) +{ + if (map.isEmpty()) + return { binding, binding }; // assume 1:1 mapping + + auto it = map.constFind(binding); + if (it != map.cend()) + return *it; + + // Hitting this path is normal too. It is not given that the resource is + // present in the shaders for all the stages specified by the visibility + // mask in the QRhiShaderResourceBinding. + return { -1, -1 }; +} + +void QD3D12ShaderResourceVisitor::visit() +{ + for (int bindingIdx = 0, bindingCount = srb->sortedBindings.count(); bindingIdx != bindingCount; ++bindingIdx) { + const QRhiShaderResourceBinding &b(srb->sortedBindings[bindingIdx]); + const QRhiShaderResourceBinding::Data *bd = QRhiImplementation::shaderResourceBindingData(b); + + for (int stageIdx = 0; stageIdx < stageCount; ++stageIdx) { + const QD3D12ShaderStageData *sd = &stageData[stageIdx]; + if (!sd->valid) + continue; + + if (!bd->stage.testFlag(qd3d12_stageToSrb(sd->stage))) + continue; + + switch (bd->type) { + case QRhiShaderResourceBinding::UniformBuffer: + { + const int shaderRegister = mapBinding(bd->binding, sd->nativeResourceBindingMap).first; + if (shaderRegister >= 0 && uniformBuffer) + uniformBuffer(sd->stage, bd->u.ubuf, shaderRegister, bd->binding); + } + break; + case QRhiShaderResourceBinding::SampledTexture: + { + Q_ASSERT(bd->u.stex.count > 0); + const int textureBaseShaderRegister = mapBinding(bd->binding, sd->nativeResourceBindingMap).first; + const int samplerBaseShaderRegister = mapBinding(bd->binding, sd->nativeResourceBindingMap).second; + for (int i = 0; i < bd->u.stex.count; ++i) { + if (textureBaseShaderRegister >= 0 && texture) + texture(sd->stage, bd->u.stex.texSamplers[i], textureBaseShaderRegister + i); + if (samplerBaseShaderRegister >= 0 && sampler) + sampler(sd->stage, bd->u.stex.texSamplers[i], samplerBaseShaderRegister + i); + } + } + break; + case QRhiShaderResourceBinding::Texture: + { + Q_ASSERT(bd->u.stex.count > 0); + const int baseShaderRegister = mapBinding(bd->binding, sd->nativeResourceBindingMap).first; + if (baseShaderRegister >= 0 && texture) { + for (int i = 0; i < bd->u.stex.count; ++i) + texture(sd->stage, bd->u.stex.texSamplers[i], baseShaderRegister + i); + } + } + break; + case QRhiShaderResourceBinding::Sampler: + { + Q_ASSERT(bd->u.stex.count > 0); + const int baseShaderRegister = mapBinding(bd->binding, sd->nativeResourceBindingMap).first; + if (baseShaderRegister >= 0 && sampler) { + for (int i = 0; i < bd->u.stex.count; ++i) + sampler(sd->stage, bd->u.stex.texSamplers[i], baseShaderRegister + i); + } + } + break; + case QRhiShaderResourceBinding::ImageLoad: + { + const int shaderRegister = mapBinding(bd->binding, sd->nativeResourceBindingMap).first; + if (shaderRegister >= 0 && storageImage) + storageImage(sd->stage, bd->u.simage, Load, shaderRegister); + } + break; + case QRhiShaderResourceBinding::ImageStore: + { + const int shaderRegister = mapBinding(bd->binding, sd->nativeResourceBindingMap).first; + if (shaderRegister >= 0 && storageImage) + storageImage(sd->stage, bd->u.simage, Store, shaderRegister); + } + break; + case QRhiShaderResourceBinding::ImageLoadStore: + { + const int shaderRegister = mapBinding(bd->binding, sd->nativeResourceBindingMap).first; + if (shaderRegister >= 0 && storageImage) + storageImage(sd->stage, bd->u.simage, LoadStore, shaderRegister); + } + break; + case QRhiShaderResourceBinding::BufferLoad: + { + const int shaderRegister = mapBinding(bd->binding, sd->nativeResourceBindingMap).first; + if (shaderRegister >= 0 && storageBuffer) + storageBuffer(sd->stage, bd->u.sbuf, Load, shaderRegister); + } + break; + case QRhiShaderResourceBinding::BufferStore: + { + const int shaderRegister = mapBinding(bd->binding, sd->nativeResourceBindingMap).first; + if (shaderRegister >= 0 && storageBuffer) + storageBuffer(sd->stage, bd->u.sbuf, Store, shaderRegister); + } + break; + case QRhiShaderResourceBinding::BufferLoadStore: + { + const int shaderRegister = mapBinding(bd->binding, sd->nativeResourceBindingMap).first; + if (shaderRegister >= 0 && storageBuffer) + storageBuffer(sd->stage, bd->u.sbuf, LoadStore, shaderRegister); + } + break; + } + } + } +} + +bool QD3D12SamplerManager::create(ID3D12Device *device) +{ + // This does not need to be per-frame slot, just grab space for MAX_SAMPLERS samplers. + if (!shaderVisibleSamplerHeap.create(device, + D3D12_DESCRIPTOR_HEAP_TYPE_SAMPLER, + MAX_SAMPLERS / QD3D12_FRAMES_IN_FLIGHT)) + { + qWarning("Could not create shader-visible SAMPLER heap"); + return false; + } + + this->device = device; + return true; +} + +void QD3D12SamplerManager::destroy() +{ + if (device) { + shaderVisibleSamplerHeap.destroy(); + device = nullptr; + } +} + +QD3D12Descriptor QD3D12SamplerManager::getShaderVisibleDescriptor(const D3D12_SAMPLER_DESC &desc) +{ + auto it = gpuMap.constFind({desc}); + if (it != gpuMap.cend()) + return *it; + + QD3D12Descriptor descriptor = shaderVisibleSamplerHeap.heap.get(1); + if (descriptor.isValid()) { + device->CreateSampler(&desc, descriptor.cpuHandle); + gpuMap.insert({desc}, descriptor); + } else { + qWarning("Out of shader-visible SAMPLER descriptor heap space," + " this should not happen, maximum number of unique samplers is %u", + shaderVisibleSamplerHeap.heap.capacity); + } + + return descriptor; +} + +bool QD3D12MipmapGenerator::create(QRhiD3D12 *rhiD) +{ + this->rhiD = rhiD; + + D3D12_ROOT_PARAMETER1 rootParams[3] = {}; + D3D12_DESCRIPTOR_RANGE1 descriptorRanges[2] = {}; + + // b0 + rootParams[0].ParameterType = D3D12_ROOT_PARAMETER_TYPE_CBV; + rootParams[0].ShaderVisibility = D3D12_SHADER_VISIBILITY_ALL; + + // t0 + descriptorRanges[0].RangeType = D3D12_DESCRIPTOR_RANGE_TYPE_SRV; + descriptorRanges[0].NumDescriptors = 1; + descriptorRanges[0].Flags = D3D12_DESCRIPTOR_RANGE_FLAG_DATA_VOLATILE; + rootParams[1].ParameterType = D3D12_ROOT_PARAMETER_TYPE_DESCRIPTOR_TABLE; + rootParams[1].ShaderVisibility = D3D12_SHADER_VISIBILITY_ALL; + rootParams[1].DescriptorTable.NumDescriptorRanges = 1; + rootParams[1].DescriptorTable.pDescriptorRanges = &descriptorRanges[0]; + + // u0..3 + descriptorRanges[1].RangeType = D3D12_DESCRIPTOR_RANGE_TYPE_UAV; + descriptorRanges[1].NumDescriptors = 4; + rootParams[2].ParameterType = D3D12_ROOT_PARAMETER_TYPE_DESCRIPTOR_TABLE; + rootParams[2].ShaderVisibility = D3D12_SHADER_VISIBILITY_ALL; + rootParams[2].DescriptorTable.NumDescriptorRanges = 1; + rootParams[2].DescriptorTable.pDescriptorRanges = &descriptorRanges[1]; + + // s0 + D3D12_STATIC_SAMPLER_DESC samplerDesc = {}; + samplerDesc.Filter = D3D12_FILTER_MIN_MAG_MIP_LINEAR; + samplerDesc.AddressU = D3D12_TEXTURE_ADDRESS_MODE_CLAMP; + samplerDesc.AddressV = D3D12_TEXTURE_ADDRESS_MODE_CLAMP; + samplerDesc.AddressW = D3D12_TEXTURE_ADDRESS_MODE_CLAMP; + samplerDesc.MaxLOD = 10000.0f; + samplerDesc.ShaderVisibility = D3D12_SHADER_VISIBILITY_ALL; + + D3D12_VERSIONED_ROOT_SIGNATURE_DESC rsDesc = {}; + rsDesc.Version = D3D_ROOT_SIGNATURE_VERSION_1_1; + rsDesc.Desc_1_1.NumParameters = 3; + rsDesc.Desc_1_1.pParameters = rootParams; + rsDesc.Desc_1_1.NumStaticSamplers = 1; + rsDesc.Desc_1_1.pStaticSamplers = &samplerDesc; + + ID3DBlob *signature = nullptr; + HRESULT hr = D3D12SerializeVersionedRootSignature(&rsDesc, &signature, nullptr); + if (FAILED(hr)) { + qWarning("Failed to serialize root signature: %s", qPrintable(QSystemError::windowsComString(hr))); + return false; + } + ID3D12RootSignature *rootSig = nullptr; + hr = rhiD->dev->CreateRootSignature(0, + signature->GetBufferPointer(), + signature->GetBufferSize(), + __uuidof(ID3D12RootSignature), + reinterpret_cast(&rootSig)); + signature->Release(); + if (FAILED(hr)) { + qWarning("Failed to create root signature: %s", + qPrintable(QSystemError::windowsComString(hr))); + return false; + } + + rootSigHandle = QD3D12RootSignature::addToPool(&rhiD->rootSignaturePool, rootSig); + + D3D12_COMPUTE_PIPELINE_STATE_DESC psoDesc = {}; + psoDesc.pRootSignature = rootSig; + psoDesc.CS.pShaderBytecode = g_csMipmap; + psoDesc.CS.BytecodeLength = sizeof(g_csMipmap); + ID3D12PipelineState *pso = nullptr; + hr = rhiD->dev->CreateComputePipelineState(&psoDesc, + __uuidof(ID3D12PipelineState), + reinterpret_cast(&pso)); + if (FAILED(hr)) { + qWarning("Failed to create compute pipeline state: %s", + qPrintable(QSystemError::windowsComString(hr))); + rhiD->rootSignaturePool.remove(rootSigHandle); + rootSigHandle = {}; + return false; + } + + pipelineHandle = QD3D12Pipeline::addToPool(&rhiD->pipelinePool, QD3D12Pipeline::Compute, pso); + + return true; +} + +void QD3D12MipmapGenerator::destroy() +{ + rhiD->pipelinePool.remove(pipelineHandle); + pipelineHandle = {}; + rhiD->rootSignaturePool.remove(rootSigHandle); + rootSigHandle = {}; +} + +void QD3D12MipmapGenerator::generate(QD3D12CommandBuffer *cbD, const QD3D12ObjectHandle &textureHandle) +{ + QD3D12Pipeline *pipeline = rhiD->pipelinePool.lookupRef(pipelineHandle); + if (!pipeline) + return; + QD3D12RootSignature *rootSig = rhiD->rootSignaturePool.lookupRef(rootSigHandle); + if (!rootSig) + return; + QD3D12Resource *res = rhiD->resourcePool.lookupRef(textureHandle); + if (!res) + return; + + const quint32 mipLevelCount = res->desc.MipLevels; + if (mipLevelCount < 2) + return; + + if (res->desc.SampleDesc.Count > 1) { + qWarning("Cannot generate mipmaps for MSAA texture"); + return; + } + + const bool is1D = res->desc.Dimension == D3D12_RESOURCE_DIMENSION_TEXTURE1D; + if (is1D) { + qWarning("Cannot generate mipmaps for 1D texture"); + return; + } + + const bool is3D = res->desc.Dimension == D3D12_RESOURCE_DIMENSION_TEXTURE3D; + const bool isCubeOrArray = res->desc.Dimension == D3D12_RESOURCE_DIMENSION_TEXTURE2D + && res->desc.DepthOrArraySize > 1; + const quint32 layerCount = isCubeOrArray ? res->desc.DepthOrArraySize : 1; + + if (is3D) { + // ### needs its own shader and maybe a different solution + qWarning("3D texture mipmapping is not implemented for D3D12 atm"); + return; + } + + rhiD->barrierGen.addTransitionBarrier(textureHandle, D3D12_RESOURCE_STATE_UNORDERED_ACCESS); + rhiD->barrierGen.enqueueBufferedTransitionBarriers(cbD); + + cbD->cmdList->SetPipelineState(pipeline->pso); + cbD->cmdList->SetComputeRootSignature(rootSig->rootSig); + + const quint32 descriptorByteSize = rhiD->shaderVisibleCbvSrvUavHeap.perFrameHeapSlice[rhiD->currentFrameSlot].descriptorByteSize; + + struct CBufData { + quint32 srcMipLevel; + quint32 numMipLevels; + float texelWidth; + float texelHeight; + }; + + const quint32 allocSize = QD3D12StagingArea::allocSizeForArray(sizeof(CBufData), mipLevelCount * layerCount); + std::optional ownStagingArea; + if (rhiD->smallStagingAreas[rhiD->currentFrameSlot].remainingCapacity() < allocSize) { + ownStagingArea = QD3D12StagingArea(); + if (!ownStagingArea->create(rhiD, allocSize, D3D12_HEAP_TYPE_UPLOAD)) { + qWarning("Could not create staging area for mipmap generation"); + return; + } + } + QD3D12StagingArea *workArea = ownStagingArea.has_value() + ? &ownStagingArea.value() + : &rhiD->smallStagingAreas[rhiD->currentFrameSlot]; + + bool gotNewHeap = false; + if (!rhiD->ensureShaderVisibleDescriptorHeapCapacity(&rhiD->shaderVisibleCbvSrvUavHeap, + D3D12_DESCRIPTOR_HEAP_TYPE_CBV_SRV_UAV, + rhiD->currentFrameSlot, + (1 + 4) * mipLevelCount * layerCount, + &gotNewHeap)) + { + qWarning("Could not ensure enough space in descriptor heap for mipmap generation"); + return; + } + if (gotNewHeap) + rhiD->bindShaderVisibleHeaps(cbD); + + for (quint32 layer = 0; layer < layerCount; ++layer) { + for (quint32 level = 0; level < mipLevelCount ;) { + UINT subresource = calcSubresource(level, layer, res->desc.MipLevels); + rhiD->barrierGen.enqueueSubresourceTransitionBarrier(cbD, textureHandle, subresource, + D3D12_RESOURCE_STATE_UNORDERED_ACCESS, + D3D12_RESOURCE_STATE_NON_PIXEL_SHADER_RESOURCE); + + quint32 levelPlusOneMipWidth = res->desc.Width >> (level + 1); + quint32 levelPlusOneMipHeight = res->desc.Height >> (level + 1); + const quint32 dw = levelPlusOneMipWidth == 1 ? levelPlusOneMipHeight : levelPlusOneMipWidth; + const quint32 dh = levelPlusOneMipHeight == 1 ? levelPlusOneMipWidth : levelPlusOneMipHeight; + // number of times the size can be halved while still resulting in an even dimension + const quint32 additionalMips = qCountTrailingZeroBits(dw | dh); + const quint32 numGenMips = qMin(1u + qMin(3u, additionalMips), res->desc.MipLevels - level); + levelPlusOneMipWidth = qMax(1u, levelPlusOneMipWidth); + levelPlusOneMipHeight = qMax(1u, levelPlusOneMipHeight); + + CBufData cbufData = { + level, + numGenMips, + 1.0f / float(levelPlusOneMipWidth), + 1.0f / float(levelPlusOneMipHeight) + }; + + QD3D12StagingArea::Allocation cbuf = workArea->get(sizeof(cbufData)); + memcpy(cbuf.p, &cbufData, sizeof(cbufData)); + cbD->cmdList->SetComputeRootConstantBufferView(0, cbuf.gpuAddr); + + QD3D12Descriptor srv = rhiD->shaderVisibleCbvSrvUavHeap.perFrameHeapSlice[rhiD->currentFrameSlot].get(1); + D3D12_SHADER_RESOURCE_VIEW_DESC srvDesc = {}; + srvDesc.Format = res->desc.Format; + srvDesc.Shader4ComponentMapping = D3D12_DEFAULT_SHADER_4_COMPONENT_MAPPING; + if (isCubeOrArray) { + srvDesc.ViewDimension = D3D12_SRV_DIMENSION_TEXTURE2DARRAY; + srvDesc.Texture2DArray.MostDetailedMip = level; + srvDesc.Texture2DArray.MipLevels = 1; + srvDesc.Texture2DArray.FirstArraySlice = layer; + srvDesc.Texture2DArray.ArraySize = 1; + } else if (is3D) { + srvDesc.ViewDimension = D3D12_SRV_DIMENSION_TEXTURE3D; + srvDesc.Texture3D.MostDetailedMip = level; + srvDesc.Texture3D.MipLevels = 1; + } else { + srvDesc.ViewDimension = D3D12_SRV_DIMENSION_TEXTURE2D; + srvDesc.Texture2D.MostDetailedMip = level; + srvDesc.Texture2D.MipLevels = 1; + } + rhiD->dev->CreateShaderResourceView(res->resource, &srvDesc, srv.cpuHandle); + cbD->cmdList->SetComputeRootDescriptorTable(1, srv.gpuHandle); + + QD3D12Descriptor uavStart = rhiD->shaderVisibleCbvSrvUavHeap.perFrameHeapSlice[rhiD->currentFrameSlot].get(4); + D3D12_CPU_DESCRIPTOR_HANDLE uavCpuHandle = uavStart.cpuHandle; + // if level is N, then need UAVs for levels N+1, ..., N+4 + for (quint32 uavIdx = 0; uavIdx < 4; ++uavIdx) { + const quint32 uavMipLevel = qMin(level + 1u + uavIdx, res->desc.MipLevels - 1u); + D3D12_UNORDERED_ACCESS_VIEW_DESC uavDesc = {}; + uavDesc.Format = res->desc.Format; + if (isCubeOrArray) { + uavDesc.ViewDimension = D3D12_UAV_DIMENSION_TEXTURE2DARRAY; + uavDesc.Texture2DArray.MipSlice = uavMipLevel; + uavDesc.Texture2DArray.FirstArraySlice = layer; + uavDesc.Texture2DArray.ArraySize = 1; + } else if (is3D) { + uavDesc.ViewDimension = D3D12_UAV_DIMENSION_TEXTURE3D; + uavDesc.Texture3D.MipSlice = uavMipLevel; + uavDesc.Texture3D.FirstWSlice = 0; // depth etc. not implemented yet + uavDesc.Texture3D.WSize = 1; + } else { + uavDesc.ViewDimension = D3D12_UAV_DIMENSION_TEXTURE2D; + uavDesc.Texture2D.MipSlice = uavMipLevel; + } + rhiD->dev->CreateUnorderedAccessView(res->resource, nullptr, &uavDesc, uavCpuHandle); + uavCpuHandle.ptr += descriptorByteSize; + } + cbD->cmdList->SetComputeRootDescriptorTable(2, uavStart.gpuHandle); + + cbD->cmdList->Dispatch(levelPlusOneMipWidth, levelPlusOneMipHeight, 1); + + rhiD->barrierGen.enqueueUavBarrier(cbD, textureHandle); + rhiD->barrierGen.enqueueSubresourceTransitionBarrier(cbD, textureHandle, subresource, + D3D12_RESOURCE_STATE_NON_PIXEL_SHADER_RESOURCE, + D3D12_RESOURCE_STATE_UNORDERED_ACCESS); + + level += numGenMips; + } + } + + if (ownStagingArea.has_value()) + ownStagingArea->destroyWithDeferredRelease(&rhiD->releaseQueue); +} + +bool QD3D12MemoryAllocator::create(ID3D12Device *device, IDXGIAdapter1 *adapter) +{ + this->device = device; + + // We can function with and without D3D12MA: CreateCommittedResource is + // just fine for our purposes and not any complicated API-wise; the memory + // allocator is interesting for efficiency mainly since it can suballocate + // instead of making everything a committed resource allocation. + + static bool disableMA = qEnvironmentVariableIntValue("QT_D3D_NO_SUBALLOC"); + if (disableMA) + return true; + + DXGI_ADAPTER_DESC1 desc; + adapter->GetDesc1(&desc); + if (desc.Flags & DXGI_ADAPTER_FLAG_SOFTWARE) + return true; + + D3D12MA::ALLOCATOR_DESC allocatorDesc = {}; + allocatorDesc.pDevice = device; + allocatorDesc.pAdapter = adapter; + // A QRhi is supposed to be used from one single thread only. Disable + // the allocator's own mutexes. This may give a performance boost. + allocatorDesc.Flags = D3D12MA::ALLOCATOR_FLAG_SINGLETHREADED; + HRESULT hr = D3D12MA::CreateAllocator(&allocatorDesc, &allocator); + if (FAILED(hr)) { + qWarning("Failed to initialize D3D12 Memory Allocator: %s", + qPrintable(QSystemError::windowsComString(hr))); + return false; + } + return true; +} + +void QD3D12MemoryAllocator::destroy() +{ + if (allocator) { + allocator->Release(); + allocator = nullptr; + } +} + +HRESULT QD3D12MemoryAllocator::createResource(D3D12_HEAP_TYPE heapType, + const D3D12_RESOURCE_DESC *resourceDesc, + D3D12_RESOURCE_STATES initialState, + const D3D12_CLEAR_VALUE *optimizedClearValue, + D3D12MA::Allocation **maybeAllocation, + REFIID riidResource, + void **ppvResource) +{ + if (allocator) { + D3D12MA::ALLOCATION_DESC allocDesc = {}; + allocDesc.HeapType = heapType; + return allocator->CreateResource(&allocDesc, + resourceDesc, + initialState, + optimizedClearValue, + maybeAllocation, + riidResource, + ppvResource); + } else { + *maybeAllocation = nullptr; + D3D12_HEAP_PROPERTIES heapProps = {}; + heapProps.Type = heapType; + return device->CreateCommittedResource(&heapProps, + D3D12_HEAP_FLAG_NONE, + resourceDesc, + initialState, + optimizedClearValue, + riidResource, + ppvResource); + } +} + +void QD3D12MemoryAllocator::getBudget(D3D12MA::Budget *localBudget, D3D12MA::Budget *nonLocalBudget) +{ + if (allocator) { + allocator->GetBudget(localBudget, nonLocalBudget); + } else { + *localBudget = {}; + *nonLocalBudget = {}; + } +} + +void QRhiD3D12::waitGpu() +{ + fullFenceCounter += 1u; + if (SUCCEEDED(cmdQueue->Signal(fullFence, fullFenceCounter))) { + if (SUCCEEDED(fullFence->SetEventOnCompletion(fullFenceCounter, fullFenceEvent))) + WaitForSingleObject(fullFenceEvent, INFINITE); + } +} + +DXGI_SAMPLE_DESC QRhiD3D12::effectiveSampleCount(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; + } + + if (s > 1) { + D3D12_FEATURE_DATA_MULTISAMPLE_QUALITY_LEVELS msaaInfo = {}; + msaaInfo.Format = format; + msaaInfo.SampleCount = s; + if (SUCCEEDED(dev->CheckFeatureSupport(D3D12_FEATURE_MULTISAMPLE_QUALITY_LEVELS, &msaaInfo, sizeof(msaaInfo)))) { + if (msaaInfo.NumQualityLevels > 0) { + desc.Count = UINT(s); + desc.Quality = msaaInfo.NumQualityLevels - 1; + } else { + qWarning("No quality levels for multisampling with sample count %d", s); + } + } + } + + return desc; +} + +bool QRhiD3D12::startCommandListForCurrentFrameSlot(ID3D12GraphicsCommandList **cmdList) +{ + ID3D12CommandAllocator *cmdAlloc = cmdAllocators[currentFrameSlot]; + if (!*cmdList) { + HRESULT hr = dev->CreateCommandList(0, + D3D12_COMMAND_LIST_TYPE_DIRECT, + cmdAlloc, + nullptr, + __uuidof(ID3D12GraphicsCommandList), + reinterpret_cast(cmdList)); + if (FAILED(hr)) { + qWarning("Failed to create command list: %s", qPrintable(QSystemError::windowsComString(hr))); + return false; + } + } else { + HRESULT hr = (*cmdList)->Reset(cmdAlloc, nullptr); + if (FAILED(hr)) { + qWarning("Failed to reset command list: %s", qPrintable(QSystemError::windowsComString(hr))); + return false; + } + } + return true; +} + +static inline QRhiTexture::Format swapchainReadbackTextureFormat(DXGI_FORMAT format, QRhiTexture::Flags *flags) +{ + switch (format) { + case DXGI_FORMAT_R8G8B8A8_UNORM: + return QRhiTexture::RGBA8; + case DXGI_FORMAT_R8G8B8A8_UNORM_SRGB: + if (flags) + (*flags) |= QRhiTexture::sRGB; + return QRhiTexture::RGBA8; + case DXGI_FORMAT_B8G8R8A8_UNORM: + return QRhiTexture::BGRA8; + case DXGI_FORMAT_B8G8R8A8_UNORM_SRGB: + if (flags) + (*flags) |= QRhiTexture::sRGB; + return QRhiTexture::BGRA8; + case DXGI_FORMAT_R16G16B16A16_FLOAT: + return QRhiTexture::RGBA16F; + case DXGI_FORMAT_R32G32B32A32_FLOAT: + return QRhiTexture::RGBA32F; + case DXGI_FORMAT_R10G10B10A2_UNORM: + return QRhiTexture::RGB10A2; + default: + qWarning("DXGI_FORMAT %d cannot be read back", format); + break; + } + return QRhiTexture::UnknownFormat; +} + +void QRhiD3D12::enqueueResourceUpdates(QD3D12CommandBuffer *cbD, QRhiResourceUpdateBatch *resourceUpdates) +{ + QRhiResourceUpdateBatchPrivate *ud = QRhiResourceUpdateBatchPrivate::get(resourceUpdates); + + for (int opIdx = 0; opIdx < ud->activeBufferOpCount; ++opIdx) { + const QRhiResourceUpdateBatchPrivate::BufferOp &u(ud->bufferOps[opIdx]); + if (u.type == QRhiResourceUpdateBatchPrivate::BufferOp::DynamicUpdate) { + QD3D12Buffer *bufD = QRHI_RES(QD3D12Buffer, u.buf); + Q_ASSERT(bufD->m_type == QRhiBuffer::Dynamic); + for (int i = 0; i < QD3D12_FRAMES_IN_FLIGHT; ++i) { + if (u.offset == 0 && u.data.size() == bufD->m_size) + bufD->pendingHostWrites[i].clear(); + bufD->pendingHostWrites[i].append({ u.offset, u.data }); + } + } else if (u.type == QRhiResourceUpdateBatchPrivate::BufferOp::StaticUpload) { + QD3D12Buffer *bufD = QRHI_RES(QD3D12Buffer, u.buf); + Q_ASSERT(bufD->m_type != QRhiBuffer::Dynamic); + Q_ASSERT(u.offset + u.data.size() <= bufD->m_size); + + // The general approach to staging upload data is to first try + // using the per-frame "small" staging area, which is a very simple + // linear allocator; if that's not big enough then create a + // dedicated StagingArea and then deferred-release it to make sure + // if stays alive while the frame is possibly still in flight. + + QD3D12StagingArea::Allocation stagingAlloc; + const quint32 allocSize = QD3D12StagingArea::allocSizeForArray(bufD->m_size, 1); + if (smallStagingAreas[currentFrameSlot].remainingCapacity() >= allocSize) + stagingAlloc = smallStagingAreas[currentFrameSlot].get(bufD->m_size); + + std::optional ownStagingArea; + if (!stagingAlloc.isValid()) { + ownStagingArea = QD3D12StagingArea(); + if (!ownStagingArea->create(this, allocSize, D3D12_HEAP_TYPE_UPLOAD)) + continue; + stagingAlloc = ownStagingArea->get(allocSize); + if (!stagingAlloc.isValid()) { + ownStagingArea->destroy(); + continue; + } + } + + memcpy(stagingAlloc.p + u.offset, u.data.constData(), u.data.size()); + + barrierGen.addTransitionBarrier(bufD->handles[0], D3D12_RESOURCE_STATE_COPY_DEST); + barrierGen.enqueueBufferedTransitionBarriers(cbD); + + if (QD3D12Resource *res = resourcePool.lookupRef(bufD->handles[0])) { + cbD->cmdList->CopyBufferRegion(res->resource, + u.offset, + stagingAlloc.buffer, + stagingAlloc.bufferOffset + u.offset, + u.data.size()); + } + + if (ownStagingArea.has_value()) + ownStagingArea->destroyWithDeferredRelease(&releaseQueue); + } else if (u.type == QRhiResourceUpdateBatchPrivate::BufferOp::Read) { + QD3D12Buffer *bufD = QRHI_RES(QD3D12Buffer, u.buf); + if (bufD->m_type == QRhiBuffer::Dynamic) { + bufD->executeHostWritesForFrameSlot(currentFrameSlot); + if (QD3D12Resource *res = resourcePool.lookupRef(bufD->handles[currentFrameSlot])) { + Q_ASSERT(res->cpuMapPtr); + u.result->data.resize(u.readSize); + memcpy(u.result->data.data(), reinterpret_cast(res->cpuMapPtr) + u.offset, u.readSize); + } + if (u.result->completed) + u.result->completed(); + } else { + QD3D12Readback readback; + readback.frameSlot = currentFrameSlot; + readback.result = u.result; + readback.byteSize = u.readSize; + const quint32 allocSize = aligned(u.readSize, QD3D12StagingArea::ALIGNMENT); + if (!readback.staging.create(this, allocSize, D3D12_HEAP_TYPE_READBACK)) { + if (u.result->completed) + u.result->completed(); + continue; + } + QD3D12StagingArea::Allocation stagingAlloc = readback.staging.get(u.readSize); + if (!stagingAlloc.isValid()) { + readback.staging.destroy(); + if (u.result->completed) + u.result->completed(); + continue; + } + Q_ASSERT(stagingAlloc.bufferOffset == 0); + barrierGen.addTransitionBarrier(bufD->handles[0], D3D12_RESOURCE_STATE_COPY_SOURCE); + barrierGen.enqueueBufferedTransitionBarriers(cbD); + if (QD3D12Resource *res = resourcePool.lookupRef(bufD->handles[0])) { + cbD->cmdList->CopyBufferRegion(stagingAlloc.buffer, 0, res->resource, u.offset, u.readSize); + activeReadbacks.append(readback); + } else { + readback.staging.destroy(); + if (u.result->completed) + u.result->completed(); + } + } + } + } + + for (int opIdx = 0; opIdx < ud->activeTextureOpCount; ++opIdx) { + const QRhiResourceUpdateBatchPrivate::TextureOp &u(ud->textureOps[opIdx]); + if (u.type == QRhiResourceUpdateBatchPrivate::TextureOp::Upload) { + QD3D12Texture *texD = QRHI_RES(QD3D12Texture, u.dst); + const bool is3D = texD->m_flags.testFlag(QRhiTexture::ThreeDimensional); + QD3D12Resource *res = resourcePool.lookupRef(texD->handle); + if (!res) + continue; + barrierGen.addTransitionBarrier(texD->handle, D3D12_RESOURCE_STATE_COPY_DEST); + barrierGen.enqueueBufferedTransitionBarriers(cbD); + 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])) { + 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(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(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(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; + } + + const quint32 allocSize = QD3D12StagingArea::allocSizeForArray(totalBytes, 1); + QD3D12StagingArea::Allocation stagingAlloc; + if (smallStagingAreas[currentFrameSlot].remainingCapacity() >= allocSize) + stagingAlloc = smallStagingAreas[currentFrameSlot].get(allocSize); + + std::optional ownStagingArea; + if (!stagingAlloc.isValid()) { + ownStagingArea = QD3D12StagingArea(); + if (!ownStagingArea->create(this, allocSize, D3D12_HEAP_TYPE_UPLOAD)) + continue; + stagingAlloc = ownStagingArea->get(allocSize); + if (!stagingAlloc.isValid()) { + ownStagingArea->destroy(); + continue; + } + } + + D3D12_TEXTURE_COPY_LOCATION dst; + dst.pResource = res->resource; + dst.Type = D3D12_TEXTURE_COPY_TYPE_SUBRESOURCE_INDEX; + 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; + + D3D12_BOX srcBox; // back, right, bottom are exclusive + + if (!subresDesc.image().isNull()) { + 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()); + srcBox.bottom = UINT(size.height()); + srcBox.front = 0; + srcBox.back = 1; + + 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 * footprint.RowPitch, + imgPtr + srcPos.x() * bpc + (y + srcPos.y()) * bpl, + lineBytes); + } + } else if (!subresDesc.data().isEmpty() && isCompressedFormat(texD->m_format)) { + QSize blockDim; + quint32 bpl = 0; + compressedFormatInfo(texD->m_format, subresSize, &bpl, nullptr, &blockDim); + // x and y must be multiples of the block width and height + dstPos.setX(aligned(dstPos.x(), blockDim.width())); + dstPos.setY(aligned(dstPos.y(), blockDim.height())); + + srcBox.left = 0; + srcBox.top = 0; + // 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; + + 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 * footprint.RowPitch, imgPtr + y * bpl, copyBytes); + } else if (!subresDesc.data().isEmpty()) { + srcBox.left = 0; + srcBox.top = 0; + srcBox.right = subresSize.width(); + srcBox.bottom = subresSize.height(); + 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, 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 * footprint.RowPitch, imgPtr + y * bpl, copyBytes); + } + + src.PlacedFootprint.Footprint = footprint; + + cbD->cmdList->CopyTextureRegion(&dst, + UINT(dstPos.x()), + UINT(dstPos.y()), + is3D ? UINT(layer) : 0u, + &src, + &srcBox); + + if (ownStagingArea.has_value()) + ownStagingArea->destroyWithDeferredRelease(&releaseQueue); + } + } + } + } else if (u.type == QRhiResourceUpdateBatchPrivate::TextureOp::Copy) { + Q_ASSERT(u.src && u.dst); + QD3D12Texture *srcD = QRHI_RES(QD3D12Texture, u.src); + QD3D12Texture *dstD = QRHI_RES(QD3D12Texture, u.dst); + const bool srcIs3D = srcD->m_flags.testFlag(QRhiTexture::ThreeDimensional); + const bool dstIs3D = dstD->m_flags.testFlag(QRhiTexture::ThreeDimensional); + QD3D12Resource *srcRes = resourcePool.lookupRef(srcD->handle); + QD3D12Resource *dstRes = resourcePool.lookupRef(dstD->handle); + if (!srcRes || !dstRes) + continue; + + barrierGen.addTransitionBarrier(srcD->handle, D3D12_RESOURCE_STATE_COPY_SOURCE); + barrierGen.addTransitionBarrier(dstD->handle, D3D12_RESOURCE_STATE_COPY_DEST); + barrierGen.enqueueBufferedTransitionBarriers(cbD); + + const UINT srcSubresource = calcSubresource(UINT(u.desc.sourceLevel()), + srcIs3D ? 0u : UINT(u.desc.sourceLayer()), + srcD->mipLevelCount); + const UINT dstSubresource = calcSubresource(UINT(u.desc.destinationLevel()), + dstIs3D ? 0u : UINT(u.desc.destinationLayer()), + dstD->mipLevelCount); + const QPoint dp = u.desc.destinationTopLeft(); + const QSize mipSize = q->sizeForMipLevel(u.desc.sourceLevel(), srcD->m_pixelSize); + const QSize copySize = u.desc.pixelSize().isEmpty() ? mipSize : u.desc.pixelSize(); + const QPoint sp = u.desc.sourceTopLeft(); + + D3D12_BOX srcBox; + srcBox.left = UINT(sp.x()); + srcBox.top = UINT(sp.y()); + srcBox.front = srcIs3D ? UINT(u.desc.sourceLayer()) : 0u; + // back, right, bottom are exclusive + srcBox.right = srcBox.left + UINT(copySize.width()); + srcBox.bottom = srcBox.top + UINT(copySize.height()); + srcBox.back = srcBox.front + 1; + + D3D12_TEXTURE_COPY_LOCATION src; + src.pResource = srcRes->resource; + src.Type = D3D12_TEXTURE_COPY_TYPE_SUBRESOURCE_INDEX; + src.SubresourceIndex = srcSubresource; + D3D12_TEXTURE_COPY_LOCATION dst; + dst.pResource = dstRes->resource; + dst.Type = D3D12_TEXTURE_COPY_TYPE_SUBRESOURCE_INDEX; + dst.SubresourceIndex = dstSubresource; + + cbD->cmdList->CopyTextureRegion(&dst, + UINT(dp.x()), + UINT(dp.y()), + dstIs3D ? UINT(u.desc.destinationLayer()) : 0u, + &src, + &srcBox); + } else if (u.type == QRhiResourceUpdateBatchPrivate::TextureOp::Read) { + QD3D12Readback readback; + readback.frameSlot = currentFrameSlot; + readback.result = u.result; + + QD3D12ObjectHandle srcHandle; + bool is3D = false; + if (u.rb.texture()) { + QD3D12Texture *texD = QRHI_RES(QD3D12Texture, u.rb.texture()); + if (texD->sampleDesc.Count > 1) { + qWarning("Multisample texture cannot be read back"); + continue; + } + is3D = texD->m_flags.testFlag(QRhiTexture::ThreeDimensional); + readback.pixelSize = q->sizeForMipLevel(u.rb.level(), texD->m_pixelSize); + readback.format = texD->m_format; + srcHandle = texD->handle; + } else { + Q_ASSERT(currentSwapChain); + readback.pixelSize = currentSwapChain->pixelSize; + readback.format = swapchainReadbackTextureFormat(currentSwapChain->colorFormat, nullptr); + if (readback.format == QRhiTexture::UnknownFormat) + continue; + srcHandle = currentSwapChain->colorBuffers[currentSwapChain->currentBackBufferIndex]; + } + + textureFormatInfo(readback.format, + readback.pixelSize, + &readback.bytesPerLine, + &readback.byteSize, + nullptr); + + QD3D12Resource *srcRes = resourcePool.lookupRef(srcHandle); + if (!srcRes) + continue; + + const UINT subresource = calcSubresource(UINT(u.rb.level()), + is3D ? 0u : UINT(u.rb.layer()), + srcRes->desc.MipLevels); + D3D12_PLACED_SUBRESOURCE_FOOTPRINT layout; + // totalBytes is what we get from D3D, with the 256 aligned stride, + // readback.byteSize is the final result that's not relevant here yet + UINT64 totalBytes = 0; + dev->GetCopyableFootprints(&srcRes->desc, subresource, 1, 0, + &layout, nullptr, nullptr, &totalBytes); + readback.stagingRowPitch = layout.Footprint.RowPitch; + + const quint32 allocSize = aligned(totalBytes, QD3D12StagingArea::ALIGNMENT); + if (!readback.staging.create(this, allocSize, D3D12_HEAP_TYPE_READBACK)) { + if (u.result->completed) + u.result->completed(); + continue; + } + QD3D12StagingArea::Allocation stagingAlloc = readback.staging.get(totalBytes); + if (!stagingAlloc.isValid()) { + readback.staging.destroy(); + if (u.result->completed) + u.result->completed(); + continue; + } + Q_ASSERT(stagingAlloc.bufferOffset == 0); + + barrierGen.addTransitionBarrier(srcHandle, D3D12_RESOURCE_STATE_COPY_SOURCE); + barrierGen.enqueueBufferedTransitionBarriers(cbD); + + D3D12_TEXTURE_COPY_LOCATION dst; + dst.pResource = stagingAlloc.buffer; + dst.Type = D3D12_TEXTURE_COPY_TYPE_PLACED_FOOTPRINT; + dst.PlacedFootprint.Offset = 0; + dst.PlacedFootprint.Footprint = layout.Footprint; + + D3D12_TEXTURE_COPY_LOCATION src; + src.pResource = srcRes->resource; + src.Type = D3D12_TEXTURE_COPY_TYPE_SUBRESOURCE_INDEX; + src.SubresourceIndex = subresource; + + D3D12_BOX srcBox = {}; + if (is3D) { + srcBox.front = UINT(u.rb.layer()); + srcBox.back = srcBox.front + 1; + srcBox.right = readback.pixelSize.width(); // exclusive + srcBox.bottom = readback.pixelSize.height(); + } + cbD->cmdList->CopyTextureRegion(&dst, 0, 0, 0, &src, is3D ? &srcBox : nullptr); + activeReadbacks.append(readback); + } else if (u.type == QRhiResourceUpdateBatchPrivate::TextureOp::GenMips) { + QD3D12Texture *texD = QRHI_RES(QD3D12Texture, u.dst); + Q_ASSERT(texD->flags().testFlag(QRhiTexture::UsedWithGenerateMips)); + mipmapGen.generate(cbD, texD->handle); + } + } + + ud->free(); +} + +void QRhiD3D12::finishActiveReadbacks(bool forced) +{ + QVarLengthArray, 4> completedCallbacks; + + for (int i = activeReadbacks.size() - 1; i >= 0; --i) { + QD3D12Readback &readback(activeReadbacks[i]); + if (forced || currentFrameSlot == readback.frameSlot || readback.frameSlot < 0) { + readback.result->format = readback.format; + readback.result->pixelSize = readback.pixelSize; + readback.result->data.resize(int(readback.byteSize)); + + if (readback.format != QRhiTexture::UnknownFormat) { + quint8 *dstPtr = reinterpret_cast(readback.result->data.data()); + const quint8 *srcPtr = readback.staging.mem.p; + const quint32 lineSize = qMin(readback.bytesPerLine, readback.stagingRowPitch); + for (int y = 0, h = readback.pixelSize.height(); y < h; ++y) + memcpy(dstPtr + y * readback.bytesPerLine, srcPtr + y * readback.stagingRowPitch, lineSize); + } else { + memcpy(readback.result->data.data(), readback.staging.mem.p, readback.byteSize); + } + + readback.staging.destroy(); + + if (readback.result->completed) + completedCallbacks.append(readback.result->completed); + + activeReadbacks.removeLast(); + } + } + + for (auto f : completedCallbacks) + f(); +} + +bool QRhiD3D12::ensureShaderVisibleDescriptorHeapCapacity(QD3D12ShaderVisibleDescriptorHeap *h, + D3D12_DESCRIPTOR_HEAP_TYPE type, + int frameSlot, + quint32 neededDescriptorCount, + bool *gotNew) +{ + // Gets a new heap if needed. Note that the capacity we get is clamped + // automatically (e.g. to 1 million, or 2048 for samplers), so * 2 does not + // mean we can grow indefinitely, then again even using the same size would + // work (because we what we are after here is a new heap for the rest of + // the commands, not affecting what's already recorded). + if (h->perFrameHeapSlice[frameSlot].remainingCapacity() < neededDescriptorCount) { + const quint32 newPerFrameSize = qMax(h->perFrameHeapSlice[frameSlot].capacity * 2, + neededDescriptorCount); + QD3D12ShaderVisibleDescriptorHeap newHeap; + if (!newHeap.create(dev, type, newPerFrameSize)) { + qWarning("Could not create new shader-visible descriptor heap"); + return false; + } + h->destroyWithDeferredRelease(&releaseQueue); + *h = newHeap; + *gotNew = true; + } + return true; +} + +void QRhiD3D12::bindShaderVisibleHeaps(QD3D12CommandBuffer *cbD) +{ + ID3D12DescriptorHeap *heaps[] = { + shaderVisibleCbvSrvUavHeap.heap.heap, + samplerMgr.shaderVisibleSamplerHeap.heap.heap + }; + cbD->cmdList->SetDescriptorHeaps(2, heaps); +} + +QD3D12Buffer::QD3D12Buffer(QRhiImplementation *rhi, Type type, UsageFlags usage, quint32 size) + : QRhiBuffer(rhi, type, usage, size) +{ +} + +QD3D12Buffer::~QD3D12Buffer() +{ + destroy(); +} + +void QD3D12Buffer::destroy() +{ + if (handles[0].isNull()) + return; + + QRHI_RES_RHI(QRhiD3D12); + + // destroy() implementations, unlike other functions, are expected to test + // for m_rhi (rhiD) being null, to allow surviving in case one attempts to + // destroy a (leaked) resource after the QRhi. + // + // If there is no QRhi anymore, we do not deferred-release but that's fine + // since the QRhi already released everything that was in the resourcePool. + + for (int i = 0; i < QD3D12_FRAMES_IN_FLIGHT; ++i) { + if (rhiD) + rhiD->releaseQueue.deferredReleaseResource(handles[i]); + handles[i] = {}; + pendingHostWrites[i].clear(); + } + + if (rhiD) + rhiD->unregisterResource(this); +} + +bool QD3D12Buffer::create() +{ + if (!handles[0].isNull()) + destroy(); + + if (m_usage.testFlag(QRhiBuffer::UniformBuffer) && m_type != Dynamic) { + qWarning("UniformBuffer must always be Dynamic"); + return false; + } + + if (m_usage.testFlag(QRhiBuffer::StorageBuffer) && m_type == Dynamic) { + qWarning("StorageBuffer cannot be combined with Dynamic"); + return false; + } + + const quint32 nonZeroSize = m_size <= 0 ? 256 : m_size; + const quint32 roundedSize = aligned(nonZeroSize, m_usage.testFlag(QRhiBuffer::UniformBuffer) ? 256u : 4u); + + UINT resourceFlags = D3D12_RESOURCE_FLAG_NONE; + if (m_usage.testFlag(QRhiBuffer::StorageBuffer)) + resourceFlags |= D3D12_RESOURCE_FLAG_ALLOW_UNORDERED_ACCESS; + + QRHI_RES_RHI(QRhiD3D12); + HRESULT hr = 0; + for (int i = 0; i < QD3D12_FRAMES_IN_FLIGHT; ++i) { + if (i == 0 || m_type == Dynamic) { + D3D12_RESOURCE_DESC resourceDesc = {}; + resourceDesc.Dimension = D3D12_RESOURCE_DIMENSION_BUFFER; + resourceDesc.Width = roundedSize; + resourceDesc.Height = 1; + resourceDesc.DepthOrArraySize = 1; + resourceDesc.MipLevels = 1; + resourceDesc.Format = DXGI_FORMAT_UNKNOWN; + resourceDesc.SampleDesc = { 1, 0 }; + resourceDesc.Layout = D3D12_TEXTURE_LAYOUT_ROW_MAJOR; + resourceDesc.Flags = D3D12_RESOURCE_FLAGS(resourceFlags); + ID3D12Resource *resource = nullptr; + D3D12MA::Allocation *allocation = nullptr; + // Dynamic == host (CPU) visible + D3D12_HEAP_TYPE heapType = m_type == Dynamic + ? D3D12_HEAP_TYPE_UPLOAD + : D3D12_HEAP_TYPE_DEFAULT; + D3D12_RESOURCE_STATES resourceState = m_type == Dynamic + ? D3D12_RESOURCE_STATE_GENERIC_READ + : D3D12_RESOURCE_STATE_COMMON; + hr = rhiD->vma.createResource(heapType, + &resourceDesc, + resourceState, + nullptr, + &allocation, + __uuidof(resource), + reinterpret_cast(&resource)); + if (FAILED(hr)) + break; + if (!m_objectName.isEmpty()) { + QString decoratedName = QString::fromUtf8(m_objectName); + if (m_type == Dynamic) { + decoratedName += QLatin1Char('/'); + decoratedName += QString::number(i); + } + resource->SetName(reinterpret_cast(decoratedName.utf16())); + } + void *cpuMemPtr = nullptr; + if (m_type == Dynamic) { + // will be mapped for ever on the CPU, this makes future host write operations very simple + hr = resource->Map(0, nullptr, &cpuMemPtr); + if (FAILED(hr)) { + qWarning("Map() failed to dynamic buffer"); + resource->Release(); + if (allocation) + allocation->Release(); + break; + } + } + handles[i] = QD3D12Resource::addToPool(&rhiD->resourcePool, + resource, + resourceState, + allocation, + cpuMemPtr); + } + } + if (FAILED(hr)) { + qWarning("Failed to create buffer: '%s' Type was %d, size was %u, using D3D12MA was %d.", + qPrintable(QSystemError::windowsComString(hr)), + int(m_type), + roundedSize, + int(rhiD->vma.isUsingD3D12MA())); + return false; + } + + rhiD->registerResource(this); + return true; +} + +QRhiBuffer::NativeBuffer QD3D12Buffer::nativeBuffer() +{ + NativeBuffer b; + Q_ASSERT(sizeof(b.objects) / sizeof(b.objects[0]) >= size_t(QD3D12_FRAMES_IN_FLIGHT)); + QRHI_RES_RHI(QRhiD3D12); + if (m_type == Dynamic) { + for (int i = 0; i < QD3D12_FRAMES_IN_FLIGHT; ++i) { + executeHostWritesForFrameSlot(i); + if (QD3D12Resource *res = rhiD->resourcePool.lookupRef(handles[i])) + b.objects[i] = res->resource; + else + b.objects[i] = nullptr; + } + b.slotCount = QD3D12_FRAMES_IN_FLIGHT; + return b; + } + if (QD3D12Resource *res = rhiD->resourcePool.lookupRef(handles[0])) + b.objects[0] = res->resource; + else + b.objects[0] = nullptr; + b.slotCount = 1; + return b; +} + +char *QD3D12Buffer::beginFullDynamicBufferUpdateForCurrentFrame() +{ + // Shortcut the entire buffer update mechanism and allow the client to do + // the host writes directly to the buffer. This will lead to unexpected + // results when combined with QRhiResourceUpdateBatch-based updates for the + // buffer, but provides a fast path for dynamic buffers that have all their + // content changed in every frame. + + Q_ASSERT(m_type == Dynamic); + QRHI_RES_RHI(QRhiD3D12); + Q_ASSERT(rhiD->inFrame); + if (QD3D12Resource *res = rhiD->resourcePool.lookupRef(handles[rhiD->currentFrameSlot])) + return static_cast(res->cpuMapPtr); + + return nullptr; +} + +void QD3D12Buffer::endFullDynamicBufferUpdateForCurrentFrame() +{ + // nothing to do here +} + +void QD3D12Buffer::executeHostWritesForFrameSlot(int frameSlot) +{ + if (pendingHostWrites[frameSlot].isEmpty()) + return; + + Q_ASSERT(m_type == QRhiBuffer::Dynamic); + QRHI_RES_RHI(QRhiD3D12); + if (QD3D12Resource *res = rhiD->resourcePool.lookupRef(handles[frameSlot])) { + Q_ASSERT(res->cpuMapPtr); + for (const QD3D12Buffer::HostWrite &u : std::as_const(pendingHostWrites[frameSlot])) + memcpy(static_cast(res->cpuMapPtr) + u.offset, u.data.constData(), u.data.size()); + } + pendingHostWrites[frameSlot].clear(); +} + +static inline DXGI_FORMAT toD3DTextureFormat(QRhiTexture::Format format, QRhiTexture::Flags flags) +{ + const bool srgb = flags.testFlag(QRhiTexture::sRGB); + switch (format) { + case QRhiTexture::RGBA8: + return srgb ? DXGI_FORMAT_R8G8B8A8_UNORM_SRGB : DXGI_FORMAT_R8G8B8A8_UNORM; + case QRhiTexture::BGRA8: + return srgb ? DXGI_FORMAT_B8G8R8A8_UNORM_SRGB : DXGI_FORMAT_B8G8R8A8_UNORM; + case QRhiTexture::R8: + return DXGI_FORMAT_R8_UNORM; + case QRhiTexture::RG8: + return DXGI_FORMAT_R8G8_UNORM; + case QRhiTexture::R16: + return DXGI_FORMAT_R16_UNORM; + case QRhiTexture::RG16: + return DXGI_FORMAT_R16G16_UNORM; + case QRhiTexture::RED_OR_ALPHA8: + return DXGI_FORMAT_R8_UNORM; + + case QRhiTexture::RGBA16F: + return DXGI_FORMAT_R16G16B16A16_FLOAT; + case QRhiTexture::RGBA32F: + return DXGI_FORMAT_R32G32B32A32_FLOAT; + case QRhiTexture::R16F: + return DXGI_FORMAT_R16_FLOAT; + case QRhiTexture::R32F: + return DXGI_FORMAT_R32_FLOAT; + + case QRhiTexture::RGB10A2: + return DXGI_FORMAT_R10G10B10A2_UNORM; + + case QRhiTexture::D16: + return DXGI_FORMAT_R16_TYPELESS; + case QRhiTexture::D24: + return DXGI_FORMAT_R24G8_TYPELESS; + case QRhiTexture::D24S8: + return DXGI_FORMAT_R24G8_TYPELESS; + case QRhiTexture::D32F: + return DXGI_FORMAT_R32_TYPELESS; + + case QRhiTexture::BC1: + return srgb ? DXGI_FORMAT_BC1_UNORM_SRGB : DXGI_FORMAT_BC1_UNORM; + case QRhiTexture::BC2: + return srgb ? DXGI_FORMAT_BC2_UNORM_SRGB : DXGI_FORMAT_BC2_UNORM; + case QRhiTexture::BC3: + return srgb ? DXGI_FORMAT_BC3_UNORM_SRGB : DXGI_FORMAT_BC3_UNORM; + case QRhiTexture::BC4: + return DXGI_FORMAT_BC4_UNORM; + case QRhiTexture::BC5: + return DXGI_FORMAT_BC5_UNORM; + case QRhiTexture::BC6H: + return DXGI_FORMAT_BC6H_UF16; + case QRhiTexture::BC7: + return srgb ? DXGI_FORMAT_BC7_UNORM_SRGB : DXGI_FORMAT_BC7_UNORM; + + case QRhiTexture::ETC2_RGB8: + case QRhiTexture::ETC2_RGB8A1: + case QRhiTexture::ETC2_RGBA8: + qWarning("QRhiD3D12 does not support ETC2 textures"); + return DXGI_FORMAT_R8G8B8A8_UNORM; + + case QRhiTexture::ASTC_4x4: + case QRhiTexture::ASTC_5x4: + case QRhiTexture::ASTC_5x5: + case QRhiTexture::ASTC_6x5: + case QRhiTexture::ASTC_6x6: + case QRhiTexture::ASTC_8x5: + case QRhiTexture::ASTC_8x6: + case QRhiTexture::ASTC_8x8: + case QRhiTexture::ASTC_10x5: + case QRhiTexture::ASTC_10x6: + case QRhiTexture::ASTC_10x8: + case QRhiTexture::ASTC_10x10: + case QRhiTexture::ASTC_12x10: + case QRhiTexture::ASTC_12x12: + qWarning("QRhiD3D12 does not support ASTC textures"); + return DXGI_FORMAT_R8G8B8A8_UNORM; + + default: + break; + } + return DXGI_FORMAT_R8G8B8A8_UNORM; +} + +QD3D12RenderBuffer::QD3D12RenderBuffer(QRhiImplementation *rhi, + Type type, + const QSize &pixelSize, + int sampleCount, + Flags flags, + QRhiTexture::Format backingFormatHint) + : QRhiRenderBuffer(rhi, type, pixelSize, sampleCount, flags, backingFormatHint) +{ +} + +QD3D12RenderBuffer::~QD3D12RenderBuffer() +{ + destroy(); +} + +void QD3D12RenderBuffer::destroy() +{ + if (handle.isNull()) + return; + + QRHI_RES_RHI(QRhiD3D12); + if (rhiD) { + if (rtv.isValid()) + rhiD->releaseQueue.deferredReleaseResourceWithViews(handle, &rhiD->rtvPool, rtv, 1); + else if (dsv.isValid()) + rhiD->releaseQueue.deferredReleaseResourceWithViews(handle, &rhiD->dsvPool, dsv, 1); + } + + handle = {}; + rtv = {}; + dsv = {}; + + if (rhiD) + rhiD->unregisterResource(this); +} + +bool QD3D12RenderBuffer::create() +{ + if (!handle.isNull()) + destroy(); + + if (m_pixelSize.isEmpty()) + return false; + + QRHI_RES_RHI(QRhiD3D12); + + switch (m_type) { + case QRhiRenderBuffer::Color: + { + dxgiFormat = toD3DTextureFormat(backingFormat(), {}); + sampleDesc = rhiD->effectiveSampleCount(m_sampleCount, dxgiFormat); + D3D12_RESOURCE_DESC resourceDesc = {}; + resourceDesc.Dimension = D3D12_RESOURCE_DIMENSION_TEXTURE2D; + resourceDesc.Width = UINT64(m_pixelSize.width()); + resourceDesc.Height = UINT(m_pixelSize.height()); + resourceDesc.DepthOrArraySize = 1; + resourceDesc.MipLevels = 1; + resourceDesc.Format = dxgiFormat; + resourceDesc.SampleDesc = sampleDesc; + resourceDesc.Layout = D3D12_TEXTURE_LAYOUT_UNKNOWN; + resourceDesc.Flags = D3D12_RESOURCE_FLAG_ALLOW_RENDER_TARGET; + D3D12_CLEAR_VALUE clearValue = {}; + clearValue.Format = dxgiFormat; + // have a separate allocation and resource object (meaning both will need its own Release()) + ID3D12Resource *resource = nullptr; + D3D12MA::Allocation *allocation = nullptr; + HRESULT hr = rhiD->vma.createResource(D3D12_HEAP_TYPE_DEFAULT, + &resourceDesc, + D3D12_RESOURCE_STATE_RENDER_TARGET, + &clearValue, + &allocation, + __uuidof(ID3D12Resource), + reinterpret_cast(&resource)); + if (FAILED(hr)) { + qWarning("Failed to create color buffer: %s", qPrintable(QSystemError::windowsComString(hr))); + return false; + } + handle = QD3D12Resource::addToPool(&rhiD->resourcePool, resource, D3D12_RESOURCE_STATE_RENDER_TARGET, allocation); + rtv = rhiD->rtvPool.allocate(1); + if (!rtv.isValid()) + return false; + D3D12_RENDER_TARGET_VIEW_DESC rtvDesc = {}; + rtvDesc.Format = dxgiFormat; + rtvDesc.ViewDimension = sampleDesc.Count > 1 ? D3D12_RTV_DIMENSION_TEXTURE2DMS + : D3D12_RTV_DIMENSION_TEXTURE2D; + rhiD->dev->CreateRenderTargetView(resource, &rtvDesc, rtv.cpuHandle); + } + break; + case QRhiRenderBuffer::DepthStencil: + { + dxgiFormat = DS_FORMAT; + sampleDesc = rhiD->effectiveSampleCount(m_sampleCount, dxgiFormat); + D3D12_RESOURCE_DESC resourceDesc = {}; + resourceDesc.Dimension = D3D12_RESOURCE_DIMENSION_TEXTURE2D; + resourceDesc.Width = UINT64(m_pixelSize.width()); + resourceDesc.Height = UINT(m_pixelSize.height()); + resourceDesc.DepthOrArraySize = 1; + resourceDesc.MipLevels = 1; + resourceDesc.Format = dxgiFormat; + resourceDesc.SampleDesc = sampleDesc; + resourceDesc.Layout = D3D12_TEXTURE_LAYOUT_UNKNOWN; + resourceDesc.Flags = D3D12_RESOURCE_FLAG_ALLOW_DEPTH_STENCIL; + if (m_flags.testFlag(UsedWithSwapChainOnly)) + resourceDesc.Flags |= D3D12_RESOURCE_FLAG_DENY_SHADER_RESOURCE; + D3D12_CLEAR_VALUE clearValue = {}; + clearValue.Format = dxgiFormat; + clearValue.DepthStencil.Depth = 1.0f; + clearValue.DepthStencil.Stencil = 0; + ID3D12Resource *resource = nullptr; + D3D12MA::Allocation *allocation = nullptr; + HRESULT hr = rhiD->vma.createResource(D3D12_HEAP_TYPE_DEFAULT, + &resourceDesc, + D3D12_RESOURCE_STATE_DEPTH_WRITE, + &clearValue, + &allocation, + __uuidof(ID3D12Resource), + reinterpret_cast(&resource)); + if (FAILED(hr)) { + qWarning("Failed to create depth-stencil buffer: %s", qPrintable(QSystemError::windowsComString(hr))); + return false; + } + handle = QD3D12Resource::addToPool(&rhiD->resourcePool, resource, D3D12_RESOURCE_STATE_DEPTH_WRITE, allocation); + dsv = rhiD->dsvPool.allocate(1); + if (!dsv.isValid()) + return false; + D3D12_DEPTH_STENCIL_VIEW_DESC dsvDesc = {}; + dsvDesc.Format = dxgiFormat; + dsvDesc.ViewDimension = sampleDesc.Count > 1 ? D3D12_DSV_DIMENSION_TEXTURE2DMS + : D3D12_DSV_DIMENSION_TEXTURE2D; + rhiD->dev->CreateDepthStencilView(resource, &dsvDesc, dsv.cpuHandle); + } + break; + } + + if (!m_objectName.isEmpty()) { + if (QD3D12Resource *res = rhiD->resourcePool.lookupRef(handle)) { + const QString name = QString::fromUtf8(m_objectName); + res->resource->SetName(reinterpret_cast(name.utf16())); + } + } + + generation += 1; + rhiD->registerResource(this); + return true; +} + +QRhiTexture::Format QD3D12RenderBuffer::backingFormat() const +{ + if (m_backingFormatHint != QRhiTexture::UnknownFormat) + return m_backingFormatHint; + else + return m_type == Color ? QRhiTexture::RGBA8 : QRhiTexture::UnknownFormat; +} + +QD3D12Texture::QD3D12Texture(QRhiImplementation *rhi, Format format, const QSize &pixelSize, int depth, + int arraySize, int sampleCount, Flags flags) + : QRhiTexture(rhi, format, pixelSize, depth, arraySize, sampleCount, flags) +{ +} + +QD3D12Texture::~QD3D12Texture() +{ + destroy(); +} + +void QD3D12Texture::destroy() +{ + if (handle.isNull()) + return; + + QRHI_RES_RHI(QRhiD3D12); + if (rhiD) + rhiD->releaseQueue.deferredReleaseResourceWithViews(handle, &rhiD->cbvSrvUavPool, srv, 1); + + handle = {}; + srv = {}; + + if (rhiD) + rhiD->unregisterResource(this); +} + +static inline DXGI_FORMAT toD3DDepthTextureSRVFormat(QRhiTexture::Format format) +{ + switch (format) { + case QRhiTexture::Format::D16: + return DXGI_FORMAT_R16_FLOAT; + case QRhiTexture::Format::D24: + return DXGI_FORMAT_R24_UNORM_X8_TYPELESS; + case QRhiTexture::Format::D24S8: + return DXGI_FORMAT_R24_UNORM_X8_TYPELESS; + case QRhiTexture::Format::D32F: + return DXGI_FORMAT_R32_FLOAT; + default: + break; + } + Q_UNREACHABLE_RETURN(DXGI_FORMAT_R32_FLOAT); +} + +static inline DXGI_FORMAT toD3DDepthTextureDSVFormat(QRhiTexture::Format format) +{ + // here the result cannot be typeless + switch (format) { + case QRhiTexture::Format::D16: + return DXGI_FORMAT_D16_UNORM; + case QRhiTexture::Format::D24: + return DXGI_FORMAT_D24_UNORM_S8_UINT; + case QRhiTexture::Format::D24S8: + return DXGI_FORMAT_D24_UNORM_S8_UINT; + case QRhiTexture::Format::D32F: + return DXGI_FORMAT_D32_FLOAT; + default: + break; + } + Q_UNREACHABLE_RETURN(DXGI_FORMAT_D32_FLOAT); +} + +static inline bool isDepthTextureFormat(QRhiTexture::Format format) +{ + switch (format) { + case QRhiTexture::Format::D16: + case QRhiTexture::Format::D24: + case QRhiTexture::Format::D24S8: + case QRhiTexture::Format::D32F: + return true; + default: + return false; + } +} + +bool QD3D12Texture::prepareCreate(QSize *adjustedSize) +{ + if (!handle.isNull()) + destroy(); + + 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 hasMipMaps = m_flags.testFlag(MipMapped); + const bool is1D = m_flags.testFlag(OneDimensional); + + 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); + mipLevelCount = uint(hasMipMaps ? rhiD->q->mipLevelsForSize(size) : 1); + sampleDesc = rhiD->effectiveSampleCount(m_sampleCount, dxgiFormat); + if (sampleDesc.Count > 1) { + if (isCube) { + qWarning("Cubemap texture cannot be multisample"); + return false; + } + if (is3D) { + qWarning("3D texture cannot be multisample"); + return false; + } + if (hasMipMaps) { + qWarning("Multisample texture cannot have mipmaps"); + return false; + } + } + if (isDepth && hasMipMaps) { + qWarning("Depth texture cannot have mipmaps"); + return false; + } + if (isCube && is3D) { + qWarning("Texture cannot be both cube and 3D"); + return false; + } + if (isArray && is3D) { + qWarning("Texture cannot be both array and 3D"); + return false; + } + if (isCube && is1D) { + qWarning("Texture cannot be both cube and 1D"); + return false; + } + if (is1D && is3D) { + qWarning("Texture cannot be both 1D and 3D"); + return false; + } + if (m_depth > 1 && !is3D) { + qWarning("Texture cannot have a depth of %d when it is not 3D", m_depth); + return false; + } + if (m_arraySize > 0 && !isArray) { + qWarning("Texture cannot have an array size of %d when it is not an array", m_arraySize); + return false; + } + if (m_arraySize < 1 && isArray) { + qWarning("Texture is an array but array size is %d", m_arraySize); + return false; + } + + if (adjustedSize) + *adjustedSize = size; + + return true; +} + +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.Shader4ComponentMapping = D3D12_DEFAULT_SHADER_4_COMPONENT_MAPPING; + + if (isCube) { + srvDesc.ViewDimension = D3D12_SRV_DIMENSION_TEXTURECUBE; + srvDesc.TextureCube.MipLevels = mipLevelCount; + } else { + if (is1D) { + if (isArray) { + srvDesc.ViewDimension = D3D12_SRV_DIMENSION_TEXTURE1DARRAY; + srvDesc.Texture1DArray.MipLevels = mipLevelCount; + if (m_arrayRangeStart >= 0 && m_arrayRangeLength >= 0) { + srvDesc.Texture1DArray.FirstArraySlice = UINT(m_arrayRangeStart); + srvDesc.Texture1DArray.ArraySize = UINT(m_arrayRangeLength); + } else { + srvDesc.Texture1DArray.FirstArraySlice = 0; + srvDesc.Texture1DArray.ArraySize = UINT(qMax(0, m_arraySize)); + } + } else { + srvDesc.ViewDimension = D3D12_SRV_DIMENSION_TEXTURE1D; + srvDesc.Texture1D.MipLevels = mipLevelCount; + } + } else if (isArray) { + if (sampleDesc.Count > 1) { + srvDesc.ViewDimension = D3D12_SRV_DIMENSION_TEXTURE2DMSARRAY; + if (m_arrayRangeStart >= 0 && m_arrayRangeLength >= 0) { + srvDesc.Texture2DMSArray.FirstArraySlice = UINT(m_arrayRangeStart); + srvDesc.Texture2DMSArray.ArraySize = UINT(m_arrayRangeLength); + } else { + srvDesc.Texture2DMSArray.FirstArraySlice = 0; + srvDesc.Texture2DMSArray.ArraySize = UINT(qMax(0, m_arraySize)); + } + } else { + srvDesc.ViewDimension = D3D12_SRV_DIMENSION_TEXTURE2DARRAY; + srvDesc.Texture2DArray.MipLevels = mipLevelCount; + if (m_arrayRangeStart >= 0 && m_arrayRangeLength >= 0) { + srvDesc.Texture2DArray.FirstArraySlice = UINT(m_arrayRangeStart); + srvDesc.Texture2DArray.ArraySize = UINT(m_arrayRangeLength); + } else { + srvDesc.Texture2DArray.FirstArraySlice = 0; + srvDesc.Texture2DArray.ArraySize = UINT(qMax(0, m_arraySize)); + } + } + } else { + if (sampleDesc.Count > 1) { + srvDesc.ViewDimension = D3D12_SRV_DIMENSION_TEXTURE2DMS; + } else if (is3D) { + srvDesc.ViewDimension = D3D12_SRV_DIMENSION_TEXTURE3D; + srvDesc.Texture3D.MipLevels = mipLevelCount; + } else { + srvDesc.ViewDimension = D3D12_SRV_DIMENSION_TEXTURE2D; + srvDesc.Texture2D.MipLevels = mipLevelCount; + } + } + } + + srv = rhiD->cbvSrvUavPool.allocate(1); + if (!srv.isValid()) + return false; + + if (QD3D12Resource *res = rhiD->resourcePool.lookupRef(handle)) { + rhiD->dev->CreateShaderResourceView(res->resource, &srvDesc, srv.cpuHandle); + if (!m_objectName.isEmpty()) { + const QString name = QString::fromUtf8(m_objectName); + res->resource->SetName(reinterpret_cast(name.utf16())); + } + } else { + return false; + } + + generation += 1; + return true; +} + +bool QD3D12Texture::create() +{ + QSize size; + if (!prepareCreate(&size)) + return false; + + 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); + + QRHI_RES_RHI(QRhiD3D12); + + bool needsOptimizedClearValueSpecified = false; + UINT resourceFlags = 0; + if (m_flags.testFlag(RenderTarget)) { + if (isDepth) + resourceFlags |= D3D12_RESOURCE_FLAG_ALLOW_DEPTH_STENCIL; + else + resourceFlags |= D3D12_RESOURCE_FLAG_ALLOW_RENDER_TARGET; + needsOptimizedClearValueSpecified = true; + } + if (m_flags.testFlag(UsedWithGenerateMips)) { + if (isDepth) { + qWarning("Depth texture cannot have mipmaps generated"); + return false; + } + resourceFlags |= D3D12_RESOURCE_FLAG_ALLOW_UNORDERED_ACCESS; + } + if (m_flags.testFlag(UsedWithLoadStore)) + resourceFlags |= D3D12_RESOURCE_FLAG_ALLOW_UNORDERED_ACCESS; + + D3D12_RESOURCE_DESC resourceDesc = {}; + resourceDesc.Dimension = is1D ? D3D12_RESOURCE_DIMENSION_TEXTURE1D + : (is3D ? D3D12_RESOURCE_DIMENSION_TEXTURE3D + : D3D12_RESOURCE_DIMENSION_TEXTURE2D); + resourceDesc.Width = UINT64(size.width()); + resourceDesc.Height = UINT(size.height()); + resourceDesc.DepthOrArraySize = isCube ? 6 + : (isArray ? UINT(qMax(0, m_arraySize)) + : (is3D ? qMax(1, m_depth) + : 1)); + resourceDesc.MipLevels = mipLevelCount; + resourceDesc.Format = dxgiFormat; + resourceDesc.SampleDesc = sampleDesc; + resourceDesc.Layout = D3D12_TEXTURE_LAYOUT_UNKNOWN; + resourceDesc.Flags = D3D12_RESOURCE_FLAGS(resourceFlags); + D3D12_CLEAR_VALUE clearValue = {}; + clearValue.Format = dxgiFormat; + if (isDepth) { + clearValue.Format = toD3DDepthTextureDSVFormat(m_format); + clearValue.DepthStencil.Depth = 1.0f; + clearValue.DepthStencil.Stencil = 0; + } + ID3D12Resource *resource = nullptr; + D3D12MA::Allocation *allocation = nullptr; + HRESULT hr = rhiD->vma.createResource(D3D12_HEAP_TYPE_DEFAULT, + &resourceDesc, + D3D12_RESOURCE_STATE_COMMON, + needsOptimizedClearValueSpecified ? &clearValue : nullptr, + &allocation, + __uuidof(ID3D12Resource), + reinterpret_cast(&resource)); + if (FAILED(hr)) { + qWarning("Failed to create texture: '%s'" + " Dim was %d Size was %ux%u Depth/ArraySize was %u MipLevels was %u Format was %d Sample count was %d", + qPrintable(QSystemError::windowsComString(hr)), + int(resourceDesc.Dimension), + uint(resourceDesc.Width), + uint(resourceDesc.Height), + uint(resourceDesc.DepthOrArraySize), + uint(resourceDesc.MipLevels), + int(resourceDesc.Format), + int(resourceDesc.SampleDesc.Count)); + return false; + } + + handle = QD3D12Resource::addToPool(&rhiD->resourcePool, resource, D3D12_RESOURCE_STATE_COMMON, allocation); + + if (!finishCreate()) + return false; + + rhiD->registerResource(this); + return true; +} + +bool QD3D12Texture::createFrom(QRhiTexture::NativeTexture src) +{ + if (!src.object) + return false; + + if (!prepareCreate()) + return false; + + ID3D12Resource *resource = reinterpret_cast(src.object); + D3D12_RESOURCE_STATES state = D3D12_RESOURCE_STATES(src.layout); + + QRHI_RES_RHI(QRhiD3D12); + handle = QD3D12Resource::addNonOwningToPool(&rhiD->resourcePool, resource, state); + + if (!finishCreate()) + return false; + + rhiD->registerResource(this); + return true; +} + +QRhiTexture::NativeTexture QD3D12Texture::nativeTexture() +{ + QRHI_RES_RHI(QRhiD3D12); + if (QD3D12Resource *res = rhiD->resourcePool.lookupRef(handle)) + return { quint64(res->resource), int(res->state) }; + + return {}; +} + +void QD3D12Texture::setNativeLayout(int layout) +{ + QRHI_RES_RHI(QRhiD3D12); + if (QD3D12Resource *res = rhiD->resourcePool.lookupRef(handle)) + res->state = D3D12_RESOURCE_STATES(layout); +} + +QD3D12Sampler::QD3D12Sampler(QRhiImplementation *rhi, Filter magFilter, Filter minFilter, Filter mipmapMode, + AddressMode u, AddressMode v, AddressMode w) + : QRhiSampler(rhi, magFilter, minFilter, mipmapMode, u, v, w) +{ +} + +QD3D12Sampler::~QD3D12Sampler() +{ + destroy(); +} + +void QD3D12Sampler::destroy() +{ + shaderVisibleDescriptor = {}; + + QRHI_RES_RHI(QRhiD3D12); + if (rhiD) + rhiD->unregisterResource(this); +} + +static inline D3D12_FILTER toD3DFilter(QRhiSampler::Filter minFilter, QRhiSampler::Filter magFilter, QRhiSampler::Filter mipFilter) +{ + if (minFilter == QRhiSampler::Nearest) { + if (magFilter == QRhiSampler::Nearest) { + if (mipFilter == QRhiSampler::Linear) + return D3D12_FILTER_MIN_MAG_POINT_MIP_LINEAR; + else + return D3D12_FILTER_MIN_MAG_MIP_POINT; + } else { + if (mipFilter == QRhiSampler::Linear) + return D3D12_FILTER_MIN_POINT_MAG_MIP_LINEAR; + else + return D3D12_FILTER_MIN_POINT_MAG_LINEAR_MIP_POINT; + } + } else { + if (magFilter == QRhiSampler::Nearest) { + if (mipFilter == QRhiSampler::Linear) + return D3D12_FILTER_MIN_LINEAR_MAG_POINT_MIP_LINEAR; + else + return D3D12_FILTER_MIN_LINEAR_MAG_MIP_POINT; + } else { + if (mipFilter == QRhiSampler::Linear) + return D3D12_FILTER_MIN_MAG_MIP_LINEAR; + else + return D3D12_FILTER_MIN_MAG_LINEAR_MIP_POINT; + } + } + Q_UNREACHABLE_RETURN(D3D12_FILTER_MIN_MAG_MIP_LINEAR); +} + +static inline D3D12_TEXTURE_ADDRESS_MODE toD3DAddressMode(QRhiSampler::AddressMode m) +{ + switch (m) { + case QRhiSampler::Repeat: + return D3D12_TEXTURE_ADDRESS_MODE_WRAP; + case QRhiSampler::ClampToEdge: + return D3D12_TEXTURE_ADDRESS_MODE_CLAMP; + case QRhiSampler::Mirror: + return D3D12_TEXTURE_ADDRESS_MODE_MIRROR; + } + Q_UNREACHABLE_RETURN(D3D12_TEXTURE_ADDRESS_MODE_CLAMP); +} + +static inline D3D12_COMPARISON_FUNC toD3DTextureComparisonFunc(QRhiSampler::CompareOp op) +{ + switch (op) { + case QRhiSampler::Never: + return D3D12_COMPARISON_FUNC_NEVER; + case QRhiSampler::Less: + return D3D12_COMPARISON_FUNC_LESS; + case QRhiSampler::Equal: + return D3D12_COMPARISON_FUNC_EQUAL; + case QRhiSampler::LessOrEqual: + return D3D12_COMPARISON_FUNC_LESS_EQUAL; + case QRhiSampler::Greater: + return D3D12_COMPARISON_FUNC_GREATER; + case QRhiSampler::NotEqual: + return D3D12_COMPARISON_FUNC_NOT_EQUAL; + case QRhiSampler::GreaterOrEqual: + return D3D12_COMPARISON_FUNC_GREATER_EQUAL; + case QRhiSampler::Always: + return D3D12_COMPARISON_FUNC_ALWAYS; + } + Q_UNREACHABLE_RETURN(D3D12_COMPARISON_FUNC_NEVER); +} + +bool QD3D12Sampler::create() +{ + desc = {}; + desc.Filter = toD3DFilter(m_minFilter, m_magFilter, m_mipmapMode); + if (m_compareOp != Never) + desc.Filter = D3D12_FILTER(desc.Filter | 0x80); + desc.AddressU = toD3DAddressMode(m_addressU); + desc.AddressV = toD3DAddressMode(m_addressV); + desc.AddressW = toD3DAddressMode(m_addressW); + desc.MaxAnisotropy = 1.0f; + desc.ComparisonFunc = toD3DTextureComparisonFunc(m_compareOp); + desc.MaxLOD = m_mipmapMode == None ? 0.0f : 10000.0f; + + QRHI_RES_RHI(QRhiD3D12); + rhiD->registerResource(this, false); + return true; +} + +QD3D12Descriptor QD3D12Sampler::lookupOrCreateShaderVisibleDescriptor() +{ + if (!shaderVisibleDescriptor.isValid()) { + QRHI_RES_RHI(QRhiD3D12); + shaderVisibleDescriptor = rhiD->samplerMgr.getShaderVisibleDescriptor(desc); + } + return shaderVisibleDescriptor; +} + +QD3D12TextureRenderTarget::QD3D12TextureRenderTarget(QRhiImplementation *rhi, + const QRhiTextureRenderTargetDescription &desc, + Flags flags) + : QRhiTextureRenderTarget(rhi, desc, flags), + d(rhi) +{ +} + +QD3D12TextureRenderTarget::~QD3D12TextureRenderTarget() +{ + destroy(); +} + +void QD3D12TextureRenderTarget::destroy() +{ + if (!rtv[0].isValid() && !dsv.isValid()) + return; + + QRHI_RES_RHI(QRhiD3D12); + if (dsv.isValid()) { + if (ownsDsv && rhiD) + rhiD->releaseQueue.deferredReleaseViews(&rhiD->dsvPool, dsv, 1); + dsv = {}; + } + + for (int i = 0; i < QD3D12RenderTargetData::MAX_COLOR_ATTACHMENTS; ++i) { + if (rtv[i].isValid()) { + if (ownsRtv[i] && rhiD) + rhiD->releaseQueue.deferredReleaseViews(&rhiD->rtvPool, rtv[i], 1); + rtv[i] = {}; + } + } + + if (rhiD) + rhiD->unregisterResource(this); +} + +QRhiRenderPassDescriptor *QD3D12TextureRenderTarget::newCompatibleRenderPassDescriptor() +{ + // not yet built so cannot rely on data computed in create() + + QD3D12RenderPassDescriptor *rpD = new QD3D12RenderPassDescriptor(m_rhi); + + rpD->colorAttachmentCount = 0; + for (auto it = m_desc.cbeginColorAttachments(), itEnd = m_desc.cendColorAttachments(); it != itEnd; ++it) { + QD3D12Texture *texD = QRHI_RES(QD3D12Texture, it->texture()); + QD3D12RenderBuffer *rbD = QRHI_RES(QD3D12RenderBuffer, it->renderBuffer()); + if (texD) + rpD->colorFormat[rpD->colorAttachmentCount] = texD->dxgiFormat; + else if (rbD) + rpD->colorFormat[rpD->colorAttachmentCount] = rbD->dxgiFormat; + rpD->colorAttachmentCount += 1; + } + + rpD->hasDepthStencil = false; + if (m_desc.depthStencilBuffer()) { + rpD->hasDepthStencil = true; + rpD->dsFormat = QD3D12RenderBuffer::DS_FORMAT; + } else if (m_desc.depthTexture()) { + QD3D12Texture *depthTexD = QRHI_RES(QD3D12Texture, m_desc.depthTexture()); + rpD->hasDepthStencil = true; + rpD->dsFormat = toD3DDepthTextureDSVFormat(depthTexD->format()); // cannot be a typeless format + } + + rpD->updateSerializedFormat(); + + QRHI_RES_RHI(QRhiD3D12); + rhiD->registerResource(rpD); + return rpD; +} + +bool QD3D12TextureRenderTarget::create() +{ + if (rtv[0].isValid() || dsv.isValid()) + destroy(); + + QRHI_RES_RHI(QRhiD3D12); + Q_ASSERT(m_desc.colorAttachmentCount() > 0 || m_desc.depthTexture()); + Q_ASSERT(!m_desc.depthStencilBuffer() || !m_desc.depthTexture()); + const bool hasDepthStencil = m_desc.depthStencilBuffer() || m_desc.depthTexture(); + d.colorAttCount = 0; + int attIndex = 0; + + for (auto it = m_desc.cbeginColorAttachments(), itEnd = m_desc.cendColorAttachments(); it != itEnd; ++it, ++attIndex) { + d.colorAttCount += 1; + const QRhiColorAttachment &colorAtt(*it); + QRhiTexture *texture = colorAtt.texture(); + QRhiRenderBuffer *rb = colorAtt.renderBuffer(); + Q_ASSERT(texture || rb); + if (texture) { + QD3D12Texture *texD = QRHI_RES(QD3D12Texture, texture); + QD3D12Resource *res = rhiD->resourcePool.lookupRef(texD->handle); + if (!res) { + qWarning("Could not look up texture handle for render target"); + return false; + } + D3D12_RENDER_TARGET_VIEW_DESC rtvDesc = {}; + rtvDesc.Format = toD3DTextureFormat(texD->format(), texD->flags()); + 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; + } 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; + } else { + rtvDesc.ViewDimension = D3D12_RTV_DIMENSION_TEXTURE1D; + rtvDesc.Texture1D.MipSlice = UINT(colorAtt.level()); + } + } else if (texD->flags().testFlag(QRhiTexture::TextureArray)) { + if (texD->sampleDesc.Count > 1) { + rtvDesc.ViewDimension = D3D12_RTV_DIMENSION_TEXTURE2DMSARRAY; + rtvDesc.Texture2DMSArray.FirstArraySlice = UINT(colorAtt.layer()); + rtvDesc.Texture2DMSArray.ArraySize = 1; + } else { + rtvDesc.ViewDimension = D3D12_RTV_DIMENSION_TEXTURE2DARRAY; + rtvDesc.Texture2DArray.MipSlice = UINT(colorAtt.level()); + rtvDesc.Texture2DArray.FirstArraySlice = UINT(colorAtt.layer()); + rtvDesc.Texture2DArray.ArraySize = 1; + } + } 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; + } else { + if (texD->sampleDesc.Count > 1) { + rtvDesc.ViewDimension = D3D12_RTV_DIMENSION_TEXTURE2DMS; + } else { + rtvDesc.ViewDimension = D3D12_RTV_DIMENSION_TEXTURE2D; + rtvDesc.Texture2D.MipSlice = UINT(colorAtt.level()); + } + } + rtv[attIndex] = rhiD->rtvPool.allocate(1); + if (!rtv[attIndex].isValid()) { + qWarning("Failed to allocate RTV for texture render target"); + return false; + } + rhiD->dev->CreateRenderTargetView(res->resource, &rtvDesc, rtv[attIndex].cpuHandle); + ownsRtv[attIndex] = true; + if (attIndex == 0) { + d.pixelSize = rhiD->q->sizeForMipLevel(colorAtt.level(), texD->pixelSize()); + d.sampleCount = int(texD->sampleDesc.Count); + } + } else if (rb) { + QD3D12RenderBuffer *rbD = QRHI_RES(QD3D12RenderBuffer, rb); + ownsRtv[attIndex] = false; + rtv[attIndex] = rbD->rtv; + if (attIndex == 0) { + d.pixelSize = rbD->pixelSize(); + d.sampleCount = int(rbD->sampleDesc.Count); + } + } + } + + d.dpr = 1; + + if (hasDepthStencil) { + if (m_desc.depthTexture()) { + ownsDsv = true; + QD3D12Texture *depthTexD = QRHI_RES(QD3D12Texture, m_desc.depthTexture()); + QD3D12Resource *res = rhiD->resourcePool.lookupRef(depthTexD->handle); + if (!res) { + qWarning("Could not look up depth texture handle"); + return false; + } + D3D12_DEPTH_STENCIL_VIEW_DESC dsvDesc = {}; + dsvDesc.Format = toD3DDepthTextureDSVFormat(depthTexD->format()); + 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"); + return false; + } + rhiD->dev->CreateDepthStencilView(res->resource, &dsvDesc, dsv.cpuHandle); + if (d.colorAttCount == 0) { + d.pixelSize = depthTexD->pixelSize(); + d.sampleCount = int(depthTexD->sampleDesc.Count); + } + } else { + ownsDsv = false; + QD3D12RenderBuffer *depthRbD = QRHI_RES(QD3D12RenderBuffer, m_desc.depthStencilBuffer()); + dsv = depthRbD->dsv; + if (d.colorAttCount == 0) { + d.pixelSize = m_desc.depthStencilBuffer()->pixelSize(); + d.sampleCount = int(depthRbD->sampleDesc.Count); + } + } + d.dsAttCount = 1; + } else { + d.dsAttCount = 0; + } + + D3D12_CPU_DESCRIPTOR_HANDLE nullDescHandle = { 0 }; + for (int i = 0; i < QD3D12RenderTargetData::MAX_COLOR_ATTACHMENTS; ++i) + d.rtv[i] = i < d.colorAttCount ? rtv[i].cpuHandle : nullDescHandle; + d.dsv = dsv.cpuHandle; + d.rp = QRHI_RES(QD3D12RenderPassDescriptor, m_renderPassDesc); + + QRhiRenderTargetAttachmentTracker::updateResIdList(m_desc, &d.currentResIdList); + + rhiD->registerResource(this); + return true; +} + +QSize QD3D12TextureRenderTarget::pixelSize() const +{ + if (!QRhiRenderTargetAttachmentTracker::isUpToDate(m_desc, d.currentResIdList)) + const_cast(this)->create(); + + return d.pixelSize; +} + +float QD3D12TextureRenderTarget::devicePixelRatio() const +{ + return d.dpr; +} + +int QD3D12TextureRenderTarget::sampleCount() const +{ + return d.sampleCount; +} + +QD3D12ShaderResourceBindings::QD3D12ShaderResourceBindings(QRhiImplementation *rhi) + : QRhiShaderResourceBindings(rhi) +{ +} + +QD3D12ShaderResourceBindings::~QD3D12ShaderResourceBindings() +{ + destroy(); +} + +void QD3D12ShaderResourceBindings::destroy() +{ + sortedBindings.clear(); + + QRHI_RES_RHI(QRhiD3D12); + if (rhiD) + rhiD->unregisterResource(this); +} + +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) { + const QRhiShaderResourceBinding::Data *bd = QRhiImplementation::shaderResourceBindingData(b); + if (bd->type == QRhiShaderResourceBinding::UniformBuffer && bd->u.ubuf.hasDynamicOffset) { + hasDynamicOffset = true; + break; + } + } + + // The root signature is not part of the srb. Unintuitive, but the shader + // translation pipeline ties our hands: as long as the per-shader (so per + // stage!) nativeResourceBindingMap exist, meaning f.ex. that a SPIR-V + // combined image sampler binding X passed in here may map to the tY and sY + // HLSL registers, where Y is known only once the mapping table from the + // shader is looked up. Creating a root parameters at this stage is + // therefore impossible. + + generation += 1; + rhiD->registerResource(this, false); + return true; +} + +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); + + generation += 1; +} + +// Accessing the QRhiBuffer/Texture/Sampler resources must be avoided in the +// callbacks; that would only be possible if the srb had those specified, and +// that's not required at the time of srb and pipeline create() time, and +// createRootSignature is called from the pipeline create(). + +void QD3D12ShaderResourceBindings::visitUniformBuffer(QD3D12Stage s, + const QRhiShaderResourceBinding::Data::UniformBufferData &, + int shaderRegister, + int) +{ + D3D12_ROOT_PARAMETER1 rootParam = {}; + rootParam.ParameterType = D3D12_ROOT_PARAMETER_TYPE_CBV; + rootParam.ShaderVisibility = qd3d12_stageToVisibility(s); + rootParam.Descriptor.ShaderRegister = shaderRegister; + visitorData.cbParams[s].append(rootParam); +} + +void QD3D12ShaderResourceBindings::visitTexture(QD3D12Stage s, + const QRhiShaderResourceBinding::TextureAndSampler &, + int shaderRegister) +{ + D3D12_DESCRIPTOR_RANGE1 range = {}; + range.RangeType = D3D12_DESCRIPTOR_RANGE_TYPE_SRV; + range.NumDescriptors = 1; + range.BaseShaderRegister = shaderRegister; + range.OffsetInDescriptorsFromTableStart = visitorData.currentSrvRangeOffset[s]; + visitorData.currentSrvRangeOffset[s] += 1; + visitorData.srvRanges[s].append(range); + if (visitorData.srvRanges[s].count() == 1) { + visitorData.srvTables[s].ParameterType = D3D12_ROOT_PARAMETER_TYPE_DESCRIPTOR_TABLE; + visitorData.srvTables[s].ShaderVisibility = qd3d12_stageToVisibility(s); + } +} + +void QD3D12ShaderResourceBindings::visitSampler(QD3D12Stage s, + const QRhiShaderResourceBinding::TextureAndSampler &, + int shaderRegister) +{ + // Unlike SRVs and UAVs, samplers are handled so that each sampler becomes + // a root parameter with its own descriptor table. + + int &rangeStoreIdx(visitorData.samplerRangeHeads[s]); + if (rangeStoreIdx == 16) { + qWarning("Sampler count in QD3D12Stage %d exceeds the limit of 16, this is disallowed by QRhi", s); + return; + } + D3D12_DESCRIPTOR_RANGE1 range = {}; + range.RangeType = D3D12_DESCRIPTOR_RANGE_TYPE_SAMPLER; + range.NumDescriptors = 1; + range.BaseShaderRegister = shaderRegister; + visitorData.samplerRanges[s][rangeStoreIdx] = range; + D3D12_ROOT_PARAMETER1 param = {}; + param.ParameterType = D3D12_ROOT_PARAMETER_TYPE_DESCRIPTOR_TABLE; + param.ShaderVisibility = qd3d12_stageToVisibility(s); + param.DescriptorTable.NumDescriptorRanges = 1; + param.DescriptorTable.pDescriptorRanges = &visitorData.samplerRanges[s][rangeStoreIdx]; + rangeStoreIdx += 1; + visitorData.samplerTables[s].append(param); +} + +void QD3D12ShaderResourceBindings::visitStorageBuffer(QD3D12Stage s, + const QRhiShaderResourceBinding::Data::StorageBufferData &, + QD3D12ShaderResourceVisitor::StorageOp, + int shaderRegister) +{ + D3D12_DESCRIPTOR_RANGE1 range = {}; + range.RangeType = D3D12_DESCRIPTOR_RANGE_TYPE_UAV; + range.NumDescriptors = 1; + range.BaseShaderRegister = shaderRegister; + range.OffsetInDescriptorsFromTableStart = visitorData.currentUavRangeOffset[s]; + visitorData.currentUavRangeOffset[s] += 1; + visitorData.uavRanges[s].append(range); + if (visitorData.uavRanges[s].count() == 1) { + visitorData.uavTables[s].ParameterType = D3D12_ROOT_PARAMETER_TYPE_DESCRIPTOR_TABLE; + visitorData.uavTables[s].ShaderVisibility = qd3d12_stageToVisibility(s); + } +} + +void QD3D12ShaderResourceBindings::visitStorageImage(QD3D12Stage s, + const QRhiShaderResourceBinding::Data::StorageImageData &, + QD3D12ShaderResourceVisitor::StorageOp, + int shaderRegister) +{ + D3D12_DESCRIPTOR_RANGE1 range = {}; + range.RangeType = D3D12_DESCRIPTOR_RANGE_TYPE_UAV; + range.NumDescriptors = 1; + range.BaseShaderRegister = shaderRegister; + range.OffsetInDescriptorsFromTableStart = visitorData.currentUavRangeOffset[s]; + visitorData.currentUavRangeOffset[s] += 1; + visitorData.uavRanges[s].append(range); + if (visitorData.uavRanges[s].count() == 1) { + visitorData.uavTables[s].ParameterType = D3D12_ROOT_PARAMETER_TYPE_DESCRIPTOR_TABLE; + visitorData.uavTables[s].ShaderVisibility = qd3d12_stageToVisibility(s); + } +} + +QD3D12ObjectHandle QD3D12ShaderResourceBindings::createRootSignature(const QD3D12ShaderStageData *stageData, + int stageCount) +{ + QRHI_RES_RHI(QRhiD3D12); + + // It's not just that the root signature has to be tied to the pipeline + // (cannot just freely create it like e.g. with Vulkan where one just + // creates a descriptor layout 1:1 with the QRhiShaderResourceBindings' + // data), due to not knowing the shader-specific resource binding mapping + // tables at the point of srb creation, but each shader stage may have a + // different mapping table. (ugh!) + // + // Hence we set up everything per-stage, even if it means the root + // signature gets unnecessarily big. (note that the magic is in the + // ShaderVisibility: even though the register range is the same in the + // descriptor tables, the visibility is different) + + QD3D12ShaderResourceVisitor visitor(this, stageData, stageCount); + + visitorData = {}; + + using namespace std::placeholders; + visitor.uniformBuffer = std::bind(&QD3D12ShaderResourceBindings::visitUniformBuffer, this, _1, _2, _3, _4); + visitor.texture = std::bind(&QD3D12ShaderResourceBindings::visitTexture, this, _1, _2, _3); + visitor.sampler = std::bind(&QD3D12ShaderResourceBindings::visitSampler, this, _1, _2, _3); + visitor.storageBuffer = std::bind(&QD3D12ShaderResourceBindings::visitStorageBuffer, this, _1, _2, _3, _4); + visitor.storageImage = std::bind(&QD3D12ShaderResourceBindings::visitStorageImage, this, _1, _2, _3, _4); + + visitor.visit(); + + // The maximum size of a root signature is 256 bytes, where a descriptor + // table is 4, a root descriptor (e.g. CBV) is 8. We have 5 stages at most + // (or 1 with compute) and a separate descriptor table for SRVs (-> + // textures) and UAVs (-> storage buffers and images) per stage, plus each + // uniform buffer counts as a CBV in the stages it is visible. + // + // Due to the limited maximum size of a shader-visible sampler heap (2048) + // and the potential costly switching of descriptor heaps, each sampler is + // declared as a separate root parameter / descriptor table (meaning that + // two samplers in the same stage are two parameters and two tables, not + // just one). QRhi documents a hard limit of 16 on texture/sampler bindings + // in a shader (matching D3D11), so we can hopefully get away with this. + // + // This means that e.g. a vertex+fragment shader with a uniform buffer + // visible in both and one texture+sampler in the fragment shader would + // consume 2*8 + 4 + 4 = 24 bytes. This also implies that clients + // specifying the minimal stage bit mask for each entry in + // QRhiShaderResourceBindings are ideal for this backend since it helps + // reducing the chance of hitting the size limit. + + QVarLengthArray rootParams; + for (int s = 0; s < 6; ++s) { + if (!visitorData.cbParams[s].isEmpty()) + rootParams.append(visitorData.cbParams[s].constData(), visitorData.cbParams[s].count()); + } + for (int s = 0; s < 6; ++s) { + if (!visitorData.srvRanges[s].isEmpty()) { + visitorData.srvTables[s].DescriptorTable.NumDescriptorRanges = visitorData.srvRanges[s].count(); + visitorData.srvTables[s].DescriptorTable.pDescriptorRanges = visitorData.srvRanges[s].constData(); + rootParams.append(visitorData.srvTables[s]); + } + } + for (int s = 0; s < 6; ++s) { + if (!visitorData.samplerTables[s].isEmpty()) + rootParams.append(visitorData.samplerTables[s].constData(), visitorData.samplerTables[s].count()); + } + for (int s = 0; s < 6; ++s) { + if (!visitorData.uavRanges[s].isEmpty()) { + visitorData.uavTables[s].DescriptorTable.NumDescriptorRanges = visitorData.uavRanges[s].count(); + visitorData.uavTables[s].DescriptorTable.pDescriptorRanges = visitorData.uavRanges[s].constData(); + rootParams.append(visitorData.uavTables[s]); + } + } + + D3D12_VERSIONED_ROOT_SIGNATURE_DESC rsDesc = {}; + rsDesc.Version = D3D_ROOT_SIGNATURE_VERSION_1_1; + if (!rootParams.isEmpty()) { + rsDesc.Desc_1_1.NumParameters = rootParams.count(); + rsDesc.Desc_1_1.pParameters = rootParams.constData(); + } + + UINT rsFlags = 0; + for (int stageIdx = 0; stageIdx < stageCount; ++stageIdx) { + if (stageData[stageIdx].valid && stageData[stageIdx].stage == VS) + rsFlags |= D3D12_ROOT_SIGNATURE_FLAG_ALLOW_INPUT_ASSEMBLER_INPUT_LAYOUT; + } + rsDesc.Desc_1_1.Flags = D3D12_ROOT_SIGNATURE_FLAGS(rsFlags); + + ID3DBlob *signature = nullptr; + HRESULT hr = D3D12SerializeVersionedRootSignature(&rsDesc, &signature, nullptr); + if (FAILED(hr)) { + qWarning("Failed to serialize root signature: %s", qPrintable(QSystemError::windowsComString(hr))); + return {}; + } + ID3D12RootSignature *rootSig = nullptr; + hr = rhiD->dev->CreateRootSignature(0, + signature->GetBufferPointer(), + signature->GetBufferSize(), + __uuidof(ID3D12RootSignature), + reinterpret_cast(&rootSig)); + signature->Release(); + if (FAILED(hr)) { + qWarning("Failed to create root signature: %s", qPrintable(QSystemError::windowsComString(hr))); + return {}; + } + + 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 inline void makeHlslTargetString(char target[7], const char stage[3], int version) +{ + const int smMajor = version / 10; + const int smMinor = version % 10; + target[0] = stage[0]; + target[1] = stage[1]; + target[2] = '_'; + target[3] = '0' + smMajor; + target[4] = '_'; + target[5] = '0' + smMinor; + target[6] = '\0'; +} + +static QByteArray compileHlslShaderSource(const QShader &shader, + QShader::Variant shaderVariant, + UINT flags, + QString *error, + QShaderKey *usedShaderKey) +{ + // look for SM 6.7, 6.6, .., 5.0 + const int shaderModelMax = 67; + for (int sm = shaderModelMax; sm >= 50; --sm) { + for (QShader::Source type : { QShader::DxilShader, QShader::DxbcShader }) { + QShaderKey key = { type, sm, shaderVariant }; + QShaderCode intermediateBytecodeShader = shader.shader(key); + if (!intermediateBytecodeShader.shader().isEmpty()) { + if (usedShaderKey) + *usedShaderKey = key; + return intermediateBytecodeShader.shader(); + } + } + } + + QShaderCode hlslSource; + QShaderKey key; + for (int sm = shaderModelMax; sm >= 50; --sm) { + key = { QShader::HlslShader, sm, shaderVariant }; + hlslSource = shader.shader(key); + if (!hlslSource.shader().isEmpty()) + break; + } + + if (hlslSource.shader().isEmpty()) { + qWarning() << "No HLSL (shader model 6.7..5.0) code found in baked shader" << shader; + return QByteArray(); + } + + if (usedShaderKey) + *usedShaderKey = key; + + char target[7]; + switch (shader.stage()) { + case QShader::VertexStage: + makeHlslTargetString(target, "vs", key.sourceVersion().version()); + break; + case QShader::TessellationControlStage: + makeHlslTargetString(target, "hs", key.sourceVersion().version()); + break; + case QShader::TessellationEvaluationStage: + makeHlslTargetString(target, "ds", key.sourceVersion().version()); + break; + case QShader::GeometryStage: + makeHlslTargetString(target, "gs", key.sourceVersion().version()); + break; + case QShader::FragmentStage: + makeHlslTargetString(target, "ps", key.sourceVersion().version()); + break; + case QShader::ComputeStage: + makeHlslTargetString(target, "cs", key.sourceVersion().version()); + break; + } + + static const pD3DCompile d3dCompile = QRhiD3D::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(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; +} + +static inline UINT8 toD3DColorWriteMask(QRhiGraphicsPipeline::ColorMask c) +{ + UINT8 f = 0; + if (c.testFlag(QRhiGraphicsPipeline::R)) + f |= D3D12_COLOR_WRITE_ENABLE_RED; + if (c.testFlag(QRhiGraphicsPipeline::G)) + f |= D3D12_COLOR_WRITE_ENABLE_GREEN; + if (c.testFlag(QRhiGraphicsPipeline::B)) + f |= D3D12_COLOR_WRITE_ENABLE_BLUE; + if (c.testFlag(QRhiGraphicsPipeline::A)) + f |= D3D12_COLOR_WRITE_ENABLE_ALPHA; + return f; +} + +static inline D3D12_BLEND toD3DBlendFactor(QRhiGraphicsPipeline::BlendFactor f, bool rgb) +{ + // SrcBlendAlpha and DstBlendAlpha do not accept *_COLOR. With other APIs + // this is handled internally (so that e.g. VK_BLEND_FACTOR_SRC_COLOR is + // accepted and is in effect equivalent to VK_BLEND_FACTOR_SRC_ALPHA when + // set as an alpha src/dest factor), but for D3D we have to take care of it + // ourselves. Hence the rgb argument. + + switch (f) { + case QRhiGraphicsPipeline::Zero: + return D3D12_BLEND_ZERO; + case QRhiGraphicsPipeline::One: + return D3D12_BLEND_ONE; + case QRhiGraphicsPipeline::SrcColor: + return rgb ? D3D12_BLEND_SRC_COLOR : D3D12_BLEND_SRC_ALPHA; + case QRhiGraphicsPipeline::OneMinusSrcColor: + return rgb ? D3D12_BLEND_INV_SRC_COLOR : D3D12_BLEND_INV_SRC_ALPHA; + case QRhiGraphicsPipeline::DstColor: + return rgb ? D3D12_BLEND_DEST_COLOR : D3D12_BLEND_DEST_ALPHA; + case QRhiGraphicsPipeline::OneMinusDstColor: + return rgb ? D3D12_BLEND_INV_DEST_COLOR : D3D12_BLEND_INV_DEST_ALPHA; + case QRhiGraphicsPipeline::SrcAlpha: + return D3D12_BLEND_SRC_ALPHA; + case QRhiGraphicsPipeline::OneMinusSrcAlpha: + return D3D12_BLEND_INV_SRC_ALPHA; + case QRhiGraphicsPipeline::DstAlpha: + return D3D12_BLEND_DEST_ALPHA; + case QRhiGraphicsPipeline::OneMinusDstAlpha: + return D3D12_BLEND_INV_DEST_ALPHA; + case QRhiGraphicsPipeline::ConstantColor: + case QRhiGraphicsPipeline::ConstantAlpha: + return D3D12_BLEND_BLEND_FACTOR; + case QRhiGraphicsPipeline::OneMinusConstantColor: + case QRhiGraphicsPipeline::OneMinusConstantAlpha: + return D3D12_BLEND_INV_BLEND_FACTOR; + case QRhiGraphicsPipeline::SrcAlphaSaturate: + return D3D12_BLEND_SRC_ALPHA_SAT; + case QRhiGraphicsPipeline::Src1Color: + return rgb ? D3D12_BLEND_SRC1_COLOR : D3D12_BLEND_SRC1_ALPHA; + case QRhiGraphicsPipeline::OneMinusSrc1Color: + return rgb ? D3D12_BLEND_INV_SRC1_COLOR : D3D12_BLEND_INV_SRC1_ALPHA; + case QRhiGraphicsPipeline::Src1Alpha: + return D3D12_BLEND_SRC1_ALPHA; + case QRhiGraphicsPipeline::OneMinusSrc1Alpha: + return D3D12_BLEND_INV_SRC1_ALPHA; + } + Q_UNREACHABLE_RETURN(D3D12_BLEND_ZERO); +} + +static inline D3D12_BLEND_OP toD3DBlendOp(QRhiGraphicsPipeline::BlendOp op) +{ + switch (op) { + case QRhiGraphicsPipeline::Add: + return D3D12_BLEND_OP_ADD; + case QRhiGraphicsPipeline::Subtract: + return D3D12_BLEND_OP_SUBTRACT; + case QRhiGraphicsPipeline::ReverseSubtract: + return D3D12_BLEND_OP_REV_SUBTRACT; + case QRhiGraphicsPipeline::Min: + return D3D12_BLEND_OP_MIN; + case QRhiGraphicsPipeline::Max: + return D3D12_BLEND_OP_MAX; + } + Q_UNREACHABLE_RETURN(D3D12_BLEND_OP_ADD); +} + +static inline D3D12_CULL_MODE toD3DCullMode(QRhiGraphicsPipeline::CullMode c) +{ + switch (c) { + case QRhiGraphicsPipeline::None: + return D3D12_CULL_MODE_NONE; + case QRhiGraphicsPipeline::Front: + return D3D12_CULL_MODE_FRONT; + case QRhiGraphicsPipeline::Back: + return D3D12_CULL_MODE_BACK; + } + Q_UNREACHABLE_RETURN(D3D12_CULL_MODE_NONE); +} + +static inline D3D12_FILL_MODE toD3DFillMode(QRhiGraphicsPipeline::PolygonMode mode) +{ + switch (mode) { + case QRhiGraphicsPipeline::Fill: + return D3D12_FILL_MODE_SOLID; + case QRhiGraphicsPipeline::Line: + return D3D12_FILL_MODE_WIREFRAME; + } + Q_UNREACHABLE_RETURN(D3D12_FILL_MODE_SOLID); +} + +static inline D3D12_COMPARISON_FUNC toD3DCompareOp(QRhiGraphicsPipeline::CompareOp op) +{ + switch (op) { + case QRhiGraphicsPipeline::Never: + return D3D12_COMPARISON_FUNC_NEVER; + case QRhiGraphicsPipeline::Less: + return D3D12_COMPARISON_FUNC_LESS; + case QRhiGraphicsPipeline::Equal: + return D3D12_COMPARISON_FUNC_EQUAL; + case QRhiGraphicsPipeline::LessOrEqual: + return D3D12_COMPARISON_FUNC_LESS_EQUAL; + case QRhiGraphicsPipeline::Greater: + return D3D12_COMPARISON_FUNC_GREATER; + case QRhiGraphicsPipeline::NotEqual: + return D3D12_COMPARISON_FUNC_NOT_EQUAL; + case QRhiGraphicsPipeline::GreaterOrEqual: + return D3D12_COMPARISON_FUNC_GREATER_EQUAL; + case QRhiGraphicsPipeline::Always: + return D3D12_COMPARISON_FUNC_ALWAYS; + } + Q_UNREACHABLE_RETURN(D3D12_COMPARISON_FUNC_ALWAYS); +} + +static inline D3D12_STENCIL_OP toD3DStencilOp(QRhiGraphicsPipeline::StencilOp op) +{ + switch (op) { + case QRhiGraphicsPipeline::StencilZero: + return D3D12_STENCIL_OP_ZERO; + case QRhiGraphicsPipeline::Keep: + return D3D12_STENCIL_OP_KEEP; + case QRhiGraphicsPipeline::Replace: + return D3D12_STENCIL_OP_REPLACE; + case QRhiGraphicsPipeline::IncrementAndClamp: + return D3D12_STENCIL_OP_INCR_SAT; + case QRhiGraphicsPipeline::DecrementAndClamp: + return D3D12_STENCIL_OP_DECR_SAT; + case QRhiGraphicsPipeline::Invert: + return D3D12_STENCIL_OP_INVERT; + case QRhiGraphicsPipeline::IncrementAndWrap: + return D3D12_STENCIL_OP_INCR; + case QRhiGraphicsPipeline::DecrementAndWrap: + return D3D12_STENCIL_OP_DECR; + } + Q_UNREACHABLE_RETURN(D3D12_STENCIL_OP_KEEP); +} + +static inline D3D12_PRIMITIVE_TOPOLOGY toD3DTopology(QRhiGraphicsPipeline::Topology t, int patchControlPointCount) +{ + switch (t) { + case QRhiGraphicsPipeline::Triangles: + return D3D_PRIMITIVE_TOPOLOGY_TRIANGLELIST; + case QRhiGraphicsPipeline::TriangleStrip: + return D3D_PRIMITIVE_TOPOLOGY_TRIANGLESTRIP; + case QRhiGraphicsPipeline::TriangleFan: + qWarning("Triangle fans are not supported with D3D"); + return D3D_PRIMITIVE_TOPOLOGY_TRIANGLESTRIP; + case QRhiGraphicsPipeline::Lines: + return D3D_PRIMITIVE_TOPOLOGY_LINELIST; + case QRhiGraphicsPipeline::LineStrip: + return D3D_PRIMITIVE_TOPOLOGY_LINESTRIP; + case QRhiGraphicsPipeline::Points: + return D3D_PRIMITIVE_TOPOLOGY_POINTLIST; + case QRhiGraphicsPipeline::Patches: + Q_ASSERT(patchControlPointCount >= 1 && patchControlPointCount <= 32); + return D3D_PRIMITIVE_TOPOLOGY(D3D_PRIMITIVE_TOPOLOGY_1_CONTROL_POINT_PATCHLIST + (patchControlPointCount - 1)); + } + Q_UNREACHABLE_RETURN(D3D_PRIMITIVE_TOPOLOGY_TRIANGLELIST); +} + +static inline D3D12_PRIMITIVE_TOPOLOGY_TYPE toD3DTopologyType(QRhiGraphicsPipeline::Topology t) +{ + switch (t) { + case QRhiGraphicsPipeline::Triangles: + case QRhiGraphicsPipeline::TriangleStrip: + case QRhiGraphicsPipeline::TriangleFan: + return D3D12_PRIMITIVE_TOPOLOGY_TYPE_TRIANGLE; + case QRhiGraphicsPipeline::Lines: + case QRhiGraphicsPipeline::LineStrip: + return D3D12_PRIMITIVE_TOPOLOGY_TYPE_LINE; + case QRhiGraphicsPipeline::Points: + return D3D12_PRIMITIVE_TOPOLOGY_TYPE_POINT; + case QRhiGraphicsPipeline::Patches: + return D3D12_PRIMITIVE_TOPOLOGY_TYPE_PATCH; + } + Q_UNREACHABLE_RETURN(D3D12_PRIMITIVE_TOPOLOGY_TYPE_TRIANGLE); +} + +static inline DXGI_FORMAT toD3DAttributeFormat(QRhiVertexInputAttribute::Format format) +{ + switch (format) { + case QRhiVertexInputAttribute::Float4: + return DXGI_FORMAT_R32G32B32A32_FLOAT; + case QRhiVertexInputAttribute::Float3: + return DXGI_FORMAT_R32G32B32_FLOAT; + case QRhiVertexInputAttribute::Float2: + return DXGI_FORMAT_R32G32_FLOAT; + case QRhiVertexInputAttribute::Float: + return DXGI_FORMAT_R32_FLOAT; + case QRhiVertexInputAttribute::UNormByte4: + return DXGI_FORMAT_R8G8B8A8_UNORM; + case QRhiVertexInputAttribute::UNormByte2: + return DXGI_FORMAT_R8G8_UNORM; + case QRhiVertexInputAttribute::UNormByte: + return DXGI_FORMAT_R8_UNORM; + case QRhiVertexInputAttribute::UInt4: + return DXGI_FORMAT_R32G32B32A32_UINT; + case QRhiVertexInputAttribute::UInt3: + return DXGI_FORMAT_R32G32B32_UINT; + case QRhiVertexInputAttribute::UInt2: + return DXGI_FORMAT_R32G32_UINT; + case QRhiVertexInputAttribute::UInt: + return DXGI_FORMAT_R32_UINT; + case QRhiVertexInputAttribute::SInt4: + return DXGI_FORMAT_R32G32B32A32_SINT; + case QRhiVertexInputAttribute::SInt3: + return DXGI_FORMAT_R32G32B32_SINT; + case QRhiVertexInputAttribute::SInt2: + return DXGI_FORMAT_R32G32_SINT; + case QRhiVertexInputAttribute::SInt: + return DXGI_FORMAT_R32_SINT; + case QRhiVertexInputAttribute::Half4: + // Note: D3D does not support half3. Pass through half3 as half4. + case QRhiVertexInputAttribute::Half3: + return DXGI_FORMAT_R16G16B16A16_FLOAT; + case QRhiVertexInputAttribute::Half2: + return DXGI_FORMAT_R16G16_FLOAT; + case QRhiVertexInputAttribute::Half: + return DXGI_FORMAT_R16_FLOAT; + } + Q_UNREACHABLE_RETURN(DXGI_FORMAT_R32G32B32A32_FLOAT); +} + +QD3D12GraphicsPipeline::QD3D12GraphicsPipeline(QRhiImplementation *rhi) + : QRhiGraphicsPipeline(rhi) +{ +} + +QD3D12GraphicsPipeline::~QD3D12GraphicsPipeline() +{ + destroy(); +} + +void QD3D12GraphicsPipeline::destroy() +{ + if (handle.isNull()) + return; + + QRHI_RES_RHI(QRhiD3D12); + if (rhiD) { + rhiD->releaseQueue.deferredReleasePipeline(handle); + rhiD->releaseQueue.deferredReleaseRootSignature(rootSigHandle); + } + + handle = {}; + stageData = {}; + + if (rhiD) + rhiD->unregisterResource(this); +} + +bool QD3D12GraphicsPipeline::create() +{ + if (!handle.isNull()) + destroy(); + + QRHI_RES_RHI(QRhiD3D12); + if (!rhiD->sanityCheckGraphicsPipeline(this)) + return false; + + rhiD->pipelineCreationStart(); + + QByteArray shaderBytecode[5]; + for (const QRhiShaderStage &shaderStage : std::as_const(m_shaderStages)) { + const QD3D12Stage d3dStage = qd3d12_stage(shaderStage.type()); + stageData[d3dStage].valid = true; + stageData[d3dStage].stage = d3dStage; + auto cacheIt = rhiD->shaderBytecodeCache.data.constFind(shaderStage); + if (cacheIt != rhiD->shaderBytecodeCache.data.constEnd()) { + shaderBytecode[d3dStage] = cacheIt->bytecode; + stageData[d3dStage].nativeResourceBindingMap = cacheIt->nativeResourceBindingMap; + } else { + QString error; + QShaderKey shaderKey; + UINT compileFlags = 0; + if (m_flags.testFlag(CompileShadersWithDebugInfo)) + compileFlags |= D3DCOMPILE_DEBUG; + const QByteArray bytecode = compileHlslShaderSource(shaderStage.shader(), + shaderStage.shaderVariant(), + compileFlags, + &error, + &shaderKey); + if (bytecode.isEmpty()) { + qWarning("HLSL compute shader compilation failed: %s", qPrintable(error)); + return false; + } + + shaderBytecode[d3dStage] = bytecode; + stageData[d3dStage].nativeResourceBindingMap = shaderStage.shader().nativeResourceBindingMap(shaderKey); + rhiD->shaderBytecodeCache.insertWithCapacityLimit(shaderStage, + { bytecode, stageData[d3dStage].nativeResourceBindingMap }); + } + } + + QD3D12ShaderResourceBindings *srbD = QRHI_RES(QD3D12ShaderResourceBindings, m_shaderResourceBindings); + if (srbD) { + rootSigHandle = srbD->createRootSignature(stageData.data(), 5); + if (rootSigHandle.isNull()) { + qWarning("Failed to create root signature"); + return false; + } + } + ID3D12RootSignature *rootSig = nullptr; + if (QD3D12RootSignature *rs = rhiD->rootSignaturePool.lookupRef(rootSigHandle)) + rootSig = rs->rootSig; + if (!rootSig) { + qWarning("Cannot create graphics pipeline state without root signature"); + return false; + } + + QD3D12RenderPassDescriptor *rpD = QRHI_RES(QD3D12RenderPassDescriptor, m_renderPassDesc); + const DXGI_SAMPLE_DESC sampleDesc = rhiD->effectiveSampleCount(m_sampleCount, DXGI_FORMAT(rpD->colorFormat[0])); + + 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(); + break; + case HS: + psoDesc.HS.pShaderBytecode = shaderBytecode[d3dStage].constData(); + psoDesc.HS.BytecodeLength = shaderBytecode[d3dStage].size(); + break; + case DS: + psoDesc.DS.pShaderBytecode = shaderBytecode[d3dStage].constData(); + psoDesc.DS.BytecodeLength = shaderBytecode[d3dStage].size(); + break; + case GS: + psoDesc.GS.pShaderBytecode = shaderBytecode[d3dStage].constData(); + psoDesc.GS.BytecodeLength = shaderBytecode[d3dStage].size(); + break; + case PS: + psoDesc.PS.pShaderBytecode = shaderBytecode[d3dStage].constData(); + psoDesc.PS.BytecodeLength = shaderBytecode[d3dStage].size(); + break; + default: + Q_UNREACHABLE(); + break; + } + } + + psoDesc.BlendState.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 = {}; + blend.BlendEnable = b.enable; + blend.SrcBlend = toD3DBlendFactor(b.srcColor, true); + blend.DestBlend = toD3DBlendFactor(b.dstColor, true); + blend.BlendOp = toD3DBlendOp(b.opColor); + blend.SrcBlendAlpha = toD3DBlendFactor(b.srcAlpha, false); + blend.DestBlendAlpha = toD3DBlendFactor(b.dstAlpha, false); + blend.BlendOpAlpha = toD3DBlendOp(b.opAlpha); + blend.RenderTargetWriteMask = toD3DColorWriteMask(b.colorWrite); + psoDesc.BlendState.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; + } + + psoDesc.SampleMask = 0xFFFFFFFF; + + 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; + + 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); + } + + QVarLengthArray 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 as the + // semantic, except for matrices that are unrolled into consecutive + // vec2/3/4s attributes and need TEXCOORD_ 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); + } + } + 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; + + ID3D12PipelineState *pso = nullptr; + HRESULT hr = rhiD->dev->CreateGraphicsPipelineState(&psoDesc, + __uuidof(ID3D12PipelineState), + reinterpret_cast(&pso)); + if (FAILED(hr)) { + qWarning("Failed to create graphics pipeline state: %s", + qPrintable(QSystemError::windowsComString(hr))); + rhiD->rootSignaturePool.remove(rootSigHandle); + rootSigHandle = {}; + return false; + } + + handle = QD3D12Pipeline::addToPool(&rhiD->pipelinePool, QD3D12Pipeline::Graphics, pso); + + rhiD->pipelineCreationEnd(); + generation += 1; + rhiD->registerResource(this); + return true; +} + +QD3D12ComputePipeline::QD3D12ComputePipeline(QRhiImplementation *rhi) + : QRhiComputePipeline(rhi) +{ +} + +QD3D12ComputePipeline::~QD3D12ComputePipeline() +{ + destroy(); +} + +void QD3D12ComputePipeline::destroy() +{ + if (handle.isNull()) + return; + + QRHI_RES_RHI(QRhiD3D12); + if (rhiD) { + rhiD->releaseQueue.deferredReleasePipeline(handle); + rhiD->releaseQueue.deferredReleaseRootSignature(rootSigHandle); + } + + handle = {}; + stageData = {}; + + if (rhiD) + rhiD->unregisterResource(this); +} + +bool QD3D12ComputePipeline::create() +{ + if (!handle.isNull()) + destroy(); + + QRHI_RES_RHI(QRhiD3D12); + rhiD->pipelineCreationStart(); + + stageData.valid = true; + stageData.stage = CS; + + QByteArray shaderBytecode; + auto cacheIt = rhiD->shaderBytecodeCache.data.constFind(m_shaderStage); + if (cacheIt != rhiD->shaderBytecodeCache.data.constEnd()) { + shaderBytecode = cacheIt->bytecode; + stageData.nativeResourceBindingMap = cacheIt->nativeResourceBindingMap; + } else { + QString error; + QShaderKey shaderKey; + UINT compileFlags = 0; + if (m_flags.testFlag(CompileShadersWithDebugInfo)) + compileFlags |= D3DCOMPILE_DEBUG; + const QByteArray bytecode = compileHlslShaderSource(m_shaderStage.shader(), + m_shaderStage.shaderVariant(), + compileFlags, + &error, + &shaderKey); + if (bytecode.isEmpty()) { + qWarning("HLSL compute shader compilation failed: %s", qPrintable(error)); + return false; + } + + shaderBytecode = bytecode; + stageData.nativeResourceBindingMap = m_shaderStage.shader().nativeResourceBindingMap(shaderKey); + rhiD->shaderBytecodeCache.insertWithCapacityLimit(m_shaderStage, { bytecode, + stageData.nativeResourceBindingMap }); + } + + QD3D12ShaderResourceBindings *srbD = QRHI_RES(QD3D12ShaderResourceBindings, m_shaderResourceBindings); + if (srbD) { + rootSigHandle = srbD->createRootSignature(&stageData, 1); + if (rootSigHandle.isNull()) { + qWarning("Failed to create root signature"); + return false; + } + } + ID3D12RootSignature *rootSig = nullptr; + if (QD3D12RootSignature *rs = rhiD->rootSignaturePool.lookupRef(rootSigHandle)) + rootSig = rs->rootSig; + if (!rootSig) { + qWarning("Cannot create compute pipeline state without root signature"); + return false; + } + + D3D12_COMPUTE_PIPELINE_STATE_DESC psoDesc = {}; + psoDesc.pRootSignature = rootSig; + psoDesc.CS.pShaderBytecode = shaderBytecode.constData(); + psoDesc.CS.BytecodeLength = shaderBytecode.size(); + ID3D12PipelineState *pso = nullptr; + HRESULT hr = rhiD->dev->CreateComputePipelineState(&psoDesc, + __uuidof(ID3D12PipelineState), + reinterpret_cast(&pso)); + if (FAILED(hr)) { + qWarning("Failed to create compute pipeline state: %s", + qPrintable(QSystemError::windowsComString(hr))); + rhiD->rootSignaturePool.remove(rootSigHandle); + rootSigHandle = {}; + return false; + } + + handle = QD3D12Pipeline::addToPool(&rhiD->pipelinePool, QD3D12Pipeline::Compute, pso); + + rhiD->pipelineCreationEnd(); + generation += 1; + rhiD->registerResource(this); + return true; +} + +// This is a lot like in the Metal backend: we need to now the rtv and dsv +// formats to create a graphics pipeline, and that's exactly what our +// "renderpass descriptor" is going to hold. +QD3D12RenderPassDescriptor::QD3D12RenderPassDescriptor(QRhiImplementation *rhi) + : QRhiRenderPassDescriptor(rhi) +{ + serializedFormatData.reserve(16); +} + +QD3D12RenderPassDescriptor::~QD3D12RenderPassDescriptor() +{ + destroy(); +} + +void QD3D12RenderPassDescriptor::destroy() +{ + QRHI_RES_RHI(QRhiD3D12); + if (rhiD) + rhiD->unregisterResource(this); +} + +bool QD3D12RenderPassDescriptor::isCompatible(const QRhiRenderPassDescriptor *other) const +{ + if (!other) + return false; + + const QD3D12RenderPassDescriptor *o = QRHI_RES(const QD3D12RenderPassDescriptor, other); + + if (colorAttachmentCount != o->colorAttachmentCount) + return false; + + if (hasDepthStencil != o->hasDepthStencil) + return false; + + for (int i = 0; i < colorAttachmentCount; ++i) { + if (colorFormat[i] != o->colorFormat[i]) + return false; + } + + if (hasDepthStencil) { + if (dsFormat != o->dsFormat) + return false; + } + + return true; +} + +void QD3D12RenderPassDescriptor::updateSerializedFormat() +{ + serializedFormatData.clear(); + auto p = std::back_inserter(serializedFormatData); + + *p++ = colorAttachmentCount; + *p++ = hasDepthStencil; + for (int i = 0; i < colorAttachmentCount; ++i) + *p++ = colorFormat[i]; + *p++ = hasDepthStencil ? dsFormat : 0; +} + +QRhiRenderPassDescriptor *QD3D12RenderPassDescriptor::newCompatibleRenderPassDescriptor() const +{ + QD3D12RenderPassDescriptor *rpD = new QD3D12RenderPassDescriptor(m_rhi); + rpD->colorAttachmentCount = colorAttachmentCount; + rpD->hasDepthStencil = hasDepthStencil; + memcpy(rpD->colorFormat, colorFormat, sizeof(colorFormat)); + rpD->dsFormat = dsFormat; + + rpD->updateSerializedFormat(); + + QRHI_RES_RHI(QRhiD3D12); + rhiD->registerResource(rpD); + return rpD; +} + +QVector QD3D12RenderPassDescriptor::serializedFormat() const +{ + return serializedFormatData; +} + +QD3D12CommandBuffer::QD3D12CommandBuffer(QRhiImplementation *rhi) + : QRhiCommandBuffer(rhi) +{ + resetState(); +} + +QD3D12CommandBuffer::~QD3D12CommandBuffer() +{ + destroy(); +} + +void QD3D12CommandBuffer::destroy() +{ + // nothing to do here, the command list is not owned by us +} + +const QRhiNativeHandles *QD3D12CommandBuffer::nativeHandles() +{ + nativeHandlesStruct.commandList = cmdList; + return &nativeHandlesStruct; +} + +QD3D12SwapChainRenderTarget::QD3D12SwapChainRenderTarget(QRhiImplementation *rhi, QRhiSwapChain *swapchain) + : QRhiSwapChainRenderTarget(rhi, swapchain), + d(rhi) +{ +} + +QD3D12SwapChainRenderTarget::~QD3D12SwapChainRenderTarget() +{ + destroy(); +} + +void QD3D12SwapChainRenderTarget::destroy() +{ + // nothing to do here +} + +QSize QD3D12SwapChainRenderTarget::pixelSize() const +{ + return d.pixelSize; +} + +float QD3D12SwapChainRenderTarget::devicePixelRatio() const +{ + return d.dpr; +} + +int QD3D12SwapChainRenderTarget::sampleCount() const +{ + return d.sampleCount; +} + +QD3D12SwapChain::QD3D12SwapChain(QRhiImplementation *rhi) + : QRhiSwapChain(rhi), + rtWrapper(rhi, this), + cbWrapper(rhi) +{ +} + +QD3D12SwapChain::~QD3D12SwapChain() +{ + destroy(); +} + +void QD3D12SwapChain::destroy() +{ + if (!swapChain) + return; + + releaseBuffers(); + + swapChain->Release(); + swapChain = nullptr; + sourceSwapChain1->Release(); + sourceSwapChain1 = nullptr; + + for (int i = 0; i < QD3D12_FRAMES_IN_FLIGHT; ++i) { + FrameResources &fr(frameRes[i]); + if (fr.fence) + fr.fence->Release(); + if (fr.fenceEvent) + CloseHandle(fr.fenceEvent); + if (fr.cmdList) + fr.cmdList->Release(); + fr = {}; + } + + if (dcompVisual) { + dcompVisual->Release(); + dcompVisual = nullptr; + } + + if (dcompTarget) { + dcompTarget->Release(); + dcompTarget = nullptr; + } + + QRHI_RES_RHI(QRhiD3D12); + if (rhiD) { + rhiD->swapchains.remove(this); + rhiD->unregisterResource(this); + } +} + +void QD3D12SwapChain::releaseBuffers() +{ + QRHI_RES_RHI(QRhiD3D12); + rhiD->waitGpu(); + for (UINT i = 0; i < BUFFER_COUNT; ++i) { + rhiD->resourcePool.remove(colorBuffers[i]); + rhiD->rtvPool.release(rtvs[i], 1); + if (!msaaBuffers[i].isNull()) + rhiD->resourcePool.remove(msaaBuffers[i]); + if (msaaRtvs[i].isValid()) + rhiD->rtvPool.release(msaaRtvs[i], 1); + } +} + +void QD3D12SwapChain::waitCommandCompletionForFrameSlot(int frameSlot) +{ + FrameResources &fr(frameRes[frameSlot]); + if (fr.fence->GetCompletedValue() < fr.fenceCounter) { + fr.fence->SetEventOnCompletion(fr.fenceCounter, fr.fenceEvent); + WaitForSingleObject(fr.fenceEvent, INFINITE); + } +} + +void QD3D12SwapChain::addCommandCompletionSignalForCurrentFrameSlot() +{ + QRHI_RES_RHI(QRhiD3D12); + FrameResources &fr(frameRes[currentFrameSlot]); + fr.fenceCounter += 1u; + rhiD->cmdQueue->Signal(fr.fence, fr.fenceCounter); +} + +QRhiCommandBuffer *QD3D12SwapChain::currentFrameCommandBuffer() +{ + return &cbWrapper; +} + +QRhiRenderTarget *QD3D12SwapChain::currentFrameRenderTarget() +{ + return &rtWrapper; +} + +QSize QD3D12SwapChain::surfacePixelSize() +{ + Q_ASSERT(m_window); + return m_window->size() * m_window->devicePixelRatio(); +} + +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(result))); + currentOutput->Release(); + } + return ok; +} + +static 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; +} + +bool QD3D12SwapChain::isFormatSupported(Format f) +{ + if (f == SDR) + return true; + + if (!m_window) { + qWarning("Attempted to call isFormatSupported() without a window set"); + return false; + } + + QRHI_RES_RHI(QRhiD3D12); + DXGI_OUTPUT_DESC1 desc1; + if (outputDesc1ForWindow(m_window, rhiD->activeAdapter, &desc1)) { + if (desc1.ColorSpace == DXGI_COLOR_SPACE_RGB_FULL_G2084_NONE_P2020) + return f == QRhiSwapChain::HDRExtendedSrgbLinear || f == QRhiSwapChain::HDR10; + } + + return false; +} + +QRhiSwapChainHdrInfo QD3D12SwapChain::hdrInfo() +{ + QRhiSwapChainHdrInfo info = QRhiSwapChain::hdrInfo(); + if (m_window) { + QRHI_RES_RHI(QRhiD3D12); + DXGI_OUTPUT_DESC1 hdrOutputDesc; + if (outputDesc1ForWindow(m_window, rhiD->activeAdapter, &hdrOutputDesc)) { + info.isHardCodedDefaults = false; + info.limitsType = QRhiSwapChainHdrInfo::LuminanceInNits; + info.limits.luminanceInNits.minLuminance = hdrOutputDesc.MinLuminance; + info.limits.luminanceInNits.maxLuminance = hdrOutputDesc.MaxLuminance; + } + } + return info; +} + +QRhiRenderPassDescriptor *QD3D12SwapChain::newCompatibleRenderPassDescriptor() +{ + // not yet built so cannot rely on data computed in createOrResize() + chooseFormats(); + + QD3D12RenderPassDescriptor *rpD = new QD3D12RenderPassDescriptor(m_rhi); + rpD->colorAttachmentCount = 1; + rpD->hasDepthStencil = m_depthStencil != nullptr; + rpD->colorFormat[0] = int(srgbAdjustedColorFormat); + rpD->dsFormat = QD3D12RenderBuffer::DS_FORMAT; + rpD->updateSerializedFormat(); + + QRHI_RES_RHI(QRhiD3D12); + rhiD->registerResource(rpD); + return rpD; +} + +bool QRhiD3D12::ensureDirectCompositionDevice() +{ + if (dcompDevice) + return true; + + qCDebug(QRHI_LOG_INFO, "Creating Direct Composition device (needed for semi-transparent windows)"); + 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; + srgbAdjustedColorFormat = m_flags.testFlag(sRGB) ? DEFAULT_SRGB_FORMAT : DEFAULT_FORMAT; + 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) { + // 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) { + case HDRExtendedSrgbLinear: + colorFormat = DXGI_FORMAT_R16G16B16A16_FLOAT; + hdrColorSpace = DXGI_COLOR_SPACE_RGB_FULL_G10_NONE_P709; + srgbAdjustedColorFormat = colorFormat; + break; + case HDR10: + colorFormat = DXGI_FORMAT_R10G10B10A2_UNORM; + hdrColorSpace = DXGI_COLOR_SPACE_RGB_FULL_G2084_NONE_P2020; + srgbAdjustedColorFormat = colorFormat; + break; + default: + break; + } + } else { + // This happens also when Use HDR is set to Off in the Windows + // Display settings. Show a helpful warning, but continue with the + // default non-HDR format. + qWarning("The output associated with the window is not HDR capable " + "(or Use HDR is Off in the Display Settings), ignoring HDR format request"); + } + } + sampleDesc = rhiD->effectiveSampleCount(m_sampleCount, colorFormat); +} + +bool QD3D12SwapChain::createOrResize() +{ + // Can be called multiple times due to window resizes - that is not the + // same as a simple destroy+create (as with other resources). Just need to + // resize the buffers then. + + const bool needsRegistration = !window || window != m_window; + + // except if the window actually changes + if (window && window != m_window) + destroy(); + + window = m_window; + m_currentPixelSize = surfacePixelSize(); + pixelSize = m_currentPixelSize; + + if (pixelSize.isEmpty()) + return false; + + HWND hwnd = reinterpret_cast(window->winId()); + HRESULT hr; + QRHI_RES_RHI(QRhiD3D12); + + if (m_flags.testFlag(SurfaceHasPreMulAlpha) || m_flags.testFlag(SurfaceHasNonPreMulAlpha)) { + if (rhiD->ensureDirectCompositionDevice()) { + if (!dcompTarget) { + hr = rhiD->dcompDevice->CreateTargetForHwnd(hwnd, true, &dcompTarget); + if (FAILED(hr)) { + qWarning("Failed to create Direct Compsition target for the window: %s", + qPrintable(QSystemError::windowsComString(hr))); + } + } + if (dcompTarget && !dcompVisual) { + hr = rhiD->dcompDevice->CreateVisual(&dcompVisual); + if (FAILED(hr)) { + qWarning("Failed to create DirectComposition visual: %s", + qPrintable(QSystemError::windowsComString(hr))); + } + } + } + // simple consistency check + if (window->requestedFormat().alphaBufferSize() <= 0) + qWarning("Swapchain says surface has alpha but the window has no alphaBufferSize set. " + "This may lead to problems."); + } + + swapInterval = m_flags.testFlag(QRhiSwapChain::NoVSync) ? 0 : 1; + swapChainFlags = 0; + if (swapInterval == 0 && rhiD->supportsAllowTearing) + swapChainFlags |= DXGI_SWAP_CHAIN_FLAG_ALLOW_TEARING; + + if (!swapChain) { + chooseFormats(); + + DXGI_SWAP_CHAIN_DESC1 desc = {}; + desc.Width = UINT(pixelSize.width()); + desc.Height = UINT(pixelSize.height()); + desc.Format = colorFormat; + desc.SampleDesc.Count = 1; + 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; + + if (dcompVisual) { + // With DirectComposition setting AlphaMode to STRAIGHT fails the + // swapchain creation, whereas the result seems to be identical + // with any of the other values, including IGNORE. (?) + desc.AlphaMode = DXGI_ALPHA_MODE_PREMULTIPLIED; + + // DirectComposition has its own limitations, cannot use + // SCALING_NONE. So with semi-transparency requested we are forced + // to SCALING_STRETCH. + desc.Scaling = DXGI_SCALING_STRETCH; + } + + if (dcompVisual) + hr = rhiD->dxgiFactory->CreateSwapChainForComposition(rhiD->cmdQueue, &desc, nullptr, &sourceSwapChain1); + else + hr = rhiD->dxgiFactory->CreateSwapChainForHwnd(rhiD->cmdQueue, hwnd, &desc, nullptr, nullptr, &sourceSwapChain1); + + // If failed and we tried a HDR format, then try with SDR. This + // matches other backends, such as Vulkan where if the format is + // not supported, the default one is used instead. + if (FAILED(hr) && m_format != SDR) { + colorFormat = DEFAULT_FORMAT; + desc.Format = DEFAULT_FORMAT; + if (dcompVisual) + hr = rhiD->dxgiFactory->CreateSwapChainForComposition(rhiD->cmdQueue, &desc, nullptr, &sourceSwapChain1); + else + hr = rhiD->dxgiFactory->CreateSwapChainForHwnd(rhiD->cmdQueue, hwnd, &desc, nullptr, nullptr, &sourceSwapChain1); + } + + if (SUCCEEDED(hr)) { + if (FAILED(sourceSwapChain1->QueryInterface(__uuidof(IDXGISwapChain3), reinterpret_cast(&swapChain)))) { + qWarning("IDXGISwapChain3 not available"); + return false; + } + if (m_format != SDR) { + hr = swapChain->SetColorSpace1(hdrColorSpace); + if (FAILED(hr)) { + qWarning("Failed to set color space on swapchain: %s", + qPrintable(QSystemError::windowsComString(hr))); + } + } + if (dcompVisual) { + hr = dcompVisual->SetContent(swapChain); + if (SUCCEEDED(hr)) { + hr = dcompTarget->SetRoot(dcompVisual); + if (FAILED(hr)) { + qWarning("Failed to associate Direct Composition visual with the target: %s", + qPrintable(QSystemError::windowsComString(hr))); + } + } else { + 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))); + return false; + } + + for (int i = 0; i < QD3D12_FRAMES_IN_FLIGHT; ++i) { + hr = rhiD->dev->CreateFence(0, + D3D12_FENCE_FLAG_NONE, + __uuidof(ID3D12Fence), + reinterpret_cast(&frameRes[i].fence)); + if (FAILED(hr)) { + qWarning("Failed to create fence for swapchain: %s", + qPrintable(QSystemError::windowsComString(hr))); + return false; + } + frameRes[i].fenceEvent = CreateEvent(nullptr, FALSE, FALSE, nullptr); + + frameRes[i].fenceCounter = 0; + } + } else { + releaseBuffers(); + hr = swapChain->ResizeBuffers(BUFFER_COUNT, + UINT(pixelSize.width()), + UINT(pixelSize.height()), + colorFormat, + swapChainFlags); + if (hr == DXGI_ERROR_DEVICE_REMOVED || hr == DXGI_ERROR_DEVICE_RESET) { + qWarning("Device loss detected in ResizeBuffers()"); + rhiD->deviceLost = true; + return false; + } else if (FAILED(hr)) { + qWarning("Failed to resize D3D12 swapchain: %s", qPrintable(QSystemError::windowsComString(hr))); + return false; + } + } + + for (UINT i = 0; i < BUFFER_COUNT; ++i) { + ID3D12Resource *colorBuffer; + hr = swapChain->GetBuffer(i, __uuidof(ID3D12Resource), reinterpret_cast(&colorBuffer)); + if (FAILED(hr)) { + qWarning("Failed to get buffer %u for D3D12 swapchain: %s", + i, qPrintable(QSystemError::windowsComString(hr))); + return false; + } + colorBuffers[i] = QD3D12Resource::addToPool(&rhiD->resourcePool, colorBuffer, D3D12_RESOURCE_STATE_PRESENT); + rtvs[i] = rhiD->rtvPool.allocate(1); + D3D12_RENDER_TARGET_VIEW_DESC rtvDesc = {}; + rtvDesc.Format = srgbAdjustedColorFormat; + rtvDesc.ViewDimension = D3D12_RTV_DIMENSION_TEXTURE2D; + rhiD->dev->CreateRenderTargetView(colorBuffer, &rtvDesc, rtvs[i].cpuHandle); + } + + if (m_depthStencil && m_depthStencil->sampleCount() != m_sampleCount) { + qWarning("Depth-stencil buffer's sampleCount (%d) does not match color buffers' sample count (%d). Expect problems.", + m_depthStencil->sampleCount(), m_sampleCount); + } + if (m_depthStencil && m_depthStencil->pixelSize() != pixelSize) { + if (m_depthStencil->flags().testFlag(QRhiRenderBuffer::UsedWithSwapChainOnly)) { + m_depthStencil->setPixelSize(pixelSize); + if (!m_depthStencil->create()) + qWarning("Failed to rebuild swapchain's associated depth-stencil buffer for size %dx%d", + pixelSize.width(), pixelSize.height()); + } else { + qWarning("Depth-stencil buffer's size (%dx%d) does not match the surface size (%dx%d). Expect problems.", + m_depthStencil->pixelSize().width(), m_depthStencil->pixelSize().height(), + pixelSize.width(), pixelSize.height()); + } + } + + ds = m_depthStencil ? QRHI_RES(QD3D12RenderBuffer, m_depthStencil) : nullptr; + + if (sampleDesc.Count > 1) { + for (UINT i = 0; i < BUFFER_COUNT; ++i) { + D3D12_RESOURCE_DESC resourceDesc = {}; + resourceDesc.Dimension = D3D12_RESOURCE_DIMENSION_TEXTURE2D; + resourceDesc.Width = UINT64(pixelSize.width()); + resourceDesc.Height = UINT(pixelSize.height()); + resourceDesc.DepthOrArraySize = 1; + resourceDesc.MipLevels = 1; + resourceDesc.Format = srgbAdjustedColorFormat; + resourceDesc.SampleDesc = sampleDesc; + resourceDesc.Layout = D3D12_TEXTURE_LAYOUT_UNKNOWN; + resourceDesc.Flags = D3D12_RESOURCE_FLAG_ALLOW_RENDER_TARGET; + D3D12_CLEAR_VALUE clearValue = {}; + clearValue.Format = colorFormat; + ID3D12Resource *resource = nullptr; + D3D12MA::Allocation *allocation = nullptr; + HRESULT hr = rhiD->vma.createResource(D3D12_HEAP_TYPE_DEFAULT, + &resourceDesc, + D3D12_RESOURCE_STATE_RENDER_TARGET, + &clearValue, + &allocation, + __uuidof(ID3D12Resource), + reinterpret_cast(&resource)); + if (FAILED(hr)) { + qWarning("Failed to create MSAA color buffer: %s", qPrintable(QSystemError::windowsComString(hr))); + return false; + } + msaaBuffers[i] = QD3D12Resource::addToPool(&rhiD->resourcePool, resource, D3D12_RESOURCE_STATE_RENDER_TARGET, allocation); + msaaRtvs[i] = rhiD->rtvPool.allocate(1); + if (!msaaRtvs[i].isValid()) + return false; + D3D12_RENDER_TARGET_VIEW_DESC rtvDesc = {}; + rtvDesc.Format = srgbAdjustedColorFormat; + rtvDesc.ViewDimension = sampleDesc.Count > 1 ? D3D12_RTV_DIMENSION_TEXTURE2DMS + : D3D12_RTV_DIMENSION_TEXTURE2D; + rhiD->dev->CreateRenderTargetView(resource, &rtvDesc, msaaRtvs[i].cpuHandle); + } + } + + currentBackBufferIndex = swapChain->GetCurrentBackBufferIndex(); + currentFrameSlot = 0; + + rtWrapper.setRenderPassDescriptor(m_renderPassDesc); // for the public getter in QRhiRenderTarget + QD3D12SwapChainRenderTarget *rtD = QRHI_RES(QD3D12SwapChainRenderTarget, &rtWrapper); + rtD->d.rp = QRHI_RES(QD3D12RenderPassDescriptor, 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; + + if (needsRegistration) { + rhiD->swapchains.insert(this); + rhiD->registerResource(this); + } + + return true; +} + +QT_END_NAMESPACE diff --git a/src/gui/rhi/qrhid3d12_p.h b/src/gui/rhi/qrhid3d12_p.h new file mode 100644 index 00000000..a6954d27 --- /dev/null +++ b/src/gui/rhi/qrhid3d12_p.h @@ -0,0 +1,1194 @@ +// 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 QRHID3D12_P_H +#define QRHID3D12_P_H + +// +// W A R N I N G +// ------------- +// +// This file is not part of the Qt API. It exists purely as an +// implementation detail. This header file may change from version to +// version without notice, or even be removed. +// +// We mean it. +// + +#include "qrhi_p.h" +#include +#include +#include + +#include +#include + +#include +#include +#include +#include + +#include "D3D12MemAlloc.h" + +QT_BEGIN_NAMESPACE + +static const int QD3D12_FRAMES_IN_FLIGHT = 2; + +class QRhiD3D12; + +struct QD3D12Descriptor +{ + D3D12_CPU_DESCRIPTOR_HANDLE cpuHandle = {}; + D3D12_GPU_DESCRIPTOR_HANDLE gpuHandle = {}; + + bool isValid() const { return cpuHandle.ptr != 0; } +}; + +struct QD3D12ReleaseQueue; + +struct QD3D12DescriptorHeap +{ + bool isValid() const { return heap && capacity; } + bool create(ID3D12Device *device, + quint32 descriptorCount, + D3D12_DESCRIPTOR_HEAP_TYPE heapType, + D3D12_DESCRIPTOR_HEAP_FLAGS heapFlags); + void createWithExisting(const QD3D12DescriptorHeap &other, + quint32 offsetInDescriptors, + quint32 descriptorCount); + void destroy(); + void destroyWithDeferredRelease(QD3D12ReleaseQueue *releaseQueue); + + QD3D12Descriptor get(quint32 count); + QD3D12Descriptor at(quint32 index) const; + quint32 remainingCapacity() const { return capacity - head; } + + QD3D12Descriptor incremented(const QD3D12Descriptor &descriptor, quint32 offsetInDescriptors) const + { + D3D12_CPU_DESCRIPTOR_HANDLE cpuHandle = descriptor.cpuHandle; + cpuHandle.ptr += offsetInDescriptors * descriptorByteSize; + D3D12_GPU_DESCRIPTOR_HANDLE gpuHandle = descriptor.gpuHandle; + if (gpuHandle.ptr) + gpuHandle.ptr += offsetInDescriptors * descriptorByteSize; + return { cpuHandle, gpuHandle }; + } + + ID3D12DescriptorHeap *heap = nullptr; + quint32 capacity = 0; + QD3D12Descriptor heapStart; + quint32 head = 0; + quint32 descriptorByteSize = 0; + D3D12_DESCRIPTOR_HEAP_TYPE heapType; + D3D12_DESCRIPTOR_HEAP_FLAGS heapFlags; +}; + +struct QD3D12CpuDescriptorPool +{ + bool isValid() const { return !heaps.isEmpty(); } + bool create(ID3D12Device *device, D3D12_DESCRIPTOR_HEAP_TYPE heapType, const char *debugName = ""); + void destroy(); + + QD3D12Descriptor allocate(quint32 count); + void release(const QD3D12Descriptor &descriptor, quint32 count); + + static const int DESCRIPTORS_PER_HEAP = 256; + + struct HeapWithMap { + QD3D12DescriptorHeap heap; + QBitArray map; + static HeapWithMap init(const QD3D12DescriptorHeap &heap, quint32 descriptorCount) { + HeapWithMap result; + result.heap = heap; + result.map.resize(descriptorCount); + return result; + } + }; + + ID3D12Device *device; + quint32 descriptorByteSize; + QVector heaps; + const char *debugName; +}; + +struct QD3D12StagingArea +{ + static const quint32 ALIGNMENT = D3D12_TEXTURE_DATA_PLACEMENT_ALIGNMENT; // 512 so good enough both for cb and texdata + + struct Allocation { + quint8 *p = nullptr; + D3D12_GPU_VIRTUAL_ADDRESS gpuAddr = 0; + ID3D12Resource *buffer = nullptr; + quint32 bufferOffset = 0; + bool isValid() const { return p != nullptr; } + }; + + bool isValid() const { return allocation && mem.isValid(); } + bool create(QRhiD3D12 *rhi, quint32 capacity, D3D12_HEAP_TYPE heapType); + void destroy(); + void destroyWithDeferredRelease(QD3D12ReleaseQueue *releaseQueue); + + Allocation get(quint32 byteSize); + + quint32 remainingCapacity() const + { + return capacity - head; + } + + static quint32 allocSizeForArray(quint32 size, int count = 1) + { + return count * ((size + ALIGNMENT - 1) & ~(ALIGNMENT - 1)); + } + + Allocation mem; + ID3D12Resource *resource = nullptr; + D3D12MA::Allocation *allocation = nullptr; + quint32 head; + quint32 capacity; +}; + +struct QD3D12ObjectHandle +{ + quint32 index = 0; + quint32 generation = 0; + + // the default, null handle is guaranteed to give ObjectPool::isValid() == false + bool isNull() const { return index == 0 && generation == 0; } +}; + +inline bool operator==(const QD3D12ObjectHandle &a, const QD3D12ObjectHandle &b) noexcept +{ + return a.index == b.index && a.generation == b.generation; +} + +inline bool operator!=(const QD3D12ObjectHandle &a, const QD3D12ObjectHandle &b) noexcept +{ + return !(a == b); +} + +template +struct QD3D12ObjectPool +{ + void create(const char *debugName = "") + { + this->debugName = debugName; + Q_ASSERT(data.isEmpty()); + data.append(Data()); // index 0 is always invalid + } + + void destroy() { + int leakCount = 0; // will nicely destroy everything here, but warn about it if enabled + for (Data &d : data) { + if (d.object.has_value()) { + leakCount += 1; + d.object->releaseResources(); + } + } + data.clear(); +#ifndef QT_NO_DEBUG + // debug builds: just do it always + static bool leakCheck = true; +#else + // release builds: opt-in + static bool leakCheck = qEnvironmentVariableIntValue("QT_RHI_LEAK_CHECK"); +#endif + if (leakCheck) { + if (leakCount > 0) { + qWarning("QD3D12ObjectPool::destroy(): Pool %p '%s' had %d unreleased objects", + this, debugName, leakCount); + } + } + } + + bool isValid(const QD3D12ObjectHandle &handle) const + { + return handle.index > 0 + && handle.index < quint32(data.count()) + && handle.generation > 0 + && handle.generation == data[handle.index].generation + && data[handle.index].object.has_value(); + } + + T lookup(const QD3D12ObjectHandle &handle) const + { + return isValid(handle) ? *data[handle.index].object : T(); + } + + const T *lookupRef(const QD3D12ObjectHandle &handle) const + { + return isValid(handle) ? &*data[handle.index].object : nullptr; + } + + T *lookupRef(const QD3D12ObjectHandle &handle) + { + return isValid(handle) ? &*data[handle.index].object : nullptr; + } + + QD3D12ObjectHandle add(const T &object) + { + Q_ASSERT(!data.isEmpty()); + const quint32 count = quint32(data.count()); + quint32 index = 1; // index 0 is always invalid + for (; index < count; ++index) { + if (!data[index].object.has_value()) + break; + } + if (index < count) { + data[index].object = object; + quint32 &generation = data[index].generation; + generation += 1u; + return { index, generation }; + } else { + data.append({ object, 1 }); + return { count, 1 }; + } + } + + void remove(const QD3D12ObjectHandle &handle) + { + if (T *object = lookupRef(handle)) { + object->releaseResources(); + data[handle.index].object.reset(); + } + } + + const char *debugName; + struct Data { + std::optional object; + quint32 generation = 0; + }; + QVector data; +}; + +struct QD3D12Resource +{ + ID3D12Resource *resource; + D3D12_RESOURCE_STATES state; + D3D12_RESOURCE_DESC desc; + D3D12MA::Allocation *allocation; + void *cpuMapPtr; + enum { UavUsageRead = 0x01, UavUsageWrite = 0x02 }; + int uavUsage; + bool owns; + + // note that this assumes the allocation (if there is one) and the resource + // are separately releaseable, see D3D12MemAlloc docs + static QD3D12ObjectHandle addToPool(QD3D12ObjectPool *pool, + ID3D12Resource *resource, + D3D12_RESOURCE_STATES state, + D3D12MA::Allocation *allocation = nullptr, + void *cpuMapPtr = nullptr) + { + Q_ASSERT(resource); + return pool->add({ resource, state, resource->GetDesc(), allocation, cpuMapPtr, 0, true }); + } + + // for QRhiTexture::createFrom() where the ID3D12Resource is not owned by us + static QD3D12ObjectHandle addNonOwningToPool(QD3D12ObjectPool *pool, + ID3D12Resource *resource, + D3D12_RESOURCE_STATES state) + { + Q_ASSERT(resource); + return pool->add({ resource, state, resource->GetDesc(), nullptr, nullptr, 0, false }); + } + + void releaseResources() + { + if (owns) { + // order matters: resource first, then the allocation + resource->Release(); + if (allocation) + allocation->Release(); + } + } +}; + +struct QD3D12Pipeline +{ + enum Type { + Graphics, + Compute + }; + Type type; + ID3D12PipelineState *pso; + + static QD3D12ObjectHandle addToPool(QD3D12ObjectPool *pool, + Type type, + ID3D12PipelineState *pso) + { + return pool->add({ type, pso }); + } + + void releaseResources() + { + pso->Release(); + } +}; + +struct QD3D12RootSignature +{ + ID3D12RootSignature *rootSig; + + static QD3D12ObjectHandle addToPool(QD3D12ObjectPool *pool, + ID3D12RootSignature *rootSig) + { + return pool->add({ rootSig }); + } + + void releaseResources() + { + rootSig->Release(); + } +}; + +struct QD3D12ReleaseQueue +{ + void create(QD3D12ObjectPool *resourcePool, + QD3D12ObjectPool *pipelinePool, + QD3D12ObjectPool *rootSignaturePool) + { + this->resourcePool = resourcePool; + this->pipelinePool = pipelinePool; + this->rootSignaturePool = rootSignaturePool; + } + + void deferredReleaseResource(const QD3D12ObjectHandle &handle); + void deferredReleaseResourceWithViews(const QD3D12ObjectHandle &handle, + QD3D12CpuDescriptorPool *pool, + const QD3D12Descriptor &viewsStart, + int viewCount); + void deferredReleasePipeline(const QD3D12ObjectHandle &handle); + void deferredReleaseRootSignature(const QD3D12ObjectHandle &handle); + void deferredReleaseCallback(std::function callback, void *userData); + void deferredReleaseResourceAndAllocation(ID3D12Resource *resource, + D3D12MA::Allocation *allocation); + void deferredReleaseDescriptorHeap(ID3D12DescriptorHeap *heap); + void deferredReleaseViews(QD3D12CpuDescriptorPool *pool, + const QD3D12Descriptor &viewsStart, + int viewCount); + + void activatePendingDeferredReleaseRequests(int frameSlot); + void executeDeferredReleases(int frameSlot, bool forced = false); + void releaseAll(); + + struct DeferredReleaseEntry { + enum Type { + Resource, + Pipeline, + RootSignature, + Callback, + ResourceAndAllocation, + DescriptorHeap, + Views + }; + Type type = Resource; + std::optional frameSlotToBeReleasedIn; + QD3D12ObjectHandle handle; + QD3D12CpuDescriptorPool *poolForViews = nullptr; + QD3D12Descriptor viewsStart; + int viewCount = 0; + std::function callback = nullptr; + void *callbackUserData = nullptr; + QPair resourceAndAllocation = {}; + ID3D12DescriptorHeap *descriptorHeap = nullptr; + }; + QVector queue; + QD3D12ObjectPool *resourcePool = nullptr; + QD3D12ObjectPool *pipelinePool = nullptr; + QD3D12ObjectPool *rootSignaturePool = nullptr; +}; + +struct QD3D12CommandBuffer; + +struct QD3D12ResourceBarrierGenerator +{ + static const int PREALLOC = 16; + + void create(QD3D12ObjectPool *resourcePool) + { + this->resourcePool = resourcePool; + } + + void addTransitionBarrier(const QD3D12ObjectHandle &resourceHandle, D3D12_RESOURCE_STATES stateAfter); + void enqueueBufferedTransitionBarriers(QD3D12CommandBuffer *cbD); + void enqueueSubresourceTransitionBarrier(QD3D12CommandBuffer *cbD, + const QD3D12ObjectHandle &resourceHandle, + UINT subresource, + D3D12_RESOURCE_STATES stateBefore, + D3D12_RESOURCE_STATES stateAfter); + void enqueueUavBarrier(QD3D12CommandBuffer *cbD, const QD3D12ObjectHandle &resourceHandle); + + struct TransitionResourceBarrier { + QD3D12ObjectHandle resourceHandle; + D3D12_RESOURCE_STATES stateBefore; + D3D12_RESOURCE_STATES stateAfter; + }; + QVarLengthArray transitionResourceBarriers; + QD3D12ObjectPool *resourcePool = nullptr; +}; + +struct QD3D12ShaderBytecodeCache +{ + struct Shader { + Shader() = default; + Shader(const QByteArray &bytecode, const QShader::NativeResourceBindingMap &rbm) + : bytecode(bytecode), nativeResourceBindingMap(rbm) + { } + QByteArray bytecode; + QShader::NativeResourceBindingMap nativeResourceBindingMap; + }; + + QHash data; + + void insertWithCapacityLimit(const QRhiShaderStage &key, const Shader &s); +}; + +struct QD3D12ShaderVisibleDescriptorHeap +{ + bool create(ID3D12Device *device, D3D12_DESCRIPTOR_HEAP_TYPE type, quint32 perFrameDescriptorCount); + void destroy(); + void destroyWithDeferredRelease(QD3D12ReleaseQueue *releaseQueue); + + QD3D12DescriptorHeap heap; + QD3D12DescriptorHeap perFrameHeapSlice[QD3D12_FRAMES_IN_FLIGHT]; +}; + +// wrap foreign struct so we can legally supply equality operators and qHash: +struct Q_D3D12_SAMPLER_DESC +{ + D3D12_SAMPLER_DESC desc; + + friend bool operator==(const Q_D3D12_SAMPLER_DESC &lhs, const Q_D3D12_SAMPLER_DESC &rhs) noexcept + { + return lhs.desc.Filter == rhs.desc.Filter + && lhs.desc.AddressU == rhs.desc.AddressU + && lhs.desc.AddressV == rhs.desc.AddressV + && lhs.desc.AddressW == rhs.desc.AddressW + && lhs.desc.MipLODBias == rhs.desc.MipLODBias + && lhs.desc.MaxAnisotropy == rhs.desc.MaxAnisotropy + && lhs.desc.ComparisonFunc == rhs.desc.ComparisonFunc + // BorderColor is never used, skip it + && lhs.desc.MinLOD == rhs.desc.MinLOD + && lhs.desc.MaxLOD == rhs.desc.MaxLOD; + } + + friend bool operator!=(const Q_D3D12_SAMPLER_DESC &lhs, const Q_D3D12_SAMPLER_DESC &rhs) noexcept + { + return !(lhs == rhs); + } + + friend size_t qHash(const Q_D3D12_SAMPLER_DESC &key, size_t seed = 0) noexcept + { + QtPrivate::QHashCombine hash; + seed = hash(seed, key.desc.Filter); + seed = hash(seed, key.desc.AddressU); + seed = hash(seed, key.desc.AddressV); + seed = hash(seed, key.desc.AddressW); + seed = hash(seed, key.desc.MipLODBias); + seed = hash(seed, key.desc.MaxAnisotropy); + seed = hash(seed, key.desc.ComparisonFunc); + // BorderColor is never used, skip it + seed = hash(seed, key.desc.MinLOD); + seed = hash(seed, key.desc.MaxLOD); + return seed; + } +}; + +struct QD3D12SamplerManager +{ + const quint32 MAX_SAMPLERS = 512; + + bool create(ID3D12Device *device); + void destroy(); + + QD3D12Descriptor getShaderVisibleDescriptor(const D3D12_SAMPLER_DESC &desc); + + ID3D12Device *device = nullptr; + QD3D12ShaderVisibleDescriptorHeap shaderVisibleSamplerHeap; + QHash gpuMap; +}; + +enum QD3D12Stage { VS = 0, HS, DS, GS, PS, CS }; + +static inline QD3D12Stage qd3d12_stage(QRhiShaderStage::Type type) +{ + switch (type) { + case QRhiShaderStage::Vertex: + return VS; + case QRhiShaderStage::TessellationControl: + return HS; + case QRhiShaderStage::TessellationEvaluation: + return DS; + case QRhiShaderStage::Geometry: + return GS; + case QRhiShaderStage::Fragment: + return PS; + case QRhiShaderStage::Compute: + return CS; + } + Q_UNREACHABLE_RETURN(VS); +} + +static inline D3D12_SHADER_VISIBILITY qd3d12_stageToVisibility(QD3D12Stage s) +{ + switch (s) { + case VS: + return D3D12_SHADER_VISIBILITY_VERTEX; + case HS: + return D3D12_SHADER_VISIBILITY_HULL; + case DS: + return D3D12_SHADER_VISIBILITY_DOMAIN; + case GS: + return D3D12_SHADER_VISIBILITY_GEOMETRY; + case PS: + return D3D12_SHADER_VISIBILITY_PIXEL; + case CS: + return D3D12_SHADER_VISIBILITY_ALL; + } + Q_UNREACHABLE_RETURN(D3D12_SHADER_VISIBILITY_ALL); +} + +static inline QRhiShaderResourceBinding::StageFlag qd3d12_stageToSrb(QD3D12Stage s) +{ + switch (s) { + case VS: + return QRhiShaderResourceBinding::VertexStage; + case HS: + return QRhiShaderResourceBinding::TessellationControlStage; + case DS: + return QRhiShaderResourceBinding::TessellationEvaluationStage; + case GS: + return QRhiShaderResourceBinding::GeometryStage; + case PS: + return QRhiShaderResourceBinding::FragmentStage; + case CS: + return QRhiShaderResourceBinding::ComputeStage; + } + Q_UNREACHABLE_RETURN(QRhiShaderResourceBinding::VertexStage); +} + +struct QD3D12ShaderStageData +{ + bool valid = false; // to allow simple arrays where unused stages are indicated by !valid + QD3D12Stage stage = VS; + QShader::NativeResourceBindingMap nativeResourceBindingMap; +}; + +struct QD3D12ShaderResourceBindings; + +struct QD3D12ShaderResourceVisitor +{ + enum StorageOp { Load = 0, Store, LoadStore }; + + QD3D12ShaderResourceVisitor(const QD3D12ShaderResourceBindings *srb, + const QD3D12ShaderStageData *stageData, + int stageCount) + : srb(srb), + stageData(stageData), + stageCount(stageCount) + { + } + + std::function uniformBuffer = nullptr; + std::function texture = nullptr; + std::function sampler = nullptr; + std::function storageImage = nullptr; + std::function storageBuffer = nullptr; + + void visit(); + + const QD3D12ShaderResourceBindings *srb; + const QD3D12ShaderStageData *stageData; + int stageCount; +}; + +struct QD3D12Readback +{ + // common + int frameSlot = -1; + QRhiReadbackResult *result = nullptr; + QD3D12StagingArea staging; + quint32 byteSize = 0; + // textures + quint32 bytesPerLine = 0; + QSize pixelSize; + QRhiTexture::Format format = QRhiTexture::UnknownFormat; + quint32 stagingRowPitch = 0; +}; + +struct QD3D12MipmapGenerator +{ + bool create(QRhiD3D12 *rhiD); + void destroy(); + void generate(QD3D12CommandBuffer *cbD, const QD3D12ObjectHandle &textureHandle); + + QRhiD3D12 *rhiD; + QD3D12ObjectHandle rootSigHandle; + QD3D12ObjectHandle pipelineHandle; +}; + +struct QD3D12MemoryAllocator +{ + bool create(ID3D12Device *device, IDXGIAdapter1 *adapter); + void destroy(); + + HRESULT createResource(D3D12_HEAP_TYPE heapType, + const D3D12_RESOURCE_DESC *resourceDesc, + D3D12_RESOURCE_STATES initialState, + const D3D12_CLEAR_VALUE *optimizedClearValue, + D3D12MA::Allocation **maybeAllocation, + REFIID riidResource, + void **ppvResource); + + void getBudget(D3D12MA::Budget *localBudget, D3D12MA::Budget *nonLocalBudget); + + bool isUsingD3D12MA() const { return allocator != nullptr; } + + ID3D12Device *device = nullptr; + D3D12MA::Allocator *allocator = nullptr; +}; + +struct QD3D12Buffer : public QRhiBuffer +{ + QD3D12Buffer(QRhiImplementation *rhi, Type type, UsageFlags usage, quint32 size); + ~QD3D12Buffer(); + + void destroy() override; + bool create() override; + QRhiBuffer::NativeBuffer nativeBuffer() override; + char *beginFullDynamicBufferUpdateForCurrentFrame() override; + void endFullDynamicBufferUpdateForCurrentFrame() override; + + void executeHostWritesForFrameSlot(int frameSlot); + + QD3D12ObjectHandle handles[QD3D12_FRAMES_IN_FLIGHT] = {}; + struct HostWrite { + quint32 offset; + QRhiBufferData data; + }; + QVarLengthArray pendingHostWrites[QD3D12_FRAMES_IN_FLIGHT]; + friend class QRhiD3D12; +}; + +struct QD3D12RenderBuffer : public QRhiRenderBuffer +{ + QD3D12RenderBuffer(QRhiImplementation *rhi, + Type type, + const QSize &pixelSize, + int sampleCount, + Flags flags, + QRhiTexture::Format backingFormatHint); + ~QD3D12RenderBuffer(); + void destroy() override; + bool create() override; + QRhiTexture::Format backingFormat() const override; + + static const DXGI_FORMAT DS_FORMAT = DXGI_FORMAT_D24_UNORM_S8_UINT; + + QD3D12ObjectHandle handle; + QD3D12Descriptor rtv; + QD3D12Descriptor dsv; + DXGI_FORMAT dxgiFormat; + DXGI_SAMPLE_DESC sampleDesc; + uint generation = 0; + friend class QRhiD3D12; +}; + +struct QD3D12Texture : public QRhiTexture +{ + QD3D12Texture(QRhiImplementation *rhi, Format format, const QSize &pixelSize, int depth, + int arraySize, int sampleCount, Flags flags); + ~QD3D12Texture(); + void destroy() override; + bool create() override; + bool createFrom(NativeTexture src) override; + NativeTexture nativeTexture() override; + void setNativeLayout(int layout) override; + + bool prepareCreate(QSize *adjustedSize = nullptr); + bool finishCreate(); + + QD3D12ObjectHandle handle; + QD3D12Descriptor srv; + DXGI_FORMAT dxgiFormat; + uint mipLevelCount; + DXGI_SAMPLE_DESC sampleDesc; + uint generation = 0; + friend class QRhiD3D12; +}; + +struct QD3D12Sampler : public QRhiSampler +{ + QD3D12Sampler(QRhiImplementation *rhi, Filter magFilter, Filter minFilter, Filter mipmapMode, + AddressMode u, AddressMode v, AddressMode w); + ~QD3D12Sampler(); + void destroy() override; + bool create() override; + + QD3D12Descriptor lookupOrCreateShaderVisibleDescriptor(); + + D3D12_SAMPLER_DESC desc = {}; + QD3D12Descriptor shaderVisibleDescriptor; +}; + +struct QD3D12RenderPassDescriptor : public QRhiRenderPassDescriptor +{ + QD3D12RenderPassDescriptor(QRhiImplementation *rhi); + ~QD3D12RenderPassDescriptor(); + void destroy() override; + bool isCompatible(const QRhiRenderPassDescriptor *other) const override; + QRhiRenderPassDescriptor *newCompatibleRenderPassDescriptor() const override; + QVector serializedFormat() const override; + + void updateSerializedFormat(); + + static const int MAX_COLOR_ATTACHMENTS = 8; + int colorAttachmentCount = 0; + bool hasDepthStencil = false; + int colorFormat[MAX_COLOR_ATTACHMENTS]; + int dsFormat; + QVector serializedFormatData; +}; + +struct QD3D12RenderTargetData +{ + QD3D12RenderTargetData(QRhiImplementation *) { } + + QD3D12RenderPassDescriptor *rp = nullptr; + QSize pixelSize; + float dpr = 1; + int sampleCount = 1; + int colorAttCount = 0; + int dsAttCount = 0; + QRhiRenderTargetAttachmentTracker::ResIdList currentResIdList; + static const int MAX_COLOR_ATTACHMENTS = QD3D12RenderPassDescriptor::MAX_COLOR_ATTACHMENTS; + D3D12_CPU_DESCRIPTOR_HANDLE rtv[MAX_COLOR_ATTACHMENTS]; + D3D12_CPU_DESCRIPTOR_HANDLE dsv; +}; + +struct QD3D12SwapChainRenderTarget : public QRhiSwapChainRenderTarget +{ + QD3D12SwapChainRenderTarget(QRhiImplementation *rhi, QRhiSwapChain *swapchain); + ~QD3D12SwapChainRenderTarget(); + void destroy() override; + + QSize pixelSize() const override; + float devicePixelRatio() const override; + int sampleCount() const override; + + QD3D12RenderTargetData d; +}; + +struct QD3D12TextureRenderTarget : public QRhiTextureRenderTarget +{ + QD3D12TextureRenderTarget(QRhiImplementation *rhi, + const QRhiTextureRenderTargetDescription &desc, + Flags flags); + ~QD3D12TextureRenderTarget(); + void destroy() override; + + QSize pixelSize() const override; + float devicePixelRatio() const override; + int sampleCount() const override; + + QRhiRenderPassDescriptor *newCompatibleRenderPassDescriptor() override; + bool create() override; + + QD3D12RenderTargetData d; + bool ownsRtv[QD3D12RenderTargetData::MAX_COLOR_ATTACHMENTS]; + QD3D12Descriptor rtv[QD3D12RenderTargetData::MAX_COLOR_ATTACHMENTS]; + bool ownsDsv = false; + QD3D12Descriptor dsv; + friend class QRhiD3D12; +}; + +struct QD3D12ShaderResourceBindings : public QRhiShaderResourceBindings +{ + QD3D12ShaderResourceBindings(QRhiImplementation *rhi); + ~QD3D12ShaderResourceBindings(); + void destroy() override; + bool create() override; + void updateResources(UpdateFlags flags) override; + + QD3D12ObjectHandle createRootSignature(const QD3D12ShaderStageData *stageData, int stageCount); + + struct VisitorData { + QVarLengthArray cbParams[6]; + + D3D12_ROOT_PARAMETER1 srvTables[6] = {}; + QVarLengthArray srvRanges[6]; + quint32 currentSrvRangeOffset[6] = {}; + + QVarLengthArray samplerTables[6]; + std::array samplerRanges[6] = {}; + int samplerRangeHeads[6] = {}; + + D3D12_ROOT_PARAMETER1 uavTables[6] = {}; + QVarLengthArray uavRanges[6]; + quint32 currentUavRangeOffset[6] = {}; + } visitorData; + + + void visitUniformBuffer(QD3D12Stage s, + const QRhiShaderResourceBinding::Data::UniformBufferData &d, + int shaderRegister, + int binding); + 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); + + QVarLengthArray sortedBindings; + bool hasDynamicOffset = false; + uint generation = 0; +}; + +struct QD3D12GraphicsPipeline : public QRhiGraphicsPipeline +{ + QD3D12GraphicsPipeline(QRhiImplementation *rhi); + ~QD3D12GraphicsPipeline(); + void destroy() override; + bool create() override; + + QD3D12ObjectHandle handle; + QD3D12ObjectHandle rootSigHandle; + std::array stageData; + D3D12_PRIMITIVE_TOPOLOGY topology; + uint generation = 0; + friend class QRhiD3D12; +}; + +struct QD3D12ComputePipeline : public QRhiComputePipeline +{ + QD3D12ComputePipeline(QRhiImplementation *rhi); + ~QD3D12ComputePipeline(); + void destroy() override; + bool create() override; + + QD3D12ObjectHandle handle; + QD3D12ObjectHandle rootSigHandle; + QD3D12ShaderStageData stageData; + uint generation = 0; + friend class QRhiD3D12; +}; + +struct QD3D12CommandBuffer : public QRhiCommandBuffer +{ + QD3D12CommandBuffer(QRhiImplementation *rhi); + ~QD3D12CommandBuffer(); + void destroy() override; + + const QRhiNativeHandles *nativeHandles(); + + ID3D12GraphicsCommandList *cmdList = nullptr; // not owned + QRhiD3D12CommandBufferNativeHandles nativeHandlesStruct; + + enum PassType { + NoPass, + RenderPass, + ComputePass + }; + + void resetState() + { + recordingPass = NoPass; + currentTarget = nullptr; + + resetPerPassState(); + } + + void resetPerPassState() + { + currentGraphicsPipeline = nullptr; + currentComputePipeline = nullptr; + currentPipelineGeneration = 0; + currentGraphicsSrb = nullptr; + currentComputeSrb = nullptr; + currentSrbGeneration = 0; + currentIndexBuffer = {}; + currentIndexOffset = 0; + currentIndexFormat = DXGI_FORMAT_R16_UINT; + currentVertexBuffers = {}; + currentVertexOffsets = {}; + } + + PassType recordingPass; + QRhiRenderTarget *currentTarget; + + QD3D12GraphicsPipeline *currentGraphicsPipeline; + QD3D12ComputePipeline *currentComputePipeline; + uint currentPipelineGeneration; + QRhiShaderResourceBindings *currentGraphicsSrb; + QRhiShaderResourceBindings *currentComputeSrb; + uint currentSrbGeneration; + QD3D12ObjectHandle currentIndexBuffer; + quint32 currentIndexOffset; + DXGI_FORMAT currentIndexFormat; + std::array currentVertexBuffers; + std::array currentVertexOffsets; +}; + +struct QD3D12SwapChain : public QRhiSwapChain +{ + QD3D12SwapChain(QRhiImplementation *rhi); + ~QD3D12SwapChain(); + void destroy() override; + + QRhiCommandBuffer *currentFrameCommandBuffer() override; + QRhiRenderTarget *currentFrameRenderTarget() override; + + QSize surfacePixelSize() override; + bool isFormatSupported(Format f) override; + QRhiSwapChainHdrInfo hdrInfo() override; + + QRhiRenderPassDescriptor *newCompatibleRenderPassDescriptor() override; + bool createOrResize() override; + + void releaseBuffers(); + void waitCommandCompletionForFrameSlot(int frameSlot); + void addCommandCompletionSignalForCurrentFrameSlot(); + void chooseFormats(); + + QWindow *window = nullptr; + IDXGISwapChain1 *sourceSwapChain1 = nullptr; + IDXGISwapChain3 *swapChain = nullptr; + QSize pixelSize; + UINT swapInterval = 1; + UINT swapChainFlags = 0; + DXGI_FORMAT colorFormat; + DXGI_FORMAT srgbAdjustedColorFormat; + DXGI_COLOR_SPACE_TYPE hdrColorSpace; + IDCompositionTarget *dcompTarget = nullptr; + IDCompositionVisual *dcompVisual = nullptr; + static const UINT BUFFER_COUNT = 3; + QD3D12ObjectHandle colorBuffers[BUFFER_COUNT]; + QD3D12Descriptor rtvs[BUFFER_COUNT]; + DXGI_SAMPLE_DESC sampleDesc; + QD3D12ObjectHandle msaaBuffers[BUFFER_COUNT]; + QD3D12Descriptor msaaRtvs[BUFFER_COUNT]; + QD3D12RenderBuffer *ds = nullptr; + UINT currentBackBufferIndex = 0; + QD3D12SwapChainRenderTarget rtWrapper; + QD3D12CommandBuffer cbWrapper; + + struct FrameResources { + ID3D12Fence *fence = nullptr; + HANDLE fenceEvent = nullptr; + UINT64 fenceCounter = 0; + ID3D12GraphicsCommandList *cmdList = nullptr; + } frameRes[QD3D12_FRAMES_IN_FLIGHT]; + + int currentFrameSlot = 0; // index in frameRes +}; + +class QRhiD3D12 : public QRhiImplementation +{ +public: + // 16MB * QD3D12_FRAMES_IN_FLIGHT; buffer and texture upload staging data that + // gets no space from this will get their own temporary staging areas. + static const quint32 SMALL_STAGING_AREA_BYTES_PER_FRAME = 16 * 1024 * 1024; + + static const quint32 SHADER_VISIBLE_CBV_SRV_UAV_HEAP_PER_FRAME_START_SIZE = 16384; + + QRhiD3D12(QRhiD3D12InitParams *params, QRhiD3D12NativeHandles *importDevice = nullptr); + + bool create(QRhi::Flags flags) override; + void destroy() override; + + QRhiGraphicsPipeline *createGraphicsPipeline() override; + QRhiComputePipeline *createComputePipeline() override; + QRhiShaderResourceBindings *createShaderResourceBindings() override; + QRhiBuffer *createBuffer(QRhiBuffer::Type type, + QRhiBuffer::UsageFlags usage, + quint32 size) override; + QRhiRenderBuffer *createRenderBuffer(QRhiRenderBuffer::Type type, + const QSize &pixelSize, + int sampleCount, + QRhiRenderBuffer::Flags flags, + QRhiTexture::Format backingFormatHint) override; + QRhiTexture *createTexture(QRhiTexture::Format format, + const QSize &pixelSize, + int depth, + int arraySize, + int sampleCount, + QRhiTexture::Flags flags) override; + QRhiSampler *createSampler(QRhiSampler::Filter magFilter, + QRhiSampler::Filter minFilter, + QRhiSampler::Filter mipmapMode, + QRhiSampler:: AddressMode u, + QRhiSampler::AddressMode v, + QRhiSampler::AddressMode w) override; + + QRhiTextureRenderTarget *createTextureRenderTarget(const QRhiTextureRenderTargetDescription &desc, + QRhiTextureRenderTarget::Flags flags) override; + + QRhiSwapChain *createSwapChain() override; + QRhi::FrameOpResult beginFrame(QRhiSwapChain *swapChain, QRhi::BeginFrameFlags flags) override; + QRhi::FrameOpResult endFrame(QRhiSwapChain *swapChain, QRhi::EndFrameFlags flags) override; + QRhi::FrameOpResult beginOffscreenFrame(QRhiCommandBuffer **cb, QRhi::BeginFrameFlags flags) override; + QRhi::FrameOpResult endOffscreenFrame(QRhi::EndFrameFlags flags) override; + QRhi::FrameOpResult finish() override; + + void resourceUpdate(QRhiCommandBuffer *cb, QRhiResourceUpdateBatch *resourceUpdates) override; + + void beginPass(QRhiCommandBuffer *cb, + QRhiRenderTarget *rt, + const QColor &colorClearValue, + const QRhiDepthStencilClearValue &depthStencilClearValue, + QRhiResourceUpdateBatch *resourceUpdates, + QRhiCommandBuffer::BeginPassFlags flags) override; + void endPass(QRhiCommandBuffer *cb, QRhiResourceUpdateBatch *resourceUpdates) override; + + void setGraphicsPipeline(QRhiCommandBuffer *cb, + QRhiGraphicsPipeline *ps) override; + + void setShaderResources(QRhiCommandBuffer *cb, + QRhiShaderResourceBindings *srb, + int dynamicOffsetCount, + const QRhiCommandBuffer::DynamicOffset *dynamicOffsets) override; + + void setVertexInput(QRhiCommandBuffer *cb, + int startBinding, int bindingCount, const QRhiCommandBuffer::VertexInput *bindings, + QRhiBuffer *indexBuf, quint32 indexOffset, + QRhiCommandBuffer::IndexFormat indexFormat) override; + + void setViewport(QRhiCommandBuffer *cb, const QRhiViewport &viewport) override; + void setScissor(QRhiCommandBuffer *cb, const QRhiScissor &scissor) override; + void setBlendConstants(QRhiCommandBuffer *cb, const QColor &c) override; + void setStencilRef(QRhiCommandBuffer *cb, quint32 refValue) override; + + void draw(QRhiCommandBuffer *cb, quint32 vertexCount, + quint32 instanceCount, quint32 firstVertex, quint32 firstInstance) override; + + void drawIndexed(QRhiCommandBuffer *cb, quint32 indexCount, + quint32 instanceCount, quint32 firstIndex, + qint32 vertexOffset, quint32 firstInstance) override; + + void debugMarkBegin(QRhiCommandBuffer *cb, const QByteArray &name) override; + void debugMarkEnd(QRhiCommandBuffer *cb) override; + void debugMarkMsg(QRhiCommandBuffer *cb, const QByteArray &msg) override; + + void beginComputePass(QRhiCommandBuffer *cb, + QRhiResourceUpdateBatch *resourceUpdates, + QRhiCommandBuffer::BeginPassFlags flags) override; + void endComputePass(QRhiCommandBuffer *cb, QRhiResourceUpdateBatch *resourceUpdates) override; + void setComputePipeline(QRhiCommandBuffer *cb, QRhiComputePipeline *ps) override; + void dispatch(QRhiCommandBuffer *cb, int x, int y, int z) override; + + const QRhiNativeHandles *nativeHandles(QRhiCommandBuffer *cb) override; + void beginExternal(QRhiCommandBuffer *cb) override; + void endExternal(QRhiCommandBuffer *cb) override; + double lastCompletedGpuTime(QRhiCommandBuffer *cb) override; + + QList supportedSampleCounts() const override; + int ubufAlignment() const override; + bool isYUpInFramebuffer() const override; + bool isYUpInNDC() const override; + bool isClipDepthZeroToOne() const override; + QMatrix4x4 clipSpaceCorrMatrix() const override; + bool isTextureFormatSupported(QRhiTexture::Format format, QRhiTexture::Flags flags) const override; + bool isFeatureSupported(QRhi::Feature feature) const override; + int resourceLimit(QRhi::ResourceLimit limit) const override; + const QRhiNativeHandles *nativeHandles() override; + QRhiDriverInfo driverInfo() const override; + QRhiStats statistics() override; + bool makeThreadLocalNativeContextCurrent() override; + void releaseCachedResources() override; + bool isDeviceLost() const override; + + QByteArray pipelineCacheData() override; + void setPipelineCacheData(const QByteArray &data) override; + + void waitGpu(); + DXGI_SAMPLE_DESC effectiveSampleCount(int sampleCount, DXGI_FORMAT format) const; + bool ensureDirectCompositionDevice(); + bool startCommandListForCurrentFrameSlot(ID3D12GraphicsCommandList **cmdList); + void enqueueResourceUpdates(QD3D12CommandBuffer *cbD, QRhiResourceUpdateBatch *resourceUpdates); + void finishActiveReadbacks(bool forced = false); + bool ensureShaderVisibleDescriptorHeapCapacity(QD3D12ShaderVisibleDescriptorHeap *h, + D3D12_DESCRIPTOR_HEAP_TYPE type, + int frameSlot, + quint32 neededDescriptorCount, + bool *gotNew); + void bindShaderVisibleHeaps(QD3D12CommandBuffer *cbD); + + bool debugLayer = false; + ID3D12Device *dev = nullptr; + D3D_FEATURE_LEVEL minimumFeatureLevel = D3D_FEATURE_LEVEL(0); + LUID adapterLuid = {}; + bool importedDevice = false; + bool importedCommandQueue = false; + QRhi::Flags rhiFlags; + IDXGIFactory2 *dxgiFactory = nullptr; + bool supportsAllowTearing = false; + IDXGIAdapter1 *activeAdapter = nullptr; + QRhiDriverInfo driverInfoStruct; + QRhiD3D12NativeHandles nativeHandlesStruct; + bool deviceLost = false; + ID3D12CommandQueue *cmdQueue = nullptr; + ID3D12Fence *fullFence = nullptr; + HANDLE fullFenceEvent = nullptr; + UINT64 fullFenceCounter = 0; + ID3D12CommandAllocator *cmdAllocators[QD3D12_FRAMES_IN_FLIGHT] = {}; + QD3D12MemoryAllocator vma; + QD3D12CpuDescriptorPool rtvPool; + QD3D12CpuDescriptorPool dsvPool; + QD3D12CpuDescriptorPool cbvSrvUavPool; + QD3D12ObjectPool resourcePool; + QD3D12ObjectPool pipelinePool; + QD3D12ObjectPool rootSignaturePool; + QD3D12ReleaseQueue releaseQueue; + QD3D12ResourceBarrierGenerator barrierGen; + QD3D12SamplerManager samplerMgr; + QD3D12MipmapGenerator mipmapGen; + QD3D12StagingArea smallStagingAreas[QD3D12_FRAMES_IN_FLIGHT]; + QD3D12ShaderVisibleDescriptorHeap shaderVisibleCbvSrvUavHeap; + IDCompositionDevice *dcompDevice = nullptr; + QD3D12SwapChain *currentSwapChain = nullptr; + QSet swapchains; + QD3D12ShaderBytecodeCache shaderBytecodeCache; + QVarLengthArray activeReadbacks; + bool offscreenActive = false; + QD3D12CommandBuffer *offscreenCb[QD3D12_FRAMES_IN_FLIGHT] = {}; + + struct VisitorData { + QVarLengthArray, 4> cbufs[6]; + QVarLengthArray srvs[6]; + QVarLengthArray samplers[6]; + QVarLengthArray, 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); +}; + +QT_END_NAMESPACE + +#endif diff --git a/src/gui/rhi/qrhid3dhelpers_p.h b/src/gui/rhi/qrhid3dhelpers_p.h new file mode 100644 index 00000000..f20c0428 --- /dev/null +++ b/src/gui/rhi/qrhid3dhelpers_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 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 +#include + +#include +#include + +QT_BEGIN_NAMESPACE + +namespace QRhiD3D { + +inline 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(symbol); + } + } + return nullptr; +} + +inline IDCompositionDevice *createDirectCompositionDevice() +{ + QSystemLibrary dcomplib(QStringLiteral("dcomp")); + typedef HRESULT (__stdcall *DCompositionCreateDeviceFuncPtr)( + _In_opt_ IDXGIDevice *dxgiDevice, + _In_ REFIID iid, + _Outptr_ void **dcompositionDevice); + DCompositionCreateDeviceFuncPtr func = reinterpret_cast( + 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(&device)); + if (FAILED(hr)) { + qWarning("Failed to Direct Composition device: %s", + qPrintable(QSystemError::windowsComString(hr))); + return nullptr; + } + return device; +} + +} // namespace + +QT_END_NAMESPACE + +#endif diff --git a/src/gui/rhi/qrhigles2.cpp b/src/gui/rhi/qrhigles2.cpp index e9997a97..02138d7f 100644 --- a/src/gui/rhi/qrhigles2.cpp +++ b/src/gui/rhi/qrhigles2.cpp @@ -1,7 +1,7 @@ -// Copyright (C) 2021 The Qt Company Ltd. +// 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 "qrhigles2_p_p.h" +#include "qrhigles2_p.h" #include #include #include @@ -27,10 +27,13 @@ QT_BEGIN_NAMESPACE /*! \class QRhiGles2InitParams - \internal \inmodule QtGui + \since 6.6 \brief OpenGL specific initialization parameters. + \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 combination with QOpenGLContext. Most commonly, this is a QOffscreenSurface in practice. Additionally, while optional, it is recommended that the QWindow @@ -51,18 +54,18 @@ QT_BEGIN_NAMESPACE thread) are satisfied. The implicitly created context is destroyed automatically together with the QRhi. - The QSurfaceFormat for the context is specified in \l format. The + The QSurfaceFormat for the context is specified in \c format. The constructor sets this to QSurfaceFormat::defaultFormat() so applications that call QSurfaceFormat::setDefaultFormat() with the appropriate settings - before the constructor runs will not need to change value of \l format. + before the constructor runs will not need to change value of \c format. \note Remember to set the depth and stencil buffer sizes to 24 and 8 when the renderer relies on depth or stencil testing, either in the global default QSurfaceFormat, or, alternatively, separately in all the involved - QSurfaceFormat instances: in \l format, the format argument passed to + QSurfaceFormat instances: in \c format, the format argument passed to newFallbackSurface(), and on any QWindow that is used with the QRhi. - A QSurface has to be specified in \l fallbackSurface. In order to prevent + A QSurface has to be specified in \c fallbackSurface. In order to prevent mistakes in threaded situations, this is never created automatically by the QRhi because, like QWindow, instances of QSurface subclasses can often be created on the gui/main thread only. @@ -77,14 +80,14 @@ QT_BEGIN_NAMESPACE instances that have their surface type set to QSurface::OpenGLSurface or QSurface::RasterGLSurface. - \note \l window is optional. It is recommended to specify it whenever + \note \c window is optional. It is recommended to specify it whenever possible, in order to avoid problems on multi-adapter and multi-screen - systems. When \l window is not set, the very first - QOpenGLContext::makeCurrent() happens with \l fallbackSurface which may be + systems. When \c window is not set, the very first + QOpenGLContext::makeCurrent() happens with \c fallbackSurface which may be an invisible window on some platforms (for example, Windows) and that may trigger unexpected problems in some cases. - In case resource sharing with an existing QOpenGLContext is desired, \l + In case resource sharing with an existing QOpenGLContext is desired, \c shareContext can be set to an existing QOpenGLContext. Alternatively, Qt::AA_ShareOpenGLContexts is honored as well, when enabled. @@ -93,8 +96,7 @@ QT_BEGIN_NAMESPACE When interoperating with another graphics engine, it may be necessary to get a QRhi instance that uses the same OpenGL context. This can be achieved by passing a pointer to a QRhiGles2NativeHandles to QRhi::create(). The - \l{QRhiGles2NativeHandles::context}{context} must be set to a non-null - value. + \c{QRhiGles2NativeHandles::context} must be set to a non-null value then. An alternative approach is to create a QOpenGLContext that \l{QOpenGLContext::setShareContext()}{shares resources} with the other @@ -104,13 +106,51 @@ QT_BEGIN_NAMESPACE QRhiGles2NativeHandles. */ +/*! + \variable QRhiGles2InitParams::format + + The QSurfaceFormat, initialized to QSurfaceFormat::defaultFormat() by default. +*/ + +/*! + \variable QRhiGles2InitParams::fallbackSurface + + A QSurface compatible with \l format. Typically a QOffscreenSurface. + Providing this is mandatory. Be aware of the threading implications: a + QOffscreenSurface, like QWindow, must only ever be created and destroyed on + the main (gui) thread, even if the QRhi is created and operates on another + thread. +*/ + +/*! + \variable QRhiGles2InitParams::window + + Optional, but setting it is recommended when targeting a QWindow with the + QRhi. +*/ + +/*! + \variable QRhiGles2InitParams::shareContext + + Optional, the QOpenGLContext to share resource with. QRhi creates its own + context, and setting this member to a valid QOpenGLContext leads to calling + \l{QOpenGLContext::setShareContext()}{setShareContext()} with it. +*/ + /*! \class QRhiGles2NativeHandles - \internal \inmodule QtGui + \since 6.6 \brief Holds the OpenGL context used by the QRhi. + + \note This is a RHI API with limited compatibility guarantees, see \l QRhi + for details. */ +/*! + \variable QRhiGles2NativeHandles::context +*/ + #ifndef GL_BGRA #define GL_BGRA 0x80E1 #endif @@ -319,6 +359,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 @@ -455,6 +499,14 @@ QT_BEGIN_NAMESPACE # define GL_TEXTURE_1D_ARRAY 0x8C18 #endif +#ifndef GL_HALF_FLOAT +#define GL_HALF_FLOAT 0x140B +#endif + +#ifndef GL_MAX_VERTEX_OUTPUT_COMPONENTS +#define GL_MAX_VERTEX_OUTPUT_COMPONENTS 0x9122 +#endif + /*! Constructs a new QRhiGles2InitParams. @@ -940,7 +992,7 @@ bool QRhiGles2::create(QRhi::Flags flags) f->glGetIntegerv(GL_MAX_VARYING_VECTORS, &caps.maxVertexOutputs); } else if (caps.ctxMajor >= 3) { GLint components = 0; - f->glGetIntegerv(GL_MAX_VARYING_COMPONENTS, &components); + f->glGetIntegerv(caps.coreProfile ? GL_MAX_VERTEX_OUTPUT_COMPONENTS : GL_MAX_VARYING_COMPONENTS, &components); caps.maxVertexOutputs = components / 4; } else { // OpenGL before 3.0 only has this, and not the same as @@ -963,6 +1015,8 @@ bool QRhiGles2::create(QRhi::Flags flags) if (!caps.gles && (caps.ctxMajor > 3 || (caps.ctxMajor == 3 && caps.ctxMinor >= 2))) f->glEnable(GL_TEXTURE_CUBE_MAP_SEAMLESS); + caps.halfAttributes = f->hasOpenGLExtension(QOpenGLExtensions::HalfFloatVertex); + nativeHandlesStruct.context = ctx; contextLost = false; @@ -1319,6 +1373,12 @@ bool QRhiGles2::isFeatureSupported(QRhi::Feature feature) const return caps.texture1D; case QRhi::OneDimensionalTextureMipmaps: return caps.texture1D; + case QRhi::HalfAttributes: + return caps.halfAttributes; + case QRhi::RenderToOneDimensionalTexture: + return caps.texture1D; + case QRhi::ThreeDimensionalTextureMipmaps: + return caps.texture3D; default: Q_UNREACHABLE_RETURN(false); } @@ -1616,7 +1676,7 @@ void QRhiGles2::setShaderResources(QRhiCommandBuffer *cb, QRhiShaderResourceBind if (cbD->passNeedsResourceTracking) { QRhiPassResourceTracker &passResTracker(cbD->passResTrackers[cbD->currentPassResTrackerIndex]); for (int i = 0, ie = srbD->m_bindings.size(); i != ie; ++i) { - const QRhiShaderResourceBinding::Data *b = srbD->m_bindings.at(i).data(); + const QRhiShaderResourceBinding::Data *b = shaderResourceBindingData(srbD->m_bindings.at(i)); switch (b->type) { case QRhiShaderResourceBinding::UniformBuffer: // no BufUniformRead / AccessUniform because no real uniform buffers are used @@ -1956,6 +2016,12 @@ void QRhiGles2::endExternal(QRhiCommandBuffer *cb) enqueueBindFramebuffer(cbD->currentTarget, cbD); } +double QRhiGles2::lastCompletedGpuTime(QRhiCommandBuffer *cb) +{ + Q_UNUSED(cb); + return 0; +} + QRhi::FrameOpResult QRhiGles2::beginFrame(QRhiSwapChain *swapChain, QRhi::BeginFrameFlags) { QGles2SwapChain *swapChainD = QRHI_RES(QGles2SwapChain, swapChain); @@ -2181,6 +2247,8 @@ void QRhiGles2::enqueueSubresUpload(QGles2Texture *texD, QGles2CommandBuffer *cb cmd.args.subImage.rowLength = 0; cmd.args.subImage.data = cbD->retainImage(img); } else if (!rawData.isEmpty() && isCompressed) { + const int depth = qMax(1, texD->m_depth); + const int arraySize = qMax(0, texD->m_arraySize); if ((texD->flags().testFlag(QRhiTexture::UsedAsCompressedAtlas) || is3D || isArray) && !texD->zeroInitialized) { @@ -2192,9 +2260,9 @@ void QRhiGles2::enqueueSubresUpload(QGles2Texture *texD, QGles2CommandBuffer *cb quint32 byteSize = 0; compressedFormatInfo(texD->m_format, texD->m_pixelSize, nullptr, &byteSize, nullptr); if (is3D) - byteSize *= texD->m_depth; + byteSize *= depth; if (isArray) - byteSize *= texD->m_arraySize; + byteSize *= arraySize; QByteArray zeroBuf(byteSize, 0); QGles2CommandBuffer::Command &cmd(cbD->commands.get()); cmd.cmd = QGles2CommandBuffer::Command::CompressedImage; @@ -2204,9 +2272,8 @@ void QRhiGles2::enqueueSubresUpload(QGles2Texture *texD, QGles2CommandBuffer *cb cmd.args.compressedImage.level = level; cmd.args.compressedImage.glintformat = texD->glintformat; cmd.args.compressedImage.w = texD->m_pixelSize.width(); - cmd.args.compressedImage.h = - is1D && isArray ? texD->m_arraySize : texD->m_pixelSize.height(); - cmd.args.compressedImage.depth = is3D ? texD->m_depth : (isArray ? texD->m_arraySize : 0); + cmd.args.compressedImage.h = is1D && isArray ? arraySize : texD->m_pixelSize.height(); + cmd.args.compressedImage.depth = is3D ? depth : (isArray ? arraySize : 0); cmd.args.compressedImage.size = byteSize; cmd.args.compressedImage.data = cbD->retainData(zeroBuf); texD->zeroInitialized = true; @@ -2238,8 +2305,8 @@ void QRhiGles2::enqueueSubresUpload(QGles2Texture *texD, QGles2CommandBuffer *cb cmd.args.compressedImage.level = level; cmd.args.compressedImage.glintformat = texD->glintformat; cmd.args.compressedImage.w = size.width(); - cmd.args.compressedImage.h = is1D && isArray ? texD->m_arraySize : size.height(); - cmd.args.compressedImage.depth = is3D ? texD->m_depth : (isArray ? texD->m_arraySize : 0); + cmd.args.compressedImage.h = is1D && isArray ? arraySize : size.height(); + cmd.args.compressedImage.depth = is3D ? depth : (isArray ? arraySize : 0); cmd.args.compressedImage.size = rawData.size(); cmd.args.compressedImage.data = cbD->retainData(rawData); } @@ -2943,6 +3010,22 @@ void QRhiGles2::executeCommandBuffer(QRhiCommandBuffer *cb) type = GL_INT; size = 1; break; + case QRhiVertexInputAttribute::Half4: + type = GL_HALF_FLOAT; + size = 4; + break; + case QRhiVertexInputAttribute::Half3: + type = GL_HALF_FLOAT; + size = 3; + break; + case QRhiVertexInputAttribute::Half2: + type = GL_HALF_FLOAT; + size = 2; + break; + case QRhiVertexInputAttribute::Half: + type = GL_HALF_FLOAT; + size = 1; + break; default: break; } @@ -3117,7 +3200,7 @@ void QRhiGles2::executeCommandBuffer(QRhiCommandBuffer *cb) break; case QGles2CommandBuffer::Command::GetBufferSubData: { - QRhiBufferReadbackResult *result = cmd.args.getBufferSubData.result; + QRhiReadbackResult *result = cmd.args.getBufferSubData.result; bindVertexIndexBufferWithStateReset(&state, f, cmd.args.getBufferSubData.target, cmd.args.getBufferSubData.buffer); if (caps.gles) { if (caps.properMapBuffer) { @@ -3346,17 +3429,55 @@ void QRhiGles2::executeCommandBuffer(QRhiCommandBuffer *cb) 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); + GL_RENDERBUFFER, cmd.args.blitFromRenderbuffer.renderbuffer); f->glBindFramebuffer(GL_DRAW_FRAMEBUFFER, fbo[1]); - if (cmd.args.blitFromRb.target == GL_TEXTURE_3D || cmd.args.blitFromRb.target == GL_TEXTURE_2D_ARRAY) { + if (cmd.args.blitFromRenderbuffer.target == GL_TEXTURE_3D || cmd.args.blitFromRenderbuffer.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); + cmd.args.blitFromRenderbuffer.dstTexture, + cmd.args.blitFromRenderbuffer.dstLevel, + cmd.args.blitFromRenderbuffer.dstLayer); } else { - f->glFramebufferTexture2D(GL_DRAW_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, cmd.args.blitFromRb.target, - cmd.args.blitFromRb.texture, cmd.args.blitFromRb.dstLevel); + 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.blitFromRb.w, cmd.args.blitFromRb.h, - 0, 0, cmd.args.blitFromRb.w, cmd.args.blitFromRb.h, + f->glBlitFramebuffer(0, 0, cmd.args.blitFromRenderbuffer.w, cmd.args.blitFromRenderbuffer.h, + 0, 0, cmd.args.blitFromRenderbuffer.w, cmd.args.blitFromRenderbuffer.h, + 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]); + if (cmd.args.blitFromTexture.srcTarget == GL_TEXTURE_2D_MULTISAMPLE_ARRAY) { + f->glFramebufferTextureLayer(GL_READ_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, + cmd.args.blitFromTexture.srcTexture, + cmd.args.blitFromTexture.srcLevel, + cmd.args.blitFromTexture.srcLayer); + } 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.blitFromTexture.dstTarget == GL_TEXTURE_3D || cmd.args.blitFromTexture.dstTarget == GL_TEXTURE_2D_ARRAY) { + 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.blitFromTexture.dstTarget, + cmd.args.blitFromTexture.dstTexture, cmd.args.blitFromTexture.dstLevel); + } + f->glBlitFramebuffer(0, 0, cmd.args.blitFromTexture.w, cmd.args.blitFromTexture.h, + 0, 0, cmd.args.blitFromTexture.w, cmd.args.blitFromTexture.h, GL_COLOR_BUFFER_BIT, GL_NEAREST); // Qt 5 used Nearest when resolving samples, stick to that f->glBindFramebuffer(GL_FRAMEBUFFER, ctx->defaultFramebufferObject()); @@ -3706,7 +3827,7 @@ void QRhiGles2::bindShaderResources(QGles2CommandBuffer *cbD, QVarLengthArray separateSamplerBindings; for (int i = 0, ie = srbD->m_bindings.size(); i != ie; ++i) { - const QRhiShaderResourceBinding::Data *b = srbD->m_bindings.at(i).data(); + const QRhiShaderResourceBinding::Data *b = shaderResourceBindingData(srbD->m_bindings.at(i)); switch (b->type) { case QRhiShaderResourceBinding::UniformBuffer: @@ -4205,32 +4326,64 @@ 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; + } 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()); + } + 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(colorAtt.layer()); + 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 = colorAtt.layer(); + 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(colorAtt.resolveLayer()); + 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 = colorAtt.resolveLayer(); } } } @@ -4325,7 +4478,7 @@ void QRhiGles2::dispatch(QRhiCommandBuffer *cb, int x, int y, int z) QGles2ShaderResourceBindings *srbD = QRHI_RES(QGles2ShaderResourceBindings, cbD->currentComputeSrb); const int bindingCount = srbD->m_bindings.size(); for (int i = 0; i < bindingCount; ++i) { - const QRhiShaderResourceBinding::Data *b = srbD->m_bindings.at(i).data(); + const QRhiShaderResourceBinding::Data *b = shaderResourceBindingData(srbD->m_bindings.at(i)); switch (b->type) { case QRhiShaderResourceBinding::ImageLoad: case QRhiShaderResourceBinding::ImageStore: @@ -5135,12 +5288,10 @@ bool QGles2Texture::prepareCreate(QSize *adjustedSize) return false; } - m_depth = qMax(1, m_depth); if (m_depth > 1 && !is3D) { qWarning("Texture cannot have a depth of %d when it is not 3D", m_depth); return false; } - m_arraySize = qMax(0, m_arraySize); if (m_arraySize > 0 && !isArray) { qWarning("Texture cannot have an array size of %d when it is not an array", m_arraySize); return false; @@ -5151,7 +5302,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))); @@ -5215,13 +5366,13 @@ bool QGles2Texture::create() const QSize mipSize = rhiD->q->sizeForMipLevel(level, size); if (isArray) rhiD->f->glTexImage2D(target, level, GLint(glintformat), mipSize.width(), - m_arraySize, 0, glformat, gltype, nullptr); + qMax(0, m_arraySize), 0, glformat, gltype, nullptr); else rhiD->glTexImage1D(target, level, GLint(glintformat), mipSize.width(), 0, glformat, gltype, nullptr); } } else if (is3D || isArray) { - const int layerCount = is3D ? m_depth : m_arraySize; + const int layerCount = is3D ? qMax(1, m_depth) : qMax(0, m_arraySize); if (hasMipMaps) { for (int level = 0; level != mipLevelCount; ++level) { const QSize mipSize = rhiD->q->sizeForMipLevel(level, size); @@ -5253,10 +5404,11 @@ bool QGles2Texture::create() if (is1D && !isArray) rhiD->glTexStorage1D(target, mipLevelCount, glsizedintformat, size.width()); else if (!is1D && (is3D || isArray)) - rhiD->f->glTexStorage3D(target, mipLevelCount, glsizedintformat, size.width(), size.height(), is3D ? m_depth : m_arraySize); + rhiD->f->glTexStorage3D(target, mipLevelCount, glsizedintformat, size.width(), size.height(), + is3D ? qMax(1, m_depth) : qMax(0, m_arraySize)); else rhiD->f->glTexStorage2D(target, mipLevelCount, glsizedintformat, size.width(), - is1D ? m_arraySize : size.height()); + is1D ? qMax(0, m_arraySize) : size.height()); } specified = true; } else { @@ -5447,13 +5599,13 @@ bool QGles2TextureRenderTarget::create() if (framebuffer) destroy(); - const bool hasColorAttachments = m_desc.cbeginColorAttachments() != m_desc.cendColorAttachments(); + const bool hasColorAttachments = m_desc.colorAttachmentCount() > 0; Q_ASSERT(hasColorAttachments || m_desc.depthTexture()); Q_ASSERT(!m_desc.depthStencilBuffer() || !m_desc.depthTexture()); const bool hasDepthStencil = m_desc.depthStencilBuffer() || m_desc.depthTexture(); if (hasColorAttachments) { - const int count = m_desc.cendColorAttachments() - m_desc.cbeginColorAttachments(); + const int count = int(m_desc.colorAttachmentCount()); if (count > rhiD->caps.maxDrawBuffers) { qWarning("QGles2TextureRenderTarget: Too many color attachments (%d, max is %d)", count, rhiD->caps.maxDrawBuffers); @@ -5597,7 +5749,7 @@ bool QGles2ShaderResourceBindings::create() hasDynamicOffset = false; for (int i = 0, ie = m_bindings.size(); i != ie; ++i) { - const QRhiShaderResourceBinding::Data *b = m_bindings.at(i).data(); + const QRhiShaderResourceBinding::Data *b = QRhiImplementation::shaderResourceBindingData(m_bindings.at(i)); if (b->type == QRhiShaderResourceBinding::UniformBuffer) { if (b->u.ubuf.hasDynamicOffset) { hasDynamicOffset = true; diff --git a/src/gui/rhi/qrhigles2_p.h b/src/gui/rhi/qrhigles2_p.h index e3f4fbe7..c48cf9ce 100644 --- a/src/gui/rhi/qrhigles2_p.h +++ b/src/gui/rhi/qrhigles2_p.h @@ -1,8 +1,8 @@ -// Copyright (C) 2019 The Qt Company Ltd. +// 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 QRHIGLES2_H -#define QRHIGLES2_H +#ifndef QRHIGLES2_P_H +#define QRHIGLES2_P_H // // W A R N I N G @@ -15,32 +15,1074 @@ // We mean it. // -#include -#include +#include "qrhi_p.h" +#include +#include +#include +#include +#include +#include +#include QT_BEGIN_NAMESPACE -class QOpenGLContext; -class QOffscreenSurface; -class QSurface; -class QWindow; +class QOpenGLExtensions; -struct Q_GUI_EXPORT QRhiGles2InitParams : public QRhiInitParams +struct QGles2Buffer : public QRhiBuffer { - QRhiGles2InitParams(); + QGles2Buffer(QRhiImplementation *rhi, Type type, UsageFlags usage, quint32 size); + ~QGles2Buffer(); + void destroy() override; + bool create() override; + QRhiBuffer::NativeBuffer nativeBuffer() override; + char *beginFullDynamicBufferUpdateForCurrentFrame() override; + void endFullDynamicBufferUpdateForCurrentFrame() override; - QSurfaceFormat format; + quint32 nonZeroSize = 0; + GLuint buffer = 0; + GLenum targetForDataOps; + QByteArray data; + enum Access { + AccessNone, + AccessVertex, + AccessIndex, + AccessUniform, + AccessStorageRead, + AccessStorageWrite, + AccessStorageReadWrite, + AccessUpdate + }; + struct UsageState { + Access access; + }; + UsageState usageState; + friend class QRhiGles2; +}; + +struct QGles2RenderBuffer : public QRhiRenderBuffer +{ + QGles2RenderBuffer(QRhiImplementation *rhi, Type type, const QSize &pixelSize, + int sampleCount, QRhiRenderBuffer::Flags flags, + QRhiTexture::Format backingFormatHint); + ~QGles2RenderBuffer(); + void destroy() override; + bool create() override; + bool createFrom(NativeRenderBuffer src) override; + QRhiTexture::Format backingFormat() const override; + + GLuint renderbuffer = 0; + GLuint stencilRenderbuffer = 0; // when packed depth-stencil not supported + int samples; + bool owns = true; + uint generation = 0; + friend class QRhiGles2; +}; + +struct QGles2SamplerData +{ + GLenum glminfilter = 0; + GLenum glmagfilter = 0; + GLenum glwraps = 0; + GLenum glwrapt = 0; + GLenum glwrapr = 0; + GLenum gltexcomparefunc = 0; +}; + +inline bool operator==(const QGles2SamplerData &a, const QGles2SamplerData &b) +{ + return a.glminfilter == b.glminfilter + && a.glmagfilter == b.glmagfilter + && a.glwraps == b.glwraps + && a.glwrapt == b.glwrapt + && a.glwrapr == b.glwrapr + && a.gltexcomparefunc == b.gltexcomparefunc; +} + +inline bool operator!=(const QGles2SamplerData &a, const QGles2SamplerData &b) +{ + return !(a == b); +} + +struct QGles2Texture : public QRhiTexture +{ + QGles2Texture(QRhiImplementation *rhi, Format format, const QSize &pixelSize, int depth, + int arraySize, int sampleCount, Flags flags); + ~QGles2Texture(); + void destroy() override; + bool create() override; + bool createFrom(NativeTexture src) override; + NativeTexture nativeTexture() override; + + bool prepareCreate(QSize *adjustedSize = nullptr); + + GLuint texture = 0; + bool owns = true; + GLenum target; + GLenum glintformat; + GLenum glsizedintformat; + GLenum glformat; + GLenum gltype; + QGles2SamplerData samplerState; + bool specified = false; + bool zeroInitialized = false; + int mipLevelCount = 0; + + enum Access { + AccessNone, + AccessSample, + AccessFramebuffer, + AccessStorageRead, + AccessStorageWrite, + AccessStorageReadWrite, + AccessUpdate, + AccessRead + }; + struct UsageState { + Access access; + }; + UsageState usageState; + + uint generation = 0; + friend class QRhiGles2; +}; + +struct QGles2Sampler : public QRhiSampler +{ + QGles2Sampler(QRhiImplementation *rhi, Filter magFilter, Filter minFilter, Filter mipmapMode, + AddressMode u, AddressMode v, AddressMode w); + ~QGles2Sampler(); + void destroy() override; + bool create() override; + + QGles2SamplerData d; + uint generation = 0; + friend class QRhiGles2; +}; + +struct QGles2RenderPassDescriptor : public QRhiRenderPassDescriptor +{ + QGles2RenderPassDescriptor(QRhiImplementation *rhi); + ~QGles2RenderPassDescriptor(); + void destroy() override; + bool isCompatible(const QRhiRenderPassDescriptor *other) const override; + QRhiRenderPassDescriptor *newCompatibleRenderPassDescriptor() const override; + QVector serializedFormat() const override; +}; + +struct QGles2RenderTargetData +{ + QGles2RenderTargetData(QRhiImplementation *) { } + + bool isValid() const { return rp != nullptr; } + + QGles2RenderPassDescriptor *rp = nullptr; + QSize pixelSize; + float dpr = 1; + int sampleCount = 1; + int colorAttCount = 0; + int dsAttCount = 0; + bool srgbUpdateAndBlend = false; + QRhiRenderTargetAttachmentTracker::ResIdList currentResIdList; + std::optional stereoTarget; +}; + +struct QGles2SwapChainRenderTarget : public QRhiSwapChainRenderTarget +{ + QGles2SwapChainRenderTarget(QRhiImplementation *rhi, QRhiSwapChain *swapchain); + ~QGles2SwapChainRenderTarget(); + void destroy() override; + + QSize pixelSize() const override; + float devicePixelRatio() const override; + int sampleCount() const override; + + QGles2RenderTargetData d; +}; + +struct QGles2TextureRenderTarget : public QRhiTextureRenderTarget +{ + QGles2TextureRenderTarget(QRhiImplementation *rhi, const QRhiTextureRenderTargetDescription &desc, Flags flags); + ~QGles2TextureRenderTarget(); + void destroy() override; + + QSize pixelSize() const override; + float devicePixelRatio() const override; + int sampleCount() const override; + + QRhiRenderPassDescriptor *newCompatibleRenderPassDescriptor() override; + bool create() override; + + QGles2RenderTargetData d; + GLuint framebuffer = 0; + friend class QRhiGles2; +}; + +struct QGles2ShaderResourceBindings : public QRhiShaderResourceBindings +{ + QGles2ShaderResourceBindings(QRhiImplementation *rhi); + ~QGles2ShaderResourceBindings(); + void destroy() override; + bool create() override; + void updateResources(UpdateFlags flags) override; + + bool hasDynamicOffset = false; + uint generation = 0; + friend class QRhiGles2; +}; + +struct QGles2UniformDescription +{ + QShaderDescription::VariableType type; + int glslLocation; + int binding; + quint32 offset; + quint32 size; + int arrayDim; +}; + +Q_DECLARE_TYPEINFO(QGles2UniformDescription, Q_RELOCATABLE_TYPE); + +struct QGles2SamplerDescription +{ + int glslLocation; + int combinedBinding; + int tbinding; + int sbinding; +}; + +Q_DECLARE_TYPEINFO(QGles2SamplerDescription, Q_RELOCATABLE_TYPE); + +using QGles2UniformDescriptionVector = QVarLengthArray; +using QGles2SamplerDescriptionVector = QVarLengthArray; + +struct QGles2UniformState +{ + static constexpr int MAX_TRACKED_LOCATION = 1023; + int componentCount; + float v[4]; +}; + +struct QGles2GraphicsPipeline : public QRhiGraphicsPipeline +{ + QGles2GraphicsPipeline(QRhiImplementation *rhi); + ~QGles2GraphicsPipeline(); + void destroy() override; + bool create() override; + + GLuint program = 0; + GLenum drawMode = GL_TRIANGLES; + QGles2UniformDescriptionVector uniforms; + QGles2SamplerDescriptionVector samplers; + QGles2UniformState uniformState[QGles2UniformState::MAX_TRACKED_LOCATION + 1]; + QRhiShaderResourceBindings *currentSrb = nullptr; + uint currentSrbGeneration = 0; + uint generation = 0; + friend class QRhiGles2; +}; + +struct QGles2ComputePipeline : public QRhiComputePipeline +{ + QGles2ComputePipeline(QRhiImplementation *rhi); + ~QGles2ComputePipeline(); + void destroy() override; + bool create() override; + + GLuint program = 0; + QGles2UniformDescriptionVector uniforms; + QGles2SamplerDescriptionVector samplers; + QGles2UniformState uniformState[QGles2UniformState::MAX_TRACKED_LOCATION + 1]; + QRhiShaderResourceBindings *currentSrb = nullptr; + uint currentSrbGeneration = 0; + uint generation = 0; + friend class QRhiGles2; +}; + +struct QGles2CommandBuffer : public QRhiCommandBuffer +{ + QGles2CommandBuffer(QRhiImplementation *rhi); + ~QGles2CommandBuffer(); + void destroy() override; + + // keep at a reasonably low value otherwise sizeof Command explodes + static const int MAX_DYNAMIC_OFFSET_COUNT = 8; + + struct Command { + enum Cmd { + BeginFrame, + EndFrame, + ResetFrame, + Viewport, + Scissor, + BlendConstants, + StencilRef, + BindVertexBuffer, + BindIndexBuffer, + Draw, + DrawIndexed, + BindGraphicsPipeline, + BindShaderResources, + BindFramebuffer, + Clear, + BufferSubData, + GetBufferSubData, + CopyTex, + ReadPixels, + SubImage, + CompressedImage, + CompressedSubImage, + BlitFromRenderbuffer, + BlitFromTexture, + GenMip, + BindComputePipeline, + Dispatch, + BarriersForPass, + Barrier + }; + Cmd cmd; + + // QRhi*/QGles2* references should be kept at minimum (so no + // QRhiTexture/Buffer/etc. pointers). + union Args { + struct { + float x, y, w, h; + float d0, d1; + } viewport; + struct { + int x, y, w, h; + } scissor; + struct { + float r, g, b, a; + } blendConstants; + struct { + quint32 ref; + QRhiGraphicsPipeline *ps; + } stencilRef; + struct { + QRhiGraphicsPipeline *ps; + GLuint buffer; + quint32 offset; + int binding; + } bindVertexBuffer; + struct { + GLuint buffer; + quint32 offset; + GLenum type; + } bindIndexBuffer; + struct { + QRhiGraphicsPipeline *ps; + quint32 vertexCount; + quint32 firstVertex; + quint32 instanceCount; + quint32 baseInstance; + } draw; + struct { + QRhiGraphicsPipeline *ps; + quint32 indexCount; + quint32 firstIndex; + quint32 instanceCount; + quint32 baseInstance; + qint32 baseVertex; + } drawIndexed; + struct { + QRhiGraphicsPipeline *ps; + } bindGraphicsPipeline; + struct { + QRhiGraphicsPipeline *maybeGraphicsPs; + QRhiComputePipeline *maybeComputePs; + QRhiShaderResourceBindings *srb; + int dynamicOffsetCount; + uint dynamicOffsetPairs[MAX_DYNAMIC_OFFSET_COUNT * 2]; // binding, offset + } bindShaderResources; + struct { + GLbitfield mask; + float c[4]; + float d; + quint32 s; + } clear; + struct { + GLuint fbo; + bool srgb; + int colorAttCount; + bool stereo; + QRhiSwapChain::StereoTargetBuffer stereoTarget; + } bindFramebuffer; + struct { + GLenum target; + GLuint buffer; + int offset; + int size; + const void *data; // must come from retainData() + } bufferSubData; + struct { + QRhiReadbackResult *result; + GLenum target; + GLuint buffer; + int offset; + int size; + } getBufferSubData; + struct { + GLenum srcTarget; + GLenum srcFaceTarget; + GLuint srcTexture; + int srcLevel; + int srcX; + int srcY; + int srcZ; + GLenum dstTarget; + GLuint dstTexture; + GLenum dstFaceTarget; + int dstLevel; + int dstX; + int dstY; + int dstZ; + int w; + int h; + } copyTex; + struct { + QRhiReadbackResult *result; + GLuint texture; + int w; + int h; + QRhiTexture::Format format; + GLenum readTarget; + int level; + int slice3D; + } readPixels; + struct { + GLenum target; + GLuint texture; + GLenum faceTarget; + int level; + int dx; + int dy; + int dz; + int w; + int h; + GLenum glformat; + GLenum gltype; + int rowStartAlign; + int rowLength; + const void *data; // must come from retainImage() + } subImage; + struct { + GLenum target; + GLuint texture; + GLenum faceTarget; + int level; + GLenum glintformat; + int w; + int h; + int depth; + int size; + const void *data; // must come from retainData() + } compressedImage; + struct { + GLenum target; + GLuint texture; + GLenum faceTarget; + int level; + int dx; + int dy; + int dz; + int w; + int h; + GLenum glintformat; + int size; + const void *data; // must come from retainData() + } compressedSubImage; + struct { + GLuint renderbuffer; + int w; + int h; + GLenum target; + GLuint dstTexture; + int dstLevel; + int dstLayer; + } blitFromRenderbuffer; + struct { + GLenum srcTarget; + GLuint srcTexture; + int srcLevel; + int srcLayer; + int w; + int h; + GLenum dstTarget; + GLuint dstTexture; + int dstLevel; + int dstLayer; + } blitFromTexture; + struct { + GLenum target; + GLuint texture; + } genMip; + struct { + QRhiComputePipeline *ps; + } bindComputePipeline; + struct { + GLuint x; + GLuint y; + GLuint z; + } dispatch; + struct { + int trackerIndex; + } barriersForPass; + struct { + GLbitfield barriers; + } barrier; + } args; + }; + + enum PassType { + NoPass, + RenderPass, + ComputePass + }; + + QRhiBackendCommandList commands; + QVarLengthArray passResTrackers; + int currentPassResTrackerIndex; + + PassType recordingPass; + bool passNeedsResourceTracking; + QRhiRenderTarget *currentTarget; + QRhiGraphicsPipeline *currentGraphicsPipeline; + QRhiComputePipeline *currentComputePipeline; + uint currentPipelineGeneration; + QRhiShaderResourceBindings *currentGraphicsSrb; + QRhiShaderResourceBindings *currentComputeSrb; + uint currentSrbGeneration; + + struct GraphicsPassState { + bool valid = false; + bool scissor; + bool cullFace; + GLenum cullMode; + GLenum frontFace; + bool blendEnabled; + struct ColorMask { bool r, g, b, a; } colorMask; + struct Blend { + GLenum srcColor; + GLenum dstColor; + GLenum srcAlpha; + GLenum dstAlpha; + GLenum opColor; + GLenum opAlpha; + } blend; + bool depthTest; + bool depthWrite; + GLenum depthFunc; + bool stencilTest; + GLuint stencilReadMask; + GLuint stencilWriteMask; + struct StencilFace { + GLenum func; + GLenum failOp; + GLenum zfailOp; + GLenum zpassOp; + } stencil[2]; // front, back + bool polyOffsetFill; + float polyOffsetFactor; + float polyOffsetUnits; + float lineWidth; + int cpCount; + GLenum polygonMode; + void reset() { valid = false; } + struct { + // not part of QRhiGraphicsPipeline but used by setGraphicsPipeline() + GLint stencilRef = 0; + } dynamic; + } graphicsPassState; + + struct ComputePassState { + enum Access { + Read = 0x01, + Write = 0x02 + }; + QHash > writtenResources; + void reset() { + writtenResources.clear(); + } + } computePassState; + + struct TextureUnitState { + void *ps; + uint psGeneration; + uint texture; + } textureUnitState[16]; + + QVarLengthArray dataRetainPool; + QVarLengthArray bufferDataRetainPool; + QVarLengthArray imageRetainPool; + + // relies heavily on implicit sharing (no copies of the actual data will be made) + const void *retainData(const QByteArray &data) { + dataRetainPool.append(data); + return dataRetainPool.last().constData(); + } + const uchar *retainBufferData(const QRhiBufferData &data) { + bufferDataRetainPool.append(data); + return reinterpret_cast(bufferDataRetainPool.last().constData()); + } + const void *retainImage(const QImage &image) { + imageRetainPool.append(image); + return imageRetainPool.last().constBits(); + } + void resetCommands() { + commands.reset(); + dataRetainPool.clear(); + bufferDataRetainPool.clear(); + imageRetainPool.clear(); + + passResTrackers.clear(); + currentPassResTrackerIndex = -1; + } + void resetState() { + recordingPass = NoPass; + passNeedsResourceTracking = true; + currentTarget = nullptr; + resetCommands(); + resetCachedState(); + } + void resetCachedState() { + currentGraphicsPipeline = nullptr; + currentComputePipeline = nullptr; + currentPipelineGeneration = 0; + currentGraphicsSrb = nullptr; + currentComputeSrb = nullptr; + currentSrbGeneration = 0; + graphicsPassState.reset(); + computePassState.reset(); + memset(textureUnitState, 0, sizeof(textureUnitState)); + } +}; + +inline bool operator==(const QGles2CommandBuffer::GraphicsPassState::StencilFace &a, + const QGles2CommandBuffer::GraphicsPassState::StencilFace &b) +{ + return a.func == b.func + && a.failOp == b.failOp + && a.zfailOp == b.zfailOp + && a.zpassOp == b.zpassOp; +} + +inline bool operator!=(const QGles2CommandBuffer::GraphicsPassState::StencilFace &a, + const QGles2CommandBuffer::GraphicsPassState::StencilFace &b) +{ + return !(a == b); +} + +inline bool operator==(const QGles2CommandBuffer::GraphicsPassState::ColorMask &a, + const QGles2CommandBuffer::GraphicsPassState::ColorMask &b) +{ + return a.r == b.r && a.g == b.g && a.b == b.b && a.a == b.a; +} + +inline bool operator!=(const QGles2CommandBuffer::GraphicsPassState::ColorMask &a, + const QGles2CommandBuffer::GraphicsPassState::ColorMask &b) +{ + return !(a == b); +} + +inline bool operator==(const QGles2CommandBuffer::GraphicsPassState::Blend &a, + const QGles2CommandBuffer::GraphicsPassState::Blend &b) +{ + return a.srcColor == b.srcColor + && a.dstColor == b.dstColor + && a.srcAlpha == b.srcAlpha + && a.dstAlpha == b.dstAlpha + && a.opColor == b.opColor + && a.opAlpha == b.opAlpha; +} + +inline bool operator!=(const QGles2CommandBuffer::GraphicsPassState::Blend &a, + const QGles2CommandBuffer::GraphicsPassState::Blend &b) +{ + return !(a == b); +} + +struct QGles2SwapChain : public QRhiSwapChain +{ + QGles2SwapChain(QRhiImplementation *rhi); + ~QGles2SwapChain(); + void destroy() override; + + QRhiCommandBuffer *currentFrameCommandBuffer() override; + QRhiRenderTarget *currentFrameRenderTarget() override; + QRhiRenderTarget *currentFrameRenderTarget(StereoTargetBuffer targetBuffer) override; + + QSize surfacePixelSize() override; + bool isFormatSupported(Format f) override; + + QRhiRenderPassDescriptor *newCompatibleRenderPassDescriptor() override; + bool createOrResize() override; + + void initSwapChainRenderTarget(QGles2SwapChainRenderTarget *rt); + + QSurface *surface = nullptr; + QSize pixelSize; + QGles2SwapChainRenderTarget rt; + QGles2SwapChainRenderTarget rtLeft; + QGles2SwapChainRenderTarget rtRight; + QGles2CommandBuffer cb; + int frameCount = 0; +}; + +class QRhiGles2 : public QRhiImplementation +{ +public: + QRhiGles2(QRhiGles2InitParams *params, QRhiGles2NativeHandles *importDevice = nullptr); + + bool create(QRhi::Flags flags) override; + void destroy() override; + + QRhiGraphicsPipeline *createGraphicsPipeline() override; + QRhiComputePipeline *createComputePipeline() override; + QRhiShaderResourceBindings *createShaderResourceBindings() override; + QRhiBuffer *createBuffer(QRhiBuffer::Type type, + QRhiBuffer::UsageFlags usage, + quint32 size) override; + QRhiRenderBuffer *createRenderBuffer(QRhiRenderBuffer::Type type, + const QSize &pixelSize, + int sampleCount, + QRhiRenderBuffer::Flags flags, + QRhiTexture::Format backingFormatHint) override; + QRhiTexture *createTexture(QRhiTexture::Format format, + const QSize &pixelSize, + int depth, + int arraySize, + int sampleCount, + QRhiTexture::Flags flags) override; + QRhiSampler *createSampler(QRhiSampler::Filter magFilter, + QRhiSampler::Filter minFilter, + QRhiSampler::Filter mipmapMode, + QRhiSampler:: AddressMode u, + QRhiSampler::AddressMode v, + QRhiSampler::AddressMode w) override; + + QRhiTextureRenderTarget *createTextureRenderTarget(const QRhiTextureRenderTargetDescription &desc, + QRhiTextureRenderTarget::Flags flags) override; + + QRhiSwapChain *createSwapChain() override; + QRhi::FrameOpResult beginFrame(QRhiSwapChain *swapChain, QRhi::BeginFrameFlags flags) override; + QRhi::FrameOpResult endFrame(QRhiSwapChain *swapChain, QRhi::EndFrameFlags flags) override; + QRhi::FrameOpResult beginOffscreenFrame(QRhiCommandBuffer **cb, QRhi::BeginFrameFlags flags) override; + QRhi::FrameOpResult endOffscreenFrame(QRhi::EndFrameFlags flags) override; + QRhi::FrameOpResult finish() override; + + void resourceUpdate(QRhiCommandBuffer *cb, QRhiResourceUpdateBatch *resourceUpdates) override; + + void beginPass(QRhiCommandBuffer *cb, + QRhiRenderTarget *rt, + const QColor &colorClearValue, + const QRhiDepthStencilClearValue &depthStencilClearValue, + QRhiResourceUpdateBatch *resourceUpdates, + QRhiCommandBuffer::BeginPassFlags flags) override; + void endPass(QRhiCommandBuffer *cb, QRhiResourceUpdateBatch *resourceUpdates) override; + + void setGraphicsPipeline(QRhiCommandBuffer *cb, + QRhiGraphicsPipeline *ps) override; + + void setShaderResources(QRhiCommandBuffer *cb, + QRhiShaderResourceBindings *srb, + int dynamicOffsetCount, + const QRhiCommandBuffer::DynamicOffset *dynamicOffsets) override; + + void setVertexInput(QRhiCommandBuffer *cb, + int startBinding, int bindingCount, const QRhiCommandBuffer::VertexInput *bindings, + QRhiBuffer *indexBuf, quint32 indexOffset, + QRhiCommandBuffer::IndexFormat indexFormat) override; + + void setViewport(QRhiCommandBuffer *cb, const QRhiViewport &viewport) override; + void setScissor(QRhiCommandBuffer *cb, const QRhiScissor &scissor) override; + void setBlendConstants(QRhiCommandBuffer *cb, const QColor &c) override; + void setStencilRef(QRhiCommandBuffer *cb, quint32 refValue) override; + + void draw(QRhiCommandBuffer *cb, quint32 vertexCount, + quint32 instanceCount, quint32 firstVertex, quint32 firstInstance) override; + + void drawIndexed(QRhiCommandBuffer *cb, quint32 indexCount, + quint32 instanceCount, quint32 firstIndex, + qint32 vertexOffset, quint32 firstInstance) override; + + void debugMarkBegin(QRhiCommandBuffer *cb, const QByteArray &name) override; + void debugMarkEnd(QRhiCommandBuffer *cb) override; + void debugMarkMsg(QRhiCommandBuffer *cb, const QByteArray &msg) override; + + void beginComputePass(QRhiCommandBuffer *cb, + QRhiResourceUpdateBatch *resourceUpdates, + QRhiCommandBuffer::BeginPassFlags flags) override; + void endComputePass(QRhiCommandBuffer *cb, QRhiResourceUpdateBatch *resourceUpdates) override; + void setComputePipeline(QRhiCommandBuffer *cb, QRhiComputePipeline *ps) override; + void dispatch(QRhiCommandBuffer *cb, int x, int y, int z) override; + + const QRhiNativeHandles *nativeHandles(QRhiCommandBuffer *cb) override; + void beginExternal(QRhiCommandBuffer *cb) override; + void endExternal(QRhiCommandBuffer *cb) override; + double lastCompletedGpuTime(QRhiCommandBuffer *cb) override; + + QList supportedSampleCounts() const override; + int ubufAlignment() const override; + bool isYUpInFramebuffer() const override; + bool isYUpInNDC() const override; + bool isClipDepthZeroToOne() const override; + QMatrix4x4 clipSpaceCorrMatrix() const override; + bool isTextureFormatSupported(QRhiTexture::Format format, QRhiTexture::Flags flags) const override; + bool isFeatureSupported(QRhi::Feature feature) const override; + int resourceLimit(QRhi::ResourceLimit limit) const override; + const QRhiNativeHandles *nativeHandles() override; + QRhiDriverInfo driverInfo() const override; + QRhiStats statistics() override; + bool makeThreadLocalNativeContextCurrent() override; + void releaseCachedResources() override; + bool isDeviceLost() const override; + + QByteArray pipelineCacheData() override; + void setPipelineCacheData(const QByteArray &data) override; + + bool ensureContext(QSurface *surface = nullptr) const; + QSurface *evaluateFallbackSurface() const; + void executeDeferredReleases(); + void trackedBufferBarrier(QGles2CommandBuffer *cbD, QGles2Buffer *bufD, QGles2Buffer::Access access); + void trackedImageBarrier(QGles2CommandBuffer *cbD, QGles2Texture *texD, QGles2Texture::Access access); + void enqueueSubresUpload(QGles2Texture *texD, QGles2CommandBuffer *cbD, + int layer, int level, const QRhiTextureSubresourceUploadDescription &subresDesc); + void enqueueResourceUpdates(QRhiCommandBuffer *cb, QRhiResourceUpdateBatch *resourceUpdates); + void trackedRegisterBuffer(QRhiPassResourceTracker *passResTracker, + QGles2Buffer *bufD, + QRhiPassResourceTracker::BufferAccess access, + QRhiPassResourceTracker::BufferStage stage); + void trackedRegisterTexture(QRhiPassResourceTracker *passResTracker, + QGles2Texture *texD, + QRhiPassResourceTracker::TextureAccess access, + QRhiPassResourceTracker::TextureStage stage); + void executeCommandBuffer(QRhiCommandBuffer *cb); + void executeBindGraphicsPipeline(QGles2CommandBuffer *cbD, QGles2GraphicsPipeline *psD); + void bindCombinedSampler(QGles2CommandBuffer *cbD, QGles2Texture *texD, QGles2Sampler *samplerD, + void *ps, uint psGeneration, int glslLocation, + int *texUnit, bool *activeTexUnitAltered); + void bindShaderResources(QGles2CommandBuffer *cbD, + QRhiGraphicsPipeline *maybeGraphicsPs, QRhiComputePipeline *maybeComputePs, + QRhiShaderResourceBindings *srb, + const uint *dynOfsPairs, int dynOfsCount); + 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); + void registerUniformIfActive(const QShaderDescription::BlockVariable &var, + const QByteArray &namePrefix, int binding, int baseOffset, + GLuint program, + QDuplicateTracker *activeUniformLocations, + QGles2UniformDescriptionVector *dst); + void gatherUniforms(GLuint program, const QShaderDescription::UniformBlock &ub, + QDuplicateTracker *activeUniformLocations, QGles2UniformDescriptionVector *dst); + void gatherSamplers(GLuint program, const QShaderDescription::InOutVariable &v, + QGles2SamplerDescriptionVector *dst); + void gatherGeneratedSamplers(GLuint program, + const QShader::SeparateToCombinedImageSamplerMapping &mapping, + QGles2SamplerDescriptionVector *dst); + void sanityCheckVertexFragmentInterface(const QShaderDescription &vsDesc, const QShaderDescription &fsDesc); + bool isProgramBinaryDiskCacheEnabled() const; + + enum ProgramCacheResult { + ProgramCacheHit, + ProgramCacheMiss, + ProgramCacheError + }; + ProgramCacheResult tryLoadFromDiskOrPipelineCache(const QRhiShaderStage *stages, + int stageCount, + GLuint program, + const QVector &inputVars, + QByteArray *cacheKey); + void trySaveToDiskCache(GLuint program, const QByteArray &cacheKey); + void trySaveToPipelineCache(GLuint program, const QByteArray &cacheKey, bool force = false); + + QRhi::Flags rhiFlags; + QOpenGLContext *ctx = nullptr; + bool importedContext = false; + QSurfaceFormat requestedFormat; QSurface *fallbackSurface = nullptr; - QWindow *window = nullptr; - QOpenGLContext *shareContext = nullptr; + QPointer maybeWindow = nullptr; + QOpenGLContext *maybeShareContext = nullptr; + mutable bool needsMakeCurrentDueToSwap = false; + QOpenGLExtensions *f = nullptr; + void (QOPENGLF_APIENTRYP glPolygonMode) (GLenum, GLenum) = nullptr; + void(QOPENGLF_APIENTRYP glTexImage1D)(GLenum, GLint, GLint, GLsizei, GLint, GLenum, GLenum, + const void *) = nullptr; + void(QOPENGLF_APIENTRYP glTexStorage1D)(GLenum, GLint, GLenum, GLsizei) = nullptr; + void(QOPENGLF_APIENTRYP glTexSubImage1D)(GLenum, GLint, GLint, GLsizei, GLenum, GLenum, + const GLvoid *) = nullptr; + void(QOPENGLF_APIENTRYP glCopyTexSubImage1D)(GLenum, GLint, GLint, GLint, GLint, + GLsizei) = nullptr; + void(QOPENGLF_APIENTRYP glCompressedTexImage1D)(GLenum, GLint, GLenum, GLsizei, GLint, GLsizei, + const GLvoid *) = nullptr; + void(QOPENGLF_APIENTRYP glCompressedTexSubImage1D)(GLenum, GLint, GLint, GLsizei, GLenum, + GLsizei, const GLvoid *) = nullptr; + void(QOPENGLF_APIENTRYP glFramebufferTexture1D)(GLenum, GLenum, GLenum, GLuint, + GLint) = nullptr; - static QOffscreenSurface *newFallbackSurface(const QSurfaceFormat &format = QSurfaceFormat::defaultFormat()); + uint vao = 0; + struct Caps { + Caps() + : ctxMajor(2), + ctxMinor(0), + maxTextureSize(2048), + maxDrawBuffers(4), + maxSamples(16), + maxTextureArraySize(0), + maxThreadGroupsPerDimension(0), + maxThreadsPerThreadGroup(0), + maxThreadGroupsX(0), + maxThreadGroupsY(0), + maxThreadGroupsZ(0), + maxUniformVectors(4096), + maxVertexInputs(8), + maxVertexOutputs(8), + msaaRenderBuffer(false), + multisampledTexture(false), + npotTextureFull(true), + gles(false), + fixedIndexPrimitiveRestart(false), + bgraExternalFormat(false), + bgraInternalFormat(false), + r8Format(false), + r16Format(false), + floatFormats(false), + rgb10Formats(false), + depthTexture(false), + packedDepthStencil(false), + needsDepthStencilCombinedAttach(false), + srgbCapableDefaultFramebuffer(false), + coreProfile(false), + uniformBuffers(false), + elementIndexUint(false), + depth24(false), + rgba8Format(false), + instancing(false), + baseVertex(false), + compute(false), + textureCompareMode(false), + properMapBuffer(false), + nonBaseLevelFramebufferTexture(false), + texelFetch(false), + intAttributes(true), + screenSpaceDerivatives(false), + programBinary(false), + texture3D(false), + tessellation(false), + geometryShader(false), + texture1D(false), + hasDrawBuffersFunc(false), + halfAttributes(false) + { } + int ctxMajor; + int ctxMinor; + int maxTextureSize; + int maxDrawBuffers; + int maxSamples; + int maxTextureArraySize; + int maxThreadGroupsPerDimension; + int maxThreadsPerThreadGroup; + int maxThreadGroupsX; + int maxThreadGroupsY; + int maxThreadGroupsZ; + int maxUniformVectors; + int maxVertexInputs; + int maxVertexOutputs; + // Multisample fb and blit are supported (GLES 3.0 or OpenGL 3.x). Not + // the same as multisample textures! + uint msaaRenderBuffer : 1; + uint multisampledTexture : 1; + uint npotTextureFull : 1; + uint gles : 1; + uint fixedIndexPrimitiveRestart : 1; + uint bgraExternalFormat : 1; + uint bgraInternalFormat : 1; + uint r8Format : 1; + uint r16Format : 1; + uint floatFormats : 1; + uint rgb10Formats : 1; + uint depthTexture : 1; + uint packedDepthStencil : 1; + uint needsDepthStencilCombinedAttach : 1; + uint srgbCapableDefaultFramebuffer : 1; + uint coreProfile : 1; + uint uniformBuffers : 1; + uint elementIndexUint : 1; + uint depth24 : 1; + uint rgba8Format : 1; + uint instancing : 1; + uint baseVertex : 1; + uint compute : 1; + uint textureCompareMode : 1; + uint properMapBuffer : 1; + uint nonBaseLevelFramebufferTexture : 1; + uint texelFetch : 1; + uint intAttributes : 1; + uint screenSpaceDerivatives : 1; + uint programBinary : 1; + uint texture3D : 1; + uint tessellation : 1; + uint geometryShader : 1; + uint texture1D : 1; + uint hasDrawBuffersFunc : 1; + uint halfAttributes : 1; + } caps; + QGles2SwapChain *currentSwapChain = nullptr; + QSet supportedCompressedFormats; + mutable QList supportedSampleCountList; + QRhiGles2NativeHandles nativeHandlesStruct; + QRhiDriverInfo driverInfoStruct; + mutable bool contextLost = false; + + struct DeferredReleaseEntry { + enum Type { + Buffer, + Pipeline, + Texture, + RenderBuffer, + TextureRenderTarget + }; + Type type; + union { + struct { + GLuint buffer; + } buffer; + struct { + GLuint program; + } pipeline; + struct { + GLuint texture; + } texture; + struct { + GLuint renderbuffer; + GLuint renderbuffer2; + } renderbuffer; + struct { + GLuint framebuffer; + } textureRenderTarget; + }; + }; + QList releaseQueue; + + struct OffscreenFrame { + OffscreenFrame(QRhiImplementation *rhi) : cbWrapper(rhi) { } + bool active = false; + QGles2CommandBuffer cbWrapper; + } ofr; + + QHash m_shaderCache; + + struct PipelineCacheData { + quint32 format; + QByteArray data; + }; + QHash m_pipelineCache; }; -struct Q_GUI_EXPORT QRhiGles2NativeHandles : public QRhiNativeHandles -{ - QOpenGLContext *context = nullptr; -}; +Q_DECLARE_TYPEINFO(QRhiGles2::DeferredReleaseEntry, Q_RELOCATABLE_TYPE); QT_END_NAMESPACE diff --git a/src/gui/rhi/qrhigles2_p_p.h b/src/gui/rhi/qrhigles2_p_p.h deleted file mode 100644 index 7caec5f7..00000000 --- a/src/gui/rhi/qrhigles2_p_p.h +++ /dev/null @@ -1,1074 +0,0 @@ -// Copyright (C) 2019 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 QRHIGLES2_P_H -#define QRHIGLES2_P_H - -// -// W A R N I N G -// ------------- -// -// This file is not part of the Qt API. It exists purely as an -// implementation detail. This header file may change from version to -// version without notice, or even be removed. -// -// We mean it. -// - -#include "qrhigles2_p.h" -#include "qrhi_p_p.h" -#include "qshaderdescription_p.h" -#include -#include -#include -#include -#include -#include - -QT_BEGIN_NAMESPACE - -class QOpenGLExtensions; - -struct QGles2Buffer : public QRhiBuffer -{ - QGles2Buffer(QRhiImplementation *rhi, Type type, UsageFlags usage, quint32 size); - ~QGles2Buffer(); - void destroy() override; - bool create() override; - QRhiBuffer::NativeBuffer nativeBuffer() override; - char *beginFullDynamicBufferUpdateForCurrentFrame() override; - void endFullDynamicBufferUpdateForCurrentFrame() override; - - quint32 nonZeroSize = 0; - GLuint buffer = 0; - GLenum targetForDataOps; - QByteArray data; - enum Access { - AccessNone, - AccessVertex, - AccessIndex, - AccessUniform, - AccessStorageRead, - AccessStorageWrite, - AccessStorageReadWrite, - AccessUpdate - }; - struct UsageState { - Access access; - }; - UsageState usageState; - friend class QRhiGles2; -}; - -struct QGles2RenderBuffer : public QRhiRenderBuffer -{ - QGles2RenderBuffer(QRhiImplementation *rhi, Type type, const QSize &pixelSize, - int sampleCount, QRhiRenderBuffer::Flags flags, - QRhiTexture::Format backingFormatHint); - ~QGles2RenderBuffer(); - void destroy() override; - bool create() override; - bool createFrom(NativeRenderBuffer src) override; - QRhiTexture::Format backingFormat() const override; - - GLuint renderbuffer = 0; - GLuint stencilRenderbuffer = 0; // when packed depth-stencil not supported - int samples; - bool owns = true; - uint generation = 0; - friend class QRhiGles2; -}; - -struct QGles2SamplerData -{ - GLenum glminfilter = 0; - GLenum glmagfilter = 0; - GLenum glwraps = 0; - GLenum glwrapt = 0; - GLenum glwrapr = 0; - GLenum gltexcomparefunc = 0; -}; - -inline bool operator==(const QGles2SamplerData &a, const QGles2SamplerData &b) -{ - return a.glminfilter == b.glminfilter - && a.glmagfilter == b.glmagfilter - && a.glwraps == b.glwraps - && a.glwrapt == b.glwrapt - && a.glwrapr == b.glwrapr - && a.gltexcomparefunc == b.gltexcomparefunc; -} - -inline bool operator!=(const QGles2SamplerData &a, const QGles2SamplerData &b) -{ - return !(a == b); -} - -struct QGles2Texture : public QRhiTexture -{ - QGles2Texture(QRhiImplementation *rhi, Format format, const QSize &pixelSize, int depth, - int arraySize, int sampleCount, Flags flags); - ~QGles2Texture(); - void destroy() override; - bool create() override; - bool createFrom(NativeTexture src) override; - NativeTexture nativeTexture() override; - - bool prepareCreate(QSize *adjustedSize = nullptr); - - GLuint texture = 0; - bool owns = true; - GLenum target; - GLenum glintformat; - GLenum glsizedintformat; - GLenum glformat; - GLenum gltype; - QGles2SamplerData samplerState; - bool specified = false; - bool zeroInitialized = false; - int mipLevelCount = 0; - - enum Access { - AccessNone, - AccessSample, - AccessFramebuffer, - AccessStorageRead, - AccessStorageWrite, - AccessStorageReadWrite, - AccessUpdate, - AccessRead - }; - struct UsageState { - Access access; - }; - UsageState usageState; - - uint generation = 0; - friend class QRhiGles2; -}; - -struct QGles2Sampler : public QRhiSampler -{ - QGles2Sampler(QRhiImplementation *rhi, Filter magFilter, Filter minFilter, Filter mipmapMode, - AddressMode u, AddressMode v, AddressMode w); - ~QGles2Sampler(); - void destroy() override; - bool create() override; - - QGles2SamplerData d; - uint generation = 0; - friend class QRhiGles2; -}; - -struct QGles2RenderPassDescriptor : public QRhiRenderPassDescriptor -{ - QGles2RenderPassDescriptor(QRhiImplementation *rhi); - ~QGles2RenderPassDescriptor(); - void destroy() override; - bool isCompatible(const QRhiRenderPassDescriptor *other) const override; - QRhiRenderPassDescriptor *newCompatibleRenderPassDescriptor() const override; - QVector serializedFormat() const override; -}; - -struct QGles2RenderTargetData -{ - QGles2RenderTargetData(QRhiImplementation *) { } - - bool isValid() const { return rp != nullptr; } - - QGles2RenderPassDescriptor *rp = nullptr; - QSize pixelSize; - float dpr = 1; - int sampleCount = 1; - int colorAttCount = 0; - int dsAttCount = 0; - bool srgbUpdateAndBlend = false; - QRhiRenderTargetAttachmentTracker::ResIdList currentResIdList; - std::optional stereoTarget; -}; - -struct QGles2SwapChainRenderTarget : public QRhiSwapChainRenderTarget -{ - QGles2SwapChainRenderTarget(QRhiImplementation *rhi, QRhiSwapChain *swapchain); - ~QGles2SwapChainRenderTarget(); - void destroy() override; - - QSize pixelSize() const override; - float devicePixelRatio() const override; - int sampleCount() const override; - - QGles2RenderTargetData d; -}; - -struct QGles2TextureRenderTarget : public QRhiTextureRenderTarget -{ - QGles2TextureRenderTarget(QRhiImplementation *rhi, const QRhiTextureRenderTargetDescription &desc, Flags flags); - ~QGles2TextureRenderTarget(); - void destroy() override; - - QSize pixelSize() const override; - float devicePixelRatio() const override; - int sampleCount() const override; - - QRhiRenderPassDescriptor *newCompatibleRenderPassDescriptor() override; - bool create() override; - - QGles2RenderTargetData d; - GLuint framebuffer = 0; - friend class QRhiGles2; -}; - -struct QGles2ShaderResourceBindings : public QRhiShaderResourceBindings -{ - QGles2ShaderResourceBindings(QRhiImplementation *rhi); - ~QGles2ShaderResourceBindings(); - void destroy() override; - bool create() override; - void updateResources(UpdateFlags flags) override; - - bool hasDynamicOffset = false; - uint generation = 0; - friend class QRhiGles2; -}; - -struct QGles2UniformDescription -{ - QShaderDescription::VariableType type; - int glslLocation; - int binding; - quint32 offset; - quint32 size; - int arrayDim; -}; - -Q_DECLARE_TYPEINFO(QGles2UniformDescription, Q_RELOCATABLE_TYPE); - -struct QGles2SamplerDescription -{ - int glslLocation; - int combinedBinding; - int tbinding; - int sbinding; -}; - -Q_DECLARE_TYPEINFO(QGles2SamplerDescription, Q_RELOCATABLE_TYPE); - -using QGles2UniformDescriptionVector = QVarLengthArray; -using QGles2SamplerDescriptionVector = QVarLengthArray; - -struct QGles2UniformState -{ - static constexpr int MAX_TRACKED_LOCATION = 1023; - int componentCount; - float v[4]; -}; - -struct QGles2GraphicsPipeline : public QRhiGraphicsPipeline -{ - QGles2GraphicsPipeline(QRhiImplementation *rhi); - ~QGles2GraphicsPipeline(); - void destroy() override; - bool create() override; - - GLuint program = 0; - GLenum drawMode = GL_TRIANGLES; - QGles2UniformDescriptionVector uniforms; - QGles2SamplerDescriptionVector samplers; - QGles2UniformState uniformState[QGles2UniformState::MAX_TRACKED_LOCATION + 1]; - QRhiShaderResourceBindings *currentSrb = nullptr; - uint currentSrbGeneration = 0; - uint generation = 0; - friend class QRhiGles2; -}; - -struct QGles2ComputePipeline : public QRhiComputePipeline -{ - QGles2ComputePipeline(QRhiImplementation *rhi); - ~QGles2ComputePipeline(); - void destroy() override; - bool create() override; - - GLuint program = 0; - QGles2UniformDescriptionVector uniforms; - QGles2SamplerDescriptionVector samplers; - QGles2UniformState uniformState[QGles2UniformState::MAX_TRACKED_LOCATION + 1]; - QRhiShaderResourceBindings *currentSrb = nullptr; - uint currentSrbGeneration = 0; - uint generation = 0; - friend class QRhiGles2; -}; - -struct QGles2CommandBuffer : public QRhiCommandBuffer -{ - QGles2CommandBuffer(QRhiImplementation *rhi); - ~QGles2CommandBuffer(); - void destroy() override; - - // keep at a reasonably low value otherwise sizeof Command explodes - static const int MAX_DYNAMIC_OFFSET_COUNT = 8; - - struct Command { - enum Cmd { - BeginFrame, - EndFrame, - ResetFrame, - Viewport, - Scissor, - BlendConstants, - StencilRef, - BindVertexBuffer, - BindIndexBuffer, - Draw, - DrawIndexed, - BindGraphicsPipeline, - BindShaderResources, - BindFramebuffer, - Clear, - BufferSubData, - GetBufferSubData, - CopyTex, - ReadPixels, - SubImage, - CompressedImage, - CompressedSubImage, - BlitFromRenderbuffer, - GenMip, - BindComputePipeline, - Dispatch, - BarriersForPass, - Barrier - }; - Cmd cmd; - - // QRhi*/QGles2* references should be kept at minimum (so no - // QRhiTexture/Buffer/etc. pointers). - union Args { - struct { - float x, y, w, h; - float d0, d1; - } viewport; - struct { - int x, y, w, h; - } scissor; - struct { - float r, g, b, a; - } blendConstants; - struct { - quint32 ref; - QRhiGraphicsPipeline *ps; - } stencilRef; - struct { - QRhiGraphicsPipeline *ps; - GLuint buffer; - quint32 offset; - int binding; - } bindVertexBuffer; - struct { - GLuint buffer; - quint32 offset; - GLenum type; - } bindIndexBuffer; - struct { - QRhiGraphicsPipeline *ps; - quint32 vertexCount; - quint32 firstVertex; - quint32 instanceCount; - quint32 baseInstance; - } draw; - struct { - QRhiGraphicsPipeline *ps; - quint32 indexCount; - quint32 firstIndex; - quint32 instanceCount; - quint32 baseInstance; - qint32 baseVertex; - } drawIndexed; - struct { - QRhiGraphicsPipeline *ps; - } bindGraphicsPipeline; - struct { - QRhiGraphicsPipeline *maybeGraphicsPs; - QRhiComputePipeline *maybeComputePs; - QRhiShaderResourceBindings *srb; - int dynamicOffsetCount; - uint dynamicOffsetPairs[MAX_DYNAMIC_OFFSET_COUNT * 2]; // binding, offset - } bindShaderResources; - struct { - GLbitfield mask; - float c[4]; - float d; - quint32 s; - } clear; - struct { - GLuint fbo; - bool srgb; - int colorAttCount; - bool stereo; - QRhiSwapChain::StereoTargetBuffer stereoTarget; - } bindFramebuffer; - struct { - GLenum target; - GLuint buffer; - int offset; - int size; - const void *data; // must come from retainData() - } bufferSubData; - struct { - QRhiBufferReadbackResult *result; - GLenum target; - GLuint buffer; - int offset; - int size; - } getBufferSubData; - struct { - GLenum srcTarget; - GLenum srcFaceTarget; - GLuint srcTexture; - int srcLevel; - int srcX; - int srcY; - int srcZ; - GLenum dstTarget; - GLuint dstTexture; - GLenum dstFaceTarget; - int dstLevel; - int dstX; - int dstY; - int dstZ; - int w; - int h; - } copyTex; - struct { - QRhiReadbackResult *result; - GLuint texture; - int w; - int h; - QRhiTexture::Format format; - GLenum readTarget; - int level; - int slice3D; - } readPixels; - struct { - GLenum target; - GLuint texture; - GLenum faceTarget; - int level; - int dx; - int dy; - int dz; - int w; - int h; - GLenum glformat; - GLenum gltype; - int rowStartAlign; - int rowLength; - const void *data; // must come from retainImage() - } subImage; - struct { - GLenum target; - GLuint texture; - GLenum faceTarget; - int level; - GLenum glintformat; - int w; - int h; - int depth; - int size; - const void *data; // must come from retainData() - } compressedImage; - struct { - GLenum target; - GLuint texture; - GLenum faceTarget; - int level; - int dx; - int dy; - int dz; - int w; - int h; - GLenum glintformat; - int size; - const void *data; // must come from retainData() - } compressedSubImage; - struct { - GLuint renderbuffer; - int w; - int h; - GLenum target; - GLuint texture; - int dstLevel; - int dstLayer; - } blitFromRb; - struct { - GLenum target; - GLuint texture; - } genMip; - struct { - QRhiComputePipeline *ps; - } bindComputePipeline; - struct { - GLuint x; - GLuint y; - GLuint z; - } dispatch; - struct { - int trackerIndex; - } barriersForPass; - struct { - GLbitfield barriers; - } barrier; - } args; - }; - - enum PassType { - NoPass, - RenderPass, - ComputePass - }; - - QRhiBackendCommandList commands; - QVarLengthArray passResTrackers; - int currentPassResTrackerIndex; - - PassType recordingPass; - bool passNeedsResourceTracking; - QRhiRenderTarget *currentTarget; - QRhiGraphicsPipeline *currentGraphicsPipeline; - QRhiComputePipeline *currentComputePipeline; - uint currentPipelineGeneration; - QRhiShaderResourceBindings *currentGraphicsSrb; - QRhiShaderResourceBindings *currentComputeSrb; - uint currentSrbGeneration; - - struct GraphicsPassState { - bool valid = false; - bool scissor; - bool cullFace; - GLenum cullMode; - GLenum frontFace; - bool blendEnabled; - struct ColorMask { bool r, g, b, a; } colorMask; - struct Blend { - GLenum srcColor; - GLenum dstColor; - GLenum srcAlpha; - GLenum dstAlpha; - GLenum opColor; - GLenum opAlpha; - } blend; - bool depthTest; - bool depthWrite; - GLenum depthFunc; - bool stencilTest; - GLuint stencilReadMask; - GLuint stencilWriteMask; - struct StencilFace { - GLenum func; - GLenum failOp; - GLenum zfailOp; - GLenum zpassOp; - } stencil[2]; // front, back - bool polyOffsetFill; - float polyOffsetFactor; - float polyOffsetUnits; - float lineWidth; - int cpCount; - GLenum polygonMode; - void reset() { valid = false; } - struct { - // not part of QRhiGraphicsPipeline but used by setGraphicsPipeline() - GLint stencilRef = 0; - } dynamic; - } graphicsPassState; - - struct ComputePassState { - enum Access { - Read = 0x01, - Write = 0x02 - }; - QHash > writtenResources; - void reset() { - writtenResources.clear(); - } - } computePassState; - - struct TextureUnitState { - void *ps; - uint psGeneration; - uint texture; - } textureUnitState[16]; - - QVarLengthArray dataRetainPool; - QVarLengthArray bufferDataRetainPool; - QVarLengthArray imageRetainPool; - - // relies heavily on implicit sharing (no copies of the actual data will be made) - const void *retainData(const QByteArray &data) { - dataRetainPool.append(data); - return dataRetainPool.last().constData(); - } - const uchar *retainBufferData(const QRhiBufferData &data) { - bufferDataRetainPool.append(data); - return reinterpret_cast(bufferDataRetainPool.last().constData()); - } - const void *retainImage(const QImage &image) { - imageRetainPool.append(image); - return imageRetainPool.last().constBits(); - } - void resetCommands() { - commands.reset(); - dataRetainPool.clear(); - bufferDataRetainPool.clear(); - imageRetainPool.clear(); - - passResTrackers.clear(); - currentPassResTrackerIndex = -1; - } - void resetState() { - recordingPass = NoPass; - passNeedsResourceTracking = true; - currentTarget = nullptr; - resetCommands(); - resetCachedState(); - } - void resetCachedState() { - currentGraphicsPipeline = nullptr; - currentComputePipeline = nullptr; - currentPipelineGeneration = 0; - currentGraphicsSrb = nullptr; - currentComputeSrb = nullptr; - currentSrbGeneration = 0; - graphicsPassState.reset(); - computePassState.reset(); - memset(textureUnitState, 0, sizeof(textureUnitState)); - } -}; - -inline bool operator==(const QGles2CommandBuffer::GraphicsPassState::StencilFace &a, - const QGles2CommandBuffer::GraphicsPassState::StencilFace &b) -{ - return a.func == b.func - && a.failOp == b.failOp - && a.zfailOp == b.zfailOp - && a.zpassOp == b.zpassOp; -} - -inline bool operator!=(const QGles2CommandBuffer::GraphicsPassState::StencilFace &a, - const QGles2CommandBuffer::GraphicsPassState::StencilFace &b) -{ - return !(a == b); -} - -inline bool operator==(const QGles2CommandBuffer::GraphicsPassState::ColorMask &a, - const QGles2CommandBuffer::GraphicsPassState::ColorMask &b) -{ - return a.r == b.r && a.g == b.g && a.b == b.b && a.a == b.a; -} - -inline bool operator!=(const QGles2CommandBuffer::GraphicsPassState::ColorMask &a, - const QGles2CommandBuffer::GraphicsPassState::ColorMask &b) -{ - return !(a == b); -} - -inline bool operator==(const QGles2CommandBuffer::GraphicsPassState::Blend &a, - const QGles2CommandBuffer::GraphicsPassState::Blend &b) -{ - return a.srcColor == b.srcColor - && a.dstColor == b.dstColor - && a.srcAlpha == b.srcAlpha - && a.dstAlpha == b.dstAlpha - && a.opColor == b.opColor - && a.opAlpha == b.opAlpha; -} - -inline bool operator!=(const QGles2CommandBuffer::GraphicsPassState::Blend &a, - const QGles2CommandBuffer::GraphicsPassState::Blend &b) -{ - return !(a == b); -} - -struct QGles2SwapChain : public QRhiSwapChain -{ - QGles2SwapChain(QRhiImplementation *rhi); - ~QGles2SwapChain(); - void destroy() override; - - QRhiCommandBuffer *currentFrameCommandBuffer() override; - QRhiRenderTarget *currentFrameRenderTarget() override; - QRhiRenderTarget *currentFrameRenderTarget(StereoTargetBuffer targetBuffer) override; - - QSize surfacePixelSize() override; - bool isFormatSupported(Format f) override; - - QRhiRenderPassDescriptor *newCompatibleRenderPassDescriptor() override; - bool createOrResize() override; - - void initSwapChainRenderTarget(QGles2SwapChainRenderTarget *rt); - - QSurface *surface = nullptr; - QSize pixelSize; - QGles2SwapChainRenderTarget rt; - QGles2SwapChainRenderTarget rtLeft; - QGles2SwapChainRenderTarget rtRight; - QGles2CommandBuffer cb; - int frameCount = 0; -}; - -class QRhiGles2 : public QRhiImplementation -{ -public: - QRhiGles2(QRhiGles2InitParams *params, QRhiGles2NativeHandles *importDevice = nullptr); - - bool create(QRhi::Flags flags) override; - void destroy() override; - - QRhiGraphicsPipeline *createGraphicsPipeline() override; - QRhiComputePipeline *createComputePipeline() override; - QRhiShaderResourceBindings *createShaderResourceBindings() override; - QRhiBuffer *createBuffer(QRhiBuffer::Type type, - QRhiBuffer::UsageFlags usage, - quint32 size) override; - QRhiRenderBuffer *createRenderBuffer(QRhiRenderBuffer::Type type, - const QSize &pixelSize, - int sampleCount, - QRhiRenderBuffer::Flags flags, - QRhiTexture::Format backingFormatHint) override; - QRhiTexture *createTexture(QRhiTexture::Format format, - const QSize &pixelSize, - int depth, - int arraySize, - int sampleCount, - QRhiTexture::Flags flags) override; - QRhiSampler *createSampler(QRhiSampler::Filter magFilter, - QRhiSampler::Filter minFilter, - QRhiSampler::Filter mipmapMode, - QRhiSampler:: AddressMode u, - QRhiSampler::AddressMode v, - QRhiSampler::AddressMode w) override; - - QRhiTextureRenderTarget *createTextureRenderTarget(const QRhiTextureRenderTargetDescription &desc, - QRhiTextureRenderTarget::Flags flags) override; - - QRhiSwapChain *createSwapChain() override; - QRhi::FrameOpResult beginFrame(QRhiSwapChain *swapChain, QRhi::BeginFrameFlags flags) override; - QRhi::FrameOpResult endFrame(QRhiSwapChain *swapChain, QRhi::EndFrameFlags flags) override; - QRhi::FrameOpResult beginOffscreenFrame(QRhiCommandBuffer **cb, QRhi::BeginFrameFlags flags) override; - QRhi::FrameOpResult endOffscreenFrame(QRhi::EndFrameFlags flags) override; - QRhi::FrameOpResult finish() override; - - void resourceUpdate(QRhiCommandBuffer *cb, QRhiResourceUpdateBatch *resourceUpdates) override; - - void beginPass(QRhiCommandBuffer *cb, - QRhiRenderTarget *rt, - const QColor &colorClearValue, - const QRhiDepthStencilClearValue &depthStencilClearValue, - QRhiResourceUpdateBatch *resourceUpdates, - QRhiCommandBuffer::BeginPassFlags flags) override; - void endPass(QRhiCommandBuffer *cb, QRhiResourceUpdateBatch *resourceUpdates) override; - - void setGraphicsPipeline(QRhiCommandBuffer *cb, - QRhiGraphicsPipeline *ps) override; - - void setShaderResources(QRhiCommandBuffer *cb, - QRhiShaderResourceBindings *srb, - int dynamicOffsetCount, - const QRhiCommandBuffer::DynamicOffset *dynamicOffsets) override; - - void setVertexInput(QRhiCommandBuffer *cb, - int startBinding, int bindingCount, const QRhiCommandBuffer::VertexInput *bindings, - QRhiBuffer *indexBuf, quint32 indexOffset, - QRhiCommandBuffer::IndexFormat indexFormat) override; - - void setViewport(QRhiCommandBuffer *cb, const QRhiViewport &viewport) override; - void setScissor(QRhiCommandBuffer *cb, const QRhiScissor &scissor) override; - void setBlendConstants(QRhiCommandBuffer *cb, const QColor &c) override; - void setStencilRef(QRhiCommandBuffer *cb, quint32 refValue) override; - - void draw(QRhiCommandBuffer *cb, quint32 vertexCount, - quint32 instanceCount, quint32 firstVertex, quint32 firstInstance) override; - - void drawIndexed(QRhiCommandBuffer *cb, quint32 indexCount, - quint32 instanceCount, quint32 firstIndex, - qint32 vertexOffset, quint32 firstInstance) override; - - void debugMarkBegin(QRhiCommandBuffer *cb, const QByteArray &name) override; - void debugMarkEnd(QRhiCommandBuffer *cb) override; - void debugMarkMsg(QRhiCommandBuffer *cb, const QByteArray &msg) override; - - void beginComputePass(QRhiCommandBuffer *cb, - QRhiResourceUpdateBatch *resourceUpdates, - QRhiCommandBuffer::BeginPassFlags flags) override; - void endComputePass(QRhiCommandBuffer *cb, QRhiResourceUpdateBatch *resourceUpdates) override; - void setComputePipeline(QRhiCommandBuffer *cb, QRhiComputePipeline *ps) override; - void dispatch(QRhiCommandBuffer *cb, int x, int y, int z) override; - - const QRhiNativeHandles *nativeHandles(QRhiCommandBuffer *cb) override; - void beginExternal(QRhiCommandBuffer *cb) override; - void endExternal(QRhiCommandBuffer *cb) override; - - QList supportedSampleCounts() const override; - int ubufAlignment() const override; - bool isYUpInFramebuffer() const override; - bool isYUpInNDC() const override; - bool isClipDepthZeroToOne() const override; - QMatrix4x4 clipSpaceCorrMatrix() const override; - bool isTextureFormatSupported(QRhiTexture::Format format, QRhiTexture::Flags flags) const override; - bool isFeatureSupported(QRhi::Feature feature) const override; - int resourceLimit(QRhi::ResourceLimit limit) const override; - const QRhiNativeHandles *nativeHandles() override; - QRhiDriverInfo driverInfo() const override; - QRhiStats statistics() override; - bool makeThreadLocalNativeContextCurrent() override; - void releaseCachedResources() override; - bool isDeviceLost() const override; - - QByteArray pipelineCacheData() override; - void setPipelineCacheData(const QByteArray &data) override; - - bool ensureContext(QSurface *surface = nullptr) const; - QSurface *evaluateFallbackSurface() const; - void executeDeferredReleases(); - void trackedBufferBarrier(QGles2CommandBuffer *cbD, QGles2Buffer *bufD, QGles2Buffer::Access access); - void trackedImageBarrier(QGles2CommandBuffer *cbD, QGles2Texture *texD, QGles2Texture::Access access); - void enqueueSubresUpload(QGles2Texture *texD, QGles2CommandBuffer *cbD, - int layer, int level, const QRhiTextureSubresourceUploadDescription &subresDesc); - void enqueueResourceUpdates(QRhiCommandBuffer *cb, QRhiResourceUpdateBatch *resourceUpdates); - void trackedRegisterBuffer(QRhiPassResourceTracker *passResTracker, - QGles2Buffer *bufD, - QRhiPassResourceTracker::BufferAccess access, - QRhiPassResourceTracker::BufferStage stage); - void trackedRegisterTexture(QRhiPassResourceTracker *passResTracker, - QGles2Texture *texD, - QRhiPassResourceTracker::TextureAccess access, - QRhiPassResourceTracker::TextureStage stage); - void executeCommandBuffer(QRhiCommandBuffer *cb); - void executeBindGraphicsPipeline(QGles2CommandBuffer *cbD, QGles2GraphicsPipeline *psD); - void bindCombinedSampler(QGles2CommandBuffer *cbD, QGles2Texture *texD, QGles2Sampler *samplerD, - void *ps, uint psGeneration, int glslLocation, - int *texUnit, bool *activeTexUnitAltered); - void bindShaderResources(QGles2CommandBuffer *cbD, - QRhiGraphicsPipeline *maybeGraphicsPs, QRhiComputePipeline *maybeComputePs, - QRhiShaderResourceBindings *srb, - const uint *dynOfsPairs, int dynOfsCount); - 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); - void registerUniformIfActive(const QShaderDescription::BlockVariable &var, - const QByteArray &namePrefix, int binding, int baseOffset, - GLuint program, - QDuplicateTracker *activeUniformLocations, - QGles2UniformDescriptionVector *dst); - void gatherUniforms(GLuint program, const QShaderDescription::UniformBlock &ub, - QDuplicateTracker *activeUniformLocations, QGles2UniformDescriptionVector *dst); - void gatherSamplers(GLuint program, const QShaderDescription::InOutVariable &v, - QGles2SamplerDescriptionVector *dst); - void gatherGeneratedSamplers(GLuint program, - const QShader::SeparateToCombinedImageSamplerMapping &mapping, - QGles2SamplerDescriptionVector *dst); - void sanityCheckVertexFragmentInterface(const QShaderDescription &vsDesc, const QShaderDescription &fsDesc); - bool isProgramBinaryDiskCacheEnabled() const; - - enum ProgramCacheResult { - ProgramCacheHit, - ProgramCacheMiss, - ProgramCacheError - }; - ProgramCacheResult tryLoadFromDiskOrPipelineCache(const QRhiShaderStage *stages, - int stageCount, - GLuint program, - const QVector &inputVars, - QByteArray *cacheKey); - void trySaveToDiskCache(GLuint program, const QByteArray &cacheKey); - void trySaveToPipelineCache(GLuint program, const QByteArray &cacheKey, bool force = false); - - QRhi::Flags rhiFlags; - QOpenGLContext *ctx = nullptr; - bool importedContext = false; - QSurfaceFormat requestedFormat; - QSurface *fallbackSurface = nullptr; - QPointer maybeWindow = nullptr; - QOpenGLContext *maybeShareContext = nullptr; - mutable bool needsMakeCurrentDueToSwap = false; - QOpenGLExtensions *f = nullptr; - void (QOPENGLF_APIENTRYP glPolygonMode) (GLenum, GLenum) = nullptr; - void(QOPENGLF_APIENTRYP glTexImage1D)(GLenum, GLint, GLint, GLsizei, GLint, GLenum, GLenum, - const void *) = nullptr; - void(QOPENGLF_APIENTRYP glTexStorage1D)(GLenum, GLint, GLenum, GLsizei) = nullptr; - void(QOPENGLF_APIENTRYP glTexSubImage1D)(GLenum, GLint, GLint, GLsizei, GLenum, GLenum, - const GLvoid *) = nullptr; - void(QOPENGLF_APIENTRYP glCopyTexSubImage1D)(GLenum, GLint, GLint, GLint, GLint, - GLsizei) = nullptr; - void(QOPENGLF_APIENTRYP glCompressedTexImage1D)(GLenum, GLint, GLenum, GLsizei, GLint, GLsizei, - const GLvoid *) = nullptr; - void(QOPENGLF_APIENTRYP glCompressedTexSubImage1D)(GLenum, GLint, GLint, GLsizei, GLenum, - GLsizei, const GLvoid *) = nullptr; - void(QOPENGLF_APIENTRYP glFramebufferTexture1D)(GLenum, GLenum, GLenum, GLuint, - GLint) = nullptr; - - uint vao = 0; - struct Caps { - Caps() - : ctxMajor(2), - ctxMinor(0), - maxTextureSize(2048), - maxDrawBuffers(4), - maxSamples(16), - maxTextureArraySize(0), - maxThreadGroupsPerDimension(0), - maxThreadsPerThreadGroup(0), - maxThreadGroupsX(0), - maxThreadGroupsY(0), - maxThreadGroupsZ(0), - maxUniformVectors(4096), - maxVertexInputs(8), - maxVertexOutputs(8), - msaaRenderBuffer(false), - multisampledTexture(false), - npotTextureFull(true), - gles(false), - fixedIndexPrimitiveRestart(false), - bgraExternalFormat(false), - bgraInternalFormat(false), - r8Format(false), - r16Format(false), - floatFormats(false), - rgb10Formats(false), - depthTexture(false), - packedDepthStencil(false), - needsDepthStencilCombinedAttach(false), - srgbCapableDefaultFramebuffer(false), - coreProfile(false), - uniformBuffers(false), - elementIndexUint(false), - depth24(false), - rgba8Format(false), - instancing(false), - baseVertex(false), - compute(false), - textureCompareMode(false), - properMapBuffer(false), - nonBaseLevelFramebufferTexture(false), - texelFetch(false), - intAttributes(true), - screenSpaceDerivatives(false), - programBinary(false), - texture3D(false), - tessellation(false), - geometryShader(false), - texture1D(false), - hasDrawBuffersFunc(false) - { } - int ctxMajor; - int ctxMinor; - int maxTextureSize; - int maxDrawBuffers; - int maxSamples; - int maxTextureArraySize; - int maxThreadGroupsPerDimension; - int maxThreadsPerThreadGroup; - int maxThreadGroupsX; - int maxThreadGroupsY; - int maxThreadGroupsZ; - int maxUniformVectors; - int maxVertexInputs; - int maxVertexOutputs; - // Multisample fb and blit are supported (GLES 3.0 or OpenGL 3.x). Not - // the same as multisample textures! - uint msaaRenderBuffer : 1; - uint multisampledTexture : 1; - uint npotTextureFull : 1; - uint gles : 1; - uint fixedIndexPrimitiveRestart : 1; - uint bgraExternalFormat : 1; - uint bgraInternalFormat : 1; - uint r8Format : 1; - uint r16Format : 1; - uint floatFormats : 1; - uint rgb10Formats : 1; - uint depthTexture : 1; - uint packedDepthStencil : 1; - uint needsDepthStencilCombinedAttach : 1; - uint srgbCapableDefaultFramebuffer : 1; - uint coreProfile : 1; - uint uniformBuffers : 1; - uint elementIndexUint : 1; - uint depth24 : 1; - uint rgba8Format : 1; - uint instancing : 1; - uint baseVertex : 1; - uint compute : 1; - uint textureCompareMode : 1; - uint properMapBuffer : 1; - uint nonBaseLevelFramebufferTexture : 1; - uint texelFetch : 1; - uint intAttributes : 1; - uint screenSpaceDerivatives : 1; - uint programBinary : 1; - uint texture3D : 1; - uint tessellation : 1; - uint geometryShader : 1; - uint texture1D : 1; - uint hasDrawBuffersFunc : 1; - } caps; - QGles2SwapChain *currentSwapChain = nullptr; - QSet supportedCompressedFormats; - mutable QList supportedSampleCountList; - QRhiGles2NativeHandles nativeHandlesStruct; - QRhiDriverInfo driverInfoStruct; - mutable bool contextLost = false; - - struct DeferredReleaseEntry { - enum Type { - Buffer, - Pipeline, - Texture, - RenderBuffer, - TextureRenderTarget - }; - Type type; - union { - struct { - GLuint buffer; - } buffer; - struct { - GLuint program; - } pipeline; - struct { - GLuint texture; - } texture; - struct { - GLuint renderbuffer; - GLuint renderbuffer2; - } renderbuffer; - struct { - GLuint framebuffer; - } textureRenderTarget; - }; - }; - QList releaseQueue; - - struct OffscreenFrame { - OffscreenFrame(QRhiImplementation *rhi) : cbWrapper(rhi) { } - bool active = false; - QGles2CommandBuffer cbWrapper; - } ofr; - - QHash m_shaderCache; - - struct PipelineCacheData { - quint32 format; - QByteArray data; - }; - QHash m_pipelineCache; -}; - -Q_DECLARE_TYPEINFO(QRhiGles2::DeferredReleaseEntry, Q_RELOCATABLE_TYPE); - -QT_END_NAMESPACE - -#endif diff --git a/src/gui/rhi/qrhimetal.mm b/src/gui/rhi/qrhimetal.mm index df2d6050..a59a557b 100644 --- a/src/gui/rhi/qrhimetal.mm +++ b/src/gui/rhi/qrhimetal.mm @@ -1,8 +1,8 @@ -// Copyright (C) 2019 The Qt Company Ltd. +// 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 "qrhimetal_p_p.h" -#include +#include "qrhimetal_p.h" +#include "qshader_p.h" #include #include #include @@ -51,9 +51,12 @@ QT_BEGIN_NAMESPACE /*! \class QRhiMetalInitParams \inmodule QtRhi - \internal + \since 6.6 \brief Metal specific initialization parameters. + \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. \badcode @@ -86,14 +89,25 @@ QT_BEGIN_NAMESPACE /*! \class QRhiMetalNativeHandles \inmodule QtRhi - \internal + \since 6.6 \brief Holds the Metal device used by the QRhi. + + \note This is a RHI API with limited compatibility guarantees, see \l QRhi + for details. */ +/*! + \variable QRhiMetalNativeHandles::dev +*/ + +/*! + \variable QRhiMetalNativeHandles::cmdQueue +*/ + /*! \class QRhiMetalCommandBufferNativeHandles \inmodule QtRhi - \internal + \since 6.6 \brief Holds the MTLCommandBuffer and MTLRenderCommandEncoder objects that are backing a QRhiCommandBuffer. \note The command buffer object is only guaranteed to be valid while @@ -105,8 +119,19 @@ QT_BEGIN_NAMESPACE \note The command encoder is only valid while recording a pass, that is, between \l{QRhiCommandBuffer::beginPass()} - \l{QRhiCommandBuffer::endPass()}. + + \note This is a RHI API with limited compatibility guarantees, see \l QRhi + for details. */ +/*! + \variable QRhiMetalCommandBufferNativeHandles::commandBuffer +*/ + +/*! + \variable QRhiMetalCommandBufferNativeHandles::encoder +*/ + struct QMetalShader { id lib = nil; @@ -194,6 +219,7 @@ struct QRhiMetalData struct OffscreenFrame { OffscreenFrame(QRhiImplementation *rhi) : cbWrapper(rhi) { } bool active = false; + double lastGpuTime = 0; QMetalCommandBuffer cbWrapper; } ofr; @@ -208,6 +234,17 @@ struct QRhiMetalData }; QVarLengthArray activeTextureReadbacks; + struct BufferReadback + { + int activeFrameSlot = -1; + QRhiReadbackResult *result; + quint32 offset; + quint32 readSize; + id buf; + }; + + QVarLengthArray activeBufferReadbacks; + MTLCaptureManager *captureMgr; id captureScope = nil; @@ -285,6 +322,7 @@ struct QMetalShaderResourceBindingsData { struct QMetalCommandBufferData { id cb; + double lastGpuTime = 0; id currentRenderPassEncoder; id currentComputePassEncoder; id tessellationComputeEncoder; @@ -382,6 +420,9 @@ struct QMetalGraphicsPipelineData } tess; void setupVertexInputDescriptor(MTLVertexDescriptor *desc); void setupStageInputDescriptor(MTLStageInputOutputDescriptor *desc); + + // SPIRV-Cross buffer size buffers + QMetalBuffer *bufferSizeBuffer = nullptr; }; struct QMetalComputePipelineData @@ -389,6 +430,9 @@ struct QMetalComputePipelineData id ps = nil; QMetalShader cs; MTLSize localSize; + + // SPIRV-Cross buffer size buffers + QMetalBuffer *bufferSizeBuffer = nullptr; }; struct QMetalSwapChainData @@ -396,10 +440,16 @@ struct QMetalSwapChainData CAMetalLayer *layer = nullptr; id curDrawable = nil; dispatch_semaphore_t sem[QMTL_FRAMES_IN_FLIGHT]; + double lastGpuTime[QMTL_FRAMES_IN_FLIGHT]; MTLRenderPassDescriptor *rp = nullptr; id msaaTex[QMTL_FRAMES_IN_FLIGHT]; QRhiTexture::Format rhiColorFormat; MTLPixelFormat colorFormat; +#ifdef Q_OS_MACOS + bool liveResizeObserverSet = false; + QMacNotificationObserver liveResizeStartObserver; + QMacNotificationObserver liveResizeEndObserver; +#endif }; QRhiMetal::QRhiMetal(QRhiMetalInitParams *params, QRhiMetalNativeHandles *importDevice) @@ -708,7 +758,7 @@ bool QRhiMetal::isFeatureSupported(QRhi::Feature feature) const case QRhi::DebugMarkers: return true; case QRhi::Timestamps: - return false; + return true; case QRhi::Instancing: return true; case QRhi::CustomInstanceStepRate: @@ -780,6 +830,12 @@ bool QRhiMetal::isFeatureSupported(QRhi::Feature feature) const return true; case QRhi::OneDimensionalTextureMipmaps: return false; + case QRhi::HalfAttributes: + return true; + case QRhi::RenderToOneDimensionalTexture: + return false; + case QRhi::ThreeDimensionalTextureMipmaps: + return true; default: Q_UNREACHABLE(); return false; @@ -1195,7 +1251,7 @@ void QRhiMetal::enqueueShaderResourceBindings(QMetalShaderResourceBindings *srbD QMetalShaderResourceBindingsData bindingData; for (const QRhiShaderResourceBinding &binding : std::as_const(srbD->sortedBindings)) { - const QRhiShaderResourceBinding::Data *b = binding.data(); + const QRhiShaderResourceBinding::Data *b = shaderResourceBindingData(binding); switch (b->type) { case QRhiShaderResourceBinding::UniformBuffer: { @@ -1450,9 +1506,15 @@ void QRhiMetal::setShaderResources(QRhiCommandBuffer *cb, QRhiShaderResourceBind bool hasDynamicOffsetInSrb = false; bool resNeedsRebind = false; + // SPIRV-Cross buffer size buffers + // Need to determine storage buffer sizes here as this is the last opportunity for storage + // buffer bindings (offset, size) to be specified before draw / dispatch call + const bool needsBufferSizeBuffer = (compPsD && compPsD->d->bufferSizeBuffer) || (gfxPsD && gfxPsD->d->bufferSizeBuffer); + QMap> storageBufferSizes; + // do buffer writes, figure out if we need to rebind, and mark as in-use for (int i = 0, ie = srbD->sortedBindings.count(); i != ie; ++i) { - const QRhiShaderResourceBinding::Data *b = srbD->sortedBindings.at(i).data(); + const QRhiShaderResourceBinding::Data *b = shaderResourceBindingData(srbD->sortedBindings.at(i)); QMetalShaderResourceBindings::BoundResourceData &bd(srbD->boundResourceData[i]); switch (b->type) { case QRhiShaderResourceBinding::UniformBuffer: @@ -1526,6 +1588,17 @@ void QRhiMetal::setShaderResources(QRhiCommandBuffer *cb, QRhiShaderResourceBind { QMetalBuffer *bufD = QRHI_RES(QMetalBuffer, b->u.sbuf.buf); Q_ASSERT(bufD->m_usage.testFlag(QRhiBuffer::StorageBuffer)); + + if (needsBufferSizeBuffer) { + for (int i = 0; i < 6; ++i) { + const QRhiShaderResourceBinding::StageFlag stage = + QRhiShaderResourceBinding::StageFlag(1 << i); + if (b->stage.testFlag(stage)) { + storageBufferSizes[stage][b->binding] = b->u.sbuf.maybeSize ? b->u.sbuf.maybeSize : bufD->size(); + } + } + } + executeBufferHostWritesForCurrentFrame(bufD); if (bufD->generation != bd.sbuf.generation || bufD->m_id != bd.sbuf.id) { resNeedsRebind = true; @@ -1541,6 +1614,111 @@ void QRhiMetal::setShaderResources(QRhiCommandBuffer *cb, QRhiShaderResourceBind } } + if (needsBufferSizeBuffer) { + QMetalBuffer *bufD = nullptr; + QVarLengthArray, 4> shaders; + + if (compPsD) { + bufD = compPsD->d->bufferSizeBuffer; + Q_ASSERT(compPsD->d->cs.nativeShaderInfo.extraBufferBindings.contains(QShaderPrivate::MslBufferSizeBufferBinding)); + shaders.append(qMakePair(&compPsD->d->cs, QRhiShaderResourceBinding::StageFlag::ComputeStage)); + } else { + bufD = gfxPsD->d->bufferSizeBuffer; + if (gfxPsD->d->tess.enabled) { + + // Assumptions + // * We only use one of the compute vertex shader variants in a pipeline at any one time + // * The vertex shader variants all have the same storage block bindings + // * The vertex shader variants all have the same native resource binding map + // * The vertex shader variants all have the same MslBufferSizeBufferBinding requirement + // * The vertex shader variants all have the same MslBufferSizeBufferBinding binding + // => We only need to use one vertex shader variant to generate the identical shader + // resource bindings + Q_ASSERT(gfxPsD->d->tess.compVs[0].desc.storageBlocks() == gfxPsD->d->tess.compVs[1].desc.storageBlocks()); + Q_ASSERT(gfxPsD->d->tess.compVs[0].desc.storageBlocks() == gfxPsD->d->tess.compVs[2].desc.storageBlocks()); + Q_ASSERT(gfxPsD->d->tess.compVs[0].nativeResourceBindingMap == gfxPsD->d->tess.compVs[1].nativeResourceBindingMap); + Q_ASSERT(gfxPsD->d->tess.compVs[0].nativeResourceBindingMap == gfxPsD->d->tess.compVs[2].nativeResourceBindingMap); + Q_ASSERT(gfxPsD->d->tess.compVs[0].nativeShaderInfo.extraBufferBindings.contains(QShaderPrivate::MslBufferSizeBufferBinding) + == gfxPsD->d->tess.compVs[1].nativeShaderInfo.extraBufferBindings.contains(QShaderPrivate::MslBufferSizeBufferBinding)); + Q_ASSERT(gfxPsD->d->tess.compVs[0].nativeShaderInfo.extraBufferBindings.contains(QShaderPrivate::MslBufferSizeBufferBinding) + == gfxPsD->d->tess.compVs[2].nativeShaderInfo.extraBufferBindings.contains(QShaderPrivate::MslBufferSizeBufferBinding)); + Q_ASSERT(gfxPsD->d->tess.compVs[0].nativeShaderInfo.extraBufferBindings[QShaderPrivate::MslBufferSizeBufferBinding] + == gfxPsD->d->tess.compVs[1].nativeShaderInfo.extraBufferBindings[QShaderPrivate::MslBufferSizeBufferBinding]); + Q_ASSERT(gfxPsD->d->tess.compVs[0].nativeShaderInfo.extraBufferBindings[QShaderPrivate::MslBufferSizeBufferBinding] + == gfxPsD->d->tess.compVs[2].nativeShaderInfo.extraBufferBindings[QShaderPrivate::MslBufferSizeBufferBinding]); + + if (gfxPsD->d->tess.compVs[0].nativeShaderInfo.extraBufferBindings.contains(QShaderPrivate::MslBufferSizeBufferBinding)) + shaders.append(qMakePair(&gfxPsD->d->tess.compVs[0], QRhiShaderResourceBinding::StageFlag::VertexStage)); + + if (gfxPsD->d->tess.compTesc.nativeShaderInfo.extraBufferBindings.contains(QShaderPrivate::MslBufferSizeBufferBinding)) + shaders.append(qMakePair(&gfxPsD->d->tess.compTesc, QRhiShaderResourceBinding::StageFlag::TessellationControlStage)); + + if (gfxPsD->d->tess.vertTese.nativeShaderInfo.extraBufferBindings.contains(QShaderPrivate::MslBufferSizeBufferBinding)) + shaders.append(qMakePair(&gfxPsD->d->tess.vertTese, QRhiShaderResourceBinding::StageFlag::TessellationEvaluationStage)); + + } else { + if (gfxPsD->d->vs.nativeShaderInfo.extraBufferBindings.contains(QShaderPrivate::MslBufferSizeBufferBinding)) + shaders.append(qMakePair(&gfxPsD->d->vs, QRhiShaderResourceBinding::StageFlag::VertexStage)); + } + if (gfxPsD->d->fs.nativeShaderInfo.extraBufferBindings.contains(QShaderPrivate::MslBufferSizeBufferBinding)) + shaders.append(qMakePair(&gfxPsD->d->fs, QRhiShaderResourceBinding::StageFlag::FragmentStage)); + } + + quint32 offset = 0; + for (const QPair &shader : shaders) { + + const int binding = shader.first->nativeShaderInfo.extraBufferBindings[QShaderPrivate::MslBufferSizeBufferBinding]; + + // if we don't have a srb entry for the buffer size buffer + if (!(storageBufferSizes.contains(shader.second) && storageBufferSizes[shader.second].contains(binding))) { + + int maxNativeBinding = 0; + for (const QShaderDescription::StorageBlock &block : shader.first->desc.storageBlocks()) + maxNativeBinding = qMax(maxNativeBinding, shader.first->nativeResourceBindingMap[block.binding].first); + + const int size = (maxNativeBinding + 1) * sizeof(int); + + Q_ASSERT(offset + size <= bufD->size()); + srbD->sortedBindings.append(QRhiShaderResourceBinding::bufferLoad(binding, shader.second, bufD, offset, size)); + + QMetalShaderResourceBindings::BoundResourceData bd; + bd.sbuf.id = bufD->m_id; + bd.sbuf.generation = bufD->generation; + srbD->boundResourceData.append(bd); + } + + // create the buffer size buffer data + QVarLengthArray bufferSizeBufferData; + Q_ASSERT(storageBufferSizes.contains(shader.second)); + const QMap &sizes(storageBufferSizes[shader.second]); + for (const QShaderDescription::StorageBlock &block : shader.first->desc.storageBlocks()) { + const int index = shader.first->nativeResourceBindingMap[block.binding].first; + + // if the native binding is -1, the buffer is present but not accessed in the shader + if (index < 0) + continue; + + if (bufferSizeBufferData.size() <= index) + bufferSizeBufferData.resize(index + 1); + + Q_ASSERT(sizes.contains(block.binding)); + bufferSizeBufferData[index] = sizes[block.binding]; + } + + QRhiBufferData data; + const quint32 size = bufferSizeBufferData.size() * sizeof(int); + data.assign(reinterpret_cast(bufferSizeBufferData.constData()), size); + Q_ASSERT(offset + size <= bufD->size()); + bufD->d->pendingUpdates[bufD->d->slotted ? currentFrameSlot : 0].append({ offset, data }); + + // buffer offsets must be 32byte aligned + offset += ((size + 31) / 32) * 32; + } + + executeBufferHostWritesForCurrentFrame(bufD); + bufD->lastActiveFrameSlot = currentFrameSlot; + } + // make sure the resources for the correct slot get bound const int resSlot = hasSlottedResourceInSrb ? currentFrameSlot : 0; if (hasSlottedResourceInSrb && cbD->currentResSlot != resSlot) @@ -2090,27 +2268,35 @@ void QRhiMetal::endExternal(QRhiCommandBuffer *cb) cbD->resetPerPassCachedState(); } +double QRhiMetal::lastCompletedGpuTime(QRhiCommandBuffer *cb) +{ + QMetalCommandBuffer *cbD = QRHI_RES(QMetalCommandBuffer, cb); + return cbD->d->lastGpuTime; +} + QRhi::FrameOpResult QRhiMetal::beginFrame(QRhiSwapChain *swapChain, QRhi::BeginFrameFlags flags) { Q_UNUSED(flags); QMetalSwapChain *swapChainD = QRHI_RES(QMetalSwapChain, swapChain); - - // This is a bit messed up since for this swapchain we want to wait for the - // commands+present to complete, while for others just for the commands - // (for this same frame slot) but not sure how to do that in a sane way so - // wait for full cb completion for now. - for (QMetalSwapChain *sc : std::as_const(swapchains)) { - dispatch_semaphore_t sem = sc->d->sem[swapChainD->currentFrameSlot]; - dispatch_semaphore_wait(sem, DISPATCH_TIME_FOREVER); - if (sc != swapChainD) - dispatch_semaphore_signal(sem); - } - currentSwapChain = swapChainD; currentFrameSlot = swapChainD->currentFrameSlot; - if (swapChainD->ds) - swapChainD->ds->lastActiveFrameSlot = currentFrameSlot; + + // If we are too far ahead, block. This is also what ensures that any + // resource used in the previous frame for this slot is now not in use + // anymore by the GPU. + dispatch_semaphore_wait(swapChainD->d->sem[currentFrameSlot], DISPATCH_TIME_FOREVER); + + // Do this also for any other swapchain's commands with the same frame slot + // While this reduces concurrency, it keeps resource usage safe: swapchain + // A starting its frame 0, followed by swapchain B starting its own frame 0 + // will make B wait for A's frame 0 commands, so if a resource is written + // in B's frame or when B checks for pending resource releases, that won't + // mess up A's in-flight commands (as they are not in flight anymore). + for (QMetalSwapChain *sc : std::as_const(swapchains)) { + if (sc != swapChainD) + sc->waitUntilCompleted(currentFrameSlot); // wait+signal + } [d->captureScope beginScope]; @@ -2132,8 +2318,12 @@ QRhi::FrameOpResult QRhiMetal::beginFrame(QRhiSwapChain *swapChain, QRhi::BeginF swapChainD->rtWrapper.d->fb.hasStencil = swapChainD->ds ? true : false; swapChainD->rtWrapper.d->fb.depthNeedsStore = false; + if (swapChainD->ds) + swapChainD->ds->lastActiveFrameSlot = currentFrameSlot; + executeDeferredReleases(); - swapChainD->cbWrapper.resetState(); + swapChainD->cbWrapper.resetState(swapChainD->d->lastGpuTime[currentFrameSlot]); + swapChainD->d->lastGpuTime[currentFrameSlot] = 0; finishActiveReadbacks(); return QRhi::FrameOpSuccess; @@ -2144,26 +2334,30 @@ QRhi::FrameOpResult QRhiMetal::endFrame(QRhiSwapChain *swapChain, QRhi::EndFrame QMetalSwapChain *swapChainD = QRHI_RES(QMetalSwapChain, swapChain); Q_ASSERT(currentSwapChain == swapChainD); + __block int thisFrameSlot = currentFrameSlot; + [swapChainD->cbWrapper.d->cb addCompletedHandler: ^(id cb) { + swapChainD->d->lastGpuTime[thisFrameSlot] += cb.GPUEndTime - cb.GPUStartTime; + dispatch_semaphore_signal(swapChainD->d->sem[thisFrameSlot]); + }]; + const bool needsPresent = !flags.testFlag(QRhi::SkipPresent); - if (needsPresent) { - // beginFrame-endFrame without a render pass inbetween means there is no - // drawable, handle this gracefully because presentDrawable does not like - // null arguments. - if (id drawable = swapChainD->d->curDrawable) { - // QTBUG-103415: while the docs suggest the following two approaches are - // equivalent, there is a difference in case a frame is recorded earlier than - // (i.e. not in response to) the next CVDisplayLink callback. Therefore, stick - // with presentDrawable, which gives results identical to OpenGL, and all other - // platforms, i.e. throttles to vsync as expected, meaning constant 15-17 ms with - // a 60 Hz screen, no jumps with smaller intervals, regardless of when the frame - // is submitted by the app) -#if 1 + const bool presentsWithTransaction = swapChainD->d->layer.presentsWithTransaction; + if (!presentsWithTransaction && needsPresent) { + // beginFrame-endFrame without a render pass inbetween means there is no drawable. + if (id drawable = swapChainD->d->curDrawable) [swapChainD->cbWrapper.d->cb presentDrawable: drawable]; -#else - [swapChainD->cbWrapper.d->cb addScheduledHandler:^(id) { - [drawable present]; - }]; -#endif + } + + [swapChainD->cbWrapper.d->cb commit]; + + if (presentsWithTransaction && needsPresent) { + // beginFrame-endFrame without a render pass inbetween means there is no drawable. + if (id drawable = swapChainD->d->curDrawable) { + // The layer has presentsWithTransaction set to true to avoid flicker on resizing, + // so here it is important to follow what the Metal docs say when it comes to the + // issuing the present. + [swapChainD->cbWrapper.d->cb waitUntilScheduled]; + [drawable present]; } } @@ -2171,13 +2365,6 @@ QRhi::FrameOpResult QRhiMetal::endFrame(QRhiSwapChain *swapChain, QRhi::EndFrame [swapChainD->d->curDrawable release]; swapChainD->d->curDrawable = nil; - __block int thisFrameSlot = currentFrameSlot; - [swapChainD->cbWrapper.d->cb addCompletedHandler: ^(id) { - dispatch_semaphore_signal(swapChainD->d->sem[thisFrameSlot]); - }]; - - [swapChainD->cbWrapper.d->cb commit]; - [d->captureScope endScope]; if (needsPresent) @@ -2193,21 +2380,17 @@ QRhi::FrameOpResult QRhiMetal::beginOffscreenFrame(QRhiCommandBuffer **cb, QRhi: Q_UNUSED(flags); currentFrameSlot = (currentFrameSlot + 1) % QMTL_FRAMES_IN_FLIGHT; - for (QMetalSwapChain *sc : std::as_const(swapchains)) { - // wait+signal is the general pattern to ensure the commands for a - // given frame slot have completed (if sem is 1, we go 0 then 1; if - // sem is 0 we go -1, block, completion increments to 0, then us to 1) - dispatch_semaphore_t sem = sc->d->sem[currentFrameSlot]; - dispatch_semaphore_wait(sem, DISPATCH_TIME_FOREVER); - dispatch_semaphore_signal(sem); - } + + for (QMetalSwapChain *sc : std::as_const(swapchains)) + sc->waitUntilCompleted(currentFrameSlot); d->ofr.active = true; *cb = &d->ofr.cbWrapper; d->ofr.cbWrapper.d->cb = [d->cmdQueue commandBufferWithUnretainedReferences]; executeDeferredReleases(); - d->ofr.cbWrapper.resetState(); + d->ofr.cbWrapper.resetState(d->ofr.lastGpuTime); + d->ofr.lastGpuTime = 0; finishActiveReadbacks(); return QRhi::FrameOpSuccess; @@ -2219,10 +2402,13 @@ QRhi::FrameOpResult QRhiMetal::endOffscreenFrame(QRhi::EndFrameFlags flags) Q_ASSERT(d->ofr.active); d->ofr.active = false; - [d->ofr.cbWrapper.d->cb commit]; + id cb = d->ofr.cbWrapper.d->cb; + [cb commit]; // offscreen frames wait for completion, unlike swapchain ones - [d->ofr.cbWrapper.d->cb waitUntilCompleted]; + [cb waitUntilCompleted]; + + d->ofr.lastGpuTime += cb.GPUEndTime - cb.GPUStartTime; finishActiveReadbacks(true); @@ -2253,9 +2439,7 @@ QRhi::FrameOpResult QRhiMetal::finish() // beginFrame decremented sem already and going to be signaled by endFrame continue; } - dispatch_semaphore_t sem = sc->d->sem[i]; - dispatch_semaphore_wait(sem, DISPATCH_TIME_FOREVER); - dispatch_semaphore_signal(sem); + sc->waitUntilCompleted(i); } } @@ -2265,10 +2449,13 @@ QRhi::FrameOpResult QRhiMetal::finish() } if (inFrame) { - if (d->ofr.active) + if (d->ofr.active) { + d->ofr.lastGpuTime += cb.GPUEndTime - cb.GPUStartTime; d->ofr.cbWrapper.d->cb = [d->cmdQueue commandBufferWithUnretainedReferences]; - else + } else { + swapChainD->d->lastGpuTime[currentFrameSlot] += cb.GPUEndTime - cb.GPUStartTime; swapChainD->cbWrapper.d->cb = [d->cmdQueue commandBufferWithUnretainedReferences]; + } } executeDeferredReleases(true); @@ -2468,13 +2655,23 @@ void QRhiMetal::enqueueResourceUpdates(QRhiCommandBuffer *cb, QRhiResourceUpdate QMetalBuffer *bufD = QRHI_RES(QMetalBuffer, u.buf); executeBufferHostWritesForCurrentFrame(bufD); const int idx = bufD->d->slotted ? currentFrameSlot : 0; - char *p = reinterpret_cast([bufD->d->buf[idx] contents]); - if (p) { - u.result->data.resize(u.readSize); - memcpy(u.result->data.data(), p + u.offset, size_t(u.readSize)); + if (bufD->m_type == QRhiBuffer::Dynamic) { + char *p = reinterpret_cast([bufD->d->buf[idx] contents]); + if (p) { + u.result->data.resize(u.readSize); + memcpy(u.result->data.data(), p + u.offset, size_t(u.readSize)); + } + if (u.result->completed) + u.result->completed(); + } else { + QRhiMetalData::BufferReadback readback; + readback.activeFrameSlot = idx; + readback.buf = bufD->d->buf[idx]; + readback.offset = u.offset; + readback.readSize = u.readSize; + readback.result = u.result; + d->activeBufferReadbacks.append(readback); } - if (u.result->completed) - u.result->completed(); } } @@ -2905,7 +3102,23 @@ void QRhiMetal::finishActiveReadbacks(bool forced) if (readback.result->completed) completedCallbacks.append(readback.result->completed); - d->activeTextureReadbacks.removeLast(); + d->activeTextureReadbacks.remove(i); + } + } + + for (int i = d->activeBufferReadbacks.count() - 1; i >= 0; --i) { + const QRhiMetalData::BufferReadback &readback(d->activeBufferReadbacks[i]); + if (forced || currentFrameSlot == readback.activeFrameSlot + || readback.activeFrameSlot < 0) { + readback.result->data.resize(readback.readSize); + char *p = reinterpret_cast([readback.buf contents]); + Q_ASSERT(p); + memcpy(readback.result->data.data(), p + readback.offset, size_t(readback.readSize)); + + if (readback.result->completed) + completedCallbacks.append(readback.result->completed); + + d->activeBufferReadbacks.remove(i); } } @@ -3499,12 +3712,10 @@ bool QMetalTexture::prepareCreate(QSize *adjustedSize) qWarning("Texture cannot be both 1D and cube"); return false; } - m_depth = qMax(1, m_depth); if (m_depth > 1 && !is3D) { qWarning("Texture cannot have a depth of %d when it is not 3D", m_depth); return false; } - m_arraySize = qMax(0, m_arraySize); if (m_arraySize > 0 && !isArray) { qWarning("Texture cannot have an array size of %d when it is not an array", m_arraySize); return false; @@ -3540,11 +3751,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 @@ -3554,12 +3765,12 @@ bool QMetalTexture::create() desc.pixelFormat = d->format; desc.width = NSUInteger(size.width()); desc.height = NSUInteger(size.height()); - desc.depth = is3D ? m_depth : 1; + desc.depth = is3D ? qMax(1, m_depth) : 1; desc.mipmapLevelCount = NSUInteger(mipLevelCount); if (samples > 1) desc.sampleCount = NSUInteger(samples); if (isArray) - desc.arrayLength = NSUInteger(m_arraySize); + desc.arrayLength = NSUInteger(qMax(0, m_arraySize)); desc.resourceOptions = MTLResourceStorageModePrivate; desc.storageMode = MTLStorageModePrivate; desc.usage = MTLTextureUsageShaderRead; @@ -3618,7 +3829,8 @@ id QMetalTextureData::viewForLevel(int level) const bool isCube = q->m_flags.testFlag(QRhiTexture::CubeMap); const bool isArray = q->m_flags.testFlag(QRhiTexture::TextureArray); id view = [tex newTextureViewWithPixelFormat: format textureType: type - levels: NSMakeRange(NSUInteger(level), 1) slices: NSMakeRange(0, isCube ? 6 : (isArray ? q->m_arraySize : 1))]; + levels: NSMakeRange(NSUInteger(level), 1) + slices: NSMakeRange(0, isCube ? 6 : (isArray ? qMax(0, q->m_arraySize) : 1))]; perLevelViews[level] = view; return view; @@ -3881,7 +4093,7 @@ void QMetalTextureRenderTarget::destroy() QRhiRenderPassDescriptor *QMetalTextureRenderTarget::newCompatibleRenderPassDescriptor() { - const int colorAttachmentCount = m_desc.cendColorAttachments() - m_desc.cbeginColorAttachments(); + const int colorAttachmentCount = int(m_desc.colorAttachmentCount()); QMetalRenderPassDescriptor *rpD = new QMetalRenderPassDescriptor(m_rhi); rpD->colorAttachmentCount = colorAttachmentCount; rpD->hasDepthStencil = m_desc.depthStencilBuffer() || m_desc.depthTexture(); @@ -3908,8 +4120,7 @@ QRhiRenderPassDescriptor *QMetalTextureRenderTarget::newCompatibleRenderPassDesc bool QMetalTextureRenderTarget::create() { QRHI_RES_RHI(QRhiMetal); - const bool hasColorAttachments = m_desc.cbeginColorAttachments() != m_desc.cendColorAttachments(); - Q_ASSERT(hasColorAttachments || m_desc.depthTexture()); + Q_ASSERT(m_desc.colorAttachmentCount() > 0 || m_desc.depthTexture()); Q_ASSERT(!m_desc.depthStencilBuffer() || !m_desc.depthTexture()); const bool hasDepthStencil = m_desc.depthStencilBuffer() || m_desc.depthTexture(); @@ -4030,13 +4241,9 @@ bool QMetalShaderResourceBindings::create() rhiD->updateLayoutDesc(this); std::copy(m_bindings.cbegin(), m_bindings.cend(), std::back_inserter(sortedBindings)); - std::sort(sortedBindings.begin(), sortedBindings.end(), - [](const QRhiShaderResourceBinding &a, const QRhiShaderResourceBinding &b) - { - return a.data()->binding < b.data()->binding; - }); + std::sort(sortedBindings.begin(), sortedBindings.end(), QRhiImplementation::sortedBindingLessThan); if (!sortedBindings.isEmpty()) - maxBinding = sortedBindings.last().data()->binding; + maxBinding = QRhiImplementation::shaderResourceBindingData(sortedBindings.last())->binding; else maxBinding = -1; @@ -4054,13 +4261,8 @@ void QMetalShaderResourceBindings::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(), - [](const QRhiShaderResourceBinding &a, const QRhiShaderResourceBinding &b) - { - return a.data()->binding < b.data()->binding; - }); - } + if (!flags.testFlag(BindingsAreSorted)) + std::sort(sortedBindings.begin(), sortedBindings.end(), QRhiImplementation::sortedBindingLessThan); for (BoundResourceData &bd : boundResourceData) memset(&bd, 0, sizeof(BoundResourceData)); @@ -4099,6 +4301,9 @@ void QMetalGraphicsPipeline::destroy() qDeleteAll(d->tess.hostVisibleWorkBuffers); d->tess.hostVisibleWorkBuffers.clear(); + delete d->bufferSizeBuffer; + d->bufferSizeBuffer = nullptr; + if (!d->ps && !d->ds && !d->tess.vertexComputeState[0] && !d->tess.vertexComputeState[1] && !d->tess.vertexComputeState[2] && !d->tess.tessControlComputeState) @@ -4158,6 +4363,14 @@ static inline MTLVertexFormat toMetalAttributeFormat(QRhiVertexInputAttribute::F return MTLVertexFormatInt2; case QRhiVertexInputAttribute::SInt: return MTLVertexFormatInt; + case QRhiVertexInputAttribute::Half4: + return MTLVertexFormatHalf4; + case QRhiVertexInputAttribute::Half3: + return MTLVertexFormatHalf3; + case QRhiVertexInputAttribute::Half2: + return MTLVertexFormatHalf2; + case QRhiVertexInputAttribute::Half: + return MTLVertexFormatHalf; default: Q_UNREACHABLE(); return MTLVertexFormatFloat4; @@ -4369,15 +4582,39 @@ static inline MTLTessellationPartitionMode toMetalTessellationPartitionMode(QSha } } +static inline MTLLanguageVersion toMetalLanguageVersion(const QShaderVersion &version) +{ + int v = version.version(); + return MTLLanguageVersion(((v / 10) << 16) + (v % 10)); +} + id QRhiMetalData::createMetalLib(const QShader &shader, QShader::Variant shaderVariant, QString *error, QByteArray *entryPoint, QShaderKey *activeKey) { - QShaderKey key = { QShader::MetalLibShader, 20, shaderVariant }; - QShaderCode mtllib = shader.shader(key); - if (mtllib.shader().isEmpty()) { - key.setSourceVersion(12); - mtllib = shader.shader(key); + QVarLengthArray versions; + if (@available(macOS 13, iOS 16, *)) + versions << 30; + if (@available(macOS 12, iOS 15, *)) + versions << 24; + if (@available(macOS 11, iOS 14, *)) + versions << 23; + if (@available(macOS 10.15, iOS 13, *)) + versions << 22; + if (@available(macOS 10.14, iOS 12, *)) + versions << 21; + versions << 20 << 12; + + const QList shaders = shader.availableShaders(); + + QShaderKey key; + + for (const int &version : versions) { + key = { QShader::Source::MetalLibShader, version, shaderVariant }; + if (shaders.contains(key)) + break; } + + QShaderCode mtllib = shader.shader(key); if (!mtllib.shader().isEmpty()) { dispatch_data_t data = dispatch_data_create(mtllib.shader().constData(), size_t(mtllib.shader().size()), @@ -4396,12 +4633,13 @@ id QRhiMetalData::createMetalLib(const QShader &shader, QShader::Var } } - key = { QShader::MslShader, 20, shaderVariant }; - QShaderCode mslSource = shader.shader(key); - if (mslSource.shader().isEmpty()) { - key.setSourceVersion(12); - mslSource = shader.shader(key); + for (const int &version : versions) { + key = { QShader::Source::MslShader, version, shaderVariant }; + if (shaders.contains(key)) + break; } + + QShaderCode mslSource = shader.shader(key); if (mslSource.shader().isEmpty()) { qWarning() << "No MSL 2.0 or 1.2 code found in baked shader" << shader; return nil; @@ -4409,7 +4647,7 @@ id QRhiMetalData::createMetalLib(const QShader &shader, QShader::Var NSString *src = [NSString stringWithUTF8String: mslSource.shader().constData()]; MTLCompileOptions *opts = [[MTLCompileOptions alloc] init]; - opts.languageVersion = key.sourceVersion() == 20 ? MTLLanguageVersion2_0 : MTLLanguageVersion1_2; + opts.languageVersion = toMetalLanguageVersion(key.sourceVersion()); NSError *err = nil; id lib = [dev newLibraryWithSource: src options: opts error: &err]; [opts release]; @@ -4660,6 +4898,8 @@ bool QMetalGraphicsPipeline::createVertexFragmentPipeline() d->vs.lib = lib; d->vs.func = func; d->vs.nativeResourceBindingMap = shader.nativeResourceBindingMap(activeKey); + d->vs.desc = shader.description(); + d->vs.nativeShaderInfo = shader.nativeShaderInfo(activeKey); rhiD->d->shaderCache.insert(shaderStage, d->vs); [d->vs.lib retain]; [d->vs.func retain]; @@ -4669,6 +4909,8 @@ bool QMetalGraphicsPipeline::createVertexFragmentPipeline() d->fs.lib = lib; d->fs.func = func; d->fs.nativeResourceBindingMap = shader.nativeResourceBindingMap(activeKey); + d->fs.desc = shader.description(); + d->fs.nativeShaderInfo = shader.nativeShaderInfo(activeKey); rhiD->d->shaderCache.insert(shaderStage, d->fs); [d->fs.lib retain]; [d->fs.func retain]; @@ -4808,19 +5050,176 @@ id QMetalGraphicsPipelineData::Tessellation::tescCompPi return ps; } -static inline bool hasBuiltin(const QVector &builtinList, QShaderDescription::BuiltinType builtin) +static inline bool indexTaken(quint32 index, quint64 indices) { - return std::find_if(builtinList.cbegin(), builtinList.cend(), - [builtin](const QShaderDescription::BuiltinVariable &b) { return b.type == builtin; }) != builtinList.cend(); + return (indices >> index) & 0x1; +} + +static inline void takeIndex(quint32 index, quint64 &indices) +{ + indices |= 1 << index; +} + +static inline int nextAttributeIndex(quint64 indices) +{ + // Maximum number of vertex attributes per vertex descriptor. There does + // not appear to be a way to query this from the implementation. + // https://developer.apple.com/metal/Metal-Feature-Set-Tables.pdf indicates + // that all GPU families have a value of 31. + static const int maxVertexAttributes = 31; + + for (int index = 0; index < maxVertexAttributes; ++index) { + if (!indexTaken(index, indices)) + return index; + } + + Q_UNREACHABLE_RETURN(-1); +} + +static inline int aligned(quint32 offset, quint32 alignment) +{ + return ((offset + alignment - 1) / alignment) * alignment; +} + +template +static void addUnusedVertexAttribute(const T &variable, QRhiMetal *rhiD, quint32 &offset, quint32 &vertexAlignment) +{ + + int elements = 1; + for (const int dim : variable.arrayDims) + elements *= dim; + + if (variable.type == QShaderDescription::VariableType::Struct) { + for (int element = 0; element < elements; ++element) { + for (const auto &member : variable.structMembers) { + addUnusedVertexAttribute(member, rhiD, offset, vertexAlignment); + } + } + } else { + const QRhiVertexInputAttribute::Format format = rhiD->shaderDescVariableFormatToVertexInputFormat(variable.type); + const quint32 size = rhiD->byteSizePerVertexForVertexInputFormat(format); + + // MSL specification 3.0 says alignment = size for non packed scalars and vectors + const quint32 alignment = size; + vertexAlignment = std::max(vertexAlignment, alignment); + + for (int element = 0; element < elements; ++element) { + // adjust alignment + offset = aligned(offset, alignment); + offset += size; + } + } +} + +template +static void addVertexAttribute(const T &variable, int binding, QRhiMetal *rhiD, int &index, quint32 &offset, MTLVertexAttributeDescriptorArray *attributes, quint64 &indices, quint32 &vertexAlignment) +{ + + int elements = 1; + for (const int dim : variable.arrayDims) + elements *= dim; + + if (variable.type == QShaderDescription::VariableType::Struct) { + for (int element = 0; element < elements; ++element) { + for (const auto &member : variable.structMembers) { + addVertexAttribute(member, binding, rhiD, index, offset, attributes, indices, vertexAlignment); + } + } + } else { + const QRhiVertexInputAttribute::Format format = rhiD->shaderDescVariableFormatToVertexInputFormat(variable.type); + const quint32 size = rhiD->byteSizePerVertexForVertexInputFormat(format); + + // MSL specification 3.0 says alignment = size for non packed scalars and vectors + const quint32 alignment = size; + vertexAlignment = std::max(vertexAlignment, alignment); + + for (int element = 0; element < elements; ++element) { + Q_ASSERT(!indexTaken(index, indices)); + + // adjust alignment + offset = aligned(offset, alignment); + + attributes[index].bufferIndex = binding; + attributes[index].format = toMetalAttributeFormat(format); + attributes[index].offset = offset; + + takeIndex(index, indices); + index++; + if (indexTaken(index, indices)) + index = nextAttributeIndex(indices); + + offset += size; + } + } +} + +static inline bool matches(const QList &a, const QList &b) +{ + if (a.size() == b.size()) { + bool match = true; + for (int i = 0; i < a.size() && match; ++i) { + match &= a[i].type == b[i].type + && a[i].arrayDims == b[i].arrayDims + && matches(a[i].structMembers, b[i].structMembers); + } + return match; + } + + return false; } static inline bool matches(const QShaderDescription::InOutVariable &a, const QShaderDescription::InOutVariable &b) { return a.location == b.location && a.type == b.type - && a.perPatch == b.perPatch; + && a.perPatch == b.perPatch + && matches(a.structMembers, b.structMembers); } +// +// Create the tessellation evaluation render pipeline state +// +// The tesc runs as a compute shader in a compute pipeline and writes per patch and per patch +// control point data into separate storage buffers. The tese runs as a vertex shader in a render +// pipeline. Our task is to generate a render pipeline descriptor for the tese that pulls vertices +// from these buffers. +// +// As the buffers we are pulling vertices from are written by a compute pipeline, they follow the +// MSL alignment conventions which we must take into account when generating our +// MTLVertexDescriptor. We must include the user defined tese input attributes, and any builtins +// that were used. +// +// SPIRV-Cross generates the MSL tese shader code with input attribute indices that reflect the +// specified GLSL locations. Interface blocks are flattened with each member having an incremented +// attribute index. SPIRV-Cross reports an error on compilation if there are clashes in the index +// address space. +// +// After the user specified attributes are processed, SPIRV-Cross places the in-use builtins at the +// next available (lowest value) attribute index. Tese builtins are processed in the following +// order: +// +// in gl_PerVertex +// { +// vec4 gl_Position; +// float gl_PointSize; +// float gl_ClipDistance[]; +// }; +// +// patch in float gl_TessLevelOuter[4]; +// patch in float gl_TessLevelInner[2]; +// +// Enumerations in QShaderDescription::BuiltinType are defined in this order. +// +// For quads, SPIRV-Cross places MTLQuadTessellationFactorsHalf per patch in the tessellation +// factor buffer. For triangles it uses MTLTriangleTessellationFactorsHalf. +// +// It should be noted that SPIRV-Cross handles the following builtin inputs internally, with no +// host side support required. +// +// in vec3 gl_TessCoord; +// in int gl_PatchVerticesIn; +// in int gl_PrimitiveID; +// id QMetalGraphicsPipelineData::Tessellation::teseFragRenderPipeline(QRhiMetal *rhiD, QMetalGraphicsPipeline *pipeline) { if (pipeline->d->ps) @@ -4829,154 +5228,191 @@ id QMetalGraphicsPipelineData::Tessellation::teseFragRen MTLRenderPipelineDescriptor *rpDesc = [[MTLRenderPipelineDescriptor alloc] init]; MTLVertexDescriptor *vertexDesc = [MTLVertexDescriptor vertexDescriptor]; - // Going to use the same buffer indices for the extra buffers as the tess.control compute shader did. + // tesc output buffers const QMap &ebb(compTesc.nativeShaderInfo.extraBufferBindings); const int tescOutputBufferBinding = ebb.value(QShaderPrivate::MslTessVertTescOutputBufferBinding, -1); const int tescPatchOutputBufferBinding = ebb.value(QShaderPrivate::MslTessTescPatchOutputBufferBinding, -1); const int tessFactorBufferBinding = ebb.value(QShaderPrivate::MslTessTescTessLevelBufferBinding, -1); - - QMap teseInVars; - for (const QShaderDescription::InOutVariable &teseInVar : vertTese.desc.inputVariables()) - teseInVars[teseInVar.location] = teseInVar; - quint32 offsetInTescOutput = 0; quint32 offsetInTescPatchOutput = 0; - int lastLocation = -1; + quint32 offsetInTessFactorBuffer = 0; + quint32 tescOutputAlignment = 0; + quint32 tescPatchOutputAlignment = 0; + quint32 tessFactorAlignment = 0; + QSet usedBuffers; - // these need to be sorted in location order so that lastLocation is calculated correctly - use QMap. + // tesc output variables in ascending location order QMap tescOutVars; - for (const QShaderDescription::InOutVariable &tescOutVar : compTesc.desc.outputVariables()) + for (const auto &tescOutVar : compTesc.desc.outputVariables()) tescOutVars[tescOutVar.location] = tescOutVar; - for (const QShaderDescription::InOutVariable &tescOutVar : tescOutVars) { - const int location = tescOutVar.location; - lastLocation = location; - const QRhiVertexInputAttribute::Format format = rhiD->shaderDescVariableFormatToVertexInputFormat(tescOutVar.type); - if (teseInVars.contains(location)) { - if (!matches(teseInVars[location], tescOutVar)) { - qWarning() << "mismatched tessellation control output -> tesssellation evaluation input at location" << location; - qWarning() << "tesc out:" << tescOutVar << "tese in:" << teseInVars[location]; + // tese input variables in ascending location order + QMap teseInVars; + for (const auto &teseInVar : vertTese.desc.inputVariables()) + teseInVars[teseInVar.location] = teseInVar; + + // bit mask tracking usage of vertex attribute indices + quint64 indices = 0; + + for (QShaderDescription::InOutVariable &tescOutVar : tescOutVars) { + + int index = tescOutVar.location; + int binding = -1; + quint32 *offset = nullptr; + quint32 *alignment = nullptr; + + if (tescOutVar.perPatch) { + binding = tescPatchOutputBufferBinding; + offset = &offsetInTescPatchOutput; + alignment = &tescPatchOutputAlignment; + } else { + tescOutVar.arrayDims.removeLast(); + binding = tescOutputBufferBinding; + offset = &offsetInTescOutput; + alignment = &tescOutputAlignment; + } + + if (teseInVars.contains(index)) { + + if (!matches(teseInVars[index], tescOutVar)) { + qWarning() << "mismatched tessellation control output -> tesssellation evaluation input at location" << index; + qWarning() << " tesc out:" << tescOutVar; + qWarning() << " tese in:" << teseInVars[index]; } - if (tescOutVar.perPatch) { - if (tescPatchOutputBufferBinding >= 0) { - vertexDesc.attributes[location].bufferIndex = tescPatchOutputBufferBinding; - vertexDesc.attributes[location].format = toMetalAttributeFormat(format); - vertexDesc.attributes[location].offset = offsetInTescPatchOutput; - } + + if (binding != -1) { + addVertexAttribute(tescOutVar, binding, rhiD, index, *offset, vertexDesc.attributes, indices, *alignment); + usedBuffers << binding; } else { - if (tescOutputBufferBinding >= 0) { - vertexDesc.attributes[location].bufferIndex = tescOutputBufferBinding; - vertexDesc.attributes[location].format = toMetalAttributeFormat(format); - vertexDesc.attributes[location].offset = offsetInTescOutput; - } + qWarning() << "baked tessellation control shader missing output buffer binding information"; + addUnusedVertexAttribute(tescOutVar, rhiD, *offset, *alignment); } + } else { qWarning() << "missing tessellation evaluation input for tessellation control output:" << tescOutVar; + addUnusedVertexAttribute(tescOutVar, rhiD, *offset, *alignment); } - if (tescOutVar.perPatch) - offsetInTescPatchOutput += rhiD->byteSizePerVertexForVertexInputFormat(format); - else - offsetInTescOutput += rhiD->byteSizePerVertexForVertexInputFormat(format); + + teseInVars.remove(tescOutVar.location); } - const QVector tescOutBuiltins = compTesc.desc.outputBuiltinVariables(); - const QVector teseInBuiltins = vertTese.desc.inputBuiltinVariables(); + for (const QShaderDescription::InOutVariable &teseInVar : teseInVars) + qWarning() << "missing tessellation control output for tessellation evaluation input:" << teseInVar; - // Take a tess.control shader with an output variable layout(location = 0) out vec3 outColor[]. - // Assume it also writes to glPosition, e.g. gl_out[gl_InvocationID].gl_Position = ... - // The tess.eval. shader translated to a Metal vertex function will then contain: - // - // struct main0_in { - // float3 inColor [[attribute(0)]]; - // float4 gl_Position [[attribute(1)]]; } - // - // The vertex description has to be set up accordingly. The color is - // simple because that will be in the input/output variable list with - // location 0. The position is a builtin however. So for now just - // assume that builtins such as that come after the other variables, - // with increasing location values. + // tesc output builtins in ascending location order + QMap tescOutBuiltins; + for (const auto &tescOutBuiltin : compTesc.desc.outputBuiltinVariables()) + tescOutBuiltins[tescOutBuiltin.type] = tescOutBuiltin; - if (hasBuiltin(tescOutBuiltins, QShaderDescription::PositionBuiltin) - && hasBuiltin(teseInBuiltins, QShaderDescription::PositionBuiltin) - && tescOutputBufferBinding >= 0) - { - const int location = ++lastLocation; - vertexDesc.attributes[location].bufferIndex = tescOutputBufferBinding; - vertexDesc.attributes[location].format = toMetalAttributeFormat(QRhiVertexInputAttribute::Float4); - vertexDesc.attributes[location].offset = offsetInTescOutput; - offsetInTescOutput += 4 * sizeof(float); - } + // tese input builtins in ascending location order + QMap teseInBuiltins; + for (const auto &teseInBuiltin : vertTese.desc.inputBuiltinVariables()) + teseInBuiltins[teseInBuiltin.type] = teseInBuiltin; - // Per-patch outputs from the tess.control stage. are mostly handled above. - // Consider: - // layout(location = 1) patch in vec3 stuff; - // layout(location = 2) patch in float more_stuff; - // - // This maps to: - // - // struct main0_patchIn { - // float3 stuff [[attribute(1)]]; - // float more_stuff [[attribute(2)]]; - // patch_control_point gl_in; }; - // - // These are already in place (location 1 and 2, referencing the per-patch - // output buffer of tesc) at this point. But now if the tess.eval.shader - // reads gl_TessLevelInner and gl_TessLevelOuter, which are also per-patch, - // that adds, if the mode is triangles: - // (assuming gl_Position got location 3, sorted based on the builtin type - // (Position < Outer < Inner)) - // - // float4 gl_TessLevel [[attribute(4)]]; - // - // or if the mode is quads: - // - // float4 gl_TessLevelOuter [[attribute(4)]]; - // float2 gl_TessLevelInner [[attribute(5)]]; - // - // Like gl_Position, these built-ins needs to be handled specially. - // Note that the data is in a dedicated buffer, not in the patch buffer. - - const bool hasTessLevelOuter = hasBuiltin(tescOutBuiltins, QShaderDescription::TessLevelOuterBuiltin) - && hasBuiltin(teseInBuiltins, QShaderDescription::TessLevelOuterBuiltin); - const bool hasTessLevelInner = hasBuiltin(tescOutBuiltins, QShaderDescription::TessLevelInnerBuiltin) - && hasBuiltin(teseInBuiltins, QShaderDescription::TessLevelInnerBuiltin); - if (vertTese.desc.tessellationMode() != QShaderDescription::TrianglesTessellationMode - && vertTese.desc.tessellationMode() != QShaderDescription::QuadTessellationMode) - { - qWarning("Tessellation evaluation stage mode is neither 'triangles' nor 'quads', this should not happen"); - } const bool trianglesMode = vertTese.desc.tessellationMode() == QShaderDescription::TrianglesTessellationMode; - if ((hasTessLevelOuter || hasTessLevelInner) && tessFactorBufferBinding >= 0) { - int loc0 = -1; - int loc1 = -1; - if (trianglesMode) { - loc0 = ++lastLocation; // float4 gl_TessLevel + bool tessLevelAdded = false; + + for (const QShaderDescription::BuiltinVariable &builtin : tescOutBuiltins) { + + QShaderDescription::InOutVariable variable; + int binding = -1; + quint32 *offset = nullptr; + quint32 *alignment = nullptr; + + switch (builtin.type) { + case QShaderDescription::BuiltinType::PositionBuiltin: + variable.type = QShaderDescription::VariableType::Vec4; + binding = tescOutputBufferBinding; + offset = &offsetInTescOutput; + alignment = &tescOutputAlignment; + break; + case QShaderDescription::BuiltinType::PointSizeBuiltin: + variable.type = QShaderDescription::VariableType::Float; + binding = tescOutputBufferBinding; + offset = &offsetInTescOutput; + alignment = &tescOutputAlignment; + break; + case QShaderDescription::BuiltinType::ClipDistanceBuiltin: + variable.type = QShaderDescription::VariableType::Float; + variable.arrayDims = builtin.arrayDims; + binding = tescOutputBufferBinding; + offset = &offsetInTescOutput; + alignment = &tescOutputAlignment; + break; + case QShaderDescription::BuiltinType::TessLevelOuterBuiltin: + variable.type = QShaderDescription::VariableType::Half4; + binding = tessFactorBufferBinding; + offset = &offsetInTessFactorBuffer; + tessLevelAdded = trianglesMode; + alignment = &tessFactorAlignment; + break; + case QShaderDescription::BuiltinType::TessLevelInnerBuiltin: + if (trianglesMode) { + if (!tessLevelAdded) { + variable.type = QShaderDescription::VariableType::Half4; + binding = tessFactorBufferBinding; + offsetInTessFactorBuffer = 0; + offset = &offsetInTessFactorBuffer; + alignment = &tessFactorAlignment; + tessLevelAdded = true; + } else { + teseInBuiltins.remove(builtin.type); + continue; + } + } else { + variable.type = QShaderDescription::VariableType::Half2; + binding = tessFactorBufferBinding; + offsetInTessFactorBuffer = 8; + offset = &offsetInTessFactorBuffer; + alignment = &tessFactorAlignment; + } + break; + default: + Q_UNREACHABLE(); + break; + } + + if (teseInBuiltins.contains(builtin.type)) { + if (binding != -1) { + int index = nextAttributeIndex(indices); + addVertexAttribute(variable, binding, rhiD, index, *offset, vertexDesc.attributes, indices, *alignment); + usedBuffers << binding; + } else { + qWarning() << "baked tessellation control shader missing output buffer binding information"; + addUnusedVertexAttribute(variable, rhiD, *offset, *alignment); + } } else { - loc0 = ++lastLocation; // float4 gl_TessLevelOuter - loc1 = ++lastLocation; // float2 gl_TessLevelInner + addUnusedVertexAttribute(variable, rhiD, *offset, *alignment); } - if (loc0 >= 0) { - vertexDesc.attributes[loc0].bufferIndex = tessFactorBufferBinding; - vertexDesc.attributes[loc0].format = MTLVertexFormatHalf4; - vertexDesc.attributes[loc0].offset = 0; - } - if (loc1 >= 0) { - vertexDesc.attributes[loc1].bufferIndex = tessFactorBufferBinding; - vertexDesc.attributes[loc1].format = MTLVertexFormatHalf2; - vertexDesc.attributes[loc1].offset = 8; - } - vertexDesc.layouts[tessFactorBufferBinding].stepFunction = MTLVertexStepFunctionPerPatch; - vertexDesc.layouts[tessFactorBufferBinding].stride = trianglesMode ? 8 : 12; + + teseInBuiltins.remove(builtin.type); } - if (offsetInTescOutput > 0) { + for (const QShaderDescription::BuiltinVariable &builtin : teseInBuiltins) { + switch (builtin.type) { + case QShaderDescription::BuiltinType::PositionBuiltin: + case QShaderDescription::BuiltinType::PointSizeBuiltin: + case QShaderDescription::BuiltinType::ClipDistanceBuiltin: + qWarning() << "missing tessellation control output for tessellation evaluation builtin input:" << builtin; + break; + default: + break; + } + } + + if (usedBuffers.contains(tescOutputBufferBinding)) { vertexDesc.layouts[tescOutputBufferBinding].stepFunction = MTLVertexStepFunctionPerPatchControlPoint; - vertexDesc.layouts[tescOutputBufferBinding].stride = offsetInTescOutput; + vertexDesc.layouts[tescOutputBufferBinding].stride = aligned(offsetInTescOutput, tescOutputAlignment); } - if (offsetInTescPatchOutput > 0) { + if (usedBuffers.contains(tescPatchOutputBufferBinding)) { vertexDesc.layouts[tescPatchOutputBufferBinding].stepFunction = MTLVertexStepFunctionPerPatch; - vertexDesc.layouts[tescPatchOutputBufferBinding].stride = offsetInTescPatchOutput; + vertexDesc.layouts[tescPatchOutputBufferBinding].stride = aligned(offsetInTescPatchOutput, tescPatchOutputAlignment); + } + + if (usedBuffers.contains(tessFactorBufferBinding)) { + vertexDesc.layouts[tessFactorBufferBinding].stepFunction = MTLVertexStepFunctionPerPatch; + vertexDesc.layouts[tessFactorBufferBinding].stride = trianglesMode ? sizeof(MTLTriangleTessellationFactorsHalf) : sizeof(MTLQuadTessellationFactorsHalf); } rpDesc.vertexDescriptor = vertexDesc; @@ -5217,7 +5653,9 @@ bool QMetalGraphicsPipeline::createTessellationPipelines(const QShader &tessVert } d->fs.lib = fragLib; d->fs.func = fragFunc; - d->fs.nativeResourceBindingMap = tese.nativeResourceBindingMap(activeKey); + d->fs.desc = tessFrag.description(); + d->fs.nativeShaderInfo = tessFrag.nativeShaderInfo(activeKey); + d->fs.nativeResourceBindingMap = tessFrag.nativeResourceBindingMap(activeKey); if (!d->tess.teseFragRenderPipeline(rhiD, this)) { qWarning("Failed to pre-generate render pipeline for tessellation evaluation + fragment shader"); @@ -5276,6 +5714,42 @@ bool QMetalGraphicsPipeline::create() if (!ok) return false; + // SPIRV-Cross buffer size buffers + int buffers = 0; + QVarLengthArray shaders; + if (d->tess.enabled) { + shaders.append(&d->tess.compVs[0]); + shaders.append(&d->tess.compVs[1]); + shaders.append(&d->tess.compVs[2]); + shaders.append(&d->tess.compTesc); + shaders.append(&d->tess.vertTese); + } else { + shaders.append(&d->vs); + } + shaders.append(&d->fs); + + for (QMetalShader *shader : shaders) { + if (shader->nativeShaderInfo.extraBufferBindings.contains(QShaderPrivate::MslBufferSizeBufferBinding)) { + const int binding = shader->nativeShaderInfo.extraBufferBindings[QShaderPrivate::MslBufferSizeBufferBinding]; + shader->nativeResourceBindingMap[binding] = qMakePair(binding, -1); + int maxNativeBinding = 0; + for (const QShaderDescription::StorageBlock &block : shader->desc.storageBlocks()) + maxNativeBinding = qMax(maxNativeBinding, shader->nativeResourceBindingMap[block.binding].first); + + // we use one buffer to hold data for all graphics shader stages, each with a different offset. + // buffer offsets must be 32byte aligned - adjust buffer count accordingly + buffers += ((maxNativeBinding + 1 + 7) / 8) * 8; + } + } + + if (buffers) { + if (!d->bufferSizeBuffer) + d->bufferSizeBuffer = new QMetalBuffer(rhiD, QRhiBuffer::Static, QRhiBuffer::StorageBuffer, buffers * sizeof(int)); + + d->bufferSizeBuffer->setSize(buffers * sizeof(int)); + d->bufferSizeBuffer->create(); + } + rhiD->pipelineCreationEnd(); lastActiveFrameSlot = -1; generation += 1; @@ -5302,6 +5776,9 @@ void QMetalComputePipeline::destroy() if (!d->ps) return; + delete d->bufferSizeBuffer; + d->bufferSizeBuffer = nullptr; + QRhiMetalData::DeferredReleaseEntry e; e.type = QRhiMetalData::DeferredReleaseEntry::ComputePipeline; e.lastActiveFrameSlot = lastActiveFrameSlot; @@ -5370,6 +5847,14 @@ bool QMetalComputePipeline::create() d->cs.func = func; d->cs.localSize = shader.description().computeShaderLocalSize(); d->cs.nativeResourceBindingMap = shader.nativeResourceBindingMap(activeKey); + d->cs.desc = shader.description(); + d->cs.nativeShaderInfo = shader.nativeShaderInfo(activeKey); + + // SPIRV-Cross buffer size buffers + if (d->cs.nativeShaderInfo.extraBufferBindings.contains(QShaderPrivate::MslBufferSizeBufferBinding)) { + const int binding = d->cs.nativeShaderInfo.extraBufferBindings[QShaderPrivate::MslBufferSizeBufferBinding]; + d->cs.nativeResourceBindingMap[binding] = qMakePair(binding, -1); + } if (rhiD->d->shaderCache.count() >= QRhiMetal::MAX_SHADER_CACHE_ENTRIES) { for (QMetalShader &s : rhiD->d->shaderCache) @@ -5404,6 +5889,21 @@ bool QMetalComputePipeline::create() return false; } + // SPIRV-Cross buffer size buffers + if (d->cs.nativeShaderInfo.extraBufferBindings.contains(QShaderPrivate::MslBufferSizeBufferBinding)) { + int buffers = 0; + for (const QShaderDescription::StorageBlock &block : d->cs.desc.storageBlocks()) + buffers = qMax(buffers, d->cs.nativeResourceBindingMap[block.binding].first); + + buffers += 1; + + if (!d->bufferSizeBuffer) + d->bufferSizeBuffer = new QMetalBuffer(rhiD, QRhiBuffer::Static, QRhiBuffer::StorageBuffer, buffers * sizeof(int)); + + d->bufferSizeBuffer->setSize(buffers * sizeof(int)); + d->bufferSizeBuffer->create(); + } + rhiD->pipelineCreationEnd(); lastActiveFrameSlot = -1; generation += 1; @@ -5436,8 +5936,9 @@ const QRhiNativeHandles *QMetalCommandBuffer::nativeHandles() return &nativeHandlesStruct; } -void QMetalCommandBuffer::resetState() +void QMetalCommandBuffer::resetState(double lastGpuTime) { + d->lastGpuTime = lastGpuTime; d->currentRenderPassEncoder = nil; d->currentComputePassEncoder = nil; d->tessellationComputeEncoder = nil; @@ -5502,8 +6003,7 @@ void QMetalSwapChain::destroy() for (int i = 0; i < QMTL_FRAMES_IN_FLIGHT; ++i) { if (d->sem[i]) { // the semaphores cannot be released if they do not have the initial value - dispatch_semaphore_wait(d->sem[i], DISPATCH_TIME_FOREVER); - dispatch_semaphore_signal(d->sem[i]); + waitUntilCompleted(i); dispatch_release(d->sem[i]); d->sem[i] = nullptr; @@ -5515,6 +6015,12 @@ void QMetalSwapChain::destroy() d->msaaTex[i] = nil; } +#ifdef Q_OS_MACOS + d->liveResizeStartObserver.remove(); + d->liveResizeEndObserver.remove(); + d->liveResizeObserverSet = false; +#endif + d->layer = nullptr; [d->curDrawable release]; @@ -5581,7 +6087,7 @@ bool QMetalSwapChain::isFormatSupported(Format f) { if (f == HDRExtendedSrgbLinear) { if (@available(macOS 10.11, iOS 16.0, *)) - return true; + return hdrInfo().limits.colorComponentValue.maxPotentialColorComponentValue > 1.0f; else return false; } @@ -5628,6 +6134,17 @@ void QMetalSwapChain::chooseFormats() d->rhiColorFormat = QRhiTexture::BGRA8; } +void QMetalSwapChain::waitUntilCompleted(int slot) +{ + // wait+signal is the general pattern to ensure the commands for a + // given frame slot have completed (if sem is 1, we go 0 then 1; if + // sem is 0 we go -1, block, completion increments to 0, then us to 1) + + dispatch_semaphore_t sem = d->sem[slot]; + dispatch_semaphore_wait(sem, DISPATCH_TIME_FOREVER); + dispatch_semaphore_signal(sem); +} + bool QMetalSwapChain::createOrResize() { Q_ASSERT(m_window); @@ -5699,10 +6216,39 @@ bool QMetalSwapChain::createOrResize() [d->layer setDevice: rhiD->d->dev]; +#ifdef Q_OS_MACOS + // Can only use presentsWithTransaction (to get smooth resizing) when + // presenting from the main (gui) thread. We predict that based on the + // thread this function is called on since if the QRhiSwapChain is + // initialied on a given thread then that's almost certainly the thread on + // which the QRhi renders and presents. + const bool canUsePresentsWithTransaction = NSThread.isMainThread; + + // Have an env.var. just in case it turns out presentsWithTransaction is + // not desired in some specific case. + static bool allowPresentsWithTransaction = !qEnvironmentVariableIntValue("QT_MTL_NO_TRANSACTION"); + + if (allowPresentsWithTransaction && canUsePresentsWithTransaction && !d->liveResizeObserverSet) { + d->liveResizeObserverSet = true; + NSView *view = reinterpret_cast(window->winId()); + NSWindow *window = view.window; + if (window) { + qCDebug(QRHI_LOG_INFO, "will set presentsWithTransaction during live resize"); + d->liveResizeStartObserver = QMacNotificationObserver(window, NSWindowWillStartLiveResizeNotification, [this] { + d->layer.presentsWithTransaction = true; + }); + d->liveResizeEndObserver = QMacNotificationObserver(window, NSWindowDidEndLiveResizeNotification, [this] { + d->layer.presentsWithTransaction = false; + }); + } + } +#endif + [d->curDrawable release]; d->curDrawable = nil; for (int i = 0; i < QMTL_FRAMES_IN_FLIGHT; ++i) { + d->lastGpuTime[i] = 0; if (!d->sem[i]) d->sem[i] = dispatch_semaphore_create(QMTL_FRAMES_IN_FLIGHT - 1); } @@ -5768,16 +6314,20 @@ QRhiSwapChainHdrInfo QMetalSwapChain::hdrInfo() info.limits.colorComponentValue.maxColorComponentValue = 1; info.isHardCodedDefaults = true; - if (m_format != SDR && m_window) { + if (m_window) { // Must use m_window, not window, given this may be called before createOrResize(). #ifdef Q_OS_MACOS NSView *view = reinterpret_cast(m_window->winId()); - info.limits.colorComponentValue.maxColorComponentValue = view.window.screen.maximumExtendedDynamicRangeColorComponentValue; + NSScreen *screen = view.window.screen; + info.limits.colorComponentValue.maxColorComponentValue = screen.maximumExtendedDynamicRangeColorComponentValue; + info.limits.colorComponentValue.maxPotentialColorComponentValue = screen.maximumPotentialExtendedDynamicRangeColorComponentValue; info.isHardCodedDefaults = false; #else if (@available(iOS 16.0, *)) { UIView *view = reinterpret_cast(m_window->winId()); + UIScreen *screen = view.window.windowScene.screen; info.limits.colorComponentValue.maxColorComponentValue = view.window.windowScene.screen.currentEDRHeadroom; + info.limits.colorComponentValue.maxPotentialColorComponentValue = screen.potentialEDRHeadroom; info.isHardCodedDefaults = false; } #endif diff --git a/src/gui/rhi/qrhimetal_p.h b/src/gui/rhi/qrhimetal_p.h index 016bf749..8fb2ce84 100644 --- a/src/gui/rhi/qrhimetal_p.h +++ b/src/gui/rhi/qrhimetal_p.h @@ -1,8 +1,8 @@ -// Copyright (C) 2019 The Qt Company Ltd. +// 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 QRHIMETAL_H -#define QRHIMETAL_H +#ifndef QRHIMETAL_P_H +#define QRHIMETAL_P_H // // W A R N I N G @@ -15,29 +15,493 @@ // We mean it. // -#include - -Q_FORWARD_DECLARE_OBJC_CLASS(MTLDevice); -Q_FORWARD_DECLARE_OBJC_CLASS(MTLCommandQueue); -Q_FORWARD_DECLARE_OBJC_CLASS(MTLCommandBuffer); -Q_FORWARD_DECLARE_OBJC_CLASS(MTLRenderCommandEncoder); +#include "qrhi_p.h" +#include QT_BEGIN_NAMESPACE -struct Q_GUI_EXPORT QRhiMetalInitParams : public QRhiInitParams +static const int QMTL_FRAMES_IN_FLIGHT = 2; + +// have to hide the ObjC stuff, this header cannot contain MTL* at all +struct QMetalBufferData; + +struct QMetalBuffer : public QRhiBuffer { + QMetalBuffer(QRhiImplementation *rhi, Type type, UsageFlags usage, quint32 size); + ~QMetalBuffer(); + void destroy() override; + bool create() override; + QRhiBuffer::NativeBuffer nativeBuffer() override; + char *beginFullDynamicBufferUpdateForCurrentFrame() override; + void endFullDynamicBufferUpdateForCurrentFrame() override; + + QMetalBufferData *d; + uint generation = 0; + int lastActiveFrameSlot = -1; + friend class QRhiMetal; + friend struct QMetalShaderResourceBindings; + + static constexpr int WorkBufPoolUsage = 1 << 8; + static_assert(WorkBufPoolUsage > QRhiBuffer::StorageBuffer); }; -struct Q_GUI_EXPORT QRhiMetalNativeHandles : public QRhiNativeHandles +struct QMetalRenderBufferData; + +struct QMetalRenderBuffer : public QRhiRenderBuffer { - MTLDevice *dev = nullptr; - MTLCommandQueue *cmdQueue = nullptr; + QMetalRenderBuffer(QRhiImplementation *rhi, Type type, const QSize &pixelSize, + int sampleCount, QRhiRenderBuffer::Flags flags, + QRhiTexture::Format backingFormatHint); + ~QMetalRenderBuffer(); + void destroy() override; + bool create() override; + QRhiTexture::Format backingFormat() const override; + + QMetalRenderBufferData *d; + int samples = 1; + uint generation = 0; + int lastActiveFrameSlot = -1; + friend class QRhiMetal; }; -struct Q_GUI_EXPORT QRhiMetalCommandBufferNativeHandles : public QRhiNativeHandles +struct QMetalTextureData; + +struct QMetalTexture : public QRhiTexture { - MTLCommandBuffer *commandBuffer = nullptr; - MTLRenderCommandEncoder *encoder = nullptr; + QMetalTexture(QRhiImplementation *rhi, Format format, const QSize &pixelSize, int depth, + int arraySize, int sampleCount, Flags flags); + ~QMetalTexture(); + void destroy() override; + bool create() override; + bool createFrom(NativeTexture src) override; + NativeTexture nativeTexture() override; + + bool prepareCreate(QSize *adjustedSize = nullptr); + + QMetalTextureData *d; + int mipLevelCount = 0; + int samples = 1; + uint generation = 0; + int lastActiveFrameSlot = -1; + friend class QRhiMetal; + friend struct QMetalShaderResourceBindings; + friend struct QMetalTextureData; +}; + +struct QMetalSamplerData; + +struct QMetalSampler : public QRhiSampler +{ + QMetalSampler(QRhiImplementation *rhi, Filter magFilter, Filter minFilter, Filter mipmapMode, + AddressMode u, AddressMode v, AddressMode w); + ~QMetalSampler(); + void destroy() override; + bool create() override; + + QMetalSamplerData *d; + uint generation = 0; + int lastActiveFrameSlot = -1; + friend class QRhiMetal; + friend struct QMetalShaderResourceBindings; +}; + +struct QMetalRenderPassDescriptor : public QRhiRenderPassDescriptor +{ + QMetalRenderPassDescriptor(QRhiImplementation *rhi); + ~QMetalRenderPassDescriptor(); + void destroy() override; + bool isCompatible(const QRhiRenderPassDescriptor *other) const override; + QRhiRenderPassDescriptor *newCompatibleRenderPassDescriptor() const override; + QVector serializedFormat() const override; + + void updateSerializedFormat(); + + // there is no MTLRenderPassDescriptor here as one will be created for each pass in beginPass() + + // but the things needed for the render pipeline descriptor have to be provided + static const int MAX_COLOR_ATTACHMENTS = 8; + int colorAttachmentCount = 0; + bool hasDepthStencil = false; + int colorFormat[MAX_COLOR_ATTACHMENTS]; + int dsFormat; + QVector serializedFormatData; +}; + +struct QMetalRenderTargetData; + +struct QMetalSwapChainRenderTarget : public QRhiSwapChainRenderTarget +{ + QMetalSwapChainRenderTarget(QRhiImplementation *rhi, QRhiSwapChain *swapchain); + ~QMetalSwapChainRenderTarget(); + void destroy() override; + + QSize pixelSize() const override; + float devicePixelRatio() const override; + int sampleCount() const override; + + QMetalRenderTargetData *d; +}; + +struct QMetalTextureRenderTarget : public QRhiTextureRenderTarget +{ + QMetalTextureRenderTarget(QRhiImplementation *rhi, const QRhiTextureRenderTargetDescription &desc, Flags flags); + ~QMetalTextureRenderTarget(); + void destroy() override; + + QSize pixelSize() const override; + float devicePixelRatio() const override; + int sampleCount() const override; + + QRhiRenderPassDescriptor *newCompatibleRenderPassDescriptor() override; + bool create() override; + + QMetalRenderTargetData *d; + friend class QRhiMetal; +}; + +struct QMetalShaderResourceBindings : public QRhiShaderResourceBindings +{ + QMetalShaderResourceBindings(QRhiImplementation *rhi); + ~QMetalShaderResourceBindings(); + void destroy() override; + bool create() override; + void updateResources(UpdateFlags flags) override; + + QVarLengthArray sortedBindings; + int maxBinding = -1; + + struct BoundUniformBufferData { + quint64 id; + uint generation; + }; + struct BoundSampledTextureData { + int count; + struct { + quint64 texId; + uint texGeneration; + quint64 samplerId; + uint samplerGeneration; + } d[QRhiShaderResourceBinding::Data::MAX_TEX_SAMPLER_ARRAY_SIZE]; + }; + struct BoundStorageImageData { + quint64 id; + uint generation; + }; + struct BoundStorageBufferData { + quint64 id; + uint generation; + }; + struct BoundResourceData { + union { + BoundUniformBufferData ubuf; + BoundSampledTextureData stex; + BoundStorageImageData simage; + BoundStorageBufferData sbuf; + }; + }; + QVarLengthArray boundResourceData; + + uint generation = 0; + friend class QRhiMetal; +}; + +struct QMetalGraphicsPipelineData; +struct QMetalCommandBuffer; + +struct QMetalGraphicsPipeline : public QRhiGraphicsPipeline +{ + QMetalGraphicsPipeline(QRhiImplementation *rhi); + ~QMetalGraphicsPipeline(); + void destroy() override; + bool create() override; + + void makeActiveForCurrentRenderPassEncoder(QMetalCommandBuffer *cbD); + void setupAttachmentsInMetalRenderPassDescriptor(void *metalRpDesc, QMetalRenderPassDescriptor *rpD); + void setupMetalDepthStencilDescriptor(void *metalDsDesc); + void mapStates(); + bool createVertexFragmentPipeline(); + bool createTessellationPipelines(const QShader &tessVert, const QShader &tesc, const QShader &tese, const QShader &tessFrag); + + QMetalGraphicsPipelineData *d; + uint generation = 0; + int lastActiveFrameSlot = -1; + friend class QRhiMetal; +}; + +struct QMetalComputePipelineData; + +struct QMetalComputePipeline : public QRhiComputePipeline +{ + QMetalComputePipeline(QRhiImplementation *rhi); + ~QMetalComputePipeline(); + void destroy() override; + bool create() override; + + QMetalComputePipelineData *d; + uint generation = 0; + int lastActiveFrameSlot = -1; + friend class QRhiMetal; +}; + +struct QMetalCommandBufferData; +struct QMetalSwapChain; + +struct QMetalCommandBuffer : public QRhiCommandBuffer +{ + QMetalCommandBuffer(QRhiImplementation *rhi); + ~QMetalCommandBuffer(); + void destroy() override; + + QMetalCommandBufferData *d = nullptr; + QRhiMetalCommandBufferNativeHandles nativeHandlesStruct; + + enum PassType { + NoPass, + RenderPass, + ComputePass + }; + + // per-pass (render or compute command encoder) persistent state + PassType recordingPass; + QRhiRenderTarget *currentTarget; + + // per-pass (render or compute command encoder) volatile (cached) state + QMetalGraphicsPipeline *currentGraphicsPipeline; + QMetalComputePipeline *currentComputePipeline; + uint currentPipelineGeneration; + QMetalShaderResourceBindings *currentGraphicsSrb; + QMetalShaderResourceBindings *currentComputeSrb; + uint currentSrbGeneration; + int currentResSlot; + QMetalBuffer *currentIndexBuffer; + quint32 currentIndexOffset; + QRhiCommandBuffer::IndexFormat currentIndexFormat; + int currentCullMode; + int currentTriangleFillMode; + int currentFrontFaceWinding; + QPair currentDepthBiasValues; + + const QRhiNativeHandles *nativeHandles(); + void resetState(double lastGpuTime = 0); + void resetPerPassState(); + void resetPerPassCachedState(); +}; + +struct QMetalSwapChainData; + +struct QMetalSwapChain : public QRhiSwapChain +{ + QMetalSwapChain(QRhiImplementation *rhi); + ~QMetalSwapChain(); + void destroy() override; + + QRhiCommandBuffer *currentFrameCommandBuffer() override; + QRhiRenderTarget *currentFrameRenderTarget() override; + QSize surfacePixelSize() override; + bool isFormatSupported(Format f) override; + + QRhiRenderPassDescriptor *newCompatibleRenderPassDescriptor() override; + + bool createOrResize() override; + + virtual QRhiSwapChainHdrInfo hdrInfo() override; + + void chooseFormats(); + void waitUntilCompleted(int slot); + + QWindow *window = nullptr; + QSize pixelSize; + int currentFrameSlot = 0; // 0..QMTL_FRAMES_IN_FLIGHT-1 + int frameCount = 0; + int samples = 1; + QMetalSwapChainRenderTarget rtWrapper; + QMetalCommandBuffer cbWrapper; + QMetalRenderBuffer *ds = nullptr; + QMetalSwapChainData *d = nullptr; +}; + +struct QRhiMetalData; + +class QRhiMetal : public QRhiImplementation +{ +public: + QRhiMetal(QRhiMetalInitParams *params, QRhiMetalNativeHandles *importDevice = nullptr); + ~QRhiMetal(); + + static bool probe(QRhiMetalInitParams *params); + static QRhiSwapChainProxyData updateSwapChainProxyData(QWindow *window); + + bool create(QRhi::Flags flags) override; + void destroy() override; + + QRhiGraphicsPipeline *createGraphicsPipeline() override; + QRhiComputePipeline *createComputePipeline() override; + QRhiShaderResourceBindings *createShaderResourceBindings() override; + QRhiBuffer *createBuffer(QRhiBuffer::Type type, + QRhiBuffer::UsageFlags usage, + quint32 size) override; + QRhiRenderBuffer *createRenderBuffer(QRhiRenderBuffer::Type type, + const QSize &pixelSize, + int sampleCount, + QRhiRenderBuffer::Flags flags, + QRhiTexture::Format backingFormatHint) override; + QRhiTexture *createTexture(QRhiTexture::Format format, + const QSize &pixelSize, + int depth, + int arraySize, + int sampleCount, + QRhiTexture::Flags flags) override; + QRhiSampler *createSampler(QRhiSampler::Filter magFilter, + QRhiSampler::Filter minFilter, + QRhiSampler::Filter mipmapMode, + QRhiSampler:: AddressMode u, + QRhiSampler::AddressMode v, + QRhiSampler::AddressMode w) override; + + QRhiTextureRenderTarget *createTextureRenderTarget(const QRhiTextureRenderTargetDescription &desc, + QRhiTextureRenderTarget::Flags flags) override; + + QRhiSwapChain *createSwapChain() override; + QRhi::FrameOpResult beginFrame(QRhiSwapChain *swapChain, QRhi::BeginFrameFlags flags) override; + QRhi::FrameOpResult endFrame(QRhiSwapChain *swapChain, QRhi::EndFrameFlags flags) override; + QRhi::FrameOpResult beginOffscreenFrame(QRhiCommandBuffer **cb, QRhi::BeginFrameFlags flags) override; + QRhi::FrameOpResult endOffscreenFrame(QRhi::EndFrameFlags flags) override; + QRhi::FrameOpResult finish() override; + + void resourceUpdate(QRhiCommandBuffer *cb, QRhiResourceUpdateBatch *resourceUpdates) override; + + void beginPass(QRhiCommandBuffer *cb, + QRhiRenderTarget *rt, + const QColor &colorClearValue, + const QRhiDepthStencilClearValue &depthStencilClearValue, + QRhiResourceUpdateBatch *resourceUpdates, + QRhiCommandBuffer::BeginPassFlags flags) override; + void endPass(QRhiCommandBuffer *cb, QRhiResourceUpdateBatch *resourceUpdates) override; + + void setGraphicsPipeline(QRhiCommandBuffer *cb, + QRhiGraphicsPipeline *ps) override; + + void setShaderResources(QRhiCommandBuffer *cb, + QRhiShaderResourceBindings *srb, + int dynamicOffsetCount, + const QRhiCommandBuffer::DynamicOffset *dynamicOffsets) override; + + void setVertexInput(QRhiCommandBuffer *cb, + int startBinding, int bindingCount, const QRhiCommandBuffer::VertexInput *bindings, + QRhiBuffer *indexBuf, quint32 indexOffset, + QRhiCommandBuffer::IndexFormat indexFormat) override; + + void setViewport(QRhiCommandBuffer *cb, const QRhiViewport &viewport) override; + void setScissor(QRhiCommandBuffer *cb, const QRhiScissor &scissor) override; + void setBlendConstants(QRhiCommandBuffer *cb, const QColor &c) override; + void setStencilRef(QRhiCommandBuffer *cb, quint32 refValue) override; + + void draw(QRhiCommandBuffer *cb, quint32 vertexCount, + quint32 instanceCount, quint32 firstVertex, quint32 firstInstance) override; + + void drawIndexed(QRhiCommandBuffer *cb, quint32 indexCount, + quint32 instanceCount, quint32 firstIndex, + qint32 vertexOffset, quint32 firstInstance) override; + + void debugMarkBegin(QRhiCommandBuffer *cb, const QByteArray &name) override; + void debugMarkEnd(QRhiCommandBuffer *cb) override; + void debugMarkMsg(QRhiCommandBuffer *cb, const QByteArray &msg) override; + + void beginComputePass(QRhiCommandBuffer *cb, + QRhiResourceUpdateBatch *resourceUpdates, + QRhiCommandBuffer::BeginPassFlags flags) override; + void endComputePass(QRhiCommandBuffer *cb, QRhiResourceUpdateBatch *resourceUpdates) override; + void setComputePipeline(QRhiCommandBuffer *cb, QRhiComputePipeline *ps) override; + void dispatch(QRhiCommandBuffer *cb, int x, int y, int z) override; + + const QRhiNativeHandles *nativeHandles(QRhiCommandBuffer *cb) override; + void beginExternal(QRhiCommandBuffer *cb) override; + void endExternal(QRhiCommandBuffer *cb) override; + double lastCompletedGpuTime(QRhiCommandBuffer *cb) override; + + QList supportedSampleCounts() const override; + int ubufAlignment() const override; + bool isYUpInFramebuffer() const override; + bool isYUpInNDC() const override; + bool isClipDepthZeroToOne() const override; + QMatrix4x4 clipSpaceCorrMatrix() const override; + bool isTextureFormatSupported(QRhiTexture::Format format, QRhiTexture::Flags flags) const override; + bool isFeatureSupported(QRhi::Feature feature) const override; + int resourceLimit(QRhi::ResourceLimit limit) const override; + const QRhiNativeHandles *nativeHandles() override; + QRhiDriverInfo driverInfo() const override; + QRhiStats statistics() override; + bool makeThreadLocalNativeContextCurrent() override; + void releaseCachedResources() override; + bool isDeviceLost() const override; + + QByteArray pipelineCacheData() override; + void setPipelineCacheData(const QByteArray &data) override; + + void executeDeferredReleases(bool forced = false); + void finishActiveReadbacks(bool forced = false); + qsizetype subresUploadByteSize(const QRhiTextureSubresourceUploadDescription &subresDesc) const; + void enqueueSubresUpload(QMetalTexture *texD, void *mp, void *blitEncPtr, + int layer, int level, const QRhiTextureSubresourceUploadDescription &subresDesc, + qsizetype *curOfs); + void enqueueResourceUpdates(QRhiCommandBuffer *cb, QRhiResourceUpdateBatch *resourceUpdates); + void executeBufferHostWritesForSlot(QMetalBuffer *bufD, int slot); + void executeBufferHostWritesForCurrentFrame(QMetalBuffer *bufD); + static const int SUPPORTED_STAGES = 5; + void enqueueShaderResourceBindings(QMetalShaderResourceBindings *srbD, + QMetalCommandBuffer *cbD, + int dynamicOffsetCount, + const QRhiCommandBuffer::DynamicOffset *dynamicOffsets, + bool offsetOnlyChange, + const QShader::NativeResourceBindingMap *nativeResourceBindingMaps[SUPPORTED_STAGES]); + int effectiveSampleCount(int sampleCount) const; + struct TessDrawArgs { + QMetalCommandBuffer *cbD; + enum { + NonIndexed, + U16Indexed, + U32Indexed + } type; + struct NonIndexedArgs { + quint32 vertexCount; + quint32 instanceCount; + quint32 firstVertex; + quint32 firstInstance; + }; + struct IndexedArgs { + quint32 indexCount; + quint32 instanceCount; + quint32 firstIndex; + qint32 vertexOffset; + quint32 firstInstance; + void *indexBuffer; + }; + union { + NonIndexedArgs draw; + IndexedArgs drawIndexed; + }; + }; + void tessellatedDraw(const TessDrawArgs &args); + + QRhi::Flags rhiFlags; + bool importedDevice = false; + bool importedCmdQueue = false; + QMetalSwapChain *currentSwapChain = nullptr; + QSet swapchains; + QRhiMetalNativeHandles nativeHandlesStruct; + QRhiDriverInfo driverInfoStruct; + quint32 osMajor = 0; + quint32 osMinor = 0; + + struct { + int maxTextureSize = 4096; + bool baseVertexAndInstance = true; + QVector supportedSampleCounts; + bool isAppleGPU = false; + int maxThreadGroupSize = 512; + } caps; + + QRhiMetalData *d = nullptr; }; QT_END_NAMESPACE diff --git a/src/gui/rhi/qrhimetal_p_p.h b/src/gui/rhi/qrhimetal_p_p.h deleted file mode 100644 index ff57e84a..00000000 --- a/src/gui/rhi/qrhimetal_p_p.h +++ /dev/null @@ -1,508 +0,0 @@ -// Copyright (C) 2019 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 QRHIMETAL_P_H -#define QRHIMETAL_P_H - -// -// W A R N I N G -// ------------- -// -// This file is not part of the Qt API. It exists purely as an -// implementation detail. This header file may change from version to -// version without notice, or even be removed. -// -// We mean it. -// - -#include "qrhimetal_p.h" -#include "qrhi_p_p.h" -#include - -QT_BEGIN_NAMESPACE - -static const int QMTL_FRAMES_IN_FLIGHT = 2; - -// have to hide the ObjC stuff, this header cannot contain MTL* at all -struct QMetalBufferData; - -struct QMetalBuffer : public QRhiBuffer -{ - QMetalBuffer(QRhiImplementation *rhi, Type type, UsageFlags usage, quint32 size); - ~QMetalBuffer(); - void destroy() override; - bool create() override; - QRhiBuffer::NativeBuffer nativeBuffer() override; - char *beginFullDynamicBufferUpdateForCurrentFrame() override; - void endFullDynamicBufferUpdateForCurrentFrame() override; - - QMetalBufferData *d; - uint generation = 0; - int lastActiveFrameSlot = -1; - friend class QRhiMetal; - friend struct QMetalShaderResourceBindings; - - static constexpr int WorkBufPoolUsage = 1 << 8; - static_assert(WorkBufPoolUsage > QRhiBuffer::StorageBuffer); -}; - -struct QMetalRenderBufferData; - -struct QMetalRenderBuffer : public QRhiRenderBuffer -{ - QMetalRenderBuffer(QRhiImplementation *rhi, Type type, const QSize &pixelSize, - int sampleCount, QRhiRenderBuffer::Flags flags, - QRhiTexture::Format backingFormatHint); - ~QMetalRenderBuffer(); - void destroy() override; - bool create() override; - QRhiTexture::Format backingFormat() const override; - - QMetalRenderBufferData *d; - int samples = 1; - uint generation = 0; - int lastActiveFrameSlot = -1; - friend class QRhiMetal; -}; - -struct QMetalTextureData; - -struct QMetalTexture : public QRhiTexture -{ - QMetalTexture(QRhiImplementation *rhi, Format format, const QSize &pixelSize, int depth, - int arraySize, int sampleCount, Flags flags); - ~QMetalTexture(); - void destroy() override; - bool create() override; - bool createFrom(NativeTexture src) override; - NativeTexture nativeTexture() override; - - bool prepareCreate(QSize *adjustedSize = nullptr); - - QMetalTextureData *d; - int mipLevelCount = 0; - int samples = 1; - uint generation = 0; - int lastActiveFrameSlot = -1; - friend class QRhiMetal; - friend struct QMetalShaderResourceBindings; - friend struct QMetalTextureData; -}; - -struct QMetalSamplerData; - -struct QMetalSampler : public QRhiSampler -{ - QMetalSampler(QRhiImplementation *rhi, Filter magFilter, Filter minFilter, Filter mipmapMode, - AddressMode u, AddressMode v, AddressMode w); - ~QMetalSampler(); - void destroy() override; - bool create() override; - - QMetalSamplerData *d; - uint generation = 0; - int lastActiveFrameSlot = -1; - friend class QRhiMetal; - friend struct QMetalShaderResourceBindings; -}; - -struct QMetalRenderPassDescriptor : public QRhiRenderPassDescriptor -{ - QMetalRenderPassDescriptor(QRhiImplementation *rhi); - ~QMetalRenderPassDescriptor(); - void destroy() override; - bool isCompatible(const QRhiRenderPassDescriptor *other) const override; - QRhiRenderPassDescriptor *newCompatibleRenderPassDescriptor() const override; - QVector serializedFormat() const override; - - void updateSerializedFormat(); - - // there is no MTLRenderPassDescriptor here as one will be created for each pass in beginPass() - - // but the things needed for the render pipeline descriptor have to be provided - static const int MAX_COLOR_ATTACHMENTS = 8; - int colorAttachmentCount = 0; - bool hasDepthStencil = false; - int colorFormat[MAX_COLOR_ATTACHMENTS]; - int dsFormat; - QVector serializedFormatData; -}; - -struct QMetalRenderTargetData; - -struct QMetalSwapChainRenderTarget : public QRhiSwapChainRenderTarget -{ - QMetalSwapChainRenderTarget(QRhiImplementation *rhi, QRhiSwapChain *swapchain); - ~QMetalSwapChainRenderTarget(); - void destroy() override; - - QSize pixelSize() const override; - float devicePixelRatio() const override; - int sampleCount() const override; - - QMetalRenderTargetData *d; -}; - -struct QMetalTextureRenderTarget : public QRhiTextureRenderTarget -{ - QMetalTextureRenderTarget(QRhiImplementation *rhi, const QRhiTextureRenderTargetDescription &desc, Flags flags); - ~QMetalTextureRenderTarget(); - void destroy() override; - - QSize pixelSize() const override; - float devicePixelRatio() const override; - int sampleCount() const override; - - QRhiRenderPassDescriptor *newCompatibleRenderPassDescriptor() override; - bool create() override; - - QMetalRenderTargetData *d; - friend class QRhiMetal; -}; - -struct QMetalShaderResourceBindings : public QRhiShaderResourceBindings -{ - QMetalShaderResourceBindings(QRhiImplementation *rhi); - ~QMetalShaderResourceBindings(); - void destroy() override; - bool create() override; - void updateResources(UpdateFlags flags) override; - - QVarLengthArray sortedBindings; - int maxBinding = -1; - - struct BoundUniformBufferData { - quint64 id; - uint generation; - }; - struct BoundSampledTextureData { - int count; - struct { - quint64 texId; - uint texGeneration; - quint64 samplerId; - uint samplerGeneration; - } d[QRhiShaderResourceBinding::Data::MAX_TEX_SAMPLER_ARRAY_SIZE]; - }; - struct BoundStorageImageData { - quint64 id; - uint generation; - }; - struct BoundStorageBufferData { - quint64 id; - uint generation; - }; - struct BoundResourceData { - union { - BoundUniformBufferData ubuf; - BoundSampledTextureData stex; - BoundStorageImageData simage; - BoundStorageBufferData sbuf; - }; - }; - QVarLengthArray boundResourceData; - - uint generation = 0; - friend class QRhiMetal; -}; - -struct QMetalGraphicsPipelineData; -struct QMetalCommandBuffer; - -struct QMetalGraphicsPipeline : public QRhiGraphicsPipeline -{ - QMetalGraphicsPipeline(QRhiImplementation *rhi); - ~QMetalGraphicsPipeline(); - void destroy() override; - bool create() override; - - void makeActiveForCurrentRenderPassEncoder(QMetalCommandBuffer *cbD); - void setupAttachmentsInMetalRenderPassDescriptor(void *metalRpDesc, QMetalRenderPassDescriptor *rpD); - void setupMetalDepthStencilDescriptor(void *metalDsDesc); - void mapStates(); - bool createVertexFragmentPipeline(); - bool createTessellationPipelines(const QShader &tessVert, const QShader &tesc, const QShader &tese, const QShader &tessFrag); - - QMetalGraphicsPipelineData *d; - uint generation = 0; - int lastActiveFrameSlot = -1; - friend class QRhiMetal; -}; - -struct QMetalComputePipelineData; - -struct QMetalComputePipeline : public QRhiComputePipeline -{ - QMetalComputePipeline(QRhiImplementation *rhi); - ~QMetalComputePipeline(); - void destroy() override; - bool create() override; - - QMetalComputePipelineData *d; - uint generation = 0; - int lastActiveFrameSlot = -1; - friend class QRhiMetal; -}; - -struct QMetalCommandBufferData; -struct QMetalSwapChain; - -struct QMetalCommandBuffer : public QRhiCommandBuffer -{ - QMetalCommandBuffer(QRhiImplementation *rhi); - ~QMetalCommandBuffer(); - void destroy() override; - - QMetalCommandBufferData *d = nullptr; - QRhiMetalCommandBufferNativeHandles nativeHandlesStruct; - - enum PassType { - NoPass, - RenderPass, - ComputePass - }; - - // per-pass (render or compute command encoder) persistent state - PassType recordingPass; - QRhiRenderTarget *currentTarget; - - // per-pass (render or compute command encoder) volatile (cached) state - QMetalGraphicsPipeline *currentGraphicsPipeline; - QMetalComputePipeline *currentComputePipeline; - uint currentPipelineGeneration; - QMetalShaderResourceBindings *currentGraphicsSrb; - QMetalShaderResourceBindings *currentComputeSrb; - uint currentSrbGeneration; - int currentResSlot; - QMetalBuffer *currentIndexBuffer; - quint32 currentIndexOffset; - QRhiCommandBuffer::IndexFormat currentIndexFormat; - int currentCullMode; - int currentTriangleFillMode; - int currentFrontFaceWinding; - QPair currentDepthBiasValues; - - const QRhiNativeHandles *nativeHandles(); - void resetState(); - void resetPerPassState(); - void resetPerPassCachedState(); -}; - -struct QMetalSwapChainData; - -struct QMetalSwapChain : public QRhiSwapChain -{ - QMetalSwapChain(QRhiImplementation *rhi); - ~QMetalSwapChain(); - void destroy() override; - - QRhiCommandBuffer *currentFrameCommandBuffer() override; - QRhiRenderTarget *currentFrameRenderTarget() override; - QSize surfacePixelSize() override; - bool isFormatSupported(Format f) override; - - QRhiRenderPassDescriptor *newCompatibleRenderPassDescriptor() override; - - bool createOrResize() override; - - virtual QRhiSwapChainHdrInfo hdrInfo() override; - - void chooseFormats(); - - QWindow *window = nullptr; - QSize pixelSize; - int currentFrameSlot = 0; // 0..QMTL_FRAMES_IN_FLIGHT-1 - int frameCount = 0; - int samples = 1; - QMetalSwapChainRenderTarget rtWrapper; - QMetalCommandBuffer cbWrapper; - QMetalRenderBuffer *ds = nullptr; - QMetalSwapChainData *d = nullptr; -}; - -struct QRhiMetalData; - -class QRhiMetal : public QRhiImplementation -{ -public: - QRhiMetal(QRhiMetalInitParams *params, QRhiMetalNativeHandles *importDevice = nullptr); - ~QRhiMetal(); - - static bool probe(QRhiMetalInitParams *params); - static QRhiSwapChainProxyData updateSwapChainProxyData(QWindow *window); - - bool create(QRhi::Flags flags) override; - void destroy() override; - - QRhiGraphicsPipeline *createGraphicsPipeline() override; - QRhiComputePipeline *createComputePipeline() override; - QRhiShaderResourceBindings *createShaderResourceBindings() override; - QRhiBuffer *createBuffer(QRhiBuffer::Type type, - QRhiBuffer::UsageFlags usage, - quint32 size) override; - QRhiRenderBuffer *createRenderBuffer(QRhiRenderBuffer::Type type, - const QSize &pixelSize, - int sampleCount, - QRhiRenderBuffer::Flags flags, - QRhiTexture::Format backingFormatHint) override; - QRhiTexture *createTexture(QRhiTexture::Format format, - const QSize &pixelSize, - int depth, - int arraySize, - int sampleCount, - QRhiTexture::Flags flags) override; - QRhiSampler *createSampler(QRhiSampler::Filter magFilter, - QRhiSampler::Filter minFilter, - QRhiSampler::Filter mipmapMode, - QRhiSampler:: AddressMode u, - QRhiSampler::AddressMode v, - QRhiSampler::AddressMode w) override; - - QRhiTextureRenderTarget *createTextureRenderTarget(const QRhiTextureRenderTargetDescription &desc, - QRhiTextureRenderTarget::Flags flags) override; - - QRhiSwapChain *createSwapChain() override; - QRhi::FrameOpResult beginFrame(QRhiSwapChain *swapChain, QRhi::BeginFrameFlags flags) override; - QRhi::FrameOpResult endFrame(QRhiSwapChain *swapChain, QRhi::EndFrameFlags flags) override; - QRhi::FrameOpResult beginOffscreenFrame(QRhiCommandBuffer **cb, QRhi::BeginFrameFlags flags) override; - QRhi::FrameOpResult endOffscreenFrame(QRhi::EndFrameFlags flags) override; - QRhi::FrameOpResult finish() override; - - void resourceUpdate(QRhiCommandBuffer *cb, QRhiResourceUpdateBatch *resourceUpdates) override; - - void beginPass(QRhiCommandBuffer *cb, - QRhiRenderTarget *rt, - const QColor &colorClearValue, - const QRhiDepthStencilClearValue &depthStencilClearValue, - QRhiResourceUpdateBatch *resourceUpdates, - QRhiCommandBuffer::BeginPassFlags flags) override; - void endPass(QRhiCommandBuffer *cb, QRhiResourceUpdateBatch *resourceUpdates) override; - - void setGraphicsPipeline(QRhiCommandBuffer *cb, - QRhiGraphicsPipeline *ps) override; - - void setShaderResources(QRhiCommandBuffer *cb, - QRhiShaderResourceBindings *srb, - int dynamicOffsetCount, - const QRhiCommandBuffer::DynamicOffset *dynamicOffsets) override; - - void setVertexInput(QRhiCommandBuffer *cb, - int startBinding, int bindingCount, const QRhiCommandBuffer::VertexInput *bindings, - QRhiBuffer *indexBuf, quint32 indexOffset, - QRhiCommandBuffer::IndexFormat indexFormat) override; - - void setViewport(QRhiCommandBuffer *cb, const QRhiViewport &viewport) override; - void setScissor(QRhiCommandBuffer *cb, const QRhiScissor &scissor) override; - void setBlendConstants(QRhiCommandBuffer *cb, const QColor &c) override; - void setStencilRef(QRhiCommandBuffer *cb, quint32 refValue) override; - - void draw(QRhiCommandBuffer *cb, quint32 vertexCount, - quint32 instanceCount, quint32 firstVertex, quint32 firstInstance) override; - - void drawIndexed(QRhiCommandBuffer *cb, quint32 indexCount, - quint32 instanceCount, quint32 firstIndex, - qint32 vertexOffset, quint32 firstInstance) override; - - void debugMarkBegin(QRhiCommandBuffer *cb, const QByteArray &name) override; - void debugMarkEnd(QRhiCommandBuffer *cb) override; - void debugMarkMsg(QRhiCommandBuffer *cb, const QByteArray &msg) override; - - void beginComputePass(QRhiCommandBuffer *cb, - QRhiResourceUpdateBatch *resourceUpdates, - QRhiCommandBuffer::BeginPassFlags flags) override; - void endComputePass(QRhiCommandBuffer *cb, QRhiResourceUpdateBatch *resourceUpdates) override; - void setComputePipeline(QRhiCommandBuffer *cb, QRhiComputePipeline *ps) override; - void dispatch(QRhiCommandBuffer *cb, int x, int y, int z) override; - - const QRhiNativeHandles *nativeHandles(QRhiCommandBuffer *cb) override; - void beginExternal(QRhiCommandBuffer *cb) override; - void endExternal(QRhiCommandBuffer *cb) override; - - QList supportedSampleCounts() const override; - int ubufAlignment() const override; - bool isYUpInFramebuffer() const override; - bool isYUpInNDC() const override; - bool isClipDepthZeroToOne() const override; - QMatrix4x4 clipSpaceCorrMatrix() const override; - bool isTextureFormatSupported(QRhiTexture::Format format, QRhiTexture::Flags flags) const override; - bool isFeatureSupported(QRhi::Feature feature) const override; - int resourceLimit(QRhi::ResourceLimit limit) const override; - const QRhiNativeHandles *nativeHandles() override; - QRhiDriverInfo driverInfo() const override; - QRhiStats statistics() override; - bool makeThreadLocalNativeContextCurrent() override; - void releaseCachedResources() override; - bool isDeviceLost() const override; - - QByteArray pipelineCacheData() override; - void setPipelineCacheData(const QByteArray &data) override; - - void executeDeferredReleases(bool forced = false); - void finishActiveReadbacks(bool forced = false); - qsizetype subresUploadByteSize(const QRhiTextureSubresourceUploadDescription &subresDesc) const; - void enqueueSubresUpload(QMetalTexture *texD, void *mp, void *blitEncPtr, - int layer, int level, const QRhiTextureSubresourceUploadDescription &subresDesc, - qsizetype *curOfs); - void enqueueResourceUpdates(QRhiCommandBuffer *cb, QRhiResourceUpdateBatch *resourceUpdates); - void executeBufferHostWritesForSlot(QMetalBuffer *bufD, int slot); - void executeBufferHostWritesForCurrentFrame(QMetalBuffer *bufD); - static const int SUPPORTED_STAGES = 5; - void enqueueShaderResourceBindings(QMetalShaderResourceBindings *srbD, - QMetalCommandBuffer *cbD, - int dynamicOffsetCount, - const QRhiCommandBuffer::DynamicOffset *dynamicOffsets, - bool offsetOnlyChange, - const QShader::NativeResourceBindingMap *nativeResourceBindingMaps[SUPPORTED_STAGES]); - int effectiveSampleCount(int sampleCount) const; - struct TessDrawArgs { - QMetalCommandBuffer *cbD; - enum { - NonIndexed, - U16Indexed, - U32Indexed - } type; - struct NonIndexedArgs { - quint32 vertexCount; - quint32 instanceCount; - quint32 firstVertex; - quint32 firstInstance; - }; - struct IndexedArgs { - quint32 indexCount; - quint32 instanceCount; - quint32 firstIndex; - qint32 vertexOffset; - quint32 firstInstance; - void *indexBuffer; - }; - union { - NonIndexedArgs draw; - IndexedArgs drawIndexed; - }; - }; - void tessellatedDraw(const TessDrawArgs &args); - - QRhi::Flags rhiFlags; - bool importedDevice = false; - bool importedCmdQueue = false; - QMetalSwapChain *currentSwapChain = nullptr; - QSet swapchains; - QRhiMetalNativeHandles nativeHandlesStruct; - QRhiDriverInfo driverInfoStruct; - quint32 osMajor = 0; - quint32 osMinor = 0; - - struct { - int maxTextureSize = 4096; - bool baseVertexAndInstance = true; - QVector supportedSampleCounts; - bool isAppleGPU = false; - int maxThreadGroupSize = 512; - } caps; - - QRhiMetalData *d = nullptr; -}; - -QT_END_NAMESPACE - -#endif diff --git a/src/gui/rhi/qrhinull.cpp b/src/gui/rhi/qrhinull.cpp index 04f64aa1..566b922c 100644 --- a/src/gui/rhi/qrhinull.cpp +++ b/src/gui/rhi/qrhinull.cpp @@ -1,7 +1,7 @@ -// Copyright (C) 2019 The Qt Company Ltd. +// 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 "qrhinull_p_p.h" +#include "qrhinull_p.h" #include #include @@ -9,10 +9,13 @@ QT_BEGIN_NAMESPACE /*! \class QRhiNullInitParams - \internal \inmodule QtGui + \since 6.6 \brief Null backend specific initialization parameters. + \note This is a RHI API with limited compatibility guarantees, see \l QRhi + for details. + A Null QRhi needs no special parameters for initialization. \badcode @@ -28,9 +31,12 @@ QT_BEGIN_NAMESPACE /*! \class QRhiNullNativeHandles - \internal \inmodule QtGui + \since 6.6 \brief Empty. + + \note This is a RHI API with limited compatibility guarantees, see \l QRhi + for details. */ QRhiNull::QRhiNull(QRhiNullInitParams *params) @@ -344,6 +350,12 @@ void QRhiNull::endExternal(QRhiCommandBuffer *cb) Q_UNUSED(cb); } +double QRhiNull::lastCompletedGpuTime(QRhiCommandBuffer *cb) +{ + Q_UNUSED(cb); + return 0; +} + QRhi::FrameOpResult QRhiNull::beginFrame(QRhiSwapChain *swapChain, QRhi::BeginFrameFlags flags) { Q_UNUSED(flags); @@ -456,7 +468,7 @@ void QRhiNull::resourceUpdate(QRhiCommandBuffer *cb, QRhiResourceUpdateBatch *re QNullBuffer *bufD = QRHI_RES(QNullBuffer, u.buf); memcpy(bufD->data + u.offset, u.data.constData(), size_t(u.data.size())); } else if (u.type == QRhiResourceUpdateBatchPrivate::BufferOp::Read) { - QRhiBufferReadbackResult *result = u.result; + QRhiReadbackResult *result = u.result; result->data.resize(u.readSize); QNullBuffer *bufD = QRHI_RES(QNullBuffer, u.buf); memcpy(result->data.data(), bufD->data + u.offset, size_t(u.readSize)); @@ -664,10 +676,11 @@ bool QNullTexture::create() const bool is1D = m_flags.testFlags(OneDimensional); QSize size = is1D ? QSize(qMax(1, m_pixelSize.width()), 1) : (m_pixelSize.isEmpty() ? QSize(1, 1) : m_pixelSize); - m_depth = qMax(1, m_depth); const int mipLevelCount = hasMipMaps ? rhiD->q->mipLevelsForSize(size) : 1; - m_arraySize = qMax(0, m_arraySize); - const int layerCount = is3D ? m_depth : (isCube ? 6 : (isArray ? m_arraySize : 1)); + const int layerCount = is3D ? qMax(1, m_depth) + : (isCube ? 6 + : (isArray ? qMax(0, m_arraySize) + : 1)); if (m_format == RGBA8) { image.resize(layerCount); diff --git a/src/gui/rhi/qrhinull_p.h b/src/gui/rhi/qrhinull_p.h index 061b6eba..fc266b4f 100644 --- a/src/gui/rhi/qrhinull_p.h +++ b/src/gui/rhi/qrhinull_p.h @@ -1,8 +1,8 @@ -// Copyright (C) 2019 The Qt Company Ltd. +// 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 QRHINULL_H -#define QRHINULL_H +#ifndef QRHINULL_P_H +#define QRHINULL_P_H // // W A R N I N G @@ -15,16 +15,279 @@ // We mean it. // -#include +#include "qrhi_p.h" QT_BEGIN_NAMESPACE -struct Q_GUI_EXPORT QRhiNullInitParams : public QRhiInitParams +struct QNullBuffer : public QRhiBuffer { + QNullBuffer(QRhiImplementation *rhi, Type type, UsageFlags usage, quint32 size); + ~QNullBuffer(); + void destroy() override; + bool create() override; + char *beginFullDynamicBufferUpdateForCurrentFrame() override; + + char *data = nullptr; }; -struct Q_GUI_EXPORT QRhiNullNativeHandles : public QRhiNativeHandles +struct QNullRenderBuffer : public QRhiRenderBuffer { + QNullRenderBuffer(QRhiImplementation *rhi, Type type, const QSize &pixelSize, + int sampleCount, QRhiRenderBuffer::Flags flags, + QRhiTexture::Format backingFormatHint); + ~QNullRenderBuffer(); + void destroy() override; + bool create() override; + QRhiTexture::Format backingFormat() const override; + + bool valid = false; + uint generation = 0; +}; + +struct QNullTexture : public QRhiTexture +{ + QNullTexture(QRhiImplementation *rhi, Format format, const QSize &pixelSize, int depth, + int arraySize, int sampleCount, Flags flags); + ~QNullTexture(); + void destroy() override; + bool create() override; + bool createFrom(NativeTexture src) override; + + bool valid = false; + QVarLengthArray, 6> image; + uint generation = 0; +}; + +struct QNullSampler : public QRhiSampler +{ + QNullSampler(QRhiImplementation *rhi, Filter magFilter, Filter minFilter, Filter mipmapMode, + AddressMode u, AddressMode v, AddressMode w); + ~QNullSampler(); + void destroy() override; + bool create() override; +}; + +struct QNullRenderPassDescriptor : public QRhiRenderPassDescriptor +{ + QNullRenderPassDescriptor(QRhiImplementation *rhi); + ~QNullRenderPassDescriptor(); + void destroy() override; + bool isCompatible(const QRhiRenderPassDescriptor *other) const override; + QRhiRenderPassDescriptor *newCompatibleRenderPassDescriptor() const override; + QVector serializedFormat() const override; +}; + +struct QNullRenderTargetData +{ + QNullRenderTargetData(QRhiImplementation *) { } + + QNullRenderPassDescriptor *rp = nullptr; + QSize pixelSize; + float dpr = 1; + QRhiRenderTargetAttachmentTracker::ResIdList currentResIdList; +}; + +struct QNullSwapChainRenderTarget : public QRhiSwapChainRenderTarget +{ + QNullSwapChainRenderTarget(QRhiImplementation *rhi, QRhiSwapChain *swapchain); + ~QNullSwapChainRenderTarget(); + void destroy() override; + + QSize pixelSize() const override; + float devicePixelRatio() const override; + int sampleCount() const override; + + QNullRenderTargetData d; +}; + +struct QNullTextureRenderTarget : public QRhiTextureRenderTarget +{ + QNullTextureRenderTarget(QRhiImplementation *rhi, const QRhiTextureRenderTargetDescription &desc, Flags flags); + ~QNullTextureRenderTarget(); + void destroy() override; + + QSize pixelSize() const override; + float devicePixelRatio() const override; + int sampleCount() const override; + + QRhiRenderPassDescriptor *newCompatibleRenderPassDescriptor() override; + bool create() override; + + QNullRenderTargetData d; +}; + +struct QNullShaderResourceBindings : public QRhiShaderResourceBindings +{ + QNullShaderResourceBindings(QRhiImplementation *rhi); + ~QNullShaderResourceBindings(); + void destroy() override; + bool create() override; + void updateResources(UpdateFlags flags) override; +}; + +struct QNullGraphicsPipeline : public QRhiGraphicsPipeline +{ + QNullGraphicsPipeline(QRhiImplementation *rhi); + ~QNullGraphicsPipeline(); + void destroy() override; + bool create() override; +}; + +struct QNullComputePipeline : public QRhiComputePipeline +{ + QNullComputePipeline(QRhiImplementation *rhi); + ~QNullComputePipeline(); + void destroy() override; + bool create() override; +}; + +struct QNullCommandBuffer : public QRhiCommandBuffer +{ + QNullCommandBuffer(QRhiImplementation *rhi); + ~QNullCommandBuffer(); + void destroy() override; +}; + +struct QNullSwapChain : public QRhiSwapChain +{ + QNullSwapChain(QRhiImplementation *rhi); + ~QNullSwapChain(); + void destroy() override; + + QRhiCommandBuffer *currentFrameCommandBuffer() override; + QRhiRenderTarget *currentFrameRenderTarget() override; + + QSize surfacePixelSize() override; + bool isFormatSupported(Format f) override; + + QRhiRenderPassDescriptor *newCompatibleRenderPassDescriptor() override; + bool createOrResize() override; + + QWindow *window = nullptr; + QNullSwapChainRenderTarget rt; + QNullCommandBuffer cb; + int frameCount = 0; +}; + +class QRhiNull : public QRhiImplementation +{ +public: + QRhiNull(QRhiNullInitParams *params); + + bool create(QRhi::Flags flags) override; + void destroy() override; + + QRhiGraphicsPipeline *createGraphicsPipeline() override; + QRhiComputePipeline *createComputePipeline() override; + QRhiShaderResourceBindings *createShaderResourceBindings() override; + QRhiBuffer *createBuffer(QRhiBuffer::Type type, + QRhiBuffer::UsageFlags usage, + quint32 size) override; + QRhiRenderBuffer *createRenderBuffer(QRhiRenderBuffer::Type type, + const QSize &pixelSize, + int sampleCount, + QRhiRenderBuffer::Flags flags, + QRhiTexture::Format backingFormatHint) override; + QRhiTexture *createTexture(QRhiTexture::Format format, + const QSize &pixelSize, + int depth, + int arraySize, + int sampleCount, + QRhiTexture::Flags flags) override; + QRhiSampler *createSampler(QRhiSampler::Filter magFilter, + QRhiSampler::Filter minFilter, + QRhiSampler::Filter mipmapMode, + QRhiSampler:: AddressMode u, + QRhiSampler::AddressMode v, + QRhiSampler::AddressMode w) override; + + QRhiTextureRenderTarget *createTextureRenderTarget(const QRhiTextureRenderTargetDescription &desc, + QRhiTextureRenderTarget::Flags flags) override; + + QRhiSwapChain *createSwapChain() override; + QRhi::FrameOpResult beginFrame(QRhiSwapChain *swapChain, QRhi::BeginFrameFlags flags) override; + QRhi::FrameOpResult endFrame(QRhiSwapChain *swapChain, QRhi::EndFrameFlags flags) override; + QRhi::FrameOpResult beginOffscreenFrame(QRhiCommandBuffer **cb, QRhi::BeginFrameFlags flags) override; + QRhi::FrameOpResult endOffscreenFrame(QRhi::EndFrameFlags flags) override; + QRhi::FrameOpResult finish() override; + + void resourceUpdate(QRhiCommandBuffer *cb, QRhiResourceUpdateBatch *resourceUpdates) override; + + void beginPass(QRhiCommandBuffer *cb, + QRhiRenderTarget *rt, + const QColor &colorClearValue, + const QRhiDepthStencilClearValue &depthStencilClearValue, + QRhiResourceUpdateBatch *resourceUpdates, + QRhiCommandBuffer::BeginPassFlags flags) override; + void endPass(QRhiCommandBuffer *cb, QRhiResourceUpdateBatch *resourceUpdates) override; + + void setGraphicsPipeline(QRhiCommandBuffer *cb, + QRhiGraphicsPipeline *ps) override; + + void setShaderResources(QRhiCommandBuffer *cb, + QRhiShaderResourceBindings *srb, + int dynamicOffsetCount, + const QRhiCommandBuffer::DynamicOffset *dynamicOffsets) override; + + void setVertexInput(QRhiCommandBuffer *cb, + int startBinding, int bindingCount, const QRhiCommandBuffer::VertexInput *bindings, + QRhiBuffer *indexBuf, quint32 indexOffset, + QRhiCommandBuffer::IndexFormat indexFormat) override; + + void setViewport(QRhiCommandBuffer *cb, const QRhiViewport &viewport) override; + void setScissor(QRhiCommandBuffer *cb, const QRhiScissor &scissor) override; + void setBlendConstants(QRhiCommandBuffer *cb, const QColor &c) override; + void setStencilRef(QRhiCommandBuffer *cb, quint32 refValue) override; + + void draw(QRhiCommandBuffer *cb, quint32 vertexCount, + quint32 instanceCount, quint32 firstVertex, quint32 firstInstance) override; + + void drawIndexed(QRhiCommandBuffer *cb, quint32 indexCount, + quint32 instanceCount, quint32 firstIndex, + qint32 vertexOffset, quint32 firstInstance) override; + + void debugMarkBegin(QRhiCommandBuffer *cb, const QByteArray &name) override; + void debugMarkEnd(QRhiCommandBuffer *cb) override; + void debugMarkMsg(QRhiCommandBuffer *cb, const QByteArray &msg) override; + + void beginComputePass(QRhiCommandBuffer *cb, + QRhiResourceUpdateBatch *resourceUpdates, + QRhiCommandBuffer::BeginPassFlags flags) override; + void endComputePass(QRhiCommandBuffer *cb, QRhiResourceUpdateBatch *resourceUpdates) override; + void setComputePipeline(QRhiCommandBuffer *cb, QRhiComputePipeline *ps) override; + void dispatch(QRhiCommandBuffer *cb, int x, int y, int z) override; + + const QRhiNativeHandles *nativeHandles(QRhiCommandBuffer *cb) override; + void beginExternal(QRhiCommandBuffer *cb) override; + void endExternal(QRhiCommandBuffer *cb) override; + double lastCompletedGpuTime(QRhiCommandBuffer *cb) override; + + QList supportedSampleCounts() const override; + int ubufAlignment() const override; + bool isYUpInFramebuffer() const override; + bool isYUpInNDC() const override; + bool isClipDepthZeroToOne() const override; + QMatrix4x4 clipSpaceCorrMatrix() const override; + bool isTextureFormatSupported(QRhiTexture::Format format, QRhiTexture::Flags flags) const override; + bool isFeatureSupported(QRhi::Feature feature) const override; + int resourceLimit(QRhi::ResourceLimit limit) const override; + const QRhiNativeHandles *nativeHandles() override; + QRhiDriverInfo driverInfo() const override; + QRhiStats statistics() override; + bool makeThreadLocalNativeContextCurrent() override; + void releaseCachedResources() override; + bool isDeviceLost() const override; + + QByteArray pipelineCacheData() override; + void setPipelineCacheData(const QByteArray &data) override; + + void simulateTextureUpload(const QRhiResourceUpdateBatchPrivate::TextureOp &u); + void simulateTextureCopy(const QRhiResourceUpdateBatchPrivate::TextureOp &u); + void simulateTextureGenMips(const QRhiResourceUpdateBatchPrivate::TextureOp &u); + + QRhiNullNativeHandles nativeHandlesStruct; + QRhiSwapChain *currentSwapChain = nullptr; + QNullCommandBuffer offscreenCommandBuffer; }; QT_END_NAMESPACE diff --git a/src/gui/rhi/qrhinull_p_p.h b/src/gui/rhi/qrhinull_p_p.h deleted file mode 100644 index 2ace5a36..00000000 --- a/src/gui/rhi/qrhinull_p_p.h +++ /dev/null @@ -1,295 +0,0 @@ -// Copyright (C) 2019 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 QRHINULL_P_H -#define QRHINULL_P_H - -// -// W A R N I N G -// ------------- -// -// This file is not part of the Qt API. It exists purely as an -// implementation detail. This header file may change from version to -// version without notice, or even be removed. -// -// We mean it. -// - -#include "qrhinull_p.h" -#include "qrhi_p_p.h" - -QT_BEGIN_NAMESPACE - -struct QNullBuffer : public QRhiBuffer -{ - QNullBuffer(QRhiImplementation *rhi, Type type, UsageFlags usage, quint32 size); - ~QNullBuffer(); - void destroy() override; - bool create() override; - char *beginFullDynamicBufferUpdateForCurrentFrame() override; - - char *data = nullptr; -}; - -struct QNullRenderBuffer : public QRhiRenderBuffer -{ - QNullRenderBuffer(QRhiImplementation *rhi, Type type, const QSize &pixelSize, - int sampleCount, QRhiRenderBuffer::Flags flags, - QRhiTexture::Format backingFormatHint); - ~QNullRenderBuffer(); - void destroy() override; - bool create() override; - QRhiTexture::Format backingFormat() const override; - - bool valid = false; - uint generation = 0; -}; - -struct QNullTexture : public QRhiTexture -{ - QNullTexture(QRhiImplementation *rhi, Format format, const QSize &pixelSize, int depth, - int arraySize, int sampleCount, Flags flags); - ~QNullTexture(); - void destroy() override; - bool create() override; - bool createFrom(NativeTexture src) override; - - bool valid = false; - QVarLengthArray, 6> image; - uint generation = 0; -}; - -struct QNullSampler : public QRhiSampler -{ - QNullSampler(QRhiImplementation *rhi, Filter magFilter, Filter minFilter, Filter mipmapMode, - AddressMode u, AddressMode v, AddressMode w); - ~QNullSampler(); - void destroy() override; - bool create() override; -}; - -struct QNullRenderPassDescriptor : public QRhiRenderPassDescriptor -{ - QNullRenderPassDescriptor(QRhiImplementation *rhi); - ~QNullRenderPassDescriptor(); - void destroy() override; - bool isCompatible(const QRhiRenderPassDescriptor *other) const override; - QRhiRenderPassDescriptor *newCompatibleRenderPassDescriptor() const override; - QVector serializedFormat() const override; -}; - -struct QNullRenderTargetData -{ - QNullRenderTargetData(QRhiImplementation *) { } - - QNullRenderPassDescriptor *rp = nullptr; - QSize pixelSize; - float dpr = 1; - QRhiRenderTargetAttachmentTracker::ResIdList currentResIdList; -}; - -struct QNullSwapChainRenderTarget : public QRhiSwapChainRenderTarget -{ - QNullSwapChainRenderTarget(QRhiImplementation *rhi, QRhiSwapChain *swapchain); - ~QNullSwapChainRenderTarget(); - void destroy() override; - - QSize pixelSize() const override; - float devicePixelRatio() const override; - int sampleCount() const override; - - QNullRenderTargetData d; -}; - -struct QNullTextureRenderTarget : public QRhiTextureRenderTarget -{ - QNullTextureRenderTarget(QRhiImplementation *rhi, const QRhiTextureRenderTargetDescription &desc, Flags flags); - ~QNullTextureRenderTarget(); - void destroy() override; - - QSize pixelSize() const override; - float devicePixelRatio() const override; - int sampleCount() const override; - - QRhiRenderPassDescriptor *newCompatibleRenderPassDescriptor() override; - bool create() override; - - QNullRenderTargetData d; -}; - -struct QNullShaderResourceBindings : public QRhiShaderResourceBindings -{ - QNullShaderResourceBindings(QRhiImplementation *rhi); - ~QNullShaderResourceBindings(); - void destroy() override; - bool create() override; - void updateResources(UpdateFlags flags) override; -}; - -struct QNullGraphicsPipeline : public QRhiGraphicsPipeline -{ - QNullGraphicsPipeline(QRhiImplementation *rhi); - ~QNullGraphicsPipeline(); - void destroy() override; - bool create() override; -}; - -struct QNullComputePipeline : public QRhiComputePipeline -{ - QNullComputePipeline(QRhiImplementation *rhi); - ~QNullComputePipeline(); - void destroy() override; - bool create() override; -}; - -struct QNullCommandBuffer : public QRhiCommandBuffer -{ - QNullCommandBuffer(QRhiImplementation *rhi); - ~QNullCommandBuffer(); - void destroy() override; -}; - -struct QNullSwapChain : public QRhiSwapChain -{ - QNullSwapChain(QRhiImplementation *rhi); - ~QNullSwapChain(); - void destroy() override; - - QRhiCommandBuffer *currentFrameCommandBuffer() override; - QRhiRenderTarget *currentFrameRenderTarget() override; - - QSize surfacePixelSize() override; - bool isFormatSupported(Format f) override; - - QRhiRenderPassDescriptor *newCompatibleRenderPassDescriptor() override; - bool createOrResize() override; - - QWindow *window = nullptr; - QNullSwapChainRenderTarget rt; - QNullCommandBuffer cb; - int frameCount = 0; -}; - -class QRhiNull : public QRhiImplementation -{ -public: - QRhiNull(QRhiNullInitParams *params); - - bool create(QRhi::Flags flags) override; - void destroy() override; - - QRhiGraphicsPipeline *createGraphicsPipeline() override; - QRhiComputePipeline *createComputePipeline() override; - QRhiShaderResourceBindings *createShaderResourceBindings() override; - QRhiBuffer *createBuffer(QRhiBuffer::Type type, - QRhiBuffer::UsageFlags usage, - quint32 size) override; - QRhiRenderBuffer *createRenderBuffer(QRhiRenderBuffer::Type type, - const QSize &pixelSize, - int sampleCount, - QRhiRenderBuffer::Flags flags, - QRhiTexture::Format backingFormatHint) override; - QRhiTexture *createTexture(QRhiTexture::Format format, - const QSize &pixelSize, - int depth, - int arraySize, - int sampleCount, - QRhiTexture::Flags flags) override; - QRhiSampler *createSampler(QRhiSampler::Filter magFilter, - QRhiSampler::Filter minFilter, - QRhiSampler::Filter mipmapMode, - QRhiSampler:: AddressMode u, - QRhiSampler::AddressMode v, - QRhiSampler::AddressMode w) override; - - QRhiTextureRenderTarget *createTextureRenderTarget(const QRhiTextureRenderTargetDescription &desc, - QRhiTextureRenderTarget::Flags flags) override; - - QRhiSwapChain *createSwapChain() override; - QRhi::FrameOpResult beginFrame(QRhiSwapChain *swapChain, QRhi::BeginFrameFlags flags) override; - QRhi::FrameOpResult endFrame(QRhiSwapChain *swapChain, QRhi::EndFrameFlags flags) override; - QRhi::FrameOpResult beginOffscreenFrame(QRhiCommandBuffer **cb, QRhi::BeginFrameFlags flags) override; - QRhi::FrameOpResult endOffscreenFrame(QRhi::EndFrameFlags flags) override; - QRhi::FrameOpResult finish() override; - - void resourceUpdate(QRhiCommandBuffer *cb, QRhiResourceUpdateBatch *resourceUpdates) override; - - void beginPass(QRhiCommandBuffer *cb, - QRhiRenderTarget *rt, - const QColor &colorClearValue, - const QRhiDepthStencilClearValue &depthStencilClearValue, - QRhiResourceUpdateBatch *resourceUpdates, - QRhiCommandBuffer::BeginPassFlags flags) override; - void endPass(QRhiCommandBuffer *cb, QRhiResourceUpdateBatch *resourceUpdates) override; - - void setGraphicsPipeline(QRhiCommandBuffer *cb, - QRhiGraphicsPipeline *ps) override; - - void setShaderResources(QRhiCommandBuffer *cb, - QRhiShaderResourceBindings *srb, - int dynamicOffsetCount, - const QRhiCommandBuffer::DynamicOffset *dynamicOffsets) override; - - void setVertexInput(QRhiCommandBuffer *cb, - int startBinding, int bindingCount, const QRhiCommandBuffer::VertexInput *bindings, - QRhiBuffer *indexBuf, quint32 indexOffset, - QRhiCommandBuffer::IndexFormat indexFormat) override; - - void setViewport(QRhiCommandBuffer *cb, const QRhiViewport &viewport) override; - void setScissor(QRhiCommandBuffer *cb, const QRhiScissor &scissor) override; - void setBlendConstants(QRhiCommandBuffer *cb, const QColor &c) override; - void setStencilRef(QRhiCommandBuffer *cb, quint32 refValue) override; - - void draw(QRhiCommandBuffer *cb, quint32 vertexCount, - quint32 instanceCount, quint32 firstVertex, quint32 firstInstance) override; - - void drawIndexed(QRhiCommandBuffer *cb, quint32 indexCount, - quint32 instanceCount, quint32 firstIndex, - qint32 vertexOffset, quint32 firstInstance) override; - - void debugMarkBegin(QRhiCommandBuffer *cb, const QByteArray &name) override; - void debugMarkEnd(QRhiCommandBuffer *cb) override; - void debugMarkMsg(QRhiCommandBuffer *cb, const QByteArray &msg) override; - - void beginComputePass(QRhiCommandBuffer *cb, - QRhiResourceUpdateBatch *resourceUpdates, - QRhiCommandBuffer::BeginPassFlags flags) override; - void endComputePass(QRhiCommandBuffer *cb, QRhiResourceUpdateBatch *resourceUpdates) override; - void setComputePipeline(QRhiCommandBuffer *cb, QRhiComputePipeline *ps) override; - void dispatch(QRhiCommandBuffer *cb, int x, int y, int z) override; - - const QRhiNativeHandles *nativeHandles(QRhiCommandBuffer *cb) override; - void beginExternal(QRhiCommandBuffer *cb) override; - void endExternal(QRhiCommandBuffer *cb) override; - - QList supportedSampleCounts() const override; - int ubufAlignment() const override; - bool isYUpInFramebuffer() const override; - bool isYUpInNDC() const override; - bool isClipDepthZeroToOne() const override; - QMatrix4x4 clipSpaceCorrMatrix() const override; - bool isTextureFormatSupported(QRhiTexture::Format format, QRhiTexture::Flags flags) const override; - bool isFeatureSupported(QRhi::Feature feature) const override; - int resourceLimit(QRhi::ResourceLimit limit) const override; - const QRhiNativeHandles *nativeHandles() override; - QRhiDriverInfo driverInfo() const override; - QRhiStats statistics() override; - bool makeThreadLocalNativeContextCurrent() override; - void releaseCachedResources() override; - bool isDeviceLost() const override; - - QByteArray pipelineCacheData() override; - void setPipelineCacheData(const QByteArray &data) override; - - void simulateTextureUpload(const QRhiResourceUpdateBatchPrivate::TextureOp &u); - void simulateTextureCopy(const QRhiResourceUpdateBatchPrivate::TextureOp &u); - void simulateTextureGenMips(const QRhiResourceUpdateBatchPrivate::TextureOp &u); - - QRhiNullNativeHandles nativeHandlesStruct; - QRhiSwapChain *currentSwapChain = nullptr; - QNullCommandBuffer offscreenCommandBuffer; -}; - -QT_END_NAMESPACE - -#endif diff --git a/src/gui/rhi/qrhivulkan.cpp b/src/gui/rhi/qrhivulkan.cpp index db6206e4..108b93b2 100644 --- a/src/gui/rhi/qrhivulkan.cpp +++ b/src/gui/rhi/qrhivulkan.cpp @@ -1,8 +1,8 @@ -// Copyright (C) 2019 The Qt Company Ltd. +// 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 "qrhivulkan_p_p.h" -#include "qrhivulkanext_p.h" +#include "qrhivulkan_p.h" +#include #define VMA_IMPLEMENTATION #define VMA_DYNAMIC_VULKAN_FUNCTIONS 1 @@ -59,10 +59,13 @@ QT_BEGIN_NAMESPACE /*! \class QRhiVulkanInitParams - \internal \inmodule QtGui + \since 6.6 \brief Vulkan specific initialization parameters. + \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 the user to ensure this is available and initialized. This is typically done in main() similarly to the following: @@ -164,35 +167,122 @@ QT_BEGIN_NAMESPACE available. */ +/*! + \variable QRhiVulkanInitParams::inst + + The QVulkanInstance that has already been successfully + \l{QVulkanInstance::create()}{created}, required. +*/ + +/*! + \variable QRhiVulkanInitParams::window + + Optional, but recommended when targeting a QWindow. +*/ + +/*! + \variable QRhiVulkanInitParams::deviceExtensions + + Optional, empty by default. The list of Vulkan device extensions to enable. + Unsupported extensions are ignored gracefully. +*/ + /*! \class QRhiVulkanNativeHandles - \internal \inmodule QtGui + \since 6.6 \brief Collects device, queue, and other Vulkan objects that are used by the QRhi. \note Ownership of the Vulkan objects is never transferred. + + \note This is a RHI API with limited compatibility guarantees, see \l QRhi + for details. */ +/*! + \variable QRhiVulkanNativeHandles::physDev + + When different from \nullptr, specifies the Vulkan physical device to use. +*/ + +/*! + \variable QRhiVulkanNativeHandles::dev + + When wanting to import not just a physical device, but also use an already + existing VkDevice, set this and the graphics queue index and family index. +*/ + +/*! + \variable QRhiVulkanNativeHandles::gfxQueueFamilyIdx + + Graphics queue family index. +*/ + +/*! + \variable QRhiVulkanNativeHandles::gfxQueueIdx + + Graphics queue index. +*/ + +/*! + \variable QRhiVulkanNativeHandles::vmemAllocator + + Relevant only when importing an existing memory allocator object, + leave it set to \nullptr otherwise. +*/ + +/*! + \variable QRhiVulkanNativeHandles::gfxQueue + + Output only, not used by QRhi::create(), only set by the + QRhi::nativeHandles() accessor. The graphics VkQueue used by the QRhi. +*/ + +/*! + \variable QRhiVulkanNativeHandles::inst + + Output only, not used by QRhi::create(), only set by the + QRhi::nativeHandles() accessor. The QVulkanInstance used by the QRhi. +*/ + /*! \class QRhiVulkanCommandBufferNativeHandles - \internal \inmodule QtGui + \since 6.6 \brief Holds the Vulkan command buffer object that is backing a QRhiCommandBuffer. \note The Vulkan command buffer object is only guaranteed to be valid, and in recording state, while recording a frame. That is, between a \l{QRhi::beginFrame()}{beginFrame()} - \l{QRhi::endFrame()}{endFrame()} or \l{QRhi::beginOffscreenFrame()}{beginOffscreenFrame()} - - \l{QRhi::endOffsrceenFrame()}{endOffscreenFrame()} pair. + \l{QRhi::endOffscreenFrame()}{endOffscreenFrame()} pair. + + \note This is a RHI API with limited compatibility guarantees, see \l QRhi + for details. */ +/*! + \variable QRhiVulkanCommandBufferNativeHandles::commandBuffer + + The VkCommandBuffer object. +*/ + /*! \class QRhiVulkanRenderPassNativeHandles - \internal \inmodule QtGui + \since 6.6 \brief Holds the Vulkan render pass object backing a QRhiRenderPassDescriptor. + + \note This is a RHI API with limited compatibility guarantees, see \l QRhi + for details. */ +/*! + \variable QRhiVulkanRenderPassNativeHandles::renderPass + + The VkRenderPass object. +*/ + template inline Int aligned(Int v, Int byteAlign) { @@ -221,6 +311,13 @@ static inline VmaAllocator toVmaAllocator(QVkAllocator a) return reinterpret_cast(a); } +/*! + \return the list of instance extensions that are expected to be enabled on + the QVulkanInstance that is used for the Vulkan-based QRhi. + + The returned list can be safely passed to QVulkanInstance::setExtensions() + as-is, because unsupported extensions are filtered out automatically. + */ QByteArrayList QRhiVulkanInitParams::preferredInstanceExtensions() { return { @@ -228,6 +325,11 @@ QByteArrayList QRhiVulkanInitParams::preferredInstanceExtensions() }; } +/*! + \return the list of device extensions that are expected to be enabled on the + \c VkDevice when creating a Vulkan-based QRhi with an externally created + \c VkDevice object. + */ QByteArrayList QRhiVulkanInitParams::preferredExtensionsForImportedDevice() { return { @@ -427,7 +529,31 @@ bool QRhiVulkan::create(QRhi::Flags flags) driverInfoStruct.vendorId = physDevProperties.vendorID; driverInfoStruct.deviceType = toRhiDeviceType(physDevProperties.deviceType); - f->vkGetPhysicalDeviceFeatures(physDev, &physDevFeatures); +#ifdef VK_VERSION_1_2 // Vulkan11Features is only in Vulkan 1.2 + VkPhysicalDeviceFeatures2 physDevFeaturesChainable = {}; + physDevFeaturesChainable.sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_FEATURES_2; + physDevFeatures11 = {}; + physDevFeatures11.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 + if (caps.apiVersion >= QVersionNumber(1, 2)) { + physDevFeaturesChainable.pNext = &physDevFeatures11; + physDevFeatures11.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)); + } else +#endif // VK_VERSION_1_2 + { + f->vkGetPhysicalDeviceFeatures(physDev, &physDevFeatures); + } // Choose queue and create device, unless the device was specified in importParams. if (!importedDevice) { @@ -500,12 +626,14 @@ 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 for (const QByteArray &ext : requestedDeviceExtensions) { if (!ext.isEmpty() && !requestedDevExts.contains(ext)) { @@ -558,42 +686,18 @@ 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; -#endif - +#ifdef VK_VERSION_1_2 if (caps.apiVersion >= QVersionNumber(1, 2)) { - physDevFeatures2.pNext = &features11; - features11.pNext = &features12; + physDevFeaturesChainable.features.robustBufferAccess = VK_FALSE; #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; -#endif - - devInfo.pNext = &physDevFeatures2; - } + devInfo.pNext = &physDevFeaturesChainable; + } else #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); @@ -720,6 +824,7 @@ bool QRhiVulkan::create(QRhi::Flags flags) nativeHandlesStruct.gfxQueueIdx = gfxQueueIdx; nativeHandlesStruct.gfxQueue = gfxQueue; nativeHandlesStruct.vmemAllocator = allocator; + nativeHandlesStruct.inst = inst; return true; } @@ -1668,12 +1773,32 @@ void QRhiVulkan::ensureCommandPoolForNewFrame() df->vkResetCommandPool(dev, cmdPool[currentFrameSlot], flags); } +double QRhiVulkan::elapsedSecondsFromTimestamp(quint64 timestamp[2], bool *ok) +{ + quint64 mask = 0; + for (quint64 i = 0; i < timestampValidBits; i += 8) + mask |= 0xFFULL << i; + const quint64 ts0 = timestamp[0] & mask; + const quint64 ts1 = timestamp[1] & mask; + const float nsecsPerTick = physDevProperties.limits.timestampPeriod; + if (!qFuzzyIsNull(nsecsPerTick)) { + const float elapsedMs = float(ts1 - ts0) * nsecsPerTick / 1000000.0f; + const double elapsedSec = elapsedMs / 1000.0; + *ok = true; + return elapsedSec; + } + *ok = false; + return 0; +} + QRhi::FrameOpResult QRhiVulkan::beginFrame(QRhiSwapChain *swapChain, QRhi::BeginFrameFlags) { QVkSwapChain *swapChainD = QRHI_RES(QVkSwapChain, swapChain); const int frameResIndex = swapChainD->bufferCount > 1 ? swapChainD->currentFrameSlot : 0; QVkSwapChain::FrameResources &frame(swapChainD->frameRes[frameResIndex]); + inst->handle()->beginFrame(swapChainD->window); + if (!frame.imageAcquired) { // Wait if we are too far ahead, i.e. the thread gets throttled based on the presentation rate // (note that we are using FIFO mode -> vsync) @@ -1717,30 +1842,6 @@ QRhi::FrameOpResult QRhiVulkan::beginFrame(QRhiSwapChain *swapChain, QRhi::Begin // mess up A's in-flight commands (as they are not in flight anymore). waitCommandCompletion(frameResIndex); - // Now is the time to read the timestamps for the previous frame for this slot. - if (frame.timestampQueryIndex >= 0) { - quint64 timestamp[2] = { 0, 0 }; - VkResult err = df->vkGetQueryPoolResults(dev, timestampQueryPool, uint32_t(frame.timestampQueryIndex), 2, - 2 * sizeof(quint64), timestamp, sizeof(quint64), - VK_QUERY_RESULT_64_BIT | VK_QUERY_RESULT_WAIT_BIT); - timestampQueryPoolMap.clearBit(frame.timestampQueryIndex / 2); - frame.timestampQueryIndex = -1; - if (err == VK_SUCCESS) { - quint64 mask = 0; - for (quint64 i = 0; i < timestampValidBits; i += 8) - mask |= 0xFFULL << i; - const quint64 ts0 = timestamp[0] & mask; - const quint64 ts1 = timestamp[1] & mask; - const float nsecsPerTick = physDevProperties.limits.timestampPeriod; - if (!qFuzzyIsNull(nsecsPerTick)) { - const float elapsedMs = float(ts1 - ts0) * nsecsPerTick / 1000000.0f; - runGpuFrameTimeCallbacks(elapsedMs); - } - } else { - qWarning("Failed to query timestamp: %d", err); - } - } - currentFrameSlot = int(swapChainD->currentFrameSlot); currentSwapChain = swapChainD; if (swapChainD->ds) @@ -1754,9 +1855,34 @@ QRhi::FrameOpResult QRhiVulkan::beginFrame(QRhiSwapChain *swapChain, QRhi::Begin if (cbres != QRhi::FrameOpSuccess) return cbres; - // when profiling is enabled, pick a free query (pair) from the pool - int timestampQueryIdx = -1; - if (hasGpuFrameTimeCallback() && swapChainD->bufferCount > 1) { // no timestamps if not having at least 2 frames in flight + swapChainD->cbWrapper.cb = frame.cmdBuf; + + QVkSwapChain::ImageResources &image(swapChainD->imageRes[swapChainD->currentImageIndex]); + swapChainD->rtWrapper.d.fb = image.fb; + + prepareNewFrame(&swapChainD->cbWrapper); + + // Read the timestamps for the previous frame for this slot. + if (frame.timestampQueryIndex >= 0) { + quint64 timestamp[2] = { 0, 0 }; + VkResult err = df->vkGetQueryPoolResults(dev, timestampQueryPool, uint32_t(frame.timestampQueryIndex), 2, + 2 * sizeof(quint64), timestamp, sizeof(quint64), + VK_QUERY_RESULT_64_BIT | VK_QUERY_RESULT_WAIT_BIT); + timestampQueryPoolMap.clearBit(frame.timestampQueryIndex / 2); + frame.timestampQueryIndex = -1; + if (err == VK_SUCCESS) { + bool ok = false; + const double elapsedSec = elapsedSecondsFromTimestamp(timestamp, &ok); + if (ok) + swapChainD->cbWrapper.lastGpuTime = elapsedSec; + } else { + qWarning("Failed to query timestamp: %d", err); + } + } + + // No timestamps if the client did not opt in, or when not having at least 2 frames in flight. + if (rhiFlags.testFlag(QRhi::EnableTimestamps) && swapChainD->bufferCount > 1) { + int timestampQueryIdx = -1; for (int i = 0; i < timestampQueryPoolMap.size(); ++i) { if (!timestampQueryPoolMap.testBit(i)) { timestampQueryPoolMap.setBit(i); @@ -1764,21 +1890,14 @@ QRhi::FrameOpResult QRhiVulkan::beginFrame(QRhiSwapChain *swapChain, QRhi::Begin break; } } + if (timestampQueryIdx >= 0) { + df->vkCmdResetQueryPool(frame.cmdBuf, timestampQueryPool, uint32_t(timestampQueryIdx), 2); + // record timestamp at the start of the command buffer + df->vkCmdWriteTimestamp(frame.cmdBuf, VK_PIPELINE_STAGE_TOP_OF_PIPE_BIT, + timestampQueryPool, uint32_t(timestampQueryIdx)); + frame.timestampQueryIndex = timestampQueryIdx; + } } - if (timestampQueryIdx >= 0) { - df->vkCmdResetQueryPool(frame.cmdBuf, timestampQueryPool, uint32_t(timestampQueryIdx), 2); - // record timestamp at the start of the command buffer - df->vkCmdWriteTimestamp(frame.cmdBuf, VK_PIPELINE_STAGE_TOP_OF_PIPE_BIT, - timestampQueryPool, uint32_t(timestampQueryIdx)); - frame.timestampQueryIndex = timestampQueryIdx; - } - - swapChainD->cbWrapper.cb = frame.cmdBuf; - - QVkSwapChain::ImageResources &image(swapChainD->imageRes[swapChainD->currentImageIndex]); - swapChainD->rtWrapper.d.fb = image.fb; - - prepareNewFrame(&swapChainD->cbWrapper); return QRhi::FrameOpSuccess; } @@ -1788,6 +1907,10 @@ QRhi::FrameOpResult QRhiVulkan::endFrame(QRhiSwapChain *swapChain, QRhi::EndFram QVkSwapChain *swapChainD = QRHI_RES(QVkSwapChain, swapChain); Q_ASSERT(currentSwapChain == swapChainD); + auto cleanup = qScopeGuard([this, swapChainD] { + inst->handle()->endFrame(swapChainD->window); + }); + recordPrimaryCommandBuffer(&swapChainD->cbWrapper); int frameResIndex = swapChainD->bufferCount > 1 ? swapChainD->currentFrameSlot : 0; @@ -2028,6 +2151,24 @@ QRhi::FrameOpResult QRhiVulkan::beginOffscreenFrame(QRhiCommandBuffer **cb, QRhi prepareNewFrame(cbWrapper); ofr.active = true; + if (rhiFlags.testFlag(QRhi::EnableTimestamps)) { + int timestampQueryIdx = -1; + for (int i = 0; i < timestampQueryPoolMap.size(); ++i) { + if (!timestampQueryPoolMap.testBit(i)) { + timestampQueryPoolMap.setBit(i); + timestampQueryIdx = i * 2; + break; + } + } + if (timestampQueryIdx >= 0) { + df->vkCmdResetQueryPool(cbWrapper->cb, timestampQueryPool, uint32_t(timestampQueryIdx), 2); + // record timestamp at the start of the command buffer + df->vkCmdWriteTimestamp(cbWrapper->cb, VK_PIPELINE_STAGE_TOP_OF_PIPE_BIT, + timestampQueryPool, uint32_t(timestampQueryIdx)); + ofr.timestampQueryIndex = timestampQueryIdx; + } + } + *cb = cbWrapper; return QRhi::FrameOpSuccess; } @@ -2041,6 +2182,12 @@ QRhi::FrameOpResult QRhiVulkan::endOffscreenFrame(QRhi::EndFrameFlags flags) QVkCommandBuffer *cbWrapper(ofr.cbWrapper[currentFrameSlot]); recordPrimaryCommandBuffer(cbWrapper); + // record another timestamp, when enabled + if (ofr.timestampQueryIndex >= 0) { + df->vkCmdWriteTimestamp(cbWrapper->cb, VK_PIPELINE_STAGE_BOTTOM_OF_PIPE_BIT, + timestampQueryPool, uint32_t(ofr.timestampQueryIndex + 1)); + } + if (!ofr.cmdFence) { VkFenceCreateInfo fenceInfo = {}; fenceInfo.sType = VK_STRUCTURE_TYPE_FENCE_CREATE_INFO; @@ -2063,6 +2210,24 @@ QRhi::FrameOpResult QRhiVulkan::endOffscreenFrame(QRhi::EndFrameFlags flags) // previous) frame is safe since we waited for completion above. finishActiveReadbacks(true); + // Read the timestamps, if we wrote them. + if (ofr.timestampQueryIndex >= 0) { + quint64 timestamp[2] = { 0, 0 }; + VkResult err = df->vkGetQueryPoolResults(dev, timestampQueryPool, uint32_t(ofr.timestampQueryIndex), 2, + 2 * sizeof(quint64), timestamp, sizeof(quint64), + VK_QUERY_RESULT_64_BIT | VK_QUERY_RESULT_WAIT_BIT); + timestampQueryPoolMap.clearBit(ofr.timestampQueryIndex / 2); + ofr.timestampQueryIndex = -1; + if (err == VK_SUCCESS) { + bool ok = false; + const double elapsedSec = elapsedSecondsFromTimestamp(timestamp, &ok); + if (ok) + cbWrapper->lastGpuTime = elapsedSec; + } else { + qWarning("Failed to query timestamp: %d", err); + } + } + return QRhi::FrameOpSuccess; } @@ -2461,7 +2626,7 @@ void QRhiVulkan::dispatch(QRhiCommandBuffer *cb, int x, int y, int z) QVkShaderResourceBindings *srbD = QRHI_RES(QVkShaderResourceBindings, cbD->currentComputeSrb); const int bindingCount = srbD->m_bindings.size(); for (int i = 0; i < bindingCount; ++i) { - const QRhiShaderResourceBinding::Data *b = srbD->m_bindings.at(i).data(); + const QRhiShaderResourceBinding::Data *b = shaderResourceBindingData(srbD->m_bindings.at(i)); switch (b->type) { case QRhiShaderResourceBinding::ImageLoad: case QRhiShaderResourceBinding::ImageStore: @@ -2619,7 +2784,7 @@ void QRhiVulkan::updateShaderResourceBindings(QRhiShaderResourceBindings *srb, i int frameSlot = updateAll ? 0 : descSetIdx; while (frameSlot < (updateAll ? QVK_FRAMES_IN_FLIGHT : descSetIdx + 1)) { for (int i = 0, ie = srbD->sortedBindings.size(); i != ie; ++i) { - const QRhiShaderResourceBinding::Data *b = srbD->sortedBindings.at(i).data(); + const QRhiShaderResourceBinding::Data *b = shaderResourceBindingData(srbD->sortedBindings.at(i)); QVkShaderResourceBindings::BoundResourceData &bd(srbD->boundResourceData[frameSlot][i]); VkWriteDescriptorSet writeInfo = {}; @@ -3431,10 +3596,10 @@ void QRhiVulkan::enqueueResourceUpdates(QVkCommandBuffer *cbD, QRhiResourceUpdat if (!origStage) origStage = VK_PIPELINE_STAGE_TOP_OF_PIPE_BIT; - for (int layer = 0; layer < (isCube ? 6 : (isArray ? utexD->m_arraySize : 1)); ++layer) { + for (int layer = 0; layer < (isCube ? 6 : (isArray ? qMax(0, utexD->m_arraySize) : 1)); ++layer) { int w = utexD->m_pixelSize.width(); int h = utexD->m_pixelSize.height(); - int depth = is3D ? utexD->m_depth : 1; + int depth = is3D ? qMax(1, utexD->m_depth) : 1; for (int level = 1; level < int(utexD->mipLevelCount); ++level) { if (level == 1) { subresourceBarrier(cbD, utexD->image, @@ -4262,6 +4427,12 @@ bool QRhiVulkan::isFeatureSupported(QRhi::Feature feature) const return true; case QRhi::OneDimensionalTextureMipmaps: return true; + case QRhi::HalfAttributes: + return true; + case QRhi::RenderToOneDimensionalTexture: + return true; + case QRhi::ThreeDimensionalTextureMipmaps: + return true; default: Q_UNREACHABLE_RETURN(false); } @@ -4566,7 +4737,7 @@ void QRhiVulkan::setShaderResources(QRhiCommandBuffer *cb, QRhiShaderResourceBin // Do host writes and mark referenced shader resources as in-use. // Also prepare to ensure the descriptor set we are going to bind refers to up-to-date Vk objects. for (int i = 0, ie = srbD->sortedBindings.size(); i != ie; ++i) { - const QRhiShaderResourceBinding::Data *b = srbD->sortedBindings[i].data(); + const QRhiShaderResourceBinding::Data *b = shaderResourceBindingData(srbD->sortedBindings[i]); QVkShaderResourceBindings::BoundResourceData &bd(descSetBd[i]); switch (b->type) { case QRhiShaderResourceBinding::UniformBuffer: @@ -4713,7 +4884,7 @@ void QRhiVulkan::setShaderResources(QRhiCommandBuffer *cb, QRhiShaderResourceBin // and neither srb nor dynamicOffsets has any such ordering // requirement. for (const QRhiShaderResourceBinding &binding : std::as_const(srbD->sortedBindings)) { - const QRhiShaderResourceBinding::Data *b = binding.data(); + const QRhiShaderResourceBinding::Data *b = shaderResourceBindingData(binding); if (b->type == QRhiShaderResourceBinding::UniformBuffer && b->u.ubuf.hasDynamicOffset) { uint32_t offset = 0; for (int i = 0; i < dynamicOffsetCount; ++i) { @@ -5144,6 +5315,12 @@ void QRhiVulkan::endExternal(QRhiCommandBuffer *cb) cbD->resetCachedState(); } +double QRhiVulkan::lastCompletedGpuTime(QRhiCommandBuffer *cb) +{ + QVkCommandBuffer *cbD = QRHI_RES(QVkCommandBuffer, cb); + return cbD->lastGpuTime; +} + void QRhiVulkan::setObjectName(uint64_t object, VkObjectType type, const QByteArray &name, int slot) { #ifdef VK_EXT_debug_utils @@ -5276,6 +5453,14 @@ static inline VkFormat toVkAttributeFormat(QRhiVertexInputAttribute::Format form return VK_FORMAT_R32G32_SINT; case QRhiVertexInputAttribute::SInt: return VK_FORMAT_R32_SINT; + case QRhiVertexInputAttribute::Half4: + return VK_FORMAT_R16G16B16A16_SFLOAT; + case QRhiVertexInputAttribute::Half3: + return VK_FORMAT_R16G16B16_SFLOAT; + case QRhiVertexInputAttribute::Half2: + return VK_FORMAT_R16G16_SFLOAT; + case QRhiVertexInputAttribute::Half: + return VK_FORMAT_R16_SFLOAT; default: Q_UNREACHABLE_RETURN(VK_FORMAT_R32G32B32A32_SFLOAT); } @@ -5925,12 +6110,10 @@ bool QVkTexture::prepareCreate(QSize *adjustedSize) qWarning("Texture cannot be both 1D and 3D"); return false; } - m_depth = qMax(1, m_depth); if (m_depth > 1 && !is3D) { qWarning("Texture cannot have a depth of %d when it is not 3D", m_depth); return false; } - m_arraySize = qMax(0, m_arraySize); if (m_arraySize > 0 && !isArray) { qWarning("Texture cannot have an array size of %d when it is not an array", m_arraySize); return false; @@ -5979,7 +6162,7 @@ bool QVkTexture::finishCreate() viewInfo.subresourceRange.baseArrayLayer = uint32_t(m_arrayRangeStart); viewInfo.subresourceRange.layerCount = uint32_t(m_arrayRangeLength); } else { - viewInfo.subresourceRange.layerCount = isCube ? 6 : (isArray ? m_arraySize : 1); + viewInfo.subresourceRange.layerCount = isCube ? 6 : (isArray ? qMax(0, m_arraySize) : 1); } VkResult err = rhiD->df->vkCreateImageView(rhiD->dev, &viewInfo, nullptr, &imageView); @@ -6033,9 +6216,9 @@ bool QVkTexture::create() imageInfo.format = vkformat; imageInfo.extent.width = uint32_t(size.width()); imageInfo.extent.height = uint32_t(size.height()); - imageInfo.extent.depth = is3D ? m_depth : 1; + imageInfo.extent.depth = is3D ? qMax(1, m_depth) : 1; imageInfo.mipLevels = mipLevelCount; - imageInfo.arrayLayers = isCube ? 6 : (isArray ? m_arraySize : 1); + imageInfo.arrayLayers = isCube ? 6 : (isArray ? qMax(0, m_arraySize) : 1); imageInfo.samples = samples; imageInfo.tiling = VK_IMAGE_TILING_OPTIMAL; imageInfo.initialLayout = VK_IMAGE_LAYOUT_PREINITIALIZED; @@ -6136,7 +6319,7 @@ VkImageView QVkTexture::imageViewForLevel(int level) viewInfo.subresourceRange.baseMipLevel = uint32_t(level); viewInfo.subresourceRange.levelCount = 1; viewInfo.subresourceRange.baseArrayLayer = 0; - viewInfo.subresourceRange.layerCount = isCube ? 6 : (isArray ? m_arraySize : 1); + viewInfo.subresourceRange.layerCount = isCube ? 6 : (isArray ? qMax(0, m_arraySize) : 1); VkImageView v = VK_NULL_HANDLE; QRHI_RES_RHI(QRhiVulkan); @@ -6490,8 +6673,7 @@ bool QVkTextureRenderTarget::create() if (d.fb) destroy(); - const bool hasColorAttachments = m_desc.cbeginColorAttachments() != m_desc.cendColorAttachments(); - Q_ASSERT(hasColorAttachments || m_desc.depthTexture()); + Q_ASSERT(m_desc.colorAttachmentCount() > 0 || m_desc.depthTexture()); Q_ASSERT(!m_desc.depthStencilBuffer() || !m_desc.depthTexture()); const bool hasDepthStencil = m_desc.depthStencilBuffer() || m_desc.depthTexture(); @@ -6694,16 +6876,12 @@ bool QVkShaderResourceBindings::create() sortedBindings.clear(); std::copy(m_bindings.cbegin(), m_bindings.cend(), std::back_inserter(sortedBindings)); - std::sort(sortedBindings.begin(), sortedBindings.end(), - [](const QRhiShaderResourceBinding &a, const QRhiShaderResourceBinding &b) - { - return a.data()->binding < b.data()->binding; - }); + std::sort(sortedBindings.begin(), sortedBindings.end(), QRhiImplementation::sortedBindingLessThan); hasSlottedResource = false; hasDynamicOffset = false; for (const QRhiShaderResourceBinding &binding : std::as_const(sortedBindings)) { - const QRhiShaderResourceBinding::Data *b = binding.data(); + const QRhiShaderResourceBinding::Data *b = QRhiImplementation::shaderResourceBindingData(binding); if (b->type == QRhiShaderResourceBinding::UniformBuffer && b->u.ubuf.buf) { if (QRHI_RES(QVkBuffer, b->u.ubuf.buf)->type() == QRhiBuffer::Dynamic) hasSlottedResource = true; @@ -6714,7 +6892,7 @@ bool QVkShaderResourceBindings::create() QVarLengthArray vkbindings; for (const QRhiShaderResourceBinding &binding : std::as_const(sortedBindings)) { - const QRhiShaderResourceBinding::Data *b = binding.data(); + const QRhiShaderResourceBinding::Data *b = QRhiImplementation::shaderResourceBindingData(binding); VkDescriptorSetLayoutBinding vkbinding = {}; vkbinding.binding = uint32_t(b->binding); vkbinding.descriptorType = toVkDescriptorType(b); @@ -6763,13 +6941,8 @@ void QVkShaderResourceBindings::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(), - [](const QRhiShaderResourceBinding &a, const QRhiShaderResourceBinding &b) - { - return a.data()->binding < b.data()->binding; - }); - } + if (!flags.testFlag(BindingsAreSorted)) + std::sort(sortedBindings.begin(), sortedBindings.end(), QRhiImplementation::sortedBindingLessThan); // Reset the state tracking table too - it can deal with assigning a // different QRhiBuffer/Texture/Sampler for a binding point, but it cannot @@ -6873,7 +7046,9 @@ bool QVkGraphicsPipeline::create() pipelineInfo.pStages = shaderStageCreateInfos.constData(); QVarLengthArray vertexBindings; +#ifdef VK_EXT_vertex_attribute_divisor QVarLengthArray nonOneStepRates; +#endif int bindingIndex = 0; for (auto it = m_vertexInputLayout.cbeginBindings(), itEnd = m_vertexInputLayout.cendBindings(); it != itEnd; ++it, ++bindingIndex) @@ -6885,9 +7060,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"); @@ -6913,13 +7091,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 dynEnable; @@ -7367,11 +7547,9 @@ bool QVkSwapChain::ensureSurface() const bool srgbRequested = m_flags.testFlag(sRGB); for (int i = 0; i < int(formatCount); ++i) { if (formats[i].format != VK_FORMAT_UNDEFINED) { - bool ok = false; - if (m_format == SDR) - ok = srgbRequested == isSrgbFormat(formats[i].format); - else - ok = hdrFormatMatchesVkSurfaceFormat(m_format, formats[i]); + bool ok = srgbRequested == isSrgbFormat(formats[i].format); + if (m_format != SDR) + ok &= hdrFormatMatchesVkSurfaceFormat(m_format, formats[i]); if (ok) { colorFormat = formats[i].format; colorSpace = formats[i].colorSpace; diff --git a/src/gui/rhi/qrhivulkan_p.h b/src/gui/rhi/qrhivulkan_p.h index f592cf7e..fa35dcc4 100644 --- a/src/gui/rhi/qrhivulkan_p.h +++ b/src/gui/rhi/qrhivulkan_p.h @@ -1,8 +1,8 @@ -// Copyright (C) 2019 The Qt Company Ltd. +// 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 QRHIVULKAN_H -#define QRHIVULKAN_H +#ifndef QRHIVULKAN_P_H +#define QRHIVULKAN_P_H // // W A R N I N G @@ -15,43 +15,997 @@ // We mean it. // -#include -#include // this is where vulkan.h gets pulled in +#include "qrhi_p.h" QT_BEGIN_NAMESPACE -struct Q_GUI_EXPORT QRhiVulkanInitParams : public QRhiInitParams -{ - QVulkanInstance *inst = nullptr; - QWindow *window = nullptr; - QByteArrayList deviceExtensions; +class QVulkanFunctions; +class QVulkanDeviceFunctions; - static QByteArrayList preferredInstanceExtensions(); - static QByteArrayList preferredExtensionsForImportedDevice(); +static const int QVK_FRAMES_IN_FLIGHT = 2; + +static const int QVK_DESC_SETS_PER_POOL = 128; +static const int QVK_UNIFORM_BUFFERS_PER_POOL = 256; +static const int QVK_COMBINED_IMAGE_SAMPLERS_PER_POOL = 256; +static const int QVK_STORAGE_BUFFERS_PER_POOL = 128; +static const int QVK_STORAGE_IMAGES_PER_POOL = 128; + +static const int QVK_MAX_ACTIVE_TIMESTAMP_PAIRS = 16; + +// no vk_mem_alloc.h available here, void* is good enough +typedef void * QVkAlloc; +typedef void * QVkAllocator; + +struct QVkBuffer : public QRhiBuffer +{ + QVkBuffer(QRhiImplementation *rhi, Type type, UsageFlags usage, quint32 size); + ~QVkBuffer(); + void destroy() override; + bool create() override; + QRhiBuffer::NativeBuffer nativeBuffer() override; + char *beginFullDynamicBufferUpdateForCurrentFrame() override; + void endFullDynamicBufferUpdateForCurrentFrame() override; + + VkBuffer buffers[QVK_FRAMES_IN_FLIGHT]; + QVkAlloc allocations[QVK_FRAMES_IN_FLIGHT]; + struct DynamicUpdate { + quint32 offset; + QRhiBufferData data; + }; + QVarLengthArray pendingDynamicUpdates[QVK_FRAMES_IN_FLIGHT]; + VkBuffer stagingBuffers[QVK_FRAMES_IN_FLIGHT]; + QVkAlloc stagingAllocations[QVK_FRAMES_IN_FLIGHT]; + struct UsageState { + VkAccessFlags access = 0; + VkPipelineStageFlags stage = 0; + }; + UsageState usageState[QVK_FRAMES_IN_FLIGHT]; + int lastActiveFrameSlot = -1; + uint generation = 0; + friend class QRhiVulkan; }; -struct Q_GUI_EXPORT QRhiVulkanNativeHandles : public QRhiNativeHandles +Q_DECLARE_TYPEINFO(QVkBuffer::DynamicUpdate, Q_RELOCATABLE_TYPE); + +struct QVkTexture; + +struct QVkRenderBuffer : public QRhiRenderBuffer { - // to import a physical device (always required) + QVkRenderBuffer(QRhiImplementation *rhi, Type type, const QSize &pixelSize, + int sampleCount, Flags flags, + QRhiTexture::Format backingFormatHint); + ~QVkRenderBuffer(); + void destroy() override; + bool create() override; + QRhiTexture::Format backingFormat() const override; + + VkDeviceMemory memory = VK_NULL_HANDLE; + VkImage image = VK_NULL_HANDLE; + VkImageView imageView = VK_NULL_HANDLE; + VkSampleCountFlagBits samples; + QVkTexture *backingTexture = nullptr; + VkFormat vkformat; + int lastActiveFrameSlot = -1; + uint generation = 0; + friend class QRhiVulkan; +}; + +struct QVkTexture : public QRhiTexture +{ + QVkTexture(QRhiImplementation *rhi, Format format, const QSize &pixelSize, int depth, + int arraySize, int sampleCount, Flags flags); + ~QVkTexture(); + void destroy() override; + bool create() override; + bool createFrom(NativeTexture src) override; + NativeTexture nativeTexture() override; + void setNativeLayout(int layout) override; + + bool prepareCreate(QSize *adjustedSize = nullptr); + bool finishCreate(); + VkImageView imageViewForLevel(int level); + + VkImage image = VK_NULL_HANDLE; + VkImageView imageView = VK_NULL_HANDLE; + QVkAlloc imageAlloc = nullptr; + VkBuffer stagingBuffers[QVK_FRAMES_IN_FLIGHT]; + QVkAlloc stagingAllocations[QVK_FRAMES_IN_FLIGHT]; + VkImageView perLevelImageViews[QRhi::MAX_MIP_LEVELS]; + bool owns = true; + struct UsageState { + // no tracking of subresource layouts (some operations can keep + // subresources in different layouts for some time, but that does not + // need to be kept track of) + VkImageLayout layout; + VkAccessFlags access; + VkPipelineStageFlags stage; + }; + UsageState usageState; + VkFormat vkformat; + uint mipLevelCount = 0; + VkSampleCountFlagBits samples; + int lastActiveFrameSlot = -1; + uint generation = 0; + friend class QRhiVulkan; +}; + +struct QVkSampler : public QRhiSampler +{ + QVkSampler(QRhiImplementation *rhi, Filter magFilter, Filter minFilter, Filter mipmapMode, + AddressMode u, AddressMode v, AddressMode w); + ~QVkSampler(); + void destroy() override; + bool create() override; + + VkSampler sampler = VK_NULL_HANDLE; + int lastActiveFrameSlot = -1; + uint generation = 0; + friend class QRhiVulkan; +}; + +struct QVkRenderPassDescriptor : public QRhiRenderPassDescriptor +{ + QVkRenderPassDescriptor(QRhiImplementation *rhi); + ~QVkRenderPassDescriptor(); + void destroy() override; + bool isCompatible(const QRhiRenderPassDescriptor *other) const override; + QRhiRenderPassDescriptor *newCompatibleRenderPassDescriptor() const override; + QVector serializedFormat() const override; + const QRhiNativeHandles *nativeHandles() override; + + void updateSerializedFormat(); + + VkRenderPass rp = VK_NULL_HANDLE; + bool ownsRp = false; + QVarLengthArray attDescs; + QVarLengthArray colorRefs; + QVarLengthArray resolveRefs; + QVarLengthArray subpassDeps; + bool hasDepthStencil = false; + VkAttachmentReference dsRef; + QVector serializedFormatData; + QRhiVulkanRenderPassNativeHandles nativeHandlesStruct; + int lastActiveFrameSlot = -1; +}; + +struct QVkRenderTargetData +{ + VkFramebuffer fb = VK_NULL_HANDLE; + QVkRenderPassDescriptor *rp = nullptr; + QSize pixelSize; + float dpr = 1; + int sampleCount = 1; + int colorAttCount = 0; + int dsAttCount = 0; + int resolveAttCount = 0; + QRhiRenderTargetAttachmentTracker::ResIdList currentResIdList; + static const int MAX_COLOR_ATTACHMENTS = 8; +}; + +struct QVkSwapChainRenderTarget : public QRhiSwapChainRenderTarget +{ + QVkSwapChainRenderTarget(QRhiImplementation *rhi, QRhiSwapChain *swapchain); + ~QVkSwapChainRenderTarget(); + void destroy() override; + + QSize pixelSize() const override; + float devicePixelRatio() const override; + int sampleCount() const override; + + QVkRenderTargetData d; +}; + +struct QVkTextureRenderTarget : public QRhiTextureRenderTarget +{ + QVkTextureRenderTarget(QRhiImplementation *rhi, const QRhiTextureRenderTargetDescription &desc, Flags flags); + ~QVkTextureRenderTarget(); + void destroy() override; + + QSize pixelSize() const override; + float devicePixelRatio() const override; + int sampleCount() const override; + + QRhiRenderPassDescriptor *newCompatibleRenderPassDescriptor() override; + bool create() override; + + QVkRenderTargetData d; + VkImageView rtv[QVkRenderTargetData::MAX_COLOR_ATTACHMENTS]; + VkImageView resrtv[QVkRenderTargetData::MAX_COLOR_ATTACHMENTS]; + int lastActiveFrameSlot = -1; + friend class QRhiVulkan; +}; + +struct QVkShaderResourceBindings : public QRhiShaderResourceBindings +{ + QVkShaderResourceBindings(QRhiImplementation *rhi); + ~QVkShaderResourceBindings(); + void destroy() override; + bool create() override; + void updateResources(UpdateFlags flags) override; + + QVarLengthArray sortedBindings; + bool hasSlottedResource = false; + bool hasDynamicOffset = false; + int poolIndex = -1; + VkDescriptorSetLayout layout = VK_NULL_HANDLE; + VkDescriptorSet descSets[QVK_FRAMES_IN_FLIGHT]; // multiple sets to support dynamic buffers + int lastActiveFrameSlot = -1; + uint generation = 0; + + // Keep track of the generation number of each referenced QRhi* to be able + // to detect that the underlying descriptor set became out of date and they + // need to be written again with the up-to-date VkBuffer etc. objects. + struct BoundUniformBufferData { + quint64 id; + uint generation; + }; + struct BoundSampledTextureData { + int count; + struct { + quint64 texId; + uint texGeneration; + quint64 samplerId; + uint samplerGeneration; + } d[QRhiShaderResourceBinding::Data::MAX_TEX_SAMPLER_ARRAY_SIZE]; + }; + struct BoundStorageImageData { + quint64 id; + uint generation; + }; + struct BoundStorageBufferData { + quint64 id; + uint generation; + }; + struct BoundResourceData { + union { + BoundUniformBufferData ubuf; + BoundSampledTextureData stex; + BoundStorageImageData simage; + BoundStorageBufferData sbuf; + }; + }; + QVarLengthArray boundResourceData[QVK_FRAMES_IN_FLIGHT]; + + friend class QRhiVulkan; +}; + +Q_DECLARE_TYPEINFO(QVkShaderResourceBindings::BoundResourceData, Q_RELOCATABLE_TYPE); + +struct QVkGraphicsPipeline : public QRhiGraphicsPipeline +{ + QVkGraphicsPipeline(QRhiImplementation *rhi); + ~QVkGraphicsPipeline(); + void destroy() override; + bool create() override; + + VkPipelineLayout layout = VK_NULL_HANDLE; + VkPipeline pipeline = VK_NULL_HANDLE; + int lastActiveFrameSlot = -1; + uint generation = 0; + friend class QRhiVulkan; +}; + +struct QVkComputePipeline : public QRhiComputePipeline +{ + QVkComputePipeline(QRhiImplementation *rhi); + ~QVkComputePipeline(); + void destroy() override; + bool create() override; + + VkPipelineLayout layout = VK_NULL_HANDLE; + VkPipeline pipeline = VK_NULL_HANDLE; + int lastActiveFrameSlot = -1; + uint generation = 0; + friend class QRhiVulkan; +}; + +struct QVkCommandBuffer : public QRhiCommandBuffer +{ + QVkCommandBuffer(QRhiImplementation *rhi); + ~QVkCommandBuffer(); + void destroy() override; + + const QRhiNativeHandles *nativeHandles(); + + VkCommandBuffer cb = VK_NULL_HANDLE; // primary + QRhiVulkanCommandBufferNativeHandles nativeHandlesStruct; + + enum PassType { + NoPass, + RenderPass, + ComputePass + }; + + void resetState() { + recordingPass = NoPass; + passUsesSecondaryCb = false; + lastGpuTime = 0; + currentTarget = nullptr; + activeSecondaryCbStack.clear(); + resetCommands(); + resetCachedState(); + } + + void resetCachedState() { + currentGraphicsPipeline = nullptr; + currentComputePipeline = nullptr; + currentPipelineGeneration = 0; + currentGraphicsSrb = nullptr; + currentComputeSrb = nullptr; + currentSrbGeneration = 0; + currentDescSetSlot = -1; + currentIndexBuffer = VK_NULL_HANDLE; + currentIndexOffset = 0; + currentIndexFormat = VK_INDEX_TYPE_UINT16; + memset(currentVertexBuffers, 0, sizeof(currentVertexBuffers)); + memset(currentVertexOffsets, 0, sizeof(currentVertexOffsets)); + inExternal = false; + } + + PassType recordingPass; + bool passUsesSecondaryCb; + double lastGpuTime = 0; + QRhiRenderTarget *currentTarget; + QRhiGraphicsPipeline *currentGraphicsPipeline; + QRhiComputePipeline *currentComputePipeline; + uint currentPipelineGeneration; + QRhiShaderResourceBindings *currentGraphicsSrb; + QRhiShaderResourceBindings *currentComputeSrb; + uint currentSrbGeneration; + int currentDescSetSlot; + VkBuffer currentIndexBuffer; + quint32 currentIndexOffset; + VkIndexType currentIndexFormat; + static const int VERTEX_INPUT_RESOURCE_SLOT_COUNT = 32; + VkBuffer currentVertexBuffers[VERTEX_INPUT_RESOURCE_SLOT_COUNT]; + quint32 currentVertexOffsets[VERTEX_INPUT_RESOURCE_SLOT_COUNT]; + QVarLengthArray activeSecondaryCbStack; + bool inExternal; + + struct { + QHash > writtenResources; + void reset() { + writtenResources.clear(); + } + } computePassState; + + struct Command { + enum Cmd { + CopyBuffer, + CopyBufferToImage, + CopyImage, + CopyImageToBuffer, + ImageBarrier, + BufferBarrier, + BlitImage, + BeginRenderPass, + EndRenderPass, + BindPipeline, + BindDescriptorSet, + BindVertexBuffer, + BindIndexBuffer, + SetViewport, + SetScissor, + SetBlendConstants, + SetStencilRef, + Draw, + DrawIndexed, + DebugMarkerBegin, + DebugMarkerEnd, + DebugMarkerInsert, + TransitionPassResources, + Dispatch, + ExecuteSecondary + }; + Cmd cmd; + + union Args { + struct { + VkBuffer src; + VkBuffer dst; + VkBufferCopy desc; + } copyBuffer; + struct { + VkBuffer src; + VkImage dst; + VkImageLayout dstLayout; + int count; + int bufferImageCopyIndex; + } copyBufferToImage; + struct { + VkImage src; + VkImageLayout srcLayout; + VkImage dst; + VkImageLayout dstLayout; + VkImageCopy desc; + } copyImage; + struct { + VkImage src; + VkImageLayout srcLayout; + VkBuffer dst; + VkBufferImageCopy desc; + } copyImageToBuffer; + struct { + VkPipelineStageFlags srcStageMask; + VkPipelineStageFlags dstStageMask; + int count; + int index; + } imageBarrier; + struct { + VkPipelineStageFlags srcStageMask; + VkPipelineStageFlags dstStageMask; + int count; + int index; + } bufferBarrier; + struct { + VkImage src; + VkImageLayout srcLayout; + VkImage dst; + VkImageLayout dstLayout; + VkFilter filter; + VkImageBlit desc; + } blitImage; + struct { + VkRenderPassBeginInfo desc; + int clearValueIndex; + bool useSecondaryCb; + } beginRenderPass; + struct { + } endRenderPass; + struct { + VkPipelineBindPoint bindPoint; + VkPipeline pipeline; + } bindPipeline; + struct { + VkPipelineBindPoint bindPoint; + VkPipelineLayout pipelineLayout; + VkDescriptorSet descSet; + int dynamicOffsetCount; + int dynamicOffsetIndex; + } bindDescriptorSet; + struct { + int startBinding; + int count; + int vertexBufferIndex; + int vertexBufferOffsetIndex; + } bindVertexBuffer; + struct { + VkBuffer buf; + VkDeviceSize ofs; + VkIndexType type; + } bindIndexBuffer; + struct { + VkViewport viewport; + } setViewport; + struct { + VkRect2D scissor; + } setScissor; + struct { + float c[4]; + } setBlendConstants; + struct { + uint32_t ref; + } setStencilRef; + struct { + uint32_t vertexCount; + uint32_t instanceCount; + uint32_t firstVertex; + uint32_t firstInstance; + } draw; + struct { + uint32_t indexCount; + uint32_t instanceCount; + uint32_t firstIndex; + int32_t vertexOffset; + uint32_t firstInstance; + } drawIndexed; + struct { +#ifdef VK_EXT_debug_utils + VkDebugUtilsLabelEXT label; + int labelNameIndex; +#endif + } debugMarkerBegin; + struct { + } debugMarkerEnd; + struct { +#ifdef VK_EXT_debug_utils + VkDebugUtilsLabelEXT label; + int labelNameIndex; +#endif + } debugMarkerInsert; + struct { + int trackerIndex; + } transitionResources; + struct { + int x, y, z; + } dispatch; + struct { + VkCommandBuffer cb; + } executeSecondary; + } args; + }; + + QRhiBackendCommandList commands; + QVarLengthArray passResTrackers; + int currentPassResTrackerIndex; + + void resetCommands() { + commands.reset(); + resetPools(); + + passResTrackers.clear(); + currentPassResTrackerIndex = -1; + } + + void resetPools() { + pools.clearValue.clear(); + pools.bufferImageCopy.clear(); + pools.dynamicOffset.clear(); + pools.vertexBuffer.clear(); + pools.vertexBufferOffset.clear(); + pools.debugMarkerData.clear(); + pools.imageBarrier.clear(); + pools.bufferBarrier.clear(); + } + + struct { + QVarLengthArray clearValue; + QVarLengthArray bufferImageCopy; + QVarLengthArray dynamicOffset; + QVarLengthArray vertexBuffer; + QVarLengthArray vertexBufferOffset; + QVarLengthArray debugMarkerData; + QVarLengthArray imageBarrier; + QVarLengthArray bufferBarrier; + } pools; + + friend class QRhiVulkan; +}; + +struct QVkSwapChain : public QRhiSwapChain +{ + QVkSwapChain(QRhiImplementation *rhi); + ~QVkSwapChain(); + void destroy() override; + + QRhiCommandBuffer *currentFrameCommandBuffer() override; + QRhiRenderTarget *currentFrameRenderTarget() override; + + QSize surfacePixelSize() override; + bool isFormatSupported(Format f) override; + + QRhiRenderPassDescriptor *newCompatibleRenderPassDescriptor() override; + bool createOrResize() override; + + bool ensureSurface(); + + static const quint32 EXPECTED_MAX_BUFFER_COUNT = 4; + + QWindow *window = nullptr; + QSize pixelSize; + bool supportsReadback = false; + VkSwapchainKHR sc = VK_NULL_HANDLE; + int bufferCount = 0; + VkSurfaceKHR surface = VK_NULL_HANDLE; + VkSurfaceKHR lastConnectedSurface = VK_NULL_HANDLE; + VkFormat colorFormat = VK_FORMAT_B8G8R8A8_UNORM; + VkColorSpaceKHR colorSpace = VK_COLOR_SPACE_SRGB_NONLINEAR_KHR; + QVkRenderBuffer *ds = nullptr; + VkSampleCountFlagBits samples = VK_SAMPLE_COUNT_1_BIT; + QVarLengthArray supportedPresentationModes; + VkDeviceMemory msaaImageMem = VK_NULL_HANDLE; + QVkSwapChainRenderTarget rtWrapper; + QVkCommandBuffer cbWrapper; + + struct ImageResources { + VkImage image = VK_NULL_HANDLE; + VkImageView imageView = VK_NULL_HANDLE; + VkFramebuffer fb = VK_NULL_HANDLE; + VkImage msaaImage = VK_NULL_HANDLE; + VkImageView msaaImageView = VK_NULL_HANDLE; + enum LastUse { + ScImageUseNone, + ScImageUseRender, + ScImageUseTransferSource + }; + LastUse lastUse = ScImageUseNone; + }; + QVarLengthArray imageRes; + + struct FrameResources { + VkFence imageFence = VK_NULL_HANDLE; + bool imageFenceWaitable = false; + VkSemaphore imageSem = VK_NULL_HANDLE; + VkSemaphore drawSem = VK_NULL_HANDLE; + bool imageAcquired = false; + bool imageSemWaitable = false; + VkFence cmdFence = VK_NULL_HANDLE; + bool cmdFenceWaitable = false; + VkCommandBuffer cmdBuf = VK_NULL_HANDLE; // primary + int timestampQueryIndex = -1; + } frameRes[QVK_FRAMES_IN_FLIGHT]; + + quint32 currentImageIndex = 0; // index in imageRes + quint32 currentFrameSlot = 0; // index in frameRes + int frameCount = 0; + + friend class QRhiVulkan; +}; + +class QRhiVulkan : public QRhiImplementation +{ +public: + QRhiVulkan(QRhiVulkanInitParams *params, QRhiVulkanNativeHandles *importParams = nullptr); + + bool create(QRhi::Flags flags) override; + void destroy() override; + + QRhiGraphicsPipeline *createGraphicsPipeline() override; + QRhiComputePipeline *createComputePipeline() override; + QRhiShaderResourceBindings *createShaderResourceBindings() override; + QRhiBuffer *createBuffer(QRhiBuffer::Type type, + QRhiBuffer::UsageFlags usage, + quint32 size) override; + QRhiRenderBuffer *createRenderBuffer(QRhiRenderBuffer::Type type, + const QSize &pixelSize, + int sampleCount, + QRhiRenderBuffer::Flags flags, + QRhiTexture::Format backingFormatHint) override; + QRhiTexture *createTexture(QRhiTexture::Format format, + const QSize &pixelSize, + int depth, + int arraySize, + int sampleCount, + QRhiTexture::Flags flags) override; + QRhiSampler *createSampler(QRhiSampler::Filter magFilter, + QRhiSampler::Filter minFilter, + QRhiSampler::Filter mipmapMode, + QRhiSampler:: AddressMode u, + QRhiSampler::AddressMode v, + QRhiSampler::AddressMode w) override; + + QRhiTextureRenderTarget *createTextureRenderTarget(const QRhiTextureRenderTargetDescription &desc, + QRhiTextureRenderTarget::Flags flags) override; + + QRhiSwapChain *createSwapChain() override; + QRhi::FrameOpResult beginFrame(QRhiSwapChain *swapChain, QRhi::BeginFrameFlags flags) override; + QRhi::FrameOpResult endFrame(QRhiSwapChain *swapChain, QRhi::EndFrameFlags flags) override; + QRhi::FrameOpResult beginOffscreenFrame(QRhiCommandBuffer **cb, QRhi::BeginFrameFlags flags) override; + QRhi::FrameOpResult endOffscreenFrame(QRhi::EndFrameFlags flags) override; + QRhi::FrameOpResult finish() override; + + void resourceUpdate(QRhiCommandBuffer *cb, QRhiResourceUpdateBatch *resourceUpdates) override; + + void beginPass(QRhiCommandBuffer *cb, + QRhiRenderTarget *rt, + const QColor &colorClearValue, + const QRhiDepthStencilClearValue &depthStencilClearValue, + QRhiResourceUpdateBatch *resourceUpdates, + QRhiCommandBuffer::BeginPassFlags flags) override; + void endPass(QRhiCommandBuffer *cb, QRhiResourceUpdateBatch *resourceUpdates) override; + + void setGraphicsPipeline(QRhiCommandBuffer *cb, + QRhiGraphicsPipeline *ps) override; + + void setShaderResources(QRhiCommandBuffer *cb, + QRhiShaderResourceBindings *srb, + int dynamicOffsetCount, + const QRhiCommandBuffer::DynamicOffset *dynamicOffsets) override; + + void setVertexInput(QRhiCommandBuffer *cb, + int startBinding, int bindingCount, const QRhiCommandBuffer::VertexInput *bindings, + QRhiBuffer *indexBuf, quint32 indexOffset, + QRhiCommandBuffer::IndexFormat indexFormat) override; + + void setViewport(QRhiCommandBuffer *cb, const QRhiViewport &viewport) override; + void setScissor(QRhiCommandBuffer *cb, const QRhiScissor &scissor) override; + void setBlendConstants(QRhiCommandBuffer *cb, const QColor &c) override; + void setStencilRef(QRhiCommandBuffer *cb, quint32 refValue) override; + + void draw(QRhiCommandBuffer *cb, quint32 vertexCount, + quint32 instanceCount, quint32 firstVertex, quint32 firstInstance) override; + + void drawIndexed(QRhiCommandBuffer *cb, quint32 indexCount, + quint32 instanceCount, quint32 firstIndex, + qint32 vertexOffset, quint32 firstInstance) override; + + void debugMarkBegin(QRhiCommandBuffer *cb, const QByteArray &name) override; + void debugMarkEnd(QRhiCommandBuffer *cb) override; + void debugMarkMsg(QRhiCommandBuffer *cb, const QByteArray &msg) override; + + void beginComputePass(QRhiCommandBuffer *cb, + QRhiResourceUpdateBatch *resourceUpdates, + QRhiCommandBuffer::BeginPassFlags flags) override; + void endComputePass(QRhiCommandBuffer *cb, QRhiResourceUpdateBatch *resourceUpdates) override; + void setComputePipeline(QRhiCommandBuffer *cb, QRhiComputePipeline *ps) override; + void dispatch(QRhiCommandBuffer *cb, int x, int y, int z) override; + + const QRhiNativeHandles *nativeHandles(QRhiCommandBuffer *cb) override; + void beginExternal(QRhiCommandBuffer *cb) override; + void endExternal(QRhiCommandBuffer *cb) override; + double lastCompletedGpuTime(QRhiCommandBuffer *cb) override; + + QList supportedSampleCounts() const override; + int ubufAlignment() const override; + bool isYUpInFramebuffer() const override; + bool isYUpInNDC() const override; + bool isClipDepthZeroToOne() const override; + QMatrix4x4 clipSpaceCorrMatrix() const override; + bool isTextureFormatSupported(QRhiTexture::Format format, QRhiTexture::Flags flags) const override; + bool isFeatureSupported(QRhi::Feature feature) const override; + int resourceLimit(QRhi::ResourceLimit limit) const override; + const QRhiNativeHandles *nativeHandles() override; + QRhiDriverInfo driverInfo() const override; + QRhiStats statistics() override; + bool makeThreadLocalNativeContextCurrent() override; + void releaseCachedResources() override; + bool isDeviceLost() const override; + + QByteArray pipelineCacheData() override; + void setPipelineCacheData(const QByteArray &data) override; + + VkResult createDescriptorPool(VkDescriptorPool *pool); + bool allocateDescriptorSet(VkDescriptorSetAllocateInfo *allocInfo, VkDescriptorSet *result, int *resultPoolIndex); + uint32_t chooseTransientImageMemType(VkImage img, uint32_t startIndex); + bool createTransientImage(VkFormat format, const QSize &pixelSize, VkImageUsageFlags usage, + VkImageAspectFlags aspectMask, VkSampleCountFlagBits samples, + VkDeviceMemory *mem, VkImage *images, VkImageView *views, int count); + + bool recreateSwapChain(QRhiSwapChain *swapChain); + void releaseSwapChainResources(QRhiSwapChain *swapChain); + + VkFormat optimalDepthStencilFormat(); + VkSampleCountFlagBits effectiveSampleCount(int sampleCount); + bool createDefaultRenderPass(QVkRenderPassDescriptor *rpD, + bool hasDepthStencil, + VkSampleCountFlagBits samples, + VkFormat colorFormat); + bool createOffscreenRenderPass(QVkRenderPassDescriptor *rpD, + const QRhiColorAttachment *firstColorAttachment, + const QRhiColorAttachment *lastColorAttachment, + bool preserveColor, + bool preserveDs, + QRhiRenderBuffer *depthStencilBuffer, + QRhiTexture *depthTexture); + bool ensurePipelineCache(const void *initialData = nullptr, size_t initialDataSize = 0); + VkShaderModule createShader(const QByteArray &spirv); + + void prepareNewFrame(QRhiCommandBuffer *cb); + VkCommandBuffer startSecondaryCommandBuffer(QVkRenderTargetData *rtD = nullptr); + void endAndEnqueueSecondaryCommandBuffer(VkCommandBuffer cb, QVkCommandBuffer *cbD); + QRhi::FrameOpResult startPrimaryCommandBuffer(VkCommandBuffer *cb); + QRhi::FrameOpResult endAndSubmitPrimaryCommandBuffer(VkCommandBuffer cb, VkFence cmdFence, + VkSemaphore *waitSem, VkSemaphore *signalSem); + void waitCommandCompletion(int frameSlot); + VkDeviceSize subresUploadByteSize(const QRhiTextureSubresourceUploadDescription &subresDesc) const; + using BufferImageCopyList = QVarLengthArray; + void prepareUploadSubres(QVkTexture *texD, int layer, int level, + const QRhiTextureSubresourceUploadDescription &subresDesc, + size_t *curOfs, void *mp, + BufferImageCopyList *copyInfos); + void enqueueResourceUpdates(QVkCommandBuffer *cbD, QRhiResourceUpdateBatch *resourceUpdates); + void executeBufferHostWritesForSlot(QVkBuffer *bufD, int slot); + void enqueueTransitionPassResources(QVkCommandBuffer *cbD); + void recordPrimaryCommandBuffer(QVkCommandBuffer *cbD); + void trackedRegisterBuffer(QRhiPassResourceTracker *passResTracker, + QVkBuffer *bufD, + int slot, + QRhiPassResourceTracker::BufferAccess access, + QRhiPassResourceTracker::BufferStage stage); + void trackedRegisterTexture(QRhiPassResourceTracker *passResTracker, + QVkTexture *texD, + QRhiPassResourceTracker::TextureAccess access, + QRhiPassResourceTracker::TextureStage stage); + void recordTransitionPassResources(QVkCommandBuffer *cbD, const QRhiPassResourceTracker &tracker); + void activateTextureRenderTarget(QVkCommandBuffer *cbD, QVkTextureRenderTarget *rtD); + void executeDeferredReleases(bool forced = false); + void finishActiveReadbacks(bool forced = false); + + void setObjectName(uint64_t object, VkObjectType type, const QByteArray &name, int slot = -1); + void trackedBufferBarrier(QVkCommandBuffer *cbD, QVkBuffer *bufD, int slot, + VkAccessFlags access, VkPipelineStageFlags stage); + void trackedImageBarrier(QVkCommandBuffer *cbD, QVkTexture *texD, + VkImageLayout layout, VkAccessFlags access, VkPipelineStageFlags stage); + void depthStencilExplicitBarrier(QVkCommandBuffer *cbD, QVkRenderBuffer *rbD); + void subresourceBarrier(QVkCommandBuffer *cbD, VkImage image, + VkImageLayout oldLayout, VkImageLayout newLayout, + VkAccessFlags srcAccess, VkAccessFlags dstAccess, + VkPipelineStageFlags srcStage, VkPipelineStageFlags dstStage, + int startLayer, int layerCount, + int startLevel, int levelCount); + void updateShaderResourceBindings(QRhiShaderResourceBindings *srb, int descSetIdx = -1); + void ensureCommandPoolForNewFrame(); + double elapsedSecondsFromTimestamp(quint64 timestamp[2], bool *ok); + + QVulkanInstance *inst = nullptr; + QWindow *maybeWindow = nullptr; + QByteArrayList requestedDeviceExtensions; + bool importedDevice = false; VkPhysicalDevice physDev = VK_NULL_HANDLE; - // to import a device and queue VkDevice dev = VK_NULL_HANDLE; + VkCommandPool cmdPool[QVK_FRAMES_IN_FLIGHT] = {}; quint32 gfxQueueFamilyIdx = 0; quint32 gfxQueueIdx = 0; VkQueue gfxQueue = VK_NULL_HANDLE; - // and optionally, the mem allocator - void *vmemAllocator = nullptr; + quint32 timestampValidBits = 0; + bool importedAllocator = false; + QVkAllocator allocator = nullptr; + QVulkanFunctions *f = nullptr; + QVulkanDeviceFunctions *df = nullptr; + QRhi::Flags rhiFlags; + VkPhysicalDeviceFeatures physDevFeatures; +#ifdef VK_VERSION_1_2 + VkPhysicalDeviceVulkan11Features physDevFeatures11; + VkPhysicalDeviceVulkan12Features physDevFeatures12; +#endif +#ifdef VK_VERSION_1_3 + VkPhysicalDeviceVulkan13Features physDevFeatures13; +#endif + VkPhysicalDeviceProperties physDevProperties; + VkDeviceSize ubufAlign; + VkDeviceSize texbufAlign; + bool deviceLost = false; + bool releaseCachedResourcesCalledBeforeFrameStart = false; + +#ifdef VK_EXT_debug_utils + PFN_vkSetDebugUtilsObjectNameEXT vkSetDebugUtilsObjectNameEXT = nullptr; + PFN_vkCmdBeginDebugUtilsLabelEXT vkCmdBeginDebugUtilsLabelEXT = nullptr; + PFN_vkCmdEndDebugUtilsLabelEXT vkCmdEndDebugUtilsLabelEXT = nullptr; + PFN_vkCmdInsertDebugUtilsLabelEXT vkCmdInsertDebugUtilsLabelEXT = nullptr; +#endif + + PFN_vkCreateSwapchainKHR vkCreateSwapchainKHR = nullptr; + PFN_vkDestroySwapchainKHR vkDestroySwapchainKHR; + PFN_vkGetSwapchainImagesKHR vkGetSwapchainImagesKHR; + PFN_vkAcquireNextImageKHR vkAcquireNextImageKHR; + PFN_vkQueuePresentKHR vkQueuePresentKHR; + PFN_vkGetPhysicalDeviceSurfaceCapabilitiesKHR vkGetPhysicalDeviceSurfaceCapabilitiesKHR; + PFN_vkGetPhysicalDeviceSurfaceFormatsKHR vkGetPhysicalDeviceSurfaceFormatsKHR; + PFN_vkGetPhysicalDeviceSurfacePresentModesKHR vkGetPhysicalDeviceSurfacePresentModesKHR; + + struct { + bool compute = false; + bool wideLines = false; + bool debugUtils = false; + bool vertexAttribDivisor = false; + bool texture3DSliceAs2D = false; + bool tessellation = false; + bool geometryShader = false; + bool nonFillPolygonMode = false; + QVersionNumber apiVersion; + } caps; + + VkPipelineCache pipelineCache = VK_NULL_HANDLE; + struct DescriptorPoolData { + DescriptorPoolData() { } + DescriptorPoolData(VkDescriptorPool pool_) + : pool(pool_) + { } + VkDescriptorPool pool = VK_NULL_HANDLE; + int refCount = 0; + int allocedDescSets = 0; + }; + QVarLengthArray descriptorPools; + QVarLengthArray freeSecondaryCbs[QVK_FRAMES_IN_FLIGHT]; + + VkQueryPool timestampQueryPool = VK_NULL_HANDLE; + QBitArray timestampQueryPoolMap; + + VkFormat optimalDsFormat = VK_FORMAT_UNDEFINED; + QMatrix4x4 clipCorrectMatrix; + + QVkSwapChain *currentSwapChain = nullptr; + QSet swapchains; + QRhiVulkanNativeHandles nativeHandlesStruct; + QRhiDriverInfo driverInfoStruct; + + struct OffscreenFrame { + OffscreenFrame(QRhiImplementation *rhi) + { + for (int i = 0; i < QVK_FRAMES_IN_FLIGHT; ++i) + cbWrapper[i] = new QVkCommandBuffer(rhi); + } + ~OffscreenFrame() + { + for (int i = 0; i < QVK_FRAMES_IN_FLIGHT; ++i) + delete cbWrapper[i]; + } + bool active = false; + QVkCommandBuffer *cbWrapper[QVK_FRAMES_IN_FLIGHT]; + VkFence cmdFence = VK_NULL_HANDLE; + int timestampQueryIndex = -1; + } ofr; + + struct TextureReadback { + int activeFrameSlot = -1; + QRhiReadbackDescription desc; + QRhiReadbackResult *result; + VkBuffer stagingBuf; + QVkAlloc stagingAlloc; + quint32 byteSize; + QSize pixelSize; + QRhiTexture::Format format; + }; + QVarLengthArray activeTextureReadbacks; + struct BufferReadback { + int activeFrameSlot = -1; + QRhiReadbackResult *result; + quint32 byteSize; + VkBuffer stagingBuf; + QVkAlloc stagingAlloc; + }; + QVarLengthArray activeBufferReadbacks; + + struct DeferredReleaseEntry { + enum Type { + Pipeline, + ShaderResourceBindings, + Buffer, + RenderBuffer, + Texture, + Sampler, + TextureRenderTarget, + RenderPass, + StagingBuffer, + SecondaryCommandBuffer + }; + Type type; + int lastActiveFrameSlot; // -1 if not used otherwise 0..FRAMES_IN_FLIGHT-1 + union { + struct { + VkPipeline pipeline; + VkPipelineLayout layout; + } pipelineState; + struct { + int poolIndex; + VkDescriptorSetLayout layout; + } shaderResourceBindings; + struct { + VkBuffer buffers[QVK_FRAMES_IN_FLIGHT]; + QVkAlloc allocations[QVK_FRAMES_IN_FLIGHT]; + VkBuffer stagingBuffers[QVK_FRAMES_IN_FLIGHT]; + QVkAlloc stagingAllocations[QVK_FRAMES_IN_FLIGHT]; + } buffer; + struct { + VkDeviceMemory memory; + VkImage image; + VkImageView imageView; + } renderBuffer; + struct { + VkImage image; + VkImageView imageView; + QVkAlloc allocation; + VkBuffer stagingBuffers[QVK_FRAMES_IN_FLIGHT]; + QVkAlloc stagingAllocations[QVK_FRAMES_IN_FLIGHT]; + VkImageView extraImageViews[QRhi::MAX_MIP_LEVELS]; + } texture; + struct { + VkSampler sampler; + } sampler; + struct { + VkFramebuffer fb; + VkImageView rtv[QVkRenderTargetData::MAX_COLOR_ATTACHMENTS]; + VkImageView resrtv[QVkRenderTargetData::MAX_COLOR_ATTACHMENTS]; + } textureRenderTarget; + struct { + VkRenderPass rp; + } renderPass; + struct { + VkBuffer stagingBuffer; + QVkAlloc stagingAllocation; + } stagingBuffer; + struct { + VkCommandBuffer cb; + } secondaryCommandBuffer; + }; + }; + QList releaseQueue; }; -struct Q_GUI_EXPORT QRhiVulkanCommandBufferNativeHandles : public QRhiNativeHandles -{ - VkCommandBuffer commandBuffer = VK_NULL_HANDLE; -}; - -struct Q_GUI_EXPORT QRhiVulkanRenderPassNativeHandles : public QRhiNativeHandles -{ - VkRenderPass renderPass = VK_NULL_HANDLE; -}; +Q_DECLARE_TYPEINFO(QRhiVulkan::DescriptorPoolData, Q_RELOCATABLE_TYPE); +Q_DECLARE_TYPEINFO(QRhiVulkan::DeferredReleaseEntry, Q_RELOCATABLE_TYPE); +Q_DECLARE_TYPEINFO(QRhiVulkan::TextureReadback, Q_RELOCATABLE_TYPE); +Q_DECLARE_TYPEINFO(QRhiVulkan::BufferReadback, Q_RELOCATABLE_TYPE); QT_END_NAMESPACE diff --git a/src/gui/rhi/qrhivulkan_p_p.h b/src/gui/rhi/qrhivulkan_p_p.h deleted file mode 100644 index 22214723..00000000 --- a/src/gui/rhi/qrhivulkan_p_p.h +++ /dev/null @@ -1,1001 +0,0 @@ -// Copyright (C) 2019 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 QRHIVULKAN_P_H -#define QRHIVULKAN_P_H - -// -// W A R N I N G -// ------------- -// -// This file is not part of the Qt API. It exists purely as an -// implementation detail. This header file may change from version to -// version without notice, or even be removed. -// -// We mean it. -// - -#include "qrhivulkan_p.h" -#include "qrhi_p_p.h" - -QT_BEGIN_NAMESPACE - -class QVulkanFunctions; -class QVulkanDeviceFunctions; - -static const int QVK_FRAMES_IN_FLIGHT = 2; - -static const int QVK_DESC_SETS_PER_POOL = 128; -static const int QVK_UNIFORM_BUFFERS_PER_POOL = 256; -static const int QVK_COMBINED_IMAGE_SAMPLERS_PER_POOL = 256; -static const int QVK_STORAGE_BUFFERS_PER_POOL = 128; -static const int QVK_STORAGE_IMAGES_PER_POOL = 128; - -static const int QVK_MAX_ACTIVE_TIMESTAMP_PAIRS = 16; - -// no vk_mem_alloc.h available here, void* is good enough -typedef void * QVkAlloc; -typedef void * QVkAllocator; - -struct QVkBuffer : public QRhiBuffer -{ - QVkBuffer(QRhiImplementation *rhi, Type type, UsageFlags usage, quint32 size); - ~QVkBuffer(); - void destroy() override; - bool create() override; - QRhiBuffer::NativeBuffer nativeBuffer() override; - char *beginFullDynamicBufferUpdateForCurrentFrame() override; - void endFullDynamicBufferUpdateForCurrentFrame() override; - - VkBuffer buffers[QVK_FRAMES_IN_FLIGHT]; - QVkAlloc allocations[QVK_FRAMES_IN_FLIGHT]; - struct DynamicUpdate { - quint32 offset; - QRhiBufferData data; - }; - QVarLengthArray pendingDynamicUpdates[QVK_FRAMES_IN_FLIGHT]; - VkBuffer stagingBuffers[QVK_FRAMES_IN_FLIGHT]; - QVkAlloc stagingAllocations[QVK_FRAMES_IN_FLIGHT]; - struct UsageState { - VkAccessFlags access = 0; - VkPipelineStageFlags stage = 0; - }; - UsageState usageState[QVK_FRAMES_IN_FLIGHT]; - int lastActiveFrameSlot = -1; - uint generation = 0; - friend class QRhiVulkan; -}; - -Q_DECLARE_TYPEINFO(QVkBuffer::DynamicUpdate, Q_RELOCATABLE_TYPE); - -struct QVkTexture; - -struct QVkRenderBuffer : public QRhiRenderBuffer -{ - QVkRenderBuffer(QRhiImplementation *rhi, Type type, const QSize &pixelSize, - int sampleCount, Flags flags, - QRhiTexture::Format backingFormatHint); - ~QVkRenderBuffer(); - void destroy() override; - bool create() override; - QRhiTexture::Format backingFormat() const override; - - VkDeviceMemory memory = VK_NULL_HANDLE; - VkImage image = VK_NULL_HANDLE; - VkImageView imageView = VK_NULL_HANDLE; - VkSampleCountFlagBits samples; - QVkTexture *backingTexture = nullptr; - VkFormat vkformat; - int lastActiveFrameSlot = -1; - uint generation = 0; - friend class QRhiVulkan; -}; - -struct QVkTexture : public QRhiTexture -{ - QVkTexture(QRhiImplementation *rhi, Format format, const QSize &pixelSize, int depth, - int arraySize, int sampleCount, Flags flags); - ~QVkTexture(); - void destroy() override; - bool create() override; - bool createFrom(NativeTexture src) override; - NativeTexture nativeTexture() override; - void setNativeLayout(int layout) override; - - bool prepareCreate(QSize *adjustedSize = nullptr); - bool finishCreate(); - VkImageView imageViewForLevel(int level); - - VkImage image = VK_NULL_HANDLE; - VkImageView imageView = VK_NULL_HANDLE; - QVkAlloc imageAlloc = nullptr; - VkBuffer stagingBuffers[QVK_FRAMES_IN_FLIGHT]; - QVkAlloc stagingAllocations[QVK_FRAMES_IN_FLIGHT]; - VkImageView perLevelImageViews[QRhi::MAX_MIP_LEVELS]; - bool owns = true; - struct UsageState { - // no tracking of subresource layouts (some operations can keep - // subresources in different layouts for some time, but that does not - // need to be kept track of) - VkImageLayout layout; - VkAccessFlags access; - VkPipelineStageFlags stage; - }; - UsageState usageState; - VkFormat vkformat; - uint mipLevelCount = 0; - VkSampleCountFlagBits samples; - int lastActiveFrameSlot = -1; - uint generation = 0; - friend class QRhiVulkan; -}; - -struct QVkSampler : public QRhiSampler -{ - QVkSampler(QRhiImplementation *rhi, Filter magFilter, Filter minFilter, Filter mipmapMode, - AddressMode u, AddressMode v, AddressMode w); - ~QVkSampler(); - void destroy() override; - bool create() override; - - VkSampler sampler = VK_NULL_HANDLE; - int lastActiveFrameSlot = -1; - uint generation = 0; - friend class QRhiVulkan; -}; - -struct QVkRenderPassDescriptor : public QRhiRenderPassDescriptor -{ - QVkRenderPassDescriptor(QRhiImplementation *rhi); - ~QVkRenderPassDescriptor(); - void destroy() override; - bool isCompatible(const QRhiRenderPassDescriptor *other) const override; - QRhiRenderPassDescriptor *newCompatibleRenderPassDescriptor() const override; - QVector serializedFormat() const override; - const QRhiNativeHandles *nativeHandles() override; - - void updateSerializedFormat(); - - VkRenderPass rp = VK_NULL_HANDLE; - bool ownsRp = false; - QVarLengthArray attDescs; - QVarLengthArray colorRefs; - QVarLengthArray resolveRefs; - QVarLengthArray subpassDeps; - bool hasDepthStencil = false; - VkAttachmentReference dsRef; - QVector serializedFormatData; - QRhiVulkanRenderPassNativeHandles nativeHandlesStruct; - int lastActiveFrameSlot = -1; -}; - -struct QVkRenderTargetData -{ - VkFramebuffer fb = VK_NULL_HANDLE; - QVkRenderPassDescriptor *rp = nullptr; - QSize pixelSize; - float dpr = 1; - int sampleCount = 1; - int colorAttCount = 0; - int dsAttCount = 0; - int resolveAttCount = 0; - QRhiRenderTargetAttachmentTracker::ResIdList currentResIdList; - static const int MAX_COLOR_ATTACHMENTS = 8; -}; - -struct QVkSwapChainRenderTarget : public QRhiSwapChainRenderTarget -{ - QVkSwapChainRenderTarget(QRhiImplementation *rhi, QRhiSwapChain *swapchain); - ~QVkSwapChainRenderTarget(); - void destroy() override; - - QSize pixelSize() const override; - float devicePixelRatio() const override; - int sampleCount() const override; - - QVkRenderTargetData d; -}; - -struct QVkTextureRenderTarget : public QRhiTextureRenderTarget -{ - QVkTextureRenderTarget(QRhiImplementation *rhi, const QRhiTextureRenderTargetDescription &desc, Flags flags); - ~QVkTextureRenderTarget(); - void destroy() override; - - QSize pixelSize() const override; - float devicePixelRatio() const override; - int sampleCount() const override; - - QRhiRenderPassDescriptor *newCompatibleRenderPassDescriptor() override; - bool create() override; - - QVkRenderTargetData d; - VkImageView rtv[QVkRenderTargetData::MAX_COLOR_ATTACHMENTS]; - VkImageView resrtv[QVkRenderTargetData::MAX_COLOR_ATTACHMENTS]; - int lastActiveFrameSlot = -1; - friend class QRhiVulkan; -}; - -struct QVkShaderResourceBindings : public QRhiShaderResourceBindings -{ - QVkShaderResourceBindings(QRhiImplementation *rhi); - ~QVkShaderResourceBindings(); - void destroy() override; - bool create() override; - void updateResources(UpdateFlags flags) override; - - QVarLengthArray sortedBindings; - bool hasSlottedResource = false; - bool hasDynamicOffset = false; - int poolIndex = -1; - VkDescriptorSetLayout layout = VK_NULL_HANDLE; - VkDescriptorSet descSets[QVK_FRAMES_IN_FLIGHT]; // multiple sets to support dynamic buffers - int lastActiveFrameSlot = -1; - uint generation = 0; - - // Keep track of the generation number of each referenced QRhi* to be able - // to detect that the underlying descriptor set became out of date and they - // need to be written again with the up-to-date VkBuffer etc. objects. - struct BoundUniformBufferData { - quint64 id; - uint generation; - }; - struct BoundSampledTextureData { - int count; - struct { - quint64 texId; - uint texGeneration; - quint64 samplerId; - uint samplerGeneration; - } d[QRhiShaderResourceBinding::Data::MAX_TEX_SAMPLER_ARRAY_SIZE]; - }; - struct BoundStorageImageData { - quint64 id; - uint generation; - }; - struct BoundStorageBufferData { - quint64 id; - uint generation; - }; - struct BoundResourceData { - union { - BoundUniformBufferData ubuf; - BoundSampledTextureData stex; - BoundStorageImageData simage; - BoundStorageBufferData sbuf; - }; - }; - QVarLengthArray boundResourceData[QVK_FRAMES_IN_FLIGHT]; - - friend class QRhiVulkan; -}; - -Q_DECLARE_TYPEINFO(QVkShaderResourceBindings::BoundResourceData, Q_RELOCATABLE_TYPE); - -struct QVkGraphicsPipeline : public QRhiGraphicsPipeline -{ - QVkGraphicsPipeline(QRhiImplementation *rhi); - ~QVkGraphicsPipeline(); - void destroy() override; - bool create() override; - - VkPipelineLayout layout = VK_NULL_HANDLE; - VkPipeline pipeline = VK_NULL_HANDLE; - int lastActiveFrameSlot = -1; - uint generation = 0; - friend class QRhiVulkan; -}; - -struct QVkComputePipeline : public QRhiComputePipeline -{ - QVkComputePipeline(QRhiImplementation *rhi); - ~QVkComputePipeline(); - void destroy() override; - bool create() override; - - VkPipelineLayout layout = VK_NULL_HANDLE; - VkPipeline pipeline = VK_NULL_HANDLE; - int lastActiveFrameSlot = -1; - uint generation = 0; - friend class QRhiVulkan; -}; - -struct QVkCommandBuffer : public QRhiCommandBuffer -{ - QVkCommandBuffer(QRhiImplementation *rhi); - ~QVkCommandBuffer(); - void destroy() override; - - const QRhiNativeHandles *nativeHandles(); - - VkCommandBuffer cb = VK_NULL_HANDLE; // primary - QRhiVulkanCommandBufferNativeHandles nativeHandlesStruct; - - enum PassType { - NoPass, - RenderPass, - ComputePass - }; - - void resetState() { - recordingPass = NoPass; - passUsesSecondaryCb = false; - currentTarget = nullptr; - activeSecondaryCbStack.clear(); - resetCommands(); - resetCachedState(); - } - - void resetCachedState() { - currentGraphicsPipeline = nullptr; - currentComputePipeline = nullptr; - currentPipelineGeneration = 0; - currentGraphicsSrb = nullptr; - currentComputeSrb = nullptr; - currentSrbGeneration = 0; - currentDescSetSlot = -1; - currentIndexBuffer = VK_NULL_HANDLE; - currentIndexOffset = 0; - currentIndexFormat = VK_INDEX_TYPE_UINT16; - memset(currentVertexBuffers, 0, sizeof(currentVertexBuffers)); - memset(currentVertexOffsets, 0, sizeof(currentVertexOffsets)); - inExternal = false; - } - - PassType recordingPass; - bool passUsesSecondaryCb; - QRhiRenderTarget *currentTarget; - QRhiGraphicsPipeline *currentGraphicsPipeline; - QRhiComputePipeline *currentComputePipeline; - uint currentPipelineGeneration; - QRhiShaderResourceBindings *currentGraphicsSrb; - QRhiShaderResourceBindings *currentComputeSrb; - uint currentSrbGeneration; - int currentDescSetSlot; - VkBuffer currentIndexBuffer; - quint32 currentIndexOffset; - VkIndexType currentIndexFormat; - static const int VERTEX_INPUT_RESOURCE_SLOT_COUNT = 32; - VkBuffer currentVertexBuffers[VERTEX_INPUT_RESOURCE_SLOT_COUNT]; - quint32 currentVertexOffsets[VERTEX_INPUT_RESOURCE_SLOT_COUNT]; - QVarLengthArray activeSecondaryCbStack; - bool inExternal; - - struct { - QHash > writtenResources; - void reset() { - writtenResources.clear(); - } - } computePassState; - - struct Command { - enum Cmd { - CopyBuffer, - CopyBufferToImage, - CopyImage, - CopyImageToBuffer, - ImageBarrier, - BufferBarrier, - BlitImage, - BeginRenderPass, - EndRenderPass, - BindPipeline, - BindDescriptorSet, - BindVertexBuffer, - BindIndexBuffer, - SetViewport, - SetScissor, - SetBlendConstants, - SetStencilRef, - Draw, - DrawIndexed, - DebugMarkerBegin, - DebugMarkerEnd, - DebugMarkerInsert, - TransitionPassResources, - Dispatch, - ExecuteSecondary - }; - Cmd cmd; - - union Args { - struct { - VkBuffer src; - VkBuffer dst; - VkBufferCopy desc; - } copyBuffer; - struct { - VkBuffer src; - VkImage dst; - VkImageLayout dstLayout; - int count; - int bufferImageCopyIndex; - } copyBufferToImage; - struct { - VkImage src; - VkImageLayout srcLayout; - VkImage dst; - VkImageLayout dstLayout; - VkImageCopy desc; - } copyImage; - struct { - VkImage src; - VkImageLayout srcLayout; - VkBuffer dst; - VkBufferImageCopy desc; - } copyImageToBuffer; - struct { - VkPipelineStageFlags srcStageMask; - VkPipelineStageFlags dstStageMask; - int count; - int index; - } imageBarrier; - struct { - VkPipelineStageFlags srcStageMask; - VkPipelineStageFlags dstStageMask; - int count; - int index; - } bufferBarrier; - struct { - VkImage src; - VkImageLayout srcLayout; - VkImage dst; - VkImageLayout dstLayout; - VkFilter filter; - VkImageBlit desc; - } blitImage; - struct { - VkRenderPassBeginInfo desc; - int clearValueIndex; - bool useSecondaryCb; - } beginRenderPass; - struct { - } endRenderPass; - struct { - VkPipelineBindPoint bindPoint; - VkPipeline pipeline; - } bindPipeline; - struct { - VkPipelineBindPoint bindPoint; - VkPipelineLayout pipelineLayout; - VkDescriptorSet descSet; - int dynamicOffsetCount; - int dynamicOffsetIndex; - } bindDescriptorSet; - struct { - int startBinding; - int count; - int vertexBufferIndex; - int vertexBufferOffsetIndex; - } bindVertexBuffer; - struct { - VkBuffer buf; - VkDeviceSize ofs; - VkIndexType type; - } bindIndexBuffer; - struct { - VkViewport viewport; - } setViewport; - struct { - VkRect2D scissor; - } setScissor; - struct { - float c[4]; - } setBlendConstants; - struct { - uint32_t ref; - } setStencilRef; - struct { - uint32_t vertexCount; - uint32_t instanceCount; - uint32_t firstVertex; - uint32_t firstInstance; - } draw; - struct { - uint32_t indexCount; - uint32_t instanceCount; - uint32_t firstIndex; - int32_t vertexOffset; - uint32_t firstInstance; - } drawIndexed; - struct { -#ifdef VK_EXT_debug_utils - VkDebugUtilsLabelEXT label; - int labelNameIndex; -#endif - } debugMarkerBegin; - struct { - } debugMarkerEnd; - struct { -#ifdef VK_EXT_debug_utils - VkDebugUtilsLabelEXT label; - int labelNameIndex; -#endif - } debugMarkerInsert; - struct { - int trackerIndex; - } transitionResources; - struct { - int x, y, z; - } dispatch; - struct { - VkCommandBuffer cb; - } executeSecondary; - } args; - }; - - QRhiBackendCommandList commands; - QVarLengthArray passResTrackers; - int currentPassResTrackerIndex; - - void resetCommands() { - commands.reset(); - resetPools(); - - passResTrackers.clear(); - currentPassResTrackerIndex = -1; - } - - void resetPools() { - pools.clearValue.clear(); - pools.bufferImageCopy.clear(); - pools.dynamicOffset.clear(); - pools.vertexBuffer.clear(); - pools.vertexBufferOffset.clear(); - pools.debugMarkerData.clear(); - pools.imageBarrier.clear(); - pools.bufferBarrier.clear(); - } - - struct { - QVarLengthArray clearValue; - QVarLengthArray bufferImageCopy; - QVarLengthArray dynamicOffset; - QVarLengthArray vertexBuffer; - QVarLengthArray vertexBufferOffset; - QVarLengthArray debugMarkerData; - QVarLengthArray imageBarrier; - QVarLengthArray bufferBarrier; - } pools; - - friend class QRhiVulkan; -}; - -struct QVkSwapChain : public QRhiSwapChain -{ - QVkSwapChain(QRhiImplementation *rhi); - ~QVkSwapChain(); - void destroy() override; - - QRhiCommandBuffer *currentFrameCommandBuffer() override; - QRhiRenderTarget *currentFrameRenderTarget() override; - - QSize surfacePixelSize() override; - bool isFormatSupported(Format f) override; - - QRhiRenderPassDescriptor *newCompatibleRenderPassDescriptor() override; - bool createOrResize() override; - - bool ensureSurface(); - - static const quint32 EXPECTED_MAX_BUFFER_COUNT = 4; - - QWindow *window = nullptr; - QSize pixelSize; - bool supportsReadback = false; - VkSwapchainKHR sc = VK_NULL_HANDLE; - int bufferCount = 0; - VkSurfaceKHR surface = VK_NULL_HANDLE; - VkSurfaceKHR lastConnectedSurface = VK_NULL_HANDLE; - VkFormat colorFormat = VK_FORMAT_B8G8R8A8_UNORM; - VkColorSpaceKHR colorSpace = VK_COLOR_SPACE_SRGB_NONLINEAR_KHR; - QVkRenderBuffer *ds = nullptr; - VkSampleCountFlagBits samples = VK_SAMPLE_COUNT_1_BIT; - QVarLengthArray supportedPresentationModes; - VkDeviceMemory msaaImageMem = VK_NULL_HANDLE; - QVkSwapChainRenderTarget rtWrapper; - QVkCommandBuffer cbWrapper; - - struct ImageResources { - VkImage image = VK_NULL_HANDLE; - VkImageView imageView = VK_NULL_HANDLE; - VkFramebuffer fb = VK_NULL_HANDLE; - VkImage msaaImage = VK_NULL_HANDLE; - VkImageView msaaImageView = VK_NULL_HANDLE; - enum LastUse { - ScImageUseNone, - ScImageUseRender, - ScImageUseTransferSource - }; - LastUse lastUse = ScImageUseNone; - }; - QVarLengthArray imageRes; - - struct FrameResources { - VkFence imageFence = VK_NULL_HANDLE; - bool imageFenceWaitable = false; - VkSemaphore imageSem = VK_NULL_HANDLE; - VkSemaphore drawSem = VK_NULL_HANDLE; - bool imageAcquired = false; - bool imageSemWaitable = false; - VkFence cmdFence = VK_NULL_HANDLE; - bool cmdFenceWaitable = false; - VkCommandBuffer cmdBuf = VK_NULL_HANDLE; // primary - int timestampQueryIndex = -1; - } frameRes[QVK_FRAMES_IN_FLIGHT]; - - quint32 currentImageIndex = 0; // index in imageRes - quint32 currentFrameSlot = 0; // index in frameRes - int frameCount = 0; - - friend class QRhiVulkan; -}; - -class QRhiVulkan : public QRhiImplementation -{ -public: - QRhiVulkan(QRhiVulkanInitParams *params, QRhiVulkanNativeHandles *importParams = nullptr); - - bool create(QRhi::Flags flags) override; - void destroy() override; - - QRhiGraphicsPipeline *createGraphicsPipeline() override; - QRhiComputePipeline *createComputePipeline() override; - QRhiShaderResourceBindings *createShaderResourceBindings() override; - QRhiBuffer *createBuffer(QRhiBuffer::Type type, - QRhiBuffer::UsageFlags usage, - quint32 size) override; - QRhiRenderBuffer *createRenderBuffer(QRhiRenderBuffer::Type type, - const QSize &pixelSize, - int sampleCount, - QRhiRenderBuffer::Flags flags, - QRhiTexture::Format backingFormatHint) override; - QRhiTexture *createTexture(QRhiTexture::Format format, - const QSize &pixelSize, - int depth, - int arraySize, - int sampleCount, - QRhiTexture::Flags flags) override; - QRhiSampler *createSampler(QRhiSampler::Filter magFilter, - QRhiSampler::Filter minFilter, - QRhiSampler::Filter mipmapMode, - QRhiSampler:: AddressMode u, - QRhiSampler::AddressMode v, - QRhiSampler::AddressMode w) override; - - QRhiTextureRenderTarget *createTextureRenderTarget(const QRhiTextureRenderTargetDescription &desc, - QRhiTextureRenderTarget::Flags flags) override; - - QRhiSwapChain *createSwapChain() override; - QRhi::FrameOpResult beginFrame(QRhiSwapChain *swapChain, QRhi::BeginFrameFlags flags) override; - QRhi::FrameOpResult endFrame(QRhiSwapChain *swapChain, QRhi::EndFrameFlags flags) override; - QRhi::FrameOpResult beginOffscreenFrame(QRhiCommandBuffer **cb, QRhi::BeginFrameFlags flags) override; - QRhi::FrameOpResult endOffscreenFrame(QRhi::EndFrameFlags flags) override; - QRhi::FrameOpResult finish() override; - - void resourceUpdate(QRhiCommandBuffer *cb, QRhiResourceUpdateBatch *resourceUpdates) override; - - void beginPass(QRhiCommandBuffer *cb, - QRhiRenderTarget *rt, - const QColor &colorClearValue, - const QRhiDepthStencilClearValue &depthStencilClearValue, - QRhiResourceUpdateBatch *resourceUpdates, - QRhiCommandBuffer::BeginPassFlags flags) override; - void endPass(QRhiCommandBuffer *cb, QRhiResourceUpdateBatch *resourceUpdates) override; - - void setGraphicsPipeline(QRhiCommandBuffer *cb, - QRhiGraphicsPipeline *ps) override; - - void setShaderResources(QRhiCommandBuffer *cb, - QRhiShaderResourceBindings *srb, - int dynamicOffsetCount, - const QRhiCommandBuffer::DynamicOffset *dynamicOffsets) override; - - void setVertexInput(QRhiCommandBuffer *cb, - int startBinding, int bindingCount, const QRhiCommandBuffer::VertexInput *bindings, - QRhiBuffer *indexBuf, quint32 indexOffset, - QRhiCommandBuffer::IndexFormat indexFormat) override; - - void setViewport(QRhiCommandBuffer *cb, const QRhiViewport &viewport) override; - void setScissor(QRhiCommandBuffer *cb, const QRhiScissor &scissor) override; - void setBlendConstants(QRhiCommandBuffer *cb, const QColor &c) override; - void setStencilRef(QRhiCommandBuffer *cb, quint32 refValue) override; - - void draw(QRhiCommandBuffer *cb, quint32 vertexCount, - quint32 instanceCount, quint32 firstVertex, quint32 firstInstance) override; - - void drawIndexed(QRhiCommandBuffer *cb, quint32 indexCount, - quint32 instanceCount, quint32 firstIndex, - qint32 vertexOffset, quint32 firstInstance) override; - - void debugMarkBegin(QRhiCommandBuffer *cb, const QByteArray &name) override; - void debugMarkEnd(QRhiCommandBuffer *cb) override; - void debugMarkMsg(QRhiCommandBuffer *cb, const QByteArray &msg) override; - - void beginComputePass(QRhiCommandBuffer *cb, - QRhiResourceUpdateBatch *resourceUpdates, - QRhiCommandBuffer::BeginPassFlags flags) override; - void endComputePass(QRhiCommandBuffer *cb, QRhiResourceUpdateBatch *resourceUpdates) override; - void setComputePipeline(QRhiCommandBuffer *cb, QRhiComputePipeline *ps) override; - void dispatch(QRhiCommandBuffer *cb, int x, int y, int z) override; - - const QRhiNativeHandles *nativeHandles(QRhiCommandBuffer *cb) override; - void beginExternal(QRhiCommandBuffer *cb) override; - void endExternal(QRhiCommandBuffer *cb) override; - - QList supportedSampleCounts() const override; - int ubufAlignment() const override; - bool isYUpInFramebuffer() const override; - bool isYUpInNDC() const override; - bool isClipDepthZeroToOne() const override; - QMatrix4x4 clipSpaceCorrMatrix() const override; - bool isTextureFormatSupported(QRhiTexture::Format format, QRhiTexture::Flags flags) const override; - bool isFeatureSupported(QRhi::Feature feature) const override; - int resourceLimit(QRhi::ResourceLimit limit) const override; - const QRhiNativeHandles *nativeHandles() override; - QRhiDriverInfo driverInfo() const override; - QRhiStats statistics() override; - bool makeThreadLocalNativeContextCurrent() override; - void releaseCachedResources() override; - bool isDeviceLost() const override; - - QByteArray pipelineCacheData() override; - void setPipelineCacheData(const QByteArray &data) override; - - VkResult createDescriptorPool(VkDescriptorPool *pool); - bool allocateDescriptorSet(VkDescriptorSetAllocateInfo *allocInfo, VkDescriptorSet *result, int *resultPoolIndex); - uint32_t chooseTransientImageMemType(VkImage img, uint32_t startIndex); - bool createTransientImage(VkFormat format, const QSize &pixelSize, VkImageUsageFlags usage, - VkImageAspectFlags aspectMask, VkSampleCountFlagBits samples, - VkDeviceMemory *mem, VkImage *images, VkImageView *views, int count); - - bool recreateSwapChain(QRhiSwapChain *swapChain); - void releaseSwapChainResources(QRhiSwapChain *swapChain); - - VkFormat optimalDepthStencilFormat(); - VkSampleCountFlagBits effectiveSampleCount(int sampleCount); - bool createDefaultRenderPass(QVkRenderPassDescriptor *rpD, - bool hasDepthStencil, - VkSampleCountFlagBits samples, - VkFormat colorFormat); - bool createOffscreenRenderPass(QVkRenderPassDescriptor *rpD, - const QRhiColorAttachment *firstColorAttachment, - const QRhiColorAttachment *lastColorAttachment, - bool preserveColor, - bool preserveDs, - QRhiRenderBuffer *depthStencilBuffer, - QRhiTexture *depthTexture); - bool ensurePipelineCache(const void *initialData = nullptr, size_t initialDataSize = 0); - VkShaderModule createShader(const QByteArray &spirv); - - void prepareNewFrame(QRhiCommandBuffer *cb); - VkCommandBuffer startSecondaryCommandBuffer(QVkRenderTargetData *rtD = nullptr); - void endAndEnqueueSecondaryCommandBuffer(VkCommandBuffer cb, QVkCommandBuffer *cbD); - QRhi::FrameOpResult startPrimaryCommandBuffer(VkCommandBuffer *cb); - QRhi::FrameOpResult endAndSubmitPrimaryCommandBuffer(VkCommandBuffer cb, VkFence cmdFence, - VkSemaphore *waitSem, VkSemaphore *signalSem); - void waitCommandCompletion(int frameSlot); - VkDeviceSize subresUploadByteSize(const QRhiTextureSubresourceUploadDescription &subresDesc) const; - using BufferImageCopyList = QVarLengthArray; - void prepareUploadSubres(QVkTexture *texD, int layer, int level, - const QRhiTextureSubresourceUploadDescription &subresDesc, - size_t *curOfs, void *mp, - BufferImageCopyList *copyInfos); - void enqueueResourceUpdates(QVkCommandBuffer *cbD, QRhiResourceUpdateBatch *resourceUpdates); - void executeBufferHostWritesForSlot(QVkBuffer *bufD, int slot); - void enqueueTransitionPassResources(QVkCommandBuffer *cbD); - void recordPrimaryCommandBuffer(QVkCommandBuffer *cbD); - void trackedRegisterBuffer(QRhiPassResourceTracker *passResTracker, - QVkBuffer *bufD, - int slot, - QRhiPassResourceTracker::BufferAccess access, - QRhiPassResourceTracker::BufferStage stage); - void trackedRegisterTexture(QRhiPassResourceTracker *passResTracker, - QVkTexture *texD, - QRhiPassResourceTracker::TextureAccess access, - QRhiPassResourceTracker::TextureStage stage); - void recordTransitionPassResources(QVkCommandBuffer *cbD, const QRhiPassResourceTracker &tracker); - void activateTextureRenderTarget(QVkCommandBuffer *cbD, QVkTextureRenderTarget *rtD); - void executeDeferredReleases(bool forced = false); - void finishActiveReadbacks(bool forced = false); - - void setObjectName(uint64_t object, VkObjectType type, const QByteArray &name, int slot = -1); - void trackedBufferBarrier(QVkCommandBuffer *cbD, QVkBuffer *bufD, int slot, - VkAccessFlags access, VkPipelineStageFlags stage); - void trackedImageBarrier(QVkCommandBuffer *cbD, QVkTexture *texD, - VkImageLayout layout, VkAccessFlags access, VkPipelineStageFlags stage); - void depthStencilExplicitBarrier(QVkCommandBuffer *cbD, QVkRenderBuffer *rbD); - void subresourceBarrier(QVkCommandBuffer *cbD, VkImage image, - VkImageLayout oldLayout, VkImageLayout newLayout, - VkAccessFlags srcAccess, VkAccessFlags dstAccess, - VkPipelineStageFlags srcStage, VkPipelineStageFlags dstStage, - int startLayer, int layerCount, - int startLevel, int levelCount); - void updateShaderResourceBindings(QRhiShaderResourceBindings *srb, int descSetIdx = -1); - void ensureCommandPoolForNewFrame(); - - QVulkanInstance *inst = nullptr; - QWindow *maybeWindow = nullptr; - QByteArrayList requestedDeviceExtensions; - bool importedDevice = false; - VkPhysicalDevice physDev = VK_NULL_HANDLE; - VkDevice dev = VK_NULL_HANDLE; - VkCommandPool cmdPool[QVK_FRAMES_IN_FLIGHT] = {}; - quint32 gfxQueueFamilyIdx = 0; - quint32 gfxQueueIdx = 0; - VkQueue gfxQueue = VK_NULL_HANDLE; - quint32 timestampValidBits = 0; - bool importedAllocator = false; - QVkAllocator allocator = nullptr; - QVulkanFunctions *f = nullptr; - QVulkanDeviceFunctions *df = nullptr; - QRhi::Flags rhiFlags; - VkPhysicalDeviceFeatures physDevFeatures; - VkPhysicalDeviceProperties physDevProperties; - VkDeviceSize ubufAlign; - VkDeviceSize texbufAlign; - bool deviceLost = false; - bool releaseCachedResourcesCalledBeforeFrameStart = false; - -#ifdef VK_EXT_debug_utils - PFN_vkSetDebugUtilsObjectNameEXT vkSetDebugUtilsObjectNameEXT = nullptr; - PFN_vkCmdBeginDebugUtilsLabelEXT vkCmdBeginDebugUtilsLabelEXT = nullptr; - PFN_vkCmdEndDebugUtilsLabelEXT vkCmdEndDebugUtilsLabelEXT = nullptr; - PFN_vkCmdInsertDebugUtilsLabelEXT vkCmdInsertDebugUtilsLabelEXT = nullptr; -#endif - - PFN_vkCreateSwapchainKHR vkCreateSwapchainKHR = nullptr; - PFN_vkDestroySwapchainKHR vkDestroySwapchainKHR; - PFN_vkGetSwapchainImagesKHR vkGetSwapchainImagesKHR; - PFN_vkAcquireNextImageKHR vkAcquireNextImageKHR; - PFN_vkQueuePresentKHR vkQueuePresentKHR; - PFN_vkGetPhysicalDeviceSurfaceCapabilitiesKHR vkGetPhysicalDeviceSurfaceCapabilitiesKHR; - PFN_vkGetPhysicalDeviceSurfaceFormatsKHR vkGetPhysicalDeviceSurfaceFormatsKHR; - PFN_vkGetPhysicalDeviceSurfacePresentModesKHR vkGetPhysicalDeviceSurfacePresentModesKHR; - - struct { - bool compute = false; - bool wideLines = false; - bool debugUtils = false; - bool vertexAttribDivisor = false; - bool texture3DSliceAs2D = false; - bool tessellation = false; - bool geometryShader = false; - bool nonFillPolygonMode = false; - QVersionNumber apiVersion; - } caps; - - VkPipelineCache pipelineCache = VK_NULL_HANDLE; - struct DescriptorPoolData { - DescriptorPoolData() { } - DescriptorPoolData(VkDescriptorPool pool_) - : pool(pool_) - { } - VkDescriptorPool pool = VK_NULL_HANDLE; - int refCount = 0; - int allocedDescSets = 0; - }; - QVarLengthArray descriptorPools; - QVarLengthArray freeSecondaryCbs[QVK_FRAMES_IN_FLIGHT]; - - VkQueryPool timestampQueryPool = VK_NULL_HANDLE; - QBitArray timestampQueryPoolMap; - - VkFormat optimalDsFormat = VK_FORMAT_UNDEFINED; - QMatrix4x4 clipCorrectMatrix; - - QVkSwapChain *currentSwapChain = nullptr; - QSet swapchains; - QRhiVulkanNativeHandles nativeHandlesStruct; - QRhiDriverInfo driverInfoStruct; - - struct OffscreenFrame { - OffscreenFrame(QRhiImplementation *rhi) - { - for (int i = 0; i < QVK_FRAMES_IN_FLIGHT; ++i) - cbWrapper[i] = new QVkCommandBuffer(rhi); - } - ~OffscreenFrame() - { - for (int i = 0; i < QVK_FRAMES_IN_FLIGHT; ++i) - delete cbWrapper[i]; - } - bool active = false; - QVkCommandBuffer *cbWrapper[QVK_FRAMES_IN_FLIGHT]; - VkFence cmdFence = VK_NULL_HANDLE; - } ofr; - - struct TextureReadback { - int activeFrameSlot = -1; - QRhiReadbackDescription desc; - QRhiReadbackResult *result; - VkBuffer stagingBuf; - QVkAlloc stagingAlloc; - quint32 byteSize; - QSize pixelSize; - QRhiTexture::Format format; - }; - QVarLengthArray activeTextureReadbacks; - struct BufferReadback { - int activeFrameSlot = -1; - QRhiBufferReadbackResult *result; - quint32 byteSize; - VkBuffer stagingBuf; - QVkAlloc stagingAlloc; - }; - QVarLengthArray activeBufferReadbacks; - - struct DeferredReleaseEntry { - enum Type { - Pipeline, - ShaderResourceBindings, - Buffer, - RenderBuffer, - Texture, - Sampler, - TextureRenderTarget, - RenderPass, - StagingBuffer, - SecondaryCommandBuffer - }; - Type type; - int lastActiveFrameSlot; // -1 if not used otherwise 0..FRAMES_IN_FLIGHT-1 - union { - struct { - VkPipeline pipeline; - VkPipelineLayout layout; - } pipelineState; - struct { - int poolIndex; - VkDescriptorSetLayout layout; - } shaderResourceBindings; - struct { - VkBuffer buffers[QVK_FRAMES_IN_FLIGHT]; - QVkAlloc allocations[QVK_FRAMES_IN_FLIGHT]; - VkBuffer stagingBuffers[QVK_FRAMES_IN_FLIGHT]; - QVkAlloc stagingAllocations[QVK_FRAMES_IN_FLIGHT]; - } buffer; - struct { - VkDeviceMemory memory; - VkImage image; - VkImageView imageView; - } renderBuffer; - struct { - VkImage image; - VkImageView imageView; - QVkAlloc allocation; - VkBuffer stagingBuffers[QVK_FRAMES_IN_FLIGHT]; - QVkAlloc stagingAllocations[QVK_FRAMES_IN_FLIGHT]; - VkImageView extraImageViews[QRhi::MAX_MIP_LEVELS]; - } texture; - struct { - VkSampler sampler; - } sampler; - struct { - VkFramebuffer fb; - VkImageView rtv[QVkRenderTargetData::MAX_COLOR_ATTACHMENTS]; - VkImageView resrtv[QVkRenderTargetData::MAX_COLOR_ATTACHMENTS]; - } textureRenderTarget; - struct { - VkRenderPass rp; - } renderPass; - struct { - VkBuffer stagingBuffer; - QVkAlloc stagingAllocation; - } stagingBuffer; - struct { - VkCommandBuffer cb; - } secondaryCommandBuffer; - }; - }; - QList releaseQueue; -}; - -Q_DECLARE_TYPEINFO(QRhiVulkan::DescriptorPoolData, Q_RELOCATABLE_TYPE); -Q_DECLARE_TYPEINFO(QRhiVulkan::DeferredReleaseEntry, Q_RELOCATABLE_TYPE); -Q_DECLARE_TYPEINFO(QRhiVulkan::TextureReadback, Q_RELOCATABLE_TYPE); -Q_DECLARE_TYPEINFO(QRhiVulkan::BufferReadback, Q_RELOCATABLE_TYPE); - -QT_END_NAMESPACE - -#endif diff --git a/src/gui/rhi/qrhivulkanext_p.h b/src/gui/rhi/qrhivulkanext_p.h deleted file mode 100644 index 02b34694..00000000 --- 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 c35445f7..02d86907 100644 --- a/src/gui/rhi/qshader.cpp +++ b/src/gui/rhi/qshader.cpp @@ -1,7 +1,7 @@ -// Copyright (C) 2019 The Qt Company Ltd. +// 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 "qshader_p_p.h" +#include "qshader_p.h" #include #include @@ -9,8 +9,9 @@ QT_BEGIN_NAMESPACE /*! \class QShader - \internal + \ingroup painting-3D \inmodule QtGui + \since 6.6 \brief Contains multiple versions of a shader translated to multiple shading languages, together with reflection metadata. @@ -21,6 +22,16 @@ QT_BEGIN_NAMESPACE as, Vulkan, Metal, Direct3D, and OpenGL, take QShader as their input whenever a shader needs to be specified. + \warning The QRhi family of classes in the Qt Gui module, including QShader + and QShaderDescription, offer limited compatibility guarantees. There are + no source or binary compatibility guarantees for these classes, meaning the + API is only guaranteed to work with the Qt version the application was + developed against. Source incompatible changes are however aimed to be kept + at a minimum and will only be made in minor releases (6.7, 6.8, and so on). + To use these classes in an application, link to + \c{Qt::GuiPrivate} (if using CMake), and include the headers with the \c + rhi prefix, for example \c{#include }. + A QShader instance is empty and thus invalid by default. To get a useful instance, the two typical methods are: @@ -68,8 +79,9 @@ QT_BEGIN_NAMESPACE can be returned or passed by value. Detach happens implicitly when calling a setter. - For reference, QRhi expects that a QShader suitable for all its - backends contains at least the following: + For reference, a typical, portable QRhi expects that a QShader suitable for + all its backends contains at least the following. (this excludes support + for core profile OpenGL contexts, add GLSL 150 or newer for that) \list @@ -77,11 +89,11 @@ QT_BEGIN_NAMESPACE \li GLSL/ES 100 source code suitable for OpenGL ES 2.0 or newer - \li GLSL 120 source code suitable for OpenGL 2.1 + \li GLSL 120 source code suitable for OpenGL 2.1 or newer - \li HLSL Shader Model 5.0 source code or the corresponding DXBC bytecode suitable for Direct3D 11 + \li HLSL Shader Model 5.0 source code or the corresponding DXBC bytecode suitable for Direct3D 11/12 - \li Metal Shading Language 1.2 source code or the corresponding bytecode suitable for Metal + \li Metal Shading Language 1.2 source code or the corresponding bytecode suitable for Metal 1.2 or newer \endlist @@ -102,8 +114,8 @@ QT_BEGIN_NAMESPACE /*! \class QShaderVersion - \internal \inmodule QtGui + \since 6.6 \brief Specifies the shading language version. @@ -128,6 +140,9 @@ QT_BEGIN_NAMESPACE A default constructed QShaderVersion contains a version of 100 and no flags set. + + \note This is a RHI API with limited compatibility guarantees, see \l QShader + for details. */ /*! @@ -140,13 +155,16 @@ QT_BEGIN_NAMESPACE /*! \class QShaderKey - \internal \inmodule QtGui + \since 6.6 \brief Specifies the shading language, the version with flags, and the variant. A default constructed QShaderKey has source set to SpirvShader and sourceVersion set to 100. sourceVariant defaults to StandardShader. + + \note This is a RHI API with limited compatibility guarantees, see \l QShader + for details. */ /*! @@ -195,15 +213,40 @@ QT_BEGIN_NAMESPACE the need for three dedicated variants. */ +/*! + \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 - \internal \inmodule QtGui + \since 6.6 \brief Contains source or binary code for a shader and additional metadata. When shader() is empty after retrieving a QShaderCode instance from QShader, it indicates no shader code was found for the requested key. + + \note This is a RHI API with limited compatibility guarantees, see \l QShader + for details. */ /*! @@ -226,7 +269,7 @@ void QShader::detach() } /*! - \internal + Constructs a copy of \a other. */ QShader::QShader(const QShader &other) : d(other.d) @@ -236,7 +279,7 @@ QShader::QShader(const QShader &other) } /*! - \internal + Assigns \a other to this object. */ QShader &QShader::operator=(const QShader &other) { @@ -366,7 +409,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() */ @@ -455,6 +502,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) @@ -474,6 +524,7 @@ QShader QShader::fromSerialized(const QByteArray &data) ds >> intVal; d->qsbVersion = intVal; if (d->qsbVersion != QShaderPrivate::QSB_VERSION + && d->qsbVersion != QShaderPrivate::QSB_VERSION_WITHOUT_INPUT_OUTPUT_INTERFACE_BLOCKS && d->qsbVersion != QShaderPrivate::QSB_VERSION_WITHOUT_EXTENDED_STORAGE_BUFFER_INFO && d->qsbVersion != QShaderPrivate::QSB_VERSION_WITHOUT_NATIVE_SHADER_INFO && d->qsbVersion != QShaderPrivate::QSB_VERSION_WITHOUT_SEPARATE_IMAGES_AND_SAMPLERS @@ -576,16 +627,79 @@ QShader QShader::fromSerialized(const QByteArray &data) return bs; } +/*! + \fn QShaderVersion::QShaderVersion() = default + */ + +/*! + Constructs a new QShaderVersion with version \a v and flags \a f. + */ QShaderVersion::QShaderVersion(int v, Flags f) : m_version(v), m_flags(f) { } +/*! + \fn int QShaderVersion::version() const + \return the version. + */ + +/*! + \fn void QShaderVersion::setVersion(int v) + Sets the shading language version to \a v. + */ + +/*! + \fn QShaderVersion::Flags QShaderVersion::flags() const + \return the flags. + */ + +/*! + \fn void QShaderVersion::setFlags(Flags f) + Sets the flags \a f. + */ + +/*! + \fn QShaderCode::QShaderCode() = default + */ + +/*! + Constructs a new QShaderCode with the specified shader source \a code and + \a entry point name. + */ QShaderCode::QShaderCode(const QByteArray &code, const QByteArray &entry) : m_shader(code), m_entryPoint(entry) { } +/*! + \fn QByteArray QShaderCode::shader() const + \return the shader source or bytecode. + */ + +/*! + \fn void QShaderCode::setShader(const QByteArray &code) + Sets the shader source or byte \a code. + */ + +/*! + \fn QByteArray QShaderCode::entryPoint() const + \return the entry point name. + */ + +/*! + \fn void QShaderCode::setEntryPoint(const QByteArray &entry) + Sets the \a entry point name. + */ + +/*! + \fn QShaderKey::QShaderKey() = default + */ + +/*! + Constructs a new QShaderKey with shader type \a s, version \a sver, and + variant \a svar. + */ QShaderKey::QShaderKey(QShader::Source s, const QShaderVersion &sver, QShader::Variant svar) @@ -595,6 +709,36 @@ QShaderKey::QShaderKey(QShader::Source s, { } +/*! + \fn QShader::Source QShaderKey::source() const + \return the shader type. + */ + +/*! + \fn void QShaderKey::setSource(QShader::Source s) + Sets the shader type \a s. + */ + +/*! + \fn QShaderVersion QShaderKey::sourceVersion() const + \return the shading language version. + */ + +/*! + \fn void QShaderKey::setSourceVersion(const QShaderVersion &sver) + Sets the shading language version \a sver. + */ + +/*! + \fn QShader::Variant QShaderKey::sourceVariant() const + \return the type of the variant to use. + */ + +/*! + \fn void QShaderKey::setSourceVariant(QShader::Variant svar) + Sets the type of variant to use to \a svar. + */ + /*! Returns \c true if the two QShader objects \a lhs and \a rhs are equal, meaning they are for the same stage with matching sets of shader source or @@ -613,10 +757,9 @@ bool operator==(const QShader &lhs, const QShader &rhs) noexcept } /*! - \internal \fn bool operator!=(const QShader &lhs, const QShader &rhs) - Returns \c false if the values in the two QShader objects \a a and \a b + Returns \c false if the values in the two QShader objects \a lhs and \a rhs are equal; otherwise returns \c true. \relates QShader @@ -659,6 +802,8 @@ size_t qHash(const QShaderVersion &s, size_t seed) noexcept #endif /*! + \return true if \a lhs is smaller than \a rhs. + Establishes a sorting order between the two QShaderVersion \a lhs and \a rhs. \relates QShaderVersion @@ -675,11 +820,10 @@ bool operator<(const QShaderVersion &lhs, const QShaderVersion &rhs) noexcept } /*! - \internal \fn bool operator!=(const QShaderVersion &lhs, const QShaderVersion &rhs) - Returns \c false if the values in the two QShaderVersion objects \a a - and \a b are equal; otherwise returns \c true. + Returns \c false if the values in the two QShaderVersion objects \a lhs + and \a rhs are equal; otherwise returns \c true. \relates QShaderVersion */ @@ -696,6 +840,8 @@ bool operator==(const QShaderKey &lhs, const QShaderKey &rhs) noexcept } /*! + \return true if \a lhs is smaller than \a rhs. + Establishes a sorting order between the two keys \a lhs and \a rhs. \relates QShaderKey @@ -718,11 +864,10 @@ bool operator<(const QShaderKey &lhs, const QShaderKey &rhs) noexcept } /*! - \internal \fn bool operator!=(const QShaderKey &lhs, const QShaderKey &rhs) - Returns \c false if the values in the two QShaderKey objects \a a - and \a b are equal; otherwise returns \c true. + Returns \c false if the values in the two QShaderKey objects \a lhs + and \a rhs are equal; otherwise returns \c true. \relates QShaderKey */ @@ -752,11 +897,10 @@ bool operator==(const QShaderCode &lhs, const QShaderCode &rhs) noexcept } /*! - \internal \fn bool operator!=(const QShaderCode &lhs, const QShaderCode &rhs) - Returns \c false if the values in the two QShaderCode objects \a a - and \a b are equal; otherwise returns \c true. + Returns \c false if the values in the two QShaderCode objects \a lhs + and \a rhs are equal; otherwise returns \c true. \relates QShaderCode */ @@ -894,6 +1038,8 @@ void QShader::removeResourceBindingMap(const QShaderKey &key) /*! \struct QShader::SeparateToCombinedImageSamplerMapping + \inmodule QtGui + \brief Mapping metadata for sampler uniforms. Describes a mapping from a traditional combined image sampler uniform to binding points for a separate texture and sampler. @@ -903,8 +1049,23 @@ void QShader::removeResourceBindingMap(const QShaderKey &key) contains a \c sampler2D (or sampler3D, etc.) uniform with the name of \c{_54} which corresponds to two separate resource bindings (\c 1 and \c 2) in the original shader. + + \note This is a RHI API with limited compatibility guarantees, see \l QShader + for details. */ +/*! + \variable QShader::SeparateToCombinedImageSamplerMapping::combinedSamplerName +*/ + +/*! + \variable QShader::SeparateToCombinedImageSamplerMapping::textureBinding +*/ + +/*! + \variable QShader::SeparateToCombinedImageSamplerMapping::samplerBinding +*/ + /*! \return the combined image sampler mapping list for \a key, or an empty list if there is no data available for \a key, for example because such a @@ -952,6 +1113,8 @@ void QShader::removeSeparateToCombinedImageSamplerMappingList(const QShaderKey & /*! \struct QShader::NativeShaderInfo + \inmodule QtGui + \brief Additional metadata about the native shader code. Describes information about the native shader code, if applicable. This becomes relevant with certain shader languages for certain shader stages, @@ -968,8 +1131,19 @@ void QShader::removeSeparateToCombinedImageSamplerMappingList(const QShaderKey & not be present at all if per-patch output variables were not used. The fact that the shader code relies on such a buffer present can be indicated by the data in this struct. + + \note This is a RHI API with limited compatibility guarantees, see \l QShader + for details. */ +/*! + \variable QShader::NativeShaderInfo::flags +*/ + +/*! + \variable QShader::NativeShaderInfo::extraBufferBindings +*/ + /*! \return the native shader info struct for \a key, or an empty object if there is no data available for \a key, for example because such a mapping diff --git a/src/gui/rhi/qshader.h b/src/gui/rhi/qshader.h new file mode 100644 index 00000000..0b520225 --- /dev/null +++ b/src/gui/rhi/qshader.h @@ -0,0 +1,237 @@ +// 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 QSHADER_H +#define QSHADER_H + +// +// W A R N I N G +// ------------- +// +// This file is part of the RHI API, with limited compatibility guarantees. +// Usage of this API may make your code source and binary incompatible with +// future versions of Qt. +// + +#include +#include +#include +#include + +QT_BEGIN_NAMESPACE + +struct QShaderPrivate; +class QShaderKey; + +#ifdef Q_OS_INTEGRITY + class QShaderVersion; + size_t qHash(const QShaderVersion &, size_t = 0) noexcept; +#endif + +class Q_GUI_EXPORT QShaderVersion +{ +public: + enum Flag { + GlslEs = 0x01 + }; + Q_DECLARE_FLAGS(Flags, Flag) + + QShaderVersion() = default; + QShaderVersion(int v, Flags f = Flags()); + + int version() const { return m_version; } + void setVersion(int v) { m_version = v; } + + Flags flags() const { return m_flags; } + void setFlags(Flags f) { m_flags = f; } + +private: + int m_version = 100; + Flags m_flags; +}; + +Q_DECLARE_OPERATORS_FOR_FLAGS(QShaderVersion::Flags) +Q_DECLARE_TYPEINFO(QShaderVersion, Q_RELOCATABLE_TYPE); + +class QShaderCode; +Q_GUI_EXPORT size_t qHash(const QShaderCode &, size_t = 0) noexcept; + +class Q_GUI_EXPORT QShaderCode +{ +public: + QShaderCode() = default; + QShaderCode(const QByteArray &code, const QByteArray &entry = QByteArray()); + + QByteArray shader() const { return m_shader; } + void setShader(const QByteArray &code) { m_shader = code; } + + QByteArray entryPoint() const { return m_entryPoint; } + void setEntryPoint(const QByteArray &entry) { m_entryPoint = entry; } + +private: + friend Q_GUI_EXPORT size_t qHash(const QShaderCode &, size_t) noexcept; + + QByteArray m_shader; + QByteArray m_entryPoint; +}; + +Q_DECLARE_TYPEINFO(QShaderCode, Q_RELOCATABLE_TYPE); + +class Q_GUI_EXPORT QShader +{ +public: + enum Stage { + VertexStage = 0, + TessellationControlStage, + TessellationEvaluationStage, + GeometryStage, + FragmentStage, + ComputeStage + }; + + enum Source { + SpirvShader = 0, + GlslShader, + HlslShader, + DxbcShader, // fxc + MslShader, + DxilShader, // dxc + MetalLibShader, // xcrun metal + xcrun metallib + WgslShader + }; + + enum Variant { + StandardShader = 0, + BatchableVertexShader, + UInt16IndexedVertexAsComputeShader, + UInt32IndexedVertexAsComputeShader, + NonIndexedVertexAsComputeShader + }; + + enum class SerializedFormatVersion { + Latest = 0, + Qt_6_5, + Qt_6_4 + }; + + QShader(); + QShader(const QShader &other); + QShader &operator=(const QShader &other); + ~QShader(); + void detach(); + + bool isValid() const; + + Stage stage() const; + void setStage(Stage stage); + + QShaderDescription description() const; + void setDescription(const QShaderDescription &desc); + + QList availableShaders() const; + QShaderCode shader(const QShaderKey &key) const; + void setShader(const QShaderKey &key, const QShaderCode &shader); + void removeShader(const QShaderKey &key); + + QByteArray serialized(SerializedFormatVersion version = SerializedFormatVersion::Latest) const; + static QShader fromSerialized(const QByteArray &data); + + using NativeResourceBindingMap = QMap >; // binding -> native_binding[, native_binding] + NativeResourceBindingMap nativeResourceBindingMap(const QShaderKey &key) const; + void setResourceBindingMap(const QShaderKey &key, const NativeResourceBindingMap &map); + void removeResourceBindingMap(const QShaderKey &key); + + struct SeparateToCombinedImageSamplerMapping { + QByteArray combinedSamplerName; + int textureBinding; + int samplerBinding; + }; + using SeparateToCombinedImageSamplerMappingList = QList; + SeparateToCombinedImageSamplerMappingList separateToCombinedImageSamplerMappingList(const QShaderKey &key) const; + void setSeparateToCombinedImageSamplerMappingList(const QShaderKey &key, + const SeparateToCombinedImageSamplerMappingList &list); + void removeSeparateToCombinedImageSamplerMappingList(const QShaderKey &key); + + struct NativeShaderInfo { + int flags = 0; + QMap extraBufferBindings; + }; + NativeShaderInfo nativeShaderInfo(const QShaderKey &key) const; + void setNativeShaderInfo(const QShaderKey &key, const NativeShaderInfo &info); + void removeNativeShaderInfo(const QShaderKey &key); + +private: + QShaderPrivate *d; + friend struct QShaderPrivate; + friend Q_GUI_EXPORT bool operator==(const QShader &, const QShader &) noexcept; + friend Q_GUI_EXPORT size_t qHash(const QShader &, size_t) noexcept; +#ifndef QT_NO_DEBUG_STREAM + friend Q_GUI_EXPORT QDebug operator<<(QDebug, const QShader &); +#endif +}; + +class Q_GUI_EXPORT QShaderKey +{ +public: + QShaderKey() = default; + QShaderKey(QShader::Source s, + const QShaderVersion &sver, + QShader::Variant svar = QShader::StandardShader); + + QShader::Source source() const { return m_source; } + void setSource(QShader::Source s) { m_source = s; } + + QShaderVersion sourceVersion() const { return m_sourceVersion; } + void setSourceVersion(const QShaderVersion &sver) { m_sourceVersion = sver; } + + QShader::Variant sourceVariant() const { return m_sourceVariant; } + void setSourceVariant(QShader::Variant svar) { m_sourceVariant = svar; } + +private: + QShader::Source m_source = QShader::SpirvShader; + QShaderVersion m_sourceVersion; + QShader::Variant m_sourceVariant = QShader::StandardShader; +}; + +Q_DECLARE_TYPEINFO(QShaderKey, Q_RELOCATABLE_TYPE); + +Q_GUI_EXPORT bool operator==(const QShader &lhs, const QShader &rhs) noexcept; +Q_GUI_EXPORT size_t qHash(const QShader &s, size_t seed = 0) noexcept; + +inline bool operator!=(const QShader &lhs, const QShader &rhs) noexcept +{ + return !(lhs == rhs); +} + +Q_GUI_EXPORT bool operator==(const QShaderVersion &lhs, const QShaderVersion &rhs) noexcept; +Q_GUI_EXPORT bool operator<(const QShaderVersion &lhs, const QShaderVersion &rhs) noexcept; +Q_GUI_EXPORT bool operator==(const QShaderKey &lhs, const QShaderKey &rhs) noexcept; +Q_GUI_EXPORT bool operator<(const QShaderKey &lhs, const QShaderKey &rhs) noexcept; +Q_GUI_EXPORT bool operator==(const QShaderCode &lhs, const QShaderCode &rhs) noexcept; + +inline bool operator!=(const QShaderVersion &lhs, const QShaderVersion &rhs) noexcept +{ + return !(lhs == rhs); +} + +inline bool operator!=(const QShaderKey &lhs, const QShaderKey &rhs) noexcept +{ + return !(lhs == rhs); +} + +inline bool operator!=(const QShaderCode &lhs, const QShaderCode &rhs) noexcept +{ + return !(lhs == rhs); +} + +Q_GUI_EXPORT size_t qHash(const QShaderKey &k, size_t seed = 0) noexcept; + +#ifndef QT_NO_DEBUG_STREAM +Q_GUI_EXPORT QDebug operator<<(QDebug, const QShader &); +Q_GUI_EXPORT QDebug operator<<(QDebug dbg, const QShaderKey &k); +Q_GUI_EXPORT QDebug operator<<(QDebug dbg, const QShaderVersion &v); +#endif + +QT_END_NAMESPACE + +#endif diff --git a/src/gui/rhi/qshader_p.h b/src/gui/rhi/qshader_p.h index f85b075a..a2df33d9 100644 --- a/src/gui/rhi/qshader_p.h +++ b/src/gui/rhi/qshader_p.h @@ -1,4 +1,4 @@ -// Copyright (C) 2019 The Qt Company Ltd. +// 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 QSHADER_P_H @@ -15,225 +15,76 @@ // We mean it. // -#include -#include -#include -#include +#include +#include +#include +#include QT_BEGIN_NAMESPACE -struct QShaderPrivate; -class QShaderKey; - -#ifdef Q_OS_INTEGRITY - class QShaderVersion; - size_t qHash(const QShaderVersion &, size_t = 0) noexcept; -#endif - -class Q_GUI_EXPORT QShaderVersion +struct Q_GUI_EXPORT QShaderPrivate { -public: - enum Flag { - GlslEs = 0x01 + static const int QSB_VERSION = 9; + static const int QSB_VERSION_WITHOUT_INPUT_OUTPUT_INTERFACE_BLOCKS = 8; + static const int QSB_VERSION_WITHOUT_EXTENDED_STORAGE_BUFFER_INFO = 7; + static const int QSB_VERSION_WITHOUT_NATIVE_SHADER_INFO = 6; + static const int QSB_VERSION_WITHOUT_SEPARATE_IMAGES_AND_SAMPLERS = 5; + static const int QSB_VERSION_WITHOUT_VAR_ARRAYDIMS = 4; + static const int QSB_VERSION_WITH_CBOR = 3; + static const int QSB_VERSION_WITH_BINARY_JSON = 2; + static const int QSB_VERSION_WITHOUT_BINDINGS = 1; + + enum MslNativeShaderInfoExtraBufferBindings { + MslTessVertIndicesBufferBinding = 0, + MslTessVertTescOutputBufferBinding, + MslTessTescTessLevelBufferBinding, + MslTessTescPatchOutputBufferBinding, + MslTessTescParamsBufferBinding, + MslTessTescInputBufferBinding, + MslBufferSizeBufferBinding }; - Q_DECLARE_FLAGS(Flags, Flag) - QShaderVersion() = default; - QShaderVersion(int v, Flags f = Flags()); + QShaderPrivate() + : ref(1) + { + } - int version() const { return m_version; } - void setVersion(int v) { m_version = v; } + QShaderPrivate(const QShaderPrivate &other) + : ref(1), + qsbVersion(other.qsbVersion), + stage(other.stage), + desc(other.desc), + shaders(other.shaders), + bindings(other.bindings), + combinedImageMap(other.combinedImageMap), + nativeShaderInfoMap(other.nativeShaderInfoMap) + { + } - Flags flags() const { return m_flags; } - void setFlags(Flags f) { m_flags = f; } + static QShaderPrivate *get(QShader *s) { return s->d; } + static const QShaderPrivate *get(const QShader *s) { return s->d; } + static int qtQsbVersion(QShader::SerializedFormatVersion qtVersion) { + switch (qtVersion) { + case QShader::SerializedFormatVersion::Qt_6_4: + return (QShaderPrivate::QSB_VERSION_WITHOUT_SEPARATE_IMAGES_AND_SAMPLERS + 1); + case QShader::SerializedFormatVersion::Qt_6_5: + return (QShaderPrivate::QSB_VERSION_WITHOUT_EXTENDED_STORAGE_BUFFER_INFO + 1); + default: + return QShaderPrivate::QSB_VERSION; + } + } -private: - int m_version = 100; - Flags m_flags; + QAtomicInt ref; + int qsbVersion = QSB_VERSION; + QShader::Stage stage = QShader::VertexStage; + QShaderDescription desc; + // QMap not QHash because we need to be able to iterate based on sorted keys + QMap shaders; + QMap bindings; + QMap combinedImageMap; + QMap nativeShaderInfoMap; }; -Q_DECLARE_OPERATORS_FOR_FLAGS(QShaderVersion::Flags) -Q_DECLARE_TYPEINFO(QShaderVersion, Q_RELOCATABLE_TYPE); - -class QShaderCode; -Q_GUI_EXPORT size_t qHash(const QShaderCode &, size_t = 0) noexcept; - -class Q_GUI_EXPORT QShaderCode -{ -public: - QShaderCode() = default; - QShaderCode(const QByteArray &code, const QByteArray &entry = QByteArray()); - - QByteArray shader() const { return m_shader; } - void setShader(const QByteArray &code) { m_shader = code; } - - QByteArray entryPoint() const { return m_entryPoint; } - void setEntryPoint(const QByteArray &entry) { m_entryPoint = entry; } - -private: - friend Q_GUI_EXPORT size_t qHash(const QShaderCode &, size_t) noexcept; - - QByteArray m_shader; - QByteArray m_entryPoint; -}; - -Q_DECLARE_TYPEINFO(QShaderCode, Q_RELOCATABLE_TYPE); - -class Q_GUI_EXPORT QShader -{ -public: - enum Stage { - VertexStage = 0, - TessellationControlStage, - TessellationEvaluationStage, - GeometryStage, - FragmentStage, - ComputeStage - }; - - enum Source { - SpirvShader = 0, - GlslShader, - HlslShader, - DxbcShader, // fxc - MslShader, - DxilShader, // dxc - MetalLibShader, // xcrun metal + xcrun metallib - WgslShader - }; - - enum Variant { - StandardShader = 0, - BatchableVertexShader, - UInt16IndexedVertexAsComputeShader, - UInt32IndexedVertexAsComputeShader, - NonIndexedVertexAsComputeShader - }; - - enum class SerializedFormatVersion { - Latest = 0, - Qt_6_5, - Qt_6_4 - }; - - QShader(); - QShader(const QShader &other); - QShader &operator=(const QShader &other); - ~QShader(); - void detach(); - - bool isValid() const; - - Stage stage() const; - void setStage(Stage stage); - - QShaderDescription description() const; - void setDescription(const QShaderDescription &desc); - - QList availableShaders() const; - QShaderCode shader(const QShaderKey &key) const; - void setShader(const QShaderKey &key, const QShaderCode &shader); - void removeShader(const QShaderKey &key); - - QByteArray serialized(SerializedFormatVersion version = SerializedFormatVersion::Latest) const; - static QShader fromSerialized(const QByteArray &data); - - using NativeResourceBindingMap = QMap >; // binding -> native_binding[, native_binding] - NativeResourceBindingMap nativeResourceBindingMap(const QShaderKey &key) const; - void setResourceBindingMap(const QShaderKey &key, const NativeResourceBindingMap &map); - void removeResourceBindingMap(const QShaderKey &key); - - struct SeparateToCombinedImageSamplerMapping { - QByteArray combinedSamplerName; - int textureBinding; - int samplerBinding; - }; - using SeparateToCombinedImageSamplerMappingList = QList; - SeparateToCombinedImageSamplerMappingList separateToCombinedImageSamplerMappingList(const QShaderKey &key) const; - void setSeparateToCombinedImageSamplerMappingList(const QShaderKey &key, - const SeparateToCombinedImageSamplerMappingList &list); - void removeSeparateToCombinedImageSamplerMappingList(const QShaderKey &key); - - struct NativeShaderInfo { - int flags = 0; - QMap extraBufferBindings; - }; - NativeShaderInfo nativeShaderInfo(const QShaderKey &key) const; - void setNativeShaderInfo(const QShaderKey &key, const NativeShaderInfo &info); - void removeNativeShaderInfo(const QShaderKey &key); - -private: - QShaderPrivate *d; - friend struct QShaderPrivate; - friend Q_GUI_EXPORT bool operator==(const QShader &, const QShader &) noexcept; - friend Q_GUI_EXPORT size_t qHash(const QShader &, size_t) noexcept; -#ifndef QT_NO_DEBUG_STREAM - friend Q_GUI_EXPORT QDebug operator<<(QDebug, const QShader &); -#endif -}; - -class Q_GUI_EXPORT QShaderKey -{ -public: - QShaderKey() = default; - QShaderKey(QShader::Source s, - const QShaderVersion &sver, - QShader::Variant svar = QShader::StandardShader); - - QShader::Source source() const { return m_source; } - void setSource(QShader::Source s) { m_source = s; } - - QShaderVersion sourceVersion() const { return m_sourceVersion; } - void setSourceVersion(const QShaderVersion &sver) { m_sourceVersion = sver; } - - QShader::Variant sourceVariant() const { return m_sourceVariant; } - void setSourceVariant(QShader::Variant svar) { m_sourceVariant = svar; } - -private: - QShader::Source m_source = QShader::SpirvShader; - QShaderVersion m_sourceVersion; - QShader::Variant m_sourceVariant = QShader::StandardShader; -}; - -Q_DECLARE_TYPEINFO(QShaderKey, Q_RELOCATABLE_TYPE); - -Q_GUI_EXPORT bool operator==(const QShader &lhs, const QShader &rhs) noexcept; -Q_GUI_EXPORT size_t qHash(const QShader &s, size_t seed = 0) noexcept; - -inline bool operator!=(const QShader &lhs, const QShader &rhs) noexcept -{ - return !(lhs == rhs); -} - -Q_GUI_EXPORT bool operator==(const QShaderVersion &lhs, const QShaderVersion &rhs) noexcept; -Q_GUI_EXPORT bool operator<(const QShaderVersion &lhs, const QShaderVersion &rhs) noexcept; -Q_GUI_EXPORT bool operator==(const QShaderKey &lhs, const QShaderKey &rhs) noexcept; -Q_GUI_EXPORT bool operator<(const QShaderKey &lhs, const QShaderKey &rhs) noexcept; -Q_GUI_EXPORT bool operator==(const QShaderCode &lhs, const QShaderCode &rhs) noexcept; - -inline bool operator!=(const QShaderVersion &lhs, const QShaderVersion &rhs) noexcept -{ - return !(lhs == rhs); -} - -inline bool operator!=(const QShaderKey &lhs, const QShaderKey &rhs) noexcept -{ - return !(lhs == rhs); -} - -inline bool operator!=(const QShaderCode &lhs, const QShaderCode &rhs) noexcept -{ - return !(lhs == rhs); -} - -Q_GUI_EXPORT size_t qHash(const QShaderKey &k, size_t seed = 0) noexcept; - -#ifndef QT_NO_DEBUG_STREAM -Q_GUI_EXPORT QDebug operator<<(QDebug, const QShader &); -Q_GUI_EXPORT QDebug operator<<(QDebug dbg, const QShaderKey &k); -Q_GUI_EXPORT QDebug operator<<(QDebug dbg, const QShaderVersion &v); -#endif - QT_END_NAMESPACE #endif diff --git a/src/gui/rhi/qshader_p_p.h b/src/gui/rhi/qshader_p_p.h deleted file mode 100644 index 58fb2072..00000000 --- a/src/gui/rhi/qshader_p_p.h +++ /dev/null @@ -1,88 +0,0 @@ -// Copyright (C) 2019 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 QSHADER_P_P_H -#define QSHADER_P_P_H - -// -// W A R N I N G -// ------------- -// -// This file is not part of the Qt API. It exists for the convenience -// of a number of Qt sources files. This header file may change from -// version to version without notice, or even be removed. -// -// We mean it. -// - -#include "qshader_p.h" -#include -#include -#include - -QT_BEGIN_NAMESPACE - -struct Q_GUI_EXPORT QShaderPrivate -{ - static const int QSB_VERSION = 8; - static const int QSB_VERSION_WITHOUT_EXTENDED_STORAGE_BUFFER_INFO = 7; - static const int QSB_VERSION_WITHOUT_NATIVE_SHADER_INFO = 6; - static const int QSB_VERSION_WITHOUT_SEPARATE_IMAGES_AND_SAMPLERS = 5; - static const int QSB_VERSION_WITHOUT_VAR_ARRAYDIMS = 4; - static const int QSB_VERSION_WITH_CBOR = 3; - static const int QSB_VERSION_WITH_BINARY_JSON = 2; - static const int QSB_VERSION_WITHOUT_BINDINGS = 1; - - enum MslNativeShaderInfoExtraBufferBindings { - MslTessVertIndicesBufferBinding = 0, - MslTessVertTescOutputBufferBinding, - MslTessTescTessLevelBufferBinding, - MslTessTescPatchOutputBufferBinding, - MslTessTescParamsBufferBinding, - MslTessTescInputBufferBinding - }; - - QShaderPrivate() - : ref(1) - { - } - - QShaderPrivate(const QShaderPrivate &other) - : ref(1), - qsbVersion(other.qsbVersion), - stage(other.stage), - desc(other.desc), - shaders(other.shaders), - bindings(other.bindings), - combinedImageMap(other.combinedImageMap), - nativeShaderInfoMap(other.nativeShaderInfoMap) - { - } - - static QShaderPrivate *get(QShader *s) { return s->d; } - static const QShaderPrivate *get(const QShader *s) { return s->d; } - static int qtQsbVersion(QShader::SerializedFormatVersion qtVersion) { - switch (qtVersion) { - case QShader::SerializedFormatVersion::Qt_6_4: - return (QShaderPrivate::QSB_VERSION_WITHOUT_SEPARATE_IMAGES_AND_SAMPLERS + 1); - case QShader::SerializedFormatVersion::Qt_6_5: - return (QShaderPrivate::QSB_VERSION_WITHOUT_EXTENDED_STORAGE_BUFFER_INFO + 1); - default: - return QShaderPrivate::QSB_VERSION; - } - } - - QAtomicInt ref; - int qsbVersion = QSB_VERSION; - QShader::Stage stage = QShader::VertexStage; - QShaderDescription desc; - // QMap not QHash because we need to be able to iterate based on sorted keys - QMap shaders; - QMap bindings; - QMap combinedImageMap; - QMap nativeShaderInfoMap; -}; - -QT_END_NAMESPACE - -#endif diff --git a/src/gui/rhi/qshaderdescription.cpp b/src/gui/rhi/qshaderdescription.cpp index ca697d8e..f64daf02 100644 --- a/src/gui/rhi/qshaderdescription.cpp +++ b/src/gui/rhi/qshaderdescription.cpp @@ -1,8 +1,8 @@ -// Copyright (C) 2019 The Qt Company Ltd. +// 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 "qshaderdescription_p_p.h" -#include "qshader_p_p.h" +#include "qshaderdescription_p.h" +#include "qshader_p.h" #include #include #include @@ -12,11 +12,22 @@ QT_BEGIN_NAMESPACE /*! \class QShaderDescription - \internal + \ingroup painting-3D \inmodule QtGui + \since 6.6 \brief Describes the interface of a shader. + \warning The QRhi family of classes in the Qt Gui module, including QShader + and QShaderDescription, offer limited compatibility guarantees. There are + no source or binary compatibility guarantees for these classes, meaning the + API is only guaranteed to work with the Qt version the application was + developed against. Source incompatible changes are however aimed to be kept + at a minimum and will only be made in minor releases (6.7, 6.8, and so on). + To use these classes in an application, link to + \c{Qt::GuiPrivate} (if using CMake), and include the headers with the \c + rhi prefix, for example \c{#include }. + A shader typically has a set of inputs and outputs. A vertex shader for example has a number of input variables and may use one or more uniform buffers to access data (e.g. a modelview matrix) provided by the @@ -51,8 +62,6 @@ QT_BEGIN_NAMESPACE float opacity; } ubuf; - out gl_PerVertex { vec4 gl_Position; }; - void main() { v_color = color; @@ -200,28 +209,179 @@ QT_BEGIN_NAMESPACE \value ImageRect \value ImageBuffer \value Struct + \value Half + \value Half2 + \value Half3 + \value Half4 */ /*! - \class QShaderDescription::InOutVariable - \internal + \enum QShaderDescription::ImageFormat + Image format. + + \value ImageFormatUnknown + \value ImageFormatRgba32f + \value ImageFormatRgba16f + \value ImageFormatR32f + \value ImageFormatRgba8 + \value ImageFormatRgba8Snorm + \value ImageFormatRg32f + \value ImageFormatRg16f + \value ImageFormatR11fG11fB10f + \value ImageFormatR16f + \value ImageFormatRgba16 + \value ImageFormatRgb10A2 + \value ImageFormatRg16 + \value ImageFormatRg8 + \value ImageFormatR16 + \value ImageFormatR8 + \value ImageFormatRgba16Snorm + \value ImageFormatRg16Snorm + \value ImageFormatRg8Snorm + \value ImageFormatR16Snorm + \value ImageFormatR8Snorm + \value ImageFormatRgba32i + \value ImageFormatRgba16i + \value ImageFormatRgba8i + \value ImageFormatR32i + \value ImageFormatRg32i + \value ImageFormatRg16i + \value ImageFormatRg8i + \value ImageFormatR16i + \value ImageFormatR8i + \value ImageFormatRgba32ui + \value ImageFormatRgba16ui + \value ImageFormatRgba8ui + \value ImageFormatR32ui + \value ImageFormatRgb10a2ui + \value ImageFormatRg32ui + \value ImageFormatRg16ui + \value ImageFormatRg8ui + \value ImageFormatR16ui + \value ImageFormatR8ui + */ + +/*! + \enum QShaderDescription::ImageFlag + Image flags. + + \value ReadOnlyImage + \value WriteOnlyImage + */ + +/*! + \enum QShaderDescription::QualifierFlag + Qualifier flags. + + \value QualifierReadOnly + \value QualifierWriteOnly + \value QualifierCoherent + \value QualifierVolatile + \value QualifierRestrict + */ + +/*! + \struct QShaderDescription::InOutVariable \inmodule QtGui + \since 6.6 \brief Describes an input or output variable in the shader. + + \note This is a RHI API with limited compatibility guarantees, see \l QShaderDescription + for details. */ /*! - \class QShaderDescription::BlockVariable - \internal + \variable QShaderDescription::InOutVariable::name + */ + +/*! + \variable QShaderDescription::InOutVariable::type + */ + +/*! + \variable QShaderDescription::InOutVariable::location + */ + +/*! + \variable QShaderDescription::InOutVariable::binding + */ + +/*! + \variable QShaderDescription::InOutVariable::descriptorSet + */ + +/*! + \variable QShaderDescription::InOutVariable::imageFormat + */ + +/*! + \variable QShaderDescription::InOutVariable::imageFlags + */ + +/*! + \variable QShaderDescription::InOutVariable::arrayDims + */ + +/*! + \variable QShaderDescription::InOutVariable::perPatch + */ + +/*! + \variable QShaderDescription::InOutVariable::structMembers + */ + +/*! + \struct QShaderDescription::BlockVariable \inmodule QtGui + \since 6.6 \brief Describes a member of a uniform or push constant block. + + \note This is a RHI API with limited compatibility guarantees, see \l QShaderDescription + for details. */ /*! - \class QShaderDescription::UniformBlock - \internal + \variable QShaderDescription::BlockVariable::name + */ + +/*! + \variable QShaderDescription::BlockVariable::type + */ + +/*! + \variable QShaderDescription::BlockVariable::offset + */ + +/*! + \variable QShaderDescription::BlockVariable::size + */ + +/*! + \variable QShaderDescription::BlockVariable::arrayDims + */ + +/*! + \variable QShaderDescription::BlockVariable::arrayStride + */ + +/*! + \variable QShaderDescription::BlockVariable::matrixStride + */ + +/*! + \variable QShaderDescription::BlockVariable::matrixIsRowMajor + */ + +/*! + \variable QShaderDescription::BlockVariable::structMembers + */ + +/*! + \struct QShaderDescription::UniformBlock \inmodule QtGui + \since 6.6 \brief Describes a uniform block. @@ -229,22 +389,157 @@ QT_BEGIN_NAMESPACE (like GLSL 120 or GLSL/ES 100), uniform blocks are replaced with ordinary uniforms in a struct. The name of the struct, and so the prefix for the uniforms generated from the block members, is given by structName. + + \note This is a RHI API with limited compatibility guarantees, see \l QShaderDescription + for details. */ /*! - \class QShaderDescription::PushConstantBlock - \internal + \variable QShaderDescription::UniformBlock::blockName + */ + +/*! + \variable QShaderDescription::UniformBlock::structName + */ + +/*! + \variable QShaderDescription::UniformBlock::size + */ + +/*! + \variable QShaderDescription::UniformBlock::binding + */ + +/*! + \variable QShaderDescription::UniformBlock::descriptorSet + */ + +/*! + \variable QShaderDescription::UniformBlock::members + */ + +/*! + \struct QShaderDescription::PushConstantBlock \inmodule QtGui + \since 6.6 \brief Describes a push constant block. + + \note This is a RHI API with limited compatibility guarantees, see \l QShaderDescription + for details. */ /*! - \class QShaderDescription::StorageBlock - \internal + \variable QShaderDescription::PushConstantBlock::name + */ + +/*! + \variable QShaderDescription::PushConstantBlock::size + */ + +/*! + \variable QShaderDescription::PushConstantBlock::members + */ + +/*! + \struct QShaderDescription::StorageBlock \inmodule QtGui + \since 6.6 \brief Describes a shader storage block. + + \note This is a RHI API with limited compatibility guarantees, see \l QShaderDescription + for details. + */ + +/*! + \variable QShaderDescription::StorageBlock::blockName + */ + +/*! + \variable QShaderDescription::StorageBlock::instanceName + */ + +/*! + \variable QShaderDescription::StorageBlock::knownSize + */ + +/*! + \variable QShaderDescription::StorageBlock::binding + */ + +/*! + \variable QShaderDescription::StorageBlock::descriptorSet + */ + +/*! + \variable QShaderDescription::StorageBlock::members + */ + +/*! + \variable QShaderDescription::StorageBlock::runtimeArrayStride + */ + +/*! + \variable QShaderDescription::StorageBlock::qualifierFlags + */ + +/*! + \struct QShaderDescription::BuiltinVariable + \inmodule QtGui + \since 6.6 + + \brief Describes a built-in variable. + + \note This is a RHI API with limited compatibility guarantees, see \l QShaderDescription + for details. + */ + +/*! + \variable QShaderDescription::BuiltinVariable::type + */ + +/*! + \variable QShaderDescription::BuiltinVariable::varType + */ + +/*! + \variable QShaderDescription::BuiltinVariable::arrayDims + */ + +/*! + \enum QShaderDescription::BuiltinType + Built-in variable type. + + \value PositionBuiltin + \value PointSizeBuiltin + \value ClipDistanceBuiltin + \value CullDistanceBuiltin + \value VertexIdBuiltin + \value InstanceIdBuiltin + \value PrimitiveIdBuiltin + \value InvocationIdBuiltin + \value LayerBuiltin + \value ViewportIndexBuiltin + \value TessLevelOuterBuiltin + \value TessLevelInnerBuiltin + \value TessCoordBuiltin + \value PatchVerticesBuiltin + \value FragCoordBuiltin + \value PointCoordBuiltin + \value FrontFacingBuiltin + \value SampleIdBuiltin + \value SamplePositionBuiltin + \value SampleMaskBuiltin + \value FragDepthBuiltin + \value NumWorkGroupsBuiltin + \value WorkgroupSizeBuiltin + \value WorkgroupIdBuiltin + \value LocalInvocationIdBuiltin + \value GlobalInvocationIdBuiltin + \value LocalInvocationIndexBuiltin + \value VertexIndexBuiltin + \value InstanceIndexBuiltin */ /*! @@ -267,7 +562,7 @@ void QShaderDescription::detach() } /*! - \internal + Constructs a copy of \a other. */ QShaderDescription::QShaderDescription(const QShaderDescription &other) : d(other.d) @@ -276,7 +571,7 @@ QShaderDescription::QShaderDescription(const QShaderDescription &other) } /*! - \internal + Assigns \a other to this object. */ QShaderDescription &QShaderDescription::operator=(const QShaderDescription &other) { @@ -574,7 +869,7 @@ uint QShaderDescription::tessellationOutputVertexCount() const \value UnknownTessellationMode \value TrianglesTessellationMode \value QuadTessellationMode - \value IsolinesTessellationMode + \value IsolineTessellationMode */ /*! @@ -724,8 +1019,12 @@ static const struct TypeTab { { "image3DArray", QShaderDescription::Image3DArray }, { "imageCubeArray", QShaderDescription::ImageCubeArray }, { "imageRect", QShaderDescription::ImageRect }, - { "imageBuffer", QShaderDescription::ImageBuffer } -}; + { "imageBuffer", QShaderDescription::ImageBuffer }, + + { "half", QShaderDescription::Half }, + { "half2", QShaderDescription::Half2 }, + { "half3", QShaderDescription::Half3 }, + { "half4", QShaderDescription::Half4 } }; static QLatin1StringView typeStr(QShaderDescription::VariableType t) { @@ -936,6 +1235,8 @@ QDebug operator<<(QDebug dbg, const QShaderDescription::InOutVariable &var) dbg.nospace() << " imageFlags=" << var.imageFlags; if (!var.arrayDims.isEmpty()) dbg.nospace() << " array=" << var.arrayDims; + if (!var.structMembers.isEmpty()) + dbg.nospace() << " structMembers=" << var.structMembers; dbg.nospace() << ')'; return dbg; } @@ -943,8 +1244,10 @@ QDebug operator<<(QDebug dbg, const QShaderDescription::InOutVariable &var) QDebug operator<<(QDebug dbg, const QShaderDescription::BlockVariable &var) { QDebugStateSaver saver(dbg); - dbg.nospace() << "BlockVariable(" << typeStr(var.type) << ' ' << var.name - << " offset=" << var.offset << " size=" << var.size; + dbg.nospace() << "BlockVariable(" << typeStr(var.type) << ' ' << var.name; + if (var.offset != -1) + dbg.nospace() << " offset=" << var.offset; + dbg.nospace() << " size=" << var.size; if (!var.arrayDims.isEmpty()) dbg.nospace() << " array=" << var.arrayDims; if (var.arrayStride) @@ -1000,7 +1303,11 @@ QDebug operator<<(QDebug dbg, const QShaderDescription::StorageBlock &blk) QDebug operator<<(QDebug dbg, const QShaderDescription::BuiltinVariable &builtin) { QDebugStateSaver saver(dbg); - dbg.nospace() << "BuiltinVariable(type=" << builtinTypeStr(builtin.type) << ")"; + dbg.nospace() << "BuiltinVariable(type=" << builtinTypeStr(builtin.type); + dbg.nospace() << " varType=" << typeStr(builtin.varType); + if (!builtin.arrayDims.isEmpty()) + dbg.nospace() << " array=" << builtin.arrayDims; + dbg.nospace() << ")"; return dbg; } #endif @@ -1082,20 +1389,15 @@ static void serializeDecorations(QDataStream *stream, const QShaderDescription:: (*stream) << quint8(v.perPatch); } -static QJsonObject inOutObject(const QShaderDescription::InOutVariable &v) +static void serializeBuiltinVar(QDataStream *stream, const QShaderDescription::BuiltinVariable &v, int version) { - QJsonObject obj; - obj[nameKey()] = QString::fromUtf8(v.name); - obj[typeKey()] = typeStr(v.type); - addDeco(&obj, v); - return obj; -} - -static void serializeInOutVar(QDataStream *stream, const QShaderDescription::InOutVariable &v, int version) -{ - (*stream) << QString::fromUtf8(v.name); (*stream) << int(v.type); - serializeDecorations(stream, v, version); + if (version > QShaderPrivate::QSB_VERSION_WITHOUT_INPUT_OUTPUT_INTERFACE_BLOCKS) { + (*stream) << int(v.varType); + (*stream) << int(v.arrayDims.size()); + for (int dim : v.arrayDims) + (*stream) << dim; + } } static QJsonObject blockMemberObject(const QShaderDescription::BlockVariable &v) @@ -1103,7 +1405,8 @@ static QJsonObject blockMemberObject(const QShaderDescription::BlockVariable &v) QJsonObject obj; obj[nameKey()] = QString::fromUtf8(v.name); obj[typeKey()] = typeStr(v.type); - obj[offsetKey()] = v.offset; + if (v.offset != -1) + obj[offsetKey()] = v.offset; obj[sizeKey()] = v.size; if (!v.arrayDims.isEmpty()) { QJsonArray dimArr; @@ -1126,6 +1429,36 @@ static QJsonObject blockMemberObject(const QShaderDescription::BlockVariable &v) return obj; } +static QJsonObject inOutObject(const QShaderDescription::InOutVariable &v) +{ + QJsonObject obj; + obj[nameKey()] = QString::fromUtf8(v.name); + obj[typeKey()] = typeStr(v.type); + addDeco(&obj, v); + if (!v.structMembers.isEmpty()) { + QJsonArray arr; + for (const QShaderDescription::BlockVariable &sv : v.structMembers) + arr.append(blockMemberObject(sv)); + obj[structMembersKey()] = arr; + } + return obj; +} + +static QJsonObject builtinObject(const QShaderDescription::BuiltinVariable &v) +{ + QJsonObject obj; + + obj[nameKey()] = builtinTypeStr(v.type); + obj[typeKey()] = typeStr(v.varType); + if (!v.arrayDims.isEmpty()) { + QJsonArray dimArr; + for (int dim : v.arrayDims) + dimArr.append(dim); + obj[arrayDimsKey()] = dimArr; + } + return obj; +} + static void serializeBlockMemberVar(QDataStream *stream, const QShaderDescription::BlockVariable &v) { (*stream) << QString::fromUtf8(v.name); @@ -1143,6 +1476,19 @@ static void serializeBlockMemberVar(QDataStream *stream, const QShaderDescriptio serializeBlockMemberVar(stream, sv); } +static void serializeInOutVar(QDataStream *stream, const QShaderDescription::InOutVariable &v, + int version) +{ + (*stream) << QString::fromUtf8(v.name); + (*stream) << int(v.type); + serializeDecorations(stream, v, version); + if (version > QShaderPrivate::QSB_VERSION_WITHOUT_INPUT_OUTPUT_INTERFACE_BLOCKS) { + (*stream) << int(v.structMembers.size()); + for (const QShaderDescription::BlockVariable &sv : v.structMembers) + serializeBlockMemberVar(stream, sv); + } +} + QJsonDocument QShaderDescriptionPrivate::makeDoc() { QJsonObject root; @@ -1238,20 +1584,14 @@ QJsonDocument QShaderDescriptionPrivate::makeDoc() root[storageImagesKey()] = jstorageImages; QJsonArray jinBuiltins; - for (const QShaderDescription::BuiltinVariable &v : std::as_const(inBuiltins)) { - QJsonObject builtin; - builtin[typeKey()] = builtinTypeStr(v.type); - jinBuiltins.append(builtin); - } + for (const QShaderDescription::BuiltinVariable &v : std::as_const(inBuiltins)) + jinBuiltins.append(builtinObject(v)); if (!jinBuiltins.isEmpty()) root[inBuiltinsKey()] = jinBuiltins; QJsonArray joutBuiltins; - for (const QShaderDescription::BuiltinVariable &v : std::as_const(outBuiltins)) { - QJsonObject builtin; - builtin[typeKey()] = builtinTypeStr(v.type); - joutBuiltins.append(builtin); - } + for (const QShaderDescription::BuiltinVariable &v : std::as_const(outBuiltins)) + joutBuiltins.append(builtinObject(v)); if (!joutBuiltins.isEmpty()) root[outBuiltinsKey()] = joutBuiltins; @@ -1385,11 +1725,11 @@ void QShaderDescriptionPrivate::writeToStream(QDataStream *stream, int version) (*stream) << int(inBuiltins.size()); for (const QShaderDescription::BuiltinVariable &v : std::as_const(inBuiltins)) - (*stream) << int(v.type); + serializeBuiltinVar(stream, v, version); (*stream) << int(outBuiltins.size()); for (const QShaderDescription::BuiltinVariable &v : std::as_const(outBuiltins)) - (*stream) << int(v.type); + serializeBuiltinVar(stream, v, version); } } @@ -1418,16 +1758,21 @@ static void deserializeDecorations(QDataStream *stream, int version, QShaderDesc } } -static QShaderDescription::InOutVariable deserializeInOutVar(QDataStream *stream, int version) +static QShaderDescription::BuiltinVariable deserializeBuiltinVar(QDataStream *stream, int version) { - QShaderDescription::InOutVariable var; - QString tmp; - (*stream) >> tmp; - var.name = tmp.toUtf8(); + QShaderDescription::BuiltinVariable var; int t; (*stream) >> t; - var.type = QShaderDescription::VariableType(t); - deserializeDecorations(stream, version, &var); + var.type = QShaderDescription::BuiltinType(t); + if (version > QShaderPrivate::QSB_VERSION_WITHOUT_INPUT_OUTPUT_INTERFACE_BLOCKS) { + (*stream) >> t; + var.varType = QShaderDescription::VariableType(t); + int count; + (*stream) >> count; + var.arrayDims.resize(count); + for (int i = 0; i < count; ++i) + (*stream) >> var.arrayDims[i]; + } return var; } @@ -1457,6 +1802,26 @@ static QShaderDescription::BlockVariable deserializeBlockMemberVar(QDataStream * return var; } +static QShaderDescription::InOutVariable deserializeInOutVar(QDataStream *stream, int version) +{ + QShaderDescription::InOutVariable var; + QString tmp; + (*stream) >> tmp; + var.name = tmp.toUtf8(); + int t; + (*stream) >> t; + var.type = QShaderDescription::VariableType(t); + deserializeDecorations(stream, version, &var); + if (version > QShaderPrivate::QSB_VERSION_WITHOUT_INPUT_OUTPUT_INTERFACE_BLOCKS) { + int count; + (*stream) >> count; + var.structMembers.resize(count); + for (int i = 0; i < count; ++i) + var.structMembers[i] = deserializeBlockMemberVar(stream, version); + } + return var; +} + void QShaderDescriptionPrivate::loadFromStream(QDataStream *stream, int version) { Q_ASSERT(ref.loadRelaxed() == 1); // must be detached @@ -1596,19 +1961,13 @@ void QShaderDescriptionPrivate::loadFromStream(QDataStream *stream, int version) (*stream) >> count; inBuiltins.resize(count); - for (int i = 0; i < count; ++i) { - int t; - (*stream) >> t; - inBuiltins[i].type = QShaderDescription::BuiltinType(t); - } + for (int i = 0; i < count; ++i) + inBuiltins[i] = deserializeBuiltinVar(stream, version); (*stream) >> count; outBuiltins.resize(count); - for (int i = 0; i < count; ++i) { - int t; - (*stream) >> t; - outBuiltins[i].type = QShaderDescription::BuiltinType(t); - } + for (int i = 0; i < count; ++i) + outBuiltins[i] = deserializeBuiltinVar(stream, version); } } @@ -1657,7 +2016,8 @@ bool operator==(const QShaderDescription::InOutVariable &lhs, const QShaderDescr && lhs.imageFormat == rhs.imageFormat && lhs.imageFlags == rhs.imageFlags && lhs.arrayDims == rhs.arrayDims - && lhs.perPatch == rhs.perPatch; + && lhs.perPatch == rhs.perPatch + && lhs.structMembers == rhs.structMembers; } /*! @@ -1734,7 +2094,9 @@ bool operator==(const QShaderDescription::StorageBlock &lhs, const QShaderDescri */ bool operator==(const QShaderDescription::BuiltinVariable &lhs, const QShaderDescription::BuiltinVariable &rhs) noexcept { - return lhs.type == rhs.type; + return lhs.type == rhs.type + && lhs.varType == rhs.varType + && lhs.arrayDims == rhs.arrayDims; } QT_END_NAMESPACE diff --git a/src/gui/rhi/qshaderdescription.h b/src/gui/rhi/qshaderdescription.h new file mode 100644 index 00000000..02492a15 --- /dev/null +++ b/src/gui/rhi/qshaderdescription.h @@ -0,0 +1,386 @@ +// 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 QSHADERDESCRIPTION_H +#define QSHADERDESCRIPTION_H + +// +// W A R N I N G +// ------------- +// +// This file is part of the RHI API, with limited compatibility guarantees. +// Usage of this API may make your code source and binary incompatible with +// future versions of Qt. +// + +#include +#include +#include +#include + +QT_BEGIN_NAMESPACE + +struct QShaderDescriptionPrivate; +class QDataStream; + +class Q_GUI_EXPORT QShaderDescription +{ +public: + QShaderDescription(); + QShaderDescription(const QShaderDescription &other); + QShaderDescription &operator=(const QShaderDescription &other); + ~QShaderDescription(); + void detach(); + + bool isValid() const; + + void serialize(QDataStream *stream, int version) const; + QByteArray toJson() const; + + static QShaderDescription deserialize(QDataStream *stream, int version); + + enum VariableType { + Unknown = 0, + + // do not reorder + Float, + Vec2, + Vec3, + Vec4, + Mat2, + Mat2x3, + Mat2x4, + Mat3, + Mat3x2, + Mat3x4, + Mat4, + Mat4x2, + Mat4x3, + + Int, + Int2, + Int3, + Int4, + + Uint, + Uint2, + Uint3, + Uint4, + + Bool, + Bool2, + Bool3, + Bool4, + + Double, + Double2, + Double3, + Double4, + DMat2, + DMat2x3, + DMat2x4, + DMat3, + DMat3x2, + DMat3x4, + DMat4, + DMat4x2, + DMat4x3, + + Sampler1D, + Sampler2D, + Sampler2DMS, + Sampler3D, + SamplerCube, + Sampler1DArray, + Sampler2DArray, + Sampler2DMSArray, + Sampler3DArray, + SamplerCubeArray, + SamplerRect, + SamplerBuffer, + SamplerExternalOES, + Sampler, + + Image1D, + Image2D, + Image2DMS, + Image3D, + ImageCube, + Image1DArray, + Image2DArray, + Image2DMSArray, + Image3DArray, + ImageCubeArray, + ImageRect, + ImageBuffer, + + Struct, + + Half, + Half2, + Half3, + Half4 + }; + + enum ImageFormat { + // must match SPIR-V's ImageFormat + ImageFormatUnknown = 0, + ImageFormatRgba32f = 1, + ImageFormatRgba16f = 2, + ImageFormatR32f = 3, + ImageFormatRgba8 = 4, + ImageFormatRgba8Snorm = 5, + ImageFormatRg32f = 6, + ImageFormatRg16f = 7, + ImageFormatR11fG11fB10f = 8, + ImageFormatR16f = 9, + ImageFormatRgba16 = 10, + ImageFormatRgb10A2 = 11, + ImageFormatRg16 = 12, + ImageFormatRg8 = 13, + ImageFormatR16 = 14, + ImageFormatR8 = 15, + ImageFormatRgba16Snorm = 16, + ImageFormatRg16Snorm = 17, + ImageFormatRg8Snorm = 18, + ImageFormatR16Snorm = 19, + ImageFormatR8Snorm = 20, + ImageFormatRgba32i = 21, + ImageFormatRgba16i = 22, + ImageFormatRgba8i = 23, + ImageFormatR32i = 24, + ImageFormatRg32i = 25, + ImageFormatRg16i = 26, + ImageFormatRg8i = 27, + ImageFormatR16i = 28, + ImageFormatR8i = 29, + ImageFormatRgba32ui = 30, + ImageFormatRgba16ui = 31, + ImageFormatRgba8ui = 32, + ImageFormatR32ui = 33, + ImageFormatRgb10a2ui = 34, + ImageFormatRg32ui = 35, + ImageFormatRg16ui = 36, + ImageFormatRg8ui = 37, + ImageFormatR16ui = 38, + ImageFormatR8ui = 39 + }; + + enum ImageFlag { + ReadOnlyImage = 1 << 0, + WriteOnlyImage = 1 << 1 + }; + Q_DECLARE_FLAGS(ImageFlags, ImageFlag) + + enum QualifierFlag { + QualifierReadOnly = 1 << 0, + QualifierWriteOnly = 1 << 1, + QualifierCoherent = 1 << 2, + QualifierVolatile = 1 << 3, + QualifierRestrict = 1 << 4, + }; + Q_DECLARE_FLAGS(QualifierFlags, QualifierFlag) + + // Optional data (like decorations) usually default to an otherwise invalid value (-1 or 0). This is intentional. + + struct BlockVariable { + QByteArray name; + VariableType type = Unknown; + int offset = 0; + int size = 0; + QList arrayDims; + int arrayStride = 0; + int matrixStride = 0; + bool matrixIsRowMajor = false; + QList structMembers; + }; + + struct InOutVariable { + QByteArray name; + VariableType type = Unknown; + int location = -1; + int binding = -1; + int descriptorSet = -1; + ImageFormat imageFormat = ImageFormatUnknown; + ImageFlags imageFlags; + QList arrayDims; + bool perPatch = false; + QList structMembers; + }; + + struct UniformBlock { + QByteArray blockName; + QByteArray structName; // instanceName + int size = 0; + int binding = -1; + int descriptorSet = -1; + QList members; + }; + + struct PushConstantBlock { + QByteArray name; + int size = 0; + QList members; + }; + + struct StorageBlock { + QByteArray blockName; + QByteArray instanceName; + int knownSize = 0; + int binding = -1; + int descriptorSet = -1; + QList members; + int runtimeArrayStride = 0; + QualifierFlags qualifierFlags; + }; + + QList inputVariables() const; + QList outputVariables() const; + QList uniformBlocks() const; + QList pushConstantBlocks() const; + QList storageBlocks() const; + QList combinedImageSamplers() const; + QList separateImages() const; + QList separateSamplers() const; + QList storageImages() const; + + enum BuiltinType { + // must match SpvBuiltIn + PositionBuiltin = 0, + PointSizeBuiltin = 1, + ClipDistanceBuiltin = 3, + CullDistanceBuiltin = 4, + VertexIdBuiltin = 5, + InstanceIdBuiltin = 6, + PrimitiveIdBuiltin = 7, + InvocationIdBuiltin = 8, + LayerBuiltin = 9, + ViewportIndexBuiltin = 10, + TessLevelOuterBuiltin = 11, + TessLevelInnerBuiltin = 12, + TessCoordBuiltin = 13, + PatchVerticesBuiltin = 14, + FragCoordBuiltin = 15, + PointCoordBuiltin = 16, + FrontFacingBuiltin = 17, + SampleIdBuiltin = 18, + SamplePositionBuiltin = 19, + SampleMaskBuiltin = 20, + FragDepthBuiltin = 22, + NumWorkGroupsBuiltin = 24, + WorkgroupSizeBuiltin = 25, + WorkgroupIdBuiltin = 26, + LocalInvocationIdBuiltin = 27, + GlobalInvocationIdBuiltin = 28, + LocalInvocationIndexBuiltin = 29, + VertexIndexBuiltin = 42, + InstanceIndexBuiltin = 43 + }; + + struct BuiltinVariable { + BuiltinType type; + VariableType varType; + QList arrayDims; + }; + + QList inputBuiltinVariables() const; + QList outputBuiltinVariables() const; + + std::array computeShaderLocalSize() const; + + uint tessellationOutputVertexCount() const; + + enum TessellationMode { + UnknownTessellationMode, + TrianglesTessellationMode, + QuadTessellationMode, + IsolineTessellationMode + }; + + TessellationMode tessellationMode() const; + + enum TessellationWindingOrder { + UnknownTessellationWindingOrder, + CwTessellationWindingOrder, + CcwTessellationWindingOrder + }; + + TessellationWindingOrder tessellationWindingOrder() const; + + enum TessellationPartitioning { + UnknownTessellationPartitioning, + EqualTessellationPartitioning, + FractionalEvenTessellationPartitioning, + FractionalOddTessellationPartitioning + }; + + TessellationPartitioning tessellationPartitioning() const; + +private: + QShaderDescriptionPrivate *d; + friend struct QShaderDescriptionPrivate; +#ifndef QT_NO_DEBUG_STREAM + friend Q_GUI_EXPORT QDebug operator<<(QDebug, const QShaderDescription &); +#endif + friend Q_GUI_EXPORT bool operator==(const QShaderDescription &lhs, const QShaderDescription &rhs) noexcept; +}; + +Q_DECLARE_OPERATORS_FOR_FLAGS(QShaderDescription::ImageFlags) +Q_DECLARE_OPERATORS_FOR_FLAGS(QShaderDescription::QualifierFlags) + +#ifndef QT_NO_DEBUG_STREAM +Q_GUI_EXPORT QDebug operator<<(QDebug, const QShaderDescription &); +Q_GUI_EXPORT QDebug operator<<(QDebug, const QShaderDescription::InOutVariable &); +Q_GUI_EXPORT QDebug operator<<(QDebug, const QShaderDescription::BlockVariable &); +Q_GUI_EXPORT QDebug operator<<(QDebug, const QShaderDescription::UniformBlock &); +Q_GUI_EXPORT QDebug operator<<(QDebug, const QShaderDescription::PushConstantBlock &); +Q_GUI_EXPORT QDebug operator<<(QDebug, const QShaderDescription::StorageBlock &); +Q_GUI_EXPORT QDebug operator<<(QDebug, const QShaderDescription::BuiltinVariable &); +#endif + +Q_GUI_EXPORT bool operator==(const QShaderDescription &lhs, const QShaderDescription &rhs) noexcept; +Q_GUI_EXPORT bool operator==(const QShaderDescription::InOutVariable &lhs, const QShaderDescription::InOutVariable &rhs) noexcept; +Q_GUI_EXPORT bool operator==(const QShaderDescription::BlockVariable &lhs, const QShaderDescription::BlockVariable &rhs) noexcept; +Q_GUI_EXPORT bool operator==(const QShaderDescription::UniformBlock &lhs, const QShaderDescription::UniformBlock &rhs) noexcept; +Q_GUI_EXPORT bool operator==(const QShaderDescription::PushConstantBlock &lhs, const QShaderDescription::PushConstantBlock &rhs) noexcept; +Q_GUI_EXPORT bool operator==(const QShaderDescription::StorageBlock &lhs, const QShaderDescription::StorageBlock &rhs) noexcept; +Q_GUI_EXPORT bool operator==(const QShaderDescription::BuiltinVariable &lhs, const QShaderDescription::BuiltinVariable &rhs) noexcept; + +inline bool operator!=(const QShaderDescription &lhs, const QShaderDescription &rhs) noexcept +{ + return !(lhs == rhs); +} + +inline bool operator!=(const QShaderDescription::InOutVariable &lhs, const QShaderDescription::InOutVariable &rhs) noexcept +{ + return !(lhs == rhs); +} + +inline bool operator!=(const QShaderDescription::BlockVariable &lhs, const QShaderDescription::BlockVariable &rhs) noexcept +{ + return !(lhs == rhs); +} + +inline bool operator!=(const QShaderDescription::UniformBlock &lhs, const QShaderDescription::UniformBlock &rhs) noexcept +{ + return !(lhs == rhs); +} + +inline bool operator!=(const QShaderDescription::PushConstantBlock &lhs, const QShaderDescription::PushConstantBlock &rhs) noexcept +{ + return !(lhs == rhs); +} + +inline bool operator!=(const QShaderDescription::StorageBlock &lhs, const QShaderDescription::StorageBlock &rhs) noexcept +{ + return !(lhs == rhs); +} + +inline bool operator!=(const QShaderDescription::BuiltinVariable &lhs, const QShaderDescription::BuiltinVariable &rhs) noexcept +{ + return !(lhs == rhs); +} + +QT_END_NAMESPACE + +#endif diff --git a/src/gui/rhi/qshaderdescription_p.h b/src/gui/rhi/qshaderdescription_p.h index 13e72ad1..df694bbf 100644 --- a/src/gui/rhi/qshaderdescription_p.h +++ b/src/gui/rhi/qshaderdescription_p.h @@ -1,8 +1,8 @@ -// Copyright (C) 2021 The Qt Company Ltd. +// 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 QSHADERDESCRIPTION_H -#define QSHADERDESCRIPTION_H +#ifndef QSHADERDESCRIPTION_P_H +#define QSHADERDESCRIPTION_P_H // // W A R N I N G @@ -15,367 +15,67 @@ // We mean it. // -#include -#include +#include #include -#include -#include +#include +#include QT_BEGIN_NAMESPACE -struct QShaderDescriptionPrivate; -class QDataStream; - -class Q_GUI_EXPORT QShaderDescription +struct Q_GUI_EXPORT QShaderDescriptionPrivate { -public: - QShaderDescription(); - QShaderDescription(const QShaderDescription &other); - QShaderDescription &operator=(const QShaderDescription &other); - ~QShaderDescription(); - void detach(); + QShaderDescriptionPrivate() + : ref(1) + { + } - bool isValid() const; + QShaderDescriptionPrivate(const QShaderDescriptionPrivate &other) + : ref(1), + inVars(other.inVars), + outVars(other.outVars), + uniformBlocks(other.uniformBlocks), + pushConstantBlocks(other.pushConstantBlocks), + storageBlocks(other.storageBlocks), + combinedImageSamplers(other.combinedImageSamplers), + separateImages(other.separateImages), + separateSamplers(other.separateSamplers), + storageImages(other.storageImages), + inBuiltins(other.inBuiltins), + outBuiltins(other.outBuiltins), + localSize(other.localSize), + tessOutVertCount(other.tessOutVertCount), + tessMode(other.tessMode), + tessWind(other.tessWind), + tessPart(other.tessPart) + { + } - void serialize(QDataStream *stream, int version) const; - QByteArray toJson() const; + static QShaderDescriptionPrivate *get(QShaderDescription *desc) { return desc->d; } + static const QShaderDescriptionPrivate *get(const QShaderDescription *desc) { return desc->d; } - static QShaderDescription deserialize(QDataStream *stream, int version); + QJsonDocument makeDoc(); + void writeToStream(QDataStream *stream, int version); + void loadFromStream(QDataStream *stream, int version); - enum VariableType { - Unknown = 0, - - // do not reorder - Float, - Vec2, - Vec3, - Vec4, - Mat2, - Mat2x3, - Mat2x4, - Mat3, - Mat3x2, - Mat3x4, - Mat4, - Mat4x2, - Mat4x3, - - Int, - Int2, - Int3, - Int4, - - Uint, - Uint2, - Uint3, - Uint4, - - Bool, - Bool2, - Bool3, - Bool4, - - Double, - Double2, - Double3, - Double4, - DMat2, - DMat2x3, - DMat2x4, - DMat3, - DMat3x2, - DMat3x4, - DMat4, - DMat4x2, - DMat4x3, - - Sampler1D, - Sampler2D, - Sampler2DMS, - Sampler3D, - SamplerCube, - Sampler1DArray, - Sampler2DArray, - Sampler2DMSArray, - Sampler3DArray, - SamplerCubeArray, - SamplerRect, - SamplerBuffer, - SamplerExternalOES, - Sampler, - - Image1D, - Image2D, - Image2DMS, - Image3D, - ImageCube, - Image1DArray, - Image2DArray, - Image2DMSArray, - Image3DArray, - ImageCubeArray, - ImageRect, - ImageBuffer, - - Struct - }; - - enum ImageFormat { - // must match SPIR-V's ImageFormat - ImageFormatUnknown = 0, - ImageFormatRgba32f = 1, - ImageFormatRgba16f = 2, - ImageFormatR32f = 3, - ImageFormatRgba8 = 4, - ImageFormatRgba8Snorm = 5, - ImageFormatRg32f = 6, - ImageFormatRg16f = 7, - ImageFormatR11fG11fB10f = 8, - ImageFormatR16f = 9, - ImageFormatRgba16 = 10, - ImageFormatRgb10A2 = 11, - ImageFormatRg16 = 12, - ImageFormatRg8 = 13, - ImageFormatR16 = 14, - ImageFormatR8 = 15, - ImageFormatRgba16Snorm = 16, - ImageFormatRg16Snorm = 17, - ImageFormatRg8Snorm = 18, - ImageFormatR16Snorm = 19, - ImageFormatR8Snorm = 20, - ImageFormatRgba32i = 21, - ImageFormatRgba16i = 22, - ImageFormatRgba8i = 23, - ImageFormatR32i = 24, - ImageFormatRg32i = 25, - ImageFormatRg16i = 26, - ImageFormatRg8i = 27, - ImageFormatR16i = 28, - ImageFormatR8i = 29, - ImageFormatRgba32ui = 30, - ImageFormatRgba16ui = 31, - ImageFormatRgba8ui = 32, - ImageFormatR32ui = 33, - ImageFormatRgb10a2ui = 34, - ImageFormatRg32ui = 35, - ImageFormatRg16ui = 36, - ImageFormatRg8ui = 37, - ImageFormatR16ui = 38, - ImageFormatR8ui = 39 - }; - - enum ImageFlag { - ReadOnlyImage = 1 << 0, - WriteOnlyImage = 1 << 1 - }; - Q_DECLARE_FLAGS(ImageFlags, ImageFlag) - - enum QualifierFlag { - QualifierReadOnly = 1 << 0, - QualifierWriteOnly = 1 << 1, - QualifierCoherent = 1 << 2, - QualifierVolatile = 1 << 3, - QualifierRestrict = 1 << 4, - }; - Q_DECLARE_FLAGS(QualifierFlags, QualifierFlag) - - // Optional data (like decorations) usually default to an otherwise invalid value (-1 or 0). This is intentional. - - struct InOutVariable { - QByteArray name; - VariableType type = Unknown; - int location = -1; - int binding = -1; - int descriptorSet = -1; - ImageFormat imageFormat = ImageFormatUnknown; - ImageFlags imageFlags; - QList arrayDims; - bool perPatch = false; - }; - - struct BlockVariable { - QByteArray name; - VariableType type = Unknown; - int offset = 0; - int size = 0; - QList arrayDims; - int arrayStride = 0; - int matrixStride = 0; - bool matrixIsRowMajor = false; - QList structMembers; - }; - - struct UniformBlock { - QByteArray blockName; - QByteArray structName; // instanceName - int size = 0; - int binding = -1; - int descriptorSet = -1; - QList members; - }; - - struct PushConstantBlock { - QByteArray name; - int size = 0; - QList members; - }; - - struct StorageBlock { - QByteArray blockName; - QByteArray instanceName; - int knownSize = 0; - int binding = -1; - int descriptorSet = -1; - QList members; - int runtimeArrayStride = 0; - QualifierFlags qualifierFlags; - }; - - QList inputVariables() const; - QList outputVariables() const; - QList uniformBlocks() const; - QList pushConstantBlocks() const; - QList storageBlocks() const; - QList combinedImageSamplers() const; - QList separateImages() const; - QList separateSamplers() const; - QList storageImages() const; - - enum BuiltinType { - // must match SpvBuiltIn - PositionBuiltin = 0, - PointSizeBuiltin = 1, - ClipDistanceBuiltin = 3, - CullDistanceBuiltin = 4, - VertexIdBuiltin = 5, - InstanceIdBuiltin = 6, - PrimitiveIdBuiltin = 7, - InvocationIdBuiltin = 8, - LayerBuiltin = 9, - ViewportIndexBuiltin = 10, - TessLevelOuterBuiltin = 11, - TessLevelInnerBuiltin = 12, - TessCoordBuiltin = 13, - PatchVerticesBuiltin = 14, - FragCoordBuiltin = 15, - PointCoordBuiltin = 16, - FrontFacingBuiltin = 17, - SampleIdBuiltin = 18, - SamplePositionBuiltin = 19, - SampleMaskBuiltin = 20, - FragDepthBuiltin = 22, - NumWorkGroupsBuiltin = 24, - WorkgroupSizeBuiltin = 25, - WorkgroupIdBuiltin = 26, - LocalInvocationIdBuiltin = 27, - GlobalInvocationIdBuiltin = 28, - LocalInvocationIndexBuiltin = 29, - VertexIndexBuiltin = 42, - InstanceIndexBuiltin = 43 - }; - - struct BuiltinVariable { - BuiltinType type; - }; - - QList inputBuiltinVariables() const; - QList outputBuiltinVariables() const; - - std::array computeShaderLocalSize() const; - - uint tessellationOutputVertexCount() const; - - enum TessellationMode { - UnknownTessellationMode, - TrianglesTessellationMode, - QuadTessellationMode, - IsolineTessellationMode - }; - - TessellationMode tessellationMode() const; - - enum TessellationWindingOrder { - UnknownTessellationWindingOrder, - CwTessellationWindingOrder, - CcwTessellationWindingOrder - }; - - TessellationWindingOrder tessellationWindingOrder() const; - - enum TessellationPartitioning { - UnknownTessellationPartitioning, - EqualTessellationPartitioning, - FractionalEvenTessellationPartitioning, - FractionalOddTessellationPartitioning - }; - - TessellationPartitioning tessellationPartitioning() const; - -private: - QShaderDescriptionPrivate *d; - friend struct QShaderDescriptionPrivate; -#ifndef QT_NO_DEBUG_STREAM - friend Q_GUI_EXPORT QDebug operator<<(QDebug, const QShaderDescription &); -#endif - friend Q_GUI_EXPORT bool operator==(const QShaderDescription &lhs, const QShaderDescription &rhs) noexcept; + QAtomicInt ref; + QList inVars; + QList outVars; + QList uniformBlocks; + QList pushConstantBlocks; + QList storageBlocks; + QList combinedImageSamplers; + QList separateImages; + QList separateSamplers; + QList storageImages; + QList inBuiltins; + QList outBuiltins; + std::array localSize = {}; + uint tessOutVertCount = 0; + QShaderDescription::TessellationMode tessMode = QShaderDescription::UnknownTessellationMode; + QShaderDescription::TessellationWindingOrder tessWind = QShaderDescription::UnknownTessellationWindingOrder; + QShaderDescription::TessellationPartitioning tessPart = QShaderDescription::UnknownTessellationPartitioning; }; -Q_DECLARE_OPERATORS_FOR_FLAGS(QShaderDescription::ImageFlags) -Q_DECLARE_OPERATORS_FOR_FLAGS(QShaderDescription::QualifierFlags) - -#ifndef QT_NO_DEBUG_STREAM -Q_GUI_EXPORT QDebug operator<<(QDebug, const QShaderDescription &); -Q_GUI_EXPORT QDebug operator<<(QDebug, const QShaderDescription::InOutVariable &); -Q_GUI_EXPORT QDebug operator<<(QDebug, const QShaderDescription::BlockVariable &); -Q_GUI_EXPORT QDebug operator<<(QDebug, const QShaderDescription::UniformBlock &); -Q_GUI_EXPORT QDebug operator<<(QDebug, const QShaderDescription::PushConstantBlock &); -Q_GUI_EXPORT QDebug operator<<(QDebug, const QShaderDescription::StorageBlock &); -Q_GUI_EXPORT QDebug operator<<(QDebug, const QShaderDescription::BuiltinVariable &); -#endif - -Q_GUI_EXPORT bool operator==(const QShaderDescription &lhs, const QShaderDescription &rhs) noexcept; -Q_GUI_EXPORT bool operator==(const QShaderDescription::InOutVariable &lhs, const QShaderDescription::InOutVariable &rhs) noexcept; -Q_GUI_EXPORT bool operator==(const QShaderDescription::BlockVariable &lhs, const QShaderDescription::BlockVariable &rhs) noexcept; -Q_GUI_EXPORT bool operator==(const QShaderDescription::UniformBlock &lhs, const QShaderDescription::UniformBlock &rhs) noexcept; -Q_GUI_EXPORT bool operator==(const QShaderDescription::PushConstantBlock &lhs, const QShaderDescription::PushConstantBlock &rhs) noexcept; -Q_GUI_EXPORT bool operator==(const QShaderDescription::StorageBlock &lhs, const QShaderDescription::StorageBlock &rhs) noexcept; -Q_GUI_EXPORT bool operator==(const QShaderDescription::BuiltinVariable &lhs, const QShaderDescription::BuiltinVariable &rhs) noexcept; - -inline bool operator!=(const QShaderDescription &lhs, const QShaderDescription &rhs) noexcept -{ - return !(lhs == rhs); -} - -inline bool operator!=(const QShaderDescription::InOutVariable &lhs, const QShaderDescription::InOutVariable &rhs) noexcept -{ - return !(lhs == rhs); -} - -inline bool operator!=(const QShaderDescription::BlockVariable &lhs, const QShaderDescription::BlockVariable &rhs) noexcept -{ - return !(lhs == rhs); -} - -inline bool operator!=(const QShaderDescription::UniformBlock &lhs, const QShaderDescription::UniformBlock &rhs) noexcept -{ - return !(lhs == rhs); -} - -inline bool operator!=(const QShaderDescription::PushConstantBlock &lhs, const QShaderDescription::PushConstantBlock &rhs) noexcept -{ - return !(lhs == rhs); -} - -inline bool operator!=(const QShaderDescription::StorageBlock &lhs, const QShaderDescription::StorageBlock &rhs) noexcept -{ - return !(lhs == rhs); -} - -inline bool operator!=(const QShaderDescription::BuiltinVariable &lhs, const QShaderDescription::BuiltinVariable &rhs) noexcept -{ - return !(lhs == rhs); -} - QT_END_NAMESPACE #endif diff --git a/src/gui/rhi/qshaderdescription_p_p.h b/src/gui/rhi/qshaderdescription_p_p.h deleted file mode 100644 index c22ddda8..00000000 --- a/src/gui/rhi/qshaderdescription_p_p.h +++ /dev/null @@ -1,81 +0,0 @@ -// Copyright (C) 2019 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 QSHADERDESCRIPTION_P_H -#define QSHADERDESCRIPTION_P_H - -// -// W A R N I N G -// ------------- -// -// This file is not part of the Qt API. It exists for the convenience -// of a number of Qt sources files. This header file may change from -// version to version without notice, or even be removed. -// -// We mean it. -// - -#include "qshaderdescription_p.h" -#include -#include -#include - -QT_BEGIN_NAMESPACE - -struct Q_GUI_EXPORT QShaderDescriptionPrivate -{ - QShaderDescriptionPrivate() - : ref(1) - { - } - - QShaderDescriptionPrivate(const QShaderDescriptionPrivate &other) - : ref(1), - inVars(other.inVars), - outVars(other.outVars), - uniformBlocks(other.uniformBlocks), - pushConstantBlocks(other.pushConstantBlocks), - storageBlocks(other.storageBlocks), - combinedImageSamplers(other.combinedImageSamplers), - separateImages(other.separateImages), - separateSamplers(other.separateSamplers), - storageImages(other.storageImages), - inBuiltins(other.inBuiltins), - outBuiltins(other.outBuiltins), - localSize(other.localSize), - tessOutVertCount(other.tessOutVertCount), - tessMode(other.tessMode), - tessWind(other.tessWind), - tessPart(other.tessPart) - { - } - - static QShaderDescriptionPrivate *get(QShaderDescription *desc) { return desc->d; } - static const QShaderDescriptionPrivate *get(const QShaderDescription *desc) { return desc->d; } - - QJsonDocument makeDoc(); - void writeToStream(QDataStream *stream, int version); - void loadFromStream(QDataStream *stream, int version); - - QAtomicInt ref; - QList inVars; - QList outVars; - QList uniformBlocks; - QList pushConstantBlocks; - QList storageBlocks; - QList combinedImageSamplers; - QList separateImages; - QList separateSamplers; - QList storageImages; - QList inBuiltins; - QList outBuiltins; - std::array localSize = {}; - uint tessOutVertCount = 0; - QShaderDescription::TessellationMode tessMode = QShaderDescription::UnknownTessellationMode; - QShaderDescription::TessellationWindingOrder tessWind = QShaderDescription::UnknownTessellationWindingOrder; - QShaderDescription::TessellationPartitioning tessPart = QShaderDescription::UnknownTessellationPartitioning; -}; - -QT_END_NAMESPACE - -#endif diff --git a/src/gui/rhi/qt_attribution.json b/src/gui/rhi/qt_attribution.json new file mode 100644 index 00000000..c356f5f0 --- /dev/null +++ b/src/gui/rhi/qt_attribution.json @@ -0,0 +1,16 @@ +[ + { + "Id": "rhi-miniengine-d3d12-mipmap", + "Name": "Mipmap generator for D3D12", + "QDocModule": "qtgui", + "Description": "Compute shader for mipmap generation from MiniEngine in DirectX-Graphics-Samples", + "QtUsage": "Compute shader for mipmap generation with Direct 3D 12", + + "Homepage": "https://github.com/microsoft/DirectX-Graphics-Samples", + "Version": "0aa79bad78992da0b6a8279ddb9002c1753cb849", + "License": "MIT License", + "LicenseId": "MIT", + "LicenseFile": "MiniEngine_LICENSE.txt", + "Copyright": "Copyright (c) 2015 Microsoft" + } +] diff --git a/src/gui/rhi/tdr.hlsl b/src/gui/rhi/tdr.hlsl deleted file mode 100644 index f79de91c..00000000 --- a/src/gui/rhi/tdr.hlsl +++ /dev/null @@ -1,9 +0,0 @@ -RWBuffer uav; -cbuffer ConstantBuffer { uint zero; } - -[numthreads(256, 1, 1)] -void killDeviceByTimingOut(uint3 id: SV_DispatchThreadID) -{ - while (zero == 0) - uav[id.x] = zero; -} diff --git a/src/gui/text/coretext/qfontengine_coretext.mm b/src/gui/text/coretext/qfontengine_coretext.mm index 599a7f08..6cdba260 100644 --- a/src/gui/text/coretext/qfontengine_coretext.mm +++ b/src/gui/text/coretext/qfontengine_coretext.mm @@ -12,6 +12,8 @@ #include #include #include +#include +#include #include @@ -716,10 +718,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/qcssparser.cpp b/src/gui/text/qcssparser.cpp index f79369a3..2082263d 100644 --- a/src/gui/text/qcssparser.cpp +++ b/src/gui/text/qcssparser.cpp @@ -47,6 +47,7 @@ static const QCssKnownValue properties[NumProperties - 1] = { { "-qt-style-features", QtStyleFeatures }, { "-qt-table-type", QtTableType }, { "-qt-user-state", QtUserState }, + { "accent-color", QtAccent }, { "alternate-background-color", QtAlternateBackground }, { "background", Background }, { "background-attachment", BackgroundAttachment }, @@ -1341,17 +1342,23 @@ bool ValueExtractor::extractFont(QFont *font, int *fontSizeAdjustment) return hit; } -bool ValueExtractor::extractPalette(QBrush *fg, QBrush *sfg, QBrush *sbg, QBrush *abg, QBrush *pfg) +bool ValueExtractor::extractPalette(QBrush *foreground, + QBrush *selectedForeground, + QBrush *selectedBackground, + QBrush *alternateBackground, + QBrush *placeHolderTextForeground, + QBrush *accent) { bool hit = false; for (int i = 0; i < declarations.size(); ++i) { const Declaration &decl = declarations.at(i); switch (decl.d->propertyId) { - case Color: *fg = decl.brushValue(pal); break; - case QtSelectionForeground: *sfg = decl.brushValue(pal); break; - case QtSelectionBackground: *sbg = decl.brushValue(pal); break; - case QtAlternateBackground: *abg = decl.brushValue(pal); break; - case QtPlaceHolderTextColor: *pfg = decl.brushValue(pal); break; + case Color: *foreground = decl.brushValue(pal); break; + case QtSelectionForeground: *selectedForeground = decl.brushValue(pal); break; + case QtSelectionBackground: *selectedBackground = decl.brushValue(pal); break; + case QtAlternateBackground: *alternateBackground = decl.brushValue(pal); break; + case QtPlaceHolderTextColor: *placeHolderTextForeground = decl.brushValue(pal); break; + case QtAccent: *accent = decl.brushValue(pal); break; default: continue; } hit = true; diff --git a/src/gui/text/qcssparser_p.h b/src/gui/text/qcssparser_p.h index 8e4c7ce6..1369bdf1 100644 --- a/src/gui/text/qcssparser_p.h +++ b/src/gui/text/qcssparser_p.h @@ -166,6 +166,7 @@ enum Property { WordSpacing, TextDecorationColor, QtPlaceHolderTextColor, + QtAccent, NumProperties }; @@ -823,7 +824,9 @@ struct Q_GUI_EXPORT ValueExtractor bool extractBox(int *margins, int *paddings, int *spacing = nullptr); bool extractBorder(int *borders, QBrush *colors, BorderStyle *Styles, QSize *radii); bool extractOutline(int *borders, QBrush *colors, BorderStyle *Styles, QSize *radii, int *offsets); - bool extractPalette(QBrush *fg, QBrush *sfg, QBrush *sbg, QBrush *abg, QBrush *pfg); + bool extractPalette(QBrush *foreground, QBrush *selectedForeground, QBrush *selectedBackground, + QBrush *alternateBackground, QBrush *placeHolderTextForeground, + QBrush *accent); int extractStyleFeatures(); bool extractImage(QIcon *icon, Qt::Alignment *a, QSize *size); bool extractIcon(QIcon *icon, QSize *size); diff --git a/src/gui/text/qfont.cpp b/src/gui/text/qfont.cpp index 78e9e3e3..eeb67903 100644 --- a/src/gui/text/qfont.cpp +++ b/src/gui/text/qfont.cpp @@ -213,7 +213,7 @@ QFontPrivate::QFontPrivate(const QFontPrivate &other) strikeOut(other.strikeOut), kerning(other.kerning), capital(other.capital), letterSpacingIsAbsolute(other.letterSpacingIsAbsolute), letterSpacing(other.letterSpacing), wordSpacing(other.wordSpacing), - scFont(other.scFont) + features(other.features), scFont(other.scFont) { if (scFont && scFont != this) scFont->ref.ref(); @@ -343,9 +343,20 @@ void QFontPrivate::resolve(uint mask, const QFontPrivate *other) wordSpacing = other->wordSpacing; if (! (mask & QFont::CapitalizationResolved)) capital = other->capital; + + if (!(mask & QFont::FeaturesResolved)) + features = other->features; } +void QFontPrivate::setFeature(quint32 tag, quint32 value) +{ + features.insert(tag, value); +} +void QFontPrivate::unsetFeature(quint32 tag) +{ + features.remove(tag); +} QFontEngineData::QFontEngineData() @@ -1746,6 +1757,7 @@ bool QFont::operator==(const QFont &f) const && f.d->letterSpacingIsAbsolute == d->letterSpacingIsAbsolute && f.d->letterSpacing == d->letterSpacing && f.d->wordSpacing == d->wordSpacing + && f.d->features == d->features )); } @@ -1783,7 +1795,21 @@ bool QFont::operator<(const QFont &f) const int f1attrs = (f.d->underline << 3) + (f.d->overline << 2) + (f.d->strikeOut<<1) + f.d->kerning; int f2attrs = (d->underline << 3) + (d->overline << 2) + (d->strikeOut<<1) + d->kerning; - return f1attrs < f2attrs; + if (f1attrs != f2attrs) return f1attrs < f2attrs; + + 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(); + } + + return false; } @@ -2204,6 +2230,221 @@ void QFont::cacheStatistics() { } +/*! + \since 6.6 + \overload + \preliminary + + Applies an integer value to a specific typographical feature 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. + + A feature is defined by a 32-bit \a tag (encoded from the four-character name of the table by + using the stringToTag() function), as well as an integer value. + + 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 intepretations. For example, when applied to the + \c salt feature, the value is an index that specifies the stylistic alternative to use. + + 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[stringToTag("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 clearFeatures(), setFeature(), unsetFeature(), featureTags(), stringToTag() +*/ +void QFont::setFeature(quint32 tag, quint32 value) +{ + if (tag != 0) { + d->detachButKeepEngineData(this); + d->setFeature(tag, value); + resolve_mask |= QFont::FeaturesResolved; + } +} + +/*! + \since 6.6 + \overload + \preliminary + + 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 setFeature(quint32, quint32) for more details on font features. + + \note This is equivalent to calling setFeature(stringToTag(feature), value). + + \sa clearFeatures(), unsetFeature(), featureTags(), featureValue(), stringToTag() +*/ +void QFont::setFeature(const char *feature, quint32 value) +{ + setFeature(stringToTag(feature), value); +} + +/*! + \since 6.6 + \overload + \preliminary + + 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 setFeature(quint32, quint32) for more details on font features. + + \sa clearFeatures(), setFeature(), featureTags(), featureValue(), stringToTag() +*/ +void QFont::unsetFeature(quint32 tag) +{ + if (tag != 0) { + d->detachButKeepEngineData(this); + d->unsetFeature(tag); + resolve_mask |= QFont::FeaturesResolved; + } +} + +/*! + \since 6.6 + \overload + \preliminary + + Unsets the \a feature 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 setFeature(quint32, quint32) for more details on font features. + + \note This is equivalent to calling unsetFeature(stringToTag(feature)). + + \sa clearFeatures(), setFeature(), featureTags(), featureValue(), stringToTag() +*/ +void QFont::unsetFeature(const char *feature) +{ + unsetFeature(stringToTag(feature)); +} + +/*! + \since 6.6 + \preliminary + + Returns a list of tags for all font features currently set on this QFont. + + See \l setFeature(quint32, quint32) for more details on font features. + + \sa setFeature(), unsetFeature(), isFeatureSet(), clearFeatures(), tagToString() +*/ +QList QFont::featureTags() const +{ + return d->features.keys(); +} + +/*! + \since 6.6 + \preliminary + + Returns the value set for a specific feature \a tag. If the tag has not been set, 0 will be + returned instead. + + See \l setFeature(quint32, quint32) for more details on font features. + + \sa setFeature(), unsetFeature(), featureTags(), isFeatureSet(), stringToTag() +*/ +quint32 QFont::featureValue(quint32 tag) const +{ + return d->features.value(tag); +} + +/*! + \since 6.6 + \preliminary + + Returns true if a value for the feature given by \a tag has been set on the QFont, otherwise + returns false. + + See \l setFeature(quint32, quint32) for more details on font features. + + \sa setFeature(), unsetFeature(), featureTags(), featureValue(), stringToTag() +*/ +bool QFont::isFeatureSet(quint32 tag) const +{ + return d->features.contains(tag); +} + +/*! + \since 6.6 + \preliminary + + Clears any previously set features on the QFont. + + See \l setFeature(quint32, quint32) for more details on font features. + + \sa setFeature(), unsetFeature(), featureTags(), featureValue() +*/ +void QFont::clearFeatures() +{ + d->features.clear(); +} + +/*! + \since 6.6 + \preliminary + + Returns the decoded name for \a tag as defined in the OpenType font specification. The tag + is decoded into four 8 bit characters. For valid tags, each will be in the basic Latin range of + 0x20 to 0x7E. + + \sa setFeature(), unsetFeature(), featureTags(), featureValue(), stringToTag() +*/ +QByteArray QFont::tagToString(quint32 tag) +{ + char str[4] = + { char((tag & 0xff000000) >> 24), + char((tag & 0x00ff0000) >> 16), + char((tag & 0x0000ff00) >> 8), + char((tag & 0x000000ff)) }; + return QByteArray(str, 4); +} + +/*! + \since 6.6 + \preliminary + + Returns the encoded tag for \a name as defined in the OpenType font specification. The name + must be a null-terminated string of four characters exactly, and in order to be a valid tag, + each character must be in the basic Latin range of 0x20 to 0x7E. + + The function returns 0 for strings of the wrong length, but does not otherwise check the input + for validity. + + \sa setFeature(), unsetFeature(), featureTags(), featureValue(), tagToString() +*/ +quint32 QFont::stringToTag(const char *name) +{ + if (qstrlen(name) != 4) + return 0; + + return MAKE_TAG(name[0], name[1], name[2], name[3]); +} extern QStringList qt_fallbacksForFamily(const QString &family, QFont::Style style, QFont::StyleHint styleHint, QChar::Script script); @@ -2341,6 +2582,8 @@ QDataStream &operator<<(QDataStream &s, const QFont &font) else s << font.d->request.families; } + if (s.version() >= QDataStream::Qt_6_6) + s << font.d->features; return s; } @@ -2455,6 +2698,11 @@ QDataStream &operator>>(QDataStream &s, QFont &font) else font.d->request.families = value; } + if (s.version() >= QDataStream::Qt_6_6) { + font.d->features.clear(); + s >> font.d->features; + } + return s; } @@ -2794,13 +3042,15 @@ bool QFontInfo::exactMatch() const // QFontCache // ********************************************************************** +using namespace std::chrono_literals; + #ifdef QFONTCACHE_DEBUG // fast timeouts for debugging -static const int fast_timeout = 1000; // 1s -static const int slow_timeout = 5000; // 5s +static constexpr auto fast_timeout = 1s; +static constexpr auto slow_timeout = 5s; #else -static const int fast_timeout = 10000; // 10s -static const int slow_timeout = 300000; // 5m +static constexpr auto fast_timeout = 10s; +static constexpr auto slow_timeout = 5min; #endif // QFONTCACHE_DEBUG #ifndef QFONTCACHE_MIN_COST @@ -3010,7 +3260,7 @@ void QFontCache::increaseCost(uint cost) return; if (timer_id == -1 || ! fast) { - FC_DEBUG(" TIMER: starting fast timer (%d ms)", fast_timeout); + FC_DEBUG(" TIMER: starting fast timer (%d s)", static_cast(fast_timeout.count())); if (timer_id != -1) killTimer(timer_id); diff --git a/src/gui/text/qfont.h b/src/gui/text/qfont.h index 7fb591b5..e84fbe20 100644 --- a/src/gui/text/qfont.h +++ b/src/gui/text/qfont.h @@ -4,10 +4,10 @@ #ifndef QFONT_H #define QFONT_H +#include #include #include #include -#include QT_BEGIN_NAMESPACE @@ -126,7 +126,8 @@ public: HintingPreferenceResolved = 0x8000, StyleNameResolved = 0x10000, FamiliesResolved = 0x20000, - AllPropertiesResolved = 0x3ffff + FeaturesResolved = 0x40000, + AllPropertiesResolved = 0x7ffff }; Q_ENUM(ResolveProperties) @@ -206,6 +207,20 @@ public: void setHintingPreference(HintingPreference hintingPreference); HintingPreference hintingPreference() const; + // Note: The following set of APIs are preliminary and may change in future releases + void setFeature(const char *feature, quint32 value); + void setFeature(quint32 tag, quint32 value); + void unsetFeature(quint32 tag); + void unsetFeature(const char *feature); + quint32 featureValue(quint32 tag) const; + bool isFeatureSet(quint32 tag) const; + QList featureTags() const; + void clearFeatures(); + + static QByteArray tagToString(quint32 tag); + static quint32 stringToTag(const char *tagString); + // -- + // dupicated from QFontInfo bool exactMatch() const; diff --git a/src/gui/text/qfont_p.h b/src/gui/text/qfont_p.h index 13738bb0..3596322f 100644 --- a/src/gui/text/qfont_p.h +++ b/src/gui/text/qfont_p.h @@ -164,6 +164,7 @@ public: QFixed letterSpacing; QFixed wordSpacing; + QHash features; mutable QFontPrivate *scFont; QFont smallCapsFont() const { return QFont(smallCapsFontPrivate()); } @@ -178,6 +179,9 @@ public: static void detachButKeepEngineData(QFont *font); + void setFeature(quint32 tag, quint32 value); + void unsetFeature(quint32 tag); + private: QFontPrivate &operator=(const QFontPrivate &) { return *this; } }; diff --git a/src/gui/text/qfontengine.cpp b/src/gui/text/qfontengine.cpp index 5035b61f..a402a6a0 100644 --- a/src/gui/text/qfontengine.cpp +++ b/src/gui/text/qfontengine.cpp @@ -1471,10 +1471,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; diff --git a/src/gui/text/qfontengineglyphcache_p.h b/src/gui/text/qfontengineglyphcache_p.h index b829b797..9054ea59 100644 --- a/src/gui/text/qfontengineglyphcache_p.h +++ b/src/gui/text/qfontengineglyphcache_p.h @@ -15,7 +15,7 @@ // We mean it. // - +#include #include #include "QtCore/qatomic.h" #include diff --git a/src/gui/text/qfontinfo.h b/src/gui/text/qfontinfo.h index 5818ca0d..0edee5ab 100644 --- a/src/gui/text/qfontinfo.h +++ b/src/gui/text/qfontinfo.h @@ -6,7 +6,8 @@ #include #include -#include + +#include QT_BEGIN_NAMESPACE diff --git a/src/gui/text/qfontmetrics.h b/src/gui/text/qfontmetrics.h index 18b54861..1942d1fa 100644 --- a/src/gui/text/qfontmetrics.h +++ b/src/gui/text/qfontmetrics.h @@ -6,10 +6,11 @@ #include #include -#include + #ifndef QT_INCLUDE_COMPAT #include #endif +#include QT_BEGIN_NAMESPACE diff --git a/src/gui/text/qglyphrun.h b/src/gui/text/qglyphrun.h index a338a35b..88f9957d 100644 --- a/src/gui/text/qglyphrun.h +++ b/src/gui/text/qglyphrun.h @@ -7,8 +7,8 @@ #include #include #include -#include #include +#include #if !defined(QT_NO_RAWFONT) diff --git a/src/gui/text/qglyphrun_p.h b/src/gui/text/qglyphrun_p.h index c95ec8ab..db160344 100644 --- a/src/gui/text/qglyphrun_p.h +++ b/src/gui/text/qglyphrun_p.h @@ -15,6 +15,7 @@ // We mean it. // +#include #include #include "qglyphrun.h" #include "qrawfont.h" diff --git a/src/gui/text/qplatformfontdatabase.h b/src/gui/text/qplatformfontdatabase.h index 8527e7b3..a5e65086 100644 --- a/src/gui/text/qplatformfontdatabase.h +++ b/src/gui/text/qplatformfontdatabase.h @@ -25,7 +25,7 @@ QT_BEGIN_NAMESPACE -Q_DECLARE_LOGGING_CATEGORY(lcQpaFonts) +Q_DECLARE_EXPORTED_LOGGING_CATEGORY(lcQpaFonts, Q_GUI_EXPORT) class QWritingSystemsPrivate; diff --git a/src/gui/text/qsyntaxhighlighter.cpp b/src/gui/text/qsyntaxhighlighter.cpp index b974a1d3..6c1e2e74 100644 --- a/src/gui/text/qsyntaxhighlighter.cpp +++ b/src/gui/text/qsyntaxhighlighter.cpp @@ -219,7 +219,7 @@ void QSyntaxHighlighterPrivate::reformatBlock(const QTextBlock &block) an int value. If no state is set, the returned value is -1. You can designate any other value to identify any given state using the setCurrentBlockState() function. Once the state is set the - QTextBlock keeps that value until it is set set again or until the + QTextBlock keeps that value until it is set again or until the corresponding paragraph of text is deleted. For example, if you're writing a simple C++ syntax highlighter, diff --git a/src/gui/text/qtextdocument.cpp b/src/gui/text/qtextdocument.cpp index a6675422..e5dc5136 100644 --- a/src/gui/text/qtextdocument.cpp +++ b/src/gui/text/qtextdocument.cpp @@ -1826,9 +1826,10 @@ void QTextDocument::setBaselineOffset(qreal baseline) \fn qreal QTextDocument::baselineOffset() const \since 6.0 - Returns the the baseline offset in % used in the document layout. + Returns the baseline offset in % used in the document layout. - \sa setBaselineOffset(), setSubScriptBaseline(), subScriptBaseline(), setSuperScriptBaseline(), superScriptBaseline() + \sa setBaselineOffset(), setSubScriptBaseline(), subScriptBaseline(), setSuperScriptBaseline(), + superScriptBaseline() */ qreal QTextDocument::baselineOffset() const { @@ -2995,18 +2996,25 @@ void QTextHtmlExporter::emitBlock(const QTextBlock &block) if (list->itemNumber(block) == 0) { // first item? emit