Merge remote-tracking branch 'tint/main' into HEAD Integrates Tint repo into Dawn KIs: - Building docs for Tint is turned off, because it fails due to lack of annotations in Dawn source files. - Dawn CQ needs to be updated to run Tint specific tests - Significant post-merge cleanup needed R=bclayton,cwallez BUG=dawn:1339 Change-Id: I6c9714a0030934edd6c51f3cac4684dcd59d1ea3
diff --git a/.clang-format b/.clang-format index 2fb833a..ff58eea 100644 --- a/.clang-format +++ b/.clang-format
@@ -1,2 +1,20 @@ # http://clang.llvm.org/docs/ClangFormatStyleOptions.html BasedOnStyle: Chromium +Standard: Cpp11 + +AllowShortFunctionsOnASingleLine: false + +ColumnLimit: 100 + +# Use 4 space indents +IndentWidth: 4 +ObjCBlockIndentWidth: 4 +AccessModifierOffset: -2 + +CompactNamespaces: true + +# This should result in only one indentation level with compacted namespaces +NamespaceIndentation: All + +# Use this option once clang-format 6 is out. +IndentPPDirectives: AfterHash
diff --git a/.gitattributes b/.gitattributes new file mode 100644 index 0000000..d31b156 --- /dev/null +++ b/.gitattributes
@@ -0,0 +1,4 @@ +* text=auto +*.sh eol=lf +*.gn eol=lf +*.gni eol=lf
diff --git a/.gitignore b/.gitignore index 19bafab..42d8414 100644 --- a/.gitignore +++ b/.gitignore
@@ -1,31 +1,123 @@ -.cipd -.DS_Store -.gclient -.gclient_entries -.vs +*.pyc + +# Directories added by gclient sync and the GN build +/.cipd +/.gclient +/.gclient_entries +/build +/buildtools +/testing +/third_party/abseil-cpp/ +/third_party/angle +/third_party/benchmark +/third_party/binutils +/third_party/catapult +/third_party/clang-format +/third_party/cpplint +/third_party/glfw +/third_party/googletest +/third_party/gpuweb +/third_party/gpuweb-cts +/third_party/jinja2 +/third_party/jsoncpp +/third_party/llvm-build +/third_party/markupsafe +/third_party/node +/third_party/node-addon-api +/third_party/node-api-headers +/third_party/protobuf +/third_party/swiftshader +/third_party/vulkan-deps +/third_party/vulkan_memory_allocator +/third_party/webgpu-cts +/third_party/zlib +/tools/clang +/tools/cmake +/tools/golang +/tools/memory +/out + +# Modified from https://www.gitignore.io/api/vim,macos,linux,emacs,windows,sublimetext,visualstudio,visualstudiocode,intellij + +### Emacs ### +*~ +\#*\# +/.emacs.desktop +/.emacs.desktop.lock +*.elc +auto-save-list +tramp +.\#* + +### Linux ### +.fuse_hidden* +.directory +.Trash-* +.nfs* + +### macOS ### +*.DS_Store +.AppleDouble +.LSOverride +._* +.DocumentRevisions-V100 +.fseventsd +.Spotlight-V100 +.TemporaryItems +.Trashes +.VolumeIcon.icns +.com.apple.timemachine.donotpresent +.AppleDB +.AppleDesktop +Network Trash Folder +Temporary Items +.apdisk + +### SublimeText ### +*.tmlanguage.cache +*.tmPreferences.cache +*.stTheme.cache +*.sublime-workspace +*.sublime-project +sftp-config.json +GitHub.sublime-settings + +### Vim ### +[._]*.s[a-v][a-z] +[._]*.sw[a-p] +[._]s[a-v][a-z] +[._]sw[a-p] +Session.vim +.netrwhist +tags + +### VisualStudio ### +.vs/* + +### VisualStudioCode ### .vscode/* !.vscode/tasks.json + +### Windows ### +Thumbs.db +ehthumbs.db +ehthumbs_vista.db +Desktop.ini +$RECYCLE.BIN/ + +### Intellij ### .idea + +### Dawn node tools binaries +src/dawn/node/tools/bin/ + +### Cached node transpiled tools +/.node_transpile_work_dir + +# Misc inherited from Tint +/test.wgsl coverage.summary default.profraw lcov.info - -/buildtools /cmake-build-*/ -/out /testing -/third_party/clang-format -/third_party/catapult -/third_party/cpplint -/third_party/benchmark -/third_party/binutils -/third_party/googletest -/third_party/gpuweb-cts -/third_party/llvm-build -/third_party/protobuf -/third_party/vulkan-deps -/tools/clang -/tools/bin - -/build*/ -/test.wgsl
diff --git a/.gn b/.gn index 2bc6b1c..3860440 100644 --- a/.gn +++ b/.gn
@@ -1,4 +1,4 @@ -# Copyright 2020 The Tint Authors +# Copyright 2018 The Dawn Authors # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. @@ -17,4 +17,33 @@ # Use Python3 to run scripts. On Windows this will use python.exe or python.bat script_executable = "python3" -check_targets = [ "//*" ] +default_args = { + clang_use_chrome_plugins = false + + # Override the mac version so standalone Dawn compiles with at least 10.11 + # which allows us to not skip the -Wunguarded-availability warning and get + # proper warnings for use of APIs that are 10.12 and above (even if + # Chromium is still on 10.10). + mac_deployment_target = "10.11.0" + mac_min_system_version = "10.11.0" + + angle_enable_abseil = false + angle_standalone = false + angle_build_all = false + angle_has_rapidjson = false + angle_vulkan_headers_dir = "//third_party/vulkan-deps/vulkan-headers/src" + angle_vulkan_loader_dir = "//third_party/vulkan-deps/vulkan-loader/src" + angle_vulkan_tools_dir = "//third_party/vulkan-deps/vulkan-tools/src" + angle_vulkan_validation_layers_dir = + "//third_party/vulkan-deps/vulkan-validation-layers/src" + + vma_vulkan_headers_dir = "//third_party/vulkan-deps/vulkan-headers/src" +} + +check_targets = [ + # Everything in BUILD.gn + "//:*", + + # Everything in third_party/BUILD.gn + "//third_party/:*", +]
diff --git a/.vscode/tasks.json b/.vscode/tasks.json index f584277..9d72ed9 100644 --- a/.vscode/tasks.json +++ b/.vscode/tasks.json
@@ -10,46 +10,42 @@ // ${cwd}: the current working directory of the spawned process "version": "2.0.0", "tasks": [ + // Invokes ninja in the 'out/active' directory, which is created with + // the 'gn gen' task (see below). { - "label": "make", + "label": "build", "group": { "kind": "build", "isDefault": true }, "type": "shell", - "osx": { - "command": "sh", - "args": [ - "-c", - "cmake --build . && echo Done" - ], - "options": { - "cwd": "${workspaceRoot}/build", - }, - }, "linux": { "command": "sh", "args": [ "-c", - "cmake --build . && echo Done" - ], - "options": { - "cwd": "${workspaceRoot}/build", - }, - }, - "windows": { - // Invokes ninja in the 'out/active' directory, which is created - // with the 'generate' task (see below). - "command": "/C", - "args": [ "ninja && echo Done" ], + }, + "osx": { + "command": "sh", + "args": [ + "-c", + "ninja && echo Done" + ], + }, + "windows": { + "command": "/C", + "args": [ + "ninja && echo Done", + ], "options": { - "cwd": "${workspaceRoot}/out/active", "shell": { "executable": "cmd" - } - }, + }, + } + }, + "options": { + "cwd": "${workspaceRoot}/out/active", }, "presentation": { "echo": false, @@ -72,36 +68,28 @@ } } }, + // Generates a GN build directory at 'out/<build-type>' with the + // is_debug argument set to to true iff the build-type is Debug. + // A symbolic link to this build directory is created at 'out/active' + // which is used to track the active build directory. { - "label": "configure", + "label": "gn gen", "type": "shell", - "osx": { - "command": "cmake", - "args": [ - "..", - "-GNinja", - "-DCMAKE_BUILD_TYPE=${input:buildType}", - ], - "options": { - "cwd": "${workspaceRoot}/build" - }, - }, "linux": { - "command": "cmake", + "command": "sh", "args": [ - "..", - "-GNinja", - "-DCMAKE_BUILD_TYPE=${input:buildType}", + "-c", + "gn gen 'out/${input:buildType}' --args=is_debug=$(if [ '${input:buildType}' = 'Debug' ]; then echo 'true'; else echo 'false'; fi) && (rm -fr out/active || true) && ln -s ${input:buildType} out/active", ], - "options": { - "cwd": "${workspaceRoot}/build" - }, + }, + "osx": { + "command": "sh", + "args": [ + "-c", + "gn gen 'out/${input:buildType}' --args=is_debug=$(if [ '${input:buildType}' = 'Debug' ]; then echo 'true'; else echo 'false'; fi) && (rm -fr out/active || true) && ln -s ${input:buildType} out/active", + ], }, "windows": { - // Generates a GN build directory at 'out/<build-type>' with the - // is_debug argument set to true iff the build-type is Debug. - // A symbolic link to this build directory is created at 'out/active' - // which is used to track the active build directory. "command": "/C", "args": [ "(IF \"${input:buildType}\" == \"Debug\" ( gn gen \"out\\${input:buildType}\" --args=is_debug=true ) ELSE ( gn gen \"out\\${input:buildType}\" --args=is_debug=false )) && (IF EXIST \"out\\active\" rmdir \"out\\active\" /q /s) && (mklink /j \"out\\active\" \"out\\${input:buildType}\")", @@ -109,13 +97,52 @@ "options": { "shell": { "executable": "cmd" - } - }, + }, + } + }, + "options": { + "cwd": "${workspaceRoot}" }, "problemMatcher": [], }, + // Rebases the current branch on to origin/main and then calls + // `gclient sync`. { - "label": "Push branch for review", + "label": "sync", + "type": "shell", + "linux": { + "command": "sh", + "args": [ + "-c", + "git fetch origin && git rebase origin/main && gclient sync && echo Done" + ], + }, + "osx": { + "command": "sh", + "args": [ + "-c", + "git fetch origin && git rebase origin/main && gclient sync && echo Done" + ], + }, + "windows": { + "command": "/C", + "args": [ + "git fetch origin && git rebase origin/main && gclient sync && echo Done", + ], + "options": { + "shell": { + "executable": "cmd" + }, + } + }, + "options": { + "cwd": "${workspaceRoot}" + }, + "problemMatcher": [], + }, + // Pushes the changes at HEAD to gerrit for review + { + "label": "push", "type": "shell", "command": "git", "args": [ @@ -136,8 +163,6 @@ "options": [ "Debug", "Release", - "MinSizeRel", - "RelWithDebInfo", ], "default": "Debug", "description": "The type of build",
diff --git a/AUTHORS b/AUTHORS index a66d09e..bded374 100644 --- a/AUTHORS +++ b/AUTHORS
@@ -1,8 +1,7 @@ -# This is the list of the Tint authors for copyright purposes. +# This is the list of Dawn & Tint authors for copyright purposes. # # This does not necessarily list everyone who has contributed code, since in # some cases, their employer may be the copyright holder. To see the full list # of contributors, see the revision history in source control. - Google LLC Vasyl Teliman
diff --git a/AUTHORS.dawn b/AUTHORS.dawn new file mode 100644 index 0000000..32a6c3c --- /dev/null +++ b/AUTHORS.dawn
@@ -0,0 +1,6 @@ +# This is the list of Dawn authors for copyright purposes. +# +# This does not necessarily list everyone who has contributed code, since in +# some cases, their employer may be the copyright holder. To see the full list +# of contributors, see the revision history in source control. +Google Inc.
diff --git a/AUTHORS.tint b/AUTHORS.tint new file mode 100644 index 0000000..a66d09e --- /dev/null +++ b/AUTHORS.tint
@@ -0,0 +1,8 @@ +# This is the list of the Tint authors for copyright purposes. +# +# This does not necessarily list everyone who has contributed code, since in +# some cases, their employer may be the copyright holder. To see the full list +# of contributors, see the revision history in source control. + +Google LLC +Vasyl Teliman
diff --git a/BUILD.gn b/BUILD.gn index f73c1da..c33776d 100644 --- a/BUILD.gn +++ b/BUILD.gn
@@ -1,4 +1,4 @@ -# Copyright 2020 The Tint Authors +# Copyright 2018 The Dawn Authors # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. @@ -12,13 +12,29 @@ # See the License for the specific language governing permissions and # limitations under the License. +import("scripts/dawn_overrides_with_defaults.gni") + +group("all") { + testonly = true + deps = [ + "src/dawn/fuzzers", + "src/dawn/native:webgpu_dawn", + "src/dawn/tests", + "src/fuzzers/dawn:dawn_fuzzers", + "src/tint/fuzzers", + "src/tint:libtint", + "test/tint:tint_unittests", + ] + if (dawn_standalone) { + deps += [ + "samples/dawn:samples", + "src/tint/cmd:tint", + ] + } +} + # This target is built when no specific target is specified on the command line. group("default") { testonly = true - deps = [ - "src/tint:libtint", - "src/tint/cmd:tint", - "src/tint/fuzzers", - "test/tint:tint_unittests", - ] + deps = [ ":all" ] }
diff --git a/CMakeLists.txt b/CMakeLists.txt index 17a7ec5..f125476 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt
@@ -1,4 +1,4 @@ -# Copyright 2020 The Tint Authors. +# Copyright 2022 The Dawn & Tint Authors # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. @@ -14,8 +14,22 @@ cmake_minimum_required(VERSION 3.10.2) -project(tint) +# When upgrading to CMake 3.11 we can remove DAWN_DUMMY_FILE because source-less add_library +# becomes available. +# When upgrading to CMake 3.12 we should add CONFIGURE_DEPENDS to DawnGenerator to rerun CMake in +# case any of the generator files changes. We should also remove the CACHE "" FORCE stuff to +# override options in third_party dependencies. We can also add the HOMEPAGE_URL +# entry to the project `HOMEPAGE_URL "https://dawn.googlesource.com/dawn"` + +project( + Dawn + DESCRIPTION "Dawn, a WebGPU implementation" + LANGUAGES C CXX +) enable_testing() + +set_property(GLOBAL PROPERTY USE_FOLDERS ON) + set(CMAKE_RUNTIME_OUTPUT_DIRECTORY ${PROJECT_BINARY_DIR}) set(CMAKE_POSITION_INDEPENDENT_CODE ON) set(CMAKE_CXX_STANDARD 17) @@ -26,20 +40,17 @@ set(CMAKE_BUILD_TYPE "Debug") endif() -# TINT_IS_SUBPROJECT is 1 if added via add_subdirectory() from another project. -get_directory_property(TINT_IS_SUBPROJECT PARENT_DIRECTORY) -if(TINT_IS_SUBPROJECT) - set(TINT_IS_SUBPROJECT 1) +set(DAWN_BUILD_GEN_DIR "${Dawn_BINARY_DIR}/gen") +set(DAWN_GENERATOR_DIR "${Dawn_SOURCE_DIR}/generator") +set(DAWN_SRC_DIR "${Dawn_SOURCE_DIR}/src") +set(DAWN_INCLUDE_DIR "${Dawn_SOURCE_DIR}/include") +set(DAWN_TEMPLATE_DIR "${DAWN_GENERATOR_DIR}/templates") - # If tint is used as a subproject, default to disabling the building of - # documentation and tests. These are unlikely to be desirable, but can be - # enabled. - set(TINT_BUILD_DOCS_DEFAULT OFF) - set(TINT_BUILD_TESTS_DEFAULT OFF) -else() - set(TINT_BUILD_DOCS_DEFAULT ON) - set(TINT_BUILD_TESTS_DEFAULT ON) -endif() +set(DAWN_DUMMY_FILE "${DAWN_SRC_DIR}/Dummy.cpp") + +################################################################################ +# Configuration options +################################################################################ # option_if_not_defined(name description default) # Behaves like: @@ -65,7 +76,159 @@ endif() endfunction() -set_if_not_defined(TINT_THIRD_PARTY_DIR "${tint_SOURCE_DIR}/third_party" "Directory in which to find third-party dependencies.") +# Default values for the backend-enabling options +set(ENABLE_D3D12 OFF) +set(ENABLE_METAL OFF) +set(ENABLE_OPENGLES OFF) +set(ENABLE_DESKTOP_GL OFF) +set(ENABLE_VULKAN OFF) +set(USE_X11 OFF) +set(BUILD_SAMPLES OFF) +if (WIN32) + set(ENABLE_D3D12 ON) + if (NOT WINDOWS_STORE) + # Enable Vulkan in win32 compilation only + # since UWP only supports d3d + set(ENABLE_VULKAN ON) + endif() +elseif(APPLE) + set(ENABLE_METAL ON) +elseif(ANDROID) + set(ENABLE_VULKAN ON) + set(ENABLE_OPENGLES ON) +elseif(UNIX) + set(ENABLE_OPENGLES ON) + set(ENABLE_DESKTOP_GL ON) + set(ENABLE_VULKAN ON) + set(USE_X11 ON) +endif() + +# GLFW is not supported in UWP +if ((WIN32 AND NOT WINDOWS_STORE) OR UNIX AND NOT ANDROID) + set(DAWN_SUPPORTS_GLFW_FOR_WINDOWING ON) +endif() + +# Current examples are depend on GLFW +if (DAWN_SUPPORTS_GLFW_FOR_WINDOWING) + set(BUILD_SAMPLES ON) +endif() + +option_if_not_defined(DAWN_ENABLE_D3D12 "Enable compilation of the D3D12 backend" ${ENABLE_D3D12}) +option_if_not_defined(DAWN_ENABLE_METAL "Enable compilation of the Metal backend" ${ENABLE_METAL}) +option_if_not_defined(DAWN_ENABLE_NULL "Enable compilation of the Null backend" ON) +option_if_not_defined(DAWN_ENABLE_DESKTOP_GL "Enable compilation of the OpenGL backend" ${ENABLE_DESKTOP_GL}) +option_if_not_defined(DAWN_ENABLE_OPENGLES "Enable compilation of the OpenGL ES backend" ${ENABLE_OPENGLES}) +option_if_not_defined(DAWN_ENABLE_VULKAN "Enable compilation of the Vulkan backend" ${ENABLE_VULKAN}) +option_if_not_defined(DAWN_ALWAYS_ASSERT "Enable assertions on all build types" OFF) +option_if_not_defined(DAWN_USE_X11 "Enable support for X11 surface" ${USE_X11}) + +option_if_not_defined(DAWN_BUILD_SAMPLES "Enables building Dawn's samples" ${BUILD_SAMPLES}) +option_if_not_defined(DAWN_BUILD_NODE_BINDINGS "Enables building Dawn's NodeJS bindings" OFF) + +option_if_not_defined(DAWN_ENABLE_PIC "Build with Position-Independent-Code enabled" OFF) + +set_if_not_defined(DAWN_THIRD_PARTY_DIR "${Dawn_SOURCE_DIR}/third_party" "Directory in which to find third-party dependencies.") + +# Recommended setting for compability with future abseil releases. +set(ABSL_PROPAGATE_CXX_STD ON) + +set_if_not_defined(DAWN_ABSEIL_DIR "${DAWN_THIRD_PARTY_DIR}/abseil-cpp" "Directory in which to find Abseil") +set_if_not_defined(DAWN_GLFW_DIR "${DAWN_THIRD_PARTY_DIR}/glfw" "Directory in which to find GLFW") +set_if_not_defined(DAWN_JINJA2_DIR "${DAWN_THIRD_PARTY_DIR}/jinja2" "Directory in which to find Jinja2") +set_if_not_defined(DAWN_SPIRV_HEADERS_DIR "${DAWN_THIRD_PARTY_DIR}/vulkan-deps/spirv-headers/src" "Directory in which to find SPIRV-Headers") +set_if_not_defined(DAWN_SPIRV_TOOLS_DIR "${DAWN_THIRD_PARTY_DIR}/vulkan-deps/spirv-tools/src" "Directory in which to find SPIRV-Tools") +set_if_not_defined(DAWN_TINT_DIR "${Dawn_SOURCE_DIR}" "Directory in which to find Tint") +set_if_not_defined(DAWN_VULKAN_HEADERS_DIR "${DAWN_THIRD_PARTY_DIR}/vulkan-deps/vulkan-headers/src" "Directory in which to find Vulkan-Headers") + +# Dependencies for DAWN_BUILD_NODE_BINDINGS +set_if_not_defined(NODE_ADDON_API_DIR "${DAWN_THIRD_PARTY_DIR}/node-addon-api" "Directory in which to find node-addon-api") +set_if_not_defined(NODE_API_HEADERS_DIR "${DAWN_THIRD_PARTY_DIR}/node-api-headers" "Directory in which to find node-api-headers") +set_if_not_defined(WEBGPU_IDL_PATH "${DAWN_THIRD_PARTY_DIR}/gpuweb/webgpu.idl" "Path to the webgpu.idl definition file") +set_if_not_defined(GO_EXECUTABLE "go" "Golang executable for running the IDL generator") + +# Much of the backend code is shared among desktop OpenGL and OpenGL ES +if (${DAWN_ENABLE_DESKTOP_GL} OR ${DAWN_ENABLE_OPENGLES}) + set(DAWN_ENABLE_OPENGL ON) +endif() + +if(DAWN_ENABLE_PIC) + set(CMAKE_POSITION_INDEPENDENT_CODE ON) +endif() + +################################################################################ +# Dawn's public and internal "configs" +################################################################################ + +# The public config contains only the include paths for the Dawn headers. +add_library(dawn_public_config INTERFACE) +target_include_directories(dawn_public_config INTERFACE + "${DAWN_INCLUDE_DIR}" + "${DAWN_BUILD_GEN_DIR}/include" +) + +# The internal config conatins additional path but includes the dawn_public_config include paths +add_library(dawn_internal_config INTERFACE) +target_include_directories(dawn_internal_config INTERFACE + "${DAWN_SRC_DIR}" + "${DAWN_BUILD_GEN_DIR}/src" +) +target_link_libraries(dawn_internal_config INTERFACE dawn_public_config) + +# Compile definitions for the internal config +if (DAWN_ALWAYS_ASSERT OR $<CONFIG:Debug>) + target_compile_definitions(dawn_internal_config INTERFACE "DAWN_ENABLE_ASSERTS") +endif() +if (DAWN_ENABLE_D3D12) + target_compile_definitions(dawn_internal_config INTERFACE "DAWN_ENABLE_BACKEND_D3D12") +endif() +if (DAWN_ENABLE_METAL) + target_compile_definitions(dawn_internal_config INTERFACE "DAWN_ENABLE_BACKEND_METAL") +endif() +if (DAWN_ENABLE_NULL) + target_compile_definitions(dawn_internal_config INTERFACE "DAWN_ENABLE_BACKEND_NULL") +endif() +if (DAWN_ENABLE_DESKTOP_GL) + target_compile_definitions(dawn_internal_config INTERFACE "DAWN_ENABLE_BACKEND_DESKTOP_GL") +endif() +if (DAWN_ENABLE_OPENGLES) + target_compile_definitions(dawn_internal_config INTERFACE "DAWN_ENABLE_BACKEND_OPENGLES") +endif() +if (DAWN_ENABLE_OPENGL) + target_compile_definitions(dawn_internal_config INTERFACE "DAWN_ENABLE_BACKEND_OPENGL") +endif() +if (DAWN_ENABLE_VULKAN) + target_compile_definitions(dawn_internal_config INTERFACE "DAWN_ENABLE_BACKEND_VULKAN") +endif() +if (DAWN_USE_X11) + target_compile_definitions(dawn_internal_config INTERFACE "DAWN_USE_X11") +endif() +if (WIN32) + target_compile_definitions(dawn_internal_config INTERFACE "NOMINMAX" "WIN32_LEAN_AND_MEAN") +endif() + +set(CMAKE_CXX_STANDARD "17") + +################################################################################ +# Tint +################################################################################ + +# TINT_IS_SUBPROJECT is 1 if added via add_subdirectory() from another project. +get_directory_property(TINT_IS_SUBPROJECT PARENT_DIRECTORY) +if(TINT_IS_SUBPROJECT) + set(TINT_IS_SUBPROJECT 1) + + # If tint is used as a subproject, default to disabling the building of + # documentation and tests. These are unlikely to be desirable, but can be + # enabled. + set(TINT_BUILD_DOCS_DEFAULT OFF) + set(TINT_BUILD_TESTS_DEFAULT OFF) +else() + set(TINT_BUILD_DOCS_DEFAULT ON) + set(TINT_BUILD_TESTS_DEFAULT ON) +endif() + +# Forcing building docs off right now, since currently this will try to build docs for both Tint & Dawn, and Dawn isn't annotated yet. +set(TINT_BUILD_DOCS_DEFAULT OFF) option_if_not_defined(TINT_BUILD_SAMPLES "Build samples" ON) option_if_not_defined(TINT_BUILD_DOCS "Build documentation" ${TINT_BUILD_DOCS_DEFAULT}) @@ -200,7 +363,7 @@ endif() if (${TINT_BUILD_SPV_READER}) - include_directories("${TINT_THIRD_PARTY_DIR}/vulkan-deps/spirv-tools/src/include") + include_directories("${DAWN_THIRD_PARTY_DIR}/vulkan-deps/spirv-tools/include") endif() if((CMAKE_CXX_COMPILER_ID STREQUAL "Clang") AND (CMAKE_CXX_SIMULATE_ID STREQUAL "MSVC")) @@ -264,7 +427,7 @@ if (${TINT_BUILD_SPV_READER} OR ${TINT_BUILD_SPV_WRITER}) target_include_directories(${TARGET} PUBLIC - "${TINT_THIRD_PARTY_DIR}/vulkan-deps/spirv-headers/src/include") + "${DAWN_THIRD_PARTY_DIR}/spirv-headers/include") endif() target_compile_definitions(${TARGET} PUBLIC -DTINT_BUILD_SPV_READER=$<BOOL:${TINT_BUILD_SPV_READER}>) @@ -388,8 +551,25 @@ endif() endfunction() +################################################################################ +# Run on all subdirectories +################################################################################ + add_subdirectory(third_party) add_subdirectory(src/tint) +add_subdirectory(generator) +add_subdirectory(src/dawn) + +################################################################################ +# Samples +################################################################################ + +if (DAWN_BUILD_SAMPLES) + #TODO(dawn:269): Add this once implementation-based swapchains are removed. + #add_subdirectory(src/utils) + add_subdirectory(samples/dawn) +endif() + if (TINT_BUILD_SAMPLES) add_subdirectory(src/tint/cmd) endif()
diff --git a/DEPS b/DEPS index 7bf1906..0c13f63 100644 --- a/DEPS +++ b/DEPS
@@ -8,100 +8,257 @@ vars = { 'chromium_git': 'https://chromium.googlesource.com', + 'dawn_git': 'https://dawn.googlesource.com', + 'github_git': 'https://github.com', + 'swiftshader_git': 'https://swiftshader.googlesource.com', - 'tint_gn_revision': 'git_revision:281ba2c91861b10fec7407c4b6172ec3d4661243', + 'dawn_standalone': True, + 'dawn_node': False, # Also fetches dependencies required for building NodeJS bindings. + 'dawn_cmake_version': 'version:3.13.5', + 'dawn_cmake_win32_sha1': 'b106d66bcdc8a71ea2cdf5446091327bfdb1bcd7', + 'dawn_gn_version': 'git_revision:bd99dbf98cbdefe18a4128189665c5761263bcfb', + 'dawn_go_version': 'version:1.16', - # We don't use location metadata in our test isolates. + 'node_darwin_arm64_sha': '31859fc1fa0994a95f44f09c367d6ff63607cfde', + 'node_darwin_x64_sha': '16dfd094763b71988933a31735f9dea966f9abd6', + 'node_linux_x64_sha': 'ab9544e24e752d3d17f335fb7b2055062e582d11', + 'node_win_x64_sha': '5ef847033c517c499f56f9d136d159b663bab717', + + # GN variable required by //testing that will be output in the gclient_args.gni 'generate_location_tags': False, } deps = { - 'third_party/gpuweb-cts': { - 'url': '{chromium_git}/external/github.com/gpuweb/cts@b0291fd966b55a5efc496772555b94842bde1085', - }, - - 'third_party/vulkan-deps': { - 'url': '{chromium_git}/vulkan-deps@20efc30b0c6fe3c9bbd4f8ed6335593ee51391b0', - }, - # Dependencies required to use GN/Clang in standalone 'build': { - 'url': '{chromium_git}/chromium/src/build@555c8b467c21e2c4b22d00e87e3faa0431df9ac2', + 'url': '{chromium_git}/chromium/src/build@c7876b5a44308b94074287939244bc562007de69', + 'condition': 'dawn_standalone', }, - 'buildtools': { - 'url': '{chromium_git}/chromium/src/buildtools@f78b4b9f33bd8ef9944d5ce643daff1c31880189', + 'url': '{chromium_git}/chromium/src/buildtools@e1471b21ee9c6765ee95e9db0c76fe997ccad35c', + 'condition': 'dawn_standalone', }, - - 'tools/clang': { - 'url': '{chromium_git}/chromium/src/tools/clang@8b7330592cb85ba09505a6be7bacabd0ad6160a3', - }, - 'buildtools/clang_format/script': { - 'url': '{chromium_git}/external/github.com/llvm/llvm-project/clang/tools/clang-format.git@2271e89c145a5e27d6c110b6a1113c057a8301a3', + 'url': '{chromium_git}/external/github.com/llvm/llvm-project/clang/tools/clang-format.git@99803d74e35962f63a775f29477882afd4d57d94', + 'condition': 'dawn_standalone', }, - 'buildtools/linux64': { 'packages': [{ 'package': 'gn/gn/linux-amd64', - 'version': Var('tint_gn_revision'), + 'version': Var('dawn_gn_version'), }], 'dep_type': 'cipd', - 'condition': 'host_os == "linux"', + 'condition': 'dawn_standalone and host_os == "linux"', }, 'buildtools/mac': { 'packages': [{ 'package': 'gn/gn/mac-${{arch}}', - 'version': Var('tint_gn_revision'), + 'version': Var('dawn_gn_version'), }], 'dep_type': 'cipd', - 'condition': 'host_os == "mac"', + 'condition': 'dawn_standalone and host_os == "mac"', }, 'buildtools/win': { 'packages': [{ 'package': 'gn/gn/windows-amd64', - 'version': Var('tint_gn_revision'), + 'version': Var('dawn_gn_version'), }], 'dep_type': 'cipd', - 'condition': 'host_os == "win"', + 'condition': 'dawn_standalone and host_os == "win"', }, 'buildtools/third_party/libc++/trunk': { 'url': '{chromium_git}/external/github.com/llvm/llvm-project/libcxx.git@79a2e924d96e2fc1e4b937c42efd08898fa472d7', + 'condition': 'dawn_standalone', }, 'buildtools/third_party/libc++abi/trunk': { - 'url': '{chromium_git}/external/github.com/llvm/llvm-project/libcxxabi.git@2715a6c0de8dac4c7674934a6b3d30ba0c685271', + 'url': '{chromium_git}/external/github.com/llvm/llvm-project/libcxxabi.git@edde7bbc4049ae4a32257d9f16451312c763c601', + 'condition': 'dawn_standalone', }, - # Dependencies required for testing + 'tools/clang': { + 'url': '{chromium_git}/chromium/src/tools/clang@df9b14e26c163dd8e2c0ab081e2689f038ae7141', + 'condition': 'dawn_standalone', + }, + 'tools/clang/dsymutil': { + 'packages': [{ + 'package': 'chromium/llvm-build-tools/dsymutil', + 'version': 'M56jPzDv1620Rnm__jTMYS62Zi8rxHVq7yw0qeBFEgkC', + }], + 'condition': 'dawn_standalone and (checkout_mac or checkout_ios)', + 'dep_type': 'cipd', + }, + + # Testing, GTest and GMock 'testing': { 'url': '{chromium_git}/chromium/src/testing@d485ae97b7900c1fb7edfbe2901ae5adcb120865', + 'condition': 'dawn_standalone', }, - + 'third_party/googletest': { + 'url': '{chromium_git}/external/github.com/google/googletest@6b74da4757a549563d7c37c8fae3e704662a043b', + 'condition': 'dawn_standalone', + }, + # This is a dependency of //testing 'third_party/catapult': { 'url': '{chromium_git}/catapult.git@fa35beefb3429605035f98211ddb8750dee6a13d', + 'condition': 'dawn_standalone', }, + # Jinja2 and MarkupSafe for the code generator + 'third_party/jinja2': { + 'url': '{chromium_git}/chromium/src/third_party/jinja2@ee69aa00ee8536f61db6a451f3858745cf587de6', + 'condition': 'dawn_standalone', + }, + 'third_party/markupsafe': { + 'url': '{chromium_git}/chromium/src/third_party/markupsafe@0944e71f4b2cb9a871bcbe353f95e889b64a611a', + 'condition': 'dawn_standalone', + }, + + # GLFW for tests and samples + 'third_party/glfw': { + 'url': '{chromium_git}/external/github.com/glfw/glfw@94773111300fee0453844a4c9407af7e880b4df8', + 'condition': 'dawn_standalone', + }, + + 'third_party/vulkan_memory_allocator': { + 'url': '{chromium_git}/external/github.com/GPUOpen-LibrariesAndSDKs/VulkanMemoryAllocator@5e49f57a6e71a026a54eb42e366de09a4142d24e', + 'condition': 'dawn_standalone', + }, + + 'third_party/angle': { + 'url': '{chromium_git}/angle/angle@152616eedcfded69cab516e093efd3a98190fa5b', + 'condition': 'dawn_standalone', + }, + + 'third_party/swiftshader': { + 'url': '{swiftshader_git}/SwiftShader@9c16e141823e93c2d6ad05b94165cc18ffda6ffe', + 'condition': 'dawn_standalone', + }, + + 'third_party/vulkan-deps': { + 'url': '{chromium_git}/vulkan-deps@746dd371204b5b582e0ed0365909fefa4b0ef0fa', + 'condition': 'dawn_standalone', + }, + + 'third_party/zlib': { + 'url': '{chromium_git}/chromium/src/third_party/zlib@c29ee8c9c3824ca013479bf8115035527967fe02', + 'condition': 'dawn_standalone', + }, + + 'third_party/abseil-cpp': { + 'url': '{chromium_git}/chromium/src/third_party/abseil-cpp@789af048b388657987c59d4da406859034fe310f', + 'condition': 'dawn_standalone', + }, + + # WebGPU CTS - not used directly by Dawn, only transitively by Chromium. + 'third_party/webgpu-cts': { + 'url': '{chromium_git}/external/github.com/gpuweb/cts@87e74a93e0c046b30a798667f19a449fc99ddb5d', + 'condition': 'build_with_chromium', + }, + + # Dependencies required to build / run Dawn NodeJS bindings + 'third_party/node-api-headers': { + 'url': '{github_git}/nodejs/node-api-headers.git@d68505e4055ecb630e14c26c32e5c2c65e179bba', + 'condition': 'dawn_node', + }, + 'third_party/node-addon-api': { + 'url': '{github_git}/nodejs/node-addon-api.git@4a3de56c3e4ed0031635a2f642b27efeeed00add', + 'condition': 'dawn_node', + }, + 'third_party/gpuweb': { + 'url': '{github_git}/gpuweb/gpuweb.git@881403b5fda2d9ac9ffc5daa24e34738205bf155', + 'condition': 'dawn_node', + }, + 'third_party/gpuweb-cts': { + 'url': '{chromium_git}/external/github.com/gpuweb/cts@b0291fd966b55a5efc496772555b94842bde1085', + 'condition': 'dawn_standalone', + }, + + 'tools/golang': { + 'condition': 'dawn_node', + 'packages': [{ + 'package': 'infra/3pp/tools/go/${{platform}}', + 'version': Var('dawn_go_version'), + }], + 'dep_type': 'cipd', + }, + + 'tools/cmake': { + 'condition': 'dawn_node and (host_os == "mac" or host_os == "linux")', + 'packages': [{ + 'package': 'infra/3pp/tools/cmake/${{platform}}', + 'version': Var('dawn_cmake_version'), + }], + 'dep_type': 'cipd', + }, + + # Misc dependencies inherited from Tint 'third_party/benchmark': { 'url': '{chromium_git}/external/github.com/google/benchmark.git@e991355c02b93fe17713efe04cbc2e278e00fdbd', + 'condition': 'dawn_standalone', }, - - 'third_party/googletest': { - 'url': '{chromium_git}/external/github.com/google/googletest.git@6b74da4757a549563d7c37c8fae3e704662a043b', - }, - 'third_party/protobuf': { 'url': '{chromium_git}/external/github.com/protocolbuffers/protobuf.git@fde7cf7358ec7cd69e8db9be4f1fa6a5c431386a', + 'condition': 'dawn_standalone', }, } hooks = [ + # Pull the compilers and system libraries for hermetic builds + { + 'name': 'sysroot_x86', + 'pattern': '.', + 'condition': 'dawn_standalone and checkout_linux and (checkout_x86 or checkout_x64)', + 'action': ['python3', 'build/linux/sysroot_scripts/install-sysroot.py', + '--arch=x86'], + }, + { + 'name': 'sysroot_x64', + 'pattern': '.', + 'condition': 'dawn_standalone and checkout_linux and checkout_x64', + 'action': ['python3', 'build/linux/sysroot_scripts/install-sysroot.py', + '--arch=x64'], + }, + { + # Update the Mac toolchain if possible, this makes builders use "hermetic XCode" which is + # is more consistent (only changes when rolling build/) and is cached. + 'name': 'mac_toolchain', + 'pattern': '.', + 'condition': 'dawn_standalone and checkout_mac', + 'action': ['python3', 'build/mac_toolchain.py'], + }, + { + # Update the Windows toolchain if necessary. Must run before 'clang' below. + 'name': 'win_toolchain', + 'pattern': '.', + 'condition': 'dawn_standalone and checkout_win', + 'action': ['python3', 'build/vs_toolchain.py', 'update', '--force'], + }, + { + # Note: On Win, this should run after win_toolchain, as it may use it. + 'name': 'clang', + 'pattern': '.', + 'action': ['python3', 'tools/clang/scripts/update.py'], + 'condition': 'dawn_standalone', + }, + { + # Pull rc binaries using checked-in hashes. + 'name': 'rc_win', + 'pattern': '.', + 'condition': 'dawn_standalone and checkout_win and host_os == "win"', + 'action': [ 'download_from_google_storage', + '--no_resume', + '--no_auth', + '--bucket', 'chromium-browser-clang/rc', + '-s', 'build/toolchain/win/rc/win/rc.exe.sha1', + ], + }, # Pull clang-format binaries using checked-in hashes. { 'name': 'clang_format_win', 'pattern': '.', - 'condition': 'host_os == "win"', + 'condition': 'dawn_standalone and host_os == "win"', 'action': [ 'download_from_google_storage', '--no_resume', '--platform=win32', @@ -113,7 +270,7 @@ { 'name': 'clang_format_mac', 'pattern': '.', - 'condition': 'host_os == "mac"', + 'condition': 'dawn_standalone and host_os == "mac"', 'action': [ 'download_from_google_storage', '--no_resume', '--platform=darwin', @@ -125,7 +282,7 @@ { 'name': 'clang_format_linux', 'pattern': '.', - 'condition': 'host_os == "linux"', + 'condition': 'dawn_standalone and host_os == "linux"', 'action': [ 'download_from_google_storage', '--no_resume', '--platform=linux*', @@ -134,7 +291,6 @@ '-s', 'buildtools/linux64/clang-format.sha1', ], }, - # Pull the compilers and system libraries for hermetic builds { 'name': 'sysroot_x86', @@ -186,15 +342,90 @@ { 'name': 'lastchange', 'pattern': '.', + 'condition': 'dawn_standalone', 'action': ['python3', 'build/util/lastchange.py', '-o', 'build/util/LASTCHANGE'], }, + # TODO(https://crbug.com/1180257): Use CIPD for CMake on Windows. + { + 'name': 'cmake_win32', + 'pattern': '.', + 'condition': 'dawn_node and host_os == "win"', + 'action': [ 'download_from_google_storage', + '--no_resume', + '--platform=win32', + '--no_auth', + '--bucket', 'chromium-tools', + Var('dawn_cmake_win32_sha1'), + '-o', 'tools/cmake-win32.zip' + ], + }, + { + 'name': 'cmake_win32_extract', + 'pattern': '.', + 'condition': 'dawn_node and host_os == "win"', + 'action': [ 'python3', + 'scripts/extract.py', + 'tools/cmake-win32.zip', + 'tools/cmake-win32/', + ], + }, + + # Node binaries, when dawn_node is enabled + { + 'name': 'node_linux64', + 'pattern': '.', + 'condition': 'dawn_node and host_os == "linux"', + 'action': [ 'download_from_google_storage', + '--no_resume', + '--extract', + '--no_auth', + '--bucket', 'chromium-nodejs/16.13.0', + Var('node_linux_x64_sha'), + '-o', 'third_party/node/node-linux-x64.tar.gz', + ], + }, + { + 'name': 'node_mac', + 'pattern': '.', + 'condition': 'dawn_node and host_os == "mac"', + 'action': [ 'download_from_google_storage', + '--no_resume', + '--extract', + '--no_auth', + '--bucket', 'chromium-nodejs/16.13.0', + Var('node_darwin_x64_sha'), + '-o', 'third_party/node/node-darwin-x64.tar.gz', + ], + }, + { + 'name': 'node_mac_arm64', + 'pattern': '.', + 'condition': 'dawn_node and host_os == "mac"', + 'action': [ 'download_from_google_storage', + '--no_resume', + '--extract', + '--no_auth', + '--bucket', 'chromium-nodejs/16.13.0', + Var('node_darwin_arm64_sha'), + '-o', 'third_party/node/node-darwin-arm64.tar.gz', + ], + }, + { + 'name': 'node_win', + 'pattern': '.', + 'condition': 'dawn_node and host_os == "win"', + 'action': [ 'download_from_google_storage', + '--no_resume', + '--no_auth', + '--bucket', 'chromium-nodejs/16.13.0', + Var('node_win_x64_sha'), + '-o', 'third_party/node/node.exe', + ], + }, + ] recursedeps = [ - # buildtools provides clang_format, libc++, and libc++abi - 'buildtools', - # vulkan-deps provides spirv-headers, spirv-tools & gslang - # It also provides other Vulkan tools that Tint doesn't use 'third_party/vulkan-deps', ]
diff --git a/DIR_METADATA b/DIR_METADATA new file mode 100644 index 0000000..0ca8187 --- /dev/null +++ b/DIR_METADATA
@@ -0,0 +1,3 @@ +monorail { + component: "Internals>GPU>Dawn" +}
diff --git a/LICENSE b/LICENSE index d64569567..14b77bd 100644 --- a/LICENSE +++ b/LICENSE
@@ -3,9 +3,9 @@ Version 2.0, January 2004 http://www.apache.org/licenses/ - TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION - 1. Definitions. + 1. Definitions. "License" shall mean the terms and conditions for use, reproduction, and distribution as defined by Sections 1 through 9 of this document. @@ -64,14 +64,14 @@ on behalf of whom a Contribution has been received by Licensor and subsequently incorporated within the Work. - 2. Grant of Copyright License. Subject to the terms and conditions of + 2. Grant of Copyright License. Subject to the terms and conditions of this License, each Contributor hereby grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, irrevocable copyright license to reproduce, prepare Derivative Works of, publicly display, publicly perform, sublicense, and distribute the Work and such Derivative Works in Source or Object form. - 3. Grant of Patent License. Subject to the terms and conditions of + 3. Grant of Patent License. Subject to the terms and conditions of this License, each Contributor hereby grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, irrevocable (except as stated in this section) patent license to make, have made, @@ -87,7 +87,7 @@ granted to You under this License for that Work shall terminate as of the date such litigation is filed. - 4. Redistribution. You may reproduce and distribute copies of the + 4. Redistribution. You may reproduce and distribute copies of the Work or Derivative Works thereof in any medium, with or without modifications, and in Source or Object form, provided that You meet the following conditions: @@ -128,7 +128,7 @@ reproduction, and distribution of the Work otherwise complies with the conditions stated in this License. - 5. Submission of Contributions. Unless You explicitly state otherwise, + 5. Submission of Contributions. Unless You explicitly state otherwise, any Contribution intentionally submitted for inclusion in the Work by You to the Licensor shall be under the terms and conditions of this License, without any additional terms or conditions. @@ -136,12 +136,12 @@ the terms of any separate license agreement you may have executed with Licensor regarding such Contributions. - 6. Trademarks. This License does not grant permission to use the trade + 6. Trademarks. This License does not grant permission to use the trade names, trademarks, service marks, or product names of the Licensor, except as required for reasonable and customary use in describing the origin of the Work and reproducing the content of the NOTICE file. - 7. Disclaimer of Warranty. Unless required by applicable law or + 7. Disclaimer of Warranty. Unless required by applicable law or agreed to in writing, Licensor provides the Work (and each Contributor provides its Contributions) on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or @@ -151,7 +151,7 @@ appropriateness of using or redistributing the Work and assume any risks associated with Your exercise of permissions under this License. - 8. Limitation of Liability. In no event and under no legal theory, + 8. Limitation of Liability. In no event and under no legal theory, whether in tort (including negligence), contract, or otherwise, unless required by applicable law (such as deliberate and grossly negligent acts) or agreed to in writing, shall any Contributor be @@ -163,7 +163,7 @@ other commercial damages or losses), even if such Contributor has been advised of the possibility of such damages. - 9. Accepting Warranty or Additional Liability. While redistributing + 9. Accepting Warranty or Additional Liability. While redistributing the Work or Derivative Works thereof, You may choose to offer, and charge a fee for, acceptance of support, warranty, indemnity, or other liability obligations and/or rights consistent with this @@ -174,9 +174,9 @@ incurred by, or claims asserted against, such Contributor by reason of your accepting any such warranty or additional liability. - END OF TERMS AND CONDITIONS + END OF TERMS AND CONDITIONS - APPENDIX: How to apply the Apache License to your work. + APPENDIX: How to apply the Apache License to your work. To apply the Apache License to your work, attach the following boilerplate notice, with the fields enclosed by brackets "[]" @@ -187,16 +187,49 @@ same "printed page" as the copyright notice for easier identification within third-party archives. - Copyright [yyyy] [name of copyright owner] + Copyright [yyyy] [name of copyright owner] - Licensed under the Apache License, Version 2.0 (the "License"); - you may not use this file except in compliance with the License. - You may obtain a copy of the License at + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 - Unless required by applicable law or agreed to in writing, software - distributed under the License is distributed on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - See the License for the specific language governing permissions and - limitations under the License. + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + +---------- +The following license is exclusively used by the template generated header files. + +BSD 3-Clause License + +Copyright (c) 2019, "WebGPU native" developers +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are met: + +1. Redistributions of source code must retain the above copyright notice, this + list of conditions and the following disclaimer. + +2. Redistributions in binary form must reproduce the above copyright notice, + this list of conditions and the following disclaimer in the documentation + and/or other materials provided with the distribution. + +3. Neither the name of the copyright holder nor the names of its + contributors may be used to endorse or promote products derived from + this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE +FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL +DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR +SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER +CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, +OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
diff --git a/OWNERS b/OWNERS index 18239af..a7cef5e 100644 --- a/OWNERS +++ b/OWNERS
@@ -1,3 +1,16 @@ +cwallez@chromium.org +enga@chromium.org +jiawei.shao@intel.com + +# Backup reviewers if needed. +bclayton@google.com +kainino@chromium.org + +per-file dawn.json=kainino@chromium.org +per-file DEPS=* +per-file README.md=file://docs/dawn/OWNERS + +# Tint specific OWNERS amaiorano@google.com bclayton@chromium.org bclayton@google.com
diff --git a/OWNERS.dawn b/OWNERS.dawn new file mode 100644 index 0000000..1856f30 --- /dev/null +++ b/OWNERS.dawn
@@ -0,0 +1,11 @@ +cwallez@chromium.org +enga@chromium.org +jiawei.shao@intel.com + +# Backup reviewers if needed. +bclayton@google.com +kainino@chromium.org + +per-file dawn.json=kainino@chromium.org +per-file DEPS=* +per-file README.md=file://docs/dawn/OWNERS
diff --git a/OWNERS.tint b/OWNERS.tint new file mode 100644 index 0000000..18239af --- /dev/null +++ b/OWNERS.tint
@@ -0,0 +1,7 @@ +amaiorano@google.com +bclayton@chromium.org +bclayton@google.com +cwallez@chromium.org +dneto@google.com +jrprice@google.com +rharrison@chromium.org
diff --git a/PRESUBMIT.py b/PRESUBMIT.py old mode 100755 new mode 100644 index 97623c1..968a27c --- a/PRESUBMIT.py +++ b/PRESUBMIT.py
@@ -1,4 +1,4 @@ -# Copyright 2020 The Tint Authors +# Copyright 2022 The Dawn & Tint Authors # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. @@ -11,42 +11,12 @@ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. -"""Presubmit script for Tint. -See http://dev.chromium.org/developers/how-tos/depottools/presubmit-scripts -for more details about the presubmit API built into depot_tools. -""" import re USE_PYTHON3 = True - -def _LicenseHeader(input_api): - """Returns the license header regexp.""" - # Accept any year number from 2019 to the current year - current_year = int(input_api.time.strftime('%Y')) - allowed_years = (str(s) for s in reversed(xrange(2019, current_year + 1))) - years_re = '(' + '|'.join(allowed_years) + ')' - license_header = ( - r'.*? Copyright( \(c\))? %(year)s The Tint [Aa]uthors\n ' - r'.*?\n' - r'.*? Licensed under the Apache License, Version 2.0 (the "License");\n' - r'.*? you may not use this file except in compliance with the License.\n' - r'.*? You may obtain a copy of the License at\n' - r'.*?\n' - r'.*? http://www.apache.org/licenses/LICENSE-2.0\n' - r'.*?\n' - r'.*? Unless required by applicable law or agreed to in writing, software\n' - r'.*? distributed under the License is distributed on an "AS IS" BASIS,\n' - r'.*? WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n' - r'.*? See the License for the specific language governing permissions and\n' - r'.*? limitations under the License.\n') % { - 'year': years_re, - } - return license_header - - -REGEXES = [ +NONINCLUSIVE_REGEXES = [ r"(?i)black[-_]?list", r"(?i)white[-_]?list", r"(?i)gr[ea]y[-_]?list", @@ -97,18 +67,19 @@ r"(?i)red[-_]?line", ] -REGEX_LIST = [] -for reg in REGEXES: - REGEX_LIST.append(re.compile(reg)) +NONINCLUSIVE_REGEX_LIST = [] +for reg in NONINCLUSIVE_REGEXES: + NONINCLUSIVE_REGEX_LIST.append(re.compile(reg)) -def CheckNonInclusiveLanguage(input_api, output_api, source_file_filter=None): + +def _CheckNonInclusiveLanguage(input_api, output_api, source_file_filter=None): """Checks the files for non-inclusive language.""" matches = [] for f in input_api.AffectedFiles(include_deletes=False, file_filter=source_file_filter): for line_num, line in f.ChangedContents(): - for reg in REGEX_LIST: + for reg in NONINCLUSIVE_REGEX_LIST: match = reg.search(line) if match: matches.append( @@ -124,44 +95,53 @@ return [] -def CheckChange(input_api, output_api): +def _NonInclusiveFileFilter(file): + filter_list = [ + "PRESUBMIT.py", # Non-inclusive language check data + "docs/tint/spirv-input-output-variables.md", # External URL + "test/tint/samples/compute_boids.wgsl ", # External URL + ] + return file in filter_list + + +def _DoCommonChecks(input_api, output_api): results = [] - - results += input_api.canned_checks.CheckChangeHasDescription( - input_api, output_api) - results += input_api.canned_checks.CheckPatchFormatted(input_api, - output_api, - check_python=True) - results += input_api.canned_checks.CheckGNFormatted(input_api, output_api) - results += input_api.canned_checks.CheckChangeHasNoCrAndHasOnlyOneEol( - input_api, output_api) - results += input_api.canned_checks.CheckChangeHasNoTabs( - input_api, output_api) - results += input_api.canned_checks.CheckChangeTodoHasOwner( - input_api, output_api) - results += input_api.canned_checks.CheckChangeHasNoStrayWhitespace( - input_api, output_api) - results += input_api.canned_checks.CheckDoNotSubmit(input_api, output_api) - results += input_api.canned_checks.CheckChangeLintsClean(input_api, - output_api, - lint_filters="") - - def NonInclusiveFileFilter(file): - filter_list = [ - "docs/tint/spirv-input-output-variables.md", # External URL - "test/tint/samples/compute_boids.wgsl ", # External URL - ] - return file in filter_list - - results += CheckNonInclusiveLanguage(input_api, output_api, - NonInclusiveFileFilter) - + results.extend( + input_api.canned_checks.CheckChangedLUCIConfigs(input_api, output_api)) + results.extend( + input_api.canned_checks.CheckPatchFormatted(input_api, + output_api, + check_python=True)) + results.extend( + input_api.canned_checks.CheckChangeHasDescription( + input_api, output_api)) + results.extend( + input_api.canned_checks.CheckGNFormatted(input_api, output_api)) + results.extend( + input_api.canned_checks.CheckChangeHasNoCrAndHasOnlyOneEol( + input_api, output_api)) + results.extend( + input_api.canned_checks.CheckChangeHasNoTabs(input_api, output_api)) + results.extend( + input_api.canned_checks.CheckChangeTodoHasOwner(input_api, output_api)) + results.extend( + input_api.canned_checks.CheckChangeHasNoStrayWhitespace( + input_api, output_api)) + results.extend( + input_api.canned_checks.CheckDoNotSubmit(input_api, output_api)) + results.extend( + input_api.canned_checks.CheckChangeLintsClean(input_api, + output_api, + lint_filters="")) + results.extend( + _CheckNonInclusiveLanguage(input_api, output_api, + _NonInclusiveFileFilter)) return results def CheckChangeOnUpload(input_api, output_api): - return CheckChange(input_api, output_api) + return _DoCommonChecks(input_api, output_api) def CheckChangeOnCommit(input_api, output_api): - return CheckChange(input_api, output_api) + return _DoCommonChecks(input_api, output_api)
diff --git a/PRESUBMIT.py.dawn b/PRESUBMIT.py.dawn new file mode 100644 index 0000000..899e0e2 --- /dev/null +++ b/PRESUBMIT.py.dawn
@@ -0,0 +1,38 @@ +# Copyright 2018 The Dawn Authors +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +import os +import platform +import subprocess + +USE_PYTHON3 = True + + +def _DoCommonChecks(input_api, output_api): + results = [] + results.extend( + input_api.canned_checks.CheckChangedLUCIConfigs(input_api, output_api)) + results.extend( + input_api.canned_checks.CheckPatchFormatted(input_api, + output_api, + check_python=True)) + return results + + +def CheckChangeOnUpload(input_api, output_api): + return _DoCommonChecks(input_api, output_api) + + +def CheckChangeOnCommit(input_api, output_api): + return _DoCommonChecks(input_api, output_api)
diff --git a/PRESUBMIT.py.tint b/PRESUBMIT.py.tint new file mode 100755 index 0000000..97623c1 --- /dev/null +++ b/PRESUBMIT.py.tint
@@ -0,0 +1,167 @@ +# Copyright 2020 The Tint Authors +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +"""Presubmit script for Tint. +See http://dev.chromium.org/developers/how-tos/depottools/presubmit-scripts +for more details about the presubmit API built into depot_tools. +""" + +import re + +USE_PYTHON3 = True + + +def _LicenseHeader(input_api): + """Returns the license header regexp.""" + # Accept any year number from 2019 to the current year + current_year = int(input_api.time.strftime('%Y')) + allowed_years = (str(s) for s in reversed(xrange(2019, current_year + 1))) + years_re = '(' + '|'.join(allowed_years) + ')' + license_header = ( + r'.*? Copyright( \(c\))? %(year)s The Tint [Aa]uthors\n ' + r'.*?\n' + r'.*? Licensed under the Apache License, Version 2.0 (the "License");\n' + r'.*? you may not use this file except in compliance with the License.\n' + r'.*? You may obtain a copy of the License at\n' + r'.*?\n' + r'.*? http://www.apache.org/licenses/LICENSE-2.0\n' + r'.*?\n' + r'.*? Unless required by applicable law or agreed to in writing, software\n' + r'.*? distributed under the License is distributed on an "AS IS" BASIS,\n' + r'.*? WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n' + r'.*? See the License for the specific language governing permissions and\n' + r'.*? limitations under the License.\n') % { + 'year': years_re, + } + return license_header + + +REGEXES = [ + r"(?i)black[-_]?list", + r"(?i)white[-_]?list", + r"(?i)gr[ea]y[-_]?list", + r"(?i)(first class citizen)", + r"(?i)black[-_]?hat", + r"(?i)white[-_]?hat", + r"(?i)gr[ea]y[-_]?hat", + r"(?i)master", + r"(?i)slave", + r"(?i)\bhim\b", + r"(?i)\bhis\b", + r"(?i)\bshe\b", + r"(?i)\bher\b", + r"(?i)\bguys\b", + r"(?i)\bhers\b", + r"(?i)\bman\b", + r"(?i)\bwoman\b", + r"(?i)\she\s", + r"(?i)\she$", + r"(?i)^he\s", + r"(?i)^he$", + r"(?i)\she['|\u2019]d\s", + r"(?i)\she['|\u2019]d$", + r"(?i)^he['|\u2019]d\s", + r"(?i)^he['|\u2019]d$", + r"(?i)\she['|\u2019]s\s", + r"(?i)\she['|\u2019]s$", + r"(?i)^he['|\u2019]s\s", + r"(?i)^he['|\u2019]s$", + r"(?i)\she['|\u2019]ll\s", + r"(?i)\she['|\u2019]ll$", + r"(?i)^he['|\u2019]ll\s", + r"(?i)^he['|\u2019]ll$", + r"(?i)grandfather", + r"(?i)\bmitm\b", + r"(?i)\bcrazy\b", + r"(?i)\binsane\b", + r"(?i)\bblind\sto\b", + r"(?i)\bflying\sblind\b", + r"(?i)\bblind\seye\b", + r"(?i)\bcripple\b", + r"(?i)\bcrippled\b", + r"(?i)\bdumb\b", + r"(?i)\bdummy\b", + r"(?i)\bparanoid\b", + r"(?i)\bsane\b", + r"(?i)\bsanity\b", + r"(?i)red[-_]?line", +] + +REGEX_LIST = [] +for reg in REGEXES: + REGEX_LIST.append(re.compile(reg)) + +def CheckNonInclusiveLanguage(input_api, output_api, source_file_filter=None): + """Checks the files for non-inclusive language.""" + + matches = [] + for f in input_api.AffectedFiles(include_deletes=False, + file_filter=source_file_filter): + for line_num, line in f.ChangedContents(): + for reg in REGEX_LIST: + match = reg.search(line) + if match: + matches.append( + "{} ({}): found non-inclusive language: {}".format( + f.LocalPath(), line_num, match.group(0))) + + if len(matches): + return [ + output_api.PresubmitPromptWarning('Non-inclusive language found:', + items=matches) + ] + + return [] + + +def CheckChange(input_api, output_api): + results = [] + + results += input_api.canned_checks.CheckChangeHasDescription( + input_api, output_api) + results += input_api.canned_checks.CheckPatchFormatted(input_api, + output_api, + check_python=True) + results += input_api.canned_checks.CheckGNFormatted(input_api, output_api) + results += input_api.canned_checks.CheckChangeHasNoCrAndHasOnlyOneEol( + input_api, output_api) + results += input_api.canned_checks.CheckChangeHasNoTabs( + input_api, output_api) + results += input_api.canned_checks.CheckChangeTodoHasOwner( + input_api, output_api) + results += input_api.canned_checks.CheckChangeHasNoStrayWhitespace( + input_api, output_api) + results += input_api.canned_checks.CheckDoNotSubmit(input_api, output_api) + results += input_api.canned_checks.CheckChangeLintsClean(input_api, + output_api, + lint_filters="") + + def NonInclusiveFileFilter(file): + filter_list = [ + "docs/tint/spirv-input-output-variables.md", # External URL + "test/tint/samples/compute_boids.wgsl ", # External URL + ] + return file in filter_list + + results += CheckNonInclusiveLanguage(input_api, output_api, + NonInclusiveFileFilter) + + return results + + +def CheckChangeOnUpload(input_api, output_api): + return CheckChange(input_api, output_api) + + +def CheckChangeOnCommit(input_api, output_api): + return CheckChange(input_api, output_api)
diff --git a/README.chromium b/README.chromium new file mode 100644 index 0000000..e14cc8b --- /dev/null +++ b/README.chromium
@@ -0,0 +1,12 @@ +Name: Dawn +Short Name: dawn +URL: https://dawn.googlesource.com/dawn +License: Apache 2.0 +License File: LICENSE +Security Critical: yes + +Description: +Dawn is an implementation of the WebGPU standard exposed through a C/C++ +interface. It provides implementations on top of native graphics APIs like +D3D12, Metal and Vulkan, as well as a client-server implementation to remote +WebGPU outside sandboxed context like Chromium's render processes.
diff --git a/README.md b/README.md index fbe6cfb..a430410 100644 --- a/README.md +++ b/README.md
@@ -1,3 +1,56 @@ + + +# Dawn, a WebGPU implementation + +Dawn is an open-source and cross-platform implementation of the work-in-progress [WebGPU](https://webgpu.dev) standard. +More precisely it implements [`webgpu.h`](https://github.com/webgpu-native/webgpu-headers/blob/master/webgpu.h) that is a one-to-one mapping with the WebGPU IDL. +Dawn is meant to be integrated as part of a larger system and is the underlying implementation of WebGPU in Chromium. + +Dawn provides several WebGPU building blocks: + - **WebGPU C/C++ headers** that applications and other building blocks use. + - The `webgpu.h` version that Dawn implements. + - A C++ wrapper for the `webgpu.h`. + - **A "native" implementation of WebGPU** using platforms' GPU APIs: + - **D3D12** on Windows 10 + - **Metal** on macOS and iOS + - **Vulkan** on Windows, Linux, ChromeOS, Android and Fuchsia + - OpenGL as best effort where available + - **A client-server implementation of WebGPU** for applications that are in a sandbox without access to native drivers + +Helpful links: + + - [Dawn's bug tracker](https://bugs.chromium.org/p/dawn/issues/entry) if you find issues with Dawn. + - [Dawn's mailing list](https://groups.google.com/forum/#!members/dawn-graphics) for other discussions related to Dawn. + - [Dawn's source code](https://dawn.googlesource.com/dawn) + - [Dawn's Matrix chatroom](https://matrix.to/#/#webgpu-dawn:matrix.org) for live discussion around contributing or using Dawn. + - [WebGPU's Matrix chatroom](https://matrix.to/#/#WebGPU:matrix.org) + +## Documentation table of content + +Developer documentation: + + - [Dawn overview](docs/dawn/overview.md) + - [Building Dawn](docs/dawn/building.md) + - [Contributing to Dawn](docs/dawn/contributing.md) + - [Testing Dawn](docs/dawn/testing.md) + - [Debugging Dawn](docs/dawn/debugging.md) + - [Dawn's infrastructure](docs/dawn/infra.md) + - [Dawn errors](docs/dawn/errors.md) + +User documentation: (TODO, figure out what overlaps with the webgpu.h docs) + +## Status + +(TODO) + +## License + +Apache 2.0 Public License, please see [LICENSE](/LICENSE). + +## Disclaimer + +This is not an officially supported Google product. + # Tint Tint is a compiler for the WebGPU Shader Language (WGSL).
diff --git a/README.md.dawn b/README.md.dawn new file mode 100644 index 0000000..1871388 --- /dev/null +++ b/README.md.dawn
@@ -0,0 +1,52 @@ + + +# Dawn, a WebGPU implementation + +Dawn is an open-source and cross-platform implementation of the work-in-progress [WebGPU](https://webgpu.dev) standard. +More precisely it implements [`webgpu.h`](https://github.com/webgpu-native/webgpu-headers/blob/master/webgpu.h) that is a one-to-one mapping with the WebGPU IDL. +Dawn is meant to be integrated as part of a larger system and is the underlying implementation of WebGPU in Chromium. + +Dawn provides several WebGPU building blocks: + - **WebGPU C/C++ headers** that applications and other building blocks use. + - The `webgpu.h` version that Dawn implements. + - A C++ wrapper for the `webgpu.h`. + - **A "native" implementation of WebGPU** using platforms' GPU APIs: + - **D3D12** on Windows 10 + - **Metal** on macOS and iOS + - **Vulkan** on Windows, Linux, ChromeOS, Android and Fuchsia + - OpenGL as best effort where available + - **A client-server implementation of WebGPU** for applications that are in a sandbox without access to native drivers + +Helpful links: + + - [Dawn's bug tracker](https://bugs.chromium.org/p/dawn/issues/entry) if you find issues with Dawn. + - [Dawn's mailing list](https://groups.google.com/forum/#!members/dawn-graphics) for other discussions related to Dawn. + - [Dawn's source code](https://dawn.googlesource.com/dawn) + - [Dawn's Matrix chatroom](https://matrix.to/#/#webgpu-dawn:matrix.org) for live discussion around contributing or using Dawn. + - [WebGPU's Matrix chatroom](https://matrix.to/#/#WebGPU:matrix.org) + +## Documentation table of content + +Developer documentation: + + - [Dawn overview](docs/dawn/overview.md) + - [Building Dawn](docs/dawn/building.md) + - [Contributing to Dawn](docs/dawn/contributing.md) + - [Testing Dawn](docs/dawn/testing.md) + - [Debugging Dawn](docs/dawn/debugging.md) + - [Dawn's infrastructure](docs/dawn/infra.md) + - [Dawn errors](docs/dawn/errors.md) + +User documentation: (TODO, figure out what overlaps with the webgpu.h docs) + +## Status + +(TODO) + +## License + +Apache 2.0 Public License, please see [LICENSE](/LICENSE). + +## Disclaimer + +This is not an officially supported Google product.
diff --git a/README.md.tint b/README.md.tint new file mode 100644 index 0000000..fbe6cfb --- /dev/null +++ b/README.md.tint
@@ -0,0 +1,106 @@ +# Tint + +Tint is a compiler for the WebGPU Shader Language (WGSL). + +This is not an officially supported Google product. + +## Requirements + * Git + * CMake (3.10.2 or later) + * Ninja (or other build tool) + * Python, for fetching dependencies + * [depot_tools] in your path + +## Build options + * `TINT_BUILD_SPV_READER` : enable the SPIR-V input reader (off by default) + * `TINT_BUILD_WGSL_READER` : enable the WGSL input reader (on by default) + * `TINT_BUILD_SPV_WRITER` : enable the SPIR-V output writer (on by default) + * `TINT_BUILD_WGSL_WRITER` : enable the WGSL output writer (on by default) + * `TINT_BUILD_FUZZERS` : enable building fuzzzers (off by default) + +## Building +Tint uses Chromium dependency management so you need to install [depot_tools] +and add it to your PATH. + +[depot_tools]: http://commondatastorage.googleapis.com/chrome-infra-docs/flat/depot_tools/docs/html/depot_tools_tutorial.html#_setting_up + +### Getting source & dependencies + +```sh +# Clone the repo as "tint" +git clone https://dawn.googlesource.com/tint tint +cd tint + +# Bootstrap the gclient configuration +cp standalone.gclient .gclient + +# Fetch external dependencies and toolchains with gclient +gclient sync +``` + +### Compiling using CMake + Ninja +```sh +mkdir -p out/Debug +cd out/Debug +cmake -GNinja ../.. +ninja # or autoninja +``` + +### Compiling using CMake + make +```sh +mkdir -p out/Debug +cd out/Debug +cmake ../.. +make # -j N for N-way parallel build +``` + +### Compiling using gn + ninja +```sh +mkdir -p out/Debug +gn gen out/Debug +autoninja -C out/Debug +``` + +### Fuzzers on MacOS +If you are attempting fuzz, using `TINT_BUILD_FUZZERS=ON`, the version of llvm +in the XCode SDK does not have the needed libfuzzer functionality included. + +The build error that you will see from using the XCode SDK will look something +like this: +``` +ld: file not found:/Applications/Xcode.app/Contents/Developer/Toolchains/XcodeDefault.xctoolchain/usr/lib/clang/11.0.0/lib/darwin/libclang_rt.fuzzer_osx.a +``` + +The solution to this problem is to use a full version llvm, like what you would +get via homebrew, `brew install llvm`, and use something like `CC=<path to full +clang> cmake ..` to setup a build using that toolchain. + +### Checking [chromium-style] issues in CMake builds +The gn based work flow uses the Chromium toolchain for building in anticipation +of integration of Tint into Chromium based projects. This toolchain has +additional plugins for checking for style issues, which are marked with +[chromium-style] in log messages. This means that this toolchain is more strict +then the default clang toolchain. + +In the future we will have a CQ that will build this work flow and flag issues +automatically. Until that is in place, to avoid causing breakages you can run +the [chromium-style] checks using the CMake based work flows. This requires +setting `CC` to the version of clang checked out by `gclient sync` and setting +the `TINT_CHECK_CHROMIUM_STYLE` to `ON`. + +```sh +mkdir -p out/style +cd out/style +cmake ../.. +CC=../../third_party/llvm-build/Release+Asserts/bin/clang cmake -DTINT_CHECK_CHROMIUM_STYLE=ON ../../ # add -GNinja for ninja builds +``` + +## Issues +Please file any issues or feature requests at +https://bugs.chromium.org/p/tint/issues/entry + +## Contributing +Please see the CONTRIBUTING and CODE_OF_CONDUCT files on how to contribute to +Tint. + +Tint has a process for supporting [experimental extensions](docs/tint/experimental_extensions.md).
diff --git a/build_overrides/angle.gni b/build_overrides/angle.gni new file mode 100644 index 0000000..85d4d95 --- /dev/null +++ b/build_overrides/angle.gni
@@ -0,0 +1,26 @@ +# Copyright 2020 The Dawn Authors +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +# Override for angle_root +angle_root = "//third_party/angle" + +# True if ANGLE can access build/, testing/ and other Chrome folders. +angle_has_build = true + +# Paths to ANGLE dependencies in Dawn +angle_glslang_dir = "//third_party/vulkan-deps/glslang/src" +angle_spirv_cross_dir = "//third_party/vulkan-deps/spirv-cross/src" +angle_spirv_headers_dir = "//third_party/vulkan-deps/spirv-headers/src" +angle_spirv_tools_dir = "//third_party/vulkan-deps/spirv-tools/src" +angle_vulkan_memory_allocator_dir = "//third_party/vulkan_memory_allocator"
diff --git a/build_overrides/build.gni b/build_overrides/build.gni index 11ce9f4..8717867 100644 --- a/build_overrides/build.gni +++ b/build_overrides/build.gni
@@ -1,4 +1,4 @@ -# Copyright 2020 The Tint Authors. +# Copyright 2022 The Dawn Authors # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. @@ -13,10 +13,10 @@ # limitations under the License. declare_args() { - # Tell Tint and dependencies to not do Chromium-specific things + # Tell Dawn and dependencies to not do Chromium-specific things build_with_chromium = false - # In standalone Tint builds, don't try to use the hermetic install of Xcode + # In standalone Dawn builds, don't try to use the hermetic install of Xcode # that Chromium uses use_system_xcode = ""
diff --git a/build_overrides/dawn.gni b/build_overrides/dawn.gni new file mode 100644 index 0000000..87e1ded --- /dev/null +++ b/build_overrides/dawn.gni
@@ -0,0 +1,39 @@ +# Copyright 2018 The Dawn Authors +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +# These are variables that are overridable by projects that include Dawn. +# The values in this file are the defaults for when we are building from +# Dawn's repository. + +# Whether we are building from Dawn's repository. +# MUST be unset in other projects (will default to false). +dawn_standalone = true + +# True if Dawn can access build/, testing/ and other Chrome folders. +dawn_has_build = true + +# Defaults for these are set again in dawn_overrides_with_defaults.gni so that +# users of Dawn don't have to set dirs if they happen to use the same as Dawn. + +# The paths to Dawn's dependencies +dawn_abseil_dir = "//third_party/abseil-cpp" +dawn_angle_dir = "//third_party/angle" +dawn_jinja2_dir = "//third_party/jinja2" +dawn_glfw_dir = "//third_party/glfw" +dawn_googletest_dir = "//third_party/googletest" +dawn_spirv_tools_dir = "//third_party/vulkan-deps/spirv-tools/src" +dawn_swiftshader_dir = "//third_party/swiftshader" +dawn_vulkan_loader_dir = "//third_party/vulkan-deps/vulkan-loader/src" +dawn_vulkan_validation_layers_dir = + "//third_party/vulkan-deps/vulkan-validation-layers/src"
diff --git a/build_overrides/glslang.gni b/build_overrides/glslang.gni index 80a30c9..69d968d 100644 --- a/build_overrides/glslang.gni +++ b/build_overrides/glslang.gni
@@ -1,4 +1,4 @@ -# Copyright 2021 The Dawn Authors +# Copyright 2018 The Dawn Authors # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License.
diff --git a/build_overrides/spirv_tools.gni b/build_overrides/spirv_tools.gni index 13bffc5..48e7b11 100644 --- a/build_overrides/spirv_tools.gni +++ b/build_overrides/spirv_tools.gni
@@ -1,4 +1,4 @@ -# Copyright 2020 The Tint Authors +# Copyright 2018 The Dawn Authors # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. @@ -12,9 +12,9 @@ # See the License for the specific language governing permissions and # limitations under the License. -# We are building inside Tint +# We are building inside Dawn spirv_tools_standalone = false -# Paths to SPIRV-Tools dependencies in Tint +# Paths to SPIRV-Tools dependencies in Dawn spirv_tools_googletest_dir = "//third_party/googletest" spirv_tools_spirv_headers_dir = "//third_party/vulkan-deps/spirv-headers/src"
diff --git a/build_overrides/swiftshader.gni b/build_overrides/swiftshader.gni new file mode 100644 index 0000000..dc3579b --- /dev/null +++ b/build_overrides/swiftshader.gni
@@ -0,0 +1,23 @@ +# Copyright 2019 The Dawn Authors +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +# We are building SwiftShader inside Dawn +swiftshader_standalone = false + +# Path to SwiftShader +swiftshader_dir = "//third_party/swiftshader" + +# Forward to ozone_platform_x11 when inside Dawn's repository +import("../scripts/dawn_features.gni") +ozone_platform_x11 = dawn_use_x11
diff --git a/build_overrides/tint.gni b/build_overrides/tint.gni index fdcc866..8349998 100644 --- a/build_overrides/tint.gni +++ b/build_overrides/tint.gni
@@ -1,4 +1,4 @@ -# Copyright 2020 The Tint Authors. +# Copyright 2020 The Dawn Authors. # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. @@ -12,4 +12,10 @@ # See the License for the specific language governing permissions and # limitations under the License. -# This file contains Tint-related overrides. +tint_spirv_tools_dir = "//third_party/vulkan-deps/spirv-tools/src" +tint_spirv_headers_dir = "//third_party/vulkan-deps/spirv-headers/src" + +tint_build_spv_reader = true +tint_build_spv_writer = true +tint_build_wgsl_reader = true +tint_build_wgsl_writer = true
diff --git a/build_overrides/vulkan_common.gni b/build_overrides/vulkan_common.gni new file mode 100644 index 0000000..9a883e7 --- /dev/null +++ b/build_overrides/vulkan_common.gni
@@ -0,0 +1,19 @@ +# Copyright 2021 The Dawn Authors +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +vulkan_headers_dir = "//third_party/vulkan-deps/vulkan-headers/src" + +# Subdirectories for generated files +vulkan_data_subdir = "vulkandata" +vulkan_gen_subdir = ""
diff --git a/build_overrides/vulkan_headers.gni b/build_overrides/vulkan_headers.gni new file mode 100644 index 0000000..4c0047a --- /dev/null +++ b/build_overrides/vulkan_headers.gni
@@ -0,0 +1,17 @@ +# Copyright 2020 The Dawn Authors +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +# Fake the vulkan_use_x11 when inside Dawn's repository +import("../scripts/dawn_features.gni") +vulkan_use_x11 = dawn_use_x11
diff --git a/build_overrides/vulkan_loader.gni b/build_overrides/vulkan_loader.gni new file mode 100644 index 0000000..6f6eaf0 --- /dev/null +++ b/build_overrides/vulkan_loader.gni
@@ -0,0 +1,17 @@ +# Copyright 2020 The Dawn Authors +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +import("//build_overrides/vulkan_common.gni") + +vulkan_loader_shared = !is_mac
diff --git a/build_overrides/vulkan_tools.gni b/build_overrides/vulkan_tools.gni new file mode 100644 index 0000000..7bd4d99 --- /dev/null +++ b/build_overrides/vulkan_tools.gni
@@ -0,0 +1,15 @@ +# Copyright 2021 The Dawn Authors +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +import("//build_overrides/vulkan_common.gni")
diff --git a/build_overrides/vulkan_validation_layers.gni b/build_overrides/vulkan_validation_layers.gni new file mode 100644 index 0000000..9463e66 --- /dev/null +++ b/build_overrides/vulkan_validation_layers.gni
@@ -0,0 +1,25 @@ +# Copyright 2019 The Dawn Authors +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +import("//build_overrides/vulkan_common.gni") + +# These are variables that are overridable by projects that include Dawn. +# The values in this file are the defaults for when we are building from +# Dawn's repository. +vvl_spirv_tools_dir = "//third_party/vulkan-deps/spirv-tools/src" +vvl_glslang_dir = "//third_party/vulkan-deps/glslang/src" + +# Forward to ozone_platform_x11 when inside Dawn's repository +import("../scripts/dawn_features.gni") +ozone_platform_x11 = dawn_use_x11
diff --git a/codereview.settings b/codereview.settings new file mode 100644 index 0000000..10cc2bf --- /dev/null +++ b/codereview.settings
@@ -0,0 +1,5 @@ +# This file is used by git cl to get repository specific information. +GERRIT_HOST: True +CODE_REVIEW_SERVER: https://dawn-review.googlesource.com +GERRIT_SQUASH_UPLOADS: False +TRYSERVER_GERRIT_URL: https://dawn-review.googlesource.com
diff --git a/dawn.json b/dawn.json new file mode 100644 index 0000000..3578858 --- /dev/null +++ b/dawn.json
@@ -0,0 +1,2848 @@ +{ + "_comment": [ + "Copyright 2017 The Dawn Authors", + "", + "Licensed under the Apache License, Version 2.0 (the \"License\");", + "you may not use this file except in compliance with the License.", + "You may obtain a copy of the License at", + "", + " http://www.apache.org/licenses/LICENSE-2.0", + "", + "Unless required by applicable law or agreed to in writing, software", + "distributed under the License is distributed on an \"AS IS\" BASIS,", + "WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.", + "See the License for the specific language governing permissions and", + "limitations under the License." + ], + + "_doc": "See docs/dawn/codegen.md", + + "_metadata": { + "api": "WebGPU", + "c_prefix": "WGPU", + "namespace": "wgpu", + "proc_table_prefix": "Dawn", + "native_namespace": "dawn native", + "copyright_year": "2019" + }, + + "create instance": { + "category": "function", + "returns": "instance", + "args": [ + {"name": "descriptor", "type": "instance descriptor", "annotation": "const*", "optional": true} + ] + }, + "proc": { + "category": "function pointer", + "returns": "void", + "args": [] + }, + "get proc address": { + "category": "function", + "returns": "proc", + "args": [ + {"name": "device", "type": "device"}, + {"name": "proc name", "type": "char", "annotation": "const*"} + ] + }, + + "request adapter options": { + "category": "structure", + "extensible": "in", + "members": [ + {"name": "compatible surface", "type": "surface", "optional": true}, + {"name": "power preference", "type": "power preference", "default": "undefined"}, + {"name": "force fallback adapter", "type": "bool", "default": "false"} + ] + }, + "request adapter status": { + "category": "enum", + "emscripten_no_enum_table": true, + "values": [ + {"value": 0, "name": "success"}, + {"value": 1, "name": "unavailable"}, + {"value": 2, "name": "error"}, + {"value": 3, "name": "unknown"} + ] + }, + "request adapter callback": { + "category": "function pointer", + "args": [ + {"name": "status", "type": "request adapter status"}, + {"name": "adapter", "type": "adapter"}, + {"name": "message", "type": "char", "annotation": "const*", "length": "strlen", "optional": true}, + {"name": "userdata", "type": "void", "annotation": "*"} + ] + }, + "adapter": { + "category": "object", + "methods": [ + { + "name": "get limits", + "returns": "bool", + "args": [ + {"name": "limits", "type": "supported limits", "annotation": "*"} + ] + }, + { + "name": "get properties", + "args": [ + {"name": "properties", "type": "adapter properties", "annotation": "*"} + ] + }, + { + "name": "has feature", + "returns": "bool", + "args": [ + {"name": "feature", "type": "feature name"} + ] + }, + { + "name": "enumerate features", + "returns": "size_t", + "args": [ + {"name": "features", "type": "feature name", "annotation": "*"} + ] + }, + { + "name": "request device", + "args": [ + {"name": "descriptor", "type": "device descriptor", "annotation": "const*"}, + {"name": "callback", "type": "request device callback"}, + {"name": "userdata", "type": "void", "annotation": "*"} + ] + }, + { + "name": "create device", + "tags": ["dawn"], + "returns": "device", + "args": [ + {"name": "descriptor", "type": "device descriptor", "annotation": "const*", "optional": "true"} + ] + } + ] + }, + "adapter properties": { + "category": "structure", + "extensible": "out", + "members": [ + {"name": "vendor ID", "type": "uint32_t"}, + {"name": "device ID", "type": "uint32_t"}, + {"name": "name", "type": "char", "annotation": "const*", "length": "strlen"}, + {"name": "driver description", "type": "char", "annotation": "const*", "length": "strlen"}, + {"name": "adapter type", "type": "adapter type"}, + {"name": "backend type", "type": "backend type"} + ] + }, + "adapter type": { + "category": "enum", + "emscripten_no_enum_table": true, + "values": [ + {"value": 0, "name": "discrete GPU"}, + {"value": 1, "name": "integrated GPU"}, + {"value": 2, "name": "CPU"}, + {"value": 3, "name": "unknown"} + ] + }, + "device descriptor": { + "category": "structure", + "extensible": "in", + "members": [ + {"name": "label", "type": "char", "annotation": "const*", "length": "strlen", "optional": true}, + {"name": "required features count", "type": "uint32_t", "default": 0}, + {"name": "required features", "type": "feature name", "annotation": "const*", "length": "required features count", "default": "nullptr"}, + {"name": "required limits", "type": "required limits", "annotation": "const*", "optional": true}, + {"name": "default queue", "type": "queue descriptor", "tags": ["upstream"]} + ] + }, + "dawn toggles device descriptor": { + "tags": ["dawn", "native"], + "category": "structure", + "chained": "in", + "members": [ + {"name": "force enabled toggles count", "type": "uint32_t", "default": 0}, + {"name": "force enabled toggles", "type": "char", "annotation": "const*const*", "length": "force enabled toggles count"}, + {"name": "force disabled toggles count", "type": "uint32_t", "default": 0}, + {"name": "force disabled toggles", "type": "char", "annotation": "const*const*", "length": "force disabled toggles count"} + ] + }, + "dawn cache device descriptor" : { + "tags": ["dawn", "native"], + "category": "structure", + "chained": "in", + "members": [ + {"name": "isolation key", "type": "char", "annotation": "const*", "length": "strlen", "default": "\"\""} + ] + }, + "address mode": { + "category": "enum", + "values": [ + {"value": 0, "name": "repeat"}, + {"value": 1, "name": "mirror repeat"}, + {"value": 2, "name": "clamp to edge"} + ] + }, + "backend type": { + "category": "enum", + "emscripten_no_enum_table": true, + "values": [ + {"value": 0, "name": "null"}, + {"value": 1, "name": "WebGPU"}, + {"value": 2, "name": "D3D11"}, + {"value": 3, "name": "D3D12"}, + {"value": 4, "name": "metal"}, + {"value": 5, "name": "vulkan"}, + {"value": 6, "name": "openGL"}, + {"value": 7, "name": "openGLES"} + ] + }, + "bind group": { + "category": "object", + "methods": [ + { + "name": "set label", + "returns": "void", + "tags": ["dawn"], + "_TODO": "needs an upstream equivalent", + "args": [ + {"name": "label", "type": "char", "annotation": "const*", "length": "strlen"} + ] + } + ] + }, + "bind group entry": { + "category": "structure", + "extensible": "in", + "members": [ + {"name": "binding", "type": "uint32_t"}, + {"name": "buffer", "type": "buffer", "optional": true}, + {"name": "offset", "type": "uint64_t", "default": "0"}, + {"name": "size", "type": "uint64_t"}, + {"name": "sampler", "type": "sampler", "optional": true}, + {"name": "texture view", "type": "texture view", "optional": true} + ] + }, + "bind group descriptor": { + "category": "structure", + "extensible": "in", + "members": [ + {"name": "label", "type": "char", "annotation": "const*", "length": "strlen", "optional": true}, + {"name": "layout", "type": "bind group layout"}, + {"name": "entry count", "type": "uint32_t"}, + {"name": "entries", "type": "bind group entry", "annotation": "const*", "length": "entry count"} + ] + }, + "bind group layout": { + "category": "object", + "methods": [ + { + "name": "set label", + "returns": "void", + "tags": ["dawn"], + "_TODO": "needs an upstream equivalent", + "args": [ + {"name": "label", "type": "char", "annotation": "const*", "length": "strlen"} + ] + } + ] + }, + + "buffer binding type": { + "category": "enum", + "values": [ + {"value": 0, "name": "undefined", "jsrepr": "undefined", "valid": false}, + {"value": 1, "name": "uniform"}, + {"value": 2, "name": "storage"}, + {"value": 3, "name": "read only storage"} + ] + }, + "buffer binding layout": { + "category": "structure", + "extensible": "in", + "members": [ + {"name": "type", "type": "buffer binding type", "default": "undefined"}, + {"name": "has dynamic offset", "type": "bool", "default": "false"}, + {"name": "min binding size", "type": "uint64_t", "default": "0"} + ] + }, + + "sampler binding type": { + "category": "enum", + "values": [ + {"value": 0, "name": "undefined", "jsrepr": "undefined", "valid": false}, + {"value": 1, "name": "filtering"}, + {"value": 2, "name": "non filtering"}, + {"value": 3, "name": "comparison"} + ] + }, + "sampler binding layout": { + "category": "structure", + "extensible": "in", + "members": [ + {"name": "type", "type": "sampler binding type", "default": "undefined"} + ] + }, + + "texture sample type": { + "category": "enum", + "values": [ + {"value": 0, "name": "undefined", "jsrepr": "undefined", "valid": false}, + {"value": 1, "name": "float"}, + {"value": 2, "name": "unfilterable float"}, + {"value": 3, "name": "depth"}, + {"value": 4, "name": "sint"}, + {"value": 5, "name": "uint"} + ] + }, + "texture binding layout": { + "category": "structure", + "extensible": "in", + "members": [ + {"name": "sample type", "type": "texture sample type", "default": "undefined"}, + {"name": "view dimension", "type": "texture view dimension", "default": "undefined"}, + {"name": "multisampled", "type": "bool", "default": "false"} + ] + }, + + "external texture binding entry": { + "category": "structure", + "chained": "in", + "tags": ["dawn"], + "members": [ + {"name": "external texture", "type": "external texture"} + ] + }, + + "external texture binding layout": { + "category": "structure", + "chained": "in", + "tags": ["dawn"], + "members": [] + }, + + "storage texture access": { + "category": "enum", + "values": [ + {"value": 0, "name": "undefined", "jsrepr": "undefined", "valid": false}, + {"value": 1, "name": "write only"} + ] + }, + "storage texture binding layout": { + "category": "structure", + "extensible": "in", + "members": [ + {"name": "access", "type": "storage texture access", "default": "undefined"}, + {"name": "format", "type": "texture format", "default": "undefined"}, + {"name": "view dimension", "type": "texture view dimension", "default": "undefined"} + ] + }, + + "bind group layout entry": { + "category": "structure", + "extensible": "in", + "members": [ + {"name": "binding", "type": "uint32_t"}, + {"name": "visibility", "type": "shader stage"}, + {"name": "buffer", "type": "buffer binding layout"}, + {"name": "sampler", "type": "sampler binding layout"}, + {"name": "texture", "type": "texture binding layout"}, + {"name": "storage texture", "type": "storage texture binding layout"} + ] + }, + "bind group layout descriptor": { + "category": "structure", + "extensible": "in", + "members": [ + {"name": "label", "type": "char", "annotation": "const*", "length": "strlen", "optional": true}, + {"name": "entry count", "type": "uint32_t"}, + {"name": "entries", "type": "bind group layout entry", "annotation": "const*", "length": "entry count"} + ] + }, + "blend component": { + "category": "structure", + "extensible": false, + "members": [ + {"name": "operation", "type": "blend operation", "default": "add"}, + {"name": "src factor", "type": "blend factor", "default": "one"}, + {"name": "dst factor", "type": "blend factor", "default": "zero"} + ] + }, + "blend factor": { + "category": "enum", + "values": [ + {"value": 0, "name": "zero"}, + {"value": 1, "name": "one"}, + {"value": 2, "name": "src"}, + {"value": 3, "name": "one minus src"}, + {"value": 4, "name": "src alpha"}, + {"value": 5, "name": "one minus src alpha"}, + {"value": 6, "name": "dst"}, + {"value": 7, "name": "one minus dst"}, + {"value": 8, "name": "dst alpha"}, + {"value": 9, "name": "one minus dst alpha"}, + {"value": 10, "name": "src alpha saturated"}, + {"value": 11, "name": "constant"}, + {"value": 12, "name": "one minus constant"} + ] + }, + "blend operation": { + "category": "enum", + "values": [ + {"value": 0, "name": "add"}, + {"value": 1, "name": "subtract"}, + {"value": 2, "name": "reverse subtract"}, + {"value": 3, "name": "min"}, + {"value": 4, "name": "max"} + ] + }, + "bool": { + "category": "native" + }, + "buffer": { + "category": "object", + "methods": [ + { + "name": "map async", + "args": [ + {"name": "mode", "type": "map mode"}, + {"name": "offset", "type": "size_t"}, + {"name": "size", "type": "size_t"}, + {"name": "callback", "type": "buffer map callback"}, + {"name": "userdata", "type": "void", "annotation": "*"} + ] + }, + { + "name": "get mapped range", + "returns": "void *", + "args": [ + {"name": "offset", "type": "size_t", "default": 0}, + {"name": "size", "type": "size_t", "default": 0} + ] + }, + { + "name": "get const mapped range", + "returns": "void const *", + "args": [ + {"name": "offset", "type": "size_t", "default": 0}, + {"name": "size", "type": "size_t", "default": 0} + ] + }, + { + "name": "set label", + "returns": "void", + "tags": ["dawn"], + "_TODO": "needs an upstream equivalent", + "args": [ + {"name": "label", "type": "char", "annotation": "const*", "length": "strlen"} + ] + }, + { + "name": "unmap" + }, + { + "name": "destroy" + } + ] + }, + "buffer descriptor": { + "category": "structure", + "extensible": "in", + "members": [ + {"name": "label", "type": "char", "annotation": "const*", "length": "strlen", "optional": true}, + {"name": "usage", "type": "buffer usage"}, + {"name": "size", "type": "uint64_t"}, + {"name": "mapped at creation", "type": "bool", "default": "false"} + ] + }, + "buffer map callback": { + "category": "function pointer", + "args": [ + {"name": "status", "type": "buffer map async status"}, + {"name": "userdata", "type": "void", "annotation": "*"} + ] + }, + "buffer map async status": { + "category": "enum", + "emscripten_no_enum_table": true, + "values": [ + {"value": 0, "name": "success"}, + {"value": 1, "name": "error"}, + {"value": 2, "name": "unknown"}, + {"value": 3, "name": "device lost"}, + {"value": 4, "name": "destroyed before callback"}, + {"value": 5, "name": "unmapped before callback"} + ] + }, + "buffer usage": { + "category": "bitmask", + "values": [ + {"value": 0, "name": "none"}, + {"value": 1, "name": "map read"}, + {"value": 2, "name": "map write"}, + {"value": 4, "name": "copy src"}, + {"value": 8, "name": "copy dst"}, + {"value": 16, "name": "index"}, + {"value": 32, "name": "vertex"}, + {"value": 64, "name": "uniform"}, + {"value": 128, "name": "storage"}, + {"value": 256, "name": "indirect"}, + {"value": 512, "name": "query resolve"} + ] + }, + "char": { + "category": "native" + }, + "color": { + "category": "structure", + "members": [ + {"name": "r", "type": "double"}, + {"name": "g", "type": "double"}, + {"name": "b", "type": "double"}, + {"name": "a", "type": "double"} + ] + }, + "color write mask": { + "category": "bitmask", + "values": [ + {"value": 0, "name": "none"}, + {"value": 1, "name": "red"}, + {"value": 2, "name": "green"}, + {"value": 4, "name": "blue"}, + {"value": 8, "name": "alpha"}, + {"value": 15, "name": "all"} + ] + }, + "constant entry": { + "category": "structure", + "extensible": "in", + "members": [ + {"name": "key", "type": "char", "annotation": "const*", "length": "strlen"}, + {"name": "value", "type": "double"} + ] + }, + "command buffer": { + "category": "object", + "methods": [ + { + "name": "set label", + "returns": "void", + "tags": ["dawn"], + "_TODO": "needs an upstream equivalent", + "args": [ + {"name": "label", "type": "char", "annotation": "const*", "length": "strlen"} + ] + } + ] + }, + "command buffer descriptor": { + "category": "structure", + "extensible": "in", + "members": [ + {"name": "label", "type": "char", "annotation": "const*", "length": "strlen", "optional": true} + ] + }, + "command encoder": { + "category": "object", + "methods": [ + { + "name": "finish", + "returns": "command buffer", + "args": [ + {"name": "descriptor", "type": "command buffer descriptor", "annotation": "const*", "optional": true} + ] + }, + { + "name": "begin compute pass", + "returns": "compute pass encoder", + "args": [ + {"name": "descriptor", "type": "compute pass descriptor", "annotation": "const*", "optional": true} + ] + }, + { + "name": "begin render pass", + "returns": "render pass encoder", + "args": [ + {"name": "descriptor", "type": "render pass descriptor", "annotation": "const*"} + ] + }, + { + "name": "copy buffer to buffer", + "args": [ + {"name": "source", "type": "buffer"}, + {"name": "source offset", "type": "uint64_t"}, + {"name": "destination", "type": "buffer"}, + {"name": "destination offset", "type": "uint64_t"}, + {"name": "size", "type": "uint64_t"} + ] + }, + { + "name": "copy buffer to texture", + "args": [ + {"name": "source", "type": "image copy buffer", "annotation": "const*"}, + {"name": "destination", "type": "image copy texture", "annotation": "const*"}, + {"name": "copy size", "type": "extent 3D", "annotation": "const*"} + ] + }, + { + "name": "copy texture to buffer", + "args": [ + {"name": "source", "type": "image copy texture", "annotation": "const*"}, + {"name": "destination", "type": "image copy buffer", "annotation": "const*"}, + {"name": "copy size", "type": "extent 3D", "annotation": "const*"} + ] + }, + { + "name": "copy texture to texture", + "args": [ + {"name": "source", "type": "image copy texture", "annotation": "const*"}, + {"name": "destination", "type": "image copy texture", "annotation": "const*"}, + {"name": "copy size", "type": "extent 3D", "annotation": "const*"} + ] + }, + { + "name": "copy texture to texture internal", + "tags": ["dawn"], + "args": [ + {"name": "source", "type": "image copy texture", "annotation": "const*"}, + {"name": "destination", "type": "image copy texture", "annotation": "const*"}, + {"name": "copy size", "type": "extent 3D", "annotation": "const*"} + ] + }, + { + "name": "clear buffer", + "args": [ + {"name": "buffer", "type": "buffer"}, + {"name": "offset", "type": "uint64_t", "default": 0}, + {"name": "size", "type": "uint64_t", "default": "WGPU_WHOLE_SIZE"} + ] + }, + { + "name": "inject validation error", + "tags": ["dawn"], + "args": [ + {"name": "message", "type": "char", "annotation": "const*", "length": "strlen"} + ] + }, + { + "name": "insert debug marker", + "args": [ + {"name": "marker label", "type": "char", "annotation": "const*", "length": "strlen"} + ] + }, + { + "name": "pop debug group", + "args": [] + }, + { + "name": "push debug group", + "args": [ + {"name": "group label", "type": "char", "annotation": "const*", "length": "strlen"} + ] + }, + { + "name": "resolve query set", + "args": [ + {"name": "query set", "type": "query set"}, + {"name": "first query", "type": "uint32_t"}, + {"name": "query count", "type": "uint32_t"}, + {"name": "destination", "type": "buffer"}, + {"name": "destination offset", "type": "uint64_t"} + ] + }, + { + "name": "write buffer", + "tags": ["dawn"], + "args": [ + {"name": "buffer", "type": "buffer"}, + {"name": "buffer offset", "type": "uint64_t"}, + {"name": "data", "type": "uint8_t", "annotation": "const*", "length": "size"}, + {"name": "size", "type": "uint64_t"} + ] + }, + { + "name": "write timestamp", + "args": [ + {"name": "query set", "type": "query set"}, + {"name": "query index", "type": "uint32_t"} + ] + }, + { + "name": "set label", + "returns": "void", + "tags": ["dawn"], + "_TODO": "needs an upstream equivalent", + "args": [ + {"name": "label", "type": "char", "annotation": "const*", "length": "strlen"} + ] + } + ] + }, + "command encoder descriptor": { + "category": "structure", + "extensible": "in", + "members": [ + {"name": "label", "type": "char", "annotation": "const*", "length": "strlen", "optional": true} + ] + }, + "compare function": { + "category": "enum", + "values": [ + {"value": 0, "name": "undefined", "jsrepr": "undefined", "valid": false}, + {"value": 1, "name": "never"}, + {"value": 2, "name": "less"}, + {"value": 3, "name": "less equal"}, + {"value": 4, "name": "greater"}, + {"value": 5, "name": "greater equal"}, + {"value": 6, "name": "equal"}, + {"value": 7, "name": "not equal"}, + {"value": 8, "name": "always"} + ] + }, + "compilation info": { + "category": "structure", + "extensible": "in", + "members": [ + {"name": "message count", "type": "uint32_t"}, + {"name": "messages", "type": "compilation message", "annotation": "const*", "length": "message count"} + ] + }, + "compilation info callback": { + "category": "function pointer", + "args": [ + {"name": "status", "type": "compilation info request status"}, + {"name": "compilation info", "type": "compilation info", "annotation": "const*"}, + {"name": "userdata", "type": "void", "annotation": "*"} + ] + }, + "compilation info request status": { + "category": "enum", + "values": [ + {"value": 0, "name": "success"}, + {"value": 1, "name": "error"}, + {"value": 2, "name": "device lost"}, + {"value": 3, "name": "unknown"} + ] + }, + "compilation message": { + "category": "structure", + "extensible": "in", + "members": [ + {"name": "message", "type": "char", "annotation": "const*", "length": "strlen", "optional": true}, + {"name": "type", "type": "compilation message type"}, + {"name": "line num", "type": "uint64_t"}, + {"name": "line pos", "type": "uint64_t"}, + {"name": "offset", "type": "uint64_t"}, + {"name": "length", "type": "uint64_t"} + ] + }, + "compilation message type": { + "category": "enum", + "emscripten_no_enum_table": true, + "values": [ + {"value": 0, "name": "error"}, + {"value": 1, "name": "warning"}, + {"value": 2, "name": "info"} + ] + }, + "compute pass descriptor": { + "category": "structure", + "extensible": "in", + "members": [ + {"name": "label", "type": "char", "annotation": "const*", "length": "strlen", "optional": true}, + {"name": "timestamp write count", "type": "uint32_t", "default": 0}, + {"name": "timestamp writes", "type": "compute pass timestamp write", "annotation": "const*", "length": "timestamp write count"} + ] + }, + "compute pass encoder": { + "category": "object", + "methods": [ + { + "name": "insert debug marker", + "args": [ + {"name": "marker label", "type": "char", "annotation": "const*", "length": "strlen"} + ] + }, + { + "name": "pop debug group", + "args": [] + }, + { + "name": "push debug group", + "args": [ + {"name": "group label", "type": "char", "annotation": "const*", "length": "strlen"} + ] + }, + { + "name": "set pipeline", + "args": [ + {"name": "pipeline", "type": "compute pipeline"} + ] + }, + { + "name": "set bind group", + "args": [ + {"name": "group index", "type": "uint32_t"}, + {"name": "group", "type": "bind group"}, + {"name": "dynamic offset count", "type": "uint32_t", "default": "0"}, + {"name": "dynamic offsets", "type": "uint32_t", "annotation": "const*", "length": "dynamic offset count", "default": "nullptr"} + ] + }, + { + "name": "write timestamp", + "tags": ["emscripten", "dawn"], + "args": [ + {"name": "query set", "type": "query set"}, + {"name": "query index", "type": "uint32_t"} + ] + }, + { + "name": "begin pipeline statistics query", + "tags": ["upstream", "emscripten"], + "args": [ + {"name": "query set", "type": "query set"}, + {"name": "query index", "type": "uint32_t"} + ] + }, + { + "name": "dispatch", + "args": [ + {"name": "workgroupCountX", "type": "uint32_t"}, + {"name": "workgroupCountY", "type": "uint32_t", "default": "1"}, + {"name": "workgroupCountZ", "type": "uint32_t", "default": "1"} + ] + }, + { + "name": "dispatch indirect", + "args": [ + {"name": "indirect buffer", "type": "buffer"}, + {"name": "indirect offset", "type": "uint64_t"} + ] + }, + { + "name": "end" + }, + { + "name": "end pass", + "tags": ["deprecated"] + }, + { + "name": "end pipeline statistics query", + "tags": ["upstream", "emscripten"] + }, + { + "name": "set label", + "returns": "void", + "tags": ["dawn"], + "_TODO": "needs an upstream equivalent", + "args": [ + {"name": "label", "type": "char", "annotation": "const*", "length": "strlen"} + ] + } + ] + }, + "compute pass timestamp location": { + "category": "enum", + "values": [ + {"value": 0, "name": "beginning"}, + {"value": 1, "name": "end"} + ] + }, + "compute pass timestamp write": { + "category": "structure", + "members": [ + {"name": "query set", "type": "query set"}, + {"name": "query index", "type": "uint32_t"}, + {"name": "location", "type": "compute pass timestamp location"} + ] + }, + "compute pipeline": { + "category": "object", + "methods": [ + { + "name": "get bind group layout", + "returns": "bind group layout", + "args": [ + {"name": "group index", "type": "uint32_t"} + ] + }, + { + "name": "set label", + "returns": "void", + "args": [ + {"name": "label", "type": "char", "annotation": "const*", "length": "strlen"} + ] + } + ] + }, + "compute pipeline descriptor": { + "category": "structure", + "extensible": "in", + "members": [ + {"name": "label", "type": "char", "annotation": "const*", "length": "strlen", "optional": true}, + {"name": "layout", "type": "pipeline layout", "optional": true}, + {"name": "compute", "type": "programmable stage descriptor"} + ] + }, + "alpha mode": { + "category": "enum", + "tags": ["dawn"], + "values": [ + {"value": 0, "name": "premultiplied"}, + {"value": 1, "name": "unpremultiplied"} + ] + }, + "copy texture for browser options": { + "category": "structure", + "extensible": "in", + "tags": ["dawn"], + "_TODO": "support number as length input", + "members": [ + {"name": "flip y", "type": "bool", "default": "false"}, + {"name": "needs color space conversion", "type": "bool", "default": "false"}, + {"name": "src alpha mode", "type": "alpha mode", "default": "unpremultiplied"}, + {"name": "src transfer function parameters", "type": "float", "annotation": "const*", + "length": 7, "optional": true}, + {"name": "conversion matrix", "type": "float", "annotation": "const*", + "length": 9, "optional": true}, + {"name": "dst transfer function parameters", "type": "float", "annotation": "const*", + "length": 7, "optional": true}, + {"name": "dst alpha mode", "type": "alpha mode", "default": "unpremultiplied"} + ] + }, + "create compute pipeline async callback": { + "category": "function pointer", + "args": [ + {"name": "status", "type": "create pipeline async status"}, + {"name": "pipeline", "type": "compute pipeline"}, + {"name": "message", "type": "char", "annotation": "const*", "length": "strlen"}, + {"name": "userdata", "type": "void", "annotation": "*"} + ] + }, + "create pipeline async status": { + "category": "enum", + "emscripten_no_enum_table": true, + "values": [ + {"value": 0, "name": "success"}, + {"value": 1, "name": "error"}, + {"value": 2, "name": "device lost"}, + {"value": 3, "name": "device destroyed"}, + {"value": 4, "name": "unknown"} + ] + }, + "create render pipeline async callback": { + "category": "function pointer", + "args": [ + {"name": "status", "type": "create pipeline async status"}, + {"name": "pipeline", "type": "render pipeline"}, + {"name": "message", "type": "char", "annotation": "const*", "length": "strlen"}, + {"name": "userdata", "type": "void", "annotation": "*"} + ] + }, + "cull mode": { + "category": "enum", + "values": [ + {"value": 0, "name": "none"}, + {"value": 1, "name": "front"}, + {"value": 2, "name": "back"} + ] + }, + "device": { + "category": "object", + "methods": [ + { + "name": "create bind group", + "returns": "bind group", + "args": [ + {"name": "descriptor", "type": "bind group descriptor", "annotation": "const*"} + ] + }, + { + "name": "create bind group layout", + "returns": "bind group layout", + "args": [ + {"name": "descriptor", "type": "bind group layout descriptor", "annotation": "const*"} + ] + }, + { + "name": "create buffer", + "returns": "buffer", + "args": [ + {"name": "descriptor", "type": "buffer descriptor", "annotation": "const*"} + ] + }, + { + "name": "create error buffer", + "returns": "buffer", + "tags": ["dawn"] + }, + { + "name": "create command encoder", + "returns": "command encoder", + "args": [ + {"name": "descriptor", "type": "command encoder descriptor", "annotation": "const*", "optional": true} + ] + }, + { + "name": "create compute pipeline", + "returns": "compute pipeline", + "args": [ + {"name": "descriptor", "type": "compute pipeline descriptor", "annotation": "const*"} + ] + }, + { + "name": "create compute pipeline async", + "returns": "void", + "args": [ + {"name": "descriptor", "type": "compute pipeline descriptor", "annotation": "const*"}, + {"name": "callback", "type": "create compute pipeline async callback"}, + {"name": "userdata", "type": "void", "annotation": "*"} + ] + }, + { + "name": "create external texture", + "returns": "external texture", + "tags": ["dawn"], + "args": [ + {"name": "external texture descriptor", "type": "external texture descriptor", "annotation": "const*"} + ] + }, + { + "name": "create pipeline layout", + "returns": "pipeline layout", + "args": [ + {"name": "descriptor", "type": "pipeline layout descriptor", "annotation": "const*"} + ] + }, + { + "name": "create query set", + "returns": "query set", + "args": [ + {"name": "descriptor", "type": "query set descriptor", "annotation": "const*"} + ] + }, + { + "name": "create render pipeline async", + "returns": "void", + "args": [ + {"name": "descriptor", "type": "render pipeline descriptor", "annotation": "const*"}, + {"name": "callback", "type": "create render pipeline async callback"}, + {"name": "userdata", "type": "void", "annotation": "*"} + ] + }, + { + "name": "create render bundle encoder", + "returns": "render bundle encoder", + "args": [ + {"name": "descriptor", "type": "render bundle encoder descriptor", "annotation": "const*"} + ] + }, + { + "name": "create render pipeline", + "returns": "render pipeline", + "args": [ + {"name": "descriptor", "type": "render pipeline descriptor", "annotation": "const*"} + ] + }, + { + "name": "create sampler", + "returns": "sampler", + "args": [ + {"name": "descriptor", "type": "sampler descriptor", "annotation": "const*", "optional": true} + ] + }, + { + "name": "create shader module", + "returns": "shader module", + "args": [ + {"name": "descriptor", "type": "shader module descriptor", "annotation": "const*"} + ] + }, + { + "name": "create swap chain", + "returns": "swap chain", + "args": [ + {"name": "surface", "type": "surface", "optional": true}, + {"name": "descriptor", "type": "swap chain descriptor", "annotation": "const*"} + ] + }, + { + "name": "create texture", + "returns": "texture", + "args": [ + {"name": "descriptor", "type": "texture descriptor", "annotation": "const*"} + ] + }, + { + "name": "destroy" + }, + { + "name": "get limits", + "returns": "bool", + "args": [ + {"name": "limits", "type": "supported limits", "annotation": "*"} + ] + }, + { + "name": "has feature", + "returns": "bool", + "args": [ + {"name": "feature", "type": "feature name"} + ] + }, + { + "name": "enumerate features", + "returns": "size_t", + "args": [ + {"name": "features", "type": "feature name", "annotation": "*"} + ] + }, + { + "name": "get queue", + "returns": "queue" + }, + { + "name": "inject error", + "args": [ + {"name": "type", "type": "error type"}, + {"name": "message", "type": "char", "annotation": "const*", "length": "strlen"} + ], + "tags": ["dawn"] + }, + { + "name": "lose for testing", + "tags": ["dawn"] + }, + { + "name": "tick", + "tags": ["dawn"] + }, + { + "name": "set uncaptured error callback", + "args": [ + {"name": "callback", "type": "error callback"}, + {"name": "userdata", "type": "void", "annotation": "*"} + ] + }, + { + "name": "set logging callback", + "tags": ["dawn"], + "args": [ + {"name": "callback", "type": "logging callback"}, + {"name": "userdata", "type": "void", "annotation": "*"} + ] + }, + { + "name": "set device lost callback", + "args": [ + {"name": "callback", "type": "device lost callback"}, + {"name": "userdata", "type": "void", "annotation": "*"} + ] + }, + { + "name": "push error scope", + "args": [ + {"name": "filter", "type": "error filter"} + ] + }, + { + "name": "pop error scope", + "returns": "bool", + "args": [ + {"name": "callback", "type": "error callback"}, + {"name": "userdata", "type": "void", "annotation": "*"} + ] + } + ] + }, + "device lost callback": { + "category": "function pointer", + "args": [ + {"name": "reason", "type": "device lost reason"}, + {"name": "message", "type": "char", "annotation": "const*"}, + {"name": "userdata", "type": "void", "annotation": "*"} + ] + }, + "device lost reason": { + "category": "enum", + "emscripten_no_enum_table": true, + "values": [ + {"value": 0, "name": "undefined", "jsrepr": "undefined"}, + {"value": 1, "name": "destroyed"} + ] + }, + "device properties": { + "category": "structure", + "extensible": false, + "tags": ["dawn"], + "members": [ + {"name": "device ID", "type": "uint32_t"}, + {"name": "vendor ID", "type": "uint32_t"}, + {"name": "adapter type", "type": "adapter type"}, + {"name": "texture compression BC", "type": "bool", "default": "false"}, + {"name": "texture compression ETC2", "type": "bool", "default": "false"}, + {"name": "texture compression ASTC", "type": "bool", "default": "false"}, + {"name": "shader float16", "type": "bool", "default": "false"}, + {"name": "pipeline statistics query", "type": "bool", "default": "false"}, + {"name": "timestamp query", "type": "bool", "default": "false"}, + {"name": "multi planar formats", "type": "bool", "default": "false"}, + {"name": "depth clamping", "type": "bool", "default": "false"}, + {"name": "depth24 unorm stencil8", "type": "bool", "default": "false"}, + {"name": "depth32 float stencil8", "type": "bool", "default": "false"}, + {"name": "invalid feature", "type": "bool", "default": "false"}, + {"name": "dawn internal usages", "type": "bool", "default": "false"}, + {"name": "dawn native", "type": "bool", "default": "false"}, + {"name": "limits", "type": "supported limits"} + ] + }, + "double": { + "category": "native" + }, + "error callback": { + "category": "function pointer", + "args": [ + {"name": "type", "type": "error type"}, + {"name": "message", "type": "char", "annotation": "const*"}, + {"name": "userdata", "type": "void", "annotation": "*"} + ] + }, + "limits": { + "category": "structure", + "members": [ + {"name": "max texture dimension 1D", "type": "uint32_t", "default": "WGPU_LIMIT_U32_UNDEFINED"}, + {"name": "max texture dimension 2D", "type": "uint32_t", "default": "WGPU_LIMIT_U32_UNDEFINED"}, + {"name": "max texture dimension 3D", "type": "uint32_t", "default": "WGPU_LIMIT_U32_UNDEFINED"}, + {"name": "max texture array layers", "type": "uint32_t", "default": "WGPU_LIMIT_U32_UNDEFINED"}, + {"name": "max bind groups", "type": "uint32_t", "default": "WGPU_LIMIT_U32_UNDEFINED"}, + {"name": "max dynamic uniform buffers per pipeline layout", "type": "uint32_t", "default": "WGPU_LIMIT_U32_UNDEFINED"}, + {"name": "max dynamic storage buffers per pipeline layout", "type": "uint32_t", "default": "WGPU_LIMIT_U32_UNDEFINED"}, + {"name": "max sampled textures per shader stage", "type": "uint32_t", "default": "WGPU_LIMIT_U32_UNDEFINED"}, + {"name": "max samplers per shader stage", "type": "uint32_t", "default": "WGPU_LIMIT_U32_UNDEFINED"}, + {"name": "max storage buffers per shader stage", "type": "uint32_t", "default": "WGPU_LIMIT_U32_UNDEFINED"}, + {"name": "max storage textures per shader stage", "type": "uint32_t", "default": "WGPU_LIMIT_U32_UNDEFINED"}, + {"name": "max uniform buffers per shader stage", "type": "uint32_t", "default": "WGPU_LIMIT_U32_UNDEFINED"}, + {"name": "max uniform buffer binding size", "type": "uint64_t", "default": "WGPU_LIMIT_U64_UNDEFINED"}, + {"name": "max storage buffer binding size", "type": "uint64_t", "default": "WGPU_LIMIT_U64_UNDEFINED"}, + {"name": "min uniform buffer offset alignment", "type": "uint32_t", "default": "WGPU_LIMIT_U32_UNDEFINED"}, + {"name": "min storage buffer offset alignment", "type": "uint32_t", "default": "WGPU_LIMIT_U32_UNDEFINED"}, + {"name": "max vertex buffers", "type": "uint32_t", "default": "WGPU_LIMIT_U32_UNDEFINED"}, + {"name": "max vertex attributes", "type": "uint32_t", "default": "WGPU_LIMIT_U32_UNDEFINED"}, + {"name": "max vertex buffer array stride", "type": "uint32_t", "default": "WGPU_LIMIT_U32_UNDEFINED"}, + {"name": "max inter stage shader components", "type": "uint32_t", "default": "WGPU_LIMIT_U32_UNDEFINED"}, + {"name": "max compute workgroup storage size", "type": "uint32_t", "default": "WGPU_LIMIT_U32_UNDEFINED"}, + {"name": "max compute invocations per workgroup", "type": "uint32_t", "default": "WGPU_LIMIT_U32_UNDEFINED"}, + {"name": "max compute workgroup size x", "type": "uint32_t", "default": "WGPU_LIMIT_U32_UNDEFINED"}, + {"name": "max compute workgroup size y", "type": "uint32_t", "default": "WGPU_LIMIT_U32_UNDEFINED"}, + {"name": "max compute workgroup size z", "type": "uint32_t", "default": "WGPU_LIMIT_U32_UNDEFINED"}, + {"name": "max compute workgroups per dimension", "type": "uint32_t", "default": "WGPU_LIMIT_U32_UNDEFINED"} + ] + }, + "required limits": { + "category": "structure", + "extensible": "in", + "members": [ + {"name": "limits", "type": "limits"} + ] + }, + "supported limits": { + "category": "structure", + "extensible": "out", + "members": [ + {"name": "limits", "type": "limits"} + ] + }, + "logging callback": { + "category": "function pointer", + "tags": ["dawn"], + "args": [ + {"name": "type", "type": "logging type"}, + {"name": "message", "type": "char", "annotation": "const*"}, + {"name": "userdata", "type": "void", "annotation": "*"} + ] + }, + "error filter": { + "category": "enum", + "values": [ + {"value": 0, "name": "validation"}, + {"value": 1, "name": "out of memory"} + ] + }, + "error type": { + "category": "enum", + "emscripten_no_enum_table": true, + "values": [ + {"value": 0, "name": "no error"}, + {"value": 1, "name": "validation"}, + {"value": 2, "name": "out of memory"}, + {"value": 3, "name": "unknown"}, + {"value": 4, "name": "device lost"} + ] + }, + "logging type": { + "category": "enum", + "tags": ["dawn"], + "values": [ + {"value": 0, "name": "verbose"}, + {"value": 1, "name": "info"}, + {"value": 2, "name": "warning"}, + {"value": 3, "name": "error"} + ] + }, + "extent 3D": { + "category": "structure", + "members": [ + {"name": "width", "type": "uint32_t"}, + {"name": "height", "type": "uint32_t", "default": 1}, + {"name": "depth or array layers", "type": "uint32_t", "default": 1} + ] + }, + "external texture": { + "category": "object", + "tags": ["dawn"], + "methods": [ + { + "name": "set label", + "returns": "void", + "tags": ["dawn"], + "_TODO": "needs an upstream equivalent", + "args": [ + {"name": "label", "type": "char", "annotation": "const*", "length": "strlen"} + ] + }, + { + "name": "destroy", + "returns": "void" + } + ] + }, + "external texture descriptor": { + "category": "structure", + "extensible": "in", + "tags": ["dawn"], + "members": [ + {"name": "label", "type": "char", "annotation": "const*", "length": "strlen", "optional": true}, + {"name": "plane 0", "type": "texture view"}, + {"name": "plane 1", "type": "texture view", "optional": true}, + {"name": "color space", "type": "predefined color space", "default": "srgb"} + ] + }, + "feature name": { + "category": "enum", + "values": [ + {"value": 0, "name": "undefined", "jsrepr": "undefined"}, + {"value": 1, "name": "depth clip control", "tags": ["upstream", "emscripten"]}, + {"value": 2, "name": "depth24 unorm stencil8"}, + {"value": 3, "name": "depth32 float stencil8"}, + {"value": 4, "name": "timestamp query"}, + {"value": 5, "name": "pipeline statistics query"}, + {"value": 6, "name": "texture compression BC"}, + {"value": 7, "name": "texture compression ETC2"}, + {"value": 8, "name": "texture compression ASTC"}, + {"value": 9, "name": "indirect first instance"}, + {"value": 1000, "name": "depth clamping", "tags": ["emscripten", "dawn"]}, + {"value": 1001, "name": "dawn shader float 16", "tags": ["dawn"]}, + {"value": 1002, "name": "dawn internal usages", "tags": ["dawn"]}, + {"value": 1003, "name": "dawn multi planar formats", "tags": ["dawn"]}, + {"value": 1004, "name": "dawn native", "tags": ["dawn", "native"]} + ] + }, + "filter mode": { + "category": "enum", + "values": [ + {"value": 0, "name": "nearest"}, + {"value": 1, "name": "linear"} + ] + }, + "float": { + "category": "native" + }, + "front face": { + "category": "enum", + "values": [ + {"value": 0, "name": "CCW"}, + {"value": 1, "name": "CW"} + ] + }, + "image copy buffer": { + "category": "structure", + "extensible": "in", + "members": [ + {"name": "layout", "type": "texture data layout"}, + {"name": "buffer", "type": "buffer"} + ] + }, + "image copy texture": { + "category": "structure", + "extensible": "in", + "members": [ + {"name": "texture", "type": "texture"}, + {"name": "mip level", "type": "uint32_t", "default": "0"}, + {"name": "origin", "type": "origin 3D"}, + {"name": "aspect", "type": "texture aspect", "default": "all"} + ] + }, + "index format": { + "category": "enum", + "values": [ + {"value": 0, "name": "undefined", "jsrepr": "undefined"}, + {"value": 1, "name": "uint16"}, + {"value": 2, "name": "uint32"} + ] + }, + "instance": { + "category": "object", + "methods": [ + { + "name": "create surface", + "returns": "surface", + "args": [ + {"name": "descriptor", "type": "surface descriptor", "annotation": "const*"} + ] + }, + { + "name": "process events", + "tags": ["upstream", "emscripten"] + }, + { + "name": "request adapter", + "args": [ + {"name": "options", "type": "request adapter options", "annotation": "const*"}, + {"name": "callback", "type": "request adapter callback"}, + {"name": "userdata", "type": "void", "annotation": "*"} + ] + } + ] + }, + "instance descriptor": { + "category": "structure", + "extensible": "in", + "members": [] + }, + "dawn instance descriptor": { + "tags": ["dawn", "native"], + "category": "structure", + "chained": "in", + "members": [ + {"name": "additional runtime search paths count", "type": "uint32_t", "default": 0}, + {"name": "additional runtime search paths", "type": "char", "annotation": "const*const*", "length": "additional runtime search paths count"} + ] + }, + "vertex attribute": { + "category": "structure", + "extensible": false, + "members": [ + {"name": "format", "type": "vertex format"}, + {"name": "offset", "type": "uint64_t"}, + {"name": "shader location", "type": "uint32_t"} + ] + }, + "vertex buffer layout": { + "category": "structure", + "extensible": false, + "members": [ + {"name": "array stride", "type": "uint64_t"}, + {"name": "step mode", "type": "vertex step mode", "default": "vertex"}, + {"name": "attribute count", "type": "uint32_t"}, + {"name": "attributes", "type": "vertex attribute", "annotation": "const*", "length": "attribute count"} + ] + }, + "vertex step mode": { + "category": "enum", + "values": [ + {"value": 0, "name": "vertex"}, + {"value": 1, "name": "instance"} + ] + }, + "load op": { + "category": "enum", + "emscripten_no_enum_table": true, + "values": [ + {"value": 0, "name": "undefined", "jsrepr": "undefined"}, + {"value": 1, "name": "clear"}, + {"value": 2, "name": "load"} + ] + }, + "map mode": { + "category": "bitmask", + "values": [ + {"value": 0, "name": "none"}, + {"value": 1, "name": "read"}, + {"value": 2, "name": "write"} + ] + }, + "mipmap filter mode": { + "category": "enum", + "tags": ["upstream"], + "values": [ + {"value": 0, "name": "nearest"}, + {"value": 1, "name": "linear"} + ] + }, + "store op": { + "category": "enum", + "values": [ + {"value": 0, "name": "undefined", "jsrepr": "undefined"}, + {"value": 1, "name": "store"}, + {"value": 2, "name": "discard"} + ] + }, + "origin 3D": { + "category": "structure", + "members": [ + {"name": "x", "type": "uint32_t", "default": "0"}, + {"name": "y", "type": "uint32_t", "default": "0"}, + {"name": "z", "type": "uint32_t", "default": "0"} + ] + }, + "pipeline layout": { + "category": "object", + "methods": [ + { + "name": "set label", + "returns": "void", + "tags": ["dawn"], + "_TODO": "needs an upstream equivalent", + "args": [ + {"name": "label", "type": "char", "annotation": "const*", "length": "strlen"} + ] + } + ] + }, + "pipeline layout descriptor": { + "category": "structure", + "extensible": "in", + "members": [ + {"name": "label", "type": "char", "annotation": "const*", "length": "strlen", "optional": true}, + {"name": "bind group layout count", "type": "uint32_t"}, + {"name": "bind group layouts", "type": "bind group layout", "annotation": "const*", "length": "bind group layout count"} + ] + }, + "pipeline statistic name": { + "category": "enum", + "values": [ + {"value": 0, "name": "vertex shader invocations"}, + {"value": 1, "name": "clipper invocations"}, + {"value": 2, "name": "clipper primitives out"}, + {"value": 3, "name": "fragment shader invocations"}, + {"value": 4, "name": "compute shader invocations"} + ] + }, + "power preference": { + "category": "enum", + "values": [ + {"value": 0, "name": "undefined", "jsrepr": "undefined"}, + {"value": 1, "name": "low power"}, + {"value": 2, "name": "high performance"} + ] + }, + "predefined color space": { + "category": "enum", + "values": [ + {"value": 0, "name": "undefined", "jsrepr": "undefined"}, + {"value": 1, "name": "srgb"} + ] + }, + "present mode": { + "category": "enum", + "emscripten_no_enum_table": true, + "values": [ + {"value": 0, "name": "immediate"}, + {"value": 1, "name": "mailbox"}, + {"value": 2, "name": "fifo"} + ] + }, + "programmable stage descriptor": { + "category": "structure", + "extensible": "in", + "members": [ + {"name": "module", "type": "shader module"}, + {"name": "entry point", "type": "char", "annotation": "const*", "length": "strlen"}, + {"name": "constant count", "type": "uint32_t", "default": 0}, + {"name": "constants", "type": "constant entry", "annotation": "const*", "length": "constant count"} + ] + }, + "primitive topology": { + "category": "enum", + "values": [ + {"value": 0, "name": "point list"}, + {"value": 1, "name": "line list"}, + {"value": 2, "name": "line strip"}, + {"value": 3, "name": "triangle list"}, + {"value": 4, "name": "triangle strip"} + ] + }, + "query set": { + "category": "object", + "methods": [ + { + "name": "set label", + "returns": "void", + "tags": ["dawn"], + "_TODO": "needs an upstream equivalent", + "args": [ + {"name": "label", "type": "char", "annotation": "const*", "length": "strlen"} + ] + }, + { + "name": "destroy" + } + ] + }, + "query set descriptor": { + "category": "structure", + "extensible": "in", + "members": [ + {"name": "label", "type": "char", "annotation": "const*", "length": "strlen", "optional": true}, + {"name": "type", "type": "query type"}, + {"name": "count", "type": "uint32_t"}, + {"name": "pipeline statistics", "type": "pipeline statistic name", "annotation": "const*", "length": "pipeline statistics count"}, + {"name": "pipeline statistics count", "type": "uint32_t", "default": "0"} + ] + }, + "query type": { + "category": "enum", + "values": [ + {"value": 0, "name": "occlusion"}, + {"value": 1, "name": "pipeline statistics"}, + {"value": 2, "name": "timestamp"} + ] + }, + "queue": { + "category": "object", + "methods": [ + { + "name": "submit", + "args": [ + {"name": "command count", "type": "uint32_t"}, + {"name": "commands", "type": "command buffer", "annotation": "const*", "length": "command count"} + ] + }, + { + "name": "on submitted work done", + "tags": ["dawn", "emscripten"], + "args": [ + {"name": "signal value", "type": "uint64_t"}, + {"name": "callback", "type": "queue work done callback"}, + {"name": "userdata", "type": "void", "annotation": "*"} + ] + }, + { + "name": "on submitted work done", + "tags": ["upstream"], + "args": [ + {"name": "callback", "type": "queue work done callback"}, + {"name": "userdata", "type": "void", "annotation": "*"} + ] + }, + { + "name": "write buffer", + "args": [ + {"name": "buffer", "type": "buffer"}, + {"name": "buffer offset", "type": "uint64_t"}, + {"name": "data", "type": "void", "annotation": "const*", "length": "size"}, + {"name": "size", "type": "size_t"} + ] + }, + { + "name": "write texture", + "args": [ + {"name": "destination", "type": "image copy texture", "annotation": "const*"}, + {"name": "data", "type": "void", "annotation": "const*", "length": "data size"}, + {"name": "data size", "type": "size_t"}, + {"name": "data layout", "type": "texture data layout", "annotation": "const*"}, + {"name": "write size", "type": "extent 3D", "annotation": "const*"} + ] + }, + { + "name": "copy texture for browser", + "extensible": "in", + "tags": ["dawn"], + "args": [ + {"name": "source", "type": "image copy texture", "annotation": "const*"}, + {"name": "destination", "type": "image copy texture", "annotation": "const*"}, + {"name": "copy size", "type": "extent 3D", "annotation": "const*"}, + {"name": "options", "type": "copy texture for browser options", "annotation": "const*"} + ] + } + ] + }, + "queue descriptor": { + "category": "structure", + "extensible": "in", + "tags": ["upstream"], + "members": [ + {"name": "label", "type": "char", "annotation": "const*", "length": "strlen", "optional": true} + ] + }, + "queue work done callback": { + "category": "function pointer", + "args": [ + {"name": "status", "type": "queue work done status"}, + {"name": "userdata", "type": "void", "annotation": "*"} + ] + }, + "queue work done status": { + "category": "enum", + "emscripten_no_enum_table": true, + "values": [ + {"value": 0, "name": "success"}, + {"value": 1, "name": "error"}, + {"value": 2, "name": "unknown"}, + {"value": 3, "name": "device lost"} + ] + }, + + "render bundle": { + "category": "object" + }, + + "render bundle encoder": { + "category": "object", + "methods": [ + { + "name": "set pipeline", + "args": [ + {"name": "pipeline", "type": "render pipeline"} + ] + }, + { + "name": "set bind group", + "args": [ + {"name": "group index", "type": "uint32_t"}, + {"name": "group", "type": "bind group"}, + {"name": "dynamic offset count", "type": "uint32_t", "default": "0"}, + {"name": "dynamic offsets", "type": "uint32_t", "annotation": "const*", "length": "dynamic offset count", "default": "nullptr"} + ] + }, + { + "name": "draw", + "args": [ + {"name": "vertex count", "type": "uint32_t"}, + {"name": "instance count", "type": "uint32_t", "default": "1"}, + {"name": "first vertex", "type": "uint32_t", "default": "0"}, + {"name": "first instance", "type": "uint32_t", "default": "0"} + ] + }, + { + "name": "draw indexed", + "args": [ + {"name": "index count", "type": "uint32_t"}, + {"name": "instance count", "type": "uint32_t", "default": "1"}, + {"name": "first index", "type": "uint32_t", "default": "0"}, + {"name": "base vertex", "type": "int32_t", "default": "0"}, + {"name": "first instance", "type": "uint32_t", "default": "0"} + ] + }, + { + "name": "draw indirect", + "args": [ + {"name": "indirect buffer", "type": "buffer"}, + {"name": "indirect offset", "type": "uint64_t"} + ] + }, + { + "name": "draw indexed indirect", + "args": [ + {"name": "indirect buffer", "type": "buffer"}, + {"name": "indirect offset", "type": "uint64_t"} + ] + }, + { + "name": "insert debug marker", + "args": [ + {"name": "marker label", "type": "char", "annotation": "const*", "length": "strlen"} + ] + }, + { + "name": "pop debug group", + "args": [] + }, + { + "name": "push debug group", + "args": [ + {"name": "group label", "type": "char", "annotation": "const*", "length": "strlen"} + ] + }, + { + "name": "set vertex buffer", + "args": [ + {"name": "slot", "type": "uint32_t"}, + {"name": "buffer", "type": "buffer"}, + {"name": "offset", "type": "uint64_t", "default": "0"}, + {"name": "size", "type": "uint64_t", "default": "WGPU_WHOLE_SIZE"} + ] + }, + { + "name": "set index buffer", + "args": [ + {"name": "buffer", "type": "buffer"}, + {"name": "format", "type": "index format"}, + {"name": "offset", "type": "uint64_t", "default": "0"}, + {"name": "size", "type": "uint64_t", "default": "WGPU_WHOLE_SIZE"} + ] + }, + { + "name": "finish", + "returns": "render bundle", + "args": [ + {"name": "descriptor", "type": "render bundle descriptor", "annotation": "const*", "optional": true} + ] + }, + { + "name": "set label", + "returns": "void", + "tags": ["dawn"], + "_TODO": "needs an upstream equivalent", + "args": [ + {"name": "label", "type": "char", "annotation": "const*", "length": "strlen"} + ] + } + ] + }, + + "render bundle descriptor": { + "category": "structure", + "extensible": "in", + "members": [ + {"name": "label", "type": "char", "annotation": "const*", "length": "strlen", "optional": true} + ] + }, + + "render bundle encoder descriptor": { + "category": "structure", + "extensible": "in", + "members": [ + {"name": "label", "type": "char", "annotation": "const*", "length": "strlen", "optional": true}, + {"name": "color formats count", "type": "uint32_t"}, + {"name": "color formats", "type": "texture format", "annotation": "const*", "length": "color formats count"}, + {"name": "depth stencil format", "type": "texture format", "default": "undefined"}, + {"name": "sample count", "type": "uint32_t", "default": "1"}, + {"name": "depth read only", "type": "bool", "default": "false"}, + {"name": "stencil read only", "type": "bool", "default": "false"} + ] + }, + + "render pass color attachment": { + "category": "structure", + "members": [ + {"name": "view", "type": "texture view", "optional": true}, + {"name": "resolve target", "type": "texture view", "optional": true}, + {"name": "load op", "type": "load op"}, + {"name": "store op", "type": "store op"}, + {"name": "clear color", "type": "color", "default": "{ NAN, NAN, NAN, NAN }", "tags": ["deprecated"]}, + {"name": "clear value", "type": "color"} + ] + }, + + "render pass depth stencil attachment": { + "category": "structure", + "members": [ + {"name": "view", "type": "texture view"}, + {"name": "depth load op", "type": "load op", "default": "undefined"}, + {"name": "depth store op", "type": "store op", "default": "undefined"}, + {"name": "clear depth", "type": "float", "default": "NAN", "tags": ["deprecated"]}, + {"name": "depth clear value", "type": "float", "default": "0"}, + {"name": "depth read only", "type": "bool", "default": "false"}, + {"name": "stencil load op", "type": "load op", "default": "undefined"}, + {"name": "stencil store op", "type": "store op", "default": "undefined"}, + {"name": "clear stencil", "type": "uint32_t", "default": "0", "tags": ["deprecated"]}, + {"name": "stencil clear value", "type": "uint32_t", "default": "0"}, + {"name": "stencil read only", "type": "bool", "default": "false"} + ] + }, + + "render pass descriptor": { + "category": "structure", + "extensible": "in", + "members": [ + {"name": "label", "type": "char", "annotation": "const*", "length": "strlen", "optional": true}, + {"name": "color attachment count", "type": "uint32_t"}, + {"name": "color attachments", "type": "render pass color attachment", "annotation": "const*", "length": "color attachment count"}, + {"name": "depth stencil attachment", "type": "render pass depth stencil attachment", "annotation": "const*", "optional": true}, + {"name": "occlusion query set", "type": "query set", "optional": true}, + {"name": "timestamp write count", "type": "uint32_t", "default": 0}, + {"name": "timestamp writes", "type": "render pass timestamp write", "annotation": "const*", "length": "timestamp write count"} + ] + }, + "render pass encoder": { + "category": "object", + "methods": [ + { + "name": "set pipeline", + "args": [ + {"name": "pipeline", "type": "render pipeline"} + ] + }, + { + "name": "set bind group", + "args": [ + {"name": "group index", "type": "uint32_t"}, + {"name": "group", "type": "bind group"}, + {"name": "dynamic offset count", "type": "uint32_t", "default": "0"}, + {"name": "dynamic offsets", "type": "uint32_t", "annotation": "const*", "length": "dynamic offset count", "default": "nullptr"} + ] + }, + { + "name": "draw", + "args": [ + {"name": "vertex count", "type": "uint32_t"}, + {"name": "instance count", "type": "uint32_t", "default": "1"}, + {"name": "first vertex", "type": "uint32_t", "default": "0"}, + {"name": "first instance", "type": "uint32_t", "default": "0"} + ] + }, + { + "name": "draw indexed", + "args": [ + {"name": "index count", "type": "uint32_t"}, + {"name": "instance count", "type": "uint32_t", "default": "1"}, + {"name": "first index", "type": "uint32_t", "default": "0"}, + {"name": "base vertex", "type": "int32_t", "default": "0"}, + {"name": "first instance", "type": "uint32_t", "default": "0"} + ] + }, + { + "name": "draw indirect", + "args": [ + {"name": "indirect buffer", "type": "buffer"}, + {"name": "indirect offset", "type": "uint64_t"} + ] + }, + { + "name": "draw indexed indirect", + "args": [ + {"name": "indirect buffer", "type": "buffer"}, + {"name": "indirect offset", "type": "uint64_t"} + ] + }, + { + "name": "execute bundles", + "args": [ + {"name": "bundles count", "type": "uint32_t"}, + {"name": "bundles", "type": "render bundle", "annotation": "const*", "length": "bundles count"} + ] + }, + { + "name": "insert debug marker", + "args": [ + {"name": "marker label", "type": "char", "annotation": "const*", "length": "strlen"} + ] + }, + { + "name": "pop debug group", + "args": [] + }, + { + "name": "push debug group", + "args": [ + {"name": "group label", "type": "char", "annotation": "const*", "length": "strlen"} + ] + }, + { + "name": "set stencil reference", + "args": [ + {"name": "reference", "type": "uint32_t"} + ] + }, + { + "name": "set blend constant", + "args": [ + {"name": "color", "type": "color", "annotation": "const*"} + ] + }, + { + "name": "set viewport", + "args": [ + {"name": "x", "type": "float"}, + {"name": "y", "type": "float"}, + {"name": "width", "type": "float"}, + {"name": "height", "type": "float"}, + {"name": "min depth", "type": "float"}, + {"name": "max depth", "type": "float"} + ] + }, + { + "name": "set scissor rect", + "args": [ + {"name": "x", "type": "uint32_t"}, + {"name": "y", "type": "uint32_t"}, + {"name": "width", "type": "uint32_t"}, + {"name": "height", "type": "uint32_t"} + ] + }, + { + "name": "set vertex buffer", + "args": [ + {"name": "slot", "type": "uint32_t"}, + {"name": "buffer", "type": "buffer"}, + {"name": "offset", "type": "uint64_t", "default": "0"}, + {"name": "size", "type": "uint64_t", "default": "WGPU_WHOLE_SIZE"} + ] + }, + { + "name": "set index buffer", + "args": [ + {"name": "buffer", "type": "buffer"}, + {"name": "format", "type": "index format"}, + {"name": "offset", "type": "uint64_t", "default": "0"}, + {"name": "size", "type": "uint64_t", "default": "WGPU_WHOLE_SIZE"} + ] + }, + { + "name": "begin occlusion query", + "args": [ + {"name": "query index", "type": "uint32_t"} + ] + }, + { + "name": "begin pipeline statistics query", + "tags": ["upstream", "emscripten"], + "args": [ + {"name": "query set", "type": "query set"}, + {"name": "query index", "type": "uint32_t"} + ] + }, + { + "name": "end occlusion query" + }, + { + "name": "write timestamp", + "tags": ["emscripten", "dawn"], + "args": [ + {"name": "query set", "type": "query set"}, + {"name": "query index", "type": "uint32_t"} + ] + }, + { + "name": "end" + }, + { + "name": "end pass", + "tags": ["deprecated"] + }, + { + "name": "end pipeline statistics query", + "tags": ["upstream", "emscripten"] + }, + { + "name": "set label", + "returns": "void", + "tags": ["dawn"], + "_TODO": "needs an upstream equivalent", + "args": [ + {"name": "label", "type": "char", "annotation": "const*", "length": "strlen"} + ] + } + ] + }, + "render pass timestamp location": { + "category": "enum", + "values": [ + {"value": 0, "name": "beginning"}, + {"value": 1, "name": "end"} + ] + }, + "render pass timestamp write": { + "category": "structure", + "members": [ + {"name": "query set", "type": "query set"}, + {"name": "query index", "type": "uint32_t"}, + {"name": "location", "type": "render pass timestamp location"} + ] + }, + "render pipeline": { + "category": "object", + "methods": [ + { + "name": "get bind group layout", + "returns": "bind group layout", + "args": [ + {"name": "group index", "type": "uint32_t"} + ] + }, + { + "name": "set label", + "returns": "void", + "args": [ + {"name": "label", "type": "char", "annotation": "const*", "length": "strlen"} + ] + } + + ] + }, + + "request device callback": { + "category": "function pointer", + "args": [ + {"name": "status", "type": "request device status"}, + {"name": "device", "type": "device"}, + {"name": "message", "type": "char", "annotation": "const*", "length": "strlen", "optional": true}, + {"name": "userdata", "type": "void", "annotation": "*"} + ] + }, + + "request device status": { + "category": "enum", + "emscripten_no_enum_table": true, + "values": [ + {"value": 0, "name": "success"}, + {"value": 1, "name": "error"}, + {"value": 2, "name": "unknown"} + ] + }, + + "vertex state": { + "category": "structure", + "extensible": "in", + "members": [ + {"name": "module", "type": "shader module"}, + {"name": "entry point", "type": "char", "annotation": "const*", "length": "strlen"}, + {"name": "constant count", "type": "uint32_t", "default": 0}, + {"name": "constants", "type": "constant entry", "annotation": "const*", "length": "constant count"}, + {"name": "buffer count", "type": "uint32_t", "default": 0}, + {"name": "buffers", "type": "vertex buffer layout", "annotation": "const*", "length": "buffer count"} + ] + }, + + "primitive state": { + "category": "structure", + "extensible": "in", + "members": [ + {"name": "topology", "type": "primitive topology", "default": "triangle list"}, + {"name": "strip index format", "type": "index format", "default": "undefined"}, + {"name": "front face", "type": "front face", "default": "CCW"}, + {"name": "cull mode", "type": "cull mode", "default": "none"} + ] + }, + + "primitive depth clamping state": { + "category": "structure", + "chained": "in", + "tags": ["dawn", "emscripten"], + "members": [ + {"name": "clamp depth", "type": "bool", "default": "false"} + ] + }, + + "primitive depth clip control": { + "category": "structure", + "chained": "in", + "tags": ["upstream", "emscripten"], + "members": [ + {"name": "unclipped depth", "type": "bool", "default": "false"} + ] + }, + + "depth stencil state": { + "category": "structure", + "extensible": "in", + "members": [ + {"name": "format", "type": "texture format"}, + {"name": "depth write enabled", "type": "bool", "default": "false"}, + {"name": "depth compare", "type": "compare function", "default": "always"}, + {"name": "stencil front", "type": "stencil face state"}, + {"name": "stencil back", "type": "stencil face state"}, + {"name": "stencil read mask", "type": "uint32_t", "default": "0xFFFFFFFF"}, + {"name": "stencil write mask", "type": "uint32_t", "default": "0xFFFFFFFF"}, + {"name": "depth bias", "type": "int32_t", "default": "0"}, + {"name": "depth bias slope scale", "type": "float", "default": "0.0f"}, + {"name": "depth bias clamp", "type": "float", "default": "0.0f"} + ] + }, + + "multisample state": { + "category": "structure", + "extensible": "in", + "members": [ + {"name": "count", "type": "uint32_t", "default": "1"}, + {"name": "mask", "type": "uint32_t", "default": "0xFFFFFFFF"}, + {"name": "alpha to coverage enabled", "type": "bool", "default": "false"} + ] + }, + + "fragment state": { + "category": "structure", + "extensible": "in", + "members": [ + {"name": "module", "type": "shader module"}, + {"name": "entry point", "type": "char", "annotation": "const*", "length": "strlen"}, + {"name": "constant count", "type": "uint32_t", "default": 0}, + {"name": "constants", "type": "constant entry", "annotation": "const*", "length": "constant count"}, + {"name": "target count", "type": "uint32_t"}, + {"name": "targets", "type": "color target state", "annotation": "const*", "length": "target count"} + ] + }, + "color target state": { + "category": "structure", + "extensible": "in", + "members": [ + {"name": "format", "type": "texture format"}, + {"name": "blend", "type": "blend state", "annotation": "const*", "optional": true}, + {"name": "write mask", "type": "color write mask", "default": "all"} + ] + }, + "blend state": { + "category": "structure", + "extensible": false, + "members": [ + {"name": "color", "type": "blend component"}, + {"name": "alpha", "type": "blend component"} + ] + }, + + "render pipeline descriptor": { + "category": "structure", + "extensible": "in", + "members": [ + {"name": "label", "type": "char", "annotation": "const*", "length": "strlen", "optional": true}, + {"name": "layout", "type": "pipeline layout", "optional": true}, + {"name": "vertex", "type": "vertex state"}, + {"name": "primitive", "type": "primitive state"}, + {"name": "depth stencil", "type": "depth stencil state", "annotation": "const*", "optional": true}, + {"name": "multisample", "type": "multisample state"}, + {"name": "fragment", "type": "fragment state", "annotation": "const*", "optional": true} + ] + }, + + "sampler": { + "category": "object", + "methods": [ + { + "name": "set label", + "returns": "void", + "tags": ["dawn"], + "_TODO": "needs an upstream equivalent", + "args": [ + {"name": "label", "type": "char", "annotation": "const*", "length": "strlen"} + ] + } + ] + }, + "sampler descriptor": { + "category": "structure", + "extensible": "in", + "members": [ + {"name": "label", "type": "char", "annotation": "const*", "length": "strlen", "optional": true}, + {"name": "address mode u", "type": "address mode", "default": "clamp to edge"}, + {"name": "address mode v", "type": "address mode", "default": "clamp to edge"}, + {"name": "address mode w", "type": "address mode", "default": "clamp to edge"}, + {"name": "mag filter", "type": "filter mode", "default": "nearest"}, + {"name": "min filter", "type": "filter mode", "default": "nearest"}, + {"name": "mipmap filter", "type": "filter mode", "default": "nearest", "tags": ["dawn", "emscripten"]}, + {"name": "mipmap filter", "type": "mipmap filter mode", "default": "nearest", "tags": ["upstream"]}, + {"name": "lod min clamp", "type": "float", "default": "0.0f"}, + {"name": "lod max clamp", "type": "float", "default": "1000.0f"}, + {"name": "compare", "type": "compare function", "default": "undefined"}, + {"name": "max anisotropy", "type": "uint16_t", "default": "1"} + ] + }, + "shader module": { + "category": "object", + "methods": [ + { + "name": "get compilation info", + "args": [ + {"name": "callback", "type": "compilation info callback"}, + {"name": "userdata", "type": "void", "annotation": "*"} + ] + }, + { + "name": "set label", + "returns": "void", + "args": [ + {"name": "label", "type": "char", "annotation": "const*", "length": "strlen"} + ] + } + ] + }, + "shader module descriptor": { + "category": "structure", + "extensible": "in", + "members": [ + {"name": "label", "type": "char", "annotation": "const*", "length": "strlen", "optional": true}, + {"name": "hint count", "type": "uint32_t", "default": 0, "tags": ["upstream"]}, + {"name": "hints", "type": "shader module compilation hint", "annotation": "const*", "length": "hint count", "tags": ["upstream"]} + ] + }, + "shader module compilation hint": { + "category": "structure", + "extensible": "in", + "tags": ["upstream"], + "members": [ + {"name": "entry point", "type": "char", "annotation": "const*", "length": "strlen"}, + {"name": "layout", "type": "pipeline layout"} + ] + }, + "shader module SPIRV descriptor": { + "category": "structure", + "chained": "in", + "members": [ + {"name": "code size", "type": "uint32_t"}, + {"name": "code", "type": "uint32_t", "annotation": "const*", "length": "code size"} + ] + }, + "shader module WGSL descriptor": { + "category": "structure", + "chained": "in", + "members": [ + {"name": "source", "type": "char", "annotation": "const*", "length": "strlen", "tags": ["dawn", "emscripten"]}, + {"name": "code", "type": "char", "annotation": "const*", "length": "strlen", "tags": ["upstream"]} + ] + }, + "shader stage": { + "category": "bitmask", + "values": [ + {"value": 0, "name": "none"}, + {"value": 1, "name": "vertex"}, + {"value": 2, "name": "fragment"}, + {"value": 4, "name": "compute"} + ] + }, + "stencil operation": { + "category": "enum", + "values": [ + {"value": 0, "name": "keep"}, + {"value": 1, "name": "zero"}, + {"value": 2, "name": "replace"}, + {"value": 3, "name": "invert"}, + {"value": 4, "name": "increment clamp"}, + {"value": 5, "name": "decrement clamp"}, + {"value": 6, "name": "increment wrap"}, + {"value": 7, "name": "decrement wrap"} + ] + }, + "stencil face state": { + "category": "structure", + "extensible": false, + "members": [ + {"name": "compare", "type": "compare function", "default": "always"}, + {"name": "fail op", "type": "stencil operation", "default": "keep"}, + {"name": "depth fail op", "type": "stencil operation", "default": "keep"}, + {"name": "pass op", "type": "stencil operation", "default": "keep"} + ] + }, + "surface": { + "category": "object", + "methods": [ + { + "name": "get preferred format", + "returns": "texture format", + "tags": ["upstream", "emscripten"], + "args": [ + {"name": "adapter", "type": "adapter"} + ] + } + ] + }, + "surface descriptor": { + "category": "structure", + "extensible": "in", + "members": [ + {"name": "label", "type": "char", "annotation": "const*", "length": "strlen", "optional": true} + ] + }, + "surface descriptor from android native window": { + "category": "structure", + "chained": "in", + "tags": ["native"], + "members": [ + {"name": "window", "type": "void", "annotation": "*"} + ] + }, + "surface descriptor from canvas HTML selector": { + "category": "structure", + "chained": "in", + "members": [ + {"name": "selector", "type": "char", "annotation": "const*", "length": "strlen"} + ] + }, + "surface descriptor from metal layer": { + "category": "structure", + "chained": "in", + "tags": ["native"], + "members": [ + {"name": "layer", "type": "void", "annotation": "*"} + ] + }, + "surface descriptor from windows HWND": { + "category": "structure", + "chained": "in", + "tags": ["native"], + "members": [ + {"name": "hinstance", "type": "void", "annotation": "*"}, + {"name": "hwnd", "type": "void", "annotation": "*"} + ] + }, + "surface descriptor from xcb window": { + "category": "structure", + "chained": "in", + "tags": ["upstream"], + "members": [ + {"name": "connection", "type": "void", "annotation": "*"}, + {"name": "window", "type": "uint32_t"} + ] + }, + "surface descriptor from xlib window": { + "category": "structure", + "chained": "in", + "tags": ["native"], + "members": [ + {"name": "display", "type": "void", "annotation": "*"}, + {"name": "window", "type": "uint32_t"} + ] + }, + "surface descriptor from wayland surface": { + "category": "structure", + "chained": "in", + "tags": ["native"], + "members": [ + {"name": "display", "type": "void", "annotation": "*"}, + {"name": "surface", "type": "void", "annotation": "*"} + ] + }, + "surface descriptor from windows core window": { + "category": "structure", + "chained": "in", + "tags": ["dawn"], + "members": [ + {"name": "core window", "type": "void", "annotation": "*"} + ] + }, + "surface descriptor from windows swap chain panel": { + "category": "structure", + "chained": "in", + "tags": ["dawn"], + "members": [ + {"name": "swap chain panel", "type": "void", "annotation": "*"} + ] + }, + "swap chain": { + "category": "object", + "methods": [ + { + "name": "configure", + "tags": ["dawn"], + "args": [ + {"name": "format", "type": "texture format"}, + {"name": "allowed usage", "type": "texture usage"}, + {"name": "width", "type": "uint32_t"}, + {"name": "height", "type": "uint32_t"} + ] + }, + {"name": "get current texture view", "returns": "texture view"}, + {"name": "present"} + ] + }, + "swap chain descriptor": { + "category": "structure", + "extensible": "in", + "members": [ + {"name": "label", "type": "char", "annotation": "const*", "length": "strlen", "optional": true}, + {"name": "usage", "type": "texture usage"}, + {"name": "format", "type": "texture format"}, + {"name": "width", "type": "uint32_t"}, + {"name": "height", "type": "uint32_t"}, + {"name": "present mode", "type": "present mode"}, + {"name": "implementation", "type": "uint64_t", "default": 0, "tags": ["deprecated"]} + ] + }, + "s type": { + "category": "enum", + "emscripten_no_enum_table": true, + "values": [ + {"value": 0, "name": "invalid", "valid": false}, + {"value": 1, "name": "surface descriptor from metal layer", "tags": ["native"]}, + {"value": 2, "name": "surface descriptor from windows HWND", "tags": ["native"]}, + {"value": 3, "name": "surface descriptor from xlib window", "tags": ["native"]}, + {"value": 4, "name": "surface descriptor from canvas HTML selector"}, + {"value": 5, "name": "shader module SPIRV descriptor"}, + {"value": 6, "name": "shader module WGSL descriptor"}, + {"value": 7, "name": "primitive depth clip control", "tags": ["upstream", "emscripten"]}, + {"value": 8, "name": "surface descriptor from wayland surface", "tags": ["native"]}, + {"value": 9, "name": "surface descriptor from android native window", "tags": ["native"]}, + {"value": 10, "name": "surface descriptor from xcb window", "tags": ["upstream"]}, + {"value": 11, "name": "surface descriptor from windows core window", "tags": ["dawn"]}, + {"value": 12, "name": "external texture binding entry", "tags": ["dawn"]}, + {"value": 13, "name": "external texture binding layout", "tags": ["dawn"]}, + {"value": 14, "name": "surface descriptor from windows swap chain panel", "tags": ["dawn"]}, + {"value": 1000, "name": "dawn texture internal usage descriptor", "tags": ["dawn"]}, + {"value": 1001, "name": "primitive depth clamping state", "tags": ["dawn", "emscripten"]}, + {"value": 1002, "name": "dawn toggles device descriptor", "tags": ["dawn", "native"]}, + {"value": 1003, "name": "dawn encoder internal usage descriptor", "tags": ["dawn"]}, + {"value": 1004, "name": "dawn instance descriptor", "tags": ["dawn", "native"]}, + {"value": 1005, "name": "dawn cache device descriptor", "tags": ["dawn", "native"]} + ] + }, + "texture": { + "category": "object", + "methods": [ + { + "name": "create view", + "returns": "texture view", + "args": [ + {"name": "descriptor", "type": "texture view descriptor", "annotation": "const*", "optional": true} + ] + }, + { + "name": "set label", + "returns": "void", + "tags": ["dawn"], + "_TODO": "needs an upstream equivalent", + "args": [ + {"name": "label", "type": "char", "annotation": "const*", "length": "strlen"} + ] + }, + { + "name": "destroy" + } + ] + }, + "texture aspect": { + "category": "enum", + "values": [ + {"value": 0, "name": "all"}, + {"value": 1, "name": "stencil only"}, + {"value": 2, "name": "depth only"}, + {"value": 3, "name": "plane 0 only", "tags": ["dawn"]}, + {"value": 4, "name": "plane 1 only", "tags": ["dawn"]} + ] + }, + "texture component type": { + "category": "enum", + "values": [ + {"value": 0, "name": "float"}, + {"value": 1, "name": "sint"}, + {"value": 2, "name": "uint"}, + {"value": 3, "name": "depth comparison"} + ] + }, + "texture data layout": { + "category": "structure", + "extensible": "in", + "members": [ + {"name": "offset", "type": "uint64_t", "default": 0}, + {"name": "bytes per row", "type": "uint32_t", "default": "WGPU_COPY_STRIDE_UNDEFINED"}, + {"name": "rows per image", "type": "uint32_t", "default": "WGPU_COPY_STRIDE_UNDEFINED"} + ] + }, + "texture descriptor": { + "category": "structure", + "extensible": "in", + "members": [ + {"name": "label", "type": "char", "annotation": "const*", "length": "strlen", "optional": true}, + {"name": "usage", "type": "texture usage"}, + {"name": "dimension", "type": "texture dimension", "default": "2D"}, + {"name": "size", "type": "extent 3D"}, + {"name": "format", "type": "texture format"}, + {"name": "mip level count", "type": "uint32_t", "default": 1}, + {"name": "sample count", "type": "uint32_t", "default": 1}, + {"name": "view format count", "type": "uint32_t", "default": 0}, + {"name": "view formats", "type": "texture format", "annotation": "const*", "length": "view format count"} + ] + }, + "texture dimension": { + "category": "enum", + "values": [ + {"value": 0, "name": "1D"}, + {"value": 1, "name": "2D"}, + {"value": 2, "name": "3D"} + ] + }, + "texture format": { + "category": "enum", + "values": [ + {"value": 0, "name": "undefined", "valid": false, "jsrepr": "undefined"}, + + {"value": 1, "name": "R8 unorm"}, + {"value": 2, "name": "R8 snorm"}, + {"value": 3, "name": "R8 uint"}, + {"value": 4, "name": "R8 sint"}, + + {"value": 5, "name": "R16 uint"}, + {"value": 6, "name": "R16 sint"}, + {"value": 7, "name": "R16 float"}, + {"value": 8, "name": "RG8 unorm"}, + {"value": 9, "name": "RG8 snorm"}, + {"value": 10, "name": "RG8 uint"}, + {"value": 11, "name": "RG8 sint"}, + + {"value": 12, "name": "R32 float"}, + {"value": 13, "name": "R32 uint"}, + {"value": 14, "name": "R32 sint"}, + {"value": 15, "name": "RG16 uint"}, + {"value": 16, "name": "RG16 sint"}, + {"value": 17, "name": "RG16 float"}, + {"value": 18, "name": "RGBA8 unorm"}, + {"value": 19, "name": "RGBA8 unorm srgb"}, + {"value": 20, "name": "RGBA8 snorm"}, + {"value": 21, "name": "RGBA8 uint"}, + {"value": 22, "name": "RGBA8 sint"}, + {"value": 23, "name": "BGRA8 unorm"}, + {"value": 24, "name": "BGRA8 unorm srgb"}, + {"value": 25, "name": "RGB10 A2 unorm"}, + {"value": 26, "name": "RG11 B10 ufloat"}, + {"value": 27, "name": "RGB9 E5 ufloat"}, + + {"value": 28, "name": "RG32 float"}, + {"value": 29, "name": "RG32 uint"}, + {"value": 30, "name": "RG32 sint"}, + {"value": 31, "name": "RGBA16 uint"}, + {"value": 32, "name": "RGBA16 sint"}, + {"value": 33, "name": "RGBA16 float"}, + + {"value": 34, "name": "RGBA32 float"}, + {"value": 35, "name": "RGBA32 uint"}, + {"value": 36, "name": "RGBA32 sint"}, + + {"value": 37, "name": "stencil8"}, + {"value": 38, "name": "depth16 unorm"}, + {"value": 39, "name": "depth24 plus"}, + {"value": 40, "name": "depth24 plus stencil8"}, + {"value": 41, "name": "depth24 unorm stencil8"}, + {"value": 42, "name": "depth32 float"}, + {"value": 43, "name": "depth32 float stencil8"}, + + {"value": 44, "name": "BC1 RGBA unorm", "jsrepr": "'bc1-rgba-unorm'"}, + {"value": 45, "name": "BC1 RGBA unorm srgb", "jsrepr": "'bc1-rgba-unorm-srgb'"}, + {"value": 46, "name": "BC2 RGBA unorm", "jsrepr": "'bc2-rgba-unorm'"}, + {"value": 47, "name": "BC2 RGBA unorm srgb", "jsrepr": "'bc2-rgba-unorm-srgb'"}, + {"value": 48, "name": "BC3 RGBA unorm", "jsrepr": "'bc3-rgba-unorm'"}, + {"value": 49, "name": "BC3 RGBA unorm srgb", "jsrepr": "'bc3-rgba-unorm-srgb'"}, + {"value": 50, "name": "BC4 R unorm", "jsrepr": "'bc4-r-unorm'"}, + {"value": 51, "name": "BC4 R snorm", "jsrepr": "'bc4-r-snorm'"}, + {"value": 52, "name": "BC5 RG unorm", "jsrepr": "'bc5-rg-unorm'"}, + {"value": 53, "name": "BC5 RG snorm", "jsrepr": "'bc5-rg-snorm'"}, + {"value": 54, "name": "BC6H RGB ufloat", "jsrepr": "'bc6h-rgb-ufloat'"}, + {"value": 55, "name": "BC6H RGB float", "jsrepr": "'bc6h-rgb-float'"}, + {"value": 56, "name": "BC7 RGBA unorm", "jsrepr": "'bc7-rgba-unorm'"}, + {"value": 57, "name": "BC7 RGBA unorm srgb", "jsrepr": "'bc7-rgba-unorm-srgb'"}, + + {"value": 58, "name": "ETC2 RGB8 unorm", "jsrepr": "'etc2-rgb8unorm'"}, + {"value": 59, "name": "ETC2 RGB8 unorm srgb", "jsrepr": "'etc2-rgb8unorm-srgb'"}, + {"value": 60, "name": "ETC2 RGB8A1 unorm", "jsrepr": "'etc2-rgb8a1unorm'"}, + {"value": 61, "name": "ETC2 RGB8A1 unorm srgb", "jsrepr": "'etc2-rgb8a1unorm-srgb'"}, + {"value": 62, "name": "ETC2 RGBA8 unorm", "jsrepr": "'etc2-rgba8unorm'"}, + {"value": 63, "name": "ETC2 RGBA8 unorm srgb", "jsrepr": "'etc2-rgba8unorm-srgb'"}, + {"value": 64, "name": "EAC R11 unorm", "jsrepr": "'eac-r11unorm'"}, + {"value": 65, "name": "EAC R11 snorm", "jsrepr": "'eac-r11snorm'"}, + {"value": 66, "name": "EAC RG11 unorm", "jsrepr": "'eac-rg11unorm'"}, + {"value": 67, "name": "EAC RG11 snorm", "jsrepr": "'eac-rg11snorm'"}, + + {"value": 68, "name": "ASTC 4x4 unorm", "jsrepr": "'astc-4x4-unorm'"}, + {"value": 69, "name": "ASTC 4x4 unorm srgb", "jsrepr": "'astc-4x4-unorm-srgb'"}, + {"value": 70, "name": "ASTC 5x4 unorm", "jsrepr": "'astc-5x4-unorm'"}, + {"value": 71, "name": "ASTC 5x4 unorm srgb", "jsrepr": "'astc-5x4-unorm-srgb'"}, + {"value": 72, "name": "ASTC 5x5 unorm", "jsrepr": "'astc-5x5-unorm'"}, + {"value": 73, "name": "ASTC 5x5 unorm srgb", "jsrepr": "'astc-5x5-unorm-srgb'"}, + {"value": 74, "name": "ASTC 6x5 unorm", "jsrepr": "'astc-6x5-unorm'"}, + {"value": 75, "name": "ASTC 6x5 unorm srgb", "jsrepr": "'astc-6x5-unorm-srgb'"}, + {"value": 76, "name": "ASTC 6x6 unorm", "jsrepr": "'astc-6x6-unorm'"}, + {"value": 77, "name": "ASTC 6x6 unorm srgb", "jsrepr": "'astc-6x6-unorm-srgb'"}, + {"value": 78, "name": "ASTC 8x5 unorm", "jsrepr": "'astc-8x5-unorm'"}, + {"value": 79, "name": "ASTC 8x5 unorm srgb", "jsrepr": "'astc-8x5-unorm-srgb'"}, + {"value": 80, "name": "ASTC 8x6 unorm", "jsrepr": "'astc-8x6-unorm'"}, + {"value": 81, "name": "ASTC 8x6 unorm srgb", "jsrepr": "'astc-8x6-unorm-srgb'"}, + {"value": 82, "name": "ASTC 8x8 unorm", "jsrepr": "'astc-8x8-unorm'"}, + {"value": 83, "name": "ASTC 8x8 unorm srgb", "jsrepr": "'astc-8x8-unorm-srgb'"}, + {"value": 84, "name": "ASTC 10x5 unorm", "jsrepr": "'astc-10x5-unorm'"}, + {"value": 85, "name": "ASTC 10x5 unorm srgb", "jsrepr": "'astc-10x5-unorm-srgb'"}, + {"value": 86, "name": "ASTC 10x6 unorm", "jsrepr": "'astc-10x6-unorm'"}, + {"value": 87, "name": "ASTC 10x6 unorm srgb", "jsrepr": "'astc-10x6-unorm-srgb'"}, + {"value": 88, "name": "ASTC 10x8 unorm", "jsrepr": "'astc-10x8-unorm'"}, + {"value": 89, "name": "ASTC 10x8 unorm srgb", "jsrepr": "'astc-10x8-unorm-srgb'"}, + {"value": 90, "name": "ASTC 10x10 unorm", "jsrepr": "'astc-10x10-unorm'"}, + {"value": 91, "name": "ASTC 10x10 unorm srgb", "jsrepr": "'astc-10x10-unorm-srgb'"}, + {"value": 92, "name": "ASTC 12x10 unorm", "jsrepr": "'astc-12x10-unorm'"}, + {"value": 93, "name": "ASTC 12x10 unorm srgb", "jsrepr": "'astc-12x10-unorm-srgb'"}, + {"value": 94, "name": "ASTC 12x12 unorm", "jsrepr": "'astc-12x12-unorm'"}, + {"value": 95, "name": "ASTC 12x12 unorm srgb", "jsrepr": "'astc-12x12-unorm-srgb'"}, + + {"value": 96, "name": "R8 BG8 Biplanar 420 unorm", "tags": ["dawn"]} + ] + }, + "texture usage": { + "category": "bitmask", + "values": [ + {"value": 0, "name": "none"}, + {"value": 1, "name": "copy src"}, + {"value": 2, "name": "copy dst"}, + {"value": 4, "name": "texture binding"}, + {"value": 8, "name": "storage binding"}, + {"value": 16, "name": "render attachment"}, + {"value": 32, "name": "present", "tags": ["dawn"]} + ] + }, + "texture view descriptor": { + "category": "structure", + "extensible": "in", + "members": [ + {"name": "label", "type": "char", "annotation": "const*", "length": "strlen", "optional": true}, + {"name": "format", "type": "texture format", "default": "undefined"}, + {"name": "dimension", "type": "texture view dimension", "default": "undefined"}, + {"name": "base mip level", "type": "uint32_t", "default": "0"}, + {"name": "mip level count", "type": "uint32_t", "default": "WGPU_MIP_LEVEL_COUNT_UNDEFINED"}, + {"name": "base array layer", "type": "uint32_t", "default": "0"}, + {"name": "array layer count", "type": "uint32_t", "default": "WGPU_ARRAY_LAYER_COUNT_UNDEFINED"}, + {"name": "aspect", "type": "texture aspect", "default": "all"} + ] + }, + "texture view": { + "category": "object", + "methods": [ + { + "name": "set label", + "returns": "void", + "tags": ["dawn"], + "_TODO": "needs an upstream equivalent", + "args": [ + {"name": "label", "type": "char", "annotation": "const*", "length": "strlen"} + ] + } + ] + }, + "texture view dimension": { + "category": "enum", + "values": [ + {"value": 0, "name": "undefined", "valid": false, "jsrepr": "undefined"}, + {"value": 1, "name": "1D"}, + {"value": 2, "name": "2D"}, + {"value": 3, "name": "2D array"}, + {"value": 4, "name": "cube"}, + {"value": 5, "name": "cube array"}, + {"value": 6, "name": "3D"} + ] + }, + "vertex format": { + "category": "enum", + "values": [ + {"value": 0, "name": "undefined", "valid": false, "jsrepr": "undefined"}, + {"value": 1, "name": "uint8x2"}, + {"value": 2, "name": "uint8x4"}, + {"value": 3, "name": "sint8x2"}, + {"value": 4, "name": "sint8x4"}, + {"value": 5, "name": "unorm8x2"}, + {"value": 6, "name": "unorm8x4"}, + {"value": 7, "name": "snorm8x2"}, + {"value": 8, "name": "snorm8x4"}, + {"value": 9, "name": "uint16x2"}, + {"value": 10, "name": "uint16x4"}, + {"value": 11, "name": "sint16x2"}, + {"value": 12, "name": "sint16x4"}, + {"value": 13, "name": "unorm16x2"}, + {"value": 14, "name": "unorm16x4"}, + {"value": 15, "name": "snorm16x2"}, + {"value": 16, "name": "snorm16x4"}, + {"value": 17, "name": "float16x2"}, + {"value": 18, "name": "float16x4"}, + {"value": 19, "name": "float32"}, + {"value": 20, "name": "float32x2"}, + {"value": 21, "name": "float32x3"}, + {"value": 22, "name": "float32x4"}, + {"value": 23, "name": "uint32"}, + {"value": 24, "name": "uint32x2"}, + {"value": 25, "name": "uint32x3"}, + {"value": 26, "name": "uint32x4"}, + {"value": 27, "name": "sint32"}, + {"value": 28, "name": "sint32x2"}, + {"value": 29, "name": "sint32x3"}, + {"value": 30, "name": "sint32x4"} + ] + }, + "whole size" : { + "category": "constant", + "type": "uint64_t", + "value": "(0xffffffffffffffffULL)" + }, + "whole map size" : { + "category": "constant", + "type": "size_t", + "value": "SIZE_MAX" + }, + "stride undefined" : { + "category": "constant", + "tags": ["deprecated"], + "_TODO": "crbug.com/dawn/520: Remove WGPU_STRIDE_UNDEFINED in favor of WGPU_COPY_STRIDE_UNDEFINED.", + "type": "uint32_t", + "value": "(0xffffffffUL)" + }, + "copy stride undefined" : { + "category": "constant", + "type": "uint32_t", + "value": "(0xffffffffUL)" + }, + "limit u32 undefined" : { + "category": "constant", + "type": "uint32_t", + "value": "(0xffffffffUL)" + }, + "limit u64 undefined" : { + "category": "constant", + "type": "uint64_t", + "value": "(0xffffffffffffffffULL)" + }, + "array layer count undefined" : { + "category": "constant", + "type": "uint32_t", + "value": "(0xffffffffUL)" + }, + "mip level count undefined" : { + "category": "constant", + "type": "uint32_t", + "value": "(0xffffffffUL)" + }, + "ObjectType": { + "_comment": "Only used for the wire", + "category": "native" + }, + "ObjectId": { + "_comment": "Only used for the wire", + "category": "native" + }, + "ObjectHandle": { + "_comment": "Only used for the wire", + "category": "native" + }, + "void": { + "category": "native" + }, + "void *": { + "category": "native" + }, + "void const *": { + "category": "native" + }, + "int32_t": { + "category": "native" + }, + "size_t": { + "category": "native" + }, + "uint16_t": { + "category": "native" + }, + "uint32_t": { + "category": "native" + }, + "uint64_t": { + "category": "native" + }, + "uint8_t": { + "category": "native" + }, + "dawn texture internal usage descriptor": { + "category": "structure", + "chained": "in", + "tags": ["dawn"], + "members": [ + {"name": "internal usage", "type": "texture usage", "default": "none"} + ] + }, + "dawn encoder internal usage descriptor": { + "category": "structure", + "chained": "in", + "tags": ["dawn"], + "members": [ + {"name": "use internal usages", "type": "bool", "default": "false"} + ] + } +}
diff --git a/dawn_wire.json b/dawn_wire.json new file mode 100644 index 0000000..2e2318e --- /dev/null +++ b/dawn_wire.json
@@ -0,0 +1,233 @@ +{ + "_comment": [ + "Copyright 2019 The Dawn Authors", + "", + "Licensed under the Apache License, Version 2.0 (the \"License\");", + "you may not use this file except in compliance with the License.", + "You may obtain a copy of the License at", + "", + " http://www.apache.org/licenses/LICENSE-2.0", + "", + "Unless required by applicable law or agreed to in writing, software", + "distributed under the License is distributed on an \"AS IS\" BASIS,", + "WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.", + "See the License for the specific language governing permissions and", + "limitations under the License." + ], + + "_doc": "See docs/dawn/codegen.md", + + "commands": { + "buffer map async": [ + { "name": "buffer id", "type": "ObjectId" }, + { "name": "request serial", "type": "uint64_t" }, + { "name": "mode", "type": "map mode" }, + { "name": "offset", "type": "uint64_t"}, + { "name": "size", "type": "uint64_t"} + ], + "buffer update mapped data": [ + { "name": "buffer id", "type": "ObjectId" }, + { "name": "write data update info length", "type": "uint64_t" }, + { "name": "write data update info", "type": "uint8_t", "annotation": "const*", "length": "write data update info length", "skip_serialize": true}, + { "name": "offset", "type": "uint64_t"}, + { "name": "size", "type": "uint64_t"} + ], + "device create buffer": [ + { "name": "device id", "type": "ObjectId" }, + { "name": "descriptor", "type": "buffer descriptor", "annotation": "const*" }, + { "name": "result", "type": "ObjectHandle", "handle_type": "buffer" }, + { "name": "read handle create info length", "type": "uint64_t" }, + { "name": "read handle create info", "type": "uint8_t", "annotation": "const*", "length": "read handle create info length", "skip_serialize": true}, + { "name": "write handle create info length", "type": "uint64_t" }, + { "name": "write handle create info", "type": "uint8_t", "annotation": "const*", "length": "write handle create info length", "skip_serialize": true} + ], + "device create compute pipeline async": [ + { "name": "device id", "type": "ObjectId" }, + { "name": "request serial", "type": "uint64_t" }, + { "name": "pipeline object handle", "type": "ObjectHandle", "handle_type": "compute pipeline"}, + { "name": "descriptor", "type": "compute pipeline descriptor", "annotation": "const*"} + ], + "device create render pipeline async": [ + { "name": "device id", "type": "ObjectId" }, + { "name": "request serial", "type": "uint64_t" }, + { "name": "pipeline object handle", "type": "ObjectHandle", "handle_type": "render pipeline"}, + { "name": "descriptor", "type": "render pipeline descriptor", "annotation": "const*"} + ], + "device pop error scope": [ + { "name": "device id", "type": "ObjectId" }, + { "name": "request serial", "type": "uint64_t" } + ], + "destroy object": [ + { "name": "object type", "type": "ObjectType" }, + { "name": "object id", "type": "ObjectId" } + ], + "queue on submitted work done": [ + { "name": "queue id", "type": "ObjectId" }, + { "name": "signal value", "type": "uint64_t" }, + { "name": "request serial", "type": "uint64_t" } + ], + "queue write buffer": [ + {"name": "queue id", "type": "ObjectId" }, + {"name": "buffer id", "type": "ObjectId" }, + {"name": "buffer offset", "type": "uint64_t"}, + {"name": "data", "type": "uint8_t", "annotation": "const*", "length": "size", "wire_is_data_only": true}, + {"name": "size", "type": "uint64_t"} + ], + "queue write texture": [ + {"name": "queue id", "type": "ObjectId" }, + {"name": "destination", "type": "image copy texture", "annotation": "const*"}, + {"name": "data", "type": "uint8_t", "annotation": "const*", "length": "data size", "wire_is_data_only": true}, + {"name": "data size", "type": "uint64_t"}, + {"name": "data layout", "type": "texture data layout", "annotation": "const*"}, + {"name": "writeSize", "type": "extent 3D", "annotation": "const*"} + ], + "shader module get compilation info": [ + { "name": "shader module id", "type": "ObjectId" }, + { "name": "request serial", "type": "uint64_t" } + ], + "instance request adapter": [ + { "name": "instance id", "type": "ObjectId" }, + { "name": "request serial", "type": "uint64_t" }, + { "name": "adapter object handle", "type": "ObjectHandle", "handle_type": "adapter"}, + { "name": "options", "type": "request adapter options", "annotation": "const*" } + ], + "adapter request device": [ + { "name": "adapter id", "type": "ObjectId" }, + { "name": "request serial", "type": "uint64_t" }, + { "name": "device object handle", "type": "ObjectHandle", "handle_type": "device"}, + { "name": "descriptor", "type": "device descriptor", "annotation": "const*" } + ] + }, + "return commands": { + "buffer map async callback": [ + { "name": "buffer", "type": "ObjectHandle", "handle_type": "buffer" }, + { "name": "request serial", "type": "uint64_t" }, + { "name": "status", "type": "uint32_t" }, + { "name": "read data update info length", "type": "uint64_t" }, + { "name": "read data update info", "type": "uint8_t", "annotation": "const*", "length": "read data update info length", "skip_serialize": true } + ], + "device create compute pipeline async callback": [ + { "name": "device", "type": "ObjectHandle", "handle_type": "device" }, + { "name": "request serial", "type": "uint64_t" }, + { "name": "status", "type": "create pipeline async status" }, + { "name": "message", "type": "char", "annotation": "const*", "length": "strlen" } + ], + "device create render pipeline async callback": [ + { "name": "device", "type": "ObjectHandle", "handle_type": "device" }, + { "name": "request serial", "type": "uint64_t" }, + { "name": "status", "type": "create pipeline async status" }, + { "name": "message", "type": "char", "annotation": "const*", "length": "strlen" } + ], + "device uncaptured error callback": [ + { "name": "device", "type": "ObjectHandle", "handle_type": "device" }, + { "name": "type", "type": "error type"}, + { "name": "message", "type": "char", "annotation": "const*", "length": "strlen" } + ], + "device logging callback": [ + { "name": "device", "type": "ObjectHandle", "handle_type": "device" }, + { "name": "type", "type": "logging type"}, + { "name": "message", "type": "char", "annotation": "const*", "length": "strlen" } + ], + "device lost callback" : [ + { "name": "device", "type": "ObjectHandle", "handle_type": "device" }, + { "name": "reason", "type": "device lost reason" }, + { "name": "message", "type": "char", "annotation": "const*", "length": "strlen" } + ], + "device pop error scope callback": [ + { "name": "device", "type": "ObjectHandle", "handle_type": "device" }, + { "name": "request serial", "type": "uint64_t" }, + { "name": "type", "type": "error type" }, + { "name": "message", "type": "char", "annotation": "const*", "length": "strlen" } + ], + "queue work done callback": [ + { "name": "queue", "type": "ObjectHandle", "handle_type": "queue" }, + { "name": "request serial", "type": "uint64_t" }, + { "name": "status", "type": "queue work done status" } + ], + "shader module get compilation info callback": [ + { "name": "shader module", "type": "ObjectHandle", "handle_type": "shader module" }, + { "name": "request serial", "type": "uint64_t" }, + { "name": "status", "type": "compilation info request status" }, + { "name": "info", "type": "compilation info", "annotation": "const*", "optional": true } + ], + "instance request adapter callback": [ + { "name": "instance", "type": "ObjectHandle", "handle_type": "instance" }, + { "name": "request serial", "type": "uint64_t" }, + { "name": "status", "type": "request adapter status" }, + { "name": "message", "type": "char", "annotation": "const*", "length": "strlen", "optional": true }, + { "name": "properties", "type": "adapter properties", "annotation": "const*", "optional": "true" }, + { "name": "limits", "type": "supported limits", "annotation": "const*", "optional": "true" }, + { "name": "features count", "type": "uint32_t"}, + { "name": "features", "type": "feature name", "annotation": "const*", "length": "features count"} + ], + "adapter request device callback": [ + { "name": "adapter", "type": "ObjectHandle", "handle_type": "adapter" }, + { "name": "request serial", "type": "uint64_t" }, + { "name": "status", "type": "request device status" }, + { "name": "message", "type": "char", "annotation": "const*", "length": "strlen", "optional": true }, + { "name": "limits", "type": "supported limits", "annotation": "const*", "optional": "true" }, + { "name": "features count", "type": "uint32_t"}, + { "name": "features", "type": "feature name", "annotation": "const*", "length": "features count"} + ] + }, + "special items": { + "client_side_structures": [ + "SurfaceDescriptorFromMetalLayer", + "SurfaceDescriptorFromWindowsHWND", + "SurfaceDescriptorFromXlibWindow", + "SurfaceDescriptorFromWindowsCoreWindow", + "SurfaceDescriptorFromWindowsSwapChainPanel", + "SurfaceDescriptorFromAndroidNativeWindow" + ], + "client_side_commands": [ + "AdapterCreateDevice", + "AdapterGetProperties", + "AdapterGetLimits", + "AdapterHasFeature", + "AdapterEnumerateFeatures", + "AdapterRequestDevice", + "BufferMapAsync", + "BufferGetConstMappedRange", + "BufferGetMappedRange", + "DeviceCreateBuffer", + "DeviceCreateComputePipelineAsync", + "DeviceCreateRenderPipelineAsync", + "DeviceGetLimits", + "DeviceHasFeature", + "DeviceEnumerateFeatures", + "DevicePopErrorScope", + "DeviceSetDeviceLostCallback", + "DeviceSetUncapturedErrorCallback", + "DeviceSetLoggingCallback", + "InstanceRequestAdapter", + "ShaderModuleGetCompilationInfo", + "QueueOnSubmittedWorkDone", + "QueueWriteBuffer", + "QueueWriteTexture" + ], + "client_handwritten_commands": [ + "BufferDestroy", + "BufferUnmap", + "DeviceCreateErrorBuffer", + "DeviceGetQueue", + "DeviceInjectError" + ], + "client_special_objects": [ + "Adapter", + "Buffer", + "Device", + "Instance", + "Queue", + "ShaderModule" + ], + "server_custom_pre_handler_commands": [ + "BufferDestroy", + "BufferUnmap" + ], + "server_handwritten_commands": [ + "QueueSignal" + ], + "server_reverse_lookup_objects": [ + ] + } +}
diff --git a/docs/dawn/OWNERS b/docs/dawn/OWNERS new file mode 100644 index 0000000..72e8ffc --- /dev/null +++ b/docs/dawn/OWNERS
@@ -0,0 +1 @@ +*
diff --git a/docs/dawn/buffer_mapping.md b/docs/dawn/buffer_mapping.md new file mode 100644 index 0000000..0663f68 --- /dev/null +++ b/docs/dawn/buffer_mapping.md
@@ -0,0 +1,3 @@ +- Buffer mapping dawn wire memory transfer interface design + - https://docs.google.com/document/d/1JOhCpmJ_JyNZJtX6MVbSgxjtG1TdKORdeOGYtSfVYjk/edit?usp=sharing&resourcekey=0-1bFi47mR1jkBLdRFxcTVig +- TODO: make a md doc targeted at code walkthrough for contributors
diff --git a/docs/dawn/building.md b/docs/dawn/building.md new file mode 100644 index 0000000..230c222 --- /dev/null +++ b/docs/dawn/building.md
@@ -0,0 +1,46 @@ +# Building Dawn + +## System requirements + +- Linux + - The `pkg-config` command: + ```sh + # Install pkg-config on Ubuntu + sudo apt-get install pkg-config + ``` + +- Mac + - [Xcode](https://developer.apple.com/xcode/) 12.2+. + - The macOS 11.0 SDK. Run `xcode-select` to check whether you have it. + ```sh + ls `xcode-select -p`/Platforms/MacOSX.platform/Developer/SDKs + ``` + +## Install `depot_tools` + +Dawn uses the Chromium build system and dependency management so you need to [install depot_tools] and add it to the PATH. + +[install depot_tools]: http://commondatastorage.googleapis.com/chrome-infra-docs/flat/depot_tools/docs/html/depot_tools_tutorial.html#_setting_up + +## Get the code + +```sh +# Clone the repo as "dawn" +git clone https://dawn.googlesource.com/dawn dawn && cd dawn + +# Bootstrap the gclient configuration +cp scripts/standalone.gclient .gclient + +# Fetch external dependencies and toolchains with gclient +gclient sync +``` + +## Build Dawn + +Then generate build files using `gn args out/Debug` or `gn args out/Release`. +A text editor will appear asking build options, the most common option is `is_debug=true/false`; otherwise `gn args out/Release --list` shows all the possible options. + +On macOS you'll want to add the `use_system_xcode=true` in most cases. (and if you're a googler please get XCode from go/xcode). + +Then use `ninja -C out/Release` to build dawn and for example `./out/Release/dawn_end2end_tests` to run the tests. +
diff --git a/docs/dawn/codegen.md b/docs/dawn/codegen.md new file mode 100644 index 0000000..b62c449 --- /dev/null +++ b/docs/dawn/codegen.md
@@ -0,0 +1,112 @@ +# Dawn's code generators. + +Dawn relies on a lot of code generation to produce boilerplate code, especially webgpu.h-related code. They start by reading some JSON files (and sometimes XML too), process the data into an in-memory representation that's then used by some [Jinja2](https://jinja.palletsprojects.com/) templates to generate the code. This is similar to the model/view separation in Web development. + +Generators are based on [generator_lib.py](../generator/generator_lib.py) which provides facilities for integrating in build systems and using Jinja2. Templates can be found in [`generator/templates`](../generator/templates) and the generated files are in `out/<Debug/Release/foo>/gen/src` when building Dawn in standalone. Generated files can also be found in [Chromium's code search](https://source.chromium.org/chromium/chromium/src/+/master:out/Debug/gen/third_party/dawn/src/). + +## Dawn "JSON API" generators + +Most of the code generation is done from [`dawn.json`](../dawn.json) which is a JSON description of the WebGPU API with extra annotation used by some of the generators. The code for all the "Dawn JSON" generators is in [`dawn_json_generator.py`](../generator/dawn_json_generator.py) (with templates in the regular template dir). + +At this time it is used to generate: + + - the Dawn, Emscripten, and upstream webgpu-native `webgpu.h` C header + - the Dawn and Emscripten `webgpu_cpp.cpp/h` C++ wrapper over the C header + - libraries that implements `webgpu.h` by calling in a static or `thread_local` proc table + - other parts of the [Emscripten](https://emscripten.org/) WebGPU implementation + - a GMock version of the API with its proc table for testing + - validation helper functions for dawn_native + - the definition of dawn_native's proc table + - dawn_native's internal version of the webgpu.h types + - utilities for working with dawn_native's chained structs + - a lot of dawn_wire parts, see below + +Internally `dawn.json` is a dictionary from the "canonical name" of things to their definition. The "canonical name" is a space-separated (mostly) lower-case version of the name that's parsed into a `Name` Python object. Then that name can be turned into various casings with `.CamelCase()` `.SNAKE_CASE()`, etc. When `dawn.json` things reference each other, it is always via these "canonical names". + +The `"_metadata"` key in the JSON file is used by flexible templates for generating various Web Standard API that contains following metadata: + + - `"api"` a string, the name of the Web API + - `"namespace"` a string, the namespace of C++ wrapper + - `"c_prefix"` (optional) a string, the prefix of C function and data type, it will default to upper-case of `"namespace"` if it's not provided. + - `"proc_table_prefix"` a string, the prefix of proc table. + - `"impl_dir"` a string, the directory of API implementation + - `"native_namespace"` a string, the namespace of native implementation + - `"copyright_year"` (optional) a string, templates will use the year of copyright. + +The basic schema is that every entry is a thing with a `"category"` key what determines the sub-schema to apply to that thing. Categories and their sub-shema are defined below. Several parts of the schema use the concept of "record" which is a list of "record members" which are a combination of a type, a name and other metadata. For example the list of arguments of a function is a record. The list of structure members is a record. This combined concept is useful for the dawn_wire generator to generate code for structure and function calls in a very similar way. + +Most items and sub-items can include a list of `"tags"`, which, if specified, conditionally includes the item if any of its tags appears in the `enabled_tags` configuration passed to `parse_json`. This is used to include and exclude various items for Dawn, Emscripten, or upstream header variants. Tags are applied in the "parse_json" step ([rather than later](https://docs.google.com/document/d/1fBniVOxx3-hQbxHMugEPcQsaXaKBZYVO8yG9iXJp-fU/edit?usp=sharing)): this has the benefit of automatically catching when, for a particular tag configuration, an included item references an excluded item. + +A **record** is a list of **record members**, each of which is a dictionary with the following schema: + - `"name"` a string + - `"type"` a string, the name of the base type for this member + - `"annotation"` a string, default to "value". Define the C annotation to apply to the base type. Allowed annotations are `"value"` (the default), `"*"`, `"const*"` + - `"length"` (default to 1 if not set), a string. Defines length of the array pointed to for pointer arguments. If not set the length is implicitly 1 (so not an array), but otherwise it can be set to the name of another member in the same record that will contain the length of the array (this is heavily used in the `fooCount` `foos` pattern in the API). As a special case `"strlen"` can be used for `const char*` record members to denote that the length should be determined with `strlen`. + - `"optional"` (default to false) a boolean that says whether this member is optional. Member records can be optional if they are pointers (otherwise dawn_wire will always try to dereference them), objects (otherwise dawn_wire will always try to encode their ID and crash), or if they have a `"default"` key. Optional pointers and objects will always default to `nullptr`. + - `"default"` (optional) a number or string. If set the record member will use that value as default value. Depending on the member's category it can be a number, a string containing a number, or the name of an enum/bitmask value. + - `"wire_is_data_only"` (default to false) a boolean that says whether it is safe to directly return a pointer of this member that is pointing to a piece of memory in the transfer buffer into dawn_wire. To prevent TOCTOU attacks, by default in dawn_wire we must ensure every single value returned to dawn_native a copy of what's in the wire, so `"wire_is_data_only"` is set to true only when the member is data-only and don't impact control flow. + +**`"native"`**, doesn't have any other key. This is used to define native types that can be referenced by name in other things. + +**`"typedef"`** (usually only used for gradual deprecations): + - `"type"`: the name of the things this is a typedef for. + +**`"enum"`** an `uint32_t`-based enum value. + - `"values"` an array of enum values. Each value is a dictionary containing: + - `"name"` a string + - `"value"` a number that can be decimal or hexadecimal + - `"jsrepr"` (optional) a string to allow overriding how this value map to Javascript for the Emscripten bits + - `"valid"` (defaults to true) a boolean that controls whether the dawn_native validation utilities will consider this enum value valid. + - `"emscripten_no_enum_table"` (optional) if true, skips generating an enum table in `library_webgpu_enum_tables.js` + +**`"bitmask"`** an `uint32_t`-based bitmask. It is similar to **`"enum"`** but can be output differently. + +**`"function pointer"`** defines a function pointer type that can be used by other things. + - `"returns"` a string that's the name of the return type + - `"args"` a **record**, so an array of **record members** + +**`"structure"`** + - `"members"` a **record**, so an array of **record members** + - `"extensible"` (defaults to false) a boolean defining if this is an "extensible" WebGPU structure (i.e. has `nextInChain`). "descriptor" structures should usually have this set to true. + - `"chained"` (defaults to false) a boolean defining if this is a structure that can be "chained" in a WebGPU structure (i.e. has `nextInChain` and `sType`) + +**`"object"`** + - `**methods**` an array of methods for this object. Note that "release" and "reference" don't need to be specified. Each method is a dictionary containing: + - `"name"` a string + - `"return_type"` (default to no return type) a string that's the name of the return type. + - `"arguments"` a **record**, so an array of **record members** + +**`"constant"`** + - `"type"`: a string, the name of the base data type + - `"value"`: a string, the value is defined with preprocessor macro + +**`"function"`** declares a function that not belongs to any class. + - `"returns"` a string that's the name of the return type + - `"args"` a **record**, so an array of **record members** + +## Dawn "wire" generators + +The generator for the pieces of dawn_wire need additional data which is found in [`dawn_wire_json`](../dawn_wire.json). Examples of pieces that are generated are: + + - `WireCmd.cpp/.h` the most important piece: the meat of the serialization / deserialization code for WebGPU structures and commands + - `ServerHandlers/Doers.cpp` that does the complete handling of all regular WebGPU methods in the server + - `ApiProcs.cpp` that implements the complete handling of all regular WebGPU methods in the client + +Most of the WebGPU methods can be handled automatically by the wire client/server but some of them need custom handling (for example because they handle callbacks or need client-side state tracking). `dawn_wire.json` defines which methods need special handling, and extra wire commands that can be used by that special handling (and will get `WireCmd` support). + +The schema of `dawn_wire.json` is a dictionary with the following keys: + - `"commands"` an array of **records** defining extra client->server commands that can be used in special-cased code path. + - Each **record member** can have an extra `"skip_serialize"` key that's a boolean that default to false and makes `WireCmd` skip it on its on-wire format. + - `"return commands"` like `"commands"` but in revers, an array of **records** defining extra server->client commands + - `"special items"` a dictionary containing various lists of methods or object that require special handling in places in the dawn_wire autogenerated files + - `"client_side_structures"`: a list of structure that we shouldn't generate serialization/deserialization code for because they are client-side only + - `"client_handwritten_commands"`: a list of methods that are written manually and won't be automatically generated in the client + - `"client_side_commands"`: a list of methods that won't be automatically generated in the server. Gets added to `"client_handwritten_commands"` + - `"client_special_objects"`: a list of objects that need special manual state-tracking in the client and won't be autogenerated + - `"server_custom_pre_handler_commands"`: a list of methods that will run custom "pre-handlers" before calling the autogenerated handlers in the server + - `"server_handwrittten_commands"`: a list of methods that are written manually and won't be automatically generated in the server. + - `server_reverse_object_lookup_objects`: a list of objects for which the server will maintain an object -> ID mapping. + +## OpenGL loader generator + +The code to load OpenGL entrypoints from a `GetProcAddress` function is generated from [`gl.xml`](../third_party/khronos/gl.xml) and the [list of extensions](../src/dawn/native/opengl/supported_extensions.json) it supports.
diff --git a/docs/dawn/contributing.md b/docs/dawn/contributing.md new file mode 100644 index 0000000..6fabb37 --- /dev/null +++ b/docs/dawn/contributing.md
@@ -0,0 +1,122 @@ +# How to contribute to Dawn + +First off, we'd love to get your contributions to Dawn! + +Everything helps other folks using Dawn and WebGPU: from small fixes and documentation +improvements to larger features and optimizations. +Please read on to learn about the contribution process. + +## One time setup + +### Contributor License Agreement + +Contributions to this project must be accompanied by a Contributor License +Agreement. You (or your employer) retain the copyright to your contribution. +This simply gives us permission to use and redistribute your contributions as +part of the project. Head over to <https://cla.developers.google.com/> to see +your current agreements on file or to sign a new one. + +You generally only need to submit a CLA once, so if you've already submitted one +(even if it was for a different Google project), you probably don't need to do +it again. + +### Gerrit setup + +Dawn's contributions are submitted and reviewed on [Dawn's Gerrit](https://dawn-review.googlesource.com). + +Gerrit works a bit differently than Github (if that's what you're used to): +there are no forks. Instead everyone works on the same repository. Gerrit has +magic branches for various purpose: + + - `refs/for/<branch>` (most commonly `refs/for/main`) is a branch that anyone +can push to that will create or update code reviews (called CLs for ChangeList) +for the commits pushed. + - `refs/changes/00/<change number>/<patchset>` is a branch that corresponds to +the commits that were pushed for codereview for "change number" at a certain +"patchset" (a new patchset is created each time you push to a CL). + +#### Gerrit's .gitcookies + +To push commits to Gerrit your `git` command needs to be authenticated. This is +done with `.gitcookies` that will make `git` send authentication information +when connecting to the remote. To get the `.gitcookies`, log-in to [Dawn's Gerrit](https://dawn-review.googlesource.com) +and browse to the [new-password](https://dawn.googlesource.com/new-password) +page that will give you shell/cmd commands to run to update `.gitcookie`. + +#### Set up the commit-msg hook + +Gerrit associates commits to CLs based on a `Change-Id:` tag in the commit +message. Each push with commits with a `Change-Id:` will update the +corresponding CL. + +To add the `commit-msg` hook that will automatically add a `Change-Id:` to your +commit messages, run the following command: + +``` +f=`git rev-parse --git-dir`/hooks/commit-msg ; mkdir -p $(dirname $f) ; curl -Lo $f https://gerrit-review.googlesource.com/tools/hooks/commit-msg ; chmod +x $f +``` + +Gerrit helpfully reminds you of that command if you forgot to set up the hook +before pushing commits. + +## The code review process + +All submissions, including submissions by project members, require review. + +### Discuss the change if needed + +Some changes are inherently risky, because they have long-term or architectural +consequences, contain a lot of unknowns or other reasons. When that's the case +it is better to discuss it on the [Dawn Matrix Channel](https://matrix.to/#/#webgpu-dawn:matrix.org) +or the [Dawn mailing-list](https://groups.google.com/g/dawn-graphics/members). + +### Pushing changes to code review + +Before pushing changes to code review, it is better to run `git cl presubmit` +that will check the formatting of files and other small things. + +Pushing commits is done with `git push origin HEAD:refs/for/main`. Which means +push to `origin` (i.e. Gerrit) the currently checkout out commit to the +`refs/for/main` magic branch that creates or updates CLs. + +In the terminal you will see a URL where code review for this CL will happen. +CLs start in the "Work In Progress" state. To start the code review proper, +click on "Start Review", add reviewers and click "Send and start review". If +you are unsure which reviewers to use, pick one of the reviewers in the +[OWNERS file](../OWNERS) who will review or triage the CL. + +When code review asks for changes in the commits, you can amend them any way +you want (small fixup commit and `git rebase -i` are crowd favorites) and run +the same `git push origin HEAD:refs/for/main` command. + +### Tracking issues + +We usually like to have commits associated with issues in [Dawn's issue tracker](https://bugs.chromium.org/p/dawn/issues/list) +so that commits for the issue can all be found on the same page. This is done +by adding a `Bug: dawn:<issue number>` tag at the end of the commit message. It +is also possible to reference Chromium or Tint issues with +`Bug: tint:<issue number>` or `Bug: chromium:<issue number>`. + +Some small fixes (like typo fixes, or some one-off maintenance) don't need a +tracking issue. When that's the case, it's good practice to call it out by +adding a `Bug: None` tag. + +It is possible to make issues fixed automatically when the CL is merged by +adding a `Fixed: <project>:<issue number>` tag in the commit message. + +### Iterating on code review + +Dawn follows the general [Google code review guidelines](https://google.github.io/eng-practices/review/). +Most Dawn changes need reviews from two Dawn committers. Reviewers will set the +"Code Review" CR+1 or CR+2 label once the change looks good to them (although +it could still have comments that need to be addressed first). When addressing +comments, please mark them as "Done" if you just address them, or start a +discussion until they are resolved. + +Once you are granted rights (you can ask on your first contribution), you can +add the "Commit Queue" CQ+1 label to run the automated tests for Dawn. Once the +CL has CR+2 you can then add the CQ+2 label to run the automated tests and +submit the commit if they pass. + +The "Auto Submit" AS+1 label can be used to make Gerrit automatically set the +CQ+2 label once the CR+2 label is added.
diff --git a/docs/dawn/debug_markers.md b/docs/dawn/debug_markers.md new file mode 100644 index 0000000..d7fd266 --- /dev/null +++ b/docs/dawn/debug_markers.md
@@ -0,0 +1,50 @@ +# Debug Markers + +Dawn provides debug tooling integration for each backend. + +Debugging markers are exposed through this API: +``` +partial GPUProgrammablePassEncoder { + void pushDebugGroup(const char * markerLabel); + void popDebugGroup(); + void insertDebugMarker(const char * markerLabel); +}; +``` + +These APIs will result in silent no-ops if they are used without setting up +the execution environment properly. Each backend has a specific process +for setting up this environment. + +## D3D12 + +Debug markers on D3D12 are implemented with the [PIX Event Runtime](https://blogs.msdn.microsoft.com/pix/winpixeventruntime/). + +To enable marker functionality, you must: +1. Click the download link on https://www.nuget.org/packages/WinPixEventRuntime +2. Rename the .nupkg file to a .zip extension, then extract its contents. +3. Copy `bin\WinPixEventRuntime.dll` into the same directory as `libdawn_native.dll`. +4. Launch your application. + +You may now call the debug marker APIs mentioned above and see them from your GPU debugging tool. When using your tool, it is supported to both launch your application with the debugger attached, or attach the debugger while your application is running. + +D3D12 debug markers have been tested with [Microsoft PIX](https://devblogs.microsoft.com/pix/) and [Intel Graphics Frame Analyzer](https://software.intel.com/en-us/gpa/graphics-frame-analyzer). + +Unfortunately, PIX's UI does does not lend itself to capturing single frame applications like tests. You must enable capture from within your application. To do this in Dawn tests, pass the --begin-capture-on-startup flag to dawn_end2end_tests.exe. + +## Vulkan + +Debug markers on Vulkan are implemented with [VK_EXT_debug_utils](https://www.khronos.org/registry/vulkan/specs/1.2-extensions/man/html/VK_EXT_debug_utils.html). + +To enable marker functionality, you must launch your application from your debugging tool. Attaching to an already running application is not supported. + +Vulkan markers have been tested with [RenderDoc](https://renderdoc.org/). + +## Metal + +Debug markers on Metal are used with the XCode debugger. + +To enable marker functionality, you must launch your application from XCode and use [GPU Frame Capture](https://developer.apple.com/documentation/metal/tools_profiling_and_debugging/metal_gpu_capture). + +## OpenGL + +Debug markers on OpenGL are not implemented and will result in a silent no-op. This is due to low adoption of the GL_EXT_debug_marker extension in Linux device drivers.
diff --git a/docs/dawn/debugging.md b/docs/dawn/debugging.md new file mode 100644 index 0000000..740d0b2 --- /dev/null +++ b/docs/dawn/debugging.md
@@ -0,0 +1,3 @@ +# Debugging Dawn + +(TODO)
diff --git a/docs/dawn/device_facilities.md b/docs/dawn/device_facilities.md new file mode 100644 index 0000000..b625558 --- /dev/null +++ b/docs/dawn/device_facilities.md
@@ -0,0 +1,106 @@ +# Devices + +In Dawn the `Device` is a "god object" that contains a lot of facilities useful for the whole object graph that descends from it. +There a number of facilities common to all backends that live in the frontend and backend-specific facilities. +Example of frontend facilities are the management of content-less object caches, or the toggle management. +Example of backend facilities are GPU memory allocators or the backing API function pointer table. + +## Frontend facilities + +### Error Handling + +Dawn (dawn_native) uses the [Error.h](../src/dawn/native/Error.h) error handling to robustly handle errors. +With `DAWN_TRY` errors bubble up all the way to, and are "consumed" by the entry-point that was called by the application. +Error consumption uses `Device::ConsumeError` that expose them via the WebGPU "error scopes" and can also influence the device lifecycle by notifying of a device loss, or triggering a device loss.. + +See [Error.h](../src/dawn/native/Error.h) for more information about using errors. + +### Device Lifecycle + +The device lifecycle is a bit more complicated than other objects in Dawn for multiple reasons: + + - The device initialization creates facilities in both the backend and the frontend, which can fail. + When a device fails to initialize, it should still be possible to destroy it without crashing. + - Execution of commands on the GPU must be finished before the device can be destroyed (because there's noone to "DeleteWhenUnused" the device). + - On creation a device might want to run some GPU commands (like initializing zero-buffers), which must be completed before it is destroyed. + - A device can become "disconnected" when a TDR or hot-unplug happens. + In this case, destruction of the device doesn't need to wait on GPU commands to finish because they just disappeared. + +There is a state machine `State` defined in [Device.h](../src/dawn/native/Device.h) that controls all of the above. +The most common state is `Alive` when there are potentially GPU commands executing. + +Initialization of a device looks like the following: + + - `DeviceBase::DeviceBase` is called and does mostly nothing except setting `State` to `BeingCreated` (and initial toggles). + - `backend::Device::Initialize` creates things like the underlying device and other stuff that doesn't run GPU commands. + - It then calls `DeviceBase::Initialize` that enables the `DeviceBase` facilities and sets the `State` to `Alive`. + - Optionally, `backend::Device::Initialize` can now enqueue GPU commands for its initialization. + - The device is ready to be used by the application! + +While it is `Alive` the device can notify it has been disconnected by the backend, in which case it jumps directly to the `Disconnected` state. +Internal errors, or a call to `LoseForTesting` can also disconnect the device, but in the underlying API commands are still running, so the frontend will finish all commands (with `WaitForIdleForDesctruction`) and prevent any new commands to be enqueued (by setting state to `BeingDisconnected`). +After this the device is set in the `Disconnected` state. +If an `Alive` device is destroyed, then a similar flow to `LoseForTesting happens`. + +All this ensures that during destruction or forceful disconnect of the device, it properly gets to the `Disconnected` state with no commands executing on the GPU. +After disconnecting, frontend will call `backend::Device::DestroyImpl` so that it can properly free driver objects. + +### Toggles + +Toggles are booleans that control code paths inside of Dawn, like lazy-clearing resources or using D3D12 render passes. +They aren't just booleans close to the code path they control, because embedders of Dawn like Chromium want to be able to surface what toggles are used by a device (like in about:gpu). + +Toogles are to be used for any optional code path in Dawn, including: + + - Workarounds for driver bugs. + - Disabling select parts of the validation or robustness. + - Enabling limitations that help with testing. + - Using more advanced or optional backend API features. + +Toggles can be queried using `DeviceBase::IsToggleEnabled`: +``` +bool useRenderPass = device->IsToggleEnabled(Toggle::UseD3D12RenderPass); +``` + +Toggles are defined in a table in [Toggles.cpp](../src/dawn/native/Toggles.cpp) that also includes their name and description. +The name can be used to force enabling of a toggle or, at the contrary, force the disabling of a toogle. +This is particularly useful in tests so that the two sides of a code path can be tested (for example using D3D12 render passes and not). + +Here's an example of a test that is run in the D3D12 backend both with the D3D12 render passes forcibly disabled, and in the default configuration. +``` +DAWN_INSTANTIATE_TEST(RenderPassTest, + D3D12Backend(), + D3D12Backend({}, {"use_d3d12_render_pass"})); +// The {} is the list of force enabled toggles, {"..."} the force disabled ones. +``` + +The initialization order of toggles looks as follows: + + - The toggles overrides from the device descriptor are applied. + - The frontend device default toggles are applied (unless already overriden). + - The backend device default toggles are applied (unless already overriden) using `DeviceBase::SetToggle` + - The backend device can ignore overriden toggles if it can't support them by using `DeviceBase::ForceSetToggle` + +Forcing toggles should only be done when there is no "safe" option for the toggle. +This is to avoid crashes during testing when the tests try to use both sides of a toggle. +For toggles that are safe to enable, like workarounds, the tests can run against the base configuration and with the toggle enabled. +For toggles that are safe to disable, like using more advanced backing API features, the tests can run against the base configuation and with the toggle disabled. + +### Immutable object caches + +A number of WebGPU objects are immutable once created, and can be expensive to create, like pipelines. +`DeviceBase` contains caches for these objects so that they are free to create the second time. +This is also useful to be able to compare objects by pointers like `BindGroupLayouts` since two BGLs would be equal iff they are the same object. + +### Format Tables + +The frontend has a `Format` structure that represent all the information that are known about a particular WebGPU format for this Device based on the enabled features. +Formats are precomputed at device initialization and can be queried from a WebGPU format either assuming the format is a valid enum, or in a safe manner that doesn't do this assumption. +A reference to these formats can be stored persistently as they have the same lifetime as the `Device`. + +Formats also have an "index" so that backends can create parallel tables for internal informations about formats, like what they translate to in the backing API. + +### Object factory + +Like WebGPU's device object, `DeviceBase` is an factory with methods to create all kinds of other WebGPU objects. +WebGPU has some objects that aren't created from the device, like the texture view, but in Dawn these creations also go through `DeviceBase` so that there is a single factory for each backend.
diff --git a/docs/dawn/errors.md b/docs/dawn/errors.md new file mode 100644 index 0000000..9f60ba7 --- /dev/null +++ b/docs/dawn/errors.md
@@ -0,0 +1,118 @@ +# Dawn Errors + +Dawn produces errors for several reasons. The most common is validation errors, indicating that a +given descriptor, configuration, state, or action is not valid according to the WebGPU spec. Errors +can also be produced during exceptional circumstances such as the system running out of GPU memory +or the device being lost. + +The messages attached to these errors will frequently be one of the primary tools developers use to +debug problems their applications, so it is important that the messages Dawn returns are useful. + +Following the guidelines in document will help ensure that Dawn's errors are clear, informative, and +consistent. + +## Returning Errors + +Since errors are expected to be an exceptional case, it's important that code that produces an error +doesn't adversely impact the performance of the error-free path. The best way to ensure that is to +make sure that all errors are returned from within an `if` statement that uses the `DAWN_UNLIKELY()` +macro to indicate that the expression is not expected to evaluate to true. For example: + +```C++ +if (DAWN_UNLIKELY(offset > buffer.size)) { + return DAWN_VALIDATION_ERROR("Offset (%u) is larger than the size (%u) of %s." + offset, buffer.size, buffer); +} +``` + +To simplify producing validation errors, it's strongly suggested that the `DAWN_INVALID_IF()` macro +is used, which will wrap the expression in the `DAWN_UNLIKELY()` macro for you: + +```C++ +// This is equivalent to the previous example. +DAWN_INVALID_IF(offset > buffer.size, "Offset (%u) is larger than the size (%u) of %s." + offset, buffer.size, buffer); +``` + +// TODO: Cover `MaybeError`, `ResultOrError<T>`, `DAWN_TRY(_ASSIGN)`, `DAWN_TRY_CONTEXT`, etc... + +## Error message formatting + +Errors returned from `DAWN_INVALID_IF()` or `DAWN_VALIDATION_ERROR()` should follow these guidelines: + +**Write error messages as complete sentences. (First word capitalized, ends with a period, etc.)** + * Example: `Command encoding has already finished.` + * Instead of: `encoder finished` + +**Error messages should be in the present tense.** + * Example: `Buffer is not large enough...` + * Instead of: `Buffer was not large enough...` + +**When possible any values mentioned should be immediately followed in parentheses by the given value.** + * Example: `("Array stride (%u) is not...", stride)` + * Output: `Array stride (16) is not...` + +**When possible any object or descriptors should be represented by the object formatted as a string.** + * Example: `("The %s size (%s) is...", buffer, buffer.size)` + * Output: `The [Buffer] size (512) is...` or `The [Buffer "Label"] size (512) is...` + +**Enum and bitmask values should be formatted as strings rather than integers or hex values.** + * Example: `("The %s format (%s) is...", texture, texture.format)` + * Output: `The [Texture "Label"] format (TextureFormat::RGBA8Unorm) is...` + +**When possible state both the given value and the expected value or limit.** + * Example: `("Offset (%u) is larger than the size (%u) of %s.", offset, buffer.size, buffer)` + * Output: `Offset (256) is larger than the size (144) of [Buffer "Label"].` + +**State errors in terms of what failed, rather than how to satisfy the rule.** + * Example: `Binding size (3) is less than the minimum binding size (32).` + * Instead of: `Binding size (3) must not be less than the minimum binding size (32).` + +**Don't repeat information given in context.** + * See next section for details + +## Error Context + +When calling functions that perform validation consider if calling `DAWN_TRY_CONTEXT()` rather than +`DAWN_TRY()` is appropriate. Context messages, when provided, will be appended to any validation +errors as a type of human readable "callstack". An error with context messages appears will be +formatted as: + +``` +<Primary error message.> + - While <context message lvl 2> + - While <context message lvl 1> + - While <context message lvl 0> +``` + +For example, if a validation error occurs while validating the creation of a BindGroup, the message +may be: + +``` +Binding size (256) is larger than the size (80) of [Buffer "View Matrix"]. + - While validating entries[1] as a Buffer + - While validating [BindGroupDescriptor "Frame Bind Group"] against [BindGroupLayout] + - While calling CreateBindGroup +``` + +// TODO: Guidelines about when to include context + +## Context message formatting + +Context messages should follow these guidelines: + +**Begin with the action being taken, starting with a lower case. `- While ` will be appended by Dawn.** + * Example: `("validating primitive state")` + * Output: `- While validating primitive state` + +**When looping through arrays, indicate the array name and index.** + * Example: `("validating buffers[%u]", i)` + * Output: `- While validating buffers[2]` + +**Indicate which descriptors or objects are being examined in as high-level a context as possible.** + * Example: `("validating % against %", descriptor, descriptor->layout)` + * Output: `- While validating [BindGroupDescriptor "Label"] against [BindGroupLayout]` + +**When possible, indicate the function call being made as the top-level context, as well as the parameters passed.** + * Example: `("calling %s.CreatePipelineLayout(%s).", this, descriptor)` + * Output: `- While calling [Device].CreatePipelineLayout([PipelineLayoutDescriptor]).`
diff --git a/docs/dawn/external_resources.md b/docs/dawn/external_resources.md new file mode 100644 index 0000000..91f7a60 --- /dev/null +++ b/docs/dawn/external_resources.md
@@ -0,0 +1,6 @@ +# Dawn's external resources links + +## Design Docs + +- Buffer mapping dawn wire memory transfer interface design + - https://docs.google.com/document/d/1JOhCpmJ_JyNZJtX6MVbSgxjtG1TdKORdeOGYtSfVYjk/edit?usp=sharing&resourcekey=0-1bFi47mR1jkBLdRFxcTVig \ No newline at end of file
diff --git a/docs/dawn/features/dawn_internal_usages.md b/docs/dawn/features/dawn_internal_usages.md new file mode 100644 index 0000000..521a3ac --- /dev/null +++ b/docs/dawn/features/dawn_internal_usages.md
@@ -0,0 +1,38 @@ +# Dawn Internal Usages + +The `dawn-internal-usages` feature allows adding additional usage which affects how a texture is allocated, but does not affect normal frontend validation. + +Adds `WGPUDawnTextureInternalUsageDescriptor` for specifying additional internal usages to create a texture with. + +Example Usage: +``` +wgpu::DawnTextureInternalUsageDescriptor internalDesc = {}; +internalDesc.internalUsage = wgpu::TextureUsage::CopySrc; + +wgpu::TextureDescriptor desc = {}; +// set properties of desc. +desc.nextInChain = &internalDesc; + +device.CreateTexture(&desc); +``` + +Adds `WGPUDawnEncoderInternalUsageDescriptor` which may be chained on `WGPUCommandEncoderDescriptor`. Setting `WGPUDawnEncoderInternalUsageDescriptor::useInternalUsages` to `true` means that internal resource usages will be visible during validation. ex.) A texture that has `WGPUTextureUsage_CopySrc` in `WGPUDawnEncoderInternalUsageDescriptor::internalUsage`, but not in `WGPUTextureDescriptor::usage` may be used as the source of a copy command. + + +Example Usage: +``` +wgpu::DawnEncoderInternalUsageDescriptor internalEncoderDesc = { true }; +wgpu::CommandEncoderDescriptor encoderDesc = {}; +encoderDesc.nextInChain = &internalEncoderDesc; + +wgpu::CommandEncoder encoder = device.CreateCommandEncoder(&encoderDesc); + +// This will be valid +wgpu::ImageCopyTexture src = {}; +src.texture = texture; +encoder.CopyTextureToBuffer(&src, ...); +``` + +One use case for this is so that Chromium can use an internal copyTextureToTexture command to implement copies from a WebGPU texture-backed canvas to other Web platform primitives when the swapchain texture was not explicitly created with CopySrc usage in Javascript. + +Note: copyTextureToTextureInternal will be removed in favor of `WGPUDawnEncoderInternalUsageDescriptor`.
diff --git a/docs/dawn/features/dawn_native.md b/docs/dawn/features/dawn_native.md new file mode 100644 index 0000000..0b4664b --- /dev/null +++ b/docs/dawn/features/dawn_native.md
@@ -0,0 +1,15 @@ +# Dawn Native + +The `dawn-native` feature enables additional functionality that is supported only +when the WebGPU implementation is `dawn_native`. + +Additional functionality: + - `wgpu::DawnTogglesDeviceDescriptor` may be chained on `wgpu::DeviceDescriptor` on device creation to enable Dawn-specific toggles on the device. + + - `wgpu::DawnCacheDeviceDescriptor` may be chained on `wgpu::DeviceDescriptor` on device creation to enable cache options such as isolation keys. + + - Synchronous `adapter.CreateDevice(const wgpu::DeviceDescriptor*)` may be called. + +Notes: + - Enabling this feature in the `wgpu::DeviceDescriptor` does nothing, but +its presence in the Adapter's set of supported features means that the additional functionality is supported.
diff --git a/docs/dawn/fuzzing.md b/docs/dawn/fuzzing.md new file mode 100644 index 0000000..8521901 --- /dev/null +++ b/docs/dawn/fuzzing.md
@@ -0,0 +1,18 @@ +# Fuzzing Dawn + +## `dawn_wire_server_and_frontend_fuzzer` + +The `dawn_wire_server_and_frontend_fuzzer` sets up Dawn using the Null backend, and passes inputs to the wire server. This fuzzes the `dawn_wire` deserialization, as well as Dawn's frontend validation. + +## `dawn_wire_server_and_vulkan_backend_fuzzer` + +The `dawn_wire_server_and_vulkan_backend_fuzzer` is like `dawn_wire_server_and_frontend_fuzzer` but it runs using a Vulkan CPU backend such as Swiftshader. This fuzzer supports error injection by using the first bytes of the fuzzing input as a Vulkan call index for which to mock a failure. + +## Automatic Seed Corpus Generation + +Using a seed corpus significantly improves the efficiency of fuzzing. Dawn's fuzzers use interesting testcases discovered in previous fuzzing runs to seed future runs. Fuzzing can be further improved by using Dawn tests as a example of API usage which allows the fuzzer to quickly discover and use new API entrypoints and usage patterns. + +Dawn has a CI builder [cron-linux-clang-rel-x64](https://ci.chromium.org/p/dawn/builders/ci/cron-linux-clang-rel-x64) which runs on a periodic schedule. This bot runs the `dawn_end2end_tests` and `dawn_unittests` using the wire and writes out traces of the commands. This can manually be done by running: `<test_binary> --use-wire --wire-trace-dir=tmp_dir`. The output directory will contain one trace for each test, where the traces are prepended with `0xFFFFFFFFFFFFFFFF`. The header is the callsite index at which the error injector should inject an error. If the fuzzer doesn't support error injection it will skip the header. [cron-linux-clang-rel-x64] then hashes the output files to produce unique names and uploads them to the fuzzer corpus directories. +Please see the `dawn.py`[https://source.chromium.org/chromium/chromium/tools/build/+/master:recipes/recipes/dawn.py] recipe for specific details. + +Regenerating the seed corpus keeps it up to date when Dawn's API or wire protocol changes. \ No newline at end of file
diff --git a/docs/dawn/infra.md b/docs/dawn/infra.md new file mode 100644 index 0000000..605d9ca --- /dev/null +++ b/docs/dawn/infra.md
@@ -0,0 +1,92 @@ +# Dawn's Continuous Testing Infrastructure + +Dawn uses Chromium's continuous integration (CI) infrastructure to continually run tests on changes to Dawn and provide a way for developers to run tests against their changes before submitting. CI bots continually build and run tests for every new change, and Try bots build and run developers' pending changes before submission. Dawn uses two different build recipes. There is a Dawn build recipe which checks out Dawn standalone, compiles, and runs the `dawn_unittests`. And, there is the Chromium build recipe which checks out Dawn inside a Chromium checkout. Inside a Chromium checkout, there is more infrastructure available for triggering `dawn_end2end_tests` that run on real GPU hardware, and we are able to run Chromium integration tests as well as tests for WebGPU. + + - [Dawn CI Builders](https://ci.chromium.org/p/dawn/g/ci/builders) + - [Dawn Try Builders](https://ci.chromium.org/p/dawn/g/try/builders) + - [chromium.dawn Waterfall](https://ci.chromium.org/p/chromium/g/chromium.dawn/console) + +For additional information on GPU testing in Chromium, please see [[chromium/src]//docs/gpu/gpu_testing_bot_details.md](https://chromium.googlesource.com/chromium/src.git/+/master/docs/gpu/gpu_testing_bot_details.md). + +## Dawn CI/Try Builders +Dawn builders are specified in [[dawn]//infra/config/global/cr-buildbucket.cfg](../infra/config/global/cr-buildbucket.cfg). This file contains a few mixins such as `clang`, `no_clang`, `x64`, `x86`, `debug`, `release` which are used to specify the bot dimensions and build properties (builder_mixins.recipe.properties). At the time of writing, we have the following builders: + - [dawn/try/presubmit](https://ci.chromium.org/p/dawn/builders/try/presubmit) + - [dawn/try/linux-clang-dbg-x64](https://ci.chromium.org/p/dawn/builders/try/linux-clang-dbg-x64) + - [dawn/try/linux-clang-dbg-x86](https://ci.chromium.org/p/dawn/builders/try/linux-clang-dbg-x86) + - [dawn/try/linux-clang-rel-x64](https://ci.chromium.org/p/dawn/builders/try/linux-clang-rel-x64) + - [dawn/try/mac-dbg](https://ci.chromium.org/p/dawn/builders/try/mac-dbg) + - [dawn/try/mac-rel](https://ci.chromium.org/p/dawn/builders/try/mac-rel) + - [dawn/try/win-clang-dbg-x86](https://ci.chromium.org/p/dawn/builders/try/win-clang-dbg-x86) + - [dawn/try/win-clang-rel-x64](https://ci.chromium.org/p/dawn/builders/try/win-clang-rel-x64) + - [dawn/try/win-msvc-dbg-x86](https://ci.chromium.org/p/dawn/builders/try/win-msvc-dbg-x86) + - [dawn/try/win-msvc-rel-x64](https://ci.chromium.org/p/dawn/builders/try/win-msvc-rel-x64) + +There are additional `chromium/try` builders, but those are described later in this document. + +These bots are defined in both buckets luci.dawn.ci and luci.dawn.try, though their ACL permissions differ. luci.dawn.ci bots will be scheduled regularly based on [[dawn]//infra/config/global/luci-scheduler.cfg](../infra/config/global/luci-scheduler.cfg). luci.dawn.try bots will be triggered on the CQ based on [[dawn]//infra/config/global/commit-queue.cfg](../infra/config/global/commit-queue.cfg). + +One particular note is `buckets.swarming.builder_defaults.recipe.name: "dawn"` which specifies these use the [`dawn.py`](https://source.chromium.org/search/?q=file:recipes/dawn.py) build recipe. + +Build status for both CI and Try builders can be seen at this [console](https://ci.chromium.org/p/dawn) which is generated from [[dawn]//infra/config/global/luci-milo.cfg](../infra/config/global/luci-milo.cfg). + +## Dawn Build Recipe +The [`dawn.py`](https://cs.chromium.org/search/?q=file:recipes/dawn.py) build recipe is simple and intended only for testing compilation and unit tests. It does the following: + 1. Checks out Dawn standalone and dependencies + 2. Builds based on the `builder_mixins.recipe.properties` coming from the builder config in [[dawn]//infra/config/global/cr-buildbucket.cfg](../infra/config/global/cr-buildbucket.cfg). + 3. Runs the `dawn_unittests` on that same bot. + +## Dawn Chromium-Based CI Waterfall Bots +The [`chromium.dawn`](https://ci.chromium.org/p/chromium/g/chromium.dawn/console) waterfall consists of the bots specified in the `chromium.dawn` section of [[chromium/src]//testing/buildbot/waterfalls.pyl](https://source.chromium.org/search/?q=file:waterfalls.pyl%20chromium.dawn). Bots named "Builder" are responsible for building top-of-tree Dawn, whereas bots named "DEPS Builder" are responsible for building Chromium's DEPS version of Dawn. + +The other bots, such as "Dawn Linux x64 DEPS Release (Intel HD 630)" receive the build products from the Builders and are responsible for running tests. The Tester configuration may specify `mixins` from [[chromium/src]//testing/buildbot/mixins.pyl](https://source.chromium.org/search/?q=file:buildbot/mixins.pyl) which help specify bot test dimensions like OS version and GPU vendor. The Tester configuration also specifies `test_suites` from [[chromium/src]//testing/buildbot/test_suites.pyl](https://source.chromium.org/search/?q=file:buildbot/test_suites.pyl%20dawn_end2end_tests) which declare the tests are arguments passed to tests that should be run on the bot. + +The Builder and Tester bots are additionally configured at [[chromium/tools/build]//scripts/slave/recipe_modules/chromium_tests/chromium_dawn.py](https://source.chromium.org/search?q=file:chromium_dawn.py) which defines the bot specs for the builders and testers. Some things to note: + - The Tester bots set `parent_buildername` to be their respective Builder bot. + - The non DEPS bots use the `dawn_top_of_tree` config. + - The bots apply the `mb` config which references [[chromium]//tools/mb/mb_config.pyl](https://source.chromium.org/search?q=file:mb_config.pyl%20%22Dawn%20Linux%20x64%20Builder%22) and [[chromium]//tools/mb/mb_config_buckets.pyl](https://source.chromium.org/search?q=file:mb_config_buckets.pyl%20%22Dawn%20Linux%20x64%20Builder%22). Various mixins there specify build dimensions like debug, release, gn args, x86, x64, etc. + +Finally, builds on these waterfall bots are automatically scheduled based on the configuration in [[chromium/src]//infra/config/buckets/ci.star](https://source.chromium.org/search?q=file:ci.star%20%22Dawn%20Linux%20x64%20Builder%22). Note that the Tester bots are `triggered_by` the Builder bots. + +## Dawn Chromium-Based Tryjobs +[[dawn]//infra/config/global/commit-queue.cfg](../infra/config/global/commit-queue.cfg) declares additional tryjob builders which are defined in the Chromium workspace. The reason for this separation is that jobs sent to these bots rely on the Chromium infrastructure for doing builds and triggering jobs on bots with GPU hardware in swarming. + +At the time of writing, the bots for Dawn CLs are: + - [chromium/try/linux-dawn-rel](https://ci.chromium.org/p/chromium/builders/try/linux-dawn-rel) + - [chromium/try/mac-dawn-rel](https://ci.chromium.org/p/chromium/builders/try/mac-dawn-rel) + - [chromium/try/win-dawn-rel](https://ci.chromium.org/p/chromium/builders/try/win-dawn-rel) + +And for Chromium CLs: + - [chromium/try/dawn-linux-x64-deps-rel](https://ci.chromium.org/p/chromium/builders/try/dawn-linux-x64-deps-rel) + - [chromium/try/dawn-mac-x64-deps-rel](https://ci.chromium.org/p/chromium/builders/try/dawn-mac-x64-deps-rel) + - [chromium/try/dawn-win10-x86-deps-rel](https://ci.chromium.org/p/chromium/builders/try/dawn-win10-x86-deps-rel) + - [chromium/try/dawn-win10-x64-deps-rel](https://ci.chromium.org/p/chromium/builders/try/dawn-win10-x64-deps-rel) + + The configuration for these bots is generated from [[chromium]//infra/config/buckets/try.star](https://source.chromium.org/search/?q=file:try.star%20linux-dawn-rel) which uses the [`chromium_dawn_builder`](https://source.chromium.org/search/?q=%22def%20chromium_dawn_builder%22) function which sets the `mastername` to `tryserver.chromium.dawn`. + +[[chromium/tools/build]//scripts/slave/recipe_modules/chromium_tests/trybots.py](https://source.chromium.org/search/?q=file:trybots.py%20tryserver.chromium.dawn) specifies `tryserver.chromium.dawn` bots as mirroring bots from the `chromium.dawn` waterfall. Example: +``` +'dawn-linux-x64-deps-rel': { + 'bot_ids': [ + { + 'mastername': 'chromium.dawn', + 'buildername': 'Dawn Linux x64 DEPS Builder', + 'tester': 'Dawn Linux x64 DEPS Release (Intel HD 630)', + }, + { + 'mastername': 'chromium.dawn', + 'buildername': 'Dawn Linux x64 DEPS Builder', + 'tester': 'Dawn Linux x64 DEPS Release (NVIDIA)', + }, + ], +}, +``` + +Using the [[chromium/tools/build]//scripts/slave/recipes/chromium_trybot.py](https://source.chromium.org/search/?q=file:chromium_trybot.py) recipe, these trybots will cherry-pick a CL and run the same tests as the CI waterfall bots. The trybots also pick up some build mixins from [[chromium]//tools/mb/mb_config.pyl](https://source.chromium.org/search?q=file:mb_config.pyl%20dawn-linux-x64-deps-rel). + +## Bot Allocation + +Bots are physically allocated based on the configuration in [[chromium/infradata/config]//configs/chromium-swarm/starlark/bots/dawn.star](https://chrome-internal.googlesource.com/infradata/config/+/refs/heads/master/configs/chromium-swarm/starlark/bots/dawn.star) (Google only). + +`dawn/try` bots are using builderless configurations which means they use builderless GCEs shared with Chromium bots and don't need explicit allocation. + +`chromium/try` bots are still explicitly allocated with a number of GCE instances and lifetime of the build cache. All of the GCE bots should eventually be migrated to builderless (crbug.com/dawn/328). Mac bots such as `dawn-mac-x64-deps-rel`, `mac-dawn-rel`, `Dawn Mac x64 Builder`, and `Dawn Mac x64 DEPS Builder` point to specific ranges of machines that have been reserved by the infrastructure team.
diff --git a/docs/dawn/overview.md b/docs/dawn/overview.md new file mode 100644 index 0000000..acb3848 --- /dev/null +++ b/docs/dawn/overview.md
@@ -0,0 +1,54 @@ +# Dawn repository overview + +This repository contains the implementation of Dawn, which is itself composed of two main libraries (dawn_native and dawn_wire), along with support libraries, tests, and samples. Dawn makes heavy use of code-generation based on the `dawn.json` file that describes the native WebGPU API. It is used to generate the API headers, C++ wrapper, parts of the client-server implementation, and more! + +## Directory structure + +- [`dawn.json`](../dawn.json): contains a description of the native WebGPU in JSON form. It is the data model that's used by the code generators. +- [`dawn_wire.json`](../dawn_wire.json): contains additional information used to generate `dawn_wire` files, such as commands in addition to regular WebGPU commands. +- [`examples`](../examples): a small collection of samples using the native WebGPU API. They were mostly used when bringing up Dawn for the first time, and to test the `WGPUSwapChain` object. +- [`generator`](../generator): directory containg the code generators and their templates. Generators are based on Jinja2 and parse data-models from JSON files. + - [`dawn_json_generator.py`](../generator/dawn_json_generator.py): the main code generator that outputs the WebGPU headers, C++ wrapper, client-server implementation, etc. + - [`templates`](../generator/templates): Jinja2 templates for the generator, with subdirectories for groups of templates that are all used in the same library. +- [`infra`](../infra): configuration file for the commit-queue infrastructure. +- [`scripts`](../scripts): contains a grab-bag of files that are used for building Dawn, in testing, etc. +- [`src`](../src): + - [`dawn`](../src/dawn): root directory for Dawn code + - [`common`](../src/dawn/common): helper code that is allowed to be used by Dawn's core libraries, `dawn_native` and `dawn_wire`. Also allowed for use in all other Dawn targets. + - [`fuzzers`](../src/dawn/fuzzers): various fuzzers for Dawn that are running in [Clusterfuzz](https://google.github.io/clusterfuzz/). + - [`native`](../src/dawn/native): code for the implementation of WebGPU on top of graphics APIs. Files in this folder are the "frontend" while subdirectories are "backends". + - `<backend>`: code for the implementation of the backend on a specific graphics API, for example `d3d12`, `metal` or `vulkan`. + - [`tests`](../src/dawn/tests): + - [`end2end`](../src/dawn/tests/end2end): tests for the execution of the WebGPU API and require a GPU to run. + - [`perf_tests`](../src/dawn/tests/perf_tests): benchmarks for various aspects of Dawn. + - [`unittests`](../src/dawn/tests/unittests): code unittests of internal classes, but also by extension WebGPU API tests that don't require a GPU to run. + - [`validation`](../src/dawn/tests/unittests/validation): WebGPU validation tests not using the GPU (frontend tests) + - [`white_box`](../src/dawn/tests/white_box): tests using the GPU that need to access the internals of `dawn_native` or `dawn_wire`. + - [`wire`](../src/dawn/wire): code for an implementation of WebGPU as a client-server architecture. + - [`utils`](../src/dawn/utils): helper code to use Dawn used by tests and samples but disallowed for `dawn_native` and `dawn_wire`. + - [`platform`](../src/dawn/platform): definition of interfaces for dependency injection in `dawn_native` or `dawn_wire`. + - [`include`](../src/include): public headers with subdirectories for each library. Note that some headers are auto-generated and not present directly in the directory. +- [`third_party`](../third_party): directory where dependencies live as well as their buildfiles. + +## Dawn Native (`dawn_native`) + +The largest library in Dawn is `dawn_native` which implements the WebGPU API by translating to native graphics APIs such as D3D12, Metal or Vulkan. It is composed of a frontend that does all the state-tracking and validation, and backends that do the actual translation to the native graphics APIs. + +`dawn_native` hosts the [spirv-val](https://github.com/KhronosGroup/SPIRV-Tools) for validation of SPIR-V shaders and uses [Tint](https://dawn.googlesource.com/tint/) shader translator to convert WGSL shaders to an equivalent shader for use in the native graphics API (HLSL for D3D12, MSL for Metal or Vulkan SPIR-V for Vulkan). + +## Dawn Wire (`dawn_wire`) + +A second library that implements both a client that takes WebGPU commands and serializes them into a buffer, and a server that deserializes commands from a buffer, validates they are well-formed and calls the relevant WebGPU commands. Some server to client communication also happens so the API's callbacks work properly. + +Note that `dawn_wire` is meant to do as little state-tracking as possible so that the client can be lean and defer most of the heavy processing to the server side where the server calls into `dawn_native`. + +## Dawn Proc (`dawn_proc`) + +Normally libraries implementing `webgpu.h` should implement function like `wgpuDeviceCreateBuffer` but instead `dawn_native` and `dawn_wire` implement the `dawnProcTable` which is a structure containing all the WebGPU functions Dawn implements. Then a `dawn_proc` library contains a static version of this `dawnProcTable` and for example forwards `wgpuDeviceCreateBuffer` to the `procTable.deviceCreateBuffer` function pointer. This is useful in two ways: + + - It allows deciding at runtime whether to use `dawn_native` and `dawn_wire`, which is useful to test boths paths with the same binary in our infrastructure. + - It avoids applications that know they will only use Dawn to query all entrypoints at once instead of using `wgpuGetProcAddress` repeatedly. + +## Code generation + +When the WebGPU API evolves, a lot of places in Dawn have to be updated, so to reduce efforts, Dawn relies heavily on code generation for things like headers, proc tables and de/serialization. For more information, see [codegen.md](codegen.md).
diff --git a/docs/dawn/testing.md b/docs/dawn/testing.md new file mode 100644 index 0000000..749736b --- /dev/null +++ b/docs/dawn/testing.md
@@ -0,0 +1,69 @@ +# Testing Dawn + +(TODO) + +## Dawn Perf Tests + +For benchmarking with `dawn_perf_tests`, it's best to build inside a Chromium checkout using the following GN args: +``` +is_official_build = true # Enables highest optimization level, using LTO on some platforms +use_dawn = true # Required to build Dawn +use_cfi_icall=false # Required because Dawn dynamically loads function pointers, and we don't sanitize them yet. +``` + +A Chromium checkout is required for the highest optimization flags. It is possible to build and run `dawn_perf_tests` from a standalone Dawn checkout as well, only using GN arg `is_debug=false`. For more information on building, please see [building.md](./building.md). + +### Terminology + + - Iteration: The unit of work being measured. It could be a frame, a draw call, a data upload, a computation, etc. `dawn_perf_tests` metrics are reported as time per iteration. + - Step: A group of Iterations run together. The number of `iterationsPerStep` is provided to the constructor of `DawnPerfTestBase`. + - Trial: A group of Steps run consecutively. `kNumTrials` are run for each test. A Step in a Trial is run repetitively for approximately `kCalibrationRunTimeSeconds`. Metrics are accumlated per-trial and reported as the total time divided by `numSteps * iterationsPerStep`. `maxStepsInFlight` is passed to the `DawnPerfTestsBase` constructor to limit the number of Steps pipelined. + +(See [`//src/dawn/tests/perf_tests/DawnPerfTest.h`](https://cs.chromium.org/chromium/src/third_party/dawn/src/dawn/tests/perf_tests/DawnPerfTest.h) for the values of the constants). + +### Metrics + +`dawn_perf_tests` measures the following metrics: + - `wall_time`: The time per iteration, including time waiting for the GPU between Steps in a Trial. + - `cpu_time`: The time per iteration, not including time waiting for the GPU between Steps in a Trial. + - `validation_time`: The time for CommandBuffer / RenderBundle validation. + - `recording_time`: The time to convert Dawn commands to native commands. + +Metrics are reported according to the format specified at +[[chromium]//build/scripts/slave/performance_log_processor.py](https://cs.chromium.org/chromium/build/scripts/slave/performance_log_processor.py) + +### Dumping Trace Files + +The test harness supports a `--trace-file=path/to/trace.json` argument where Dawn trace events can be dumped. The traces can be viewed in Chrome's `about://tracing` viewer. + +### Test Runner + +[`//scripts/perf_test_runner.py`](https://cs.chromium.org/chromium/src/third_party/dawn/scripts/perf_test_runner.py) may be run to continuously run a test and report mean times and variances. + +Currently the script looks in the `out/Release` build directory and measures the `wall_time` metric (hardcoded into the script). These should eventually become arguments. + +Example usage: + +``` +scripts/perf_test_runner.py DrawCallPerf.Run/Vulkan__e_skip_validation +``` + +### Tests + +**BufferUploadPerf** + +Tests repetitively uploading data to the GPU using either `WriteBuffer` or `CreateBuffer` with `mappedAtCreation = true`. + +**DrawCallPerf** + +DrawCallPerf tests drawing a simple triangle with many ways of encoding commands, +binding, and uploading data to the GPU. The rationale for this is the following: + - Static/Multiple/Dynamic vertex buffers: Tests switching buffer bindings. This has + a state tracking cost as well as a GPU driver cost. + - Static/Multiple/Dynamic bind groups: Same rationale as vertex buffers + - Static/Dynamic pipelines: In addition to a change to GPU state, changing the pipeline + layout incurs additional state tracking costs in Dawn. + - With/Without render bundles: All of the above can have lower validation costs if + precomputed in a render bundle. + - Static/Dynamic data: Updating data for each draw is a common use case. It also tests + the efficiency of resource transitions.
diff --git a/docs/imgs/README.md b/docs/imgs/README.md new file mode 100644 index 0000000..60f42f4 --- /dev/null +++ b/docs/imgs/README.md
@@ -0,0 +1 @@ +Dawn's logo and derivatives found in this folder are under the [Creative Commons Attribution 4.0 International (CC BY 4.0)](https://creativecommons.org/licenses/by/4.0/) license.
diff --git a/docs/imgs/dawn_logo.png b/docs/imgs/dawn_logo.png new file mode 100644 index 0000000..a7d40cb --- /dev/null +++ b/docs/imgs/dawn_logo.png Binary files differ
diff --git a/docs/imgs/dawn_logo.svg b/docs/imgs/dawn_logo.svg new file mode 100644 index 0000000..034aad2 --- /dev/null +++ b/docs/imgs/dawn_logo.svg
@@ -0,0 +1,75 @@ +<?xml version="1.0" encoding="UTF-8" standalone="no"?> +<svg + width="768" + height="768" + viewBox="0 0 768 768" + fill="none" + version="1.1" + id="svg47" + sodipodi:docname="dawn_logo.svg" + inkscape:version="1.1 (c68e22c387, 2021-05-23)" + xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape" + xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd" + xmlns="http://www.w3.org/2000/svg" + xmlns:svg="http://www.w3.org/2000/svg"> + <defs + id="defs51" /> + <sodipodi:namedview + id="namedview49" + pagecolor="#ffffff" + bordercolor="#666666" + borderopacity="1.0" + inkscape:pageshadow="2" + inkscape:pageopacity="0.0" + inkscape:pagecheckerboard="true" + showgrid="false" + inkscape:zoom="1.6830615" + inkscape:cx="187.15894" + inkscape:cy="384.41852" + inkscape:window-width="3840" + inkscape:window-height="2066" + inkscape:window-x="-11" + inkscape:window-y="-11" + inkscape:window-maximized="1" + inkscape:current-layer="svg47" /> + <circle + cx="309" + cy="214" + r="121" + fill="#FDE293" + id="circle33" /> + <path + fill-rule="evenodd" + clip-rule="evenodd" + d="M429.5 126L670.255 543L188.745 543L429.5 126Z" + fill="#005A9C" + id="path35" /> + <path + fill-rule="evenodd" + clip-rule="evenodd" + d="M429.5 543L550 335L309 335L429.5 543Z" + fill="#0066B0" + id="path37" /> + <path + fill-rule="evenodd" + clip-rule="evenodd" + d="M308.5 335L369 439L248 439L308.5 335Z" + fill="#0086E8" + id="path39" /> + <path + fill-rule="evenodd" + clip-rule="evenodd" + d="M308.5 543L369 439L248 439L308.5 543Z" + fill="#0093FF" + id="path41" /> + <path + fill-rule="evenodd" + clip-rule="evenodd" + d="M217.5 335L338 543L97 543L217.5 335Z" + fill="#0076CC" + id="path43" /> + <path + d="M210.52 682V578.896H241.624C252.376 578.896 261.64 581.104 269.416 585.52C277.192 589.84 283.192 595.888 287.416 603.664C291.64 611.44 293.752 620.368 293.752 630.448C293.752 640.528 291.64 649.456 287.416 657.232C283.192 664.912 277.192 670.96 269.416 675.376C261.64 679.792 252.376 682 241.624 682H210.52ZM222.76 670.336H241.624C249.688 670.336 256.696 668.8 262.648 665.728C268.6 662.56 273.208 658 276.472 652.048C279.736 646.096 281.368 638.896 281.368 630.448C281.368 622 279.736 614.8 276.472 608.848C273.208 602.896 268.6 598.384 262.648 595.312C256.696 592.144 249.688 590.56 241.624 590.56H222.76V670.336ZM332.794 684.304C327.322 684.304 322.522 683.248 318.394 681.136C314.266 679.024 311.002 676.144 308.602 672.496C306.298 668.752 305.146 664.528 305.146 659.824C305.146 654.448 306.538 649.936 309.322 646.288C312.106 642.544 315.85 639.76 320.554 637.936C325.258 636.016 330.442 635.056 336.106 635.056C341.002 635.056 345.322 635.584 349.066 636.64C352.81 637.696 355.498 638.752 357.13 639.808V635.344C357.13 629.776 355.162 625.36 351.226 622.096C347.29 618.832 342.49 617.2 336.826 617.2C332.794 617.2 329.002 618.112 325.45 619.936C321.994 621.664 319.258 624.112 317.242 627.28L308.026 620.368C310.906 616.048 314.842 612.64 319.834 610.144C324.922 607.552 330.586 606.256 336.826 606.256C346.81 606.256 354.634 608.896 360.298 614.176C365.962 619.456 368.794 626.56 368.794 635.488V682H357.13V671.488H356.554C354.634 674.752 351.61 677.728 347.482 680.416C343.354 683.008 338.458 684.304 332.794 684.304ZM333.946 673.504C338.17 673.504 342.01 672.448 345.466 670.336C349.018 668.224 351.85 665.392 353.962 661.84C356.074 658.288 357.13 654.4 357.13 650.176C354.922 648.64 352.138 647.392 348.778 646.432C345.514 645.472 341.914 644.992 337.978 644.992C330.97 644.992 325.834 646.432 322.57 649.312C319.306 652.192 317.674 655.744 317.674 659.968C317.674 664 319.21 667.264 322.282 669.76C325.354 672.256 329.242 673.504 333.946 673.504ZM399.968 682L376.352 608.56H389.024L406.448 666.592H406.592L425.168 608.56H437.696L456.272 666.448H456.416L473.84 608.56H486.224L462.464 682H450.08L431.072 623.392L412.208 682H399.968ZM496.353 682V608.56H508.017V619.36H508.593C510.513 615.808 513.633 612.736 517.953 610.144C522.369 607.552 527.169 606.256 532.353 606.256C541.377 606.256 548.145 608.896 552.657 614.176C557.265 619.36 559.569 626.272 559.569 634.912V682H547.329V636.784C547.329 629.68 545.601 624.688 542.145 621.808C538.785 618.832 534.417 617.344 529.041 617.344C525.009 617.344 521.457 618.496 518.385 620.8C515.313 623.008 512.913 625.888 511.185 629.44C509.457 632.992 508.593 636.736 508.593 640.672V682H496.353Z" + fill="black" + id="path45" /> +</svg>
diff --git a/docs/imgs/dawn_logo_black.png b/docs/imgs/dawn_logo_black.png new file mode 100644 index 0000000..3c74794 --- /dev/null +++ b/docs/imgs/dawn_logo_black.png Binary files differ
diff --git a/docs/imgs/dawn_logo_black.svg b/docs/imgs/dawn_logo_black.svg new file mode 100644 index 0000000..627bf3b --- /dev/null +++ b/docs/imgs/dawn_logo_black.svg
@@ -0,0 +1,87 @@ +<?xml version="1.0" encoding="UTF-8" standalone="no"?> +<svg + width="768" + height="768" + viewBox="0 0 768 768" + fill="none" + version="1.1" + id="svg47" + sodipodi:docname="dawn_logo_black.svg" + inkscape:version="1.1 (c4e8f9e, 2021-05-24)" + xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape" + xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd" + xmlns="http://www.w3.org/2000/svg" + xmlns:svg="http://www.w3.org/2000/svg"> + <defs + id="defs51" /> + <sodipodi:namedview + id="namedview49" + pagecolor="#ffffff" + bordercolor="#666666" + borderopacity="1.0" + inkscape:pageshadow="2" + inkscape:pageopacity="0" + inkscape:pagecheckerboard="true" + showgrid="false" + inkscape:zoom="0.96102398" + inkscape:cx="255.45668" + inkscape:cy="371.99904" + inkscape:window-width="1296" + inkscape:window-height="997" + inkscape:window-x="0" + inkscape:window-y="25" + inkscape:window-maximized="0" + inkscape:current-layer="svg47" + showguides="false" /> + <path + id="circle33" + style="fill:#808080" + d="m 306.7315,89 a 121,121 0 0 0 -121,121 121,121 0 0 0 120.8418,120.98438 L 411.40142,149.41797 A 121,121 0 0 0 306.7315,89 Z" /> + <path + fill-rule="evenodd" + clip-rule="evenodd" + d="M429.5 543L550 335L309 335L429.5 543Z" + fill="#0066B0" + id="path37" + style="fill:#000000" /> + <path + fill-rule="evenodd" + clip-rule="evenodd" + d="M 429.5,124 550.29135,332.50291 H 308.70865 Z" + fill="#0066b0" + id="path37-7" + style="fill:#000000;fill-opacity:1;stroke-width:1.00242" /> + <path + id="path37-7-5" + style="fill:#000000;fill-opacity:1;stroke-width:0.489674" + d="m 306.64855,336.55438 -44.67773,77.12109 14.32812,24.73242 h 89.35547 z" /> + <path + fill-rule="evenodd" + clip-rule="evenodd" + d="M 552,337 672.79135,545.50291 H 431.20865 Z" + fill="#0066b0" + id="path37-7-9" + style="fill:#000000;fill-opacity:1;stroke-width:1.00242" /> + <path + fill-rule="evenodd" + clip-rule="evenodd" + d="m 321.32659,515.63497 43.72296,-75.16014 h -87.44593 z" + fill="#0093ff" + id="path41" + style="stroke-width:0.722694;fill:#000000" /> + <path + fill-rule="evenodd" + clip-rule="evenodd" + d="m 214.90584,337 120.5,208 H 94.405844 Z" + fill="#0076cc" + id="path43" + style="fill:#000000" /> + <path + d="M 208.52,682 V 578.896 h 31.104 c 10.752,0 20.016,2.208 27.792,6.624 7.776,4.32 13.776,10.368 18,18.144 4.224,7.776 6.336,16.704 6.336,26.784 0,10.08 -2.112,19.008 -6.336,26.784 -4.224,7.68 -10.224,13.728 -18,18.144 -7.776,4.416 -17.04,6.624 -27.792,6.624 z m 12.24,-11.664 h 18.864 c 8.064,0 15.072,-1.536 21.024,-4.608 5.952,-3.168 10.56,-7.728 13.824,-13.68 3.264,-5.952 4.896,-13.152 4.896,-21.6 0,-8.448 -1.632,-15.648 -4.896,-21.6 -3.264,-5.952 -7.872,-10.464 -13.824,-13.536 -5.952,-3.168 -12.96,-4.752 -21.024,-4.752 H 220.76 Z m 110.034,13.968 c -5.472,0 -10.272,-1.056 -14.4,-3.168 -4.128,-2.112 -7.392,-4.992 -9.792,-8.64 -2.304,-3.744 -3.456,-7.968 -3.456,-12.672 0,-5.376 1.392,-9.888 4.176,-13.536 2.784,-3.744 6.528,-6.528 11.232,-8.352 4.704,-1.92 9.888,-2.88 15.552,-2.88 4.896,0 9.216,0.528 12.96,1.584 3.744,1.056 6.432,2.112 8.064,3.168 v -4.464 c 0,-5.568 -1.968,-9.984 -5.904,-13.248 -3.936,-3.264 -8.736,-4.896 -14.4,-4.896 -4.032,0 -7.824,0.912 -11.376,2.736 -3.456,1.728 -6.192,4.176 -8.208,7.344 l -9.216,-6.912 c 2.88,-4.32 6.816,-7.728 11.808,-10.224 5.088,-2.592 10.752,-3.888 16.992,-3.888 9.984,0 17.808,2.64 23.472,7.92 5.664,5.28 8.496,12.384 8.496,21.312 V 682 H 355.13 v -10.512 h -0.576 c -1.92,3.264 -4.944,6.24 -9.072,8.928 -4.128,2.592 -9.024,3.888 -14.688,3.888 z m 1.152,-10.8 c 4.224,0 8.064,-1.056 11.52,-3.168 3.552,-2.112 6.384,-4.944 8.496,-8.496 2.112,-3.552 3.168,-7.44 3.168,-11.664 -2.208,-1.536 -4.992,-2.784 -8.352,-3.744 -3.264,-0.96 -6.864,-1.44 -10.8,-1.44 -7.008,0 -12.144,1.44 -15.408,4.32 -3.264,2.88 -4.896,6.432 -4.896,10.656 0,4.032 1.536,7.296 4.608,9.792 3.072,2.496 6.96,3.744 11.664,3.744 z M 397.968,682 374.352,608.56 h 12.672 l 17.424,58.032 h 0.144 l 18.576,-58.032 h 12.528 l 18.576,57.888 h 0.144 L 471.84,608.56 h 12.384 L 460.464,682 H 448.08 L 429.072,623.392 410.208,682 Z m 96.385,0 v -73.44 h 11.664 v 10.8 h 0.576 c 1.92,-3.552 5.04,-6.624 9.36,-9.216 4.416,-2.592 9.216,-3.888 14.4,-3.888 9.024,0 15.792,2.64 20.304,7.92 4.608,5.184 6.912,12.096 6.912,20.736 V 682 h -12.24 v -45.216 c 0,-7.104 -1.728,-12.096 -5.184,-14.976 -3.36,-2.976 -7.728,-4.464 -13.104,-4.464 -4.032,0 -7.584,1.152 -10.656,3.456 -3.072,2.208 -5.472,5.088 -7.2,8.64 -1.728,3.552 -2.592,7.296 -2.592,11.232 V 682 Z" + fill="#000000" + id="path45" /> + <path + id="path22856" + style="fill:#000000;fill-opacity:1" + d="M 367.29708,441 322.58028,517.86914 338.29708,545 h 89.5 z" /> +</svg>
diff --git a/docs/imgs/dawn_logo_black_notext.png b/docs/imgs/dawn_logo_black_notext.png new file mode 100644 index 0000000..11d5416 --- /dev/null +++ b/docs/imgs/dawn_logo_black_notext.png Binary files differ
diff --git a/docs/imgs/dawn_logo_black_notext.svg b/docs/imgs/dawn_logo_black_notext.svg new file mode 100644 index 0000000..d3804fd --- /dev/null +++ b/docs/imgs/dawn_logo_black_notext.svg
@@ -0,0 +1,83 @@ +<?xml version="1.0" encoding="UTF-8" standalone="no"?> +<svg + width="768" + height="768" + viewBox="0 0 768 768" + fill="none" + version="1.1" + id="svg47" + sodipodi:docname="dawn_logo_black_notext.svg" + inkscape:version="1.1 (c4e8f9e, 2021-05-24)" + xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape" + xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd" + xmlns="http://www.w3.org/2000/svg" + xmlns:svg="http://www.w3.org/2000/svg"> + <defs + id="defs51" /> + <sodipodi:namedview + id="namedview49" + pagecolor="#ffffff" + bordercolor="#666666" + borderopacity="1.0" + inkscape:pageshadow="2" + inkscape:pageopacity="0" + inkscape:pagecheckerboard="true" + showgrid="false" + inkscape:zoom="0.96102398" + inkscape:cx="255.45668" + inkscape:cy="371.99904" + inkscape:window-width="1296" + inkscape:window-height="997" + inkscape:window-x="0" + inkscape:window-y="25" + inkscape:window-maximized="0" + inkscape:current-layer="svg47" + showguides="false" /> + <path + id="circle33" + style="fill:#808080" + d="m 306.7315,89 a 121,121 0 0 0 -121,121 121,121 0 0 0 120.8418,120.98438 L 411.40142,149.41797 A 121,121 0 0 0 306.7315,89 Z" /> + <path + fill-rule="evenodd" + clip-rule="evenodd" + d="M429.5 543L550 335L309 335L429.5 543Z" + fill="#0066B0" + id="path37" + style="fill:#000000" /> + <path + fill-rule="evenodd" + clip-rule="evenodd" + d="M 429.5,124 550.29135,332.50291 H 308.70865 Z" + fill="#0066b0" + id="path37-7" + style="fill:#000000;fill-opacity:1;stroke-width:1.00242" /> + <path + id="path37-7-5" + style="fill:#000000;fill-opacity:1;stroke-width:0.489674" + d="m 306.64855,336.55438 -44.67773,77.12109 14.32812,24.73242 h 89.35547 z" /> + <path + fill-rule="evenodd" + clip-rule="evenodd" + d="M 552,337 672.79135,545.50291 H 431.20865 Z" + fill="#0066b0" + id="path37-7-9" + style="fill:#000000;fill-opacity:1;stroke-width:1.00242" /> + <path + fill-rule="evenodd" + clip-rule="evenodd" + d="m 321.32659,515.63497 43.72296,-75.16014 h -87.44593 z" + fill="#0093ff" + id="path41" + style="stroke-width:0.722694;fill:#000000" /> + <path + fill-rule="evenodd" + clip-rule="evenodd" + d="m 214.90584,337 120.5,208 H 94.405844 Z" + fill="#0076cc" + id="path43" + style="fill:#000000" /> + <path + id="path22856" + style="fill:#000000;fill-opacity:1" + d="M 367.29708,441 322.58028,517.86914 338.29708,545 h 89.5 z" /> +</svg>
diff --git a/docs/imgs/dawn_logo_notext.png b/docs/imgs/dawn_logo_notext.png new file mode 100644 index 0000000..f9732e5 --- /dev/null +++ b/docs/imgs/dawn_logo_notext.png Binary files differ
diff --git a/docs/imgs/dawn_logo_notext.svg b/docs/imgs/dawn_logo_notext.svg new file mode 100644 index 0000000..890619b --- /dev/null +++ b/docs/imgs/dawn_logo_notext.svg
@@ -0,0 +1,77 @@ +<?xml version="1.0" encoding="UTF-8" standalone="no"?> +<svg + width="768" + height="768" + viewBox="0 0 768 768" + fill="none" + version="1.1" + id="svg47" + sodipodi:docname="dawn_notext_logo.svg" + inkscape:version="1.1 (c68e22c387, 2021-05-23)" + xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape" + xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd" + xmlns="http://www.w3.org/2000/svg" + xmlns:svg="http://www.w3.org/2000/svg"> + <defs + id="defs51" /> + <sodipodi:namedview + id="namedview49" + pagecolor="#ffffff" + bordercolor="#666666" + borderopacity="1.0" + inkscape:pageshadow="2" + inkscape:pageopacity="0.0" + inkscape:pagecheckerboard="true" + showgrid="false" + inkscape:zoom="1.6830615" + inkscape:cx="187.15894" + inkscape:cy="384.41852" + inkscape:window-width="3840" + inkscape:window-height="2066" + inkscape:window-x="-11" + inkscape:window-y="-11" + inkscape:window-maximized="1" + inkscape:current-layer="svg47" /> + <circle + cx="304.81589" + cy="248.44208" + r="141.44208" + fill="#fde293" + id="circle33" + style="stroke-width:1.16894" /> + <path + fill-rule="evenodd" + clip-rule="evenodd" + d="M 445.67349,145.57511 727.10233,633.02428 H 164.24466 Z" + fill="#005a9c" + id="path35" + style="stroke-width:1.16894" /> + <path + fill-rule="evenodd" + clip-rule="evenodd" + d="M 445.67349,633.02428 586.5311,389.88417 H 304.81588 Z" + fill="#0066b0" + id="path37" + style="stroke-width:1.16894" /> + <path + fill-rule="evenodd" + clip-rule="evenodd" + d="m 304.23141,389.88417 70.72104,121.57005 H 233.51037 Z" + fill="#0086e8" + id="path39" + style="stroke-width:1.16894" /> + <path + fill-rule="evenodd" + clip-rule="evenodd" + d="M 304.23141,633.02428 374.95245,511.45422 H 233.51037 Z" + fill="#0093ff" + id="path41" + style="stroke-width:1.16894" /> + <path + fill-rule="evenodd" + clip-rule="evenodd" + d="M 197.85761,389.88417 338.71522,633.02428 H 57 Z" + fill="#0076cc" + id="path43" + style="stroke-width:1.16894" /> +</svg>
diff --git a/docs/imgs/dawn_logo_white.png b/docs/imgs/dawn_logo_white.png new file mode 100644 index 0000000..8d835c4 --- /dev/null +++ b/docs/imgs/dawn_logo_white.png Binary files differ
diff --git a/docs/imgs/dawn_logo_white.svg b/docs/imgs/dawn_logo_white.svg new file mode 100644 index 0000000..0f9d00c --- /dev/null +++ b/docs/imgs/dawn_logo_white.svg
@@ -0,0 +1,88 @@ +<?xml version="1.0" encoding="UTF-8" standalone="no"?> +<svg + width="768" + height="768" + viewBox="0 0 768 768" + fill="none" + version="1.1" + id="svg47" + sodipodi:docname="dawn_white.svg" + inkscape:version="1.1 (c68e22c387, 2021-05-23)" + xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape" + xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd" + xmlns="http://www.w3.org/2000/svg" + xmlns:svg="http://www.w3.org/2000/svg"> + <defs + id="defs51" /> + <sodipodi:namedview + id="namedview49" + pagecolor="#ffffff" + bordercolor="#666666" + borderopacity="1.0" + inkscape:pageshadow="2" + inkscape:pageopacity="0" + inkscape:pagecheckerboard="true" + showgrid="false" + inkscape:zoom="2.3802083" + inkscape:cx="255.01969" + inkscape:cy="371.81619" + inkscape:window-width="3840" + inkscape:window-height="2066" + inkscape:window-x="-11" + inkscape:window-y="-11" + inkscape:window-maximized="1" + inkscape:current-layer="svg47" + showguides="false" /> + <path + id="circle33" + style="fill:#ececec" + d="m 306.7315,89 a 121,121 0 0 0 -121,121 121,121 0 0 0 120.8418,120.98438 L 411.40142,149.41797 A 121,121 0 0 0 306.7315,89 Z" /> + <path + fill-rule="evenodd" + clip-rule="evenodd" + d="M429.5 543L550 335L309 335L429.5 543Z" + fill="#0066B0" + id="path37" + style="fill:#ffffff" /> + <path + fill-rule="evenodd" + clip-rule="evenodd" + d="M 429.5,124 550.29135,332.50291 H 308.70865 Z" + fill="#0066b0" + id="path37-7" + style="fill:#ffffff;fill-opacity:1;stroke-width:1.00242" /> + <path + id="path37-7-5" + style="fill:#ffffff;fill-opacity:1;stroke-width:0.489674" + d="m 306.64855,336.55438 -44.67773,77.12109 14.32812,24.73242 h 89.35547 z" /> + <path + fill-rule="evenodd" + clip-rule="evenodd" + d="M 552,337 672.79135,545.50291 H 431.20865 Z" + fill="#0066b0" + id="path37-7-9" + style="fill:#ffffff;fill-opacity:1;stroke-width:1.00242" /> + <path + fill-rule="evenodd" + clip-rule="evenodd" + d="m 321.32659,515.63497 43.72296,-75.16014 h -87.44593 z" + fill="#0093ff" + id="path41" + style="stroke-width:0.722694;fill:#ffffff" /> + <path + fill-rule="evenodd" + clip-rule="evenodd" + d="m 214.90584,337 120.5,208 H 94.405844 Z" + fill="#0076cc" + id="path43" + style="fill:#ffffff" /> + <path + d="M 208.52,682 V 578.896 h 31.104 c 10.752,0 20.016,2.208 27.792,6.624 7.776,4.32 13.776,10.368 18,18.144 4.224,7.776 6.336,16.704 6.336,26.784 0,10.08 -2.112,19.008 -6.336,26.784 -4.224,7.68 -10.224,13.728 -18,18.144 -7.776,4.416 -17.04,6.624 -27.792,6.624 z m 12.24,-11.664 h 18.864 c 8.064,0 15.072,-1.536 21.024,-4.608 5.952,-3.168 10.56,-7.728 13.824,-13.68 3.264,-5.952 4.896,-13.152 4.896,-21.6 0,-8.448 -1.632,-15.648 -4.896,-21.6 -3.264,-5.952 -7.872,-10.464 -13.824,-13.536 -5.952,-3.168 -12.96,-4.752 -21.024,-4.752 H 220.76 Z m 110.034,13.968 c -5.472,0 -10.272,-1.056 -14.4,-3.168 -4.128,-2.112 -7.392,-4.992 -9.792,-8.64 -2.304,-3.744 -3.456,-7.968 -3.456,-12.672 0,-5.376 1.392,-9.888 4.176,-13.536 2.784,-3.744 6.528,-6.528 11.232,-8.352 4.704,-1.92 9.888,-2.88 15.552,-2.88 4.896,0 9.216,0.528 12.96,1.584 3.744,1.056 6.432,2.112 8.064,3.168 v -4.464 c 0,-5.568 -1.968,-9.984 -5.904,-13.248 -3.936,-3.264 -8.736,-4.896 -14.4,-4.896 -4.032,0 -7.824,0.912 -11.376,2.736 -3.456,1.728 -6.192,4.176 -8.208,7.344 l -9.216,-6.912 c 2.88,-4.32 6.816,-7.728 11.808,-10.224 5.088,-2.592 10.752,-3.888 16.992,-3.888 9.984,0 17.808,2.64 23.472,7.92 5.664,5.28 8.496,12.384 8.496,21.312 V 682 H 355.13 v -10.512 h -0.576 c -1.92,3.264 -4.944,6.24 -9.072,8.928 -4.128,2.592 -9.024,3.888 -14.688,3.888 z m 1.152,-10.8 c 4.224,0 8.064,-1.056 11.52,-3.168 3.552,-2.112 6.384,-4.944 8.496,-8.496 2.112,-3.552 3.168,-7.44 3.168,-11.664 -2.208,-1.536 -4.992,-2.784 -8.352,-3.744 -3.264,-0.96 -6.864,-1.44 -10.8,-1.44 -7.008,0 -12.144,1.44 -15.408,4.32 -3.264,2.88 -4.896,6.432 -4.896,10.656 0,4.032 1.536,7.296 4.608,9.792 3.072,2.496 6.96,3.744 11.664,3.744 z M 397.968,682 374.352,608.56 h 12.672 l 17.424,58.032 h 0.144 l 18.576,-58.032 h 12.528 l 18.576,57.888 h 0.144 L 471.84,608.56 h 12.384 L 460.464,682 H 448.08 L 429.072,623.392 410.208,682 Z m 96.385,0 v -73.44 h 11.664 v 10.8 h 0.576 c 1.92,-3.552 5.04,-6.624 9.36,-9.216 4.416,-2.592 9.216,-3.888 14.4,-3.888 9.024,0 15.792,2.64 20.304,7.92 4.608,5.184 6.912,12.096 6.912,20.736 V 682 h -12.24 v -45.216 c 0,-7.104 -1.728,-12.096 -5.184,-14.976 -3.36,-2.976 -7.728,-4.464 -13.104,-4.464 -4.032,0 -7.584,1.152 -10.656,3.456 -3.072,2.208 -5.472,5.088 -7.2,8.64 -1.728,3.552 -2.592,7.296 -2.592,11.232 V 682 Z" + fill="#000000" + id="path45" + style="fill:#ffffff" /> + <path + id="path22856" + style="fill:#ffffff;fill-opacity:1" + d="M 367.29708,441 322.58028,517.86914 338.29708,545 h 89.5 z" /> +</svg>
diff --git a/docs/imgs/dawn_logo_white_notext.png b/docs/imgs/dawn_logo_white_notext.png new file mode 100644 index 0000000..e6fe2b5 --- /dev/null +++ b/docs/imgs/dawn_logo_white_notext.png Binary files differ
diff --git a/docs/imgs/dawn_logo_white_notext.svg b/docs/imgs/dawn_logo_white_notext.svg new file mode 100644 index 0000000..fba1f26 --- /dev/null +++ b/docs/imgs/dawn_logo_white_notext.svg
@@ -0,0 +1,83 @@ +<?xml version="1.0" encoding="UTF-8" standalone="no"?> +<svg + width="768" + height="768" + viewBox="0 0 768 768" + fill="none" + version="1.1" + id="svg47" + sodipodi:docname="dawn_logo_white_notext.svg" + inkscape:version="1.1 (c4e8f9e, 2021-05-24)" + xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape" + xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd" + xmlns="http://www.w3.org/2000/svg" + xmlns:svg="http://www.w3.org/2000/svg"> + <defs + id="defs51" /> + <sodipodi:namedview + id="namedview49" + pagecolor="#ffffff" + bordercolor="#666666" + borderopacity="1.0" + inkscape:pageshadow="2" + inkscape:pageopacity="0" + inkscape:pagecheckerboard="true" + showgrid="false" + inkscape:zoom="1.1009789" + inkscape:cx="254.77327" + inkscape:cy="371.94172" + inkscape:window-width="1296" + inkscape:window-height="997" + inkscape:window-x="0" + inkscape:window-y="25" + inkscape:window-maximized="0" + inkscape:current-layer="svg47" + showguides="false" /> + <path + id="circle33" + style="fill:#ececec" + d="m 306.7315,89 a 121,121 0 0 0 -121,121 121,121 0 0 0 120.8418,120.98438 L 411.40142,149.41797 A 121,121 0 0 0 306.7315,89 Z" /> + <path + fill-rule="evenodd" + clip-rule="evenodd" + d="M429.5 543L550 335L309 335L429.5 543Z" + fill="#0066B0" + id="path37" + style="fill:#ffffff" /> + <path + fill-rule="evenodd" + clip-rule="evenodd" + d="M 429.5,124 550.29135,332.50291 H 308.70865 Z" + fill="#0066b0" + id="path37-7" + style="fill:#ffffff;fill-opacity:1;stroke-width:1.00242" /> + <path + id="path37-7-5" + style="fill:#ffffff;fill-opacity:1;stroke-width:0.489674" + d="m 306.64855,336.55438 -44.67773,77.12109 14.32812,24.73242 h 89.35547 z" /> + <path + fill-rule="evenodd" + clip-rule="evenodd" + d="M 552,337 672.79135,545.50291 H 431.20865 Z" + fill="#0066b0" + id="path37-7-9" + style="fill:#ffffff;fill-opacity:1;stroke-width:1.00242" /> + <path + fill-rule="evenodd" + clip-rule="evenodd" + d="m 321.32659,515.63497 43.72296,-75.16014 h -87.44593 z" + fill="#0093ff" + id="path41" + style="stroke-width:0.722694;fill:#ffffff" /> + <path + fill-rule="evenodd" + clip-rule="evenodd" + d="m 214.90584,337 120.5,208 H 94.405844 Z" + fill="#0076cc" + id="path43" + style="fill:#ffffff" /> + <path + id="path22856" + style="fill:#ffffff;fill-opacity:1" + d="M 367.29708,441 322.58028,517.86914 338.29708,545 h 89.5 z" /> +</svg>
diff --git a/generator/BUILD.gn b/generator/BUILD.gn new file mode 100644 index 0000000..a1d954a --- /dev/null +++ b/generator/BUILD.gn
@@ -0,0 +1,63 @@ +# Copyright 2019 The Dawn Authors +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +import("../scripts/dawn_overrides_with_defaults.gni") +import("dawn_generator.gni") + +# The list of directories in which to check for stale autogenerated files. +# It should include the list of all directories in which we ever generated +# files but we can't just put dawn_gen_root because there are more than +# autogenerated sources there. +_stale_dirs = [ + "dawn", + "dawn/native", + "dawn/wire", + "mock", + "src", +] + +_allowed_output_dirs_file = + "${dawn_gen_root}/removed_stale_autogen_files.allowed_output_dirs" +write_file(_allowed_output_dirs_file, dawn_allowed_gen_output_dirs) + +_stale_dirs_file = "${dawn_gen_root}/removed_stale_autogen_files.stale_dirs" +write_file(_stale_dirs_file, _stale_dirs) + +_stamp_file = "${dawn_gen_root}/removed_stale_autogen_files.stamp" + +# An action that removes autogenerated files that aren't in allowed directories +# see dawn_generator.gni for more details. +action("remove_stale_autogen_files") { + script = "remove_files.py" + args = [ + "--root-dir", + rebase_path(dawn_gen_root, root_build_dir), + "--allowed-output-dirs-file", + rebase_path(_allowed_output_dirs_file, root_build_dir), + "--stale-dirs-file", + rebase_path(_stale_dirs_file, root_build_dir), + "--stamp", + rebase_path(_stamp_file, root_build_dir), + ] + + # Have the "list of file" inputs as a dependency so that the action reruns + # as soon as they change. + inputs = [ + _allowed_output_dirs_file, + _stale_dirs_file, + ] + + # Output a stamp file so we don't re-run this action on every build. + outputs = [ _stamp_file ] +}
diff --git a/generator/CMakeLists.txt b/generator/CMakeLists.txt new file mode 100644 index 0000000..a2d6784 --- /dev/null +++ b/generator/CMakeLists.txt
@@ -0,0 +1,116 @@ +# Copyright 2020 The Dawn Authors +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +find_package(PythonInterp REQUIRED) +message(STATUS "Dawn: using python at ${PYTHON_EXECUTABLE}") + +# Check for Jinja2 +if (NOT DAWN_JINJA2_DIR) + message(STATUS "Dawn: Using system jinja2") + execute_process( + COMMAND ${PYTHON_EXECUTABLE} -c "import jinja2" + RESULT_VARIABLE RET + ) + if (NOT RET EQUAL 0) + message(FATAL_ERROR "Dawn: Missing dependencies for code generation, please ensure you have python-jinja2 installed.") + endif() +else() + message(STATUS "Dawn: Using jinja2 at ${DAWN_JINJA2_DIR}") +endif() + +# Function to invoke a generator_lib.py generator. +# - SCRIPT is the name of the script to call +# - ARGS are the extra arguments to pass to the script in addition to the base generator_lib.py arguments +# - PRINT_NAME is the name to use when outputting status or errors +# - RESULT_VARIABLE will be modified to contain the list of files generated by this generator +function(DawnGenerator) + set(oneValueArgs SCRIPT RESULT_VARIABLE PRINT_NAME) + set(multiValueArgs ARGS) + cmake_parse_arguments(G "" "${oneValueArgs}" "${multiValueArgs}" ${ARGN}) + + # Build the set of args common to all invocation of that generator. + set(BASE_ARGS + ${PYTHON_EXECUTABLE} + ${G_SCRIPT} + --template-dir + "${DAWN_TEMPLATE_DIR}" + --root-dir + "${Dawn_SOURCE_DIR}" + --output-dir + "${DAWN_BUILD_GEN_DIR}" + ${G_ARGS} + ) + if (DAWN_JINJA2_DIR) + list(APPEND BASE_ARGS --jinja2-path ${DAWN_JINJA2_DIR}) + endif() + + # Call the generator to get the list of its dependencies. + execute_process( + COMMAND ${BASE_ARGS} --print-cmake-dependencies + OUTPUT_VARIABLE DEPENDENCIES + RESULT_VARIABLE RET + ) + if (NOT RET EQUAL 0) + message(FATAL_ERROR "Dawn: Failed to get the dependencies for ${G_PRINT_NAME}. Base args are '${BASE_ARGS}'.") + endif() + + # Ask CMake to re-run if any of the dependencies changed as it might modify the build graph. + if(${CMAKE_VERSION} VERSION_GREATER_EQUAL "3.12.0") + set_property(DIRECTORY APPEND PROPERTY CMAKE_CONFIGURE_DEPENDS ${DEPENDENCIES}) + endif() + + # Call the generator to get the list of its outputs. + execute_process( + COMMAND ${BASE_ARGS} --print-cmake-outputs + OUTPUT_VARIABLE OUTPUTS + RESULT_VARIABLE RET + ) + if (NOT RET EQUAL 0) + message(FATAL_ERROR "Dawn: Failed to get the outputs for ${G_PRINT_NAME}. Base args are '${BASE_ARGS}'.") + endif() + + # Add the custom command that calls the generator. + add_custom_command( + COMMAND ${BASE_ARGS} + DEPENDS ${DEPENDENCIES} + OUTPUT ${OUTPUTS} + COMMENT "Dawn: Generating files for ${G_PRINT_NAME}." + ) + + # Return the list of outputs. + set(${G_RESULT_VARIABLE} ${OUTPUTS} PARENT_SCOPE) +endfunction() + +# Helper function to call dawn_generator.py: +# - TARGET is the generator target to build +# - PRINT_NAME and RESULT_VARIABLE are like for DawnGenerator +function(DawnJSONGenerator) + set(oneValueArgs TARGET RESULT_VARIABLE) + cmake_parse_arguments(G "" "${oneValueArgs}" "${multiValueArgs}" ${ARGN}) + + DawnGenerator( + SCRIPT "${Dawn_SOURCE_DIR}/generator/dawn_json_generator.py" + ARGS --dawn-json + "${Dawn_SOURCE_DIR}/dawn.json" + --wire-json + "${Dawn_SOURCE_DIR}/dawn_wire.json" + --targets + ${G_TARGET} + RESULT_VARIABLE RET + ${G_UNPARSED_ARGUMENTS} + ) + + # Forward the result up one more scope + set(${G_RESULT_VARIABLE} ${RET} PARENT_SCOPE) +endfunction()
diff --git a/generator/dawn_generator.gni b/generator/dawn_generator.gni new file mode 100644 index 0000000..28c5301 --- /dev/null +++ b/generator/dawn_generator.gni
@@ -0,0 +1,121 @@ +# Copyright 2019 The Dawn Authors +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +import("../scripts/dawn_overrides_with_defaults.gni") +import("generator_lib.gni") + +# Dawn used to put autogenerated files in a lot of different places. When we +# started to move them around, some compilation issues arised because some +# stale include files stayed in the build directory and were picked up. +# To counter this, now Dawn does the following: +# +# 1. The generated output file directory structure has to match the structure +# of the source tree, starting at dawn_gen_root (gen/ or +# gen/third_party/dawn depending on where we are). +# 2. include and dawn_gen_root/include has to match the structure of +# the source tree too. +# 3. Dawn files must use include relative to src/ or include such as +# "dawn/dawn.h" or "dawn/native/backend/BackendStuff.h". +# +# The allowed list below ensure 1). Include directory rules for Dawn ensure 3) +# and 2) is something we need to enforce in code review. +# +# However GN's toolchains automatically add some include directories for us +# which breaks 3) slightly. To avoid stale headers in for example +# dawn_gen_root/src/dawn/dawn/ to be picked up (instead of +# dawn_gen_root/src/dawn), we have a special action that removes files in +# disallowed gen directories. + +dawn_allowed_gen_output_dirs = [ + "src/dawn/", + "src/dawn/common/", + "src/dawn/native/", + "src/dawn/native/opengl/", + "src/dawn/wire/client/", + "src/dawn/wire/server/", + "src/dawn/wire/", + "include/dawn/", + "emscripten-bits/", + "webgpu-headers/", +] + +# Template to help invoking Dawn code generators based on generator_lib +# +# dawn_generator("my_target_gen") { +# # The script and generator specific arguments +# script = [ "my_awesome_generator.py" ] +# args = [ +# "--be-awesome", +# "yes" +# ] +# +# # The list of expected outputs, generation fails if there's a mismatch +# outputs = [ +# "MyAwesomeTarget.cpp", +# "MyAwesomeTarget.h", +# ] +# } +# +# Using the generated files is done like so: +# +# shared_library("my_target") { +# deps = [ ":my_target_gen "] +# sources = get_target_outputs(":my_target_gen") +# } +# +template("dawn_generator") { + generator_lib_action(target_name) { + forward_variables_from(invoker, "*") + + # Set arguments required to find the python libraries for the generator + generator_lib_dir = "${dawn_root}/generator" + jinja2_path = dawn_jinja2_dir + + # Force Dawn's autogenerated file structure to mirror exactly the source + # tree but start at ${dawn_gen_root} instead of ${dawn_root} + allowed_output_dirs = dawn_allowed_gen_output_dirs + custom_gen_dir = dawn_gen_root + + # Make sure that we delete stale autogenerated file in directories that are + # no longer used by code generation to avoid include conflicts. + deps = [ "${dawn_root}/generator:remove_stale_autogen_files" ] + } +} + +# Helper generator for calling the generator from dawn.json +# +# dawn_json_generator("my_target_gen") { +# # Which generator target to output +# target = "my_target" +# +# # Also supports `outputs` and `custom_gen_dir` like dawn_generator. +# } +template("dawn_json_generator") { + dawn_generator(target_name) { + script = "${dawn_root}/generator/dawn_json_generator.py" + + # The base arguments for the generator: from this dawn.json, generate this + # target using templates in this directory. + args = [ + "--dawn-json", + rebase_path("${dawn_root}/dawn.json", root_build_dir), + "--wire-json", + rebase_path("${dawn_root}/dawn_wire.json", root_build_dir), + "--targets", + invoker.target, + ] + + forward_variables_from(invoker, "*", [ "target" ]) + } +}
diff --git a/generator/dawn_json_generator.py b/generator/dawn_json_generator.py new file mode 100644 index 0000000..29c1be9 --- /dev/null +++ b/generator/dawn_json_generator.py
@@ -0,0 +1,1031 @@ +#!/usr/bin/env python3 +# Copyright 2017 The Dawn Authors +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +import json, os, sys +from collections import namedtuple + +from generator_lib import Generator, run_generator, FileRender + +############################################################ +# OBJECT MODEL +############################################################ + + +class Metadata: + def __init__(self, metadata): + self.api = metadata['api'] + self.namespace = metadata['namespace'] + self.c_prefix = metadata.get('c_prefix', self.namespace.upper()) + self.proc_table_prefix = metadata['proc_table_prefix'] + self.impl_dir = metadata.get('impl_dir', '') + self.native_namespace = metadata['native_namespace'] + self.copyright_year = metadata.get('copyright_year', None) + + +class Name: + def __init__(self, name, native=False): + self.native = native + self.name = name + if native: + self.chunks = [name] + else: + self.chunks = name.split(' ') + + def get(self): + return self.name + + def CamelChunk(self, chunk): + return chunk[0].upper() + chunk[1:] + + def canonical_case(self): + return (' '.join(self.chunks)).lower() + + def concatcase(self): + return ''.join(self.chunks) + + def camelCase(self): + return self.chunks[0] + ''.join( + [self.CamelChunk(chunk) for chunk in self.chunks[1:]]) + + def CamelCase(self): + return ''.join([self.CamelChunk(chunk) for chunk in self.chunks]) + + def SNAKE_CASE(self): + return '_'.join([chunk.upper() for chunk in self.chunks]) + + def snake_case(self): + return '_'.join(self.chunks) + + def namespace_case(self): + return '::'.join(self.chunks) + + def Dirs(self): + return '/'.join(self.chunks) + + def js_enum_case(self): + result = self.chunks[0].lower() + for chunk in self.chunks[1:]: + if not result[-1].isdigit(): + result += '-' + result += chunk.lower() + return result + + +def concat_names(*names): + return ' '.join([name.canonical_case() for name in names]) + + +class Type: + def __init__(self, name, json_data, native=False): + self.json_data = json_data + self.dict_name = name + self.name = Name(name, native=native) + self.category = json_data['category'] + self.is_wire_transparent = False + + +EnumValue = namedtuple('EnumValue', ['name', 'value', 'valid', 'json_data']) + + +class EnumType(Type): + def __init__(self, is_enabled, name, json_data): + Type.__init__(self, name, json_data) + + self.values = [] + self.contiguousFromZero = True + lastValue = -1 + for m in self.json_data['values']: + if not is_enabled(m): + continue + value = m['value'] + if value != lastValue + 1: + self.contiguousFromZero = False + lastValue = value + self.values.append( + EnumValue(Name(m['name']), value, m.get('valid', True), m)) + + # Assert that all values are unique in enums + all_values = set() + for value in self.values: + if value.value in all_values: + raise Exception("Duplicate value {} in enum {}".format( + value.value, name)) + all_values.add(value.value) + self.is_wire_transparent = True + + +BitmaskValue = namedtuple('BitmaskValue', ['name', 'value', 'json_data']) + + +class BitmaskType(Type): + def __init__(self, is_enabled, name, json_data): + Type.__init__(self, name, json_data) + self.values = [ + BitmaskValue(Name(m['name']), m['value'], m) + for m in self.json_data['values'] if is_enabled(m) + ] + self.full_mask = 0 + for value in self.values: + self.full_mask = self.full_mask | value.value + self.is_wire_transparent = True + + +class FunctionPointerType(Type): + def __init__(self, is_enabled, name, json_data): + Type.__init__(self, name, json_data) + self.return_type = None + self.arguments = [] + + +class TypedefType(Type): + def __init__(self, is_enabled, name, json_data): + Type.__init__(self, name, json_data) + self.type = None + + +class NativeType(Type): + def __init__(self, is_enabled, name, json_data): + Type.__init__(self, name, json_data, native=True) + self.is_wire_transparent = True + + +# Methods and structures are both "records", so record members correspond to +# method arguments or structure members. +class RecordMember: + def __init__(self, + name, + typ, + annotation, + json_data, + optional=False, + is_return_value=False, + default_value=None, + skip_serialize=False): + self.name = name + self.type = typ + self.annotation = annotation + self.json_data = json_data + self.length = None + self.optional = optional + self.is_return_value = is_return_value + self.handle_type = None + self.default_value = default_value + self.skip_serialize = skip_serialize + + def set_handle_type(self, handle_type): + assert self.type.dict_name == "ObjectHandle" + self.handle_type = handle_type + + +Method = namedtuple('Method', + ['name', 'return_type', 'arguments', 'json_data']) + + +class ObjectType(Type): + def __init__(self, is_enabled, name, json_data): + json_data_override = {'methods': []} + if 'methods' in json_data: + json_data_override['methods'] = [ + m for m in json_data['methods'] if is_enabled(m) + ] + Type.__init__(self, name, dict(json_data, **json_data_override)) + + +class Record: + def __init__(self, name): + self.name = Name(name) + self.members = [] + self.may_have_dawn_object = False + + def update_metadata(self): + def may_have_dawn_object(member): + if isinstance(member.type, ObjectType): + return True + elif isinstance(member.type, StructureType): + return member.type.may_have_dawn_object + else: + return False + + self.may_have_dawn_object = any( + may_have_dawn_object(member) for member in self.members) + + # Set may_have_dawn_object to true if the type is chained or + # extensible. Chained structs may contain a Dawn object. + if isinstance(self, StructureType): + self.may_have_dawn_object = (self.may_have_dawn_object + or self.chained or self.extensible) + + +class StructureType(Record, Type): + def __init__(self, is_enabled, name, json_data): + Record.__init__(self, name) + json_data_override = {} + if 'members' in json_data: + json_data_override['members'] = [ + m for m in json_data['members'] if is_enabled(m) + ] + Type.__init__(self, name, dict(json_data, **json_data_override)) + self.chained = json_data.get("chained", None) + self.extensible = json_data.get("extensible", None) + if self.chained: + assert (self.chained == "in" or self.chained == "out") + if self.extensible: + assert (self.extensible == "in" or self.extensible == "out") + # Chained structs inherit from wgpu::ChainedStruct, which has + # nextInChain, so setting both extensible and chained would result in + # two nextInChain members. + assert not (self.extensible and self.chained) + + def update_metadata(self): + Record.update_metadata(self) + + if self.may_have_dawn_object: + self.is_wire_transparent = False + return + + assert not (self.chained or self.extensible) + + def get_is_wire_transparent(member): + return member.type.is_wire_transparent and member.annotation == 'value' + + self.is_wire_transparent = all( + get_is_wire_transparent(m) for m in self.members) + + @property + def output(self): + return self.chained == "out" or self.extensible == "out" + + +class ConstantDefinition(): + def __init__(self, is_enabled, name, json_data): + self.type = None + self.value = json_data['value'] + self.json_data = json_data + self.name = Name(name) + + +class FunctionDeclaration(): + def __init__(self, is_enabled, name, json_data): + self.return_type = None + self.arguments = [] + self.json_data = json_data + self.name = Name(name) + + +class Command(Record): + def __init__(self, name, members=None): + Record.__init__(self, name) + self.members = members or [] + self.derived_object = None + self.derived_method = None + + +def linked_record_members(json_data, types): + members = [] + members_by_name = {} + for m in json_data: + member = RecordMember(Name(m['name']), + types[m['type']], + m.get('annotation', 'value'), + m, + optional=m.get('optional', False), + is_return_value=m.get('is_return_value', False), + default_value=m.get('default', None), + skip_serialize=m.get('skip_serialize', False)) + handle_type = m.get('handle_type') + if handle_type: + member.set_handle_type(types[handle_type]) + members.append(member) + members_by_name[member.name.canonical_case()] = member + + for (member, m) in zip(members, json_data): + if member.annotation != 'value': + if not 'length' in m: + if member.type.category != 'object': + member.length = "constant" + member.constant_length = 1 + else: + assert False + elif m['length'] == 'strlen': + member.length = 'strlen' + elif isinstance(m['length'], int): + assert m['length'] > 0 + member.length = "constant" + member.constant_length = m['length'] + else: + member.length = members_by_name[m['length']] + + return members + + +############################################################ +# PARSE +############################################################ + + +def link_object(obj, types): + def make_method(json_data): + arguments = linked_record_members(json_data.get('args', []), types) + return Method(Name(json_data['name']), + types[json_data.get('returns', + 'void')], arguments, json_data) + + obj.methods = [make_method(m) for m in obj.json_data.get('methods', [])] + obj.methods.sort(key=lambda method: method.name.canonical_case()) + + +def link_structure(struct, types): + struct.members = linked_record_members(struct.json_data['members'], types) + + +def link_function_pointer(function_pointer, types): + link_function(function_pointer, types) + + +def link_typedef(typedef, types): + typedef.type = types[typedef.json_data['type']] + + +def link_constant(constant, types): + constant.type = types[constant.json_data['type']] + assert constant.type.name.native + + +def link_function(function, types): + function.return_type = types[function.json_data.get('returns', 'void')] + function.arguments = linked_record_members(function.json_data['args'], + types) + + +# Sort structures so that if struct A has struct B as a member, then B is +# listed before A. +# +# This is a form of topological sort where we try to keep the order reasonably +# similar to the original order (though the sort isn't technically stable). +# +# It works by computing for each struct type what is the depth of its DAG of +# dependents, then re-sorting based on that depth using Python's stable sort. +# This makes a toposort because if A depends on B then its depth will be bigger +# than B's. It is also nice because all nodes with the same depth are kept in +# the input order. +def topo_sort_structure(structs): + for struct in structs: + struct.visited = False + struct.subdag_depth = 0 + + def compute_depth(struct): + if struct.visited: + return struct.subdag_depth + + max_dependent_depth = 0 + for member in struct.members: + if member.type.category == 'structure': + max_dependent_depth = max(max_dependent_depth, + compute_depth(member.type) + 1) + + struct.subdag_depth = max_dependent_depth + struct.visited = True + return struct.subdag_depth + + for struct in structs: + compute_depth(struct) + + result = sorted(structs, key=lambda struct: struct.subdag_depth) + + for struct in structs: + del struct.visited + del struct.subdag_depth + + return result + + +def parse_json(json, enabled_tags, disabled_tags=None): + is_enabled = lambda json_data: item_is_enabled( + enabled_tags, json_data) and not item_is_disabled( + disabled_tags, json_data) + category_to_parser = { + 'bitmask': BitmaskType, + 'enum': EnumType, + 'native': NativeType, + 'function pointer': FunctionPointerType, + 'object': ObjectType, + 'structure': StructureType, + 'typedef': TypedefType, + 'constant': ConstantDefinition, + 'function': FunctionDeclaration + } + + types = {} + + by_category = {} + for name in category_to_parser.keys(): + by_category[name] = [] + + for (name, json_data) in json.items(): + if name[0] == '_' or not is_enabled(json_data): + continue + category = json_data['category'] + parsed = category_to_parser[category](is_enabled, name, json_data) + by_category[category].append(parsed) + types[name] = parsed + + for obj in by_category['object']: + link_object(obj, types) + + for struct in by_category['structure']: + link_structure(struct, types) + + for function_pointer in by_category['function pointer']: + link_function_pointer(function_pointer, types) + + for typedef in by_category['typedef']: + link_typedef(typedef, types) + + for constant in by_category['constant']: + link_constant(constant, types) + + for function in by_category['function']: + link_function(function, types) + + for category in by_category.keys(): + by_category[category] = sorted( + by_category[category], key=lambda typ: typ.name.canonical_case()) + + by_category['structure'] = topo_sort_structure(by_category['structure']) + + for struct in by_category['structure']: + struct.update_metadata() + + api_params = { + 'types': types, + 'by_category': by_category, + 'enabled_tags': enabled_tags, + 'disabled_tags': disabled_tags, + } + return { + 'metadata': Metadata(json['_metadata']), + 'types': types, + 'by_category': by_category, + 'enabled_tags': enabled_tags, + 'disabled_tags': disabled_tags, + 'c_methods': lambda typ: c_methods(api_params, typ), + 'c_methods_sorted_by_name': get_c_methods_sorted_by_name(api_params), + } + + +############################################################ +# WIRE STUFF +############################################################ + + +# Create wire commands from api methods +def compute_wire_params(api_params, wire_json): + wire_params = api_params.copy() + types = wire_params['types'] + + commands = [] + return_commands = [] + + wire_json['special items']['client_handwritten_commands'] += wire_json[ + 'special items']['client_side_commands'] + + # Generate commands from object methods + for api_object in wire_params['by_category']['object']: + for method in api_object.methods: + command_name = concat_names(api_object.name, method.name) + command_suffix = Name(command_name).CamelCase() + + # Only object return values or void are supported. + # Other methods must be handwritten. + is_object = method.return_type.category == 'object' + is_void = method.return_type.name.canonical_case() == 'void' + if not (is_object or is_void): + assert command_suffix in ( + wire_json['special items']['client_handwritten_commands']) + continue + + if command_suffix in ( + wire_json['special items']['client_side_commands']): + continue + + # Create object method commands by prepending "self" + members = [ + RecordMember(Name('self'), types[api_object.dict_name], + 'value', {}) + ] + members += method.arguments + + # Client->Server commands that return an object return the + # result object handle + if method.return_type.category == 'object': + result = RecordMember(Name('result'), + types['ObjectHandle'], + 'value', {}, + is_return_value=True) + result.set_handle_type(method.return_type) + members.append(result) + + command = Command(command_name, members) + command.derived_object = api_object + command.derived_method = method + commands.append(command) + + for (name, json_data) in wire_json['commands'].items(): + commands.append(Command(name, linked_record_members(json_data, types))) + + for (name, json_data) in wire_json['return commands'].items(): + return_commands.append( + Command(name, linked_record_members(json_data, types))) + + wire_params['cmd_records'] = { + 'command': commands, + 'return command': return_commands + } + + for commands in wire_params['cmd_records'].values(): + for command in commands: + command.update_metadata() + commands.sort(key=lambda c: c.name.canonical_case()) + + wire_params.update(wire_json.get('special items', {})) + + return wire_params + + +############################################################# +# Generator +############################################################# + + +def as_varName(*names): + return names[0].camelCase() + ''.join( + [name.CamelCase() for name in names[1:]]) + + +def as_cType(c_prefix, name): + if name.native: + return name.concatcase() + else: + return c_prefix + name.CamelCase() + + +def as_cppType(name): + if name.native: + return name.concatcase() + else: + return name.CamelCase() + + +def as_jsEnumValue(value): + if 'jsrepr' in value.json_data: return value.json_data['jsrepr'] + return "'" + value.name.js_enum_case() + "'" + + +def convert_cType_to_cppType(typ, annotation, arg, indent=0): + if typ.category == 'native': + return arg + if annotation == 'value': + if typ.category == 'object': + return '{}::Acquire({})'.format(as_cppType(typ.name), arg) + elif typ.category == 'structure': + converted_members = [ + convert_cType_to_cppType( + member.type, member.annotation, + '{}.{}'.format(arg, as_varName(member.name)), indent + 1) + for member in typ.members + ] + + converted_members = [(' ' * 4) + m for m in converted_members] + converted_members = ',\n'.join(converted_members) + + return as_cppType(typ.name) + ' {\n' + converted_members + '\n}' + elif typ.category == 'function pointer': + return 'reinterpret_cast<{}>({})'.format(as_cppType(typ.name), arg) + else: + return 'static_cast<{}>({})'.format(as_cppType(typ.name), arg) + else: + return 'reinterpret_cast<{} {}>({})'.format(as_cppType(typ.name), + annotation, arg) + + +def decorate(name, typ, arg): + if arg.annotation == 'value': + return typ + ' ' + name + elif arg.annotation == '*': + return typ + ' * ' + name + elif arg.annotation == 'const*': + return typ + ' const * ' + name + elif arg.annotation == 'const*const*': + return 'const ' + typ + '* const * ' + name + else: + assert False + + +def annotated(typ, arg): + name = as_varName(arg.name) + return decorate(name, typ, arg) + + +def item_is_enabled(enabled_tags, json_data): + tags = json_data.get('tags') + if tags is None: return True + return any(tag in enabled_tags for tag in tags) + + +def item_is_disabled(disabled_tags, json_data): + if disabled_tags is None: return False + tags = json_data.get('tags') + if tags is None: return False + + return any(tag in disabled_tags for tag in tags) + + +def as_cppEnum(value_name): + assert not value_name.native + if value_name.concatcase()[0].isdigit(): + return "e" + value_name.CamelCase() + return value_name.CamelCase() + + +def as_MethodSuffix(type_name, method_name): + assert not type_name.native and not method_name.native + return type_name.CamelCase() + method_name.CamelCase() + + +def as_frontendType(metadata, typ): + if typ.category == 'object': + return typ.name.CamelCase() + 'Base*' + elif typ.category in ['bitmask', 'enum']: + return metadata.namespace + '::' + typ.name.CamelCase() + elif typ.category == 'structure': + return as_cppType(typ.name) + else: + return as_cType(metadata.c_prefix, typ.name) + + +def as_wireType(metadata, typ): + if typ.category == 'object': + return typ.name.CamelCase() + '*' + elif typ.category in ['bitmask', 'enum', 'structure']: + return metadata.c_prefix + typ.name.CamelCase() + else: + return as_cppType(typ.name) + + +def as_formatType(typ): + # Unsigned integral types + if typ.json_data['type'] in ['bool', 'uint32_t', 'uint64_t']: + return 'u' + + # Defaults everything else to strings. + return 's' + + +def c_methods(params, typ): + return typ.methods + [ + x for x in [ + Method(Name('reference'), params['types']['void'], [], + {'tags': ['dawn', 'emscripten']}), + Method(Name('release'), params['types']['void'], [], + {'tags': ['dawn', 'emscripten']}), + ] if item_is_enabled(params['enabled_tags'], x.json_data) + and not item_is_disabled(params['disabled_tags'], x.json_data) + ] + + +def get_c_methods_sorted_by_name(api_params): + unsorted = [(as_MethodSuffix(typ.name, method.name), typ, method) \ + for typ in api_params['by_category']['object'] \ + for method in c_methods(api_params, typ) ] + return [(typ, method) for (_, typ, method) in sorted(unsorted)] + + +def has_callback_arguments(method): + return any(arg.type.category == 'function pointer' for arg in method.arguments) + + +def make_base_render_params(metadata): + c_prefix = metadata.c_prefix + + def as_cTypeEnumSpecialCase(typ): + if typ.category == 'bitmask': + return as_cType(c_prefix, typ.name) + 'Flags' + return as_cType(c_prefix, typ.name) + + def as_cEnum(type_name, value_name): + assert not type_name.native and not value_name.native + return c_prefix + type_name.CamelCase() + '_' + value_name.CamelCase() + + def as_cMethod(type_name, method_name): + c_method = c_prefix.lower() + if type_name != None: + assert not type_name.native + c_method += type_name.CamelCase() + assert not method_name.native + c_method += method_name.CamelCase() + return c_method + + def as_cProc(type_name, method_name): + c_proc = c_prefix + 'Proc' + if type_name != None: + assert not type_name.native + c_proc += type_name.CamelCase() + assert not method_name.native + c_proc += method_name.CamelCase() + return c_proc + + return { + 'Name': lambda name: Name(name), + 'as_annotated_cType': \ + lambda arg: annotated(as_cTypeEnumSpecialCase(arg.type), arg), + 'as_annotated_cppType': \ + lambda arg: annotated(as_cppType(arg.type.name), arg), + 'as_cEnum': as_cEnum, + 'as_cppEnum': as_cppEnum, + 'as_cMethod': as_cMethod, + 'as_MethodSuffix': as_MethodSuffix, + 'as_cProc': as_cProc, + 'as_cType': lambda name: as_cType(c_prefix, name), + 'as_cppType': as_cppType, + 'as_jsEnumValue': as_jsEnumValue, + 'convert_cType_to_cppType': convert_cType_to_cppType, + 'as_varName': as_varName, + 'decorate': decorate, + 'as_formatType': as_formatType + } + + +class MultiGeneratorFromDawnJSON(Generator): + def get_description(self): + return 'Generates code for various target from Dawn.json.' + + def add_commandline_arguments(self, parser): + allowed_targets = [ + 'dawn_headers', 'cpp_headers', 'cpp', 'proc', 'mock_api', 'wire', + 'native_utils' + ] + + parser.add_argument('--dawn-json', + required=True, + type=str, + help='The DAWN JSON definition to use.') + parser.add_argument('--wire-json', + default=None, + type=str, + help='The DAWN WIRE JSON definition to use.') + parser.add_argument( + '--targets', + required=True, + type=str, + help= + 'Comma-separated subset of targets to output. Available targets: ' + + ', '.join(allowed_targets)) + def get_file_renders(self, args): + with open(args.dawn_json) as f: + loaded_json = json.loads(f.read()) + + targets = args.targets.split(',') + + wire_json = None + if args.wire_json: + with open(args.wire_json) as f: + wire_json = json.loads(f.read()) + + renders = [] + + params_dawn = parse_json(loaded_json, + enabled_tags=['dawn', 'native', 'deprecated']) + metadata = params_dawn['metadata'] + RENDER_PARAMS_BASE = make_base_render_params(metadata) + + api = metadata.api.lower() + prefix = metadata.proc_table_prefix.lower() + if 'headers' in targets: + renders.append( + FileRender('api.h', 'include/dawn/' + api + '.h', + [RENDER_PARAMS_BASE, params_dawn])) + renders.append( + FileRender('dawn_proc_table.h', + 'include/dawn/' + prefix + '_proc_table.h', + [RENDER_PARAMS_BASE, params_dawn])) + + if 'cpp_headers' in targets: + renders.append( + FileRender('api_cpp.h', 'include/dawn/' + api + '_cpp.h', + [RENDER_PARAMS_BASE, params_dawn])) + + renders.append( + FileRender('api_cpp_print.h', + 'include/dawn/' + api + '_cpp_print.h', + [RENDER_PARAMS_BASE, params_dawn])) + + if 'proc' in targets: + renders.append( + FileRender('dawn_proc.c', 'src/dawn/' + prefix + '_proc.c', + [RENDER_PARAMS_BASE, params_dawn])) + renders.append( + FileRender('dawn_thread_dispatch_proc.cpp', + 'src/dawn/' + prefix + '_thread_dispatch_proc.cpp', + [RENDER_PARAMS_BASE, params_dawn])) + + if 'webgpu_dawn_native_proc' in targets: + renders.append( + FileRender('dawn/native/api_dawn_native_proc.cpp', + 'src/dawn/native/webgpu_dawn_native_proc.cpp', + [RENDER_PARAMS_BASE, params_dawn])) + + if 'cpp' in targets: + renders.append( + FileRender('api_cpp.cpp', 'src/dawn/' + api + '_cpp.cpp', + [RENDER_PARAMS_BASE, params_dawn])) + + if 'webgpu_headers' in targets: + params_upstream = parse_json(loaded_json, + enabled_tags=['upstream', 'native'], + disabled_tags=['dawn']) + renders.append( + FileRender('api.h', 'webgpu-headers/' + api + '.h', + [RENDER_PARAMS_BASE, params_upstream])) + + if 'emscripten_bits' in targets: + params_emscripten = parse_json(loaded_json, + enabled_tags=['emscripten']) + renders.append( + FileRender('api.h', 'emscripten-bits/' + api + '.h', + [RENDER_PARAMS_BASE, params_emscripten])) + renders.append( + FileRender('api_cpp.h', 'emscripten-bits/' + api + '_cpp.h', + [RENDER_PARAMS_BASE, params_emscripten])) + renders.append( + FileRender('api_cpp.cpp', 'emscripten-bits/' + api + '_cpp.cpp', + [RENDER_PARAMS_BASE, params_emscripten])) + renders.append( + FileRender('api_struct_info.json', + 'emscripten-bits/' + api + '_struct_info.json', + [RENDER_PARAMS_BASE, params_emscripten])) + renders.append( + FileRender('library_api_enum_tables.js', + 'emscripten-bits/library_' + api + '_enum_tables.js', + [RENDER_PARAMS_BASE, params_emscripten])) + + if 'mock_api' in targets: + mock_params = [ + RENDER_PARAMS_BASE, params_dawn, { + 'has_callback_arguments': has_callback_arguments + } + ] + renders.append( + FileRender('mock_api.h', 'src/dawn/mock_' + api + '.h', + mock_params)) + renders.append( + FileRender('mock_api.cpp', 'src/dawn/mock_' + api + '.cpp', + mock_params)) + + if 'native_utils' in targets: + frontend_params = [ + RENDER_PARAMS_BASE, + params_dawn, + { + # TODO: as_frontendType and co. take a Type, not a Name :( + 'as_frontendType': lambda typ: as_frontendType(metadata, typ), + 'as_annotated_frontendType': \ + lambda arg: annotated(as_frontendType(metadata, arg.type), arg), + } + ] + + impl_dir = metadata.impl_dir + '/' if metadata.impl_dir else '' + native_dir = impl_dir + Name(metadata.native_namespace).Dirs() + namespace = metadata.namespace + renders.append( + FileRender('dawn/native/ValidationUtils.h', + 'src/' + native_dir + '/ValidationUtils_autogen.h', + frontend_params)) + renders.append( + FileRender('dawn/native/ValidationUtils.cpp', + 'src/' + native_dir + '/ValidationUtils_autogen.cpp', + frontend_params)) + renders.append( + FileRender('dawn/native/dawn_platform.h', + 'src/' + native_dir + '/' + prefix + '_platform_autogen.h', + frontend_params)) + renders.append( + FileRender('dawn/native/api_structs.h', + 'src/' + native_dir + '/' + namespace + '_structs_autogen.h', + frontend_params)) + renders.append( + FileRender('dawn/native/api_structs.cpp', + 'src/' + native_dir + '/' + namespace + '_structs_autogen.cpp', + frontend_params)) + renders.append( + FileRender('dawn/native/ProcTable.cpp', + 'src/' + native_dir + '/ProcTable.cpp', frontend_params)) + renders.append( + FileRender('dawn/native/ChainUtils.h', + 'src/' + native_dir + '/ChainUtils_autogen.h', + frontend_params)) + renders.append( + FileRender('dawn/native/ChainUtils.cpp', + 'src/' + native_dir + '/ChainUtils_autogen.cpp', + frontend_params)) + renders.append( + FileRender('dawn/native/api_absl_format.h', + 'src/' + native_dir + '/' + api + '_absl_format_autogen.h', + frontend_params)) + renders.append( + FileRender('dawn/native/api_absl_format.cpp', + 'src/' + native_dir + '/' + api + '_absl_format_autogen.cpp', + frontend_params)) + renders.append( + FileRender('dawn/native/ObjectType.h', + 'src/' + native_dir + '/ObjectType_autogen.h', + frontend_params)) + renders.append( + FileRender('dawn/native/ObjectType.cpp', + 'src/' + native_dir + '/ObjectType_autogen.cpp', + frontend_params)) + + if 'wire' in targets: + params_dawn_wire = parse_json(loaded_json, + enabled_tags=['dawn', 'deprecated'], + disabled_tags=['native']) + additional_params = compute_wire_params(params_dawn_wire, + wire_json) + + wire_params = [ + RENDER_PARAMS_BASE, params_dawn_wire, { + 'as_wireType': lambda type : as_wireType(metadata, type), + 'as_annotated_wireType': \ + lambda arg: annotated(as_wireType(metadata, arg.type), arg), + }, additional_params + ] + renders.append( + FileRender('dawn/wire/ObjectType.h', + 'src/dawn/wire/ObjectType_autogen.h', wire_params)) + renders.append( + FileRender('dawn/wire/WireCmd.h', + 'src/dawn/wire/WireCmd_autogen.h', wire_params)) + renders.append( + FileRender('dawn/wire/WireCmd.cpp', + 'src/dawn/wire/WireCmd_autogen.cpp', wire_params)) + renders.append( + FileRender('dawn/wire/client/ApiObjects.h', + 'src/dawn/wire/client/ApiObjects_autogen.h', + wire_params)) + renders.append( + FileRender('dawn/wire/client/ApiProcs.cpp', + 'src/dawn/wire/client/ApiProcs_autogen.cpp', + wire_params)) + renders.append( + FileRender('dawn/wire/client/ClientBase.h', + 'src/dawn/wire/client/ClientBase_autogen.h', + wire_params)) + renders.append( + FileRender('dawn/wire/client/ClientHandlers.cpp', + 'src/dawn/wire/client/ClientHandlers_autogen.cpp', + wire_params)) + renders.append( + FileRender( + 'dawn/wire/client/ClientPrototypes.inc', + 'src/dawn/wire/client/ClientPrototypes_autogen.inc', + wire_params)) + renders.append( + FileRender('dawn/wire/server/ServerBase.h', + 'src/dawn/wire/server/ServerBase_autogen.h', + wire_params)) + renders.append( + FileRender('dawn/wire/server/ServerDoers.cpp', + 'src/dawn/wire/server/ServerDoers_autogen.cpp', + wire_params)) + renders.append( + FileRender('dawn/wire/server/ServerHandlers.cpp', + 'src/dawn/wire/server/ServerHandlers_autogen.cpp', + wire_params)) + renders.append( + FileRender( + 'dawn/wire/server/ServerPrototypes.inc', + 'src/dawn/wire/server/ServerPrototypes_autogen.inc', + wire_params)) + + return renders + + def get_dependencies(self, args): + deps = [os.path.abspath(args.dawn_json)] + if args.wire_json != None: + deps += [os.path.abspath(args.wire_json)] + return deps + + +if __name__ == '__main__': + sys.exit(run_generator(MultiGeneratorFromDawnJSON()))
diff --git a/generator/dawn_version_generator.py b/generator/dawn_version_generator.py new file mode 100644 index 0000000..1907e88 --- /dev/null +++ b/generator/dawn_version_generator.py
@@ -0,0 +1,113 @@ +#!/usr/bin/env python3 +# Copyright 2022 The Dawn Authors +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +import os, subprocess, sys + +from generator_lib import Generator, run_generator, FileRender + + +def get_git(): + return 'git.bat' if sys.platform == 'win32' else 'git' + + +def get_gitHash(dawnDir): + result = subprocess.run([get_git(), 'rev-parse', 'HEAD'], + stdout=subprocess.PIPE, + cwd=dawnDir) + if result.returncode == 0: + return result.stdout.decode('utf-8').strip() + # No hash was available (possibly) because the directory was not a git checkout. Dawn should + # explicitly handle its absenece and disable features relying on the hash, i.e. caching. + return '' + + +def get_gitHead(dawnDir): + return os.path.join(dawnDir, '.git', 'HEAD') + + +def gitExists(dawnDir): + return os.path.exists(get_gitHead(dawnDir)) + + +def unpackGitRef(packed, resolved): + with open(packed) as fin: + refs = fin.read().strip().split('\n') + + # Strip comments + refs = [ref.split(' ') for ref in refs if ref.strip()[0] != '#'] + + # Parse results which are in the format [<gitHash>, <refFile>] from previous step. + refs = [gitHash for (gitHash, refFile) in refs if refFile == resolved] + if len(refs) == 1: + with open(resolved, 'w') as fout: + fout.write(refs[0] + '\n') + return True + return False + + +def get_gitResolvedHead(dawnDir): + result = subprocess.run( + [get_git(), 'rev-parse', '--symbolic-full-name', 'HEAD'], + stdout=subprocess.PIPE, + cwd=dawnDir) + if result.returncode != 0: + raise Exception('Failed to execute git rev-parse to resolve git head.') + + resolved = os.path.join(dawnDir, '.git', + result.stdout.decode('utf-8').strip()) + + # Check a packed-refs file exists. If so, we need to potentially unpack and include it as a dep. + packed = os.path.join(dawnDir, '.git', 'packed-refs') + if os.path.exists(packed) and unpackGitRef(packed, resolved): + return [packed, resolved] + + if not os.path.exists(resolved): + raise Exception('Unable to resolve git HEAD hash file:', path) + return [resolved] + + +def compute_params(args): + return { + 'get_gitHash': lambda: get_gitHash(os.path.abspath(args.dawn_dir)), + } + + +class DawnVersionGenerator(Generator): + def get_description(self): + return 'Generates version dependent Dawn code. Currently regenerated dependent on git hash.' + + def add_commandline_arguments(self, parser): + parser.add_argument('--dawn-dir', + required=True, + type=str, + help='The Dawn root directory path to use') + + def get_dependencies(self, args): + dawnDir = os.path.abspath(args.dawn_dir) + if gitExists(dawnDir): + return [get_gitHead(dawnDir)] + get_gitResolvedHead(dawnDir) + return [] + + def get_file_renders(self, args): + params = compute_params(args) + + return [ + FileRender('dawn/common/Version.h', + 'src/dawn/common/Version_autogen.h', [params]), + ] + + +if __name__ == '__main__': + sys.exit(run_generator(DawnVersionGenerator()))
diff --git a/generator/extract_json.py b/generator/extract_json.py new file mode 100644 index 0000000..67114bf --- /dev/null +++ b/generator/extract_json.py
@@ -0,0 +1,44 @@ +#!/usr/bin/env python3 +# Copyright 2018 The Dawn Authors +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +import os, sys, json + +if __name__ == "__main__": + if len(sys.argv) != 3: + print("Usage: extract_json.py JSON DIR") + sys.exit(1) + + with open(sys.argv[1]) as f: + files = json.loads(f.read()) + + output_dir = sys.argv[2] + + for (name, content) in files.items(): + output_file = output_dir + os.path.sep + name + + # Create the output directory if needed. + directory = os.path.dirname(output_file) + if not os.path.exists(directory): + os.makedirs(directory) + + # Skip writing to the file if it already has the correct content. + try: + with open(output_file, 'r') as outfile: + if outfile.read() == content: + continue + except (OSError, EnvironmentError): + pass + + with open(output_file, 'w') as outfile: + outfile.write(content)
diff --git a/generator/generator_lib.gni b/generator/generator_lib.gni new file mode 100644 index 0000000..8b9e04c --- /dev/null +++ b/generator/generator_lib.gni
@@ -0,0 +1,164 @@ +# Copyright 2019 The Dawn Authors +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +# Template to help invoking code generators based on generator_lib.py +# Internal use only, this should only be called from templates implementing +# generator-specific actions. +# +# Variables: +# script: Path to generator script. +# +# args: List of extra command-line arguments passed to the generator. +# +# outputs: List of expected outputs, generation will fail if there is a +# mistmatch. +# +# deps: additional deps for the code generation targets. +# +# generator_lib_dir: directory where generator_lib.py is located. +# +# custom_gen_dir: Optional custom target gen dir. Defaults to $target_gen_dir +# but allows output files to not depend on the location of the BUILD.gn +# that generates them. +# +# template_dir: Optional template root directory. Defaults to +# "${generator_lib_dir}/templates". +# +# jinja2_path: Optional Jinja2 installation path. +# +# allowed_output_dirs: Optional list of directories that are the only +# directories in which files of `outputs` are allowed to be (and not +# in children directories). Generation will fail if an output isn't +# in a directory in the list. +# +# root_dir: Optional root source dir for Python dependencies +# computation. Defaults to "${generator_lib_dir}/..". Any dependency +# outside of this directory is considered a system file and will be +# omitted. +# +template("generator_lib_action") { + _generator_args = [] + if (defined(invoker.args)) { + _generator_args += invoker.args + } + + assert(defined(invoker.generator_lib_dir), + "generator_lib_dir must be defined before calling this action!") + + _template_dir = "${invoker.generator_lib_dir}/templates" + if (defined(invoker.template_dir)) { + _template_dir = invoker.template_dir + } + _generator_args += [ + "--template-dir", + rebase_path(_template_dir), + ] + + if (defined(invoker.root_dir)) { + _generator_args += [ + "--root-dir", + rebase_path(_root_dir, root_build_dir), + ] + } + + if (defined(invoker.jinja2_path)) { + _generator_args += [ + "--jinja2-path", + rebase_path(invoker.jinja2_path), + ] + } + + # Chooses either the default gen_dir or the custom one required by the + # invoker. This allows moving the definition of code generators in different + # BUILD.gn files without changing the location of generated file. Without + # this generated headers could cause issues when old headers aren't removed. + _gen_dir = target_gen_dir + if (defined(invoker.custom_gen_dir)) { + _gen_dir = invoker.custom_gen_dir + } + + # For build parallelism GN wants to know the exact inputs and outputs of + # action targets like we use for our code generator. We avoid asking the + # generator about its inputs by using the "depfile" feature of GN/Ninja. + # + # A ninja limitation is that the depfile is a subset of Makefile that can + # contain a single target, so we output a single "JSON-tarball" instead. + _json_tarball = "${_gen_dir}/${target_name}.json_tarball" + _json_tarball_target = "${target_name}__json_tarball" + _json_tarball_depfile = "${_json_tarball}.d" + + _generator_args += [ + "--output-json-tarball", + rebase_path(_json_tarball, root_build_dir), + "--depfile", + rebase_path(_json_tarball_depfile, root_build_dir), + ] + + # After the JSON tarball is created we need an action target to extract it + # with a list of its outputs. The invoker provided a list of expected + # outputs. To make sure the list is in sync between the generator and the + # build files, we write it to a file and ask the generator to assert it is + # correct. + _expected_outputs_file = "${_gen_dir}/${target_name}.expected_outputs" + write_file(_expected_outputs_file, invoker.outputs) + + _generator_args += [ + "--expected-outputs-file", + rebase_path(_expected_outputs_file, root_build_dir), + ] + + # Check that all of the outputs are in a directory that's allowed. This is + # useful to keep the list of directories in sink with other parts of the + # build. + if (defined(invoker.allowed_output_dirs)) { + _allowed_output_dirs_file = "${_gen_dir}/${target_name}.allowed_output_dirs" + write_file(_allowed_output_dirs_file, invoker.allowed_output_dirs) + + _generator_args += [ + "--allowed-output-dirs-file", + rebase_path(_allowed_output_dirs_file, root_build_dir), + ] + } + + # The code generator invocation that will write the JSON tarball, check the + # outputs are what's expected and write a depfile for Ninja. + action(_json_tarball_target) { + script = invoker.script + outputs = [ _json_tarball ] + depfile = _json_tarball_depfile + args = _generator_args + if (defined(invoker.deps)) { + deps = invoker.deps + } + } + + # Extract the JSON tarball into the gen_dir + action(target_name) { + script = "${invoker.generator_lib_dir}/extract_json.py" + args = [ + rebase_path(_json_tarball, root_build_dir), + rebase_path(_gen_dir, root_build_dir), + ] + + deps = [ ":${_json_tarball_target}" ] + inputs = [ _json_tarball ] + + # The expected output list is relative to the gen_dir but action + # target outputs are from the root dir so we need to rebase them. + outputs = [] + foreach(source, invoker.outputs) { + outputs += [ "${_gen_dir}/${source}" ] + } + } +}
diff --git a/generator/generator_lib.py b/generator/generator_lib.py new file mode 100644 index 0000000..11b3ed2 --- /dev/null +++ b/generator/generator_lib.py
@@ -0,0 +1,374 @@ +#!/usr/bin/env python3 +# Copyright 2019 The Dawn Authors +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +"""Module to create generators that render multiple Jinja2 templates for GN. + +A helper module that can be used to create generator scripts (clients) +that expand one or more Jinja2 templates, without outputs usable from +GN and Ninja build-based systems. See generator_lib.gni as well. + +Clients should create a Generator sub-class, then call run_generator() +with a proper derived class instance. + +Clients specify a list of FileRender operations, each one of them will +output a file into a temporary output directory through Jinja2 expansion. +All temporary output files are then grouped and written to into a single JSON +file, that acts as a convenient single GN output target. Use extract_json.py +to extract the output files from the JSON tarball in another GN action. + +--depfile can be used to specify an output Ninja dependency file for the +JSON tarball, to ensure it is regenerated any time one of its dependencies +changes. + +Finally, --expected-output-files can be used to check the list of generated +output files. +""" + +import argparse, json, os, re, sys +from collections import namedtuple + +# A FileRender represents a single Jinja2 template render operation: +# +# template: Jinja2 template name, relative to --template-dir path. +# +# output: Output file path, relative to temporary output directory. +# +# params_dicts: iterable of (name:string -> value:string) dictionaries. +# All of them will be merged before being sent as Jinja2 template +# expansion parameters. +# +# Example: +# FileRender('api.c', 'src/project_api.c', [{'PROJECT_VERSION': '1.0.0'}]) +# +FileRender = namedtuple('FileRender', ['template', 'output', 'params_dicts']) + + +# The interface that must be implemented by generators. +class Generator: + def get_description(self): + """Return generator description for --help.""" + return "" + + def add_commandline_arguments(self, parser): + """Add generator-specific argparse arguments.""" + pass + + def get_file_renders(self, args): + """Return the list of FileRender objects to process.""" + return [] + + def get_dependencies(self, args): + """Return a list of extra input dependencies.""" + return [] + + +# Allow custom Jinja2 installation path through an additional python +# path from the arguments if present. This isn't done through the regular +# argparse because PreprocessingLoader uses jinja2 in the global scope before +# "main" gets to run. +# +# NOTE: If this argument appears several times, this only uses the first +# value, while argparse would typically keep the last one! +kJinja2Path = '--jinja2-path' +try: + jinja2_path_argv_index = sys.argv.index(kJinja2Path) + # Add parent path for the import to succeed. + path = os.path.join(sys.argv[jinja2_path_argv_index + 1], os.pardir) + sys.path.insert(1, path) +except ValueError: + # --jinja2-path isn't passed, ignore the exception and just import Jinja2 + # assuming it already is in the Python PATH. + pass + +import jinja2 + + +# A custom Jinja2 template loader that removes the extra indentation +# of the template blocks so that the output is correctly indented +class _PreprocessingLoader(jinja2.BaseLoader): + def __init__(self, path): + self.path = path + + def get_source(self, environment, template): + path = os.path.join(self.path, template) + if not os.path.exists(path): + raise jinja2.TemplateNotFound(template) + mtime = os.path.getmtime(path) + with open(path) as f: + source = self.preprocess(f.read()) + return source, path, lambda: mtime == os.path.getmtime(path) + + blockstart = re.compile('{%-?\s*(if|elif|else|for|block|macro)[^}]*%}') + blockend = re.compile('{%-?\s*(end(if|for|block|macro)|elif|else)[^}]*%}') + + def preprocess(self, source): + lines = source.split('\n') + + # Compute the current indentation level of the template blocks and + # remove their indentation + result = [] + indentation_level = 0 + + # Filter lines that are pure comments. line_comment_prefix is not + # enough because it removes the comment but doesn't completely remove + # the line, resulting in more verbose output. + lines = filter(lambda line: not line.strip().startswith('//*'), lines) + + # Remove indentation templates have for the Jinja control flow. + for line in lines: + # The capture in the regex adds one element per block start or end, + # so we divide by two. There is also an extra line chunk + # corresponding to the line end, so we subtract it. + numends = (len(self.blockend.split(line)) - 1) // 2 + indentation_level -= numends + + result.append(self.remove_indentation(line, indentation_level)) + + numstarts = (len(self.blockstart.split(line)) - 1) // 2 + indentation_level += numstarts + + return '\n'.join(result) + '\n' + + def remove_indentation(self, line, n): + for _ in range(n): + if line.startswith(' '): + line = line[4:] + elif line.startswith('\t'): + line = line[1:] + else: + assert line.strip() == '' + return line + + +_FileOutput = namedtuple('FileOutput', ['name', 'content']) + + +def _do_renders(renders, template_dir): + loader = _PreprocessingLoader(template_dir) + env = jinja2.Environment(extensions=['jinja2.ext.do'], + loader=loader, + lstrip_blocks=True, + trim_blocks=True, + line_comment_prefix='//*') + + def do_assert(expr): + assert expr + return '' + + def debug(text): + print(text) + + base_params = { + 'enumerate': enumerate, + 'format': format, + 'len': len, + 'debug': debug, + 'assert': do_assert, + } + + outputs = [] + for render in renders: + params = {} + params.update(base_params) + for param_dict in render.params_dicts: + params.update(param_dict) + content = env.get_template(render.template).render(**params) + outputs.append(_FileOutput(render.output, content)) + + return outputs + + +# Compute the list of imported, non-system Python modules. +# It assumes that any path outside of the root directory is system. +def _compute_python_dependencies(root_dir=None): + if not root_dir: + # Assume this script is under generator/ by default. + root_dir = os.path.join(os.path.dirname(__file__), os.pardir) + root_dir = os.path.abspath(root_dir) + + module_paths = (module.__file__ for module in sys.modules.values() + if module and hasattr(module, '__file__')) + + paths = set() + for path in module_paths: + # Builtin/namespaced modules may return None for the file path. + if not path: + continue + + path = os.path.abspath(path) + + if not path.startswith(root_dir): + continue + + if (path.endswith('.pyc') + or (path.endswith('c') and not os.path.splitext(path)[1])): + path = path[:-1] + + paths.add(path) + + return paths + + +def run_generator(generator): + parser = argparse.ArgumentParser( + description=generator.get_description(), + formatter_class=argparse.ArgumentDefaultsHelpFormatter, + ) + + generator.add_commandline_arguments(parser) + parser.add_argument('--template-dir', + default='templates', + type=str, + help='Directory with template files.') + parser.add_argument( + kJinja2Path, + default=None, + type=str, + help='Additional python path to set before loading Jinja2') + parser.add_argument( + '--output-json-tarball', + default=None, + type=str, + help=('Name of the "JSON tarball" to create (tar is too annoying ' + 'to use in python).')) + parser.add_argument( + '--depfile', + default=None, + type=str, + help='Name of the Ninja depfile to create for the JSON tarball') + parser.add_argument( + '--expected-outputs-file', + default=None, + type=str, + help="File to compare outputs with and fail if it doesn't match") + parser.add_argument( + '--root-dir', + default=None, + type=str, + help=('Optional source root directory for Python dependency ' + 'computations')) + parser.add_argument( + '--allowed-output-dirs-file', + default=None, + type=str, + help=("File containing a list of allowed directories where files " + "can be output.")) + parser.add_argument( + '--print-cmake-dependencies', + default=False, + action="store_true", + help=("Prints a semi-colon separated list of dependencies to " + "stdout and exits.")) + parser.add_argument( + '--print-cmake-outputs', + default=False, + action="store_true", + help=("Prints a semi-colon separated list of outputs to " + "stdout and exits.")) + parser.add_argument('--output-dir', + default=None, + type=str, + help='Directory where to output generate files.') + + args = parser.parse_args() + + renders = generator.get_file_renders(args) + + # Output a list of all dependencies for CMake or the tarball for GN/Ninja. + if args.depfile != None or args.print_cmake_dependencies: + dependencies = generator.get_dependencies(args) + dependencies += [ + args.template_dir + os.path.sep + render.template + for render in renders + ] + dependencies += _compute_python_dependencies(args.root_dir) + + if args.depfile != None: + with open(args.depfile, 'w') as f: + f.write(args.output_json_tarball + ": " + + " ".join(dependencies)) + + if args.print_cmake_dependencies: + sys.stdout.write(";".join(dependencies)) + return 0 + + # The caller wants to assert that the outputs are what it expects. + # Load the file and compare with our renders. + if args.expected_outputs_file != None: + with open(args.expected_outputs_file) as f: + expected = set([line.strip() for line in f.readlines()]) + + actual = {render.output for render in renders} + + if actual != expected: + print("Wrong expected outputs, caller expected:\n " + + repr(sorted(expected))) + print("Actual output:\n " + repr(sorted(actual))) + return 1 + + # Print the list of all the outputs for cmake. + if args.print_cmake_outputs: + sys.stdout.write(";".join([ + os.path.join(args.output_dir, render.output) for render in renders + ])) + return 0 + + outputs = _do_renders(renders, args.template_dir) + + # The caller wants to assert that the outputs are only in specific + # directories. + if args.allowed_output_dirs_file != None: + with open(args.allowed_output_dirs_file) as f: + allowed_dirs = set([line.strip() for line in f.readlines()]) + + for directory in allowed_dirs: + if not directory.endswith('/'): + print('Allowed directory entry "{}" doesn\'t ' + 'end with /'.format(directory)) + return 1 + + def check_in_subdirectory(path, directory): + return path.startswith( + directory) and not '/' in path[len(directory):] + + for render in renders: + if not any( + check_in_subdirectory(render.output, directory) + for directory in allowed_dirs): + print('Output file "{}" is not in the allowed directory ' + 'list below:'.format(render.output)) + for directory in sorted(allowed_dirs): + print(' "{}"'.format(directory)) + return 1 + + # Output the JSON tarball + if args.output_json_tarball != None: + json_root = {} + for output in outputs: + json_root[output.name] = output.content + + with open(args.output_json_tarball, 'w') as f: + f.write(json.dumps(json_root)) + + # Output the files directly. + if args.output_dir != None: + for output in outputs: + output_path = os.path.join(args.output_dir, output.name) + + directory = os.path.dirname(output_path) + if not os.path.exists(directory): + os.makedirs(directory) + + with open(output_path, 'w') as outfile: + outfile.write(output.content)
diff --git a/generator/opengl_loader_generator.py b/generator/opengl_loader_generator.py new file mode 100644 index 0000000..db253e2 --- /dev/null +++ b/generator/opengl_loader_generator.py
@@ -0,0 +1,282 @@ +#!/usr/bin/env python3 +# Copyright 2019 The Dawn Authors +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +import os, json, sys +from collections import namedtuple +import xml.etree.ElementTree as etree + +from generator_lib import Generator, run_generator, FileRender + + +class ProcName: + def __init__(self, gl_name, proc_name=None): + assert gl_name.startswith('gl') + if proc_name == None: + proc_name = gl_name[2:] + + self.gl_name = gl_name + self.proc_name = proc_name + + def glProcName(self): + return self.gl_name + + def ProcName(self): + return self.proc_name + + def PFNPROCNAME(self): + return 'PFN' + self.gl_name.upper() + 'PROC' + + def __repr__(self): + return 'Proc("{}", "{}")'.format(self.gl_name, self.proc_name) + + +ProcParam = namedtuple('ProcParam', ['name', 'type']) + + +class Proc: + def __init__(self, element): + # Type declaration for return values and arguments all have the same + # (weird) format. + # <element>[A][<ptype>B</ptype>][C]<other stuff.../></element> + # + # Some examples are: + # <proto>void <name>glFinish</name></proto> + # <proto><ptype>GLenum</ptype><name>glFenceSync</name></proto> + # <proto>const <ptype>GLubyte</ptype> *<name>glGetString</name></proto> + # + # This handles all the shapes found in gl.xml except for this one that + # has an array specifier after </name>: + # <param><ptype>GLuint</ptype> <name>baseAndCount</name>[2]</param> + def parse_type_declaration(element): + result = '' + if element.text != None: + result += element.text + ptype = element.find('ptype') + if ptype != None: + result += ptype.text + if ptype.tail != None: + result += ptype.tail + return result.strip() + + proto = element.find('proto') + + self.return_type = parse_type_declaration(proto) + + self.params = [] + for param in element.findall('./param'): + self.params.append( + ProcParam( + param.find('name').text, parse_type_declaration(param))) + + self.gl_name = proto.find('name').text + self.alias = None + if element.find('alias') != None: + self.alias = element.find('alias').attrib['name'] + + def glProcName(self): + return self.gl_name + + def ProcName(self): + assert self.gl_name.startswith('gl') + return self.gl_name[2:] + + def PFNGLPROCNAME(self): + return 'PFN' + self.gl_name.upper() + 'PROC' + + def __repr__(self): + return 'Proc("{}")'.format(self.gl_name) + + +EnumDefine = namedtuple('EnumDefine', ['name', 'value']) +Version = namedtuple('Version', ['major', 'minor']) +VersionBlock = namedtuple('VersionBlock', ['version', 'procs', 'enums']) +HeaderBlock = namedtuple('HeaderBlock', ['description', 'procs', 'enums']) +ExtensionBlock = namedtuple('ExtensionBlock', + ['extension', 'procs', 'enums', 'supported_specs']) + + +def parse_version(version): + return Version(*map(int, version.split('.'))) + + +def compute_params(root, supported_extensions): + # Parse all the commands and enums + all_procs = {} + for command in root.findall('''commands[@namespace='GL']/command'''): + proc = Proc(command) + assert proc.gl_name not in all_procs + all_procs[proc.gl_name] = proc + + all_enums = {} + for enum in root.findall('''enums[@namespace='GL']/enum'''): + enum_name = enum.attrib['name'] + # Special case an enum we'll never use that has different values in GL and GLES + if enum_name == 'GL_ACTIVE_PROGRAM_EXT': + continue + + assert enum_name not in all_enums + all_enums[enum_name] = EnumDefine(enum_name, enum.attrib['value']) + + # Get the list of all Desktop OpenGL function removed by the Core Profile. + core_removed_procs = set() + for proc in root.findall('''feature/remove[@profile='core']/command'''): + core_removed_procs.add(proc.attrib['name']) + + # Get list of enums and procs per OpenGL ES/Desktop OpenGL version + def parse_version_blocks(api, removed_procs=set()): + blocks = [] + for section in root.findall('''feature[@api='{}']'''.format(api)): + section_procs = [] + for command in section.findall('./require/command'): + proc_name = command.attrib['name'] + assert all_procs[proc_name].alias == None + if proc_name not in removed_procs: + section_procs.append(all_procs[proc_name]) + + section_enums = [] + for enum in section.findall('./require/enum'): + section_enums.append(all_enums[enum.attrib['name']]) + + blocks.append( + VersionBlock(parse_version(section.attrib['number']), + section_procs, section_enums)) + + return blocks + + gles_blocks = parse_version_blocks('gles2') + desktop_gl_blocks = parse_version_blocks('gl', core_removed_procs) + + def parse_extension_block(extension): + section = root.find( + '''extensions/extension[@name='{}']'''.format(extension)) + supported_specs = section.attrib['supported'].split('|') + section_procs = [] + for command in section.findall('./require/command'): + proc_name = command.attrib['name'] + assert all_procs[proc_name].alias == None + section_procs.append(all_procs[proc_name]) + + section_enums = [] + for enum in section.findall('./require/enum'): + section_enums.append(all_enums[enum.attrib['name']]) + + return ExtensionBlock(extension, section_procs, section_enums, + supported_specs) + + extension_desktop_gl_blocks = [] + extension_gles_blocks = [] + for extension in supported_extensions: + extension_block = parse_extension_block(extension) + if 'gl' in extension_block.supported_specs: + extension_desktop_gl_blocks.append(extension_block) + if 'gles2' in extension_block.supported_specs: + extension_gles_blocks.append(extension_block) + + # Compute the blocks for headers such that there is no duplicate definition + already_added_header_procs = set() + already_added_header_enums = set() + header_blocks = [] + + def add_header_block(description, block): + block_procs = [] + for proc in block.procs: + if not proc.glProcName() in already_added_header_procs: + already_added_header_procs.add(proc.glProcName()) + block_procs.append(proc) + + block_enums = [] + for enum in block.enums: + if not enum.name in already_added_header_enums: + already_added_header_enums.add(enum.name) + block_enums.append(enum) + + if len(block_procs) > 0 or len(block_enums) > 0: + header_blocks.append( + HeaderBlock(description, block_procs, block_enums)) + + for block in gles_blocks: + add_header_block( + 'OpenGL ES {}.{}'.format(block.version.major, block.version.minor), + block) + + for block in desktop_gl_blocks: + add_header_block( + 'Desktop OpenGL {}.{}'.format(block.version.major, + block.version.minor), block) + + for block in extension_desktop_gl_blocks: + add_header_block(block.extension, block) + + for block in extension_gles_blocks: + add_header_block(block.extension, block) + + return { + 'gles_blocks': gles_blocks, + 'desktop_gl_blocks': desktop_gl_blocks, + 'extension_desktop_gl_blocks': extension_desktop_gl_blocks, + 'extension_gles_blocks': extension_gles_blocks, + 'header_blocks': header_blocks, + } + + +class OpenGLLoaderGenerator(Generator): + def get_description(self): + return 'Generates code to load OpenGL function pointers' + + def add_commandline_arguments(self, parser): + parser.add_argument('--gl-xml', + required=True, + type=str, + help='The Khronos gl.xml to use.') + parser.add_argument( + '--supported-extensions', + required=True, + type=str, + help= + 'The JSON file that defines the OpenGL and GLES extensions to use.' + ) + + def get_file_renders(self, args): + supported_extensions = [] + with open(args.supported_extensions) as f: + supported_extensions_json = json.loads(f.read()) + supported_extensions = supported_extensions_json[ + 'supported_extensions'] + + params = compute_params( + etree.parse(args.gl_xml).getroot(), supported_extensions) + + return [ + FileRender( + 'opengl/OpenGLFunctionsBase.cpp', + 'src/dawn/native/opengl/OpenGLFunctionsBase_autogen.cpp', + [params]), + FileRender('opengl/OpenGLFunctionsBase.h', + 'src/dawn/native/opengl/OpenGLFunctionsBase_autogen.h', + [params]), + FileRender('opengl/opengl_platform.h', + 'src/dawn/native/opengl/opengl_platform_autogen.h', + [params]), + ] + + def get_dependencies(self, args): + return [ + os.path.abspath(args.gl_xml), + os.path.abspath(args.supported_extensions) + ] + + +if __name__ == '__main__': + sys.exit(run_generator(OpenGLLoaderGenerator()))
diff --git a/generator/remove_files.py b/generator/remove_files.py new file mode 100644 index 0000000..6ddf463 --- /dev/null +++ b/generator/remove_files.py
@@ -0,0 +1,91 @@ +#!/usr/bin/env python3 +# Copyright 2019 The Dawn Authors +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +import argparse, glob, os, sys + + +def check_in_subdirectory(path, directory): + return path.startswith(directory) and not '/' in path[len(directory):] + + +def check_is_allowed(path, allowed_dirs): + return any( + check_in_subdirectory(path, directory) for directory in allowed_dirs) + + +def get_all_files_in_dir(find_directory): + result = [] + for (directory, _, files) in os.walk(find_directory): + result += [os.path.join(directory, filename) for filename in files] + return result + + +def run(): + # Parse command line arguments + parser = argparse.ArgumentParser( + description="Removes stale autogenerated files from gen/ directories.") + parser.add_argument( + '--root-dir', + type=str, + help='The root directory, all other paths in files are relative to it.' + ) + parser.add_argument( + '--allowed-output-dirs-file', + type=str, + help='The file containing a list of allowed directories') + parser.add_argument( + '--stale-dirs-file', + type=str, + help= + 'The file containing a list of directories to check for stale files') + parser.add_argument('--stamp', + type=str, + help='A stamp written once this script completes') + args = parser.parse_args() + + root_dir = args.root_dir + stamp_file = args.stamp + + # Load the list of allowed and stale directories + with open(args.allowed_output_dirs_file) as f: + allowed_dirs = set( + [os.path.join(root_dir, line.strip()) for line in f.readlines()]) + + for directory in allowed_dirs: + if not directory.endswith('/'): + print('Allowed directory entry "{}" doesn\'t end with /'.format( + directory)) + return 1 + + with open(args.stale_dirs_file) as f: + stale_dirs = set([line.strip() for line in f.readlines()]) + + # Remove all files in stale dirs that aren't in the allowed dirs. + for stale_dir in stale_dirs: + stale_dir = os.path.join(root_dir, stale_dir) + + for candidate in get_all_files_in_dir(stale_dir): + if not check_is_allowed(candidate, allowed_dirs): + os.remove(candidate) + + # Finished! Write the stamp file so ninja knows to not run this again. + with open(stamp_file, "w") as f: + f.write("") + + return 0 + + +if __name__ == "__main__": + sys.exit(run())
diff --git a/generator/templates/.clang-format b/generator/templates/.clang-format new file mode 100644 index 0000000..9d15924 --- /dev/null +++ b/generator/templates/.clang-format
@@ -0,0 +1,2 @@ +DisableFormat: true +SortIncludes: false
diff --git a/generator/templates/BSD_LICENSE b/generator/templates/BSD_LICENSE new file mode 100644 index 0000000..eaef87a --- /dev/null +++ b/generator/templates/BSD_LICENSE
@@ -0,0 +1,29 @@ +// BSD 3-Clause License +// +// Copyright (c) {{metadata.copyright_year}}, "{{metadata.api}} native" developers +// All rights reserved. +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are met: +// +// 1. Redistributions of source code must retain the above copyright notice, this +// list of conditions and the following disclaimer. +// +// 2. Redistributions in binary form must reproduce the above copyright notice, +// this list of conditions and the following disclaimer in the documentation +// and/or other materials provided with the distribution. +// +// 3. Neither the name of the copyright holder nor the names of its +// contributors may be used to endorse or promote products derived from +// this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +// AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +// DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE +// FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL +// DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR +// SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER +// CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, +// OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
diff --git a/generator/templates/api.h b/generator/templates/api.h new file mode 100644 index 0000000..db9d94a --- /dev/null +++ b/generator/templates/api.h
@@ -0,0 +1,181 @@ +//* This template itself is part of the Dawn source and follows Dawn's license, +//* which is Apache 2.0. +//* +//* The WebGPU native API is a joint project used by Google, Mozilla, and Apple. +//* It was agreed to use a BSD 3-Clause license so that it is GPLv2-compatible. +//* +//* As a result, the template comments using //* at the top of the file are +//* removed during generation such that the resulting file starts with the +//* BSD 3-Clause comment, which is inside BSD_LICENSE as included below. +//* +//* Copyright 2020 The Dawn Authors +//* +//* Licensed under the Apache License, Version 2.0 (the "License"); +//* you may not use this file except in compliance with the License. +//* You may obtain a copy of the License at +//* +//* http://www.apache.org/licenses/LICENSE-2.0 +//* +//* Unless required by applicable law or agreed to in writing, software +//* distributed under the License is distributed on an "AS IS" BASIS, +//* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +//* See the License for the specific language governing permissions and +//* limitations under the License. +//* +//* +{% include 'BSD_LICENSE' %} +{% if 'dawn' in enabled_tags %} + #ifdef __EMSCRIPTEN__ + #error "Do not include this header. Emscripten already provides headers needed for {{metadata.api}}." + #endif +{% endif %} +#ifndef {{metadata.api.upper()}}_H_ +#define {{metadata.api.upper()}}_H_ + +{% set c_prefix = metadata.c_prefix %} +#if defined({{c_prefix}}_SHARED_LIBRARY) +# if defined(_WIN32) +# if defined({{c_prefix}}_IMPLEMENTATION) +# define {{c_prefix}}_EXPORT __declspec(dllexport) +# else +# define {{c_prefix}}_EXPORT __declspec(dllimport) +# endif +# else // defined(_WIN32) +# if defined({{c_prefix}}_IMPLEMENTATION) +# define {{c_prefix}}_EXPORT __attribute__((visibility("default"))) +# else +# define {{c_prefix}}_EXPORT +# endif +# endif // defined(_WIN32) +#else // defined({{c_prefix}}_SHARED_LIBRARY) +# define {{c_prefix}}_EXPORT +#endif // defined({{c_prefix}}_SHARED_LIBRARY) + +#include <stdint.h> +#include <stddef.h> +#include <stdbool.h> + +{% for constant in by_category["constant"] %} + #define {{c_prefix}}_{{constant.name.SNAKE_CASE()}} {{constant.value}} +{% endfor %} + +typedef uint32_t {{c_prefix}}Flags; + +{% for type in by_category["object"] %} + typedef struct {{as_cType(type.name)}}Impl* {{as_cType(type.name)}}; +{% endfor %} + +{% for type in by_category["enum"] + by_category["bitmask"] %} + typedef enum {{as_cType(type.name)}} { + {% for value in type.values %} + {{as_cEnum(type.name, value.name)}} = 0x{{format(value.value, "08X")}}, + {% endfor %} + {{as_cEnum(type.name, Name("force32"))}} = 0x7FFFFFFF + } {{as_cType(type.name)}}; + {% if type.category == "bitmask" %} + typedef {{c_prefix}}Flags {{as_cType(type.name)}}Flags; + {% endif %} + +{% endfor -%} + +typedef struct {{c_prefix}}ChainedStruct { + struct {{c_prefix}}ChainedStruct const * next; + {{c_prefix}}SType sType; +} {{c_prefix}}ChainedStruct; + +typedef struct {{c_prefix}}ChainedStructOut { + struct {{c_prefix}}ChainedStructOut * next; + {{c_prefix}}SType sType; +} {{c_prefix}}ChainedStructOut; + +{% for type in by_category["structure"] %} + typedef struct {{as_cType(type.name)}} { + {% set Out = "Out" if type.output else "" %} + {% set const = "const " if not type.output else "" %} + {% if type.extensible %} + {{c_prefix}}ChainedStruct{{Out}} {{const}}* nextInChain; + {% endif %} + {% if type.chained %} + {{c_prefix}}ChainedStruct{{Out}} chain; + {% endif %} + {% for member in type.members %} + {{as_annotated_cType(member)}}; + {% endfor %} + } {{as_cType(type.name)}}; + +{% endfor %} +{% for typeDef in by_category["typedef"] %} + // {{as_cType(typeDef.name)}} is deprecated. + // Use {{as_cType(typeDef.type.name)}} instead. + typedef {{as_cType(typeDef.type.name)}} {{as_cType(typeDef.name)}}; + +{% endfor %} +#ifdef __cplusplus +extern "C" { +#endif + +{% for type in by_category["function pointer"] %} + typedef {{as_cType(type.return_type.name)}} (*{{as_cType(type.name)}})( + {%- if type.arguments == [] -%} + void + {%- else -%} + {%- for arg in type.arguments -%} + {% if not loop.first %}, {% endif %}{{as_annotated_cType(arg)}} + {%- endfor -%} + {%- endif -%} + ); +{% endfor %} + +#if !defined({{c_prefix}}_SKIP_PROCS) + +{% for function in by_category["function"] %} + typedef {{as_cType(function.return_type.name)}} (*{{as_cProc(None, function.name)}})( + {%- for arg in function.arguments -%} + {% if not loop.first %}, {% endif %}{{as_annotated_cType(arg)}} + {%- endfor -%} + ); +{% endfor %} + +{% for type in by_category["object"] if len(c_methods(type)) > 0 %} + // Procs of {{type.name.CamelCase()}} + {% for method in c_methods(type) %} + typedef {{as_cType(method.return_type.name)}} (*{{as_cProc(type.name, method.name)}})( + {{-as_cType(type.name)}} {{as_varName(type.name)}} + {%- for arg in method.arguments -%} + , {{as_annotated_cType(arg)}} + {%- endfor -%} + ); + {% endfor %} + +{% endfor %} +#endif // !defined({{c_prefix}}_SKIP_PROCS) + +#if !defined({{c_prefix}}_SKIP_DECLARATIONS) + +{% for function in by_category["function"] %} + {{c_prefix}}_EXPORT {{as_cType(function.return_type.name)}} {{as_cMethod(None, function.name)}}( + {%- for arg in function.arguments -%} + {% if not loop.first %}, {% endif %}{{as_annotated_cType(arg)}} + {%- endfor -%} + ); +{% endfor %} + +{% for type in by_category["object"] if len(c_methods(type)) > 0 %} + // Methods of {{type.name.CamelCase()}} + {% for method in c_methods(type) %} + {{c_prefix}}_EXPORT {{as_cType(method.return_type.name)}} {{as_cMethod(type.name, method.name)}}( + {{-as_cType(type.name)}} {{as_varName(type.name)}} + {%- for arg in method.arguments -%} + , {{as_annotated_cType(arg)}} + {%- endfor -%} + ); + {% endfor %} + +{% endfor %} +#endif // !defined({{c_prefix}}_SKIP_DECLARATIONS) + +#ifdef __cplusplus +} // extern "C" +#endif + +#endif // {{metadata.api.upper()}}_H_
diff --git a/generator/templates/api_cpp.cpp b/generator/templates/api_cpp.cpp new file mode 100644 index 0000000..d540e0b --- /dev/null +++ b/generator/templates/api_cpp.cpp
@@ -0,0 +1,175 @@ +//* Copyright 2017 The Dawn Authors +//* +//* Licensed under the Apache License, Version 2.0 (the "License"); +//* you may not use this file except in compliance with the License. +//* You may obtain a copy of the License at +//* +//* http://www.apache.org/licenses/LICENSE-2.0 +//* +//* Unless required by applicable law or agreed to in writing, software +//* distributed under the License is distributed on an "AS IS" BASIS, +//* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +//* See the License for the specific language governing permissions and +//* limitations under the License. +{% set api = metadata.api.lower() %} +{% if 'dawn' in enabled_tags %} + #include "dawn/{{api}}_cpp.h" +{% else %} + #include "{{api}}/{{api}}_cpp.h" +{% endif %} + +#ifdef __GNUC__ +// error: 'offsetof' within non-standard-layout type '{{metadata.namespace}}::XXX' is conditionally-supported +#pragma GCC diagnostic ignored "-Winvalid-offsetof" +#endif + +namespace {{metadata.namespace}} { + {% for type in by_category["enum"] %} + {% set CppType = as_cppType(type.name) %} + {% set CType = as_cType(type.name) %} + + // {{CppType}} + + static_assert(sizeof({{CppType}}) == sizeof({{CType}}), "sizeof mismatch for {{CppType}}"); + static_assert(alignof({{CppType}}) == alignof({{CType}}), "alignof mismatch for {{CppType}}"); + + {% for value in type.values %} + static_assert(static_cast<uint32_t>({{CppType}}::{{as_cppEnum(value.name)}}) == {{as_cEnum(type.name, value.name)}}, "value mismatch for {{CppType}}::{{as_cppEnum(value.name)}}"); + {% endfor %} + {% endfor -%} + + {% for type in by_category["bitmask"] %} + {% set CppType = as_cppType(type.name) %} + {% set CType = as_cType(type.name) + "Flags" %} + + // {{CppType}} + + static_assert(sizeof({{CppType}}) == sizeof({{CType}}), "sizeof mismatch for {{CppType}}"); + static_assert(alignof({{CppType}}) == alignof({{CType}}), "alignof mismatch for {{CppType}}"); + + {% for value in type.values %} + static_assert(static_cast<uint32_t>({{CppType}}::{{as_cppEnum(value.name)}}) == {{as_cEnum(type.name, value.name)}}, "value mismatch for {{CppType}}::{{as_cppEnum(value.name)}}"); + {% endfor %} + {% endfor %} + + // ChainedStruct + + {% set c_prefix = metadata.c_prefix %} + static_assert(sizeof(ChainedStruct) == sizeof({{c_prefix}}ChainedStruct), + "sizeof mismatch for ChainedStruct"); + static_assert(alignof(ChainedStruct) == alignof({{c_prefix}}ChainedStruct), + "alignof mismatch for ChainedStruct"); + static_assert(offsetof(ChainedStruct, nextInChain) == offsetof({{c_prefix}}ChainedStruct, next), + "offsetof mismatch for ChainedStruct::nextInChain"); + static_assert(offsetof(ChainedStruct, sType) == offsetof({{c_prefix}}ChainedStruct, sType), + "offsetof mismatch for ChainedStruct::sType"); + {% for type in by_category["structure"] %} + {% set CppType = as_cppType(type.name) %} + {% set CType = as_cType(type.name) %} + + // {{CppType}} + + static_assert(sizeof({{CppType}}) == sizeof({{CType}}), "sizeof mismatch for {{CppType}}"); + static_assert(alignof({{CppType}}) == alignof({{CType}}), "alignof mismatch for {{CppType}}"); + + {% if type.extensible %} + static_assert(offsetof({{CppType}}, nextInChain) == offsetof({{CType}}, nextInChain), + "offsetof mismatch for {{CppType}}::nextInChain"); + {% endif %} + {% for member in type.members %} + {% set memberName = member.name.camelCase() %} + static_assert(offsetof({{CppType}}, {{memberName}}) == offsetof({{CType}}, {{memberName}}), + "offsetof mismatch for {{CppType}}::{{memberName}}"); + {% endfor %} + {% endfor -%} + + {%- macro render_c_actual_arg(arg) -%} + {%- if arg.annotation == "value" -%} + {%- if arg.type.category == "object" -%} + {{as_varName(arg.name)}}.Get() + {%- elif arg.type.category == "enum" or arg.type.category == "bitmask" -%} + static_cast<{{as_cType(arg.type.name)}}>({{as_varName(arg.name)}}) + {%- elif arg.type.category in ["function pointer", "native"] -%} + {{as_varName(arg.name)}} + {%- else -%} + UNHANDLED + {%- endif -%} + {%- else -%} + reinterpret_cast<{{decorate("", as_cType(arg.type.name), arg)}}>({{as_varName(arg.name)}}) + {%- endif -%} + {%- endmacro -%} + + {% for type in by_category["object"] %} + {% set CppType = as_cppType(type.name) %} + {% set CType = as_cType(type.name) %} + + // {{CppType}} + + static_assert(sizeof({{CppType}}) == sizeof({{CType}}), "sizeof mismatch for {{CppType}}"); + static_assert(alignof({{CppType}}) == alignof({{CType}}), "alignof mismatch for {{CppType}}"); + + {% macro render_cpp_method_declaration(type, method) -%} + {% set CppType = as_cppType(type.name) %} + {{as_cppType(method.return_type.name)}} {{CppType}}::{{method.name.CamelCase()}}( + {%- for arg in method.arguments -%} + {%- if not loop.first %}, {% endif -%} + {%- if arg.type.category == "object" and arg.annotation == "value" -%} + {{as_cppType(arg.type.name)}} const& {{as_varName(arg.name)}} + {%- else -%} + {{as_annotated_cppType(arg)}} + {%- endif -%} + {%- endfor -%} + ) const + {%- endmacro -%} + + {%- macro render_cpp_to_c_method_call(type, method) -%} + {{as_cMethod(type.name, method.name)}}(Get() + {%- for arg in method.arguments -%},{{" "}}{{render_c_actual_arg(arg)}} + {%- endfor -%} + ) + {%- endmacro -%} + + {% for method in type.methods -%} + {{render_cpp_method_declaration(type, method)}} { + {% if method.return_type.name.concatcase() == "void" %} + {{render_cpp_to_c_method_call(type, method)}}; + {% else %} + auto result = {{render_cpp_to_c_method_call(type, method)}}; + return {{convert_cType_to_cppType(method.return_type, 'value', 'result') | indent(8)}}; + {% endif %} + } + {% endfor %} + void {{CppType}}::{{c_prefix}}Reference({{CType}} handle) { + if (handle != nullptr) { + {{as_cMethod(type.name, Name("reference"))}}(handle); + } + } + void {{CppType}}::{{c_prefix}}Release({{CType}} handle) { + if (handle != nullptr) { + {{as_cMethod(type.name, Name("release"))}}(handle); + } + } + {% endfor %} + + // Function + + {% for function in by_category["function"] %} + {%- macro render_function_call(function) -%} + {{as_cMethod(None, function.name)}}( + {%- for arg in function.arguments -%} + {% if not loop.first %}, {% endif %}{{render_c_actual_arg(arg)}} + {%- endfor -%} + ) + {%- endmacro -%} + + {{as_cppType(function.return_type.name) | indent(4, true) }} {{as_cppType(function.name) }}( + {%- for arg in function.arguments -%} + {% if not loop.first %}, {% endif %}{{as_annotated_cppType(arg)}} + {%- endfor -%} + ) { + auto result = {{render_function_call(function)}}; + return {{convert_cType_to_cppType(function.return_type, 'value', 'result')}}; + } + {% endfor %} + +}
diff --git a/generator/templates/api_cpp.h b/generator/templates/api_cpp.h new file mode 100644 index 0000000..c3b21fb --- /dev/null +++ b/generator/templates/api_cpp.h
@@ -0,0 +1,260 @@ +//* Copyright 2017 The Dawn Authors +//* +//* Licensed under the Apache License, Version 2.0 (the "License"); +//* you may not use this file except in compliance with the License. +//* You may obtain a copy of the License at +//* +//* http://www.apache.org/licenses/LICENSE-2.0 +//* +//* Unless required by applicable law or agreed to in writing, software +//* distributed under the License is distributed on an "AS IS" BASIS, +//* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +//* See the License for the specific language governing permissions and +//* limitations under the License. +{% set API = metadata.api.upper() %} +{% set api = API.lower() %} +{% if 'dawn' not in enabled_tags %} + #ifdef __EMSCRIPTEN__ + #error "Do not include this header. Emscripten already provides headers needed for {{metadata.api}}." + #endif +{% endif %} +#ifndef {{API}}_CPP_H_ +#define {{API}}_CPP_H_ + +#include "dawn/{{api}}.h" +#include "dawn/EnumClassBitmasks.h" +#include <cmath> + +namespace {{metadata.namespace}} { + + {% set c_prefix = metadata.c_prefix %} + {% for constant in by_category["constant"] %} + {% set type = as_cppType(constant.type.name) %} + {% set value = c_prefix + "_" + constant.name.SNAKE_CASE() %} + static constexpr {{type}} k{{as_cppType(constant.name)}} = {{ value }}; + {% endfor %} + + {% for type in by_category["enum"] %} + enum class {{as_cppType(type.name)}} : uint32_t { + {% for value in type.values %} + {{as_cppEnum(value.name)}} = 0x{{format(value.value, "08X")}}, + {% endfor %} + }; + + {% endfor %} + + {% for type in by_category["bitmask"] %} + enum class {{as_cppType(type.name)}} : uint32_t { + {% for value in type.values %} + {{as_cppEnum(value.name)}} = 0x{{format(value.value, "08X")}}, + {% endfor %} + }; + + {% endfor %} + + {% for type in by_category["function pointer"] %} + using {{as_cppType(type.name)}} = {{as_cType(type.name)}}; + {% endfor %} + + {% for type in by_category["object"] %} + class {{as_cppType(type.name)}}; + {% endfor %} + + {% for type in by_category["structure"] %} + struct {{as_cppType(type.name)}}; + {% endfor %} + + {% for typeDef in by_category["typedef"] %} + // {{as_cppType(typeDef.name)}} is deprecated. + // Use {{as_cppType(typeDef.type.name)}} instead. + using {{as_cppType(typeDef.name)}} = {{as_cppType(typeDef.type.name)}}; + + {% endfor %} + template<typename Derived, typename CType> + class ObjectBase { + public: + ObjectBase() = default; + ObjectBase(CType handle): mHandle(handle) { + if (mHandle) Derived::{{c_prefix}}Reference(mHandle); + } + ~ObjectBase() { + if (mHandle) Derived::{{c_prefix}}Release(mHandle); + } + + ObjectBase(ObjectBase const& other) + : ObjectBase(other.Get()) { + } + Derived& operator=(ObjectBase const& other) { + if (&other != this) { + if (mHandle) Derived::{{c_prefix}}Release(mHandle); + mHandle = other.mHandle; + if (mHandle) Derived::{{c_prefix}}Reference(mHandle); + } + + return static_cast<Derived&>(*this); + } + + ObjectBase(ObjectBase&& other) { + mHandle = other.mHandle; + other.mHandle = 0; + } + Derived& operator=(ObjectBase&& other) { + if (&other != this) { + if (mHandle) Derived::{{c_prefix}}Release(mHandle); + mHandle = other.mHandle; + other.mHandle = 0; + } + + return static_cast<Derived&>(*this); + } + + ObjectBase(std::nullptr_t) {} + Derived& operator=(std::nullptr_t) { + if (mHandle != nullptr) { + Derived::{{c_prefix}}Release(mHandle); + mHandle = nullptr; + } + return static_cast<Derived&>(*this); + } + + bool operator==(std::nullptr_t) const { + return mHandle == nullptr; + } + bool operator!=(std::nullptr_t) const { + return mHandle != nullptr; + } + + explicit operator bool() const { + return mHandle != nullptr; + } + CType Get() const { + return mHandle; + } + CType Release() { + CType result = mHandle; + mHandle = 0; + return result; + } + static Derived Acquire(CType handle) { + Derived result; + result.mHandle = handle; + return result; + } + + protected: + CType mHandle = nullptr; + }; + +{% macro render_cpp_default_value(member, is_struct=True) -%} + {%- if member.annotation in ["*", "const*"] and member.optional or member.default_value == "nullptr" -%} + {{" "}}= nullptr + {%- elif member.type.category == "object" and member.optional and is_struct -%} + {{" "}}= nullptr + {%- elif member.type.category in ["enum", "bitmask"] and member.default_value != None -%} + {{" "}}= {{as_cppType(member.type.name)}}::{{as_cppEnum(Name(member.default_value))}} + {%- elif member.type.category == "native" and member.default_value != None -%} + {{" "}}= {{member.default_value}} + {%- elif member.default_value != None -%} + {{" "}}= {{member.default_value}} + {%- else -%} + {{assert(member.default_value == None)}} + {%- endif -%} +{%- endmacro %} + +{% macro render_cpp_method_declaration(type, method) %} + {% set CppType = as_cppType(type.name) %} + {{as_cppType(method.return_type.name)}} {{method.name.CamelCase()}}( + {%- for arg in method.arguments -%} + {%- if not loop.first %}, {% endif -%} + {%- if arg.type.category == "object" and arg.annotation == "value" -%} + {{as_cppType(arg.type.name)}} const& {{as_varName(arg.name)}} + {%- else -%} + {{as_annotated_cppType(arg)}} + {%- endif -%} + {{render_cpp_default_value(arg, False)}} + {%- endfor -%} + ) const +{%- endmacro %} + + {% for type in by_category["object"] %} + {% set CppType = as_cppType(type.name) %} + {% set CType = as_cType(type.name) %} + class {{CppType}} : public ObjectBase<{{CppType}}, {{CType}}> { + public: + using ObjectBase::ObjectBase; + using ObjectBase::operator=; + + {% for method in type.methods %} + {{render_cpp_method_declaration(type, method)}}; + {% endfor %} + + private: + friend ObjectBase<{{CppType}}, {{CType}}>; + static void {{c_prefix}}Reference({{CType}} handle); + static void {{c_prefix}}Release({{CType}} handle); + }; + + {% endfor %} + + {% for function in by_category["function"] %} + {{as_cppType(function.return_type.name)}} {{as_cppType(function.name)}}( + {%- for arg in function.arguments -%} + {%- if not loop.first %}, {% endif -%} + {{as_annotated_cppType(arg)}}{{render_cpp_default_value(arg, False)}} + {%- endfor -%} + ); + {% endfor %} + + struct ChainedStruct { + ChainedStruct const * nextInChain = nullptr; + SType sType = SType::Invalid; + }; + + struct ChainedStructOut { + ChainedStruct * nextInChain = nullptr; + SType sType = SType::Invalid; + }; + + {% for type in by_category["structure"] %} + {% set Out = "Out" if type.output else "" %} + {% set const = "const" if not type.output else "" %} + {% if type.chained %} + struct {{as_cppType(type.name)}} : ChainedStruct{{Out}} { + {{as_cppType(type.name)}}() { + sType = SType::{{type.name.CamelCase()}}; + } + {% else %} + struct {{as_cppType(type.name)}} { + {% endif %} + {% if type.extensible %} + ChainedStruct{{Out}} {{const}} * nextInChain = nullptr; + {% endif %} + {% for member in type.members %} + {% set member_declaration = as_annotated_cppType(member) + render_cpp_default_value(member) %} + {% if type.chained and loop.first %} + //* Align the first member to ChainedStruct to match the C struct layout. + alignas(ChainedStruct{{Out}}) {{member_declaration}}; + {% else %} + {{member_declaration}}; + {% endif %} + {% endfor %} + }; + + {% endfor %} + + // The operators of EnumClassBitmmasks in the dawn:: namespace need to be imported + // in the {{metadata.namespace}} namespace for Argument Dependent Lookup. + DAWN_IMPORT_BITMASK_OPERATORS +} // namespace {{metadata.namespace}} + +namespace dawn { + {% for type in by_category["bitmask"] %} + template<> + struct IsDawnBitmask<{{metadata.namespace}}::{{as_cppType(type.name)}}> { + static constexpr bool enable = true; + }; + + {% endfor %} +} // namespace dawn + +#endif // {{API}}_CPP_H_
diff --git a/generator/templates/api_cpp_print.h b/generator/templates/api_cpp_print.h new file mode 100644 index 0000000..040f29c --- /dev/null +++ b/generator/templates/api_cpp_print.h
@@ -0,0 +1,92 @@ +//* Copyright 2021 The Dawn Authors +//* +//* Licensed under the Apache License, Version 2.0 (the "License"); +//* you may not use this file except in compliance with the License. +//* You may obtain a copy of the License at +//* +//* http://www.apache.org/licenses/LICENSE-2.0 +//* +//* Unless required by applicable law or agreed to in writing, software +//* distributed under the License is distributed on an "AS IS" BASIS, +//* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +//* See the License for the specific language governing permissions and +//* limitations under the License. + +{% set API = metadata.api.upper() %} +{% set api = API.lower() %} +#ifndef {{API}}_CPP_PRINT_H_ +#define {{API}}_CPP_PRINT_H_ + +#include "dawn/{{api}}_cpp.h" + +#include <iomanip> +#include <ios> +#include <ostream> +#include <type_traits> + +namespace {{metadata.namespace}} { + + {% for type in by_category["enum"] %} + template <typename CharT, typename Traits> + std::basic_ostream<CharT, Traits>& operator<<(std::basic_ostream<CharT, Traits>& o, {{as_cppType(type.name)}} value) { + switch (value) { + {% for value in type.values %} + case {{as_cppType(type.name)}}::{{as_cppEnum(value.name)}}: + o << "{{as_cppType(type.name)}}::{{as_cppEnum(value.name)}}"; + break; + {% endfor %} + default: + o << "{{as_cppType(type.name)}}::" << std::showbase << std::hex << std::setfill('0') << std::setw(4) << static_cast<typename std::underlying_type<{{as_cppType(type.name)}}>::type>(value); + } + return o; + } + {% endfor %} + + {% for type in by_category["bitmask"] %} + template <typename CharT, typename Traits> + std::basic_ostream<CharT, Traits>& operator<<(std::basic_ostream<CharT, Traits>& o, {{as_cppType(type.name)}} value) { + o << "{{as_cppType(type.name)}}::"; + if (!static_cast<bool>(value)) { + {% for value in type.values if value.value == 0 %} + // 0 is often explicitly declared as None. + o << "{{as_cppEnum(value.name)}}"; + {% else %} + o << std::showbase << std::hex << std::setfill('0') << std::setw(4) << 0; + {% endfor %} + return o; + } + + bool moreThanOneBit = !HasZeroOrOneBits(value); + if (moreThanOneBit) { + o << "("; + } + + bool first = true; + {% for value in type.values if value.value != 0 %} + if (value & {{as_cppType(type.name)}}::{{as_cppEnum(value.name)}}) { + if (!first) { + o << "|"; + } + first = false; + o << "{{as_cppEnum(value.name)}}"; + value &= ~{{as_cppType(type.name)}}::{{as_cppEnum(value.name)}}; + } + {% endfor %} + + if (static_cast<bool>(value)) { + if (!first) { + o << "|"; + } + o << std::showbase << std::hex << std::setfill('0') << std::setw(4) << static_cast<typename std::underlying_type<{{as_cppType(type.name)}}>::type>(value); + } + + if (moreThanOneBit) { + o << ")"; + } + return o; + } + {% endfor %} + +} // namespace {{metadata.namespace}} + +#endif // {{API}}_CPP_PRINT_H_
diff --git a/generator/templates/api_struct_info.json b/generator/templates/api_struct_info.json new file mode 100644 index 0000000..04e56cf --- /dev/null +++ b/generator/templates/api_struct_info.json
@@ -0,0 +1,51 @@ +//* Copyright 2020 The Dawn Authors +//* +//* Licensed under the Apache License, Version 2.0 (the "License"); +//* you may not use this file except in compliance with the License. +//* You may obtain a copy of the License at +//* +//* http://www.apache.org/licenses/LICENSE-2.0 +//* +//* Unless required by applicable law or agreed to in writing, software +//* distributed under the License is distributed on an "AS IS" BASIS, +//* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +//* See the License for the specific language governing permissions and +//* limitations under the License. +//* +//* +//* This generator is used to produce part of Emscripten's struct_info.json, +//* which is a list of struct fields that it uses to generate field offset +//* information for its own code generators. +//* https://github.com/emscripten-core/emscripten/blob/master/src/struct_info.json +//* + { + {% set api = metadata.api.lower() %} + "file": "{api}/{api}.h", + "defines": [], + "structs": { + "{{metadata.c_prefix}}ChainedStruct": [ + "next", + "sType" + ], + {% for type in by_category["structure"] %} + "{{as_cType(type.name)}}": [ + {% if type.chained %} + "chain" + {%- elif type.extensible %} + "nextInChain" + {%- endif %} + {% for member in type.members -%} + {%- if (type.chained or type.extensible) or not loop.first -%} + , + {% endif %} + "{{as_varName(member.name)}}" + {%- endfor %} + + ] + {%- if not loop.last -%} + , + {% endif %} + {% endfor %} + + } + }
diff --git a/generator/templates/dawn/common/Version.h b/generator/templates/dawn/common/Version.h new file mode 100644 index 0000000..f9f67e7 --- /dev/null +++ b/generator/templates/dawn/common/Version.h
@@ -0,0 +1,24 @@ +// Copyright 2022 The Dawn Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#ifndef COMMON_VERISON_AUTOGEN_H_ +#define COMMON_VERISON_AUTOGEN_H_ + +namespace dawn { + +static constexpr char kGitHash[] = "{{get_gitHash()}}"; + +} // namespace dawn + +#endif // COMMON_VERISON_AUTOGEN_H_
diff --git a/generator/templates/dawn/native/ChainUtils.cpp b/generator/templates/dawn/native/ChainUtils.cpp new file mode 100644 index 0000000..2973788 --- /dev/null +++ b/generator/templates/dawn/native/ChainUtils.cpp
@@ -0,0 +1,66 @@ +// Copyright 2021 The Dawn Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +{% set impl_dir = metadata.impl_dir + "/" if metadata.impl_dir else "" %} +{% set namespace_name = Name(metadata.native_namespace) %} +{% set native_namespace = namespace_name.namespace_case() %} +{% set native_dir = impl_dir + namespace_name.Dirs() %} +#include "{{native_dir}}/ChainUtils_autogen.h" + +#include <unordered_set> + +namespace {{native_namespace}} { + +{% set namespace = metadata.namespace %} +{% for value in types["s type"].values %} + {% if value.valid %} + void FindInChain(const ChainedStruct* chain, const {{as_cppEnum(value.name)}}** out) { + for (; chain; chain = chain->nextInChain) { + if (chain->sType == {{namespace}}::SType::{{as_cppEnum(value.name)}}) { + *out = static_cast<const {{as_cppEnum(value.name)}}*>(chain); + break; + } + } + } + {% endif %} +{% endfor %} + +MaybeError ValidateSTypes(const ChainedStruct* chain, + std::vector<std::vector<{{namespace}}::SType>> oneOfConstraints) { + std::unordered_set<{{namespace}}::SType> allSTypes; + for (; chain; chain = chain->nextInChain) { + if (allSTypes.find(chain->sType) != allSTypes.end()) { + return DAWN_VALIDATION_ERROR("Chain cannot have duplicate sTypes"); + } + allSTypes.insert(chain->sType); + } + for (const auto& oneOfConstraint : oneOfConstraints) { + bool satisfied = false; + for ({{namespace}}::SType oneOfSType : oneOfConstraint) { + if (allSTypes.find(oneOfSType) != allSTypes.end()) { + if (satisfied) { + return DAWN_VALIDATION_ERROR("Unsupported sType combination"); + } + satisfied = true; + allSTypes.erase(oneOfSType); + } + } + } + if (!allSTypes.empty()) { + return DAWN_VALIDATION_ERROR("Unsupported sType"); + } + return {}; +} + +} // namespace {{native_namespace}}
diff --git a/generator/templates/dawn/native/ChainUtils.h b/generator/templates/dawn/native/ChainUtils.h new file mode 100644 index 0000000..3377220 --- /dev/null +++ b/generator/templates/dawn/native/ChainUtils.h
@@ -0,0 +1,86 @@ +// Copyright 2021 The Dawn Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +{% set namespace_name = Name(metadata.native_namespace) %} +{% set DIR = namespace_name.concatcase().upper() %} +#ifndef {{DIR}}_CHAIN_UTILS_H_ +#define {{DIR}}_CHAIN_UTILS_H_ + +{% set impl_dir = metadata.impl_dir + "/" if metadata.impl_dir else "" %} +{% set namespace_name = Name(metadata.native_namespace) %} +{% set native_namespace = namespace_name.namespace_case() %} +{% set native_dir = impl_dir + namespace_name.Dirs() %} +{% set prefix = metadata.proc_table_prefix.lower() %} +#include "{{native_dir}}/{{prefix}}_platform.h" +#include "{{native_dir}}/Error.h" + +namespace {{native_namespace}} { + {% for value in types["s type"].values %} + {% if value.valid %} + void FindInChain(const ChainedStruct* chain, const {{as_cppEnum(value.name)}}** out); + {% endif %} + {% endfor %} + + // Verifies that |chain| only contains ChainedStructs of types enumerated in + // |oneOfConstraints| and contains no duplicate sTypes. Each vector in + // |oneOfConstraints| defines a set of sTypes that cannot coexist in the same chain. + // For example: + // ValidateSTypes(chain, { { ShaderModuleSPIRVDescriptor, ShaderModuleWGSLDescriptor } })) + // ValidateSTypes(chain, { { Extension1 }, { Extension2 } }) + {% set namespace = metadata.namespace %} + MaybeError ValidateSTypes(const ChainedStruct* chain, + std::vector<std::vector<{{namespace}}::SType>> oneOfConstraints); + + template <typename T> + MaybeError ValidateSingleSTypeInner(const ChainedStruct* chain, T sType) { + DAWN_INVALID_IF(chain->sType != sType, + "Unsupported sType (%s). Expected (%s)", chain->sType, sType); + return {}; + } + + template <typename T, typename... Args> + MaybeError ValidateSingleSTypeInner(const ChainedStruct* chain, T sType, Args... sTypes) { + if (chain->sType == sType) { + return {}; + } + return ValidateSingleSTypeInner(chain, sTypes...); + } + + // Verifies that |chain| contains a single ChainedStruct of type |sType| or no ChainedStructs + // at all. + template <typename T> + MaybeError ValidateSingleSType(const ChainedStruct* chain, T sType) { + if (chain == nullptr) { + return {}; + } + DAWN_INVALID_IF(chain->nextInChain != nullptr, + "Chain can only contain a single chained struct."); + return ValidateSingleSTypeInner(chain, sType); + } + + // Verifies that |chain| contains a single ChainedStruct with a type enumerated in the + // parameter pack or no ChainedStructs at all. + template <typename T, typename... Args> + MaybeError ValidateSingleSType(const ChainedStruct* chain, T sType, Args... sTypes) { + if (chain == nullptr) { + return {}; + } + DAWN_INVALID_IF(chain->nextInChain != nullptr, + "Chain can only contain a single chained struct."); + return ValidateSingleSTypeInner(chain, sType, sTypes...); + } + +} // namespace {{native_namespace}} + +#endif // {{DIR}}_CHAIN_UTILS_H_
diff --git a/generator/templates/dawn/native/ObjectType.cpp b/generator/templates/dawn/native/ObjectType.cpp new file mode 100644 index 0000000..8fad3d4 --- /dev/null +++ b/generator/templates/dawn/native/ObjectType.cpp
@@ -0,0 +1,34 @@ +//* Copyright 2020 The Dawn Authors +//* +//* Licensed under the Apache License, Version 2.0 (the "License"); +//* you may not use this file except in compliance with the License. +//* You may obtain a copy of the License at +//* +//* http://www.apache.org/licenses/LICENSE-2.0 +//* +//* Unless required by applicable law or agreed to in writing, software +//* distributed under the License is distributed on an "AS IS" BASIS, +//* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +//* See the License for the specific language governing permissions and +//* limitations under the License. + +{% set impl_dir = metadata.impl_dir + "/" if metadata.impl_dir else "" %} +{% set namespace_name = Name(metadata.native_namespace) %} +{% set native_namespace = namespace_name.namespace_case() %} +{% set native_dir = impl_dir + namespace_name.Dirs() %} +#include "{{native_dir}}/ObjectType_autogen.h" + +namespace {{native_namespace}} { + + const char* ObjectTypeAsString(ObjectType type) { + switch (type) { + {% for type in by_category["object"] %} + case ObjectType::{{type.name.CamelCase()}}: + return "{{type.name.CamelCase()}}"; + {% endfor %} + default: + UNREACHABLE(); + } + } + +} // namespace {{native_namespace}}
diff --git a/generator/templates/dawn/native/ObjectType.h b/generator/templates/dawn/native/ObjectType.h new file mode 100644 index 0000000..1d59b50 --- /dev/null +++ b/generator/templates/dawn/native/ObjectType.h
@@ -0,0 +1,41 @@ +//* Copyright 2020 The Dawn Authors +//* +//* Licensed under the Apache License, Version 2.0 (the "License"); +//* you may not use this file except in compliance with the License. +//* You may obtain a copy of the License at +//* +//* http://www.apache.org/licenses/LICENSE-2.0 +//* +//* Unless required by applicable law or agreed to in writing, software +//* distributed under the License is distributed on an "AS IS" BASIS, +//* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +//* See the License for the specific language governing permissions and +//* limitations under the License. + +{% set namespace_name = Name(metadata.native_namespace) %} +{% set DIR = namespace_name.concatcase().upper() %} +#ifndef {{DIR}}_OBJECTTPYE_AUTOGEN_H_ +#define {{DIR}}_OBJECTTPYE_AUTOGEN_H_ + +#include "dawn/common/ityp_array.h" + +#include <cstdint> + +{% set native_namespace = namespace_name.namespace_case() %} +namespace {{native_namespace}} { + + enum class ObjectType : uint32_t { + {% for type in by_category["object"] %} + {{type.name.CamelCase()}}, + {% endfor %} + }; + + template <typename T> + using PerObjectType = ityp::array<ObjectType, T, {{len(by_category["object"])}}>; + + const char* ObjectTypeAsString(ObjectType type); + +} // namespace {{native_namespace}} + + +#endif // {{DIR}}_OBJECTTPYE_AUTOGEN_H_
diff --git a/generator/templates/dawn/native/ProcTable.cpp b/generator/templates/dawn/native/ProcTable.cpp new file mode 100644 index 0000000..47ac2f5 --- /dev/null +++ b/generator/templates/dawn/native/ProcTable.cpp
@@ -0,0 +1,159 @@ +//* Copyright 2017 The Dawn Authors +//* +//* Licensed under the Apache License, Version 2.0 (the "License"); +//* you may not use this file except in compliance with the License. +//* You may obtain a copy of the License at +//* +//* http://www.apache.org/licenses/LICENSE-2.0 +//* +//* Unless required by applicable law or agreed to in writing, software +//* distributed under the License is distributed on an "AS IS" BASIS, +//* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +//* See the License for the specific language governing permissions and +//* limitations under the License. + +{% set Prefix = metadata.proc_table_prefix %} +{% set prefix = Prefix.lower() %} +{% set impl_dir = metadata.impl_dir + "/" if metadata.impl_dir else "" %} +{% set namespace_name = Name(metadata.native_namespace) %} +{% set native_namespace = namespace_name.namespace_case() %} +{% set native_dir = impl_dir + namespace_name.Dirs() %} +#include "{{native_dir}}/{{prefix}}_platform.h" +#include "{{native_dir}}/{{Prefix}}Native.h" + +#include <algorithm> +#include <vector> + +{% for type in by_category["object"] %} + {% if type.name.canonical_case() not in ["texture view"] %} + #include "{{native_dir}}/{{type.name.CamelCase()}}.h" + {% endif %} +{% endfor %} + +namespace {{native_namespace}} { + + {% for type in by_category["object"] %} + {% for method in c_methods(type) %} + {% set suffix = as_MethodSuffix(type.name, method.name) %} + + {{as_cType(method.return_type.name)}} Native{{suffix}}( + {{-as_cType(type.name)}} cSelf + {%- for arg in method.arguments -%} + , {{as_annotated_cType(arg)}} + {%- endfor -%} + ) { + //* Perform conversion between C types and frontend types + auto self = FromAPI(cSelf); + + {% for arg in method.arguments %} + {% set varName = as_varName(arg.name) %} + {% if arg.type.category in ["enum", "bitmask"] and arg.annotation == "value" %} + auto {{varName}}_ = static_cast<{{as_frontendType(arg.type)}}>({{varName}}); + {% elif arg.annotation != "value" or arg.type.category == "object" %} + auto {{varName}}_ = reinterpret_cast<{{decorate("", as_frontendType(arg.type), arg)}}>({{varName}}); + {% else %} + auto {{varName}}_ = {{as_varName(arg.name)}}; + {% endif %} + {%- endfor-%} + + {% if method.return_type.name.canonical_case() != "void" %} + auto result = + {%- endif %} + self->API{{method.name.CamelCase()}}( + {%- for arg in method.arguments -%} + {%- if not loop.first %}, {% endif -%} + {{as_varName(arg.name)}}_ + {%- endfor -%} + ); + {% if method.return_type.name.canonical_case() != "void" %} + {% if method.return_type.category == "object" %} + return ToAPI(result); + {% else %} + return result; + {% endif %} + {% endif %} + } + {% endfor %} + {% endfor %} + + namespace { + + {% set c_prefix = metadata.c_prefix %} + struct ProcEntry { + {{c_prefix}}Proc proc; + const char* name; + }; + static const ProcEntry sProcMap[] = { + {% for (type, method) in c_methods_sorted_by_name %} + { reinterpret_cast<{{c_prefix}}Proc>(Native{{as_MethodSuffix(type.name, method.name)}}), "{{as_cMethod(type.name, method.name)}}" }, + {% endfor %} + }; + static constexpr size_t sProcMapSize = sizeof(sProcMap) / sizeof(sProcMap[0]); + + } // anonymous namespace + + {% for function in by_category["function"] %} + {{as_cType(function.return_type.name)}} Native{{as_cppType(function.name)}}( + {%- for arg in function.arguments -%} + {% if not loop.first %}, {% endif %}{{as_annotated_cType(arg)}} + {%- endfor -%} + ) { + {% if function.name.canonical_case() == "get proc address" %} + if (procName == nullptr) { + return nullptr; + } + + const ProcEntry* entry = std::lower_bound(&sProcMap[0], &sProcMap[sProcMapSize], procName, + [](const ProcEntry &a, const char *b) -> bool { + return strcmp(a.name, b) < 0; + } + ); + + if (entry != &sProcMap[sProcMapSize] && strcmp(entry->name, procName) == 0) { + return entry->proc; + } + + // Special case the free-standing functions of the API. + // TODO(dawn:1238) Checking string one by one is slow, it needs to be optimized. + {% for function in by_category["function"] %} + if (strcmp(procName, "{{as_cMethod(None, function.name)}}") == 0) { + return reinterpret_cast<{{c_prefix}}Proc>(Native{{as_cppType(function.name)}}); + } + + {% endfor %} + return nullptr; + {% else %} + return ToAPI({{as_cppType(function.return_type.name)}}Base::Create( + {%- for arg in function.arguments -%} + FromAPI({% if not loop.first %}, {% endif %}{{as_varName(arg.name)}}) + {%- endfor -%} + )); + {% endif %} + } + + {% endfor %} + + std::vector<const char*> GetProcMapNamesForTestingInternal() { + std::vector<const char*> result; + result.reserve(sProcMapSize); + for (const ProcEntry& entry : sProcMap) { + result.push_back(entry.name); + } + return result; + } + + static {{Prefix}}ProcTable gProcTable = { + {% for function in by_category["function"] %} + Native{{as_cppType(function.name)}}, + {% endfor %} + {% for type in by_category["object"] %} + {% for method in c_methods(type) %} + Native{{as_MethodSuffix(type.name, method.name)}}, + {% endfor %} + {% endfor %} + }; + + const {{Prefix}}ProcTable& GetProcsAutogen() { + return gProcTable; + } +}
diff --git a/generator/templates/dawn/native/ValidationUtils.cpp b/generator/templates/dawn/native/ValidationUtils.cpp new file mode 100644 index 0000000..1cb78c6 --- /dev/null +++ b/generator/templates/dawn/native/ValidationUtils.cpp
@@ -0,0 +1,48 @@ +//* Copyright 2018 The Dawn Authors +//* +//* Licensed under the Apache License, Version 2.0 (the "License"); +//* you may not use this file except in compliance with the License. +//* You may obtain a copy of the License at +//* +//* http://www.apache.org/licenses/LICENSE-2.0 +//* +//* Unless required by applicable law or agreed to in writing, software +//* distributed under the License is distributed on an "AS IS" BASIS, +//* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +//* See the License for the specific language governing permissions and +//* limitations under the License. + +{% set impl_dir = metadata.impl_dir + "/" if metadata.impl_dir else "" %} +{% set namespace_name = Name(metadata.native_namespace) %} +{% set native_namespace = namespace_name.namespace_case() %} +{% set native_dir = impl_dir + namespace_name.Dirs() %} +#include "{{native_dir}}/ValidationUtils_autogen.h" + +namespace {{native_namespace}} { + + {% set namespace = metadata.namespace %} + {% for type in by_category["enum"] %} + MaybeError Validate{{type.name.CamelCase()}}({{namespace}}::{{as_cppType(type.name)}} value) { + switch (value) { + {% for value in type.values if value.valid %} + case {{namespace}}::{{as_cppType(type.name)}}::{{as_cppEnum(value.name)}}: + return {}; + {% endfor %} + default: + return DAWN_VALIDATION_ERROR("Invalid value for {{as_cType(type.name)}}"); + } + } + + {% endfor %} + + {% for type in by_category["bitmask"] %} + MaybeError Validate{{type.name.CamelCase()}}({{namespace}}::{{as_cppType(type.name)}} value) { + if ((value & static_cast<{{namespace}}::{{as_cppType(type.name)}}>(~{{type.full_mask}})) == 0) { + return {}; + } + return DAWN_VALIDATION_ERROR("Invalid value for {{as_cType(type.name)}}"); + } + + {% endfor %} + +} // namespace {{native_namespace}}
diff --git a/generator/templates/dawn/native/ValidationUtils.h b/generator/templates/dawn/native/ValidationUtils.h new file mode 100644 index 0000000..06d3cc7 --- /dev/null +++ b/generator/templates/dawn/native/ValidationUtils.h
@@ -0,0 +1,37 @@ +//* Copyright 2018 The Dawn Authors +//* +//* Licensed under the Apache License, Version 2.0 (the "License"); +//* you may not use this file except in compliance with the License. +//* You may obtain a copy of the License at +//* +//* http://www.apache.org/licenses/LICENSE-2.0 +//* +//* Unless required by applicable law or agreed to in writing, software +//* distributed under the License is distributed on an "AS IS" BASIS, +//* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +//* See the License for the specific language governing permissions and +//* limitations under the License. + +#ifndef BACKEND_VALIDATIONUTILS_H_ +#define BACKEND_VALIDATIONUTILS_H_ + +{% set api = metadata.api.lower() %} +#include "dawn/{{api}}_cpp.h" + +{% set impl_dir = metadata.impl_dir + "/" if metadata.impl_dir else "" %} +{% set namespace_name = Name(metadata.native_namespace) %} +{% set native_namespace = namespace_name.namespace_case() %} +{% set native_dir = impl_dir + namespace_name.Dirs() %} +#include "{{native_dir}}/Error.h" + +namespace {{native_namespace}} { + + // Helper functions to check the value of enums and bitmasks + {% for type in by_category["enum"] + by_category["bitmask"] %} + {% set namespace = metadata.namespace %} + MaybeError Validate{{type.name.CamelCase()}}({{namespace}}::{{as_cppType(type.name)}} value); + {% endfor %} + +} // namespace {{native_namespace}} + +#endif // BACKEND_VALIDATIONUTILS_H_
diff --git a/generator/templates/dawn/native/api_absl_format.cpp b/generator/templates/dawn/native/api_absl_format.cpp new file mode 100644 index 0000000..a3b7ea2 --- /dev/null +++ b/generator/templates/dawn/native/api_absl_format.cpp
@@ -0,0 +1,173 @@ +//* Copyright 2021 The Dawn Authors +//* +//* Licensed under the Apache License, Version 2.0 (the "License"); +//* you may not use this file except in compliance with the License. +//* You may obtain a copy of the License at +//* +//* http://www.apache.org/licenses/LICENSE-2.0 +//* +//* Unless required by applicable law or agreed to in writing, software +//* distributed under the License is distributed on an "AS IS" BASIS, +//* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +//* See the License for the specific language governing permissions and +//* limitations under the License. + +{% set impl_dir = metadata.impl_dir + "/" if metadata.impl_dir else "" %} +{% set namespace_name = Name(metadata.native_namespace) %} +{% set native_namespace = namespace_name.namespace_case() %} +{% set native_dir = impl_dir + namespace_name.Dirs() %} +{% set api = metadata.api.lower() %} +#include "{{native_dir}}/{{api}}_absl_format_autogen.h" + +#include "{{native_dir}}/ObjectType_autogen.h" + +namespace {{native_namespace}} { + + // + // Descriptors + // + + {% for type in by_category["structure"] %} + {% for member in type.members %} + {% if member.name.canonical_case() == "label" %} + absl::FormatConvertResult<absl::FormatConversionCharSet::kString> + AbslFormatConvert(const {{as_cppType(type.name)}}* value, + const absl::FormatConversionSpec& spec, + absl::FormatSink* s) { + if (value == nullptr) { + s->Append("[null]"); + return {true}; + } + s->Append("[{{as_cppType(type.name)}}"); + if (value->label != nullptr) { + s->Append(absl::StrFormat(" \"%s\"", value->label)); + } + s->Append("]"); + return {true}; + } + {% endif %} + {% endfor %} + {% endfor %} + + // + // Compatible with absl::StrFormat (Needs to be disjoint from having a 'label' for now.) + // Currently uses a hard-coded list to determine which structures are actually supported. If + // additional structures are added, be sure to update the header file's list as well. + // + using absl::ParsedFormat; + + {% for type in by_category["structure"] %} + {% if type.name.get() in [ + "buffer binding layout", + "sampler binding layout", + "texture binding layout", + "storage texture binding layout" + ] + %} + absl::FormatConvertResult<absl::FormatConversionCharSet::kString> + AbslFormatConvert(const {{as_cppType(type.name)}}& value, + const absl::FormatConversionSpec& spec, + absl::FormatSink* s) { + {% set members = [] %} + {% set format = [] %} + {% set template = [] %} + {% for member in type.members %} + {% set memberName = member.name.camelCase() %} + {% do members.append("value." + memberName) %} + {% do format.append(memberName + ": %" + as_formatType(member)) %} + {% do template.append("'" + as_formatType(member) + "'") %} + {% endfor %} + static const auto* const fmt = + new ParsedFormat<{{template|join(",")}}>("{ {{format|join(", ")}} }"); + s->Append(absl::StrFormat(*fmt, {{members|join(", ")}})); + return {true}; + } + {% endif %} + {% endfor %} + +} // namespace {{native_namespace}} + +{% set namespace = metadata.namespace %} +namespace {{namespace}} { + + // + // Enums + // + + {% for type in by_category["enum"] %} + absl::FormatConvertResult<absl::FormatConversionCharSet::kString|absl::FormatConversionCharSet::kIntegral> + AbslFormatConvert({{as_cppType(type.name)}} value, + const absl::FormatConversionSpec& spec, + absl::FormatSink* s) { + if (spec.conversion_char() == absl::FormatConversionChar::s) { + s->Append("{{as_cppType(type.name)}}::"); + switch (value) { + {% for value in type.values %} + case {{as_cppType(type.name)}}::{{as_cppEnum(value.name)}}: + s->Append("{{as_cppEnum(value.name)}}"); + break; + {% endfor %} + } + } else { + s->Append(absl::StrFormat("%u", static_cast<typename std::underlying_type<{{as_cppType(type.name)}}>::type>(value))); + } + return {true}; + } + {% endfor %} + + // + // Bitmasks + // + + {% for type in by_category["bitmask"] %} + absl::FormatConvertResult<absl::FormatConversionCharSet::kString|absl::FormatConversionCharSet::kIntegral> + AbslFormatConvert({{as_cppType(type.name)}} value, + const absl::FormatConversionSpec& spec, + absl::FormatSink* s) { + if (spec.conversion_char() == absl::FormatConversionChar::s) { + s->Append("{{as_cppType(type.name)}}::"); + if (!static_cast<bool>(value)) { + {% for value in type.values if value.value == 0 %} + // 0 is often explicitly declared as None. + s->Append("{{as_cppEnum(value.name)}}"); + {% else %} + s->Append(absl::StrFormat("{{as_cppType(type.name)}}::%x", 0)); + {% endfor %} + return {true}; + } + + bool moreThanOneBit = !HasZeroOrOneBits(value); + if (moreThanOneBit) { + s->Append("("); + } + + bool first = true; + {% for value in type.values if value.value != 0 %} + if (value & {{as_cppType(type.name)}}::{{as_cppEnum(value.name)}}) { + if (!first) { + s->Append("|"); + } + first = false; + s->Append("{{as_cppEnum(value.name)}}"); + value &= ~{{as_cppType(type.name)}}::{{as_cppEnum(value.name)}}; + } + {% endfor %} + + if (static_cast<bool>(value)) { + if (!first) { + s->Append("|"); + } + s->Append(absl::StrFormat("{{as_cppType(type.name)}}::%x", static_cast<typename std::underlying_type<{{as_cppType(type.name)}}>::type>(value))); + } + + if (moreThanOneBit) { + s->Append(")"); + } + } else { + s->Append(absl::StrFormat("%u", static_cast<typename std::underlying_type<{{as_cppType(type.name)}}>::type>(value))); + } + return {true}; + } + {% endfor %} + +} // namespace {{namespace}}
diff --git a/generator/templates/dawn/native/api_absl_format.h b/generator/templates/dawn/native/api_absl_format.h new file mode 100644 index 0000000..ab06098 --- /dev/null +++ b/generator/templates/dawn/native/api_absl_format.h
@@ -0,0 +1,95 @@ +//* Copyright 2021 The Dawn Authors +//* +//* Licensed under the Apache License, Version 2.0 (the "License"); +//* you may not use this file except in compliance with the License. +//* You may obtain a copy of the License at +//* +//* http://www.apache.org/licenses/LICENSE-2.0 +//* +//* Unless required by applicable law or agreed to in writing, software +//* distributed under the License is distributed on an "AS IS" BASIS, +//* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +//* See the License for the specific language governing permissions and +//* limitations under the License. + +{% set API = metadata.api.upper() %} +#ifndef {{API}}_ABSL_FORMAT_H_ +#define {{API}}_ABSL_FORMAT_H_ + +{% set impl_dir = metadata.impl_dir + "/" if metadata.impl_dir else "" %} +{% set namespace_name = Name(metadata.native_namespace) %} +{% set native_namespace = namespace_name.namespace_case() %} +{% set native_dir = impl_dir + namespace_name.Dirs() %} +{% set prefix = metadata.proc_table_prefix.lower() %} +#include "{{native_dir}}/{{prefix}}_platform.h" + +#include "absl/strings/str_format.h" + +namespace {{native_namespace}} { + + // + // Descriptors + // + + // Only includes structures that have a 'label' member. + {% for type in by_category["structure"] %} + {% for member in type.members %} + {% if member.name.canonical_case() == "label" %} + absl::FormatConvertResult<absl::FormatConversionCharSet::kString> + AbslFormatConvert(const {{as_cppType(type.name)}}* value, + const absl::FormatConversionSpec& spec, + absl::FormatSink* s); + {% endif %} + {% endfor %} + {% endfor %} + + // + // Compatible with absl::StrFormat (Needs to be disjoint from having a 'label' for now.) + // Currently uses a hard-coded list to determine which structures are actually supported. If + // additional structures are added, be sure to update the cpp file's list as well. + // + {% for type in by_category["structure"] %} + {% if type.name.get() in [ + "buffer binding layout", + "sampler binding layout", + "texture binding layout", + "storage texture binding layout" + ] + %} + absl::FormatConvertResult<absl::FormatConversionCharSet::kString> + AbslFormatConvert(const {{as_cppType(type.name)}}& value, + const absl::FormatConversionSpec& spec, + absl::FormatSink* s); + {% endif %} + {% endfor %} + +} // namespace {{native_namespace}} + +{% set namespace = metadata.namespace %} +namespace {{namespace}} { + + // + // Enums + // + + {% for type in by_category["enum"] %} + absl::FormatConvertResult<absl::FormatConversionCharSet::kString|absl::FormatConversionCharSet::kIntegral> + AbslFormatConvert({{as_cppType(type.name)}} value, + const absl::FormatConversionSpec& spec, + absl::FormatSink* s); + {% endfor %} + + // + // Bitmasks + // + + {% for type in by_category["bitmask"] %} + absl::FormatConvertResult<absl::FormatConversionCharSet::kString|absl::FormatConversionCharSet::kIntegral> + AbslFormatConvert({{as_cppType(type.name)}} value, + const absl::FormatConversionSpec& spec, + absl::FormatSink* s); + {% endfor %} + +} // namespace {{namespace}} + +#endif // {{API}}_ABSL_FORMAT_H_
diff --git a/generator/templates/dawn/native/api_dawn_native_proc.cpp b/generator/templates/dawn/native/api_dawn_native_proc.cpp new file mode 100644 index 0000000..f9147c6 --- /dev/null +++ b/generator/templates/dawn/native/api_dawn_native_proc.cpp
@@ -0,0 +1,75 @@ +// Copyright 2021 The Dawn Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include <dawn/{{metadata.api.lower()}}.h> + +namespace dawn::native { + +// This file should be kept in sync with generator/templates/dawn/native/ProcTable.cpp + +{% for function in by_category["function"] %} + extern {{as_cType(function.return_type.name)}} Native{{as_cppType(function.name)}}( + {%- for arg in function.arguments -%} + {% if not loop.first %}, {% endif %}{{as_annotated_cType(arg)}} + {%- endfor -%} + ); +{% endfor %} +{% for type in by_category["object"] %} + {% for method in c_methods(type) %} + extern {{as_cType(method.return_type.name)}} Native{{as_MethodSuffix(type.name, method.name)}}( + {{-as_cType(type.name)}} cSelf + {%- for arg in method.arguments -%} + , {{as_annotated_cType(arg)}} + {%- endfor -%} + ); + {% endfor %} +{% endfor %} + +} + +extern "C" { + using namespace dawn::native; + + {% for function in by_category["function"] %} + {{as_cType(function.return_type.name)}} {{metadata.namespace}}{{as_cppType(function.name)}} ( + {%- for arg in function.arguments -%} + {% if not loop.first %}, {% endif %}{{as_annotated_cType(arg)}} + {%- endfor -%} + ) { + return Native{{as_cppType(function.name)}}( + {%- for arg in function.arguments -%} + {% if not loop.first %}, {% endif %}{{as_varName(arg.name)}} + {%- endfor -%} + ); + } + {% endfor %} + + {% for type in by_category["object"] %} + {% for method in c_methods(type) %} + {{as_cType(method.return_type.name)}} {{metadata.namespace}}{{as_MethodSuffix(type.name, method.name)}}( + {{-as_cType(type.name)}} cSelf + {%- for arg in method.arguments -%} + , {{as_annotated_cType(arg)}} + {%- endfor -%} + ) { + return Native{{as_MethodSuffix(type.name, method.name)}}( + cSelf + {%- for arg in method.arguments -%} + , {{as_varName(arg.name)}} + {%- endfor -%} + ); + } + {% endfor %} + {% endfor %} +}
diff --git a/generator/templates/dawn/native/api_structs.cpp b/generator/templates/dawn/native/api_structs.cpp new file mode 100644 index 0000000..86f54f0 --- /dev/null +++ b/generator/templates/dawn/native/api_structs.cpp
@@ -0,0 +1,75 @@ +//* Copyright 2018 The Dawn Authors +//* +//* Licensed under the Apache License, Version 2.0 (the "License"); +//* you may not use this file except in compliance with the License. +//* You may obtain a copy of the License at +//* +//* http://www.apache.org/licenses/LICENSE-2.0 +//* +//* Unless required by applicable law or agreed to in writing, software +//* distributed under the License is distributed on an "AS IS" BASIS, +//* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +//* See the License for the specific language governing permissions and +//* limitations under the License. + +{% set impl_dir = metadata.impl_dir + "/" if metadata.impl_dir else "" %} +{% set namespace_name = Name(metadata.native_namespace) %} +{% set native_namespace = namespace_name.namespace_case() %} +{% set native_dir = impl_dir + namespace_name.Dirs() %} +{% set namespace = metadata.namespace %} +#include "{{native_dir}}/{{namespace}}_structs_autogen.h" + +#include <tuple> + +#ifdef __GNUC__ +// error: 'offsetof' within non-standard-layout type '{{namespace}}::XXX' is conditionally-supported +#pragma GCC diagnostic ignored "-Winvalid-offsetof" +#endif + +namespace {{native_namespace}} { + + {% set c_prefix = metadata.c_prefix %} + static_assert(sizeof(ChainedStruct) == sizeof({{c_prefix}}ChainedStruct), + "sizeof mismatch for ChainedStruct"); + static_assert(alignof(ChainedStruct) == alignof({{c_prefix}}ChainedStruct), + "alignof mismatch for ChainedStruct"); + static_assert(offsetof(ChainedStruct, nextInChain) == offsetof({{c_prefix}}ChainedStruct, next), + "offsetof mismatch for ChainedStruct::nextInChain"); + static_assert(offsetof(ChainedStruct, sType) == offsetof({{c_prefix}}ChainedStruct, sType), + "offsetof mismatch for ChainedStruct::sType"); + + {% for type in by_category["structure"] %} + {% set CppType = as_cppType(type.name) %} + {% set CType = as_cType(type.name) %} + + static_assert(sizeof({{CppType}}) == sizeof({{CType}}), "sizeof mismatch for {{CppType}}"); + static_assert(alignof({{CppType}}) == alignof({{CType}}), "alignof mismatch for {{CppType}}"); + + {% if type.extensible %} + static_assert(offsetof({{CppType}}, nextInChain) == offsetof({{CType}}, nextInChain), + "offsetof mismatch for {{CppType}}::nextInChain"); + {% endif %} + {% for member in type.members %} + {% set memberName = member.name.camelCase() %} + static_assert(offsetof({{CppType}}, {{memberName}}) == offsetof({{CType}}, {{memberName}}), + "offsetof mismatch for {{CppType}}::{{memberName}}"); + {% endfor %} + + bool {{CppType}}::operator==(const {{as_cppType(type.name)}}& rhs) const { + return {% if type.extensible or type.chained -%} + (nextInChain == rhs.nextInChain) && + {%- endif %} std::tie( + {% for member in type.members %} + {{member.name.camelCase()-}} + {{ "," if not loop.last else "" }} + {% endfor %} + ) == std::tie( + {% for member in type.members %} + rhs.{{member.name.camelCase()-}} + {{ "," if not loop.last else "" }} + {% endfor %} + ); + } + + {% endfor %} +} // namespace {{native_namespace}}
diff --git a/generator/templates/dawn/native/api_structs.h b/generator/templates/dawn/native/api_structs.h new file mode 100644 index 0000000..d655344 --- /dev/null +++ b/generator/templates/dawn/native/api_structs.h
@@ -0,0 +1,87 @@ +//* Copyright 2017 The Dawn Authors +//* +//* Licensed under the Apache License, Version 2.0 (the "License"); +//* you may not use this file except in compliance with the License. +//* You may obtain a copy of the License at +//* +//* http://www.apache.org/licenses/LICENSE-2.0 +//* +//* Unless required by applicable law or agreed to in writing, software +//* distributed under the License is distributed on an "AS IS" BASIS, +//* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +//* See the License for the specific language governing permissions and +//* limitations under the License. + +{% set namespace_name = Name(metadata.native_namespace) %} +{% set DIR = namespace_name.concatcase().upper() %} +{% set namespace = metadata.namespace %} +#ifndef {{DIR}}_{{namespace.upper()}}_STRUCTS_H_ +#define {{DIR}}_{{namespace.upper()}}_STRUCTS_H_ + +{% set api = metadata.api.lower() %} +#include "dawn/{{api}}_cpp.h" +{% set impl_dir = metadata.impl_dir + "/" if metadata.impl_dir else "" %} +{% set native_namespace = namespace_name.namespace_case() %} +{% set native_dir = impl_dir + namespace_name.Dirs() %} +#include "{{native_dir}}/Forward.h" +#include <cmath> + +namespace {{native_namespace}} { + +{% macro render_cpp_default_value(member) -%} + {%- if member.annotation in ["*", "const*"] and member.optional or member.default_value == "nullptr" -%} + {{" "}}= nullptr + {%- elif member.type.category == "object" and member.optional -%} + {{" "}}= nullptr + {%- elif member.type.category in ["enum", "bitmask"] and member.default_value != None -%} + {{" "}}= {{namespace}}::{{as_cppType(member.type.name)}}::{{as_cppEnum(Name(member.default_value))}} + {%- elif member.type.category == "native" and member.default_value != None -%} + {{" "}}= {{member.default_value}} + {%- elif member.default_value != None -%} + {{" "}}= {{member.default_value}} + {%- else -%} + {{assert(member.default_value == None)}} + {%- endif -%} +{%- endmacro %} + + struct ChainedStruct { + ChainedStruct const * nextInChain = nullptr; + {{namespace}}::SType sType = {{namespace}}::SType::Invalid; + }; + + {% for type in by_category["structure"] %} + {% if type.chained %} + struct {{as_cppType(type.name)}} : ChainedStruct { + {{as_cppType(type.name)}}() { + sType = {{namespace}}::SType::{{type.name.CamelCase()}}; + } + {% else %} + struct {{as_cppType(type.name)}} { + {% endif %} + {% if type.extensible %} + ChainedStruct const * nextInChain = nullptr; + {% endif %} + {% for member in type.members %} + {% set member_declaration = as_annotated_frontendType(member) + render_cpp_default_value(member) %} + {% if type.chained and loop.first %} + //* Align the first member to ChainedStruct to match the C struct layout. + alignas(ChainedStruct) {{member_declaration}}; + {% else %} + {{member_declaration}}; + {% endif %} + {% endfor %} + + // Equality operators, mostly for testing. Note that this tests + // strict pointer-pointer equality if the struct contains member pointers. + bool operator==(const {{as_cppType(type.name)}}& rhs) const; + }; + + {% endfor %} + + {% for typeDef in by_category["typedef"] if typeDef.type.category == "structure" %} + using {{as_cppType(typeDef.name)}} = {{as_cppType(typeDef.type.name)}}; + {% endfor %} + +} // namespace {{native_namespace}} + +#endif // {{DIR}}_{{namespace.upper()}}_STRUCTS_H_
diff --git a/generator/templates/dawn/native/dawn_platform.h b/generator/templates/dawn/native/dawn_platform.h new file mode 100644 index 0000000..e3f1c91 --- /dev/null +++ b/generator/templates/dawn/native/dawn_platform.h
@@ -0,0 +1,82 @@ +//* Copyright 2021 The Dawn Authors +//* +//* Licensed under the Apache License, Version 2.0 (the "License"); +//* you may not use this file except in compliance with the License. +//* You may obtain a copy of the License at +//* +//* http://www.apache.org/licenses/LICENSE-2.0 +//* +//* Unless required by applicable law or agreed to in writing, software +//* distributed under the License is distributed on an "AS IS" BASIS, +//* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +//* See the License for the specific language governing permissions and +//* limitations under the License. + +{% set namespace_name = Name(metadata.native_namespace) %} +{% set NATIVE_DIR = namespace_name.concatcase().upper() %} +{% set PREFIX = metadata.proc_table_prefix.upper() %} +#ifndef {{NATIVE_DIR}}_{{PREFIX}}_PLATFORM_AUTOGEN_H_ +#define {{NATIVE_DIR}}_{{PREFIX}}_PLATFORM_AUTOGEN_H_ + +{% set api = metadata.api.lower() %} +#include "dawn/{{api}}_cpp.h" +{% set impl_dir = metadata.impl_dir + "/" if metadata.impl_dir else "" %} +{% set native_namespace = namespace_name.namespace_case() %} +{% set native_dir = impl_dir + namespace_name.Dirs() %} +#include "{{native_dir}}/Forward.h" + +{% set namespace = metadata.namespace %} +// Use our autogenerated version of the {{namespace}} structures that point to {{native_namespace}} object types +// (wgpu::Buffer is dawn::native::BufferBase*) +#include <{{native_dir}}/{{namespace}}_structs_autogen.h> + +namespace {{native_namespace}} { + + {% for type in by_category["structure"] %} + inline const {{as_cType(type.name)}}* ToAPI(const {{as_cppType(type.name)}}* rhs) { + return reinterpret_cast<const {{as_cType(type.name)}}*>(rhs); + } + + inline {{as_cType(type.name)}}* ToAPI({{as_cppType(type.name)}}* rhs) { + return reinterpret_cast<{{as_cType(type.name)}}*>(rhs); + } + + inline const {{as_cppType(type.name)}}* FromAPI(const {{as_cType(type.name)}}* rhs) { + return reinterpret_cast<const {{as_cppType(type.name)}}*>(rhs); + } + + inline {{as_cppType(type.name)}}* FromAPI({{as_cType(type.name)}}* rhs) { + return reinterpret_cast<{{as_cppType(type.name)}}*>(rhs); + } + {% endfor %} + + {% for type in by_category["object"] %} + inline const {{as_cType(type.name)}}Impl* ToAPI(const {{as_cppType(type.name)}}Base* rhs) { + return reinterpret_cast<const {{as_cType(type.name)}}Impl*>(rhs); + } + + inline {{as_cType(type.name)}}Impl* ToAPI({{as_cppType(type.name)}}Base* rhs) { + return reinterpret_cast<{{as_cType(type.name)}}Impl*>(rhs); + } + + inline const {{as_cppType(type.name)}}Base* FromAPI(const {{as_cType(type.name)}}Impl* rhs) { + return reinterpret_cast<const {{as_cppType(type.name)}}Base*>(rhs); + } + + inline {{as_cppType(type.name)}}Base* FromAPI({{as_cType(type.name)}}Impl* rhs) { + return reinterpret_cast<{{as_cppType(type.name)}}Base*>(rhs); + } + {% endfor %} + + template <typename T> + struct EnumCount; + + {% for e in by_category["enum"] if e.contiguousFromZero %} + template<> + struct EnumCount<{{namespace}}::{{as_cppType(e.name)}}> { + static constexpr uint32_t value = {{len(e.values)}}; + }; + {% endfor %} +} + +#endif // {{NATIVE_DIR}}_{{PREFIX}}_PLATFORM_AUTOGEN_H_
diff --git a/generator/templates/dawn/wire/ObjectType.h b/generator/templates/dawn/wire/ObjectType.h new file mode 100644 index 0000000..54ae08e --- /dev/null +++ b/generator/templates/dawn/wire/ObjectType.h
@@ -0,0 +1,34 @@ +//* Copyright 2020 The Dawn Authors +//* +//* Licensed under the Apache License, Version 2.0 (the "License"); +//* you may not use this file except in compliance with the License. +//* You may obtain a copy of the License at +//* +//* http://www.apache.org/licenses/LICENSE-2.0 +//* +//* Unless required by applicable law or agreed to in writing, software +//* distributed under the License is distributed on an "AS IS" BASIS, +//* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +//* See the License for the specific language governing permissions and +//* limitations under the License. + +#ifndef DAWNWIRE_OBJECTTPYE_AUTOGEN_H_ +#define DAWNWIRE_OBJECTTPYE_AUTOGEN_H_ + +#include "dawn/common/ityp_array.h" + +namespace dawn::wire { + + enum class ObjectType : uint32_t { + {% for type in by_category["object"] %} + {{type.name.CamelCase()}}, + {% endfor %} + }; + + template <typename T> + using PerObjectType = ityp::array<ObjectType, T, {{len(by_category["object"])}}>; + +} // namespace dawn::wire + + +#endif // DAWNWIRE_OBJECTTPYE_AUTOGEN_H_
diff --git a/generator/templates/dawn/wire/WireCmd.cpp b/generator/templates/dawn/wire/WireCmd.cpp new file mode 100644 index 0000000..c945bee --- /dev/null +++ b/generator/templates/dawn/wire/WireCmd.cpp
@@ -0,0 +1,855 @@ +//* Copyright 2017 The Dawn Authors +//* +//* Licensed under the Apache License, Version 2.0 (the "License"); +//* you may not use this file except in compliance with the License. +//* You may obtain a copy of the License at +//* +//* http://www.apache.org/licenses/LICENSE-2.0 +//* +//* Unless required by applicable law or agreed to in writing, software +//* distributed under the License is distributed on an "AS IS" BASIS, +//* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +//* See the License for the specific language governing permissions and +//* limitations under the License. + +#include "dawn/wire/WireCmd_autogen.h" + +#include "dawn/common/Assert.h" +#include "dawn/common/Log.h" +#include "dawn/wire/BufferConsumer_impl.h" +#include "dawn/wire/Wire.h" + +#include <algorithm> +#include <cstring> +#include <limits> + +#ifdef __GNUC__ +// error: 'offsetof' within non-standard-layout type 'wgpu::XXX' is conditionally-supported +#pragma GCC diagnostic ignored "-Winvalid-offsetof" +#endif + +//* Helper macros so that the main [de]serialization functions can be written in a generic manner. + +//* Outputs an rvalue that's the number of elements a pointer member points to. +{% macro member_length(member, record_accessor) -%} + {%- if member.length == "constant" -%} + {{member.constant_length}}u + {%- else -%} + {{record_accessor}}{{as_varName(member.length.name)}} + {%- endif -%} +{%- endmacro %} + +//* Outputs the type that will be used on the wire for the member +{% macro member_transfer_type(member) -%} + {%- if member.type.category == "object" -%} + ObjectId + {%- elif member.type.category == "structure" -%} + {{as_cType(member.type.name)}}Transfer + {%- elif member.type.category == "bitmask" -%} + {{as_cType(member.type.name)}}Flags + {%- else -%} + {{ assert(as_cType(member.type.name) != "size_t") }} + {{as_cType(member.type.name)}} + {%- endif -%} +{%- endmacro %} + +//* Outputs the size of one element of the type that will be used on the wire for the member +{% macro member_transfer_sizeof(member) -%} + sizeof({{member_transfer_type(member)}}) +{%- endmacro %} + +//* Outputs the serialization code to put `in` in `out` +{% macro serialize_member(member, in, out) %} + {%- if member.type.category == "object" -%} + {%- set Optional = "Optional" if member.optional else "" -%} + WIRE_TRY(provider.Get{{Optional}}Id({{in}}, &{{out}})); + {%- elif member.type.category == "structure" -%} + {%- if member.type.is_wire_transparent -%} + static_assert(sizeof({{out}}) == sizeof({{in}}), "Serialize memcpy size must match."); + memcpy(&{{out}}, &{{in}}, {{member_transfer_sizeof(member)}}); + {%- else -%} + {%- set Provider = ", provider" if member.type.may_have_dawn_object else "" -%} + WIRE_TRY({{as_cType(member.type.name)}}Serialize({{in}}, &{{out}}, buffer{{Provider}})); + {%- endif -%} + {%- else -%} + {{out}} = {{in}}; + {%- endif -%} +{% endmacro %} + +//* Outputs the deserialization code to put `in` in `out` +{% macro deserialize_member(member, in, out) %} + {%- if member.type.category == "object" -%} + {%- set Optional = "Optional" if member.optional else "" -%} + WIRE_TRY(resolver.Get{{Optional}}FromId({{in}}, &{{out}})); + {%- elif member.type.category == "structure" -%} + {%- if member.type.is_wire_transparent -%} + static_assert(sizeof({{out}}) == sizeof({{in}}), "Deserialize memcpy size must match."); + memcpy(&{{out}}, const_cast<const {{member_transfer_type(member)}}*>(&{{in}}), {{member_transfer_sizeof(member)}}); + {%- else -%} + WIRE_TRY({{as_cType(member.type.name)}}Deserialize(&{{out}}, &{{in}}, deserializeBuffer, allocator + {%- if member.type.may_have_dawn_object -%} + , resolver + {%- endif -%} + )); + {%- endif -%} + {%- else -%} + static_assert(sizeof({{out}}) >= sizeof({{in}}), "Deserialize assignment may not narrow."); + {{out}} = {{in}}; + {%- endif -%} +{% endmacro %} + +//* The main [de]serialization macro +//* Methods are very similar to structures that have one member corresponding to each arguments. +//* This macro takes advantage of the similarity to output [de]serialization code for a record +//* that is either a structure or a method, with some special cases for each. +{% macro write_record_serialization_helpers(record, name, members, is_cmd=False, is_return_command=False) %} + {% set Return = "Return" if is_return_command else "" %} + {% set Cmd = "Cmd" if is_cmd else "" %} + {% set Inherits = " : CmdHeader" if is_cmd else "" %} + + //* Structure for the wire format of each of the records. Members that are values + //* are embedded directly in the structure. Other members are assumed to be in the + //* memory directly following the structure in the buffer. + struct {{Return}}{{name}}Transfer{{Inherits}} { + static_assert({{[is_cmd, record.extensible, record.chained].count(True)}} <= 1, + "Record must be at most one of is_cmd, extensible, and chained."); + {% if is_cmd %} + //* Start the transfer structure with the command ID, so that casting to WireCmd gives the ID. + {{Return}}WireCmd commandId; + {% elif record.extensible %} + bool hasNextInChain; + {% elif record.chained %} + WGPUChainedStructTransfer chain; + {% endif %} + + //* Value types are directly in the command, objects being replaced with their IDs. + {% for member in members if member.annotation == "value" %} + {{member_transfer_type(member)}} {{as_varName(member.name)}}; + {% endfor %} + + //* const char* have their length embedded directly in the command. + {% for member in members if member.length == "strlen" %} + uint64_t {{as_varName(member.name)}}Strlen; + {% endfor %} + + {% for member in members if member.optional and member.annotation != "value" and member.type.category != "object" %} + bool has_{{as_varName(member.name)}}; + {% endfor %} + }; + + {% if is_cmd %} + static_assert(offsetof({{Return}}{{name}}Transfer, commandSize) == 0); + static_assert(offsetof({{Return}}{{name}}Transfer, commandId) == sizeof(CmdHeader)); + {% endif %} + + {% if record.chained %} + static_assert(offsetof({{Return}}{{name}}Transfer, chain) == 0); + {% endif %} + + //* Returns the required transfer size for `record` in addition to the transfer structure. + DAWN_DECLARE_UNUSED size_t {{Return}}{{name}}GetExtraRequiredSize(const {{Return}}{{name}}{{Cmd}}& record) { + DAWN_UNUSED(record); + + size_t result = 0; + + //* Gather how much space will be needed for the extension chain. + {% if record.extensible %} + if (record.nextInChain != nullptr) { + result += GetChainedStructExtraRequiredSize(record.nextInChain); + } + {% endif %} + + //* Special handling of const char* that have their length embedded directly in the command + {% for member in members if member.length == "strlen" %} + {% set memberName = as_varName(member.name) %} + + {% if member.optional %} + bool has_{{memberName}} = record.{{memberName}} != nullptr; + if (has_{{memberName}}) + {% endif %} + { + result += std::strlen(record.{{memberName}}); + } + {% endfor %} + + //* Gather how much space will be needed for pointer members. + {% for member in members if member.length != "strlen" and not member.skip_serialize %} + {% if member.type.category != "object" and member.optional %} + if (record.{{as_varName(member.name)}} != nullptr) + {% endif %} + { + {% if member.annotation != "value" %} + {{ assert(member.annotation != "const*const*") }} + auto memberLength = {{member_length(member, "record.")}}; + result += memberLength * {{member_transfer_sizeof(member)}}; + //* Structures might contain more pointers so we need to add their extra size as well. + {% if member.type.category == "structure" %} + for (decltype(memberLength) i = 0; i < memberLength; ++i) { + {{assert(member.annotation == "const*")}} + result += {{as_cType(member.type.name)}}GetExtraRequiredSize(record.{{as_varName(member.name)}}[i]); + } + {% endif %} + {% elif member.type.category == "structure" %} + result += {{as_cType(member.type.name)}}GetExtraRequiredSize(record.{{as_varName(member.name)}}); + {% endif %} + } + {% endfor %} + + return result; + } + // GetExtraRequiredSize isn't used for structures that are value members of other structures + // because we assume they cannot contain pointers themselves. + DAWN_UNUSED_FUNC({{Return}}{{name}}GetExtraRequiredSize); + + //* Serializes `record` into `transfer`, using `buffer` to get more space for pointed-to data + //* and `provider` to serialize objects. + DAWN_DECLARE_UNUSED WireResult {{Return}}{{name}}Serialize( + const {{Return}}{{name}}{{Cmd}}& record, + {{Return}}{{name}}Transfer* transfer, + SerializeBuffer* buffer + {%- if record.may_have_dawn_object -%} + , const ObjectIdProvider& provider + {%- endif -%} + ) { + DAWN_UNUSED(buffer); + + //* Handle special transfer members of methods. + {% if is_cmd %} + transfer->commandId = {{Return}}WireCmd::{{name}}; + {% endif %} + + //* Value types are directly in the transfer record, objects being replaced with their IDs. + {% for member in members if member.annotation == "value" %} + {% set memberName = as_varName(member.name) %} + {{serialize_member(member, "record." + memberName, "transfer->" + memberName)}} + {% endfor %} + + {% if record.extensible %} + if (record.nextInChain != nullptr) { + transfer->hasNextInChain = true; + WIRE_TRY(SerializeChainedStruct(record.nextInChain, buffer, provider)); + } else { + transfer->hasNextInChain = false; + } + {% endif %} + + {% if record.chained %} + //* Should be set by the root descriptor's call to SerializeChainedStruct. + ASSERT(transfer->chain.sType == {{as_cEnum(types["s type"].name, record.name)}}); + ASSERT(transfer->chain.hasNext == (record.chain.next != nullptr)); + {% endif %} + + //* Special handling of const char* that have their length embedded directly in the command + {% for member in members if member.length == "strlen" %} + {% set memberName = as_varName(member.name) %} + + {% if member.optional %} + bool has_{{memberName}} = record.{{memberName}} != nullptr; + transfer->has_{{memberName}} = has_{{memberName}}; + if (has_{{memberName}}) + {% endif %} + { + transfer->{{memberName}}Strlen = std::strlen(record.{{memberName}}); + + char* stringInBuffer; + WIRE_TRY(buffer->NextN(transfer->{{memberName}}Strlen, &stringInBuffer)); + memcpy(stringInBuffer, record.{{memberName}}, transfer->{{memberName}}Strlen); + } + {% endfor %} + + //* Allocate space and write the non-value arguments in it. + {% for member in members if member.annotation != "value" and member.length != "strlen" and not member.skip_serialize %} + {{ assert(member.annotation != "const*const*") }} + {% set memberName = as_varName(member.name) %} + + {% if member.type.category != "object" and member.optional %} + bool has_{{memberName}} = record.{{memberName}} != nullptr; + transfer->has_{{memberName}} = has_{{memberName}}; + if (has_{{memberName}}) + {% endif %} + { + auto memberLength = {{member_length(member, "record.")}}; + + {{member_transfer_type(member)}}* memberBuffer; + WIRE_TRY(buffer->NextN(memberLength, &memberBuffer)); + + {% if member.type.is_wire_transparent %} + memcpy( + memberBuffer, record.{{memberName}}, + {{member_transfer_sizeof(member)}} * memberLength); + {% else %} + //* This loop cannot overflow because it iterates up to |memberLength|. Even if + //* memberLength were the maximum integer value, |i| would become equal to it + //* just before exiting the loop, but not increment past or wrap around. + for (decltype(memberLength) i = 0; i < memberLength; ++i) { + {{serialize_member(member, "record." + memberName + "[i]", "memberBuffer[i]" )}} + } + {% endif %} + } + {% endfor %} + return WireResult::Success; + } + DAWN_UNUSED_FUNC({{Return}}{{name}}Serialize); + + //* Deserializes `transfer` into `record` getting more serialized data from `buffer` and `size` + //* if needed, using `allocator` to store pointed-to values and `resolver` to translate object + //* Ids to actual objects. + DAWN_DECLARE_UNUSED WireResult {{Return}}{{name}}Deserialize( + {{Return}}{{name}}{{Cmd}}* record, + const volatile {{Return}}{{name}}Transfer* transfer, + DeserializeBuffer* deserializeBuffer, + DeserializeAllocator* allocator + {%- if record.may_have_dawn_object -%} + , const ObjectIdResolver& resolver + {%- endif -%} + ) { + DAWN_UNUSED(allocator); + + {% if is_cmd %} + ASSERT(transfer->commandId == {{Return}}WireCmd::{{name}}); + {% endif %} + + {% if record.derived_method %} + record->selfId = transfer->self; + {% endif %} + + //* Value types are directly in the transfer record, objects being replaced with their IDs. + {% for member in members if member.annotation == "value" %} + {% set memberName = as_varName(member.name) %} + {{deserialize_member(member, "transfer->" + memberName, "record->" + memberName)}} + {% endfor %} + + {% if record.extensible %} + record->nextInChain = nullptr; + if (transfer->hasNextInChain) { + WIRE_TRY(DeserializeChainedStruct(&record->nextInChain, deserializeBuffer, allocator, resolver)); + } + {% endif %} + + {% if record.chained %} + //* Should be set by the root descriptor's call to DeserializeChainedStruct. + //* Don't check |record->chain.next| matches because it is not set until the + //* next iteration inside DeserializeChainedStruct. + ASSERT(record->chain.sType == {{as_cEnum(types["s type"].name, record.name)}}); + ASSERT(record->chain.next == nullptr); + {% endif %} + + //* Special handling of const char* that have their length embedded directly in the command + {% for member in members if member.length == "strlen" %} + {% set memberName = as_varName(member.name) %} + + {% if member.optional %} + bool has_{{memberName}} = transfer->has_{{memberName}}; + record->{{memberName}} = nullptr; + if (has_{{memberName}}) + {% endif %} + { + uint64_t stringLength64 = transfer->{{memberName}}Strlen; + if (stringLength64 >= std::numeric_limits<size_t>::max()) { + //* Cannot allocate space for the string. It can be at most + //* size_t::max() - 1. We need 1 byte for the null-terminator. + return WireResult::FatalError; + } + size_t stringLength = static_cast<size_t>(stringLength64); + + const volatile char* stringInBuffer; + WIRE_TRY(deserializeBuffer->ReadN(stringLength, &stringInBuffer)); + + char* copiedString; + WIRE_TRY(GetSpace(allocator, stringLength + 1, &copiedString)); + //* We can cast away the volatile qualifier because DeserializeBuffer::ReadN already + //* validated that the range [stringInBuffer, stringInBuffer + stringLength) is valid. + //* memcpy may have an unknown access pattern, but this is fine since the string is only + //* data and won't affect control flow of this function. + memcpy(copiedString, const_cast<const char*>(stringInBuffer), stringLength); + copiedString[stringLength] = '\0'; + record->{{memberName}} = copiedString; + } + {% endfor %} + + //* Get extra buffer data, and copy pointed to values in extra allocated space. + {% for member in members if member.annotation != "value" and member.length != "strlen" %} + {{ assert(member.annotation != "const*const*") }} + {% set memberName = as_varName(member.name) %} + + {% if member.type.category != "object" and member.optional %} + //* Non-constant length optional members use length=0 to denote they aren't present. + //* Otherwise we could have length=N and has_member=false, causing reads from an + //* uninitialized pointer. + {{ assert(member.length == "constant") }} + bool has_{{memberName}} = transfer->has_{{memberName}}; + record->{{memberName}} = nullptr; + if (has_{{memberName}}) + {% endif %} + { + auto memberLength = {{member_length(member, "record->")}}; + const volatile {{member_transfer_type(member)}}* memberBuffer; + WIRE_TRY(deserializeBuffer->ReadN(memberLength, &memberBuffer)); + + //* For data-only members (e.g. "data" in WriteBuffer and WriteTexture), they are + //* not security sensitive so we can directly refer the data inside the transfer + //* buffer in dawn_native. For other members, as prevention of TOCTOU attacks is an + //* important feature of the wire, we must make sure every single value returned to + //* dawn_native must be a copy of what's in the wire. + {% if member.json_data["wire_is_data_only"] %} + record->{{memberName}} = + const_cast<const {{member_transfer_type(member)}}*>(memberBuffer); + + {% else %} + {{as_cType(member.type.name)}}* copiedMembers; + WIRE_TRY(GetSpace(allocator, memberLength, &copiedMembers)); + record->{{memberName}} = copiedMembers; + + {% if member.type.is_wire_transparent %} + //* memcpy is not allowed to copy from volatile objects. However, these + //* arrays are just used as plain data, and don't impact control flow. So if + //* the underlying data were changed while the copy was still executing, we + //* would get different data - but it wouldn't cause unexpected downstream + //* effects. + memcpy( + copiedMembers, + const_cast<const {{member_transfer_type(member)}}*>(memberBuffer), + {{member_transfer_sizeof(member)}} * memberLength); + {% else %} + //* This loop cannot overflow because it iterates up to |memberLength|. Even + //* if memberLength were the maximum integer value, |i| would become equal + //* to it just before exiting the loop, but not increment past or wrap + //* around. + for (decltype(memberLength) i = 0; i < memberLength; ++i) { + {{deserialize_member(member, "memberBuffer[i]", "copiedMembers[i]")}} + } + {% endif %} + {% endif %} + } + {% endfor %} + + return WireResult::Success; + } + DAWN_UNUSED_FUNC({{Return}}{{name}}Deserialize); +{% endmacro %} + +{% macro write_command_serialization_methods(command, is_return) %} + {% set Return = "Return" if is_return else "" %} + {% set Name = Return + command.name.CamelCase() %} + {% set Cmd = Name + "Cmd" %} + + size_t {{Cmd}}::GetRequiredSize() const { + size_t size = sizeof({{Name}}Transfer) + {{Name}}GetExtraRequiredSize(*this); + return size; + } + + {% if command.may_have_dawn_object %} + WireResult {{Cmd}}::Serialize( + size_t commandSize, + SerializeBuffer* buffer, + const ObjectIdProvider& provider + ) const { + {{Name}}Transfer* transfer; + WIRE_TRY(buffer->Next(&transfer)); + transfer->commandSize = commandSize; + return ({{Name}}Serialize(*this, transfer, buffer, provider)); + } + WireResult {{Cmd}}::Serialize(size_t commandSize, SerializeBuffer* buffer) const { + ErrorObjectIdProvider provider; + return Serialize(commandSize, buffer, provider); + } + + WireResult {{Cmd}}::Deserialize( + DeserializeBuffer* deserializeBuffer, + DeserializeAllocator* allocator, + const ObjectIdResolver& resolver + ) { + const volatile {{Name}}Transfer* transfer; + WIRE_TRY(deserializeBuffer->Read(&transfer)); + return {{Name}}Deserialize(this, transfer, deserializeBuffer, allocator, resolver); + } + WireResult {{Cmd}}::Deserialize(DeserializeBuffer* deserializeBuffer, DeserializeAllocator* allocator) { + ErrorObjectIdResolver resolver; + return Deserialize(deserializeBuffer, allocator, resolver); + } + {% else %} + WireResult {{Cmd}}::Serialize(size_t commandSize, SerializeBuffer* buffer) const { + {{Name}}Transfer* transfer; + WIRE_TRY(buffer->Next(&transfer)); + transfer->commandSize = commandSize; + return ({{Name}}Serialize(*this, transfer, buffer)); + } + WireResult {{Cmd}}::Serialize( + size_t commandSize, + SerializeBuffer* buffer, + const ObjectIdProvider& + ) const { + return Serialize(commandSize, buffer); + } + + WireResult {{Cmd}}::Deserialize(DeserializeBuffer* deserializeBuffer, DeserializeAllocator* allocator) { + const volatile {{Name}}Transfer* transfer; + WIRE_TRY(deserializeBuffer->Read(&transfer)); + return {{Name}}Deserialize(this, transfer, deserializeBuffer, allocator); + } + WireResult {{Cmd}}::Deserialize( + DeserializeBuffer* deserializeBuffer, + DeserializeAllocator* allocator, + const ObjectIdResolver& + ) { + return Deserialize(deserializeBuffer, allocator); + } + {% endif %} +{% endmacro %} + +{% macro make_chained_struct_serialization_helpers(out=None) %} + {% set ChainedStructPtr = "WGPUChainedStructOut*" if out else "const WGPUChainedStruct*" %} + {% set ChainedStruct = "WGPUChainedStructOut" if out else "WGPUChainedStruct" %} + size_t GetChainedStructExtraRequiredSize({{ChainedStructPtr}} chainedStruct) { + ASSERT(chainedStruct != nullptr); + size_t result = 0; + while (chainedStruct != nullptr) { + switch (chainedStruct->sType) { + {% for sType in types["s type"].values if ( + sType.valid and + (sType.name.CamelCase() not in client_side_structures) and + (types[sType.name.get()].output == out) + ) %} + case {{as_cEnum(types["s type"].name, sType.name)}}: { + const auto& typedStruct = *reinterpret_cast<{{as_cType(sType.name)}} const *>(chainedStruct); + result += sizeof({{as_cType(sType.name)}}Transfer); + result += {{as_cType(sType.name)}}GetExtraRequiredSize(typedStruct); + chainedStruct = typedStruct.chain.next; + break; + } + {% endfor %} + // Explicitly list the Invalid enum. MSVC complains about no case labels. + case WGPUSType_Invalid: + default: + // Invalid enum. Reserve space just for the transfer header (sType and hasNext). + result += sizeof(WGPUChainedStructTransfer); + chainedStruct = chainedStruct->next; + break; + } + } + return result; + } + + [[nodiscard]] WireResult SerializeChainedStruct({{ChainedStructPtr}} chainedStruct, + SerializeBuffer* buffer, + const ObjectIdProvider& provider) { + ASSERT(chainedStruct != nullptr); + ASSERT(buffer != nullptr); + do { + switch (chainedStruct->sType) { + {% for sType in types["s type"].values if ( + sType.valid and + (sType.name.CamelCase() not in client_side_structures) and + (types[sType.name.get()].output == out) + ) %} + {% set CType = as_cType(sType.name) %} + case {{as_cEnum(types["s type"].name, sType.name)}}: { + + {{CType}}Transfer* transfer; + WIRE_TRY(buffer->Next(&transfer)); + transfer->chain.sType = chainedStruct->sType; + transfer->chain.hasNext = chainedStruct->next != nullptr; + + WIRE_TRY({{CType}}Serialize(*reinterpret_cast<{{CType}} const*>(chainedStruct), transfer, buffer + {%- if types[sType.name.get()].may_have_dawn_object -%} + , provider + {%- endif -%} + )); + + chainedStruct = chainedStruct->next; + } break; + {% endfor %} + // Explicitly list the Invalid enum. MSVC complains about no case labels. + case WGPUSType_Invalid: + default: { + // Invalid enum. Serialize just the transfer header with Invalid as the sType. + // TODO(crbug.com/dawn/369): Unknown sTypes are silently discarded. + if (chainedStruct->sType != WGPUSType_Invalid) { + dawn::WarningLog() << "Unknown sType " << chainedStruct->sType << " discarded."; + } + + WGPUChainedStructTransfer* transfer; + WIRE_TRY(buffer->Next(&transfer)); + transfer->sType = WGPUSType_Invalid; + transfer->hasNext = chainedStruct->next != nullptr; + + // Still move on in case there are valid structs after this. + chainedStruct = chainedStruct->next; + break; + } + } + } while (chainedStruct != nullptr); + return WireResult::Success; + } + + WireResult DeserializeChainedStruct({{ChainedStructPtr}}* outChainNext, + DeserializeBuffer* deserializeBuffer, + DeserializeAllocator* allocator, + const ObjectIdResolver& resolver) { + bool hasNext; + do { + const volatile WGPUChainedStructTransfer* header; + WIRE_TRY(deserializeBuffer->Peek(&header)); + WGPUSType sType = header->sType; + switch (sType) { + {% for sType in types["s type"].values if ( + sType.valid and + (sType.name.CamelCase() not in client_side_structures) and + (types[sType.name.get()].output == out) + ) %} + {% set CType = as_cType(sType.name) %} + case {{as_cEnum(types["s type"].name, sType.name)}}: { + const volatile {{CType}}Transfer* transfer; + WIRE_TRY(deserializeBuffer->Read(&transfer)); + + {{CType}}* outStruct; + WIRE_TRY(GetSpace(allocator, sizeof({{CType}}), &outStruct)); + outStruct->chain.sType = sType; + outStruct->chain.next = nullptr; + + *outChainNext = &outStruct->chain; + outChainNext = &outStruct->chain.next; + + WIRE_TRY({{CType}}Deserialize(outStruct, transfer, deserializeBuffer, allocator + {%- if types[sType.name.get()].may_have_dawn_object -%} + , resolver + {%- endif -%} + )); + + hasNext = transfer->chain.hasNext; + } break; + {% endfor %} + // Explicitly list the Invalid enum. MSVC complains about no case labels. + case WGPUSType_Invalid: + default: { + // Invalid enum. Deserialize just the transfer header with Invalid as the sType. + // TODO(crbug.com/dawn/369): Unknown sTypes are silently discarded. + if (sType != WGPUSType_Invalid) { + dawn::WarningLog() << "Unknown sType " << sType << " discarded."; + } + + const volatile WGPUChainedStructTransfer* transfer; + WIRE_TRY(deserializeBuffer->Read(&transfer)); + + {{ChainedStruct}}* outStruct; + WIRE_TRY(GetSpace(allocator, sizeof({{ChainedStruct}}), &outStruct)); + outStruct->sType = WGPUSType_Invalid; + outStruct->next = nullptr; + + // Still move on in case there are valid structs after this. + *outChainNext = outStruct; + outChainNext = &outStruct->next; + hasNext = transfer->hasNext; + break; + } + } + } while (hasNext); + + return WireResult::Success; + } +{% endmacro %} + +namespace dawn::wire { + + ObjectHandle::ObjectHandle() = default; + ObjectHandle::ObjectHandle(ObjectId id, ObjectGeneration generation) + : id(id), generation(generation) { + } + + ObjectHandle::ObjectHandle(const volatile ObjectHandle& rhs) + : id(rhs.id), generation(rhs.generation) { + } + ObjectHandle& ObjectHandle::operator=(const volatile ObjectHandle& rhs) { + id = rhs.id; + generation = rhs.generation; + return *this; + } + + ObjectHandle& ObjectHandle::AssignFrom(const ObjectHandle& rhs) { + id = rhs.id; + generation = rhs.generation; + return *this; + } + ObjectHandle& ObjectHandle::AssignFrom(const volatile ObjectHandle& rhs) { + id = rhs.id; + generation = rhs.generation; + return *this; + } + + namespace { + // Allocates enough space from allocator to countain T[count] and return it in out. + // Return FatalError if the allocator couldn't allocate the memory. + // Always writes to |out| on success. + template <typename T, typename N> + WireResult GetSpace(DeserializeAllocator* allocator, N count, T** out) { + constexpr size_t kMaxCountWithoutOverflows = std::numeric_limits<size_t>::max() / sizeof(T); + if (count > kMaxCountWithoutOverflows) { + return WireResult::FatalError; + } + + size_t totalSize = sizeof(T) * count; + *out = static_cast<T*>(allocator->GetSpace(totalSize)); + if (*out == nullptr) { + return WireResult::FatalError; + } + + return WireResult::Success; + } + + struct WGPUChainedStructTransfer { + WGPUSType sType; + bool hasNext; + }; + + size_t GetChainedStructExtraRequiredSize(const WGPUChainedStruct* chainedStruct); + [[nodiscard]] WireResult SerializeChainedStruct(const WGPUChainedStruct* chainedStruct, + SerializeBuffer* buffer, + const ObjectIdProvider& provider); + WireResult DeserializeChainedStruct(const WGPUChainedStruct** outChainNext, + DeserializeBuffer* deserializeBuffer, + DeserializeAllocator* allocator, + const ObjectIdResolver& resolver); + + size_t GetChainedStructExtraRequiredSize(WGPUChainedStructOut* chainedStruct); + [[nodiscard]] WireResult SerializeChainedStruct(WGPUChainedStructOut* chainedStruct, + SerializeBuffer* buffer, + const ObjectIdProvider& provider); + WireResult DeserializeChainedStruct(WGPUChainedStructOut** outChainNext, + DeserializeBuffer* deserializeBuffer, + DeserializeAllocator* allocator, + const ObjectIdResolver& resolver); + + //* Output structure [de]serialization first because it is used by commands. + {% for type in by_category["structure"] %} + {% set name = as_cType(type.name) %} + {% if type.name.CamelCase() not in client_side_structures %} + {{write_record_serialization_helpers(type, name, type.members, is_cmd=False)}} + {% endif %} + {% endfor %} + + + {{ make_chained_struct_serialization_helpers(out=False) }} + {{ make_chained_struct_serialization_helpers(out=True) }} + + //* Output [de]serialization helpers for commands + {% for command in cmd_records["command"] %} + {% set name = command.name.CamelCase() %} + {{write_record_serialization_helpers(command, name, command.members, is_cmd=True)}} + {% endfor %} + + //* Output [de]serialization helpers for return commands + {% for command in cmd_records["return command"] %} + {% set name = command.name.CamelCase() %} + {{write_record_serialization_helpers(command, name, command.members, + is_cmd=True, is_return_command=True)}} + {% endfor %} + + // Implementation of ObjectIdResolver that always errors. + // Used when the generator adds a provider argument because of a chained + // struct, but in practice, a chained struct in that location is invalid. + class ErrorObjectIdResolver final : public ObjectIdResolver { + public: + {% for type in by_category["object"] %} + WireResult GetFromId(ObjectId id, {{as_cType(type.name)}}* out) const override { + return WireResult::FatalError; + } + WireResult GetOptionalFromId(ObjectId id, {{as_cType(type.name)}}* out) const override { + return WireResult::FatalError; + } + {% endfor %} + }; + + // Implementation of ObjectIdProvider that always errors. + // Used when the generator adds a provider argument because of a chained + // struct, but in practice, a chained struct in that location is invalid. + class ErrorObjectIdProvider final : public ObjectIdProvider { + public: + {% for type in by_category["object"] %} + WireResult GetId({{as_cType(type.name)}} object, ObjectId* out) const override { + return WireResult::FatalError; + } + WireResult GetOptionalId({{as_cType(type.name)}} object, ObjectId* out) const override { + return WireResult::FatalError; + } + {% endfor %} + }; + + } // anonymous namespace + + {% for command in cmd_records["command"] %} + {{ write_command_serialization_methods(command, False) }} + {% endfor %} + + {% for command in cmd_records["return command"] %} + {{ write_command_serialization_methods(command, True) }} + {% endfor %} + + // Implementations of serialization/deserialization of WPGUDeviceProperties. + size_t SerializedWGPUDevicePropertiesSize(const WGPUDeviceProperties* deviceProperties) { + return sizeof(WGPUDeviceProperties) + + WGPUDevicePropertiesGetExtraRequiredSize(*deviceProperties); + } + + void SerializeWGPUDeviceProperties(const WGPUDeviceProperties* deviceProperties, + char* buffer) { + SerializeBuffer serializeBuffer(buffer, SerializedWGPUDevicePropertiesSize(deviceProperties)); + + WGPUDevicePropertiesTransfer* transfer; + + WireResult result = serializeBuffer.Next(&transfer); + ASSERT(result == WireResult::Success); + + ErrorObjectIdProvider provider; + result = WGPUDevicePropertiesSerialize(*deviceProperties, transfer, &serializeBuffer, provider); + ASSERT(result == WireResult::Success); + } + + bool DeserializeWGPUDeviceProperties(WGPUDeviceProperties* deviceProperties, + const volatile char* buffer, + size_t size) { + const volatile WGPUDevicePropertiesTransfer* transfer; + DeserializeBuffer deserializeBuffer(buffer, size); + if (deserializeBuffer.Read(&transfer) != WireResult::Success) { + return false; + } + + ErrorObjectIdResolver resolver; + return WGPUDevicePropertiesDeserialize(deviceProperties, transfer, &deserializeBuffer, + nullptr, resolver) == WireResult::Success; + } + + size_t SerializedWGPUSupportedLimitsSize(const WGPUSupportedLimits* supportedLimits) { + return sizeof(WGPUSupportedLimits) + + WGPUSupportedLimitsGetExtraRequiredSize(*supportedLimits); + } + + void SerializeWGPUSupportedLimits( + const WGPUSupportedLimits* supportedLimits, + char* buffer) { + SerializeBuffer serializeBuffer(buffer, SerializedWGPUSupportedLimitsSize(supportedLimits)); + + WGPUSupportedLimitsTransfer* transfer; + + WireResult result = serializeBuffer.Next(&transfer); + ASSERT(result == WireResult::Success); + + ErrorObjectIdProvider provider; + result = WGPUSupportedLimitsSerialize(*supportedLimits, transfer, &serializeBuffer, provider); + ASSERT(result == WireResult::Success); + } + + bool DeserializeWGPUSupportedLimits(WGPUSupportedLimits* supportedLimits, + const volatile char* buffer, + size_t size) { + const volatile WGPUSupportedLimitsTransfer* transfer; + DeserializeBuffer deserializeBuffer(buffer, size); + if (deserializeBuffer.Read(&transfer) != WireResult::Success) { + return false; + } + + ErrorObjectIdResolver resolver; + return WGPUSupportedLimitsDeserialize(supportedLimits, transfer, &deserializeBuffer, + nullptr, resolver) == WireResult::Success; + } + +} // namespace dawn::wire
diff --git a/generator/templates/dawn/wire/WireCmd.h b/generator/templates/dawn/wire/WireCmd.h new file mode 100644 index 0000000..f8c2762 --- /dev/null +++ b/generator/templates/dawn/wire/WireCmd.h
@@ -0,0 +1,138 @@ +//* Copyright 2017 The Dawn Authors +//* +//* Licensed under the Apache License, Version 2.0 (the "License"); +//* you may not use this file except in compliance with the License. +//* You may obtain a copy of the License at +//* +//* http://www.apache.org/licenses/LICENSE-2.0 +//* +//* Unless required by applicable law or agreed to in writing, software +//* distributed under the License is distributed on an "AS IS" BASIS, +//* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +//* See the License for the specific language governing permissions and +//* limitations under the License. + +#ifndef DAWNWIRE_WIRECMD_AUTOGEN_H_ +#define DAWNWIRE_WIRECMD_AUTOGEN_H_ + +#include <dawn/webgpu.h> + +#include "dawn/wire/BufferConsumer.h" +#include "dawn/wire/ObjectType_autogen.h" +#include "dawn/wire/WireResult.h" + +namespace dawn::wire { + + using ObjectId = uint32_t; + using ObjectGeneration = uint32_t; + struct ObjectHandle { + ObjectId id; + ObjectGeneration generation; + + ObjectHandle(); + ObjectHandle(ObjectId id, ObjectGeneration generation); + + ObjectHandle(const volatile ObjectHandle& rhs); + ObjectHandle& operator=(const volatile ObjectHandle& rhs); + + // MSVC has a bug where it thinks the volatile copy assignment is a duplicate. + // Workaround this by forwarding to a different function AssignFrom. + template <typename T> + ObjectHandle& operator=(const T& rhs) { + return AssignFrom(rhs); + } + ObjectHandle& AssignFrom(const ObjectHandle& rhs); + ObjectHandle& AssignFrom(const volatile ObjectHandle& rhs); + }; + + // Interface to allocate more space to deserialize pointed-to data. + // nullptr is treated as an error. + class DeserializeAllocator { + public: + virtual void* GetSpace(size_t size) = 0; + }; + + // Interface to convert an ID to a server object, if possible. + // Methods return FatalError if the ID is for a non-existent object and Success otherwise. + class ObjectIdResolver { + public: + {% for type in by_category["object"] %} + virtual WireResult GetFromId(ObjectId id, {{as_cType(type.name)}}* out) const = 0; + virtual WireResult GetOptionalFromId(ObjectId id, {{as_cType(type.name)}}* out) const = 0; + {% endfor %} + }; + + // Interface to convert a client object to its ID for the wiring. + class ObjectIdProvider { + public: + {% for type in by_category["object"] %} + virtual WireResult GetId({{as_cType(type.name)}} object, ObjectId* out) const = 0; + virtual WireResult GetOptionalId({{as_cType(type.name)}} object, ObjectId* out) const = 0; + {% endfor %} + }; + + //* Enum used as a prefix to each command on the wire format. + enum class WireCmd : uint32_t { + {% for command in cmd_records["command"] %} + {{command.name.CamelCase()}}, + {% endfor %} + }; + + //* Enum used as a prefix to each command on the return wire format. + enum class ReturnWireCmd : uint32_t { + {% for command in cmd_records["return command"] %} + {{command.name.CamelCase()}}, + {% endfor %} + }; + + struct CmdHeader { + uint64_t commandSize; + }; + +{% macro write_command_struct(command, is_return_command) %} + {% set Return = "Return" if is_return_command else "" %} + {% set Cmd = command.name.CamelCase() + "Cmd" %} + struct {{Return}}{{Cmd}} { + //* From a filled structure, compute how much size will be used in the serialization buffer. + size_t GetRequiredSize() const; + + //* Serialize the structure and everything it points to into serializeBuffer which must be + //* big enough to contain all the data (as queried from GetRequiredSize). + WireResult Serialize(size_t commandSize, SerializeBuffer* serializeBuffer, const ObjectIdProvider& objectIdProvider) const; + // Override which produces a FatalError if any object is used. + WireResult Serialize(size_t commandSize, SerializeBuffer* serializeBuffer) const; + + //* Deserializes the structure from a buffer, consuming a maximum of *size bytes. When this + //* function returns, buffer and size will be updated by the number of bytes consumed to + //* deserialize the structure. Structures containing pointers will use allocator to get + //* scratch space to deserialize the pointed-to data. + //* Deserialize returns: + //* - Success if everything went well (yay!) + //* - FatalError is something bad happened (buffer too small for example) + WireResult Deserialize(DeserializeBuffer* deserializeBuffer, DeserializeAllocator* allocator, const ObjectIdResolver& resolver); + // Override which produces a FatalError if any object is used. + WireResult Deserialize(DeserializeBuffer* deserializeBuffer, DeserializeAllocator* allocator); + + {% if command.derived_method %} + //* Command handlers want to know the object ID in addition to the backing object. + //* Doesn't need to be filled before Serialize, or GetRequiredSize. + ObjectId selfId; + {% endif %} + + {% for member in command.members %} + {{as_annotated_cType(member)}}; + {% endfor %} + }; +{% endmacro %} + + {% for command in cmd_records["command"] %} + {{write_command_struct(command, False)}} + {% endfor %} + + {% for command in cmd_records["return command"] %} + {{write_command_struct(command, True)}} + {% endfor %} + +} // namespace dawn::wire + +#endif // DAWNWIRE_WIRECMD_AUTOGEN_H_
diff --git a/generator/templates/dawn/wire/client/ApiObjects.h b/generator/templates/dawn/wire/client/ApiObjects.h new file mode 100644 index 0000000..8c1729d --- /dev/null +++ b/generator/templates/dawn/wire/client/ApiObjects.h
@@ -0,0 +1,53 @@ +//* Copyright 2019 The Dawn Authors +//* +//* Licensed under the Apache License, Version 2.0 (the "License"); +//* you may not use this file except in compliance with the License. +//* You may obtain a copy of the License at +//* +//* http://www.apache.org/licenses/LICENSE-2.0 +//* +//* Unless required by applicable law or agreed to in writing, software +//* distributed under the License is distributed on an "AS IS" BASIS, +//* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +//* See the License for the specific language governing permissions and +//* limitations under the License. + +#ifndef DAWNWIRE_CLIENT_APIOBJECTS_AUTOGEN_H_ +#define DAWNWIRE_CLIENT_APIOBJECTS_AUTOGEN_H_ + +#include "dawn/wire/ObjectType_autogen.h" +#include "dawn/wire/client/ObjectBase.h" + +namespace dawn::wire::client { + + template <typename T> + struct ObjectTypeToTypeEnum { + static constexpr ObjectType value = static_cast<ObjectType>(-1); + }; + + {% for type in by_category["object"] %} + {% set Type = type.name.CamelCase() %} + {% if type.name.CamelCase() in client_special_objects %} + class {{Type}}; + {% else %} + struct {{type.name.CamelCase()}} final : ObjectBase { + using ObjectBase::ObjectBase; + }; + {% endif %} + + inline {{Type}}* FromAPI(WGPU{{Type}} obj) { + return reinterpret_cast<{{Type}}*>(obj); + } + inline WGPU{{Type}} ToAPI({{Type}}* obj) { + return reinterpret_cast<WGPU{{Type}}>(obj); + } + + template <> + struct ObjectTypeToTypeEnum<{{Type}}> { + static constexpr ObjectType value = ObjectType::{{Type}}; + }; + + {% endfor %} +} // namespace dawn::wire::client + +#endif // DAWNWIRE_CLIENT_APIOBJECTS_AUTOGEN_H_
diff --git a/generator/templates/dawn/wire/client/ApiProcs.cpp b/generator/templates/dawn/wire/client/ApiProcs.cpp new file mode 100644 index 0000000..d6e5279 --- /dev/null +++ b/generator/templates/dawn/wire/client/ApiProcs.cpp
@@ -0,0 +1,178 @@ +//* Copyright 2019 The Dawn Authors +//* +//* Licensed under the Apache License, Version 2.0 (the "License"); +//* you may not use this file except in compliance with the License. +//* You may obtain a copy of the License at +//* +//* http://www.apache.org/licenses/LICENSE-2.0 +//* +//* Unless required by applicable law or agreed to in writing, software +//* distributed under the License is distributed on an "AS IS" BASIS, +//* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +//* See the License for the specific language governing permissions and +//* limitations under the License. + +#include "dawn/common/Log.h" +#include "dawn/wire/client/ApiObjects.h" +#include "dawn/wire/client/Client.h" + +#include <algorithm> +#include <cstring> +#include <string> +#include <vector> + +namespace dawn::wire::client { + + //* Outputs an rvalue that's the number of elements a pointer member points to. + {% macro member_length(member, accessor) -%} + {%- if member.length == "constant" -%} + {{member.constant_length}} + {%- else -%} + {{accessor}}{{as_varName(member.length.name)}} + {%- endif -%} + {%- endmacro %} + + //* Implementation of the client API functions. + {% for type in by_category["object"] %} + {% set Type = type.name.CamelCase() %} + {% set cType = as_cType(type.name) %} + + {% for method in type.methods %} + {% set Suffix = as_MethodSuffix(type.name, method.name) %} + + {% if Suffix in client_handwritten_commands %} + static + {% endif %} + {{as_cType(method.return_type.name)}} Client{{Suffix}}( + {{-cType}} cSelf + {%- for arg in method.arguments -%} + , {{as_annotated_cType(arg)}} + {%- endfor -%} + ) { + auto self = reinterpret_cast<{{as_wireType(type)}}>(cSelf); + {% if Suffix not in client_handwritten_commands %} + {{Suffix}}Cmd cmd; + + //* Create the structure going on the wire on the stack and fill it with the value + //* arguments so it can compute its size. + cmd.self = cSelf; + + //* For object creation, store the object ID the client will use for the result. + {% if method.return_type.category == "object" %} + auto* allocation = self->client->{{method.return_type.name.CamelCase()}}Allocator().New(self->client); + cmd.result = ObjectHandle{allocation->object->id, allocation->generation}; + {% endif %} + + {% for arg in method.arguments %} + //* Commands with mutable pointers should not be autogenerated. + {{assert(arg.annotation != "*")}} + cmd.{{as_varName(arg.name)}} = {{as_varName(arg.name)}}; + {% endfor %} + + //* Allocate space to send the command and copy the value args over. + self->client->SerializeCommand(cmd); + + {% if method.return_type.category == "object" %} + return reinterpret_cast<{{as_cType(method.return_type.name)}}>(allocation->object.get()); + {% endif %} + {% else %} + return self->{{method.name.CamelCase()}}( + {%- for arg in method.arguments -%} + {%if not loop.first %}, {% endif %} {{as_varName(arg.name)}} + {%- endfor -%}); + {% endif %} + } + {% endfor %} + + //* When an object's refcount reaches 0, notify the server side of it and delete it. + void Client{{as_MethodSuffix(type.name, Name("release"))}}({{cType}} cObj) { + {{Type}}* obj = reinterpret_cast<{{Type}}*>(cObj); + obj->refcount --; + + if (obj->refcount > 0) { + return; + } + + DestroyObjectCmd cmd; + cmd.objectType = ObjectType::{{type.name.CamelCase()}}; + cmd.objectId = obj->id; + + obj->client->SerializeCommand(cmd); + obj->client->{{type.name.CamelCase()}}Allocator().Free(obj); + } + + void Client{{as_MethodSuffix(type.name, Name("reference"))}}({{cType}} cObj) { + {{Type}}* obj = reinterpret_cast<{{Type}}*>(cObj); + obj->refcount ++; + } + {% endfor %} + + namespace { + WGPUInstance ClientCreateInstance(WGPUInstanceDescriptor const* descriptor) { + UNREACHABLE(); + return nullptr; + } + + struct ProcEntry { + WGPUProc proc; + const char* name; + }; + static const ProcEntry sProcMap[] = { + {% for (type, method) in c_methods_sorted_by_name %} + { reinterpret_cast<WGPUProc>(Client{{as_MethodSuffix(type.name, method.name)}}), "{{as_cMethod(type.name, method.name)}}" }, + {% endfor %} + }; + static constexpr size_t sProcMapSize = sizeof(sProcMap) / sizeof(sProcMap[0]); + } // anonymous namespace + + WGPUProc ClientGetProcAddress(WGPUDevice, const char* procName) { + if (procName == nullptr) { + return nullptr; + } + + const ProcEntry* entry = std::lower_bound(&sProcMap[0], &sProcMap[sProcMapSize], procName, + [](const ProcEntry &a, const char *b) -> bool { + return strcmp(a.name, b) < 0; + } + ); + + if (entry != &sProcMap[sProcMapSize] && strcmp(entry->name, procName) == 0) { + return entry->proc; + } + + // Special case the two free-standing functions of the API. + if (strcmp(procName, "wgpuGetProcAddress") == 0) { + return reinterpret_cast<WGPUProc>(ClientGetProcAddress); + } + + if (strcmp(procName, "wgpuCreateInstance") == 0) { + return reinterpret_cast<WGPUProc>(ClientCreateInstance); + } + + return nullptr; + } + + std::vector<const char*> GetProcMapNamesForTesting() { + std::vector<const char*> result; + result.reserve(sProcMapSize); + for (const ProcEntry& entry : sProcMap) { + result.push_back(entry.name); + } + return result; + } + + {% set Prefix = metadata.proc_table_prefix %} + static {{Prefix}}ProcTable gProcTable = { + {% for function in by_category["function"] %} + Client{{as_cppType(function.name)}}, + {% endfor %} + {% for type in by_category["object"] %} + {% for method in c_methods(type) %} + Client{{as_MethodSuffix(type.name, method.name)}}, + {% endfor %} + {% endfor %} + }; + const {{Prefix}}ProcTable& GetProcs() { + return gProcTable; + } +} // namespace dawn::wire::client
diff --git a/generator/templates/dawn/wire/client/ClientBase.h b/generator/templates/dawn/wire/client/ClientBase.h new file mode 100644 index 0000000..0f9cbfe --- /dev/null +++ b/generator/templates/dawn/wire/client/ClientBase.h
@@ -0,0 +1,74 @@ +//* Copyright 2019 The Dawn Authors +//* +//* Licensed under the Apache License, Version 2.0 (the "License"); +//* you may not use this file except in compliance with the License. +//* You may obtain a copy of the License at +//* +//* http://www.apache.org/licenses/LICENSE-2.0 +//* +//* Unless required by applicable law or agreed to in writing, software +//* distributed under the License is distributed on an "AS IS" BASIS, +//* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +//* See the License for the specific language governing permissions and +//* limitations under the License. + +#ifndef DAWNWIRE_CLIENT_CLIENTBASE_AUTOGEN_H_ +#define DAWNWIRE_CLIENT_CLIENTBASE_AUTOGEN_H_ + +#include "dawn/wire/ChunkedCommandHandler.h" +#include "dawn/wire/WireCmd_autogen.h" +#include "dawn/wire/client/ApiObjects.h" +#include "dawn/wire/client/ObjectAllocator.h" + +namespace dawn::wire::client { + + class ClientBase : public ChunkedCommandHandler, public ObjectIdProvider { + public: + ClientBase() = default; + virtual ~ClientBase() = default; + + {% for type in by_category["object"] %} + const ObjectAllocator<{{type.name.CamelCase()}}>& {{type.name.CamelCase()}}Allocator() const { + return m{{type.name.CamelCase()}}Allocator; + } + ObjectAllocator<{{type.name.CamelCase()}}>& {{type.name.CamelCase()}}Allocator() { + return m{{type.name.CamelCase()}}Allocator; + } + {% endfor %} + + void FreeObject(ObjectType objectType, ObjectBase* obj) { + switch (objectType) { + {% for type in by_category["object"] %} + case ObjectType::{{type.name.CamelCase()}}: + m{{type.name.CamelCase()}}Allocator.Free(static_cast<{{type.name.CamelCase()}}*>(obj)); + break; + {% endfor %} + } + } + + private: + // Implementation of the ObjectIdProvider interface + {% for type in by_category["object"] %} + WireResult GetId({{as_cType(type.name)}} object, ObjectId* out) const final { + ASSERT(out != nullptr); + if (object == nullptr) { + return WireResult::FatalError; + } + *out = reinterpret_cast<{{as_wireType(type)}}>(object)->id; + return WireResult::Success; + } + WireResult GetOptionalId({{as_cType(type.name)}} object, ObjectId* out) const final { + ASSERT(out != nullptr); + *out = (object == nullptr ? 0 : reinterpret_cast<{{as_wireType(type)}}>(object)->id); + return WireResult::Success; + } + {% endfor %} + + {% for type in by_category["object"] %} + ObjectAllocator<{{type.name.CamelCase()}}> m{{type.name.CamelCase()}}Allocator; + {% endfor %} + }; + +} // namespace dawn::wire::client + +#endif // DAWNWIRE_CLIENT_CLIENTBASE_AUTOGEN_H_
diff --git a/generator/templates/dawn/wire/client/ClientHandlers.cpp b/generator/templates/dawn/wire/client/ClientHandlers.cpp new file mode 100644 index 0000000..ace8475 --- /dev/null +++ b/generator/templates/dawn/wire/client/ClientHandlers.cpp
@@ -0,0 +1,97 @@ +//* Copyright 2019 The Dawn Authors +//* +//* Licensed under the Apache License, Version 2.0 (the "License"); +//* you may not use this file except in compliance with the License. +//* You may obtain a copy of the License at +//* +//* http://www.apache.org/licenses/LICENSE-2.0 +//* +//* Unless required by applicable law or agreed to in writing, software +//* distributed under the License is distributed on an "AS IS" BASIS, +//* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +//* See the License for the specific language governing permissions and +//* limitations under the License. + +#include "dawn/common/Assert.h" +#include "dawn/wire/client/Client.h" + +#include <string> + +namespace dawn::wire::client { + {% for command in cmd_records["return command"] %} + bool Client::Handle{{command.name.CamelCase()}}(DeserializeBuffer* deserializeBuffer) { + Return{{command.name.CamelCase()}}Cmd cmd; + WireResult deserializeResult = cmd.Deserialize(deserializeBuffer, &mAllocator); + + if (deserializeResult == WireResult::FatalError) { + return false; + } + + {% for member in command.members if member.handle_type %} + {% set Type = member.handle_type.name.CamelCase() %} + {% set name = as_varName(member.name) %} + + {% if member.type.dict_name == "ObjectHandle" %} + {{Type}}* {{name}} = {{Type}}Allocator().GetObject(cmd.{{name}}.id); + uint32_t {{name}}Generation = {{Type}}Allocator().GetGeneration(cmd.{{name}}.id); + if ({{name}}Generation != cmd.{{name}}.generation) { + {{name}} = nullptr; + } + {% endif %} + {% endfor %} + + return Do{{command.name.CamelCase()}}( + {%- for member in command.members -%} + {%- if member.handle_type -%} + {{as_varName(member.name)}} + {%- else -%} + cmd.{{as_varName(member.name)}} + {%- endif -%} + {%- if not loop.last -%}, {% endif %} + {%- endfor -%} + ); + } + {% endfor %} + + const volatile char* Client::HandleCommandsImpl(const volatile char* commands, size_t size) { + DeserializeBuffer deserializeBuffer(commands, size); + + while (deserializeBuffer.AvailableSize() >= sizeof(CmdHeader) + sizeof(ReturnWireCmd)) { + // Start by chunked command handling, if it is done, then it means the whole buffer + // was consumed by it, so we return a pointer to the end of the commands. + switch (HandleChunkedCommands(deserializeBuffer.Buffer(), deserializeBuffer.AvailableSize())) { + case ChunkedCommandsResult::Consumed: + return commands + size; + case ChunkedCommandsResult::Error: + return nullptr; + case ChunkedCommandsResult::Passthrough: + break; + } + + ReturnWireCmd cmdId = *static_cast<const volatile ReturnWireCmd*>(static_cast<const volatile void*>( + deserializeBuffer.Buffer() + sizeof(CmdHeader))); + bool success = false; + switch (cmdId) { + {% for command in cmd_records["return command"] %} + {% set Suffix = command.name.CamelCase() %} + case ReturnWireCmd::{{Suffix}}: + success = Handle{{Suffix}}(&deserializeBuffer); + break; + {% endfor %} + default: + success = false; + } + + if (!success) { + return nullptr; + } + mAllocator.Reset(); + } + + if (deserializeBuffer.AvailableSize() != 0) { + return nullptr; + } + + return commands; + } +} // namespace dawn::wire::client
diff --git a/generator/templates/dawn/wire/client/ClientPrototypes.inc b/generator/templates/dawn/wire/client/ClientPrototypes.inc new file mode 100644 index 0000000..3a5f62f --- /dev/null +++ b/generator/templates/dawn/wire/client/ClientPrototypes.inc
@@ -0,0 +1,32 @@ +//* Copyright 2019 The Dawn Authors +//* +//* Licensed under the Apache License, Version 2.0 (the "License"); +//* you may not use this file except in compliance with the License. +//* You may obtain a copy of the License at +//* +//* http://www.apache.org/licenses/LICENSE-2.0 +//* +//* Unless required by applicable law or agreed to in writing, software +//* distributed under the License is distributed on an "AS IS" BASIS, +//* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +//* See the License for the specific language governing permissions and +//* limitations under the License. + +//* Return command handlers +{% for command in cmd_records["return command"] %} + bool Handle{{command.name.CamelCase()}}(DeserializeBuffer* deserializeBuffer); +{% endfor %} + +//* Return command doers +{% for command in cmd_records["return command"] %} + bool Do{{command.name.CamelCase()}}( + {%- for member in command.members -%} + {%- if member.handle_type -%} + {{as_wireType(member.handle_type)}} {{as_varName(member.name)}} + {%- else -%} + {{as_annotated_wireType(member)}} + {%- endif -%} + {%- if not loop.last -%}, {% endif %} + {%- endfor -%} + ); +{% endfor %}
diff --git a/generator/templates/dawn/wire/server/ServerBase.h b/generator/templates/dawn/wire/server/ServerBase.h new file mode 100644 index 0000000..8fef34a --- /dev/null +++ b/generator/templates/dawn/wire/server/ServerBase.h
@@ -0,0 +1,105 @@ +//* Copyright 2019 The Dawn Authors +//* +//* Licensed under the Apache License, Version 2.0 (the "License"); +//* you may not use this file except in compliance with the License. +//* You may obtain a copy of the License at +//* +//* http://www.apache.org/licenses/LICENSE-2.0 +//* +//* Unless required by applicable law or agreed to in writing, software +//* distributed under the License is distributed on an "AS IS" BASIS, +//* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +//* See the License for the specific language governing permissions and +//* limitations under the License. + +#ifndef DAWNWIRE_SERVER_SERVERBASE_H_ +#define DAWNWIRE_SERVER_SERVERBASE_H_ + +#include "dawn/dawn_proc_table.h" +#include "dawn/wire/ChunkedCommandHandler.h" +#include "dawn/wire/Wire.h" +#include "dawn/wire/WireCmd_autogen.h" +#include "dawn/wire/WireDeserializeAllocator.h" +#include "dawn/wire/server/ObjectStorage.h" + +namespace dawn::wire::server { + + class ServerBase : public ChunkedCommandHandler, public ObjectIdResolver { + public: + ServerBase() = default; + virtual ~ServerBase() = default; + + protected: + void DestroyAllObjects(const DawnProcTable& procs) { + //* Free all objects when the server is destroyed + {% for type in by_category["object"] if type.name.get() != "device" %} + { + std::vector<{{as_cType(type.name)}}> handles = mKnown{{type.name.CamelCase()}}.AcquireAllHandles(); + for ({{as_cType(type.name)}} handle : handles) { + procs.{{as_varName(type.name, Name("release"))}}(handle); + } + } + {% endfor %} + //* Release devices last because dawn_native requires this. + { + std::vector<WGPUDevice> handles = mKnownDevice.AcquireAllHandles(); + for (WGPUDevice handle : handles) { + procs.deviceRelease(handle); + } + } + } + + {% for type in by_category["object"] %} + const KnownObjects<{{as_cType(type.name)}}>& {{type.name.CamelCase()}}Objects() const { + return mKnown{{type.name.CamelCase()}}; + } + KnownObjects<{{as_cType(type.name)}}>& {{type.name.CamelCase()}}Objects() { + return mKnown{{type.name.CamelCase()}}; + } + {% endfor %} + + {% for type in by_category["object"] if type.name.CamelCase() in server_reverse_lookup_objects %} + const ObjectIdLookupTable<{{as_cType(type.name)}}>& {{type.name.CamelCase()}}ObjectIdTable() const { + return m{{type.name.CamelCase()}}IdTable; + } + ObjectIdLookupTable<{{as_cType(type.name)}}>& {{type.name.CamelCase()}}ObjectIdTable() { + return m{{type.name.CamelCase()}}IdTable; + } + {% endfor %} + + private: + // Implementation of the ObjectIdResolver interface + {% for type in by_category["object"] %} + WireResult GetFromId(ObjectId id, {{as_cType(type.name)}}* out) const final { + auto data = mKnown{{type.name.CamelCase()}}.Get(id); + if (data == nullptr) { + return WireResult::FatalError; + } + + *out = data->handle; + return WireResult::Success; + } + + WireResult GetOptionalFromId(ObjectId id, {{as_cType(type.name)}}* out) const final { + if (id == 0) { + *out = nullptr; + return WireResult::Success; + } + + return GetFromId(id, out); + } + {% endfor %} + + //* The list of known IDs for each object type. + {% for type in by_category["object"] %} + KnownObjects<{{as_cType(type.name)}}> mKnown{{type.name.CamelCase()}}; + {% endfor %} + + {% for type in by_category["object"] if type.name.CamelCase() in server_reverse_lookup_objects %} + ObjectIdLookupTable<{{as_cType(type.name)}}> m{{type.name.CamelCase()}}IdTable; + {% endfor %} + }; + +} // namespace dawn::wire::server + +#endif // DAWNWIRE_SERVER_SERVERBASE_H_
diff --git a/generator/templates/dawn/wire/server/ServerDoers.cpp b/generator/templates/dawn/wire/server/ServerDoers.cpp new file mode 100644 index 0000000..9c6df80 --- /dev/null +++ b/generator/templates/dawn/wire/server/ServerDoers.cpp
@@ -0,0 +1,121 @@ +//* Copyright 2019 The Dawn Authors +//* +//* Licensed under the Apache License, Version 2.0 (the "License"); +//* you may not use this file except in compliance with the License. +//* You may obtain a copy of the License at +//* +//* http://www.apache.org/licenses/LICENSE-2.0 +//* +//* Unless required by applicable law or agreed to in writing, software +//* distributed under the License is distributed on an "AS IS" BASIS, +//* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +//* See the License for the specific language governing permissions and +//* limitations under the License. + +#include "dawn/common/Assert.h" +#include "dawn/wire/server/Server.h" + +namespace dawn::wire::server { + //* Implementation of the command doers + {% for command in cmd_records["command"] %} + {% set type = command.derived_object %} + {% set method = command.derived_method %} + {% set is_method = method is not none %} + + {% set Suffix = command.name.CamelCase() %} + {% if Suffix not in client_side_commands %} + {% if is_method and Suffix not in server_handwritten_commands %} + bool Server::Do{{Suffix}}( + {%- for member in command.members -%} + {%- if member.is_return_value -%} + {%- if member.handle_type -%} + {{as_cType(member.handle_type.name)}}* {{as_varName(member.name)}} + {%- else -%} + {{as_cType(member.type.name)}}* {{as_varName(member.name)}} + {%- endif -%} + {%- else -%} + {{as_annotated_cType(member)}} + {%- endif -%} + {%- if not loop.last -%}, {% endif %} + {%- endfor -%} + ) { + {% set ret = command.members|selectattr("is_return_value")|list %} + //* If there is a return value, assign it. + {% if ret|length == 1 %} + *{{as_varName(ret[0].name)}} = + {% else %} + //* Only one member should be a return value. + {{ assert(ret|length == 0) }} + {% endif %} + mProcs.{{as_varName(type.name, method.name)}}( + {%- for member in command.members if not member.is_return_value -%} + {{as_varName(member.name)}} + {%- if not loop.last -%}, {% endif %} + {%- endfor -%} + ); + {% if ret|length == 1 %} + //* WebGPU error handling guarantees that no null object can be returned by + //* object creation functions. + ASSERT(*{{as_varName(ret[0].name)}} != nullptr); + {% endif %} + return true; + } + {% endif %} + {% endif %} + {% endfor %} + + bool Server::DoDestroyObject(ObjectType objectType, ObjectId objectId) { + //* ID 0 are reserved for nullptr and cannot be destroyed. + if (objectId == 0) { + return false; + } + + switch(objectType) { + {% for type in by_category["object"] %} + case ObjectType::{{type.name.CamelCase()}}: { + auto* data = {{type.name.CamelCase()}}Objects().Get(objectId); + if (data == nullptr) { + return false; + } + if (data->deviceInfo != nullptr) { + if (!UntrackDeviceChild(data->deviceInfo, objectType, objectId)) { + return false; + } + } + if (data->state == AllocationState::Allocated) { + ASSERT(data->handle != nullptr); + {% if type.name.CamelCase() in server_reverse_lookup_objects %} + {{type.name.CamelCase()}}ObjectIdTable().Remove(data->handle); + {% endif %} + + {% if type.name.get() == "device" %} + //* TODO(crbug.com/dawn/384): This is a hack to make sure that all child objects + //* are destroyed before their device. We should have a solution in + //* Dawn native that makes all child objects internally null if their + //* Device is destroyed. + while (data->info->childObjectTypesAndIds.size() > 0) { + auto [childObjectType, childObjectId] = UnpackObjectTypeAndId( + *data->info->childObjectTypesAndIds.begin()); + if (!DoDestroyObject(childObjectType, childObjectId)) { + return false; + } + } + if (data->handle != nullptr) { + //* Deregisters uncaptured error and device lost callbacks since + //* they should not be forwarded if the device no longer exists on the wire. + ClearDeviceCallbacks(data->handle); + } + {% endif %} + + mProcs.{{as_varName(type.name, Name("release"))}}(data->handle); + } + {{type.name.CamelCase()}}Objects().Free(objectId); + return true; + } + {% endfor %} + default: + return false; + } + } + +} // namespace dawn::wire::server
diff --git a/generator/templates/dawn/wire/server/ServerHandlers.cpp b/generator/templates/dawn/wire/server/ServerHandlers.cpp new file mode 100644 index 0000000..5514a33 --- /dev/null +++ b/generator/templates/dawn/wire/server/ServerHandlers.cpp
@@ -0,0 +1,150 @@ +//* Copyright 2019 The Dawn Authors +//* +//* Licensed under the Apache License, Version 2.0 (the "License"); +//* you may not use this file except in compliance with the License. +//* You may obtain a copy of the License at +//* +//* http://www.apache.org/licenses/LICENSE-2.0 +//* +//* Unless required by applicable law or agreed to in writing, software +//* distributed under the License is distributed on an "AS IS" BASIS, +//* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +//* See the License for the specific language governing permissions and +//* limitations under the License. + +#include "dawn/common/Assert.h" +#include "dawn/wire/server/Server.h" + +namespace dawn::wire::server { + {% for command in cmd_records["command"] %} + {% set method = command.derived_method %} + {% set is_method = method != None %} + {% set returns = is_method and method.return_type.name.canonical_case() != "void" %} + + {% set Suffix = command.name.CamelCase() %} + //* The generic command handlers + bool Server::Handle{{Suffix}}(DeserializeBuffer* deserializeBuffer) { + {{Suffix}}Cmd cmd; + WireResult deserializeResult = cmd.Deserialize(deserializeBuffer, &mAllocator + {%- if command.may_have_dawn_object -%} + , *this + {%- endif -%} + ); + + if (deserializeResult == WireResult::FatalError) { + return false; + } + + {% if Suffix in server_custom_pre_handler_commands %} + if (!PreHandle{{Suffix}}(cmd)) { + return false; + } + {% endif %} + + //* Allocate any result objects + {%- for member in command.members if member.is_return_value -%} + {{ assert(member.handle_type) }} + {% set Type = member.handle_type.name.CamelCase() %} + {% set name = as_varName(member.name) %} + + auto* {{name}}Data = {{Type}}Objects().Allocate(cmd.{{name}}.id); + if ({{name}}Data == nullptr) { + return false; + } + {{name}}Data->generation = cmd.{{name}}.generation; + + //* TODO(crbug.com/dawn/384): This is a hack to make sure that all child objects + //* are destroyed before their device. The dawn_native device needs to track all child objects so + //* it can destroy them if the device is destroyed first. + {% if command.derived_object %} + {% set type = command.derived_object %} + {% if type.name.get() == "device" %} + {{name}}Data->deviceInfo = DeviceObjects().Get(cmd.selfId)->info.get(); + {% else %} + auto* selfData = {{type.name.CamelCase()}}Objects().Get(cmd.selfId); + {{name}}Data->deviceInfo = selfData->deviceInfo; + {% endif %} + if ({{name}}Data->deviceInfo != nullptr) { + if (!TrackDeviceChild({{name}}Data->deviceInfo, ObjectType::{{Type}}, cmd.{{name}}.id)) { + return false; + } + } + {% endif %} + {% endfor %} + + //* Do command + bool success = Do{{Suffix}}( + {%- for member in command.members -%} + {%- if member.is_return_value -%} + {%- if member.handle_type -%} + &{{as_varName(member.name)}}Data->handle //* Pass the handle of the output object to be written by the doer + {%- else -%} + &cmd.{{as_varName(member.name)}} + {%- endif -%} + {%- else -%} + cmd.{{as_varName(member.name)}} + {%- endif -%} + {%- if not loop.last -%}, {% endif %} + {%- endfor -%} + ); + + if (!success) { + return false; + } + + {%- for member in command.members if member.is_return_value and member.handle_type -%} + {% set Type = member.handle_type.name.CamelCase() %} + {% set name = as_varName(member.name) %} + + {% if Type in server_reverse_lookup_objects %} + //* For created objects, store a mapping from them back to their client IDs + {{Type}}ObjectIdTable().Store({{name}}Data->handle, cmd.{{name}}.id); + {% endif %} + {% endfor %} + + return true; + } + {% endfor %} + + const volatile char* Server::HandleCommandsImpl(const volatile char* commands, size_t size) { + DeserializeBuffer deserializeBuffer(commands, size); + + while (deserializeBuffer.AvailableSize() >= sizeof(CmdHeader) + sizeof(WireCmd)) { + // Start by chunked command handling, if it is done, then it means the whole buffer + // was consumed by it, so we return a pointer to the end of the commands. + switch (HandleChunkedCommands(deserializeBuffer.Buffer(), deserializeBuffer.AvailableSize())) { + case ChunkedCommandsResult::Consumed: + return commands + size; + case ChunkedCommandsResult::Error: + return nullptr; + case ChunkedCommandsResult::Passthrough: + break; + } + + WireCmd cmdId = *static_cast<const volatile WireCmd*>(static_cast<const volatile void*>( + deserializeBuffer.Buffer() + sizeof(CmdHeader))); + bool success = false; + switch (cmdId) { + {% for command in cmd_records["command"] %} + case WireCmd::{{command.name.CamelCase()}}: + success = Handle{{command.name.CamelCase()}}(&deserializeBuffer); + break; + {% endfor %} + default: + success = false; + } + + if (!success) { + return nullptr; + } + mAllocator.Reset(); + } + + if (deserializeBuffer.AvailableSize() != 0) { + return nullptr; + } + + return commands; + } + +} // namespace dawn::wire::server
diff --git a/generator/templates/dawn/wire/server/ServerPrototypes.inc b/generator/templates/dawn/wire/server/ServerPrototypes.inc new file mode 100644 index 0000000..31af0ed --- /dev/null +++ b/generator/templates/dawn/wire/server/ServerPrototypes.inc
@@ -0,0 +1,38 @@ +//* Copyright 2019 The Dawn Authors +//* +//* Licensed under the Apache License, Version 2.0 (the "License"); +//* you may not use this file except in compliance with the License. +//* You may obtain a copy of the License at +//* +//* http://www.apache.org/licenses/LICENSE-2.0 +//* +//* Unless required by applicable law or agreed to in writing, software +//* distributed under the License is distributed on an "AS IS" BASIS, +//* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +//* See the License for the specific language governing permissions and +//* limitations under the License. + +// Command handlers & doers +{% for command in cmd_records["command"] %} + {% set Suffix = command.name.CamelCase() %} + bool Handle{{Suffix}}(DeserializeBuffer* deserializeBuffer); + + bool Do{{Suffix}}( + {%- for member in command.members -%} + {%- if member.is_return_value -%} + {%- if member.handle_type -%} + {{as_cType(member.handle_type.name)}}* {{as_varName(member.name)}} + {%- else -%} + {{as_cType(member.type.name)}}* {{as_varName(member.name)}} + {%- endif -%} + {%- else -%} + {{as_annotated_cType(member)}} + {%- endif -%} + {%- if not loop.last -%}, {% endif %} + {%- endfor -%} + ); +{% endfor %} + +{% for CommandName in server_custom_pre_handler_commands %} + bool PreHandle{{CommandName}}(const {{CommandName}}Cmd& cmd); +{% endfor %}
diff --git a/generator/templates/dawn_proc.c b/generator/templates/dawn_proc.c new file mode 100644 index 0000000..68970c6 --- /dev/null +++ b/generator/templates/dawn_proc.c
@@ -0,0 +1,63 @@ +//* Copyright 2017 The Dawn Authors +//* +//* Licensed under the Apache License, Version 2.0 (the "License"); +//* you may not use this file except in compliance with the License. +//* You may obtain a copy of the License at +//* +//* http://www.apache.org/licenses/LICENSE-2.0 +//* +//* Unless required by applicable law or agreed to in writing, software +//* distributed under the License is distributed on an "AS IS" BASIS, +//* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +//* See the License for the specific language governing permissions and +//* limitations under the License. + +{% set Prefix = metadata.proc_table_prefix %} +{% set prefix = Prefix.lower() %} +#include "dawn/{{prefix}}_proc.h" + +static {{Prefix}}ProcTable procs; + +static {{Prefix}}ProcTable nullProcs; + +void {{prefix}}ProcSetProcs(const {{Prefix}}ProcTable* procs_) { + if (procs_) { + procs = *procs_; + } else { + procs = nullProcs; + } +} + +{% for function in by_category["function"] %} + {{as_cType(function.return_type.name)}} {{as_cMethod(None, function.name)}}( + {%- for arg in function.arguments -%} + {% if not loop.first %}, {% endif %}{{as_annotated_cType(arg)}} + {%- endfor -%} + ) { + {% if function.return_type.name.canonical_case() != "void" %}return {% endif %} + procs.{{as_varName(function.name)}}( + {%- for arg in function.arguments -%} + {% if not loop.first %}, {% endif %}{{as_varName(arg.name)}} + {%- endfor -%} + ); + } +{% endfor %} + +{% for type in by_category["object"] %} + {% for method in c_methods(type) %} + {{as_cType(method.return_type.name)}} {{as_cMethod(type.name, method.name)}}( + {{-as_cType(type.name)}} {{as_varName(type.name)}} + {%- for arg in method.arguments -%} + , {{as_annotated_cType(arg)}} + {%- endfor -%} + ) { + {% if method.return_type.name.canonical_case() != "void" %}return {% endif %} + procs.{{as_varName(type.name, method.name)}}({{as_varName(type.name)}} + {%- for arg in method.arguments -%} + , {{as_varName(arg.name)}} + {%- endfor -%} + ); + } + {% endfor %} + +{% endfor %}
diff --git a/generator/templates/dawn_proc_table.h b/generator/templates/dawn_proc_table.h new file mode 100644 index 0000000..16f3fc2 --- /dev/null +++ b/generator/templates/dawn_proc_table.h
@@ -0,0 +1,35 @@ +//* Copyright 2019 The Dawn Authors +//* +//* Licensed under the Apache License, Version 2.0 (the "License"); +//* you may not use this file except in compliance with the License. +//* You may obtain a copy of the License at +//* +//* http://www.apache.org/licenses/LICENSE-2.0 +//* +//* Unless required by applicable law or agreed to in writing, software +//* distributed under the License is distributed on an "AS IS" BASIS, +//* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +//* See the License for the specific language governing permissions and +//* limitations under the License. + +{% set Prefix = metadata.proc_table_prefix %} +#ifndef DAWN_{{Prefix.upper()}}_PROC_TABLE_H_ +#define DAWN_{{Prefix.upper()}}_PROC_TABLE_H_ + +#include "dawn/{{metadata.api.lower()}}.h" + +// Note: Often allocated as a static global. Do not add a complex constructor. +typedef struct {{Prefix}}ProcTable { + {% for function in by_category["function"] %} + {{as_cProc(None, function.name)}} {{as_varName(function.name)}}; + {% endfor %} + + {% for type in by_category["object"] %} + {% for method in c_methods(type) %} + {{as_cProc(type.name, method.name)}} {{as_varName(type.name, method.name)}}; + {% endfor %} + + {% endfor %} +} {{Prefix}}ProcTable; + +#endif // DAWN_{{Prefix.upper()}}_PROC_TABLE_H_
diff --git a/generator/templates/dawn_thread_dispatch_proc.cpp b/generator/templates/dawn_thread_dispatch_proc.cpp new file mode 100644 index 0000000..fc79464 --- /dev/null +++ b/generator/templates/dawn_thread_dispatch_proc.cpp
@@ -0,0 +1,62 @@ +{% set Prefix = metadata.proc_table_prefix %} +{% set prefix = Prefix.lower() %} +#include "dawn/{{prefix}}_thread_dispatch_proc.h" + +#include <thread> + +static {{Prefix}}ProcTable nullProcs; +thread_local {{Prefix}}ProcTable perThreadProcs; + +void {{prefix}}ProcSetPerThreadProcs(const {{Prefix}}ProcTable* procs) { + if (procs) { + perThreadProcs = *procs; + } else { + perThreadProcs = nullProcs; + } +} + +{% for function in by_category["function"] %} + static {{as_cType(function.return_type.name)}} ThreadDispatch{{as_cppType(function.name)}}( + {%- for arg in function.arguments -%} + {% if not loop.first %}, {% endif %}{{as_annotated_cType(arg)}} + {%- endfor -%} + ) { + {% if function.return_type.name.canonical_case() != "void" %}return {% endif %} + perThreadProcs.{{as_varName(function.name)}}( + {%- for arg in function.arguments -%} + {% if not loop.first %}, {% endif %}{{as_varName(arg.name)}} + {%- endfor -%} + ); + } +{% endfor %} + +{% for type in by_category["object"] %} + {% for method in c_methods(type) %} + static {{as_cType(method.return_type.name)}} ThreadDispatch{{as_MethodSuffix(type.name, method.name)}}( + {{-as_cType(type.name)}} {{as_varName(type.name)}} + {%- for arg in method.arguments -%} + , {{as_annotated_cType(arg)}} + {%- endfor -%} + ) { + {% if method.return_type.name.canonical_case() != "void" %}return {% endif %} + perThreadProcs.{{as_varName(type.name, method.name)}}({{as_varName(type.name)}} + {%- for arg in method.arguments -%} + , {{as_varName(arg.name)}} + {%- endfor -%} + ); + } + {% endfor %} +{% endfor %} + +extern "C" { + {{Prefix}}ProcTable {{prefix}}ThreadDispatchProcTable = { + {% for function in by_category["function"] %} + ThreadDispatch{{as_cppType(function.name)}}, + {% endfor %} + {% for type in by_category["object"] %} + {% for method in c_methods(type) %} + ThreadDispatch{{as_MethodSuffix(type.name, method.name)}}, + {% endfor %} + {% endfor %} + }; +}
diff --git a/generator/templates/library_api_enum_tables.js b/generator/templates/library_api_enum_tables.js new file mode 100644 index 0000000..2ec4eb6 --- /dev/null +++ b/generator/templates/library_api_enum_tables.js
@@ -0,0 +1,35 @@ +//* Copyright 2020 The Dawn Authors +//* +//* Licensed under the Apache License, Version 2.0 (the "License"); +//* you may not use this file except in compliance with the License. +//* You may obtain a copy of the License at +//* +//* http://www.apache.org/licenses/LICENSE-2.0 +//* +//* Unless required by applicable law or agreed to in writing, software +//* distributed under the License is distributed on an "AS IS" BASIS, +//* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +//* See the License for the specific language governing permissions and +//* limitations under the License. +//* +//* +//* This generator is used to produce the number-to-string mappings for +//* Emscripten's library_webgpu.js. +//* https://github.com/emscripten-core/emscripten/blob/master/src/library_webgpu.js +//* + {% for type in by_category["enum"] if not type.json_data.get("emscripten_no_enum_table") %} + {{type.name.CamelCase()}}: {% if type.contiguousFromZero -%} + [ + {% for value in type.values %} + {{as_jsEnumValue(value)}}, + {% endfor %} + ] + {%- else -%} + { + {% for value in type.values %} + {{value.value}}: {{as_jsEnumValue(value)}}, + {% endfor %} + } + {%- endif -%} + , + {% endfor %}
diff --git a/generator/templates/mock_api.cpp b/generator/templates/mock_api.cpp new file mode 100644 index 0000000..bf8f871 --- /dev/null +++ b/generator/templates/mock_api.cpp
@@ -0,0 +1,110 @@ +//* Copyright 2017 The Dawn Authors +//* +//* Licensed under the Apache License, Version 2.0 (the "License"); +//* you may not use this file except in compliance with the License. +//* You may obtain a copy of the License at +//* +//* http://www.apache.org/licenses/LICENSE-2.0 +//* +//* Unless required by applicable law or agreed to in writing, software +//* distributed under the License is distributed on an "AS IS" BASIS, +//* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +//* See the License for the specific language governing permissions and +//* limitations under the License. + +{% set api = metadata.api.lower() %} +#include "mock_{{api}}.h" + +using namespace testing; + +namespace { + {% for type in by_category["object"] %} + {% for method in c_methods(type) %} + {{as_cType(method.return_type.name)}} Forward{{as_MethodSuffix(type.name, method.name)}}( + {{-as_cType(type.name)}} self + {%- for arg in method.arguments -%} + , {{as_annotated_cType(arg)}} + {%- endfor -%} + ) { + auto object = reinterpret_cast<ProcTableAsClass::Object*>(self); + return object->procs->{{as_MethodSuffix(type.name, method.name)}}(self + {%- for arg in method.arguments -%} + , {{as_varName(arg.name)}} + {%- endfor -%} + ); + } + {% endfor %} + + {% endfor %} +} + +ProcTableAsClass::~ProcTableAsClass() { +} + +{% set Prefix = metadata.proc_table_prefix %} +void ProcTableAsClass::GetProcTable({{Prefix}}ProcTable* table) { + {% for type in by_category["object"] %} + {% for method in c_methods(type) %} + table->{{as_varName(type.name, method.name)}} = reinterpret_cast<{{as_cProc(type.name, method.name)}}>(Forward{{as_MethodSuffix(type.name, method.name)}}); + {% endfor %} + {% endfor %} +} + +{% for type in by_category["object"] %} + {% for method in type.methods if has_callback_arguments(method) %} + {% set Suffix = as_MethodSuffix(type.name, method.name) %} + + {{as_cType(method.return_type.name)}} ProcTableAsClass::{{Suffix}}( + {{-as_cType(type.name)}} {{as_varName(type.name)}} + {%- for arg in method.arguments -%} + , {{as_annotated_cType(arg)}} + {%- endfor -%} + ) { + ProcTableAsClass::Object* object = reinterpret_cast<ProcTableAsClass::Object*>({{as_varName(type.name)}}); + {% for callback_arg in method.arguments if callback_arg.type.category == 'function pointer' %} + object->m{{as_MethodSuffix(type.name, method.name)}}Callback = {{as_varName(callback_arg.name)}}; + {% endfor %} + object->userdata = userdata; + return On{{as_MethodSuffix(type.name, method.name)}}( + {{-as_varName(type.name)}} + {%- for arg in method.arguments -%} + , {{as_varName(arg.name)}} + {%- endfor -%} + ); + } + + {% for callback_arg in method.arguments if callback_arg.type.category == 'function pointer' %} + void ProcTableAsClass::Call{{Suffix}}Callback( + {{-as_cType(type.name)}} {{as_varName(type.name)}} + {%- for arg in callback_arg.type.arguments -%} + {%- if not loop.last -%}, {{as_annotated_cType(arg)}}{%- endif -%} + {%- endfor -%} + ) { + ProcTableAsClass::Object* object = reinterpret_cast<ProcTableAsClass::Object*>({{as_varName(type.name)}}); + object->m{{Suffix}}Callback( + {%- for arg in callback_arg.type.arguments -%} + {%- if not loop.last -%}{{as_varName(arg.name)}}, {% endif -%} + {%- endfor -%} + object->userdata); + } + {% endfor %} + {% endfor %} +{% endfor %} + +{% for type in by_category["object"] %} + {{as_cType(type.name)}} ProcTableAsClass::GetNew{{type.name.CamelCase()}}() { + mObjects.emplace_back(new Object); + mObjects.back()->procs = this; + return reinterpret_cast<{{as_cType(type.name)}}>(mObjects.back().get()); + } +{% endfor %} + +MockProcTable::MockProcTable() = default; + +MockProcTable::~MockProcTable() = default; + +void MockProcTable::IgnoreAllReleaseCalls() { + {% for type in by_category["object"] %} + EXPECT_CALL(*this, {{as_MethodSuffix(type.name, Name("release"))}}(_)).Times(AnyNumber()); + {% endfor %} +}
diff --git a/generator/templates/mock_api.h b/generator/templates/mock_api.h new file mode 100644 index 0000000..1c0a880 --- /dev/null +++ b/generator/templates/mock_api.h
@@ -0,0 +1,138 @@ +//* Copyright 2017 The Dawn Authors +//* +//* Licensed under the Apache License, Version 2.0 (the "License"); +//* you may not use this file except in compliance with the License. +//* You may obtain a copy of the License at +//* +//* http://www.apache.org/licenses/LICENSE-2.0 +//* +//* Unless required by applicable law or agreed to in writing, software +//* distributed under the License is distributed on an "AS IS" BASIS, +//* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +//* See the License for the specific language governing permissions and +//* limitations under the License. + +{% set API = metadata.api.upper() %} +{% set api = API.lower() %} +#ifndef MOCK_{{API}}_H +#define MOCK_{{API}}_H + +{% set Prefix = metadata.proc_table_prefix %} +{% set prefix = Prefix.lower() %} +#include <dawn/{{prefix}}_proc_table.h> +#include <dawn/{{api}}.h> +#include <gmock/gmock.h> + +#include <memory> + +// An abstract base class representing a proc table so that API calls can be mocked. Most API calls +// are directly represented by a delete virtual method but others need minimal state tracking to be +// useful as mocks. +class ProcTableAsClass { + public: + virtual ~ProcTableAsClass(); + + void GetProcTable({{Prefix}}ProcTable* table); + + // Creates an object that can be returned by a mocked call as in WillOnce(Return(foo)). + // It returns an object of the write type that isn't equal to any previously returned object. + // Otherwise some mock expectation could be triggered by two different objects having the same + // value. + {% for type in by_category["object"] %} + {{as_cType(type.name)}} GetNew{{type.name.CamelCase()}}(); + {% endfor %} + + {% for type in by_category["object"] %} + {% for method in type.methods if not has_callback_arguments(method) %} + virtual {{as_cType(method.return_type.name)}} {{as_MethodSuffix(type.name, method.name)}}( + {{-as_cType(type.name)}} {{as_varName(type.name)}} + {%- for arg in method.arguments -%} + , {{as_annotated_cType(arg)}} + {%- endfor -%} + ) = 0; + {% endfor %} + + virtual void {{as_MethodSuffix(type.name, Name("reference"))}}({{as_cType(type.name)}} self) = 0; + virtual void {{as_MethodSuffix(type.name, Name("release"))}}({{as_cType(type.name)}} self) = 0; + + {% for method in type.methods if has_callback_arguments(method) %} + {% set Suffix = as_MethodSuffix(type.name, method.name) %} + //* Stores callback and userdata and calls the On* method. + {{as_cType(method.return_type.name)}} {{Suffix}}( + {{-as_cType(type.name)}} {{as_varName(type.name)}} + {%- for arg in method.arguments -%} + , {{as_annotated_cType(arg)}} + {%- endfor -%} + ); + //* The virtual function to call after saving the callback and userdata in the proc. + //* This function can be mocked. + virtual {{as_cType(method.return_type.name)}} On{{Suffix}}( + {{-as_cType(type.name)}} {{as_varName(type.name)}} + {%- for arg in method.arguments -%} + , {{as_annotated_cType(arg)}} + {%- endfor -%} + ) = 0; + + //* Calls the stored callback. + {% for callback_arg in method.arguments if callback_arg.type.category == 'function pointer' %} + void Call{{as_MethodSuffix(type.name, method.name)}}Callback( + {{-as_cType(type.name)}} {{as_varName(type.name)}} + {%- for arg in callback_arg.type.arguments -%} + {%- if not loop.last -%}, {{as_annotated_cType(arg)}}{%- endif -%} + {%- endfor -%} + ); + {% endfor %} + {% endfor %} + {% endfor %} + + struct Object { + ProcTableAsClass* procs = nullptr; + {% for type in by_category["object"] %} + {% for method in type.methods if has_callback_arguments(method) %} + {% for callback_arg in method.arguments if callback_arg.type.category == 'function pointer' %} + {{as_cType(callback_arg.type.name)}} m{{as_MethodSuffix(type.name, method.name)}}Callback = nullptr; + {% endfor %} + {% endfor %} + {% endfor %} + void* userdata = 0; + }; + + private: + // Remembers the values returned by GetNew* so they can be freed. + std::vector<std::unique_ptr<Object>> mObjects; +}; + +class MockProcTable : public ProcTableAsClass { + public: + MockProcTable(); + ~MockProcTable() override; + + void IgnoreAllReleaseCalls(); + + {% for type in by_category["object"] %} + {% for method in type.methods if not has_callback_arguments(method) %} + MOCK_METHOD({{as_cType(method.return_type.name)}},{{" "}} + {{-as_MethodSuffix(type.name, method.name)}}, ( + {{-as_cType(type.name)}} {{as_varName(type.name)}} + {%- for arg in method.arguments -%} + , {{as_annotated_cType(arg)}} + {%- endfor -%} + ), (override)); + {% endfor %} + + MOCK_METHOD(void, {{as_MethodSuffix(type.name, Name("reference"))}}, ({{as_cType(type.name)}} self), (override)); + MOCK_METHOD(void, {{as_MethodSuffix(type.name, Name("release"))}}, ({{as_cType(type.name)}} self), (override)); + + {% for method in type.methods if has_callback_arguments(method) %} + MOCK_METHOD({{as_cType(method.return_type.name)}},{{" "-}} + On{{as_MethodSuffix(type.name, method.name)}}, ( + {{-as_cType(type.name)}} {{as_varName(type.name)}} + {%- for arg in method.arguments -%} + , {{as_annotated_cType(arg)}} + {%- endfor -%} + ), (override)); + {% endfor %} + {% endfor %} +}; + +#endif // MOCK_{{API}}_H
diff --git a/generator/templates/opengl/OpenGLFunctionsBase.cpp b/generator/templates/opengl/OpenGLFunctionsBase.cpp new file mode 100644 index 0000000..acddd4f --- /dev/null +++ b/generator/templates/opengl/OpenGLFunctionsBase.cpp
@@ -0,0 +1,70 @@ +//* Copyright 2019 The Dawn Authors +//* +//* Licensed under the Apache License, Version 2.0 (the "License"); +//* you may not use this file except in compliance with the License. +//* You may obtain a copy of the License at +//* +//* http://www.apache.org/licenses/LICENSE-2.0 +//* +//* Unless required by applicable law or agreed to in writing, software +//* distributed under the License is distributed on an "AS IS" BASIS, +//* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +//* See the License for the specific language governing permissions and +//* limitations under the License. + +#include "dawn/native/opengl/OpenGLFunctionsBase_autogen.h" + +namespace dawn::native::opengl { + +template<typename T> +MaybeError OpenGLFunctionsBase::LoadProc(GetProcAddress getProc, T* memberProc, const char* name) { + *memberProc = reinterpret_cast<T>(getProc(name)); + if (DAWN_UNLIKELY(memberProc == nullptr)) { + return DAWN_INTERNAL_ERROR(std::string("Couldn't load GL proc: ") + name); + } + return {}; +} + +MaybeError OpenGLFunctionsBase::LoadOpenGLESProcs(GetProcAddress getProc, int majorVersion, int minorVersion) { + {% for block in gles_blocks %} + // OpenGL ES {{block.version.major}}.{{block.version.minor}} + if (majorVersion > {{block.version.major}} || (majorVersion == {{block.version.major}} && minorVersion >= {{block.version.minor}})) { + {% for proc in block.procs %} + DAWN_TRY(LoadProc(getProc, &{{proc.ProcName()}}, "{{proc.glProcName()}}")); + {% endfor %} + } + + {% endfor %} + + {% for block in extension_gles_blocks %} + // {{block.extension}} + {% for proc in block.procs %} + DAWN_TRY(LoadProc(getProc, &{{proc.ProcName()}}, "{{proc.glProcName()}}")); + {% endfor %} + {% endfor %} + + return {}; +} + +MaybeError OpenGLFunctionsBase::LoadDesktopGLProcs(GetProcAddress getProc, int majorVersion, int minorVersion) { + {% for block in desktop_gl_blocks %} + // Desktop OpenGL {{block.version.major}}.{{block.version.minor}} + if (majorVersion > {{block.version.major}} || (majorVersion == {{block.version.major}} && minorVersion >= {{block.version.minor}})) { + {% for proc in block.procs %} + DAWN_TRY(LoadProc(getProc, &{{proc.ProcName()}}, "{{proc.glProcName()}}")); + {% endfor %} + } + + {% endfor %} + + {% for block in extension_desktop_gl_blocks %} + // {{block.extension}} + {% for proc in block.procs %} + DAWN_TRY(LoadProc(getProc, &{{proc.ProcName()}}, "{{proc.glProcName()}}")); + {% endfor %} + {% endfor %} + + return {}; +} + +} // namespace dawn::native::opengl
diff --git a/generator/templates/opengl/OpenGLFunctionsBase.h b/generator/templates/opengl/OpenGLFunctionsBase.h new file mode 100644 index 0000000..ac313c4 --- /dev/null +++ b/generator/templates/opengl/OpenGLFunctionsBase.h
@@ -0,0 +1,45 @@ +//* Copyright 2019 The Dawn Authors +//* +//* Licensed under the Apache License, Version 2.0 (the "License"); +//* you may not use this file except in compliance with the License. +//* You may obtain a copy of the License at +//* +//* http://www.apache.org/licenses/LICENSE-2.0 +//* +//* Unless required by applicable law or agreed to in writing, software +//* distributed under the License is distributed on an "AS IS" BASIS, +//* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +//* See the License for the specific language governing permissions and +//* limitations under the License. + +#ifndef DAWNNATIVE_OPENGL_OPENGLFUNCTIONSBASE_H_ +#define DAWNNATIVE_OPENGL_OPENGLFUNCTIONSBASE_H_ + +#include "dawn/native/Error.h" +#include "dawn/native/opengl/opengl_platform.h" + +namespace dawn::native::opengl { + using GetProcAddress = void* (*) (const char*); + + struct OpenGLFunctionsBase { + public: + {% for block in header_blocks %} + // {{block.description}} + {% for proc in block.procs %} + {{proc.PFNGLPROCNAME()}} {{proc.ProcName()}} = nullptr; + {% endfor %} + + {% endfor%} + + protected: + MaybeError LoadDesktopGLProcs(GetProcAddress getProc, int majorVersion, int minorVersion); + MaybeError LoadOpenGLESProcs(GetProcAddress getProc, int majorVersion, int minorVersion); + + private: + template<typename T> + MaybeError LoadProc(GetProcAddress getProc, T* memberProc, const char* name); + }; + +} // namespace dawn::native::opengl + +#endif // DAWNNATIVE_OPENGL_OPENGLFUNCTIONSBASE_H_
diff --git a/generator/templates/opengl/opengl_platform.h b/generator/templates/opengl/opengl_platform.h new file mode 100644 index 0000000..c2063b7 --- /dev/null +++ b/generator/templates/opengl/opengl_platform.h
@@ -0,0 +1,73 @@ +//* Copyright 2019 The Dawn Authors +//* +//* Licensed under the Apache License, Version 2.0 (the "License"); +//* you may not use this file except in compliance with the License. +//* You may obtain a copy of the License at +//* +//* http://www.apache.org/licenses/LICENSE-2.0 +//* +//* Unless required by applicable law or agreed to in writing, software +//* distributed under the License is distributed on an "AS IS" BASIS, +//* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +//* See the License for the specific language governing permissions and +//* limitations under the License. + +#include <KHR/khrplatform.h> + +using GLvoid = void; +using GLchar = char; +using GLenum = unsigned int; +using GLboolean = unsigned char; +using GLbitfield = unsigned int; +using GLbyte = khronos_int8_t; +using GLshort = short; +using GLint = int; +using GLsizei = int; +using GLubyte = khronos_uint8_t; +using GLushort = unsigned short; +using GLuint = unsigned int; +using GLfloat = khronos_float_t; +using GLclampf = khronos_float_t; +using GLdouble = double; +using GLclampd = double; +using GLfixed = khronos_int32_t; +using GLintptr = khronos_intptr_t; +using GLsizeiptr = khronos_ssize_t; +using GLhalf = unsigned short; +using GLint64 = khronos_int64_t; +using GLuint64 = khronos_uint64_t; +using GLsync = struct __GLsync*; +using GLeglImageOES = void*; +using GLDEBUGPROC = void(KHRONOS_APIENTRY*)(GLenum source, + GLenum type, + GLuint id, + GLenum severity, + GLsizei length, + const GLchar* message, + const void* userParam); +using GLDEBUGPROCARB = GLDEBUGPROC; +using GLDEBUGPROCKHR = GLDEBUGPROC; +using GLDEBUGPROCAMD = void(KHRONOS_APIENTRY*)(GLuint id, + GLenum category, + GLenum severity, + GLsizei length, + const GLchar* message, + void* userParam); + +{% for block in header_blocks %} + // {{block.description}} + {% for enum in block.enums %} + #define {{enum.name}} {{enum.value}} + {% endfor %} + + {% for proc in block.procs %} + using {{proc.PFNGLPROCNAME()}} = {{proc.return_type}}(KHRONOS_APIENTRY *)( + {%- for param in proc.params -%} + {%- if not loop.first %}, {% endif -%} + {{param.type}} {{param.name}} + {%- endfor -%} + ); + {% endfor %} + +{% endfor%} +#undef DAWN_GL_APIENTRY
diff --git a/include/dawn/BUILD.gn b/include/dawn/BUILD.gn new file mode 100644 index 0000000..d493820 --- /dev/null +++ b/include/dawn/BUILD.gn
@@ -0,0 +1,84 @@ +# Copyright 2019 The Dawn Authors +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +import("../../scripts/dawn_overrides_with_defaults.gni") + +import("${dawn_root}/generator/dawn_generator.gni") +import("${dawn_root}/scripts/dawn_component.gni") + +############################################################################### +# Dawn headers +############################################################################### + +dawn_json_generator("headers_gen") { + target = "headers" + outputs = [ + "include/dawn/dawn_proc_table.h", + "include/dawn/webgpu.h", + ] +} + +source_set("headers") { + all_dependent_configs = [ ":public" ] + public_deps = [ ":headers_gen" ] + + sources = get_target_outputs(":headers_gen") + sources += [ "${dawn_root}/include/dawn/dawn_wsi.h" ] +} + +############################################################################### +# Dawn C++ headers +############################################################################### + +dawn_json_generator("cpp_headers_gen") { + target = "cpp_headers" + outputs = [ + "include/dawn/webgpu_cpp.h", + "include/dawn/webgpu_cpp_print.h", + ] +} + +source_set("cpp_headers") { + public_deps = [ + ":cpp_headers_gen", + ":headers", + ] + + sources = get_target_outputs(":cpp_headers_gen") + sources += [ "${dawn_root}/include/dawn/EnumClassBitmasks.h" ] +} + +############################################################################### +# Dawn public include directories +############################################################################### + +config("public") { + include_dirs = [ + "${target_gen_dir}/../../include", + "${dawn_root}/include", + + "${dawn_root}/src/include", # TODO(crbug.com/dawn/1275) - remove + ] +} + +################################################################################ +# Build target aliases +# TODO(crbug.com/dawn/1275) - remove these +################################################################################ +group("dawncpp_headers") { + public_deps = [ ":cpp_headers" ] +} +group("dawn_headers") { + public_deps = [ ":headers" ] +}
diff --git a/include/dawn/EnumClassBitmasks.h b/include/dawn/EnumClassBitmasks.h new file mode 100644 index 0000000..3947f00 --- /dev/null +++ b/include/dawn/EnumClassBitmasks.h
@@ -0,0 +1,156 @@ +// Copyright 2017 The Dawn Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#ifndef DAWN_ENUM_CLASS_BITMASKS_H_ +#define DAWN_ENUM_CLASS_BITMASKS_H_ + +#include <type_traits> + +// The operators in dawn:: namespace need be introduced into other namespaces with +// using-declarations for C++ Argument Dependent Lookup to work. +#define DAWN_IMPORT_BITMASK_OPERATORS \ + using dawn::operator|; \ + using dawn::operator&; \ + using dawn::operator^; \ + using dawn::operator~; \ + using dawn::operator&=; \ + using dawn::operator|=; \ + using dawn::operator^=; \ + using dawn::HasZeroOrOneBits; + +namespace dawn { + + template <typename T> + struct IsDawnBitmask { + static constexpr bool enable = false; + }; + + template <typename T, typename Enable = void> + struct LowerBitmask { + static constexpr bool enable = false; + }; + + template <typename T> + struct LowerBitmask<T, typename std::enable_if<IsDawnBitmask<T>::enable>::type> { + static constexpr bool enable = true; + using type = T; + constexpr static T Lower(T t) { + return t; + } + }; + + template <typename T> + struct BoolConvertible { + using Integral = typename std::underlying_type<T>::type; + + constexpr BoolConvertible(Integral value) : value(value) { + } + constexpr operator bool() const { + return value != 0; + } + constexpr operator T() const { + return static_cast<T>(value); + } + + Integral value; + }; + + template <typename T> + struct LowerBitmask<BoolConvertible<T>> { + static constexpr bool enable = true; + using type = T; + static constexpr type Lower(BoolConvertible<T> t) { + return t; + } + }; + + template <typename T1, + typename T2, + typename = typename std::enable_if<LowerBitmask<T1>::enable && + LowerBitmask<T2>::enable>::type> + constexpr BoolConvertible<typename LowerBitmask<T1>::type> operator|(T1 left, T2 right) { + using T = typename LowerBitmask<T1>::type; + using Integral = typename std::underlying_type<T>::type; + return static_cast<Integral>(LowerBitmask<T1>::Lower(left)) | + static_cast<Integral>(LowerBitmask<T2>::Lower(right)); + } + + template <typename T1, + typename T2, + typename = typename std::enable_if<LowerBitmask<T1>::enable && + LowerBitmask<T2>::enable>::type> + constexpr BoolConvertible<typename LowerBitmask<T1>::type> operator&(T1 left, T2 right) { + using T = typename LowerBitmask<T1>::type; + using Integral = typename std::underlying_type<T>::type; + return static_cast<Integral>(LowerBitmask<T1>::Lower(left)) & + static_cast<Integral>(LowerBitmask<T2>::Lower(right)); + } + + template <typename T1, + typename T2, + typename = typename std::enable_if<LowerBitmask<T1>::enable && + LowerBitmask<T2>::enable>::type> + constexpr BoolConvertible<typename LowerBitmask<T1>::type> operator^(T1 left, T2 right) { + using T = typename LowerBitmask<T1>::type; + using Integral = typename std::underlying_type<T>::type; + return static_cast<Integral>(LowerBitmask<T1>::Lower(left)) ^ + static_cast<Integral>(LowerBitmask<T2>::Lower(right)); + } + + template <typename T1> + constexpr BoolConvertible<typename LowerBitmask<T1>::type> operator~(T1 t) { + using T = typename LowerBitmask<T1>::type; + using Integral = typename std::underlying_type<T>::type; + return ~static_cast<Integral>(LowerBitmask<T1>::Lower(t)); + } + + template <typename T, + typename T2, + typename = typename std::enable_if<IsDawnBitmask<T>::enable && + LowerBitmask<T2>::enable>::type> + constexpr T& operator&=(T& l, T2 right) { + T r = LowerBitmask<T2>::Lower(right); + l = l & r; + return l; + } + + template <typename T, + typename T2, + typename = typename std::enable_if<IsDawnBitmask<T>::enable && + LowerBitmask<T2>::enable>::type> + constexpr T& operator|=(T& l, T2 right) { + T r = LowerBitmask<T2>::Lower(right); + l = l | r; + return l; + } + + template <typename T, + typename T2, + typename = typename std::enable_if<IsDawnBitmask<T>::enable && + LowerBitmask<T2>::enable>::type> + constexpr T& operator^=(T& l, T2 right) { + T r = LowerBitmask<T2>::Lower(right); + l = l ^ r; + return l; + } + + template <typename T> + constexpr bool HasZeroOrOneBits(T value) { + using Integral = typename std::underlying_type<T>::type; + return (static_cast<Integral>(value) & (static_cast<Integral>(value) - 1)) == 0; + } + +} // namespace dawn + +#endif // DAWN_ENUM_CLASS_BITMASKS_H_
diff --git a/include/dawn/dawn_proc.h b/include/dawn/dawn_proc.h new file mode 100644 index 0000000..adeec46 --- /dev/null +++ b/include/dawn/dawn_proc.h
@@ -0,0 +1,36 @@ +// Copyright 2019 The Dawn Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#ifndef DAWN_DAWN_PROC_H_ +#define DAWN_DAWN_PROC_H_ + +#include "dawn/dawn_proc_table.h" +#include "dawn/webgpu.h" + +#ifdef __cplusplus +extern "C" { +#endif + +// Sets the static proctable used by libdawn_proc to implement the Dawn entrypoints. Passing NULL +// for `procs` sets up the null proctable that contains only null function pointers. It is the +// default value of the proctable. Setting the proctable back to null is good practice when you +// are done using libdawn_proc since further usage will cause a segfault instead of calling an +// unexpected function. +WGPU_EXPORT void dawnProcSetProcs(const DawnProcTable* procs); + +#ifdef __cplusplus +} // extern "C" +#endif + +#endif // DAWN_DAWN_PROC_H_
diff --git a/include/dawn/dawn_thread_dispatch_proc.h b/include/dawn/dawn_thread_dispatch_proc.h new file mode 100644 index 0000000..4d08ba8 --- /dev/null +++ b/include/dawn/dawn_thread_dispatch_proc.h
@@ -0,0 +1,33 @@ +// Copyright 2020 The Dawn Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#ifndef DAWN_DAWN_THREAD_DISPATCH_PROC_H_ +#define DAWN_DAWN_THREAD_DISPATCH_PROC_H_ + +#include "dawn/dawn_proc.h" + +#ifdef __cplusplus +extern "C" { +#endif + +// Call dawnProcSetProcs(&dawnThreadDispatchProcTable) and then use dawnProcSetPerThreadProcs +// to set per-thread procs. +WGPU_EXPORT extern DawnProcTable dawnThreadDispatchProcTable; +WGPU_EXPORT void dawnProcSetPerThreadProcs(const DawnProcTable* procs); + +#ifdef __cplusplus +} // extern "C" +#endif + +#endif // DAWN_DAWN_THREAD_DISPATCH_PROC_H_
diff --git a/include/dawn/dawn_wsi.h b/include/dawn/dawn_wsi.h new file mode 100644 index 0000000..f1a6047 --- /dev/null +++ b/include/dawn/dawn_wsi.h
@@ -0,0 +1,86 @@ +// Copyright 2017 The Dawn Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#ifndef DAWN_DAWN_WSI_H_ +#define DAWN_DAWN_WSI_H_ + +#include <dawn/webgpu.h> + +// Error message (or nullptr if there was no error) +typedef const char* DawnSwapChainError; +constexpr DawnSwapChainError DAWN_SWAP_CHAIN_NO_ERROR = nullptr; + +typedef struct { + /// Backend-specific texture id/name/pointer + union { + void* ptr; + uint64_t u64; + uint32_t u32; + } texture; +} DawnSwapChainNextTexture; + +typedef struct { + /// Initialize the swap chain implementation. + /// (*wsiContext) is one of DawnWSIContext{D3D12,Metal,GL} + void (*Init)(void* userData, void* wsiContext); + + /// Destroy the swap chain implementation. + void (*Destroy)(void* userData); + + /// Configure/reconfigure the swap chain. + DawnSwapChainError (*Configure)(void* userData, + WGPUTextureFormat format, + WGPUTextureUsage allowedUsage, + uint32_t width, + uint32_t height); + + /// Acquire the next texture from the swap chain. + DawnSwapChainError (*GetNextTexture)(void* userData, DawnSwapChainNextTexture* nextTexture); + + /// Present the last acquired texture to the screen. + DawnSwapChainError (*Present)(void* userData); + + /// Each function is called with userData as its first argument. + void* userData; + + /// For use by the D3D12 and Vulkan backends: how the swapchain will use the texture. + WGPUTextureUsage textureUsage; +} DawnSwapChainImplementation; + +#if defined(DAWN_ENABLE_BACKEND_D3D12) && defined(__cplusplus) +struct DawnWSIContextD3D12 { + WGPUDevice device = nullptr; +}; +#endif + +#if defined(DAWN_ENABLE_BACKEND_METAL) && defined(__OBJC__) +# import <Metal/Metal.h> + +struct DawnWSIContextMetal { + id<MTLDevice> device = nil; + id<MTLCommandQueue> queue = nil; +}; +#endif + +#ifdef DAWN_ENABLE_BACKEND_OPENGL +typedef struct { +} DawnWSIContextGL; +#endif + +#ifdef DAWN_ENABLE_BACKEND_VULKAN +typedef struct { +} DawnWSIContextVulkan; +#endif + +#endif // DAWN_DAWN_WSI_H
diff --git a/include/dawn/native/D3D12Backend.h b/include/dawn/native/D3D12Backend.h new file mode 100644 index 0000000..6f11bb7 --- /dev/null +++ b/include/dawn/native/D3D12Backend.h
@@ -0,0 +1,111 @@ +// Copyright 2018 The Dawn Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#ifndef DAWNNATIVE_D3D12BACKEND_H_ +#define DAWNNATIVE_D3D12BACKEND_H_ + +#include <dawn/dawn_wsi.h> +#include <dawn/native/DawnNative.h> + +#include <DXGI1_4.h> +#include <d3d12.h> +#include <windows.h> +#include <wrl/client.h> + +#include <memory> + +struct ID3D12Device; +struct ID3D12Resource; + +namespace dawn::native::d3d12 { + + class D3D11on12ResourceCache; + + DAWN_NATIVE_EXPORT Microsoft::WRL::ComPtr<ID3D12Device> GetD3D12Device(WGPUDevice device); + DAWN_NATIVE_EXPORT DawnSwapChainImplementation CreateNativeSwapChainImpl(WGPUDevice device, + HWND window); + DAWN_NATIVE_EXPORT WGPUTextureFormat + GetNativeSwapChainPreferredFormat(const DawnSwapChainImplementation* swapChain); + + enum MemorySegment { + Local, + NonLocal, + }; + + DAWN_NATIVE_EXPORT uint64_t SetExternalMemoryReservation(WGPUDevice device, + uint64_t requestedReservationSize, + MemorySegment memorySegment); + + struct DAWN_NATIVE_EXPORT ExternalImageDescriptorDXGISharedHandle : ExternalImageDescriptor { + public: + ExternalImageDescriptorDXGISharedHandle(); + + // Note: SharedHandle must be a handle to a texture object. + HANDLE sharedHandle; + }; + + // Keyed mutex acquire/release uses a fixed key of 0 to match Chromium behavior. + constexpr UINT64 kDXGIKeyedMutexAcquireReleaseKey = 0; + + struct DAWN_NATIVE_EXPORT ExternalImageAccessDescriptorDXGIKeyedMutex + : ExternalImageAccessDescriptor { + public: + // TODO(chromium:1241533): Remove deprecated keyed mutex params after removing associated + // code from Chromium - we use a fixed key of 0 for acquire and release everywhere now. + uint64_t acquireMutexKey; + uint64_t releaseMutexKey; + bool isSwapChainTexture = false; + }; + + class DAWN_NATIVE_EXPORT ExternalImageDXGI { + public: + ~ExternalImageDXGI(); + + // Note: SharedHandle must be a handle to a texture object. + static std::unique_ptr<ExternalImageDXGI> Create( + WGPUDevice device, + const ExternalImageDescriptorDXGISharedHandle* descriptor); + + WGPUTexture ProduceTexture(WGPUDevice device, + const ExternalImageAccessDescriptorDXGIKeyedMutex* descriptor); + + private: + ExternalImageDXGI(Microsoft::WRL::ComPtr<ID3D12Resource> d3d12Resource, + const WGPUTextureDescriptor* descriptor); + + Microsoft::WRL::ComPtr<ID3D12Resource> mD3D12Resource; + + // Contents of WGPUTextureDescriptor are stored individually since the descriptor + // could outlive this image. + WGPUTextureUsageFlags mUsage; + WGPUTextureUsageFlags mUsageInternal = WGPUTextureUsage_None; + WGPUTextureDimension mDimension; + WGPUExtent3D mSize; + WGPUTextureFormat mFormat; + uint32_t mMipLevelCount; + uint32_t mSampleCount; + + std::unique_ptr<D3D11on12ResourceCache> mD3D11on12ResourceCache; + }; + + struct DAWN_NATIVE_EXPORT AdapterDiscoveryOptions : public AdapterDiscoveryOptionsBase { + AdapterDiscoveryOptions(); + AdapterDiscoveryOptions(Microsoft::WRL::ComPtr<IDXGIAdapter> adapter); + + Microsoft::WRL::ComPtr<IDXGIAdapter> dxgiAdapter; + }; + +} // namespace dawn::native::d3d12 + +#endif // DAWNNATIVE_D3D12BACKEND_H_
diff --git a/include/dawn/native/DawnNative.h b/include/dawn/native/DawnNative.h new file mode 100644 index 0000000..d62d0ef --- /dev/null +++ b/include/dawn/native/DawnNative.h
@@ -0,0 +1,261 @@ +// Copyright 2018 The Dawn Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#ifndef DAWNNATIVE_DAWNNATIVE_H_ +#define DAWNNATIVE_DAWNNATIVE_H_ + +#include <dawn/dawn_proc_table.h> +#include <dawn/native/dawn_native_export.h> +#include <dawn/webgpu.h> + +#include <string> +#include <vector> + +namespace dawn::platform { + class Platform; +} // namespace dawn::platform + +namespace wgpu { + struct AdapterProperties; + struct DeviceDescriptor; +} // namespace wgpu + +namespace dawn::native { + + class InstanceBase; + class AdapterBase; + + // An optional parameter of Adapter::CreateDevice() to send additional information when creating + // a Device. For example, we can use it to enable a workaround, optimization or feature. + struct DAWN_NATIVE_EXPORT DawnDeviceDescriptor { + std::vector<const char*> requiredFeatures; + std::vector<const char*> forceEnabledToggles; + std::vector<const char*> forceDisabledToggles; + + const WGPURequiredLimits* requiredLimits = nullptr; + }; + + // A struct to record the information of a toggle. A toggle is a code path in Dawn device that + // can be manually configured to run or not outside Dawn, including workarounds, special + // features and optimizations. + struct ToggleInfo { + const char* name; + const char* description; + const char* url; + }; + + // A struct to record the information of a feature. A feature is a GPU feature that is not + // required to be supported by all Dawn backends and can only be used when it is enabled on the + // creation of device. + using FeatureInfo = ToggleInfo; + + // An adapter is an object that represent on possibility of creating devices in the system. + // Most of the time it will represent a combination of a physical GPU and an API. Not that the + // same GPU can be represented by multiple adapters but on different APIs. + // + // The underlying Dawn adapter is owned by the Dawn instance so this class is not RAII but just + // a reference to an underlying adapter. + class DAWN_NATIVE_EXPORT Adapter { + public: + Adapter(); + Adapter(AdapterBase* impl); + ~Adapter(); + + Adapter(const Adapter& other); + Adapter& operator=(const Adapter& other); + + // Essentially webgpu.h's wgpuAdapterGetProperties while we don't have WGPUAdapter in + // dawn.json + void GetProperties(wgpu::AdapterProperties* properties) const; + void GetProperties(WGPUAdapterProperties* properties) const; + + std::vector<const char*> GetSupportedExtensions() const; + std::vector<const char*> GetSupportedFeatures() const; + WGPUDeviceProperties GetAdapterProperties() const; + bool GetLimits(WGPUSupportedLimits* limits) const; + + void SetUseTieredLimits(bool useTieredLimits); + + // Check that the Adapter is able to support importing external images. This is necessary + // to implement the swapchain and interop APIs in Chromium. + bool SupportsExternalImages() const; + + explicit operator bool() const; + + // Create a device on this adapter. On an error, nullptr is returned. + WGPUDevice CreateDevice(const DawnDeviceDescriptor* deviceDescriptor); + WGPUDevice CreateDevice(const wgpu::DeviceDescriptor* deviceDescriptor); + WGPUDevice CreateDevice(const WGPUDeviceDescriptor* deviceDescriptor = nullptr); + + void RequestDevice(const DawnDeviceDescriptor* descriptor, + WGPURequestDeviceCallback callback, + void* userdata); + void RequestDevice(const wgpu::DeviceDescriptor* descriptor, + WGPURequestDeviceCallback callback, + void* userdata); + void RequestDevice(const WGPUDeviceDescriptor* descriptor, + WGPURequestDeviceCallback callback, + void* userdata); + + // Returns the underlying WGPUAdapter object. + WGPUAdapter Get() const; + + // Reset the backend device object for testing purposes. + void ResetInternalDeviceForTesting(); + + private: + AdapterBase* mImpl = nullptr; + }; + + // Base class for options passed to Instance::DiscoverAdapters. + struct DAWN_NATIVE_EXPORT AdapterDiscoveryOptionsBase { + public: + const WGPUBackendType backendType; + + protected: + AdapterDiscoveryOptionsBase(WGPUBackendType type); + }; + + enum BackendValidationLevel { Full, Partial, Disabled }; + + // Represents a connection to dawn_native and is used for dependency injection, discovering + // system adapters and injecting custom adapters (like a Swiftshader Vulkan adapter). + // + // This is an RAII class for Dawn instances and also controls the lifetime of all adapters + // for this instance. + class DAWN_NATIVE_EXPORT Instance { + public: + explicit Instance(const WGPUInstanceDescriptor* desc = nullptr); + ~Instance(); + + Instance(const Instance& other) = delete; + Instance& operator=(const Instance& other) = delete; + + // Gather all adapters in the system that can be accessed with no special options. These + // adapters will later be returned by GetAdapters. + void DiscoverDefaultAdapters(); + + // Adds adapters that can be discovered with the options provided (like a getProcAddress). + // The backend is chosen based on the type of the options used. Returns true on success. + bool DiscoverAdapters(const AdapterDiscoveryOptionsBase* options); + + // Returns all the adapters that the instance knows about. + std::vector<Adapter> GetAdapters() const; + + const ToggleInfo* GetToggleInfo(const char* toggleName); + const FeatureInfo* GetFeatureInfo(WGPUFeatureName feature); + + // Enables backend validation layers + void EnableBackendValidation(bool enableBackendValidation); + void SetBackendValidationLevel(BackendValidationLevel validationLevel); + + // Enable debug capture on Dawn startup + void EnableBeginCaptureOnStartup(bool beginCaptureOnStartup); + + void SetPlatform(dawn::platform::Platform* platform); + + // Returns the underlying WGPUInstance object. + WGPUInstance Get() const; + + private: + InstanceBase* mImpl = nullptr; + }; + + // Backend-agnostic API for dawn_native + DAWN_NATIVE_EXPORT const DawnProcTable& GetProcs(); + + // Query the names of all the toggles that are enabled in device + DAWN_NATIVE_EXPORT std::vector<const char*> GetTogglesUsed(WGPUDevice device); + + // Backdoor to get the number of lazy clears for testing + DAWN_NATIVE_EXPORT size_t GetLazyClearCountForTesting(WGPUDevice device); + + // Backdoor to get the number of deprecation warnings for testing + DAWN_NATIVE_EXPORT size_t GetDeprecationWarningCountForTesting(WGPUDevice device); + + // Query if texture has been initialized + DAWN_NATIVE_EXPORT bool IsTextureSubresourceInitialized( + WGPUTexture texture, + uint32_t baseMipLevel, + uint32_t levelCount, + uint32_t baseArrayLayer, + uint32_t layerCount, + WGPUTextureAspect aspect = WGPUTextureAspect_All); + + // Backdoor to get the order of the ProcMap for testing + DAWN_NATIVE_EXPORT std::vector<const char*> GetProcMapNamesForTesting(); + + DAWN_NATIVE_EXPORT bool DeviceTick(WGPUDevice device); + + // ErrorInjector functions used for testing only. Defined in dawn_native/ErrorInjector.cpp + DAWN_NATIVE_EXPORT void EnableErrorInjector(); + DAWN_NATIVE_EXPORT void DisableErrorInjector(); + DAWN_NATIVE_EXPORT void ClearErrorInjector(); + DAWN_NATIVE_EXPORT uint64_t AcquireErrorInjectorCallCount(); + DAWN_NATIVE_EXPORT void InjectErrorAt(uint64_t index); + + // The different types of external images + enum ExternalImageType { + OpaqueFD, + DmaBuf, + IOSurface, + DXGISharedHandle, + EGLImage, + }; + + // Common properties of external images + struct DAWN_NATIVE_EXPORT ExternalImageDescriptor { + public: + const WGPUTextureDescriptor* cTextureDescriptor; // Must match image creation params + bool isInitialized; // Whether the texture is initialized on import + ExternalImageType GetType() const; + + protected: + ExternalImageDescriptor(ExternalImageType type); + + private: + ExternalImageType mType; + }; + + struct DAWN_NATIVE_EXPORT ExternalImageAccessDescriptor { + public: + bool isInitialized; // Whether the texture is initialized on import + WGPUTextureUsageFlags usage; + }; + + struct DAWN_NATIVE_EXPORT ExternalImageExportInfo { + public: + bool isInitialized; // Whether the texture is initialized after export + ExternalImageType GetType() const; + + protected: + ExternalImageExportInfo(ExternalImageType type); + + private: + ExternalImageType mType; + }; + + DAWN_NATIVE_EXPORT const char* GetObjectLabelForTesting(void* objectHandle); + + DAWN_NATIVE_EXPORT uint64_t GetAllocatedSizeForTesting(WGPUBuffer buffer); + + DAWN_NATIVE_EXPORT bool BindGroupLayoutBindingsEqualForTesting(WGPUBindGroupLayout a, + WGPUBindGroupLayout b); + +} // namespace dawn::native + +// TODO(dawn:824): Remove once the deprecation period is passed. +namespace dawn_native = dawn::native; + +#endif // DAWNNATIVE_DAWNNATIVE_H_
diff --git a/include/dawn/native/MetalBackend.h b/include/dawn/native/MetalBackend.h new file mode 100644 index 0000000..6db34a1 --- /dev/null +++ b/include/dawn/native/MetalBackend.h
@@ -0,0 +1,73 @@ +// Copyright 2018 The Dawn Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#ifndef DAWNNATIVE_METALBACKEND_H_ +#define DAWNNATIVE_METALBACKEND_H_ + +#include <dawn/dawn_wsi.h> +#include <dawn/native/DawnNative.h> + +// The specifics of the Metal backend expose types in function signatures that might not be +// available in dependent's minimum supported SDK version. Suppress all availability errors using +// clang's pragmas. Dependents using the types without guarded availability will still get errors +// when using the types. +#pragma clang diagnostic push +#pragma clang diagnostic ignored "-Wunguarded-availability" + +struct __IOSurface; +typedef __IOSurface* IOSurfaceRef; + +#ifdef __OBJC__ +# import <Metal/Metal.h> +#endif //__OBJC__ + +namespace dawn::native::metal { + + struct DAWN_NATIVE_EXPORT AdapterDiscoveryOptions : public AdapterDiscoveryOptionsBase { + AdapterDiscoveryOptions(); + }; + + struct DAWN_NATIVE_EXPORT ExternalImageDescriptorIOSurface : ExternalImageDescriptor { + public: + ExternalImageDescriptorIOSurface(); + + IOSurfaceRef ioSurface; + + // This has been deprecated. + uint32_t plane; + }; + + DAWN_NATIVE_EXPORT WGPUTexture + WrapIOSurface(WGPUDevice device, const ExternalImageDescriptorIOSurface* descriptor); + + // When making Metal interop with other APIs, we need to be careful that QueueSubmit doesn't + // mean that the operations will be visible to other APIs/Metal devices right away. macOS + // does have a global queue of graphics operations, but the command buffers are inserted there + // when they are "scheduled". Submitting other operations before the command buffer is + // scheduled could lead to races in who gets scheduled first and incorrect rendering. + DAWN_NATIVE_EXPORT void WaitForCommandsToBeScheduled(WGPUDevice device); + +} // namespace dawn::native::metal + +#ifdef __OBJC__ +namespace dawn::native::metal { + + DAWN_NATIVE_EXPORT id<MTLDevice> GetMetalDevice(WGPUDevice device); + +} // namespace dawn::native::metal +#endif // __OBJC__ + +#pragma clang diagnostic pop + +#endif // DAWNNATIVE_METALBACKEND_H_
diff --git a/include/dawn/native/NullBackend.h b/include/dawn/native/NullBackend.h new file mode 100644 index 0000000..d2799e3 --- /dev/null +++ b/include/dawn/native/NullBackend.h
@@ -0,0 +1,25 @@ +// Copyright 2018 The Dawn Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#ifndef DAWNNATIVE_NULLBACKEND_H_ +#define DAWNNATIVE_NULLBACKEND_H_ + +#include <dawn/dawn_wsi.h> +#include <dawn/native/DawnNative.h> + +namespace dawn::native::null { + DAWN_NATIVE_EXPORT DawnSwapChainImplementation CreateNativeSwapChainImpl(); +} // namespace dawn::native::null + +#endif // DAWNNATIVE_NULLBACKEND_H_
diff --git a/include/dawn/native/OpenGLBackend.h b/include/dawn/native/OpenGLBackend.h new file mode 100644 index 0000000..53c878c --- /dev/null +++ b/include/dawn/native/OpenGLBackend.h
@@ -0,0 +1,55 @@ +// Copyright 2018 The Dawn Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#ifndef DAWNNATIVE_OPENGLBACKEND_H_ +#define DAWNNATIVE_OPENGLBACKEND_H_ + +typedef void* EGLImage; + +#include <dawn/dawn_wsi.h> +#include <dawn/native/DawnNative.h> + +namespace dawn::native::opengl { + + struct DAWN_NATIVE_EXPORT AdapterDiscoveryOptions : public AdapterDiscoveryOptionsBase { + AdapterDiscoveryOptions(); + + void* (*getProc)(const char*); + }; + + struct DAWN_NATIVE_EXPORT AdapterDiscoveryOptionsES : public AdapterDiscoveryOptionsBase { + AdapterDiscoveryOptionsES(); + + void* (*getProc)(const char*); + }; + + using PresentCallback = void (*)(void*); + DAWN_NATIVE_EXPORT DawnSwapChainImplementation + CreateNativeSwapChainImpl(WGPUDevice device, PresentCallback present, void* presentUserdata); + DAWN_NATIVE_EXPORT WGPUTextureFormat + GetNativeSwapChainPreferredFormat(const DawnSwapChainImplementation* swapChain); + + struct DAWN_NATIVE_EXPORT ExternalImageDescriptorEGLImage : ExternalImageDescriptor { + public: + ExternalImageDescriptorEGLImage(); + + ::EGLImage image; + }; + + DAWN_NATIVE_EXPORT WGPUTexture + WrapExternalEGLImage(WGPUDevice device, const ExternalImageDescriptorEGLImage* descriptor); + +} // namespace dawn::native::opengl + +#endif // DAWNNATIVE_OPENGLBACKEND_H_
diff --git a/include/dawn/native/VulkanBackend.h b/include/dawn/native/VulkanBackend.h new file mode 100644 index 0000000..a02cc3c --- /dev/null +++ b/include/dawn/native/VulkanBackend.h
@@ -0,0 +1,140 @@ +// Copyright 2018 The Dawn Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#ifndef DAWNNATIVE_VULKANBACKEND_H_ +#define DAWNNATIVE_VULKANBACKEND_H_ + +#include <dawn/dawn_wsi.h> +#include <dawn/native/DawnNative.h> + +#include <vulkan/vulkan.h> + +#include <vector> + +namespace dawn::native::vulkan { + + DAWN_NATIVE_EXPORT VkInstance GetInstance(WGPUDevice device); + + DAWN_NATIVE_EXPORT PFN_vkVoidFunction GetInstanceProcAddr(WGPUDevice device, const char* pName); + + DAWN_NATIVE_EXPORT DawnSwapChainImplementation + CreateNativeSwapChainImpl(WGPUDevice device, ::VkSurfaceKHR surface); + DAWN_NATIVE_EXPORT WGPUTextureFormat + GetNativeSwapChainPreferredFormat(const DawnSwapChainImplementation* swapChain); + + struct DAWN_NATIVE_EXPORT AdapterDiscoveryOptions : public AdapterDiscoveryOptionsBase { + AdapterDiscoveryOptions(); + + bool forceSwiftShader = false; + }; + + struct DAWN_NATIVE_EXPORT ExternalImageDescriptorVk : ExternalImageDescriptor { + public: + // The following members may be ignored if |ExternalImageDescriptor::isInitialized| is false + // since the import does not need to preserve texture contents. + + // See https://www.khronos.org/registry/vulkan/specs/1.1/html/chap7.html. The acquire + // operation old/new layouts must match exactly the layouts in the release operation. So + // we may need to issue two barriers releasedOldLayout -> releasedNewLayout -> + // cTextureDescriptor.usage if the new layout is not compatible with the desired usage. + // The first barrier is the queue transfer, the second is the layout transition to our + // desired usage. + VkImageLayout releasedOldLayout = VK_IMAGE_LAYOUT_GENERAL; + VkImageLayout releasedNewLayout = VK_IMAGE_LAYOUT_GENERAL; + + protected: + using ExternalImageDescriptor::ExternalImageDescriptor; + }; + + struct ExternalImageExportInfoVk : ExternalImageExportInfo { + public: + // See comments in |ExternalImageDescriptorVk| + // Contains the old/new layouts used in the queue release operation. + VkImageLayout releasedOldLayout; + VkImageLayout releasedNewLayout; + + protected: + using ExternalImageExportInfo::ExternalImageExportInfo; + }; + +// Can't use DAWN_PLATFORM_LINUX since header included in both Dawn and Chrome +#ifdef __linux__ + + // Common properties of external images represented by FDs. On successful import the file + // descriptor's ownership is transferred to the Dawn implementation and they shouldn't be + // used outside of Dawn again. TODO(enga): Also transfer ownership in the error case so the + // caller can assume the FD is always consumed. + struct DAWN_NATIVE_EXPORT ExternalImageDescriptorFD : ExternalImageDescriptorVk { + public: + int memoryFD; // A file descriptor from an export of the memory of the image + std::vector<int> waitFDs; // File descriptors of semaphores which will be waited on + + protected: + using ExternalImageDescriptorVk::ExternalImageDescriptorVk; + }; + + // Descriptor for opaque file descriptor image import + struct DAWN_NATIVE_EXPORT ExternalImageDescriptorOpaqueFD : ExternalImageDescriptorFD { + ExternalImageDescriptorOpaqueFD(); + + VkDeviceSize allocationSize; // Must match VkMemoryAllocateInfo from image creation + uint32_t memoryTypeIndex; // Must match VkMemoryAllocateInfo from image creation + }; + + // Descriptor for dma-buf file descriptor image import + struct DAWN_NATIVE_EXPORT ExternalImageDescriptorDmaBuf : ExternalImageDescriptorFD { + ExternalImageDescriptorDmaBuf(); + + uint32_t stride; // Stride of the buffer in bytes + uint64_t drmModifier; // DRM modifier of the buffer + }; + + // Info struct that is written to in |ExportVulkanImage|. + struct DAWN_NATIVE_EXPORT ExternalImageExportInfoFD : ExternalImageExportInfoVk { + public: + // Contains the exported semaphore handles. + std::vector<int> semaphoreHandles; + + protected: + using ExternalImageExportInfoVk::ExternalImageExportInfoVk; + }; + + struct DAWN_NATIVE_EXPORT ExternalImageExportInfoOpaqueFD : ExternalImageExportInfoFD { + ExternalImageExportInfoOpaqueFD(); + }; + + struct DAWN_NATIVE_EXPORT ExternalImageExportInfoDmaBuf : ExternalImageExportInfoFD { + ExternalImageExportInfoDmaBuf(); + }; + +#endif // __linux__ + + // Imports external memory into a Vulkan image. Internally, this uses external memory / + // semaphore extensions to import the image and wait on the provided synchronizaton + // primitives before the texture can be used. + // On failure, returns a nullptr. + DAWN_NATIVE_EXPORT WGPUTexture WrapVulkanImage(WGPUDevice device, + const ExternalImageDescriptorVk* descriptor); + + // Exports external memory from a Vulkan image. This must be called on wrapped textures + // before they are destroyed. It writes the semaphore to wait on and the old/new image + // layouts to |info|. Pass VK_IMAGE_LAYOUT_UNDEFINED as |desiredLayout| if you don't want to + // perform a layout transition. + DAWN_NATIVE_EXPORT bool ExportVulkanImage(WGPUTexture texture, + VkImageLayout desiredLayout, + ExternalImageExportInfoVk* info); + +} // namespace dawn::native::vulkan + +#endif // DAWNNATIVE_VULKANBACKEND_H_
diff --git a/include/dawn/native/dawn_native_export.h b/include/dawn/native/dawn_native_export.h new file mode 100644 index 0000000..ffbd9cc --- /dev/null +++ b/include/dawn/native/dawn_native_export.h
@@ -0,0 +1,36 @@ +// Copyright 2018 The Dawn Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#ifndef DAWNNATIVE_EXPORT_H_ +#define DAWNNATIVE_EXPORT_H_ + +#if defined(DAWN_NATIVE_SHARED_LIBRARY) +# if defined(_WIN32) +# if defined(DAWN_NATIVE_IMPLEMENTATION) +# define DAWN_NATIVE_EXPORT __declspec(dllexport) +# else +# define DAWN_NATIVE_EXPORT __declspec(dllimport) +# endif +# else // defined(_WIN32) +# if defined(DAWN_NATIVE_IMPLEMENTATION) +# define DAWN_NATIVE_EXPORT __attribute__((visibility("default"))) +# else +# define DAWN_NATIVE_EXPORT +# endif +# endif // defined(_WIN32) +#else // defined(DAWN_NATIVE_SHARED_LIBRARY) +# define DAWN_NATIVE_EXPORT +#endif // defined(DAWN_NATIVE_SHARED_LIBRARY) + +#endif // DAWNNATIVE_EXPORT_H_
diff --git a/include/dawn/platform/DawnPlatform.h b/include/dawn/platform/DawnPlatform.h new file mode 100644 index 0000000..d983794 --- /dev/null +++ b/include/dawn/platform/DawnPlatform.h
@@ -0,0 +1,119 @@ +// Copyright 2019 The Dawn Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#ifndef DAWNPLATFORM_DAWNPLATFORM_H_ +#define DAWNPLATFORM_DAWNPLATFORM_H_ + +#include "dawn/platform/dawn_platform_export.h" + +#include <cstddef> +#include <cstdint> +#include <memory> + +#include <dawn/webgpu.h> + +namespace dawn::platform { + + enum class TraceCategory { + General, // General trace events + Validation, // Dawn validation + Recording, // Native command recording + GPUWork, // Actual GPU work + }; + + class DAWN_PLATFORM_EXPORT CachingInterface { + public: + CachingInterface(); + virtual ~CachingInterface(); + + // LoadData has two modes. The first mode is used to get a value which + // corresponds to the |key|. The |valueOut| is a caller provided buffer + // allocated to the size |valueSize| which is loaded with data of the + // size returned. The second mode is used to query for the existence of + // the |key| where |valueOut| is nullptr and |valueSize| must be 0. + // The return size is non-zero if the |key| exists. + virtual size_t LoadData(const WGPUDevice device, + const void* key, + size_t keySize, + void* valueOut, + size_t valueSize) = 0; + + // StoreData puts a |value| in the cache which corresponds to the |key|. + virtual void StoreData(const WGPUDevice device, + const void* key, + size_t keySize, + const void* value, + size_t valueSize) = 0; + + private: + CachingInterface(const CachingInterface&) = delete; + CachingInterface& operator=(const CachingInterface&) = delete; + }; + + class DAWN_PLATFORM_EXPORT WaitableEvent { + public: + WaitableEvent() = default; + virtual ~WaitableEvent() = default; + virtual void Wait() = 0; // Wait for completion + virtual bool IsComplete() = 0; // Non-blocking check if the event is complete + }; + + using PostWorkerTaskCallback = void (*)(void* userdata); + + class DAWN_PLATFORM_EXPORT WorkerTaskPool { + public: + WorkerTaskPool() = default; + virtual ~WorkerTaskPool() = default; + virtual std::unique_ptr<WaitableEvent> PostWorkerTask(PostWorkerTaskCallback, + void* userdata) = 0; + }; + + class DAWN_PLATFORM_EXPORT Platform { + public: + Platform(); + virtual ~Platform(); + + virtual const unsigned char* GetTraceCategoryEnabledFlag(TraceCategory category); + + virtual double MonotonicallyIncreasingTime(); + + virtual uint64_t AddTraceEvent(char phase, + const unsigned char* categoryGroupEnabled, + const char* name, + uint64_t id, + double timestamp, + int numArgs, + const char** argNames, + const unsigned char* argTypes, + const uint64_t* argValues, + unsigned char flags); + + // The |fingerprint| is provided by Dawn to inform the client to discard the Dawn caches + // when the fingerprint changes. The returned CachingInterface is expected to outlive the + // device which uses it to persistently cache objects. + virtual CachingInterface* GetCachingInterface(const void* fingerprint, + size_t fingerprintSize); + virtual std::unique_ptr<WorkerTaskPool> CreateWorkerTaskPool(); + + private: + Platform(const Platform&) = delete; + Platform& operator=(const Platform&) = delete; + }; + +} // namespace dawn::platform + +// TODO(dawn:824): Remove once the deprecation period is passed. +namespace dawn_platform = dawn::platform; + +#endif // DAWNPLATFORM_DAWNPLATFORM_H_
diff --git a/include/dawn/platform/dawn_platform_export.h b/include/dawn/platform/dawn_platform_export.h new file mode 100644 index 0000000..0626467 --- /dev/null +++ b/include/dawn/platform/dawn_platform_export.h
@@ -0,0 +1,36 @@ +// Copyright 2020 The Dawn Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#ifndef DAWNPLATFORM_EXPORT_H_ +#define DAWNPLATFORM_EXPORT_H_ + +#if defined(DAWN_PLATFORM_SHARED_LIBRARY) +# if defined(_WIN32) +# if defined(DAWN_PLATFORM_IMPLEMENTATION) +# define DAWN_PLATFORM_EXPORT __declspec(dllexport) +# else +# define DAWN_PLATFORM_EXPORT __declspec(dllimport) +# endif +# else // defined(_WIN32) +# if defined(DAWN_PLATFORM_IMPLEMENTATION) +# define DAWN_PLATFORM_EXPORT __attribute__((visibility("default"))) +# else +# define DAWN_PLATFORM_EXPORT +# endif +# endif // defined(_WIN32) +#else // defined(DAWN_PLATFORM_SHARED_LIBRARY) +# define DAWN_PLATFORM_EXPORT +#endif // defined(DAWN_PLATFORM_SHARED_LIBRARY) + +#endif // DAWNPLATFORM_EXPORT_H_
diff --git a/include/dawn/wire/Wire.h b/include/dawn/wire/Wire.h new file mode 100644 index 0000000..6e63b3e --- /dev/null +++ b/include/dawn/wire/Wire.h
@@ -0,0 +1,79 @@ +// Copyright 2017 The Dawn Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#ifndef DAWNWIRE_WIRE_H_ +#define DAWNWIRE_WIRE_H_ + +#include <cstdint> +#include <limits> + +#include "dawn/webgpu.h" +#include "dawn/wire/dawn_wire_export.h" + +namespace dawn::wire { + + class DAWN_WIRE_EXPORT CommandSerializer { + public: + CommandSerializer(); + virtual ~CommandSerializer(); + CommandSerializer(const CommandSerializer& rhs) = delete; + CommandSerializer& operator=(const CommandSerializer& rhs) = delete; + + // Get space for serializing commands. + // GetCmdSpace will never be called with a value larger than + // what GetMaximumAllocationSize returns. Return nullptr to indicate + // a fatal error. + virtual void* GetCmdSpace(size_t size) = 0; + virtual bool Flush() = 0; + virtual size_t GetMaximumAllocationSize() const = 0; + virtual void OnSerializeError(); + }; + + class DAWN_WIRE_EXPORT CommandHandler { + public: + CommandHandler(); + virtual ~CommandHandler(); + CommandHandler(const CommandHandler& rhs) = delete; + CommandHandler& operator=(const CommandHandler& rhs) = delete; + + virtual const volatile char* HandleCommands(const volatile char* commands, size_t size) = 0; + }; + + DAWN_WIRE_EXPORT size_t + SerializedWGPUDevicePropertiesSize(const WGPUDeviceProperties* deviceProperties); + + DAWN_WIRE_EXPORT void SerializeWGPUDeviceProperties( + const WGPUDeviceProperties* deviceProperties, + char* serializeBuffer); + + DAWN_WIRE_EXPORT bool DeserializeWGPUDeviceProperties(WGPUDeviceProperties* deviceProperties, + const volatile char* deserializeBuffer, + size_t deserializeBufferSize); + + DAWN_WIRE_EXPORT size_t + SerializedWGPUSupportedLimitsSize(const WGPUSupportedLimits* supportedLimits); + + DAWN_WIRE_EXPORT void SerializeWGPUSupportedLimits(const WGPUSupportedLimits* supportedLimits, + char* serializeBuffer); + + DAWN_WIRE_EXPORT bool DeserializeWGPUSupportedLimits(WGPUSupportedLimits* supportedLimits, + const volatile char* deserializeBuffer, + size_t deserializeBufferSize); + +} // namespace dawn::wire + +// TODO(dawn:824): Remove once the deprecation period is passed. +namespace dawn_wire = dawn::wire; + +#endif // DAWNWIRE_WIRE_H_
diff --git a/include/dawn/wire/WireClient.h b/include/dawn/wire/WireClient.h new file mode 100644 index 0000000..d5e9629 --- /dev/null +++ b/include/dawn/wire/WireClient.h
@@ -0,0 +1,183 @@ +// Copyright 2019 The Dawn Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#ifndef DAWNWIRE_WIRECLIENT_H_ +#define DAWNWIRE_WIRECLIENT_H_ + +#include "dawn/dawn_proc_table.h" +#include "dawn/wire/Wire.h" + +#include <memory> +#include <vector> + +namespace dawn::wire { + + namespace client { + class Client; + class MemoryTransferService; + + DAWN_WIRE_EXPORT const DawnProcTable& GetProcs(); + } // namespace client + + struct ReservedTexture { + WGPUTexture texture; + uint32_t id; + uint32_t generation; + uint32_t deviceId; + uint32_t deviceGeneration; + }; + + struct ReservedSwapChain { + WGPUSwapChain swapchain; + uint32_t id; + uint32_t generation; + uint32_t deviceId; + uint32_t deviceGeneration; + }; + + struct ReservedDevice { + WGPUDevice device; + uint32_t id; + uint32_t generation; + }; + + struct ReservedInstance { + WGPUInstance instance; + uint32_t id; + uint32_t generation; + }; + + struct DAWN_WIRE_EXPORT WireClientDescriptor { + CommandSerializer* serializer; + client::MemoryTransferService* memoryTransferService = nullptr; + }; + + class DAWN_WIRE_EXPORT WireClient : public CommandHandler { + public: + WireClient(const WireClientDescriptor& descriptor); + ~WireClient() override; + + const volatile char* HandleCommands(const volatile char* commands, + size_t size) override final; + + ReservedTexture ReserveTexture(WGPUDevice device); + ReservedSwapChain ReserveSwapChain(WGPUDevice device); + ReservedDevice ReserveDevice(); + ReservedInstance ReserveInstance(); + + void ReclaimTextureReservation(const ReservedTexture& reservation); + void ReclaimSwapChainReservation(const ReservedSwapChain& reservation); + void ReclaimDeviceReservation(const ReservedDevice& reservation); + void ReclaimInstanceReservation(const ReservedInstance& reservation); + + // Disconnects the client. + // Commands allocated after this point will not be sent. + void Disconnect(); + + private: + std::unique_ptr<client::Client> mImpl; + }; + + namespace client { + class DAWN_WIRE_EXPORT MemoryTransferService { + public: + MemoryTransferService(); + virtual ~MemoryTransferService(); + + class ReadHandle; + class WriteHandle; + + // Create a handle for reading server data. + // This may fail and return nullptr. + virtual ReadHandle* CreateReadHandle(size_t) = 0; + + // Create a handle for writing server data. + // This may fail and return nullptr. + virtual WriteHandle* CreateWriteHandle(size_t) = 0; + + class DAWN_WIRE_EXPORT ReadHandle { + public: + ReadHandle(); + virtual ~ReadHandle(); + + // Get the required serialization size for SerializeCreate + virtual size_t SerializeCreateSize() = 0; + + // Serialize the handle into |serializePointer| so it can be received by the server. + virtual void SerializeCreate(void* serializePointer) = 0; + + // Simply return the base address of the allocation (without applying any offset) + // Returns nullptr if the allocation failed. + // The data must live at least until the ReadHandle is destructued + virtual const void* GetData() = 0; + + // Gets called when a MapReadCallback resolves. + // deserialize the data update and apply + // it to the range (offset, offset + size) of allocation + // There could be nothing to be deserialized (if using shared memory) + // Needs to check potential offset/size OOB and overflow + virtual bool DeserializeDataUpdate(const void* deserializePointer, + size_t deserializeSize, + size_t offset, + size_t size) = 0; + + private: + ReadHandle(const ReadHandle&) = delete; + ReadHandle& operator=(const ReadHandle&) = delete; + }; + + class DAWN_WIRE_EXPORT WriteHandle { + public: + WriteHandle(); + virtual ~WriteHandle(); + + // Get the required serialization size for SerializeCreate + virtual size_t SerializeCreateSize() = 0; + + // Serialize the handle into |serializePointer| so it can be received by the server. + virtual void SerializeCreate(void* serializePointer) = 0; + + // Simply return the base address of the allocation (without applying any offset) + // The data returned should be zero-initialized. + // The data returned must live at least until the WriteHandle is destructed. + // On failure, the pointer returned should be null. + virtual void* GetData() = 0; + + // Get the required serialization size for SerializeDataUpdate + virtual size_t SizeOfSerializeDataUpdate(size_t offset, size_t size) = 0; + + // Serialize a command to send the modified contents of + // the subrange (offset, offset + size) of the allocation at buffer unmap + // This subrange is always the whole mapped region for now + // There could be nothing to be serialized (if using shared memory) + virtual void SerializeDataUpdate(void* serializePointer, + size_t offset, + size_t size) = 0; + + private: + WriteHandle(const WriteHandle&) = delete; + WriteHandle& operator=(const WriteHandle&) = delete; + }; + + private: + MemoryTransferService(const MemoryTransferService&) = delete; + MemoryTransferService& operator=(const MemoryTransferService&) = delete; + }; + + // Backdoor to get the order of the ProcMap for testing + DAWN_WIRE_EXPORT std::vector<const char*> GetProcMapNamesForTesting(); + } // namespace client +} // namespace dawn::wire + +#endif // DAWNWIRE_WIRECLIENT_H_
diff --git a/include/dawn/wire/WireServer.h b/include/dawn/wire/WireServer.h new file mode 100644 index 0000000..b561bbb --- /dev/null +++ b/include/dawn/wire/WireServer.h
@@ -0,0 +1,150 @@ +// Copyright 2019 The Dawn Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#ifndef DAWNWIRE_WIRESERVER_H_ +#define DAWNWIRE_WIRESERVER_H_ + +#include <memory> + +#include "dawn/wire/Wire.h" + +struct DawnProcTable; + +namespace dawn::wire { + + namespace server { + class Server; + class MemoryTransferService; + } // namespace server + + struct DAWN_WIRE_EXPORT WireServerDescriptor { + const DawnProcTable* procs; + CommandSerializer* serializer; + server::MemoryTransferService* memoryTransferService = nullptr; + }; + + class DAWN_WIRE_EXPORT WireServer : public CommandHandler { + public: + WireServer(const WireServerDescriptor& descriptor); + ~WireServer() override; + + const volatile char* HandleCommands(const volatile char* commands, + size_t size) override final; + + bool InjectTexture(WGPUTexture texture, + uint32_t id, + uint32_t generation, + uint32_t deviceId, + uint32_t deviceGeneration); + bool InjectSwapChain(WGPUSwapChain swapchain, + uint32_t id, + uint32_t generation, + uint32_t deviceId, + uint32_t deviceGeneration); + + bool InjectDevice(WGPUDevice device, uint32_t id, uint32_t generation); + + bool InjectInstance(WGPUInstance instance, uint32_t id, uint32_t generation); + + // Look up a device by (id, generation) pair. Returns nullptr if the generation + // has expired or the id is not found. + // The Wire does not have destroy hooks to allow an embedder to observe when an object + // has been destroyed, but in Chrome, we need to know the list of live devices so we + // can call device.Tick() on all of them periodically to ensure progress on asynchronous + // work is made. Getting this list can be done by tracking the (id, generation) of + // previously injected devices, and observing if GetDevice(id, generation) returns non-null. + WGPUDevice GetDevice(uint32_t id, uint32_t generation); + + private: + std::unique_ptr<server::Server> mImpl; + }; + + namespace server { + class DAWN_WIRE_EXPORT MemoryTransferService { + public: + MemoryTransferService(); + virtual ~MemoryTransferService(); + + class ReadHandle; + class WriteHandle; + + // Deserialize data to create Read/Write handles. These handles are for the client + // to Read/Write data. + virtual bool DeserializeReadHandle(const void* deserializePointer, + size_t deserializeSize, + ReadHandle** readHandle) = 0; + virtual bool DeserializeWriteHandle(const void* deserializePointer, + size_t deserializeSize, + WriteHandle** writeHandle) = 0; + + class DAWN_WIRE_EXPORT ReadHandle { + public: + ReadHandle(); + virtual ~ReadHandle(); + + // Return the size of the command serialized if + // SerializeDataUpdate is called with the same offset/size args + virtual size_t SizeOfSerializeDataUpdate(size_t offset, size_t size) = 0; + + // Gets called when a MapReadCallback resolves. + // Serialize the data update for the range (offset, offset + size) into + // |serializePointer| to the client There could be nothing to be serialized (if + // using shared memory) + virtual void SerializeDataUpdate(const void* data, + size_t offset, + size_t size, + void* serializePointer) = 0; + + private: + ReadHandle(const ReadHandle&) = delete; + ReadHandle& operator=(const ReadHandle&) = delete; + }; + + class DAWN_WIRE_EXPORT WriteHandle { + public: + WriteHandle(); + virtual ~WriteHandle(); + + // Set the target for writes from the client. DeserializeFlush should copy data + // into the target. + void SetTarget(void* data); + // Set Staging data length for OOB check + void SetDataLength(size_t dataLength); + + // This function takes in the serialized result of + // client::MemoryTransferService::WriteHandle::SerializeDataUpdate. + // Needs to check potential offset/size OOB and overflow + virtual bool DeserializeDataUpdate(const void* deserializePointer, + size_t deserializeSize, + size_t offset, + size_t size) = 0; + + protected: + void* mTargetData = nullptr; + size_t mDataLength = 0; + + private: + WriteHandle(const WriteHandle&) = delete; + WriteHandle& operator=(const WriteHandle&) = delete; + }; + + private: + MemoryTransferService(const MemoryTransferService&) = delete; + MemoryTransferService& operator=(const MemoryTransferService&) = delete; + }; + } // namespace server + +} // namespace dawn::wire + +#endif // DAWNWIRE_WIRESERVER_H_
diff --git a/include/dawn/wire/dawn_wire_export.h b/include/dawn/wire/dawn_wire_export.h new file mode 100644 index 0000000..8043f61 --- /dev/null +++ b/include/dawn/wire/dawn_wire_export.h
@@ -0,0 +1,36 @@ +// Copyright 2018 The Dawn Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#ifndef DAWNWIRE_EXPORT_H_ +#define DAWNWIRE_EXPORT_H_ + +#if defined(DAWN_WIRE_SHARED_LIBRARY) +# if defined(_WIN32) +# if defined(DAWN_WIRE_IMPLEMENTATION) +# define DAWN_WIRE_EXPORT __declspec(dllexport) +# else +# define DAWN_WIRE_EXPORT __declspec(dllimport) +# endif +# else // defined(_WIN32) +# if defined(DAWN_WIRE_IMPLEMENTATION) +# define DAWN_WIRE_EXPORT __attribute__((visibility("default"))) +# else +# define DAWN_WIRE_EXPORT +# endif +# endif // defined(_WIN32) +#else // defined(DAWN_WIRE_SHARED_LIBRARY) +# define DAWN_WIRE_EXPORT +#endif // defined(DAWN_WIRE_SHARED_LIBRARY) + +#endif // DAWNWIRE_EXPORT_H_
diff --git a/include/webgpu/webgpu.h b/include/webgpu/webgpu.h new file mode 100644 index 0000000..4a29d37 --- /dev/null +++ b/include/webgpu/webgpu.h
@@ -0,0 +1 @@ +#include "dawn/webgpu.h"
diff --git a/include/webgpu/webgpu_cpp.h b/include/webgpu/webgpu_cpp.h new file mode 100644 index 0000000..5bbd869 --- /dev/null +++ b/include/webgpu/webgpu_cpp.h
@@ -0,0 +1 @@ +#include <dawn/webgpu_cpp.h>
diff --git a/infra/OWNERS b/infra/OWNERS new file mode 100644 index 0000000..0473960 --- /dev/null +++ b/infra/OWNERS
@@ -0,0 +1,2 @@ +rharrison@chromium.org +enga@chromium.org
diff --git a/infra/config/OWNERS b/infra/config/OWNERS new file mode 100644 index 0000000..6b5005f --- /dev/null +++ b/infra/config/OWNERS
@@ -0,0 +1,2 @@ +cwallez@chromium.org +tandrii@chromium.org
diff --git a/infra/config/PRESUBMIT.py b/infra/config/PRESUBMIT.py index 6f2e2a0..6193c41 100644 --- a/infra/config/PRESUBMIT.py +++ b/infra/config/PRESUBMIT.py
@@ -1,4 +1,4 @@ -# Copyright 2021 The Tint Authors +# Copyright 2018 The Dawn Authors # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License.
diff --git a/infra/config/global/generated/commit-queue.cfg b/infra/config/global/generated/commit-queue.cfg index 6158aa5..ac774d1 100644 --- a/infra/config/global/generated/commit-queue.cfg +++ b/infra/config/global/generated/commit-queue.cfg
@@ -12,59 +12,68 @@ } } config_groups { - name: "Tint-CQ" + name: "Dawn-CQ" gerrit { url: "https://dawn-review.googlesource.com" projects { - name: "tint" + name: "dawn" ref_regexp: "refs/heads/.+" } } verifiers { gerrit_cq_ability { - committer_list: "project-tint-committers" - dry_run_access_list: "project-tint-tryjobs-access" + committer_list: "project-dawn-committers" + dry_run_access_list: "project-dawn-tryjob-access" } tryjob { builders { - name: "tint/try/linux-clang-dbg-x64" + name: "chromium/try/linux-dawn-rel" } builders { - name: "tint/try/linux-clang-dbg-x86" + name: "chromium/try/mac-dawn-rel" } builders { - name: "tint/try/linux-clang-rel-x64" + name: "chromium/try/win-dawn-rel" } builders { - name: "tint/try/linux-clang-rel-x86" + name: "dawn/try/linux-clang-dbg-x64" } builders { - name: "tint/try/mac-dbg" + name: "dawn/try/linux-clang-dbg-x86" } builders { - name: "tint/try/mac-rel" + name: "dawn/try/linux-clang-rel-x64" } builders { - name: "tint/try/presubmit" + name: "dawn/try/linux-clang-rel-x86" + } + builders { + name: "dawn/try/mac-dbg" + } + builders { + name: "dawn/try/mac-rel" + } + builders { + name: "dawn/try/presubmit" disable_reuse: true } builders { - name: "tint/try/win-clang-dbg-x64" + name: "dawn/try/win-clang-dbg-x64" } builders { - name: "tint/try/win-clang-dbg-x86" + name: "dawn/try/win-clang-dbg-x86" } builders { - name: "tint/try/win-clang-rel-x64" + name: "dawn/try/win-clang-rel-x64" } builders { - name: "tint/try/win-clang-rel-x86" + name: "dawn/try/win-clang-rel-x86" } builders { - name: "tint/try/win-msvc-dbg-x64" + name: "dawn/try/win-msvc-dbg-x64" } builders { - name: "tint/try/win-msvc-rel-x64" + name: "dawn/try/win-msvc-rel-x64" } retry_config { single_quota: 1
diff --git a/infra/config/global/generated/cr-buildbucket.cfg b/infra/config/global/generated/cr-buildbucket.cfg index b94912c..732cf74 100644 --- a/infra/config/global/generated/cr-buildbucket.cfg +++ b/infra/config/global/generated/cr-buildbucket.cfg
@@ -11,13 +11,35 @@ } swarming { builders { + name: "cron-linux-clang-rel-x64" + swarming_host: "chromium-swarm.appspot.com" + dimensions: "cpu:x86-64" + dimensions: "os:Ubuntu-18.04" + dimensions: "pool:luci.flex.ci" + recipe { + name: "dawn" + cipd_package: "infra/recipe_bundles/chromium.googlesource.com/chromium/tools/build" + cipd_version: "refs/heads/master" + properties_j: "$build/goma:{\"enable_ats\":true,\"rpc_extra_params\":\"?prod\",\"server_host\":\"goma.chromium.org\"}" + properties_j: "clang:true" + properties_j: "debug:false" + properties_j: "gen_fuzz_corpus:true" + properties_j: "target_cpu:\"x64\"" + } + service_account: "dawn-ci-builder@chops-service-accounts.iam.gserviceaccount.com" + experiments { + key: "luci.recipes.use_python3" + value: 100 + } + } + builders { name: "linux-clang-dbg-x64" swarming_host: "chromium-swarm.appspot.com" dimensions: "cpu:x86-64" dimensions: "os:Ubuntu-18.04" dimensions: "pool:luci.flex.ci" recipe { - name: "tint" + name: "dawn" cipd_package: "infra/recipe_bundles/chromium.googlesource.com/chromium/tools/build" cipd_version: "refs/heads/master" properties_j: "$build/goma:{\"enable_ats\":true,\"rpc_extra_params\":\"?prod\",\"server_host\":\"goma.chromium.org\"}" @@ -25,7 +47,7 @@ properties_j: "debug:true" properties_j: "target_cpu:\"x64\"" } - service_account: "tint-ci-builder@chops-service-accounts.iam.gserviceaccount.com" + service_account: "dawn-ci-builder@chops-service-accounts.iam.gserviceaccount.com" experiments { key: "luci.recipes.use_python3" value: 100 @@ -38,7 +60,7 @@ dimensions: "os:Ubuntu-18.04" dimensions: "pool:luci.flex.ci" recipe { - name: "tint" + name: "dawn" cipd_package: "infra/recipe_bundles/chromium.googlesource.com/chromium/tools/build" cipd_version: "refs/heads/master" properties_j: "$build/goma:{\"enable_ats\":true,\"rpc_extra_params\":\"?prod\",\"server_host\":\"goma.chromium.org\"}" @@ -46,7 +68,7 @@ properties_j: "debug:true" properties_j: "target_cpu:\"x86\"" } - service_account: "tint-ci-builder@chops-service-accounts.iam.gserviceaccount.com" + service_account: "dawn-ci-builder@chops-service-accounts.iam.gserviceaccount.com" experiments { key: "luci.recipes.use_python3" value: 100 @@ -59,7 +81,7 @@ dimensions: "os:Ubuntu-18.04" dimensions: "pool:luci.flex.ci" recipe { - name: "tint" + name: "dawn" cipd_package: "infra/recipe_bundles/chromium.googlesource.com/chromium/tools/build" cipd_version: "refs/heads/master" properties_j: "$build/goma:{\"enable_ats\":true,\"rpc_extra_params\":\"?prod\",\"server_host\":\"goma.chromium.org\"}" @@ -67,7 +89,7 @@ properties_j: "debug:false" properties_j: "target_cpu:\"x64\"" } - service_account: "tint-ci-builder@chops-service-accounts.iam.gserviceaccount.com" + service_account: "dawn-ci-builder@chops-service-accounts.iam.gserviceaccount.com" experiments { key: "luci.recipes.use_python3" value: 100 @@ -80,7 +102,7 @@ dimensions: "os:Ubuntu-18.04" dimensions: "pool:luci.flex.ci" recipe { - name: "tint" + name: "dawn" cipd_package: "infra/recipe_bundles/chromium.googlesource.com/chromium/tools/build" cipd_version: "refs/heads/master" properties_j: "$build/goma:{\"enable_ats\":true,\"rpc_extra_params\":\"?prod\",\"server_host\":\"goma.chromium.org\"}" @@ -88,7 +110,7 @@ properties_j: "debug:false" properties_j: "target_cpu:\"x86\"" } - service_account: "tint-ci-builder@chops-service-accounts.iam.gserviceaccount.com" + service_account: "dawn-ci-builder@chops-service-accounts.iam.gserviceaccount.com" experiments { key: "luci.recipes.use_python3" value: 100 @@ -101,7 +123,7 @@ dimensions: "os:Mac-10.15|Mac-11" dimensions: "pool:luci.flex.ci" recipe { - name: "tint" + name: "dawn" cipd_package: "infra/recipe_bundles/chromium.googlesource.com/chromium/tools/build" cipd_version: "refs/heads/master" properties_j: "$build/goma:{\"rpc_extra_params\":\"?prod\",\"server_host\":\"goma.chromium.org\"}" @@ -113,7 +135,7 @@ name: "osx_sdk" path: "osx_sdk" } - service_account: "tint-ci-builder@chops-service-accounts.iam.gserviceaccount.com" + service_account: "dawn-ci-builder@chops-service-accounts.iam.gserviceaccount.com" experiments { key: "luci.recipes.use_python3" value: 100 @@ -126,7 +148,7 @@ dimensions: "os:Mac-10.15|Mac-11" dimensions: "pool:luci.flex.ci" recipe { - name: "tint" + name: "dawn" cipd_package: "infra/recipe_bundles/chromium.googlesource.com/chromium/tools/build" cipd_version: "refs/heads/master" properties_j: "$build/goma:{\"rpc_extra_params\":\"?prod\",\"server_host\":\"goma.chromium.org\"}" @@ -138,7 +160,7 @@ name: "osx_sdk" path: "osx_sdk" } - service_account: "tint-ci-builder@chops-service-accounts.iam.gserviceaccount.com" + service_account: "dawn-ci-builder@chops-service-accounts.iam.gserviceaccount.com" experiments { key: "luci.recipes.use_python3" value: 100 @@ -151,7 +173,7 @@ dimensions: "os:Windows-10" dimensions: "pool:luci.flex.ci" recipe { - name: "tint" + name: "dawn" cipd_package: "infra/recipe_bundles/chromium.googlesource.com/chromium/tools/build" cipd_version: "refs/heads/master" properties_j: "$build/goma:{\"enable_ats\":true,\"rpc_extra_params\":\"?prod\",\"server_host\":\"goma.chromium.org\"}" @@ -163,7 +185,7 @@ name: "win_toolchain" path: "win_toolchain" } - service_account: "tint-ci-builder@chops-service-accounts.iam.gserviceaccount.com" + service_account: "dawn-ci-builder@chops-service-accounts.iam.gserviceaccount.com" experiments { key: "luci.recipes.use_python3" value: 100 @@ -176,7 +198,7 @@ dimensions: "os:Windows-10" dimensions: "pool:luci.flex.ci" recipe { - name: "tint" + name: "dawn" cipd_package: "infra/recipe_bundles/chromium.googlesource.com/chromium/tools/build" cipd_version: "refs/heads/master" properties_j: "$build/goma:{\"enable_ats\":true,\"rpc_extra_params\":\"?prod\",\"server_host\":\"goma.chromium.org\"}" @@ -188,7 +210,7 @@ name: "win_toolchain" path: "win_toolchain" } - service_account: "tint-ci-builder@chops-service-accounts.iam.gserviceaccount.com" + service_account: "dawn-ci-builder@chops-service-accounts.iam.gserviceaccount.com" experiments { key: "luci.recipes.use_python3" value: 100 @@ -201,7 +223,7 @@ dimensions: "os:Windows-10" dimensions: "pool:luci.flex.ci" recipe { - name: "tint" + name: "dawn" cipd_package: "infra/recipe_bundles/chromium.googlesource.com/chromium/tools/build" cipd_version: "refs/heads/master" properties_j: "$build/goma:{\"enable_ats\":true,\"rpc_extra_params\":\"?prod\",\"server_host\":\"goma.chromium.org\"}" @@ -213,7 +235,7 @@ name: "win_toolchain" path: "win_toolchain" } - service_account: "tint-ci-builder@chops-service-accounts.iam.gserviceaccount.com" + service_account: "dawn-ci-builder@chops-service-accounts.iam.gserviceaccount.com" experiments { key: "luci.recipes.use_python3" value: 100 @@ -226,7 +248,7 @@ dimensions: "os:Windows-10" dimensions: "pool:luci.flex.ci" recipe { - name: "tint" + name: "dawn" cipd_package: "infra/recipe_bundles/chromium.googlesource.com/chromium/tools/build" cipd_version: "refs/heads/master" properties_j: "$build/goma:{\"enable_ats\":true,\"rpc_extra_params\":\"?prod\",\"server_host\":\"goma.chromium.org\"}" @@ -238,7 +260,7 @@ name: "win_toolchain" path: "win_toolchain" } - service_account: "tint-ci-builder@chops-service-accounts.iam.gserviceaccount.com" + service_account: "dawn-ci-builder@chops-service-accounts.iam.gserviceaccount.com" experiments { key: "luci.recipes.use_python3" value: 100 @@ -251,14 +273,14 @@ dimensions: "os:Windows-10" dimensions: "pool:luci.flex.ci" recipe { - name: "tint" + name: "dawn" cipd_package: "infra/recipe_bundles/chromium.googlesource.com/chromium/tools/build" cipd_version: "refs/heads/master" properties_j: "clang:false" properties_j: "debug:true" properties_j: "target_cpu:\"x64\"" } - service_account: "tint-ci-builder@chops-service-accounts.iam.gserviceaccount.com" + service_account: "dawn-ci-builder@chops-service-accounts.iam.gserviceaccount.com" experiments { key: "luci.recipes.use_python3" value: 100 @@ -271,14 +293,14 @@ dimensions: "os:Windows-10" dimensions: "pool:luci.flex.ci" recipe { - name: "tint" + name: "dawn" cipd_package: "infra/recipe_bundles/chromium.googlesource.com/chromium/tools/build" cipd_version: "refs/heads/master" properties_j: "clang:false" properties_j: "debug:false" properties_j: "target_cpu:\"x64\"" } - service_account: "tint-ci-builder@chops-service-accounts.iam.gserviceaccount.com" + service_account: "dawn-ci-builder@chops-service-accounts.iam.gserviceaccount.com" experiments { key: "luci.recipes.use_python3" value: 100 @@ -293,7 +315,7 @@ } acls { role: SCHEDULER - group: "project-tint-tryjob-access" + group: "project-dawn-tryjob-access" } acls { role: SCHEDULER @@ -307,7 +329,7 @@ dimensions: "os:Ubuntu-18.04" dimensions: "pool:luci.flex.try" recipe { - name: "tint" + name: "dawn" cipd_package: "infra/recipe_bundles/chromium.googlesource.com/chromium/tools/build" cipd_version: "refs/heads/master" properties_j: "$build/goma:{\"enable_ats\":true,\"rpc_extra_params\":\"?prod\",\"server_host\":\"goma.chromium.org\"}" @@ -316,7 +338,7 @@ properties_j: "debug:true" properties_j: "target_cpu:\"x64\"" } - service_account: "tint-try-builder@chops-service-accounts.iam.gserviceaccount.com" + service_account: "dawn-try-builder@chops-service-accounts.iam.gserviceaccount.com" experiments { key: "luci.recipes.use_python3" value: 100 @@ -329,7 +351,7 @@ dimensions: "os:Ubuntu-18.04" dimensions: "pool:luci.flex.try" recipe { - name: "tint" + name: "dawn" cipd_package: "infra/recipe_bundles/chromium.googlesource.com/chromium/tools/build" cipd_version: "refs/heads/master" properties_j: "$build/goma:{\"enable_ats\":true,\"rpc_extra_params\":\"?prod\",\"server_host\":\"goma.chromium.org\"}" @@ -338,7 +360,7 @@ properties_j: "debug:true" properties_j: "target_cpu:\"x86\"" } - service_account: "tint-try-builder@chops-service-accounts.iam.gserviceaccount.com" + service_account: "dawn-try-builder@chops-service-accounts.iam.gserviceaccount.com" experiments { key: "luci.recipes.use_python3" value: 100 @@ -351,7 +373,7 @@ dimensions: "os:Ubuntu-18.04" dimensions: "pool:luci.flex.try" recipe { - name: "tint" + name: "dawn" cipd_package: "infra/recipe_bundles/chromium.googlesource.com/chromium/tools/build" cipd_version: "refs/heads/master" properties_j: "$build/goma:{\"enable_ats\":true,\"rpc_extra_params\":\"?prod\",\"server_host\":\"goma.chromium.org\"}" @@ -360,7 +382,7 @@ properties_j: "debug:false" properties_j: "target_cpu:\"x64\"" } - service_account: "tint-try-builder@chops-service-accounts.iam.gserviceaccount.com" + service_account: "dawn-try-builder@chops-service-accounts.iam.gserviceaccount.com" experiments { key: "luci.recipes.use_python3" value: 100 @@ -373,7 +395,7 @@ dimensions: "os:Ubuntu-18.04" dimensions: "pool:luci.flex.try" recipe { - name: "tint" + name: "dawn" cipd_package: "infra/recipe_bundles/chromium.googlesource.com/chromium/tools/build" cipd_version: "refs/heads/master" properties_j: "$build/goma:{\"enable_ats\":true,\"rpc_extra_params\":\"?prod\",\"server_host\":\"goma.chromium.org\"}" @@ -382,7 +404,7 @@ properties_j: "debug:false" properties_j: "target_cpu:\"x86\"" } - service_account: "tint-try-builder@chops-service-accounts.iam.gserviceaccount.com" + service_account: "dawn-try-builder@chops-service-accounts.iam.gserviceaccount.com" experiments { key: "luci.recipes.use_python3" value: 100 @@ -395,7 +417,7 @@ dimensions: "os:Mac-10.15|Mac-11" dimensions: "pool:luci.flex.try" recipe { - name: "tint" + name: "dawn" cipd_package: "infra/recipe_bundles/chromium.googlesource.com/chromium/tools/build" cipd_version: "refs/heads/master" properties_j: "$build/goma:{\"rpc_extra_params\":\"?prod\",\"server_host\":\"goma.chromium.org\"}" @@ -408,7 +430,7 @@ name: "osx_sdk" path: "osx_sdk" } - service_account: "tint-try-builder@chops-service-accounts.iam.gserviceaccount.com" + service_account: "dawn-try-builder@chops-service-accounts.iam.gserviceaccount.com" experiments { key: "luci.recipes.use_python3" value: 100 @@ -421,7 +443,7 @@ dimensions: "os:Mac-10.15|Mac-11" dimensions: "pool:luci.flex.try" recipe { - name: "tint" + name: "dawn" cipd_package: "infra/recipe_bundles/chromium.googlesource.com/chromium/tools/build" cipd_version: "refs/heads/master" properties_j: "$build/goma:{\"rpc_extra_params\":\"?prod\",\"server_host\":\"goma.chromium.org\"}" @@ -434,7 +456,7 @@ name: "osx_sdk" path: "osx_sdk" } - service_account: "tint-try-builder@chops-service-accounts.iam.gserviceaccount.com" + service_account: "dawn-try-builder@chops-service-accounts.iam.gserviceaccount.com" experiments { key: "luci.recipes.use_python3" value: 100 @@ -451,10 +473,10 @@ cipd_package: "infra/recipe_bundles/chromium.googlesource.com/chromium/tools/build" cipd_version: "refs/heads/master" properties_j: "$depot_tools/bot_update:{\"apply_patch_on_gclient\":true}" - properties_j: "repo_name:\"tint\"" + properties_j: "repo_name:\"dawn\"" properties_j: "runhooks:true" } - service_account: "tint-try-builder@chops-service-accounts.iam.gserviceaccount.com" + service_account: "dawn-try-builder@chops-service-accounts.iam.gserviceaccount.com" experiments { key: "luci.recipes.use_python3" value: 100 @@ -467,7 +489,7 @@ dimensions: "os:Windows-10" dimensions: "pool:luci.flex.try" recipe { - name: "tint" + name: "dawn" cipd_package: "infra/recipe_bundles/chromium.googlesource.com/chromium/tools/build" cipd_version: "refs/heads/master" properties_j: "$build/goma:{\"enable_ats\":true,\"rpc_extra_params\":\"?prod\",\"server_host\":\"goma.chromium.org\"}" @@ -480,7 +502,7 @@ name: "win_toolchain" path: "win_toolchain" } - service_account: "tint-try-builder@chops-service-accounts.iam.gserviceaccount.com" + service_account: "dawn-try-builder@chops-service-accounts.iam.gserviceaccount.com" experiments { key: "luci.recipes.use_python3" value: 100 @@ -493,7 +515,7 @@ dimensions: "os:Windows-10" dimensions: "pool:luci.flex.try" recipe { - name: "tint" + name: "dawn" cipd_package: "infra/recipe_bundles/chromium.googlesource.com/chromium/tools/build" cipd_version: "refs/heads/master" properties_j: "$build/goma:{\"enable_ats\":true,\"rpc_extra_params\":\"?prod\",\"server_host\":\"goma.chromium.org\"}" @@ -506,7 +528,7 @@ name: "win_toolchain" path: "win_toolchain" } - service_account: "tint-try-builder@chops-service-accounts.iam.gserviceaccount.com" + service_account: "dawn-try-builder@chops-service-accounts.iam.gserviceaccount.com" experiments { key: "luci.recipes.use_python3" value: 100 @@ -519,7 +541,7 @@ dimensions: "os:Windows-10" dimensions: "pool:luci.flex.try" recipe { - name: "tint" + name: "dawn" cipd_package: "infra/recipe_bundles/chromium.googlesource.com/chromium/tools/build" cipd_version: "refs/heads/master" properties_j: "$build/goma:{\"enable_ats\":true,\"rpc_extra_params\":\"?prod\",\"server_host\":\"goma.chromium.org\"}" @@ -532,7 +554,7 @@ name: "win_toolchain" path: "win_toolchain" } - service_account: "tint-try-builder@chops-service-accounts.iam.gserviceaccount.com" + service_account: "dawn-try-builder@chops-service-accounts.iam.gserviceaccount.com" experiments { key: "luci.recipes.use_python3" value: 100 @@ -545,7 +567,7 @@ dimensions: "os:Windows-10" dimensions: "pool:luci.flex.try" recipe { - name: "tint" + name: "dawn" cipd_package: "infra/recipe_bundles/chromium.googlesource.com/chromium/tools/build" cipd_version: "refs/heads/master" properties_j: "$build/goma:{\"enable_ats\":true,\"rpc_extra_params\":\"?prod\",\"server_host\":\"goma.chromium.org\"}" @@ -558,7 +580,7 @@ name: "win_toolchain" path: "win_toolchain" } - service_account: "tint-try-builder@chops-service-accounts.iam.gserviceaccount.com" + service_account: "dawn-try-builder@chops-service-accounts.iam.gserviceaccount.com" experiments { key: "luci.recipes.use_python3" value: 100 @@ -571,7 +593,7 @@ dimensions: "os:Windows-10" dimensions: "pool:luci.flex.try" recipe { - name: "tint" + name: "dawn" cipd_package: "infra/recipe_bundles/chromium.googlesource.com/chromium/tools/build" cipd_version: "refs/heads/master" properties_j: "$depot_tools/bot_update:{\"apply_patch_on_gclient\":true}" @@ -579,7 +601,7 @@ properties_j: "debug:true" properties_j: "target_cpu:\"x64\"" } - service_account: "tint-try-builder@chops-service-accounts.iam.gserviceaccount.com" + service_account: "dawn-try-builder@chops-service-accounts.iam.gserviceaccount.com" experiments { key: "luci.recipes.use_python3" value: 100 @@ -592,7 +614,7 @@ dimensions: "os:Windows-10" dimensions: "pool:luci.flex.try" recipe { - name: "tint" + name: "dawn" cipd_package: "infra/recipe_bundles/chromium.googlesource.com/chromium/tools/build" cipd_version: "refs/heads/master" properties_j: "$depot_tools/bot_update:{\"apply_patch_on_gclient\":true}" @@ -600,7 +622,7 @@ properties_j: "debug:false" properties_j: "target_cpu:\"x64\"" } - service_account: "tint-try-builder@chops-service-accounts.iam.gserviceaccount.com" + service_account: "dawn-try-builder@chops-service-accounts.iam.gserviceaccount.com" experiments { key: "luci.recipes.use_python3" value: 100
diff --git a/infra/config/global/generated/luci-milo.cfg b/infra/config/global/generated/luci-milo.cfg index 075a076..5770d7d 100644 --- a/infra/config/global/generated/luci-milo.cfg +++ b/infra/config/global/generated/luci-milo.cfg
@@ -6,112 +6,118 @@ consoles { id: "ci" - name: "Tint CI Builders" - repo_url: "https://dawn.googlesource.com/tint" + name: "Dawn CI Builders" + repo_url: "https://dawn.googlesource.com/dawn" refs: "regexp:refs/heads/main" manifest_name: "REVISION" builders { - name: "buildbucket/luci.tint.ci/linux-clang-dbg-x64" + name: "buildbucket/luci.dawn.ci/linux-clang-dbg-x64" category: "linux|clang|dbg" short_name: "x64" } builders { - name: "buildbucket/luci.tint.ci/linux-clang-rel-x64" - category: "linux|clang|rel" - short_name: "x64" - } - builders { - name: "buildbucket/luci.tint.ci/linux-clang-dbg-x86" + name: "buildbucket/luci.dawn.ci/linux-clang-dbg-x86" category: "linux|clang|dbg" short_name: "x86" } builders { - name: "buildbucket/luci.tint.ci/linux-clang-rel-x86" + name: "buildbucket/luci.dawn.ci/linux-clang-rel-x64" + category: "linux|clang|rel" + short_name: "x64" + } + builders { + name: "buildbucket/luci.dawn.ci/linux-clang-rel-x86" category: "linux|clang|rel" short_name: "x86" } builders { - name: "buildbucket/luci.tint.ci/mac-dbg" + name: "buildbucket/luci.dawn.ci/mac-dbg" category: "mac" short_name: "dbg" } builders { - name: "buildbucket/luci.tint.ci/mac-rel" + name: "buildbucket/luci.dawn.ci/mac-rel" category: "mac" short_name: "rel" } builders { - name: "buildbucket/luci.tint.ci/win-clang-dbg-x64" + name: "buildbucket/luci.dawn.ci/win-clang-dbg-x64" category: "win|clang|dbg" short_name: "x64" } builders { - name: "buildbucket/luci.tint.ci/win-clang-rel-x64" - category: "win|clang|rel" - short_name: "x64" - } - builders { - name: "buildbucket/luci.tint.ci/win-clang-dbg-x86" + name: "buildbucket/luci.dawn.ci/win-clang-dbg-x86" category: "win|clang|dbg" short_name: "x86" } builders { - name: "buildbucket/luci.tint.ci/win-clang-rel-x86" + name: "buildbucket/luci.dawn.ci/win-clang-rel-x64" + category: "win|clang|rel" + short_name: "x64" + } + builders { + name: "buildbucket/luci.dawn.ci/win-clang-rel-x86" category: "win|clang|rel" short_name: "x86" } builders { - name: "buildbucket/luci.tint.ci/win-msvc-dbg-x64" + name: "buildbucket/luci.dawn.ci/win-msvc-dbg-x64" category: "win|msvc" short_name: "dbg" } builders { - name: "buildbucket/luci.tint.ci/win-msvc-rel-x64" + name: "buildbucket/luci.dawn.ci/win-msvc-rel-x64" category: "win|msvc" short_name: "rel" } + builders { + name: "buildbucket/luci.dawn.ci/cron-linux-clang-rel-x64" + category: "cron|linux|clang|rel" + short_name: "x64" + } } consoles { id: "try" - name: "Tint try Builders" + name: "Dawn try Builders" builders { - name: "buildbucket/luci.tint.try/presubmit" + name: "buildbucket/luci.dawn.try/presubmit" } builders { - name: "buildbucket/luci.tint.try/linux-clang-dbg-x64" + name: "buildbucket/luci.dawn.try/linux-clang-dbg-x64" } builders { - name: "buildbucket/luci.tint.try/linux-clang-rel-x64" + name: "buildbucket/luci.dawn.try/linux-clang-dbg-x86" } builders { - name: "buildbucket/luci.tint.try/linux-clang-dbg-x86" + name: "buildbucket/luci.dawn.try/linux-clang-rel-x64" } builders { - name: "buildbucket/luci.tint.try/linux-clang-rel-x86" + name: "buildbucket/luci.dawn.try/linux-clang-rel-x86" } builders { - name: "buildbucket/luci.tint.try/mac-dbg" + name: "buildbucket/luci.dawn.try/mac-dbg" } builders { - name: "buildbucket/luci.tint.try/mac-rel" + name: "buildbucket/luci.dawn.try/mac-rel" } builders { - name: "buildbucket/luci.tint.try/win-clang-dbg-x64" + name: "buildbucket/luci.dawn.try/win-clang-dbg-x64" } builders { - name: "buildbucket/luci.tint.try/win-clang-rel-x64" + name: "buildbucket/luci.dawn.try/win-clang-dbg-x86" } builders { - name: "buildbucket/luci.tint.try/win-clang-dbg-x86" + name: "buildbucket/luci.dawn.try/win-clang-rel-x64" } builders { - name: "buildbucket/luci.tint.try/win-clang-rel-x86" + name: "buildbucket/luci.dawn.try/win-clang-rel-x86" } builders { - name: "buildbucket/luci.tint.try/win-msvc-dbg-x64" + name: "buildbucket/luci.dawn.try/win-msvc-dbg-x64" } builders { - name: "buildbucket/luci.tint.try/win-msvc-rel-x64" + name: "buildbucket/luci.dawn.try/win-msvc-rel-x64" } builder_view_only: true } +logo_url: "https://storage.googleapis.com/chrome-infra-public/logo/dawn-logo.png"
diff --git a/infra/config/global/generated/luci-scheduler.cfg b/infra/config/global/generated/luci-scheduler.cfg index ccdeef0..6774014 100644 --- a/infra/config/global/generated/luci-scheduler.cfg +++ b/infra/config/global/generated/luci-scheduler.cfg
@@ -5,6 +5,17 @@ # https://luci-config.appspot.com/schemas/projects:luci-scheduler.cfg job { + id: "cron-linux-clang-rel-x64" + realm: "ci" + schedule: "0 0 0 * * * *" + acl_sets: "ci" + buildbucket { + server: "cr-buildbucket.appspot.com" + bucket: "ci" + builder: "cron-linux-clang-rel-x64" + } +} +job { id: "linux-clang-dbg-x64" realm: "ci" acl_sets: "ci" @@ -141,7 +152,7 @@ triggers: "win-msvc-dbg-x64" triggers: "win-msvc-rel-x64" gitiles { - repo: "https://dawn.googlesource.com/tint" + repo: "https://dawn.googlesource.com/dawn" refs: "regexp:refs/heads/main" } } @@ -149,7 +160,7 @@ name: "ci" acls { role: OWNER - granted_to: "group:project-tint-admins" + granted_to: "group:project-dawn-admins" } acls { granted_to: "group:all"
diff --git a/infra/config/global/generated/project.cfg b/infra/config/global/generated/project.cfg index 59dd096..06a9172 100644 --- a/infra/config/global/generated/project.cfg +++ b/infra/config/global/generated/project.cfg
@@ -4,7 +4,7 @@ # For the schema of this file, see ProjectCfg message: # https://luci-config.appspot.com/schemas/projects:project.cfg -name: "tint" +name: "dawn" access: "group:all" lucicfg { version: "1.30.9"
diff --git a/infra/config/global/generated/realms.cfg b/infra/config/global/generated/realms.cfg index 4f4827d..94dd87b 100644 --- a/infra/config/global/generated/realms.cfg +++ b/infra/config/global/generated/realms.cfg
@@ -16,7 +16,7 @@ } bindings { role: "role/configs.validator" - principals: "user:tint-try-builder@chops-service-accounts.iam.gserviceaccount.com" + principals: "user:dawn-try-builder@chops-service-accounts.iam.gserviceaccount.com" } bindings { role: "role/logdog.reader" @@ -28,7 +28,7 @@ } bindings { role: "role/scheduler.owner" - principals: "group:project-tint-admins" + principals: "group:project-dawn-admins" } bindings { role: "role/scheduler.reader" @@ -39,22 +39,30 @@ name: "ci" bindings { role: "role/buildbucket.builderServiceAccount" - principals: "user:tint-ci-builder@chops-service-accounts.iam.gserviceaccount.com" + principals: "user:dawn-ci-builder@chops-service-accounts.iam.gserviceaccount.com" } bindings { role: "role/buildbucket.reader" principals: "group:all" } + bindings { + role: "role/swarming.taskTriggerer" + principals: "group:flex-ci-led-users" + } } realms { name: "try" bindings { role: "role/buildbucket.builderServiceAccount" - principals: "user:tint-try-builder@chops-service-accounts.iam.gserviceaccount.com" + principals: "user:dawn-try-builder@chops-service-accounts.iam.gserviceaccount.com" } bindings { role: "role/buildbucket.triggerer" - principals: "group:project-tint-tryjob-access" + principals: "group:project-dawn-tryjob-access" principals: "group:service-account-cq" } + bindings { + role: "role/swarming.taskTriggerer" + principals: "group:flex-try-led-users" + } }
diff --git a/infra/config/global/main.star b/infra/config/global/main.star index 3a0b597..7331e9a 100755 --- a/infra/config/global/main.star +++ b/infra/config/global/main.star
@@ -1,11 +1,11 @@ #!/usr/bin/env lucicfg # -# Copyright 2021 The Tint Authors. All rights reserved. +# Copyright 2021 The Dawn Authors. All rights reserved. # Use of this source code is governed by a BSD-style license that can be # found in the LICENSE file. # """ -main.star: lucicfg configuration for Tint's standalone builers. +main.star: lucicfg configuration for Dawn's standalone builers. """ # Use LUCI Scheduler BBv2 names and add Scheduler realms configs. @@ -18,7 +18,7 @@ lucicfg.config(fail_on_warnings = True) luci.project( - name = "tint", + name = "dawn", buildbucket = "cr-buildbucket.appspot.com", logdog = "luci-logdog.appspot.com", milo = "luci-milo.appspot.com", @@ -39,7 +39,7 @@ roles = [ acl.SCHEDULER_OWNER, ], - groups = "project-tint-admins", + groups = "project-dawn-admins", ), acl.entry( roles = [ @@ -51,7 +51,7 @@ bindings = [ luci.binding( roles = "role/configs.validator", - users = "tint-try-builder@chops-service-accounts.iam.gserviceaccount.com", + users = "dawn-try-builder@chops-service-accounts.iam.gserviceaccount.com", ), ], ) @@ -73,24 +73,39 @@ ], ) +# Allow LED users to trigger swarming tasks directly when debugging ci +# builders. +luci.binding( + realm = "ci", + roles = "role/swarming.taskTriggerer", + groups = "flex-ci-led-users", +) + luci.bucket( name = "try", acls = [ acl.entry( acl.BUILDBUCKET_TRIGGERER, groups = [ - "project-tint-tryjob-access", + "project-dawn-tryjob-access", "service-account-cq", ], ), ], ) +# Allow LED users to trigger swarming tasks directly when debugging try +# builders. +luci.binding( + realm = "try", + roles = "role/swarming.taskTriggerer", + groups = "flex-try-led-users", +) + os_category = struct( LINUX = "Linux", MAC = "Mac", WINDOWS = "Windows", - UNKNOWN = "Unknown", ) def os_enum(dimension, category, console_name): @@ -100,7 +115,6 @@ LINUX = os_enum("Ubuntu-18.04", os_category.LINUX, "linux"), MAC = os_enum("Mac-10.15|Mac-11", os_category.MAC, "mac"), WINDOWS = os_enum("Windows-10", os_category.WINDOWS, "win"), - UNKNOWN = os_enum("Unknown", os_category.UNKNOWN, "unknown"), ) # Recipes @@ -112,7 +126,19 @@ A luci.recipe """ return luci.recipe( - name = "tint", + name = "dawn", + cipd_package = "infra/recipe_bundles/chromium.googlesource.com/chromium/tools/build", + cipd_version = "refs/heads/master", + ) + +def get_presubmit_executable(): + """Get standard executable for presubmit + + Returns: + A luci.recipe + """ + return luci.recipe( + name = "run_presubmit", cipd_package = "infra/recipe_bundles/chromium.googlesource.com/chromium/tools/build", cipd_version = "refs/heads/master", ) @@ -128,13 +154,13 @@ """ - if arg.startswith("linux"): + if arg.find("linux") != -1: return os.LINUX - if arg.startswith("win"): + if arg.find("win") != -1: return os.WINDOWS - if arg.startswith("mac"): + if arg.find("mac") != -1: return os.MAC - return os.UNKNOWN + return os.MAC def get_default_caches(os, clang): """Get standard caches for builders @@ -162,7 +188,6 @@ Returns: A dimension dict - """ dimensions = {} @@ -172,7 +197,7 @@ return dimensions -def get_default_properties(os, clang, debug, cpu): +def get_default_properties(os, clang, debug, cpu, fuzzer): """Get the properties for a builder that don't depend on being CI vs Try Args: @@ -180,6 +205,7 @@ clang: is this builder running clang debug: is this builder generating debug builds cpu: string representing the target CPU architecture + fuzzer: is this builder running the fuzzers Returns: A properties dict @@ -192,7 +218,10 @@ properties["clang"] = clang msvc = os.category == os_category.WINDOWS and not clang - if msvc != True: + if fuzzer: + properties["gen_fuzz_corpus"] = True + + if not msvc: goma_props = {} goma_props.update({ "server_host": "goma.chromium.org", @@ -204,7 +233,7 @@ return properties -def add_ci_builder(name, os, clang, debug, cpu): +def add_ci_builder(name, os, clang, debug, cpu, fuzzer): """Add a CI builder Args: @@ -213,23 +242,30 @@ clang: is this builder running clang debug: is this builder generating debug builds cpu: string representing the target CPU architecture + fuzzer: is this builder running the fuzzers """ dimensions_ci = get_default_dimensions(os) dimensions_ci["pool"] = "luci.flex.ci" - properties_ci = get_default_properties(os, clang, debug, cpu) - triggered_by_ci = ["primary-poller"] + properties_ci = get_default_properties(os, clang, debug, cpu, fuzzer) + schedule_ci = None + if fuzzer: + schedule_ci = "0 0 0 * * * *" + triggered_by_ci = None + if not fuzzer: + triggered_by_ci = ["primary-poller"] luci.builder( name = name, bucket = "ci", + schedule = schedule_ci, triggered_by = triggered_by_ci, executable = get_builder_executable(), properties = properties_ci, dimensions = dimensions_ci, caches = get_default_caches(os, clang), - service_account = "tint-ci-builder@chops-service-accounts.iam.gserviceaccount.com", + service_account = "dawn-ci-builder@chops-service-accounts.iam.gserviceaccount.com", ) -def add_try_builder(name, os, clang, debug, cpu): +def add_try_builder(name, os, clang, debug, cpu, fuzzer): """Add a Try builder Args: @@ -238,10 +274,11 @@ clang: is this builder running clang debug: is this builder generating debug builds cpu: string representing the target CPU architecture + fuzzer: is this builder running the fuzzers """ dimensions_try = get_default_dimensions(os) dimensions_try["pool"] = "luci.flex.try" - properties_try = get_default_properties(os, clang, debug, cpu) + properties_try = get_default_properties(os, clang, debug, cpu, fuzzer) properties_try["$depot_tools/bot_update"] = { "apply_patch_on_gclient": True, } @@ -252,23 +289,25 @@ properties = properties_try, dimensions = dimensions_try, caches = get_default_caches(os, clang), - service_account = "tint-try-builder@chops-service-accounts.iam.gserviceaccount.com", + service_account = "dawn-try-builder@chops-service-accounts.iam.gserviceaccount.com", ) -def tint_standalone_builder(name, clang, debug, cpu): - """Adds both the CI and Try standalone builders +def dawn_standalone_builder(name, clang, debug, cpu, fuzzer = False): + """Adds both the CI and Try standalone builders as appropriate Args: name: builder's name in string form clang: is this builder running clang debug: is this builder generating debug builds cpu: string representing the target CPU architecture + fuzzer: enable building fuzzer corpus """ os = get_os_from_arg(name) - add_ci_builder(name, os, clang, debug, cpu) - add_try_builder(name, os, clang, debug, cpu) + add_ci_builder(name, os, clang, debug, cpu, fuzzer) + if not fuzzer: + add_try_builder(name, os, clang, debug, cpu, fuzzer) config = "" if clang: @@ -276,7 +315,10 @@ elif os.category == os_category.WINDOWS: config = "msvc" - category = os.console_name + category = "" + if fuzzer: + category += "cron|" + category += os.console_name if os.category != os_category.MAC: category += "|" + config @@ -295,20 +337,32 @@ short_name = short_name, ) - luci.list_view_entry( - list_view = "try", - builder = "try/" + name, - ) + if not fuzzer: + luci.list_view_entry( + list_view = "try", + builder = "try/" + name, + ) + luci.cq_tryjob_verifier( + cq_group = "Dawn-CQ", + builder = "dawn:try/" + name, + ) + +def chromium_dawn_tryjob(os): + """Adds a tryjob that tests against Chromium + + Args: + os: string for the OS, should be one or linux|mac|win + """ luci.cq_tryjob_verifier( - cq_group = "Tint-CQ", - builder = "tint:try/" + name, + cq_group = "Dawn-CQ", + builder = "chromium:try/" + os + "-dawn-rel", ) luci.gitiles_poller( name = "primary-poller", bucket = "ci", - repo = "https://dawn.googlesource.com/tint", + repo = "https://dawn.googlesource.com/dawn", refs = [ "refs/heads/main", ], @@ -322,52 +376,57 @@ luci.builder( name = "presubmit", bucket = "try", - executable = luci.recipe( - name = "run_presubmit", - cipd_package = "infra/recipe_bundles/chromium.googlesource.com/chromium/tools/build", - cipd_version = "refs/heads/master", - ), + executable = get_presubmit_executable(), dimensions = { "cpu": "x86-64", "os": os.LINUX.dimension, "pool": "luci.flex.try", }, properties = { - "repo_name": "tint", + "repo_name": "dawn", "runhooks": True, "$depot_tools/bot_update": { "apply_patch_on_gclient": True, }, }, - service_account = "tint-try-builder@chops-service-accounts.iam.gserviceaccount.com", + service_account = "dawn-try-builder@chops-service-accounts.iam.gserviceaccount.com", ) -# name, clang, debug, cpu -tint_standalone_builder("linux-clang-dbg-x64", True, True, "x64") -tint_standalone_builder("linux-clang-rel-x64", True, False, "x64") -tint_standalone_builder("linux-clang-dbg-x86", True, True, "x86") -tint_standalone_builder("linux-clang-rel-x86", True, False, "x86") -tint_standalone_builder("mac-dbg", True, True, "x64") -tint_standalone_builder("mac-rel", True, False, "x64") -tint_standalone_builder("win-clang-dbg-x64", True, True, "x64") -tint_standalone_builder("win-clang-rel-x64", True, False, "x64") -tint_standalone_builder("win-clang-dbg-x86", True, True, "x86") -tint_standalone_builder("win-clang-rel-x86", True, False, "x86") -tint_standalone_builder("win-msvc-dbg-x64", False, True, "x64") -tint_standalone_builder("win-msvc-rel-x64", False, False, "x64") +# name, clang, debug, cpu(, fuzzer) +dawn_standalone_builder("linux-clang-dbg-x64", True, True, "x64") +dawn_standalone_builder("linux-clang-dbg-x86", True, True, "x86") +dawn_standalone_builder("linux-clang-rel-x64", True, False, "x64") +dawn_standalone_builder("linux-clang-rel-x86", True, False, "x86") +dawn_standalone_builder("mac-dbg", True, True, "x64") +dawn_standalone_builder("mac-rel", True, False, "x64") +dawn_standalone_builder("win-clang-dbg-x64", True, True, "x64") +dawn_standalone_builder("win-clang-dbg-x86", True, True, "x86") +dawn_standalone_builder("win-clang-rel-x64", True, False, "x64") +dawn_standalone_builder("win-clang-rel-x86", True, False, "x86") +dawn_standalone_builder("win-msvc-dbg-x64", False, True, "x64") +dawn_standalone_builder("win-msvc-rel-x64", False, False, "x64") +dawn_standalone_builder("cron-linux-clang-rel-x64", True, False, "x64", True) + +chromium_dawn_tryjob("linux") +chromium_dawn_tryjob("mac") +chromium_dawn_tryjob("win") # Views +luci.milo( + logo = "https://storage.googleapis.com/chrome-infra-public/logo/dawn-logo.png", +) + luci.console_view( name = "ci", - title = "Tint CI Builders", - repo = "https://dawn.googlesource.com/tint", + title = "Dawn CI Builders", + repo = "https://dawn.googlesource.com/dawn", refs = ["refs/heads/main"], ) luci.list_view( name = "try", - title = "Tint try Builders", + title = "Dawn try Builders", ) # CQ @@ -379,24 +438,24 @@ ) luci.cq_group( - name = "Tint-CQ", + name = "Dawn-CQ", watch = cq.refset( - "https://dawn.googlesource.com/tint", + "https://dawn.googlesource.com/dawn", refs = ["refs/heads/.+"], ), acls = [ acl.entry( acl.CQ_COMMITTER, - groups = "project-tint-committers", + groups = "project-dawn-committers", ), acl.entry( acl.CQ_DRY_RUNNER, - groups = "project-tint-tryjobs-access", + groups = "project-dawn-tryjob-access", ), ], verifiers = [ luci.cq_tryjob_verifier( - builder = "tint:try/presubmit", + builder = "dawn:try/presubmit", disable_reuse = True, ), ],
diff --git a/samples/dawn/Animometer.cpp b/samples/dawn/Animometer.cpp new file mode 100644 index 0000000..9ac7cbe --- /dev/null +++ b/samples/dawn/Animometer.cpp
@@ -0,0 +1,192 @@ +// Copyright 2017 The Dawn Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include "SampleUtils.h" + +#include "dawn/utils/ComboRenderPipelineDescriptor.h" +#include "dawn/utils/ScopedAutoreleasePool.h" +#include "dawn/utils/SystemUtils.h" +#include "dawn/utils/WGPUHelpers.h" + +#include <cstdio> +#include <cstdlib> +#include <vector> + +wgpu::Device device; +wgpu::Queue queue; +wgpu::SwapChain swapchain; +wgpu::RenderPipeline pipeline; +wgpu::BindGroup bindGroup; +wgpu::Buffer ubo; + +float RandomFloat(float min, float max) { + float zeroOne = rand() / float(RAND_MAX); + return zeroOne * (max - min) + min; +} + +constexpr size_t kNumTriangles = 10000; + +// Aligned as minUniformBufferOffsetAlignment +struct alignas(256) ShaderData { + float scale; + float time; + float offsetX; + float offsetY; + float scalar; + float scalarOffset; +}; + +static std::vector<ShaderData> shaderData; + +void init() { + device = CreateCppDawnDevice(); + + queue = device.GetQueue(); + swapchain = GetSwapChain(device); + swapchain.Configure(GetPreferredSwapChainTextureFormat(), wgpu::TextureUsage::RenderAttachment, + 640, 480); + + wgpu::ShaderModule vsModule = utils::CreateShaderModule(device, R"( + struct Constants { + scale : f32; + time : f32; + offsetX : f32; + offsetY : f32; + scalar : f32; + scalarOffset : f32; + }; + @group(0) @binding(0) var<uniform> c : Constants; + + struct VertexOut { + @location(0) v_color : vec4<f32>; + @builtin(position) Position : vec4<f32>; + }; + + @stage(vertex) fn main(@builtin(vertex_index) VertexIndex : u32) -> VertexOut { + var positions : array<vec4<f32>, 3> = array<vec4<f32>, 3>( + vec4<f32>( 0.0, 0.1, 0.0, 1.0), + vec4<f32>(-0.1, -0.1, 0.0, 1.0), + vec4<f32>( 0.1, -0.1, 0.0, 1.0) + ); + + var colors : array<vec4<f32>, 3> = array<vec4<f32>, 3>( + vec4<f32>(1.0, 0.0, 0.0, 1.0), + vec4<f32>(0.0, 1.0, 0.0, 1.0), + vec4<f32>(0.0, 0.0, 1.0, 1.0) + ); + + var position : vec4<f32> = positions[VertexIndex]; + var color : vec4<f32> = colors[VertexIndex]; + + // TODO(dawn:572): Revisit once modf has been reworked in WGSL. + var fade : f32 = c.scalarOffset + c.time * c.scalar / 10.0; + fade = fade - floor(fade); + if (fade < 0.5) { + fade = fade * 2.0; + } else { + fade = (1.0 - fade) * 2.0; + } + + var xpos : f32 = position.x * c.scale; + var ypos : f32 = position.y * c.scale; + let angle : f32 = 3.14159 * 2.0 * fade; + let xrot : f32 = xpos * cos(angle) - ypos * sin(angle); + let yrot : f32 = xpos * sin(angle) + ypos * cos(angle); + xpos = xrot + c.offsetX; + ypos = yrot + c.offsetY; + + var output : VertexOut; + output.v_color = vec4<f32>(fade, 1.0 - fade, 0.0, 1.0) + color; + output.Position = vec4<f32>(xpos, ypos, 0.0, 1.0); + return output; + })"); + + wgpu::ShaderModule fsModule = utils::CreateShaderModule(device, R"( + @stage(fragment) fn main(@location(0) v_color : vec4<f32>) -> @location(0) vec4<f32> { + return v_color; + })"); + + wgpu::BindGroupLayout bgl = utils::MakeBindGroupLayout( + device, {{0, wgpu::ShaderStage::Vertex, wgpu::BufferBindingType::Uniform, true}}); + + utils::ComboRenderPipelineDescriptor descriptor; + descriptor.layout = utils::MakeBasicPipelineLayout(device, &bgl); + descriptor.vertex.module = vsModule; + descriptor.cFragment.module = fsModule; + descriptor.cTargets[0].format = GetPreferredSwapChainTextureFormat(); + + pipeline = device.CreateRenderPipeline(&descriptor); + + shaderData.resize(kNumTriangles); + for (auto& data : shaderData) { + data.scale = RandomFloat(0.2f, 0.4f); + data.time = 0.0; + data.offsetX = RandomFloat(-0.9f, 0.9f); + data.offsetY = RandomFloat(-0.9f, 0.9f); + data.scalar = RandomFloat(0.5f, 2.0f); + data.scalarOffset = RandomFloat(0.0f, 10.0f); + } + + wgpu::BufferDescriptor bufferDesc; + bufferDesc.size = kNumTriangles * sizeof(ShaderData); + bufferDesc.usage = wgpu::BufferUsage::CopyDst | wgpu::BufferUsage::Uniform; + ubo = device.CreateBuffer(&bufferDesc); + + bindGroup = utils::MakeBindGroup(device, bgl, {{0, ubo, 0, sizeof(ShaderData)}}); +} + +void frame() { + wgpu::TextureView backbufferView = swapchain.GetCurrentTextureView(); + + static int f = 0; + f++; + for (auto& data : shaderData) { + data.time = f / 60.0f; + } + queue.WriteBuffer(ubo, 0, shaderData.data(), kNumTriangles * sizeof(ShaderData)); + + utils::ComboRenderPassDescriptor renderPass({backbufferView}); + wgpu::CommandEncoder encoder = device.CreateCommandEncoder(); + { + wgpu::RenderPassEncoder pass = encoder.BeginRenderPass(&renderPass); + pass.SetPipeline(pipeline); + + for (size_t i = 0; i < kNumTriangles; i++) { + uint32_t offset = i * sizeof(ShaderData); + pass.SetBindGroup(0, bindGroup, 1, &offset); + pass.Draw(3); + } + + pass.End(); + } + + wgpu::CommandBuffer commands = encoder.Finish(); + queue.Submit(1, &commands); + swapchain.Present(); + DoFlush(); + fprintf(stderr, "frame %i\n", f); +} + +int main(int argc, const char* argv[]) { + if (!InitSample(argc, argv)) { + return 1; + } + init(); + + while (!ShouldQuit()) { + utils::ScopedAutoreleasePool pool; + frame(); + utils::USleep(16000); + } +}
diff --git a/samples/dawn/BUILD.gn b/samples/dawn/BUILD.gn new file mode 100644 index 0000000..c7e04a5 --- /dev/null +++ b/samples/dawn/BUILD.gn
@@ -0,0 +1,77 @@ +# Copyright 2020 The Dawn Authors +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +import("../../scripts/dawn_overrides_with_defaults.gni") + +group("samples") { + deps = [ + ":Animometer", + ":CHelloTriangle", + ":ComputeBoids", + ":CppHelloTriangle", + ":ManualSwapChainTest", + ] +} + +# Static library to contain code and dependencies common to all samples +static_library("utils") { + sources = [ + "SampleUtils.cpp", + "SampleUtils.h", + ] + + # Export all of these as public deps so that `gn check` allows includes + public_deps = [ + "${dawn_root}/src/dawn:cpp", + "${dawn_root}/src/dawn/common", + "${dawn_root}/src/dawn/native", + "${dawn_root}/src/dawn/utils", + "${dawn_root}/src/dawn/utils:bindings", + "${dawn_root}/src/dawn/utils:glfw", + "${dawn_root}/src/dawn/wire", + ] + public_configs = [ "${dawn_root}/src/dawn/common:internal_config" ] +} + +# Template for samples to avoid listing utils as a dep every time +template("sample") { + executable(target_name) { + deps = [ ":utils" ] + forward_variables_from(invoker, "*", [ "deps" ]) + + if (defined(invoker.deps)) { + deps += invoker.deps + } + } +} + +sample("CppHelloTriangle") { + sources = [ "CppHelloTriangle.cpp" ] +} + +sample("CHelloTriangle") { + sources = [ "CHelloTriangle.cpp" ] +} + +sample("ComputeBoids") { + sources = [ "ComputeBoids.cpp" ] +} + +sample("Animometer") { + sources = [ "Animometer.cpp" ] +} + +sample("ManualSwapChainTest") { + sources = [ "ManualSwapChainTest.cpp" ] +}
diff --git a/samples/dawn/CHelloTriangle.cpp b/samples/dawn/CHelloTriangle.cpp new file mode 100644 index 0000000..1f7e374 --- /dev/null +++ b/samples/dawn/CHelloTriangle.cpp
@@ -0,0 +1,155 @@ +// Copyright 2017 The Dawn Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include "SampleUtils.h" + +#include "dawn/utils/ScopedAutoreleasePool.h" +#include "dawn/utils/SystemUtils.h" +#include "dawn/utils/WGPUHelpers.h" + +WGPUDevice device; +WGPUQueue queue; +WGPUSwapChain swapchain; +WGPURenderPipeline pipeline; + +WGPUTextureFormat swapChainFormat; + +void init() { + device = CreateCppDawnDevice().Release(); + queue = wgpuDeviceGetQueue(device); + + { + WGPUSwapChainDescriptor descriptor = {}; + descriptor.implementation = GetSwapChainImplementation(); + swapchain = wgpuDeviceCreateSwapChain(device, nullptr, &descriptor); + } + swapChainFormat = static_cast<WGPUTextureFormat>(GetPreferredSwapChainTextureFormat()); + wgpuSwapChainConfigure(swapchain, swapChainFormat, WGPUTextureUsage_RenderAttachment, 640, 480); + + const char* vs = R"( + @stage(vertex) fn main( + @builtin(vertex_index) VertexIndex : u32 + ) -> @builtin(position) vec4<f32> { + var pos = array<vec2<f32>, 3>( + vec2<f32>( 0.0, 0.5), + vec2<f32>(-0.5, -0.5), + vec2<f32>( 0.5, -0.5) + ); + return vec4<f32>(pos[VertexIndex], 0.0, 1.0); + })"; + WGPUShaderModule vsModule = utils::CreateShaderModule(device, vs).Release(); + + const char* fs = R"( + @stage(fragment) fn main() -> @location(0) vec4<f32> { + return vec4<f32>(1.0, 0.0, 0.0, 1.0); + })"; + WGPUShaderModule fsModule = utils::CreateShaderModule(device, fs).Release(); + + { + WGPURenderPipelineDescriptor descriptor = {}; + + // Fragment state + WGPUBlendState blend = {}; + blend.color.operation = WGPUBlendOperation_Add; + blend.color.srcFactor = WGPUBlendFactor_One; + blend.color.dstFactor = WGPUBlendFactor_One; + blend.alpha.operation = WGPUBlendOperation_Add; + blend.alpha.srcFactor = WGPUBlendFactor_One; + blend.alpha.dstFactor = WGPUBlendFactor_One; + + WGPUColorTargetState colorTarget = {}; + colorTarget.format = swapChainFormat; + colorTarget.blend = &blend; + colorTarget.writeMask = WGPUColorWriteMask_All; + + WGPUFragmentState fragment = {}; + fragment.module = fsModule; + fragment.entryPoint = "main"; + fragment.targetCount = 1; + fragment.targets = &colorTarget; + descriptor.fragment = &fragment; + + // Other state + descriptor.layout = nullptr; + descriptor.depthStencil = nullptr; + + descriptor.vertex.module = vsModule; + descriptor.vertex.entryPoint = "main"; + descriptor.vertex.bufferCount = 0; + descriptor.vertex.buffers = nullptr; + + descriptor.multisample.count = 1; + descriptor.multisample.mask = 0xFFFFFFFF; + descriptor.multisample.alphaToCoverageEnabled = false; + + descriptor.primitive.frontFace = WGPUFrontFace_CCW; + descriptor.primitive.cullMode = WGPUCullMode_None; + descriptor.primitive.topology = WGPUPrimitiveTopology_TriangleList; + descriptor.primitive.stripIndexFormat = WGPUIndexFormat_Undefined; + + pipeline = wgpuDeviceCreateRenderPipeline(device, &descriptor); + } + + wgpuShaderModuleRelease(vsModule); + wgpuShaderModuleRelease(fsModule); +} + +void frame() { + WGPUTextureView backbufferView = wgpuSwapChainGetCurrentTextureView(swapchain); + WGPURenderPassDescriptor renderpassInfo = {}; + WGPURenderPassColorAttachment colorAttachment = {}; + { + colorAttachment.view = backbufferView; + colorAttachment.resolveTarget = nullptr; + colorAttachment.clearValue = {0.0f, 0.0f, 0.0f, 0.0f}; + colorAttachment.loadOp = WGPULoadOp_Clear; + colorAttachment.storeOp = WGPUStoreOp_Store; + renderpassInfo.colorAttachmentCount = 1; + renderpassInfo.colorAttachments = &colorAttachment; + renderpassInfo.depthStencilAttachment = nullptr; + } + WGPUCommandBuffer commands; + { + WGPUCommandEncoder encoder = wgpuDeviceCreateCommandEncoder(device, nullptr); + + WGPURenderPassEncoder pass = wgpuCommandEncoderBeginRenderPass(encoder, &renderpassInfo); + wgpuRenderPassEncoderSetPipeline(pass, pipeline); + wgpuRenderPassEncoderDraw(pass, 3, 1, 0, 0); + wgpuRenderPassEncoderEnd(pass); + wgpuRenderPassEncoderRelease(pass); + + commands = wgpuCommandEncoderFinish(encoder, nullptr); + wgpuCommandEncoderRelease(encoder); + } + + wgpuQueueSubmit(queue, 1, &commands); + wgpuCommandBufferRelease(commands); + wgpuSwapChainPresent(swapchain); + wgpuTextureViewRelease(backbufferView); + + DoFlush(); +} + +int main(int argc, const char* argv[]) { + if (!InitSample(argc, argv)) { + return 1; + } + init(); + + while (!ShouldQuit()) { + utils::ScopedAutoreleasePool pool; + frame(); + utils::USleep(16000); + } +}
diff --git a/samples/dawn/CMakeLists.txt b/samples/dawn/CMakeLists.txt new file mode 100644 index 0000000..3fc9ec9 --- /dev/null +++ b/samples/dawn/CMakeLists.txt
@@ -0,0 +1,41 @@ +# Copyright 2020 The Dawn Authors +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +add_library(dawn_sample_utils STATIC ${DAWN_DUMMY_FILE}) +target_sources(dawn_sample_utils PRIVATE + "SampleUtils.cpp" + "SampleUtils.h" +) +target_link_libraries(dawn_sample_utils PUBLIC + dawn_internal_config + dawncpp + dawn_proc + dawn_common + dawn_native + dawn_wire + dawn_utils + glfw +) + +add_executable(CppHelloTriangle "CppHelloTriangle.cpp") +target_link_libraries(CppHelloTriangle dawn_sample_utils) + +add_executable(CHelloTriangle "CHelloTriangle.cpp") +target_link_libraries(CHelloTriangle dawn_sample_utils) + +add_executable(ComputeBoids "ComputeBoids.cpp") +target_link_libraries(ComputeBoids dawn_sample_utils) + +add_executable(Animometer "Animometer.cpp") +target_link_libraries(Animometer dawn_sample_utils)
diff --git a/samples/dawn/ComputeBoids.cpp b/samples/dawn/ComputeBoids.cpp new file mode 100644 index 0000000..f8c3764 --- /dev/null +++ b/samples/dawn/ComputeBoids.cpp
@@ -0,0 +1,330 @@ +// Copyright 2017 The Dawn Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include "SampleUtils.h" + +#include "dawn/utils/ComboRenderPipelineDescriptor.h" +#include "dawn/utils/ScopedAutoreleasePool.h" +#include "dawn/utils/SystemUtils.h" +#include "dawn/utils/WGPUHelpers.h" + +#include <array> +#include <cstring> +#include <random> + +wgpu::Device device; +wgpu::Queue queue; +wgpu::SwapChain swapchain; +wgpu::TextureView depthStencilView; + +wgpu::Buffer modelBuffer; +std::array<wgpu::Buffer, 2> particleBuffers; + +wgpu::RenderPipeline renderPipeline; + +wgpu::Buffer updateParams; +wgpu::ComputePipeline updatePipeline; +std::array<wgpu::BindGroup, 2> updateBGs; + +size_t pingpong = 0; + +static const uint32_t kNumParticles = 1000; + +struct Particle { + std::array<float, 2> pos; + std::array<float, 2> vel; +}; + +struct SimParams { + float deltaT; + float rule1Distance; + float rule2Distance; + float rule3Distance; + float rule1Scale; + float rule2Scale; + float rule3Scale; + int particleCount; +}; + +void initBuffers() { + std::array<std::array<float, 2>, 3> model = {{ + {-0.01, -0.02}, + {0.01, -0.02}, + {0.00, 0.02}, + }}; + modelBuffer = + utils::CreateBufferFromData(device, &model, sizeof(model), wgpu::BufferUsage::Vertex); + + SimParams params = {0.04f, 0.1f, 0.025f, 0.025f, 0.02f, 0.05f, 0.005f, kNumParticles}; + updateParams = + utils::CreateBufferFromData(device, ¶ms, sizeof(params), wgpu::BufferUsage::Uniform); + + std::vector<Particle> initialParticles(kNumParticles); + { + std::mt19937 generator; + std::uniform_real_distribution<float> dist(-1.0f, 1.0f); + for (auto& p : initialParticles) { + p.pos = {dist(generator), dist(generator)}; + p.vel = {dist(generator) * 0.1f, dist(generator) * 0.1f}; + } + } + + for (size_t i = 0; i < 2; i++) { + wgpu::BufferDescriptor descriptor; + descriptor.size = sizeof(Particle) * kNumParticles; + descriptor.usage = + wgpu::BufferUsage::CopyDst | wgpu::BufferUsage::Vertex | wgpu::BufferUsage::Storage; + particleBuffers[i] = device.CreateBuffer(&descriptor); + + queue.WriteBuffer(particleBuffers[i], 0, + reinterpret_cast<uint8_t*>(initialParticles.data()), + sizeof(Particle) * kNumParticles); + } +} + +void initRender() { + wgpu::ShaderModule vsModule = utils::CreateShaderModule(device, R"( + struct VertexIn { + @location(0) a_particlePos : vec2<f32>; + @location(1) a_particleVel : vec2<f32>; + @location(2) a_pos : vec2<f32>; + }; + + @stage(vertex) + fn main(input : VertexIn) -> @builtin(position) vec4<f32> { + var angle : f32 = -atan2(input.a_particleVel.x, input.a_particleVel.y); + var pos : vec2<f32> = vec2<f32>( + (input.a_pos.x * cos(angle)) - (input.a_pos.y * sin(angle)), + (input.a_pos.x * sin(angle)) + (input.a_pos.y * cos(angle))); + return vec4<f32>(pos + input.a_particlePos, 0.0, 1.0); + } + )"); + + wgpu::ShaderModule fsModule = utils::CreateShaderModule(device, R"( + @stage(fragment) + fn main() -> @location(0) vec4<f32> { + return vec4<f32>(1.0, 1.0, 1.0, 1.0); + } + )"); + + depthStencilView = CreateDefaultDepthStencilView(device); + + utils::ComboRenderPipelineDescriptor descriptor; + + descriptor.vertex.module = vsModule; + descriptor.vertex.bufferCount = 2; + descriptor.cBuffers[0].arrayStride = sizeof(Particle); + descriptor.cBuffers[0].stepMode = wgpu::VertexStepMode::Instance; + descriptor.cBuffers[0].attributeCount = 2; + descriptor.cAttributes[0].offset = offsetof(Particle, pos); + descriptor.cAttributes[0].format = wgpu::VertexFormat::Float32x2; + descriptor.cAttributes[1].shaderLocation = 1; + descriptor.cAttributes[1].offset = offsetof(Particle, vel); + descriptor.cAttributes[1].format = wgpu::VertexFormat::Float32x2; + descriptor.cBuffers[1].arrayStride = 2 * sizeof(float); + descriptor.cBuffers[1].attributeCount = 1; + descriptor.cBuffers[1].attributes = &descriptor.cAttributes[2]; + descriptor.cAttributes[2].shaderLocation = 2; + descriptor.cAttributes[2].format = wgpu::VertexFormat::Float32x2; + + descriptor.cFragment.module = fsModule; + descriptor.EnableDepthStencil(wgpu::TextureFormat::Depth24PlusStencil8); + descriptor.cTargets[0].format = GetPreferredSwapChainTextureFormat(); + + renderPipeline = device.CreateRenderPipeline(&descriptor); +} + +void initSim() { + wgpu::ShaderModule module = utils::CreateShaderModule(device, R"( + struct Particle { + pos : vec2<f32>; + vel : vec2<f32>; + }; + struct SimParams { + deltaT : f32; + rule1Distance : f32; + rule2Distance : f32; + rule3Distance : f32; + rule1Scale : f32; + rule2Scale : f32; + rule3Scale : f32; + particleCount : u32; + }; + struct Particles { + particles : array<Particle>; + }; + @binding(0) @group(0) var<uniform> params : SimParams; + @binding(1) @group(0) var<storage, read_write> particlesA : Particles; + @binding(2) @group(0) var<storage, read_write> particlesB : Particles; + + // https://github.com/austinEng/Project6-Vulkan-Flocking/blob/master/data/shaders/computeparticles/particle.comp + @stage(compute) @workgroup_size(1) + fn main(@builtin(global_invocation_id) GlobalInvocationID : vec3<u32>) { + var index : u32 = GlobalInvocationID.x; + if (index >= params.particleCount) { + return; + } + var vPos : vec2<f32> = particlesA.particles[index].pos; + var vVel : vec2<f32> = particlesA.particles[index].vel; + var cMass : vec2<f32> = vec2<f32>(0.0, 0.0); + var cVel : vec2<f32> = vec2<f32>(0.0, 0.0); + var colVel : vec2<f32> = vec2<f32>(0.0, 0.0); + var cMassCount : u32 = 0u; + var cVelCount : u32 = 0u; + var pos : vec2<f32>; + var vel : vec2<f32>; + + for (var i : u32 = 0u; i < params.particleCount; i = i + 1u) { + if (i == index) { + continue; + } + + pos = particlesA.particles[i].pos.xy; + vel = particlesA.particles[i].vel.xy; + if (distance(pos, vPos) < params.rule1Distance) { + cMass = cMass + pos; + cMassCount = cMassCount + 1u; + } + if (distance(pos, vPos) < params.rule2Distance) { + colVel = colVel - (pos - vPos); + } + if (distance(pos, vPos) < params.rule3Distance) { + cVel = cVel + vel; + cVelCount = cVelCount + 1u; + } + } + + if (cMassCount > 0u) { + cMass = (cMass / vec2<f32>(f32(cMassCount), f32(cMassCount))) - vPos; + } + + if (cVelCount > 0u) { + cVel = cVel / vec2<f32>(f32(cVelCount), f32(cVelCount)); + } + vVel = vVel + (cMass * params.rule1Scale) + (colVel * params.rule2Scale) + + (cVel * params.rule3Scale); + + // clamp velocity for a more pleasing simulation + vVel = normalize(vVel) * clamp(length(vVel), 0.0, 0.1); + // kinematic update + vPos = vPos + (vVel * params.deltaT); + + // Wrap around boundary + if (vPos.x < -1.0) { + vPos.x = 1.0; + } + if (vPos.x > 1.0) { + vPos.x = -1.0; + } + if (vPos.y < -1.0) { + vPos.y = 1.0; + } + if (vPos.y > 1.0) { + vPos.y = -1.0; + } + + // Write back + particlesB.particles[index].pos = vPos; + particlesB.particles[index].vel = vVel; + return; + } + )"); + + auto bgl = utils::MakeBindGroupLayout( + device, { + {0, wgpu::ShaderStage::Compute, wgpu::BufferBindingType::Uniform}, + {1, wgpu::ShaderStage::Compute, wgpu::BufferBindingType::Storage}, + {2, wgpu::ShaderStage::Compute, wgpu::BufferBindingType::Storage}, + }); + + wgpu::PipelineLayout pl = utils::MakeBasicPipelineLayout(device, &bgl); + + wgpu::ComputePipelineDescriptor csDesc; + csDesc.layout = pl; + csDesc.compute.module = module; + csDesc.compute.entryPoint = "main"; + updatePipeline = device.CreateComputePipeline(&csDesc); + + for (uint32_t i = 0; i < 2; ++i) { + updateBGs[i] = utils::MakeBindGroup( + device, bgl, + { + {0, updateParams, 0, sizeof(SimParams)}, + {1, particleBuffers[i], 0, kNumParticles * sizeof(Particle)}, + {2, particleBuffers[(i + 1) % 2], 0, kNumParticles * sizeof(Particle)}, + }); + } +} + +wgpu::CommandBuffer createCommandBuffer(const wgpu::TextureView backbufferView, size_t i) { + auto& bufferDst = particleBuffers[(i + 1) % 2]; + wgpu::CommandEncoder encoder = device.CreateCommandEncoder(); + + { + wgpu::ComputePassEncoder pass = encoder.BeginComputePass(); + pass.SetPipeline(updatePipeline); + pass.SetBindGroup(0, updateBGs[i]); + pass.Dispatch(kNumParticles); + pass.End(); + } + + { + utils::ComboRenderPassDescriptor renderPass({backbufferView}, depthStencilView); + wgpu::RenderPassEncoder pass = encoder.BeginRenderPass(&renderPass); + pass.SetPipeline(renderPipeline); + pass.SetVertexBuffer(0, bufferDst); + pass.SetVertexBuffer(1, modelBuffer); + pass.Draw(3, kNumParticles); + pass.End(); + } + + return encoder.Finish(); +} + +void init() { + device = CreateCppDawnDevice(); + + queue = device.GetQueue(); + swapchain = GetSwapChain(device); + swapchain.Configure(GetPreferredSwapChainTextureFormat(), wgpu::TextureUsage::RenderAttachment, + 640, 480); + + initBuffers(); + initRender(); + initSim(); +} + +void frame() { + wgpu::TextureView backbufferView = swapchain.GetCurrentTextureView(); + + wgpu::CommandBuffer commandBuffer = createCommandBuffer(backbufferView, pingpong); + queue.Submit(1, &commandBuffer); + swapchain.Present(); + DoFlush(); + + pingpong = (pingpong + 1) % 2; +} + +int main(int argc, const char* argv[]) { + if (!InitSample(argc, argv)) { + return 1; + } + init(); + + while (!ShouldQuit()) { + utils::ScopedAutoreleasePool pool; + frame(); + utils::USleep(16000); + } +}
diff --git a/samples/dawn/CppHelloTriangle.cpp b/samples/dawn/CppHelloTriangle.cpp new file mode 100644 index 0000000..14b4f55 --- /dev/null +++ b/samples/dawn/CppHelloTriangle.cpp
@@ -0,0 +1,184 @@ +// Copyright 2017 The Dawn Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include "SampleUtils.h" + +#include "dawn/utils/ComboRenderPipelineDescriptor.h" +#include "dawn/utils/ScopedAutoreleasePool.h" +#include "dawn/utils/SystemUtils.h" +#include "dawn/utils/WGPUHelpers.h" + +#include <vector> + +wgpu::Device device; + +wgpu::Buffer indexBuffer; +wgpu::Buffer vertexBuffer; + +wgpu::Texture texture; +wgpu::Sampler sampler; + +wgpu::Queue queue; +wgpu::SwapChain swapchain; +wgpu::TextureView depthStencilView; +wgpu::RenderPipeline pipeline; +wgpu::BindGroup bindGroup; + +void initBuffers() { + static const uint32_t indexData[3] = { + 0, + 1, + 2, + }; + indexBuffer = + utils::CreateBufferFromData(device, indexData, sizeof(indexData), wgpu::BufferUsage::Index); + + static const float vertexData[12] = { + 0.0f, 0.5f, 0.0f, 1.0f, -0.5f, -0.5f, 0.0f, 1.0f, 0.5f, -0.5f, 0.0f, 1.0f, + }; + vertexBuffer = utils::CreateBufferFromData(device, vertexData, sizeof(vertexData), + wgpu::BufferUsage::Vertex); +} + +void initTextures() { + wgpu::TextureDescriptor descriptor; + descriptor.dimension = wgpu::TextureDimension::e2D; + descriptor.size.width = 1024; + descriptor.size.height = 1024; + descriptor.size.depthOrArrayLayers = 1; + descriptor.sampleCount = 1; + descriptor.format = wgpu::TextureFormat::RGBA8Unorm; + descriptor.mipLevelCount = 1; + descriptor.usage = wgpu::TextureUsage::CopyDst | wgpu::TextureUsage::TextureBinding; + texture = device.CreateTexture(&descriptor); + + sampler = device.CreateSampler(); + + // Initialize the texture with arbitrary data until we can load images + std::vector<uint8_t> data(4 * 1024 * 1024, 0); + for (size_t i = 0; i < data.size(); ++i) { + data[i] = static_cast<uint8_t>(i % 253); + } + + wgpu::Buffer stagingBuffer = utils::CreateBufferFromData( + device, data.data(), static_cast<uint32_t>(data.size()), wgpu::BufferUsage::CopySrc); + wgpu::ImageCopyBuffer imageCopyBuffer = + utils::CreateImageCopyBuffer(stagingBuffer, 0, 4 * 1024); + wgpu::ImageCopyTexture imageCopyTexture = utils::CreateImageCopyTexture(texture, 0, {0, 0, 0}); + wgpu::Extent3D copySize = {1024, 1024, 1}; + + wgpu::CommandEncoder encoder = device.CreateCommandEncoder(); + encoder.CopyBufferToTexture(&imageCopyBuffer, &imageCopyTexture, ©Size); + + wgpu::CommandBuffer copy = encoder.Finish(); + queue.Submit(1, ©); +} + +void init() { + device = CreateCppDawnDevice(); + + queue = device.GetQueue(); + swapchain = GetSwapChain(device); + swapchain.Configure(GetPreferredSwapChainTextureFormat(), wgpu::TextureUsage::RenderAttachment, + 640, 480); + + initBuffers(); + initTextures(); + + wgpu::ShaderModule vsModule = utils::CreateShaderModule(device, R"( + @stage(vertex) fn main(@location(0) pos : vec4<f32>) + -> @builtin(position) vec4<f32> { + return pos; + })"); + + wgpu::ShaderModule fsModule = utils::CreateShaderModule(device, R"( + @group(0) @binding(0) var mySampler: sampler; + @group(0) @binding(1) var myTexture : texture_2d<f32>; + + @stage(fragment) fn main(@builtin(position) FragCoord : vec4<f32>) + -> @location(0) vec4<f32> { + return textureSample(myTexture, mySampler, FragCoord.xy / vec2<f32>(640.0, 480.0)); + })"); + + auto bgl = utils::MakeBindGroupLayout( + device, { + {0, wgpu::ShaderStage::Fragment, wgpu::SamplerBindingType::Filtering}, + {1, wgpu::ShaderStage::Fragment, wgpu::TextureSampleType::Float}, + }); + + wgpu::PipelineLayout pl = utils::MakeBasicPipelineLayout(device, &bgl); + + depthStencilView = CreateDefaultDepthStencilView(device); + + utils::ComboRenderPipelineDescriptor descriptor; + descriptor.layout = utils::MakeBasicPipelineLayout(device, &bgl); + descriptor.vertex.module = vsModule; + descriptor.vertex.bufferCount = 1; + descriptor.cBuffers[0].arrayStride = 4 * sizeof(float); + descriptor.cBuffers[0].attributeCount = 1; + descriptor.cAttributes[0].format = wgpu::VertexFormat::Float32x4; + descriptor.cFragment.module = fsModule; + descriptor.cTargets[0].format = GetPreferredSwapChainTextureFormat(); + descriptor.EnableDepthStencil(wgpu::TextureFormat::Depth24PlusStencil8); + + pipeline = device.CreateRenderPipeline(&descriptor); + + wgpu::TextureView view = texture.CreateView(); + + bindGroup = utils::MakeBindGroup(device, bgl, {{0, sampler}, {1, view}}); +} + +struct { + uint32_t a; + float b; +} s; +void frame() { + s.a = (s.a + 1) % 256; + s.b += 0.02f; + if (s.b >= 1.0f) { + s.b = 0.0f; + } + + wgpu::TextureView backbufferView = swapchain.GetCurrentTextureView(); + utils::ComboRenderPassDescriptor renderPass({backbufferView}, depthStencilView); + + wgpu::CommandEncoder encoder = device.CreateCommandEncoder(); + { + wgpu::RenderPassEncoder pass = encoder.BeginRenderPass(&renderPass); + pass.SetPipeline(pipeline); + pass.SetBindGroup(0, bindGroup); + pass.SetVertexBuffer(0, vertexBuffer); + pass.SetIndexBuffer(indexBuffer, wgpu::IndexFormat::Uint32); + pass.DrawIndexed(3); + pass.End(); + } + + wgpu::CommandBuffer commands = encoder.Finish(); + queue.Submit(1, &commands); + swapchain.Present(); + DoFlush(); +} + +int main(int argc, const char* argv[]) { + if (!InitSample(argc, argv)) { + return 1; + } + init(); + + while (!ShouldQuit()) { + utils::ScopedAutoreleasePool pool; + frame(); + utils::USleep(16000); + } +}
diff --git a/samples/dawn/ManualSwapChainTest.cpp b/samples/dawn/ManualSwapChainTest.cpp new file mode 100644 index 0000000..9c6e757 --- /dev/null +++ b/samples/dawn/ManualSwapChainTest.cpp
@@ -0,0 +1,364 @@ +// Copyright 2020 The Dawn Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +// This is an example to manually test swapchain code. Controls are the following, scoped to the +// currently focused window: +// - W: creates a new window. +// - L: Latches the current swapchain, to check what happens when the window changes but not the +// swapchain. +// - R: switches the rendering mode, between "The Red Triangle" and color-cycling clears that's +// (WARNING) likely seizure inducing. +// - D: cycles the divisor for the swapchain size. +// - P: switches present modes. +// +// Closing all the windows exits the example. ^C also works. +// +// Things to test manually: +// +// - Basic tests (with the triangle render mode): +// - Check the triangle is red on a black background and with the pointy side up. +// - Cycle render modes a bunch and check that the triangle background is always solid black. +// - Check that rendering triangles to multiple windows works. +// +// - Present mode single-window tests (with cycling color render mode): +// - Check that Fifo cycles at about 1 cycle per second and has no tearing. +// - Check that Mailbox cycles faster than Fifo and has no tearing. +// - Check that Immediate cycles faster than Fifo, it is allowed to have tearing. (dragging +// between two monitors can help see tearing) +// +// - Present mode multi-window tests, it should have the same results as single-window tests when +// all windows are in the same present mode. In mixed present modes only Immediate windows are +// allowed to tear. +// +// - Resizing tests (with the triangle render mode): +// - Check that cycling divisors on the triangle produces lower and lower resolution triangles. +// - Check latching the swapchain config and resizing the window a bunch (smaller, bigger, and +// diagonal aspect ratio). +// +// - Config change tests: +// - Check that cycling between present modes works. +// - TODO can't be tested yet: check cycling the same window over multiple devices. +// - TODO can't be tested yet: check cycling the same window over multiple formats. + +#include "dawn/common/Assert.h" +#include "dawn/common/Log.h" +#include "dawn/utils/ComboRenderPipelineDescriptor.h" +#include "dawn/utils/GLFWUtils.h" +#include "dawn/utils/ScopedAutoreleasePool.h" +#include "dawn/utils/WGPUHelpers.h" + +#include <dawn/dawn_proc.h> +#include <dawn/native/DawnNative.h> +#include <dawn/webgpu_cpp.h> +#include "GLFW/glfw3.h" + +#include <memory> +#include <unordered_map> + +struct WindowData { + GLFWwindow* window = nullptr; + uint64_t serial = 0; + + float clearCycle = 1.0f; + bool latched = false; + bool renderTriangle = true; + uint32_t divisor = 1; + + wgpu::Surface surface = nullptr; + wgpu::SwapChain swapchain = nullptr; + + wgpu::SwapChainDescriptor currentDesc; + wgpu::SwapChainDescriptor targetDesc; +}; + +static std::unordered_map<GLFWwindow*, std::unique_ptr<WindowData>> windows; +static uint64_t windowSerial = 0; + +static std::unique_ptr<dawn::native::Instance> instance; +static wgpu::Device device; +static wgpu::Queue queue; +static wgpu::RenderPipeline trianglePipeline; + +bool IsSameDescriptor(const wgpu::SwapChainDescriptor& a, const wgpu::SwapChainDescriptor& b) { + return a.usage == b.usage && a.format == b.format && a.width == b.width && + a.height == b.height && a.presentMode == b.presentMode; +} + +void OnKeyPress(GLFWwindow* window, int key, int, int action, int); + +void SyncFromWindow(WindowData* data) { + int width; + int height; + glfwGetFramebufferSize(data->window, &width, &height); + + data->targetDesc.width = std::max(1u, width / data->divisor); + data->targetDesc.height = std::max(1u, height / data->divisor); +} + +void AddWindow() { + glfwWindowHint(GLFW_CLIENT_API, GLFW_NO_API); + GLFWwindow* window = glfwCreateWindow(400, 400, "", nullptr, nullptr); + glfwSetKeyCallback(window, OnKeyPress); + + wgpu::SwapChainDescriptor descriptor; + descriptor.usage = wgpu::TextureUsage::RenderAttachment; + descriptor.format = wgpu::TextureFormat::BGRA8Unorm; + descriptor.width = 0; + descriptor.height = 0; + descriptor.presentMode = wgpu::PresentMode::Fifo; + + std::unique_ptr<WindowData> data = std::make_unique<WindowData>(); + data->window = window; + data->serial = windowSerial++; + data->surface = utils::CreateSurfaceForWindow(instance->Get(), window); + data->currentDesc = descriptor; + data->targetDesc = descriptor; + SyncFromWindow(data.get()); + + windows[window] = std::move(data); +} + +void DoRender(WindowData* data) { + wgpu::TextureView view = data->swapchain.GetCurrentTextureView(); + wgpu::CommandEncoder encoder = device.CreateCommandEncoder(); + + if (data->renderTriangle) { + utils::ComboRenderPassDescriptor desc({view}); + // Use Load to check the swapchain is lazy cleared (we shouldn't see garbage from previous + // frames). + desc.cColorAttachments[0].loadOp = wgpu::LoadOp::Load; + + wgpu::RenderPassEncoder pass = encoder.BeginRenderPass(&desc); + pass.SetPipeline(trianglePipeline); + pass.Draw(3); + pass.End(); + } else { + data->clearCycle -= 1.0 / 60.f; + if (data->clearCycle < 0.0) { + data->clearCycle = 1.0f; + } + + utils::ComboRenderPassDescriptor desc({view}); + desc.cColorAttachments[0].loadOp = wgpu::LoadOp::Clear; + desc.cColorAttachments[0].clearValue = {data->clearCycle, 1.0f - data->clearCycle, 0.0f, + 1.0f}; + + wgpu::RenderPassEncoder pass = encoder.BeginRenderPass(&desc); + pass.End(); + } + + wgpu::CommandBuffer commands = encoder.Finish(); + queue.Submit(1, &commands); + + data->swapchain.Present(); +} + +std::ostream& operator<<(std::ostream& o, const wgpu::SwapChainDescriptor& desc) { + // For now only render attachment is possible. + ASSERT(desc.usage == wgpu::TextureUsage::RenderAttachment); + o << "RenderAttachment "; + o << desc.width << "x" << desc.height << " "; + + // For now only BGRA is allowed + ASSERT(desc.format == wgpu::TextureFormat::BGRA8Unorm); + o << "BGRA8Unorm "; + + switch (desc.presentMode) { + case wgpu::PresentMode::Immediate: + o << "Immediate"; + break; + case wgpu::PresentMode::Fifo: + o << "Fifo"; + break; + case wgpu::PresentMode::Mailbox: + o << "Mailbox"; + break; + } + return o; +} + +void UpdateTitle(WindowData* data) { + std::ostringstream o; + + o << data->serial << " "; + if (data->divisor != 1) { + o << "Divisor:" << data->divisor << " "; + } + + if (data->latched) { + o << "Latched: (" << data->currentDesc << ") "; + o << "Target: (" << data->targetDesc << ")"; + } else { + o << "(" << data->currentDesc << ")"; + } + + glfwSetWindowTitle(data->window, o.str().c_str()); +} + +void OnKeyPress(GLFWwindow* window, int key, int, int action, int) { + if (action != GLFW_PRESS) { + return; + } + + ASSERT(windows.count(window) == 1); + + WindowData* data = windows[window].get(); + switch (key) { + case GLFW_KEY_W: + AddWindow(); + break; + + case GLFW_KEY_L: + data->latched = !data->latched; + UpdateTitle(data); + break; + + case GLFW_KEY_R: + data->renderTriangle = !data->renderTriangle; + UpdateTitle(data); + break; + + case GLFW_KEY_D: + data->divisor *= 2; + if (data->divisor > 32) { + data->divisor = 1; + } + break; + + case GLFW_KEY_P: + switch (data->targetDesc.presentMode) { + case wgpu::PresentMode::Immediate: + data->targetDesc.presentMode = wgpu::PresentMode::Fifo; + break; + case wgpu::PresentMode::Fifo: + data->targetDesc.presentMode = wgpu::PresentMode::Mailbox; + break; + case wgpu::PresentMode::Mailbox: + data->targetDesc.presentMode = wgpu::PresentMode::Immediate; + break; + } + break; + + default: + break; + } +} + +int main(int argc, const char* argv[]) { + // Setup GLFW + glfwSetErrorCallback([](int code, const char* message) { + dawn::ErrorLog() << "GLFW error " << code << " " << message; + }); + if (!glfwInit()) { + return 1; + } + + // Choose an adapter we like. + // TODO: allow switching the window between devices. + DawnProcTable procs = dawn::native::GetProcs(); + dawnProcSetProcs(&procs); + + instance = std::make_unique<dawn::native::Instance>(); + instance->DiscoverDefaultAdapters(); + + std::vector<dawn::native::Adapter> adapters = instance->GetAdapters(); + dawn::native::Adapter chosenAdapter; + for (dawn::native::Adapter& adapter : adapters) { + wgpu::AdapterProperties properties; + adapter.GetProperties(&properties); + if (properties.backendType != wgpu::BackendType::Null) { + chosenAdapter = adapter; + break; + } + } + ASSERT(chosenAdapter); + + // Setup the device on that adapter. + device = wgpu::Device::Acquire(chosenAdapter.CreateDevice()); + device.SetUncapturedErrorCallback( + [](WGPUErrorType errorType, const char* message, void*) { + const char* errorTypeName = ""; + switch (errorType) { + case WGPUErrorType_Validation: + errorTypeName = "Validation"; + break; + case WGPUErrorType_OutOfMemory: + errorTypeName = "Out of memory"; + break; + case WGPUErrorType_Unknown: + errorTypeName = "Unknown"; + break; + case WGPUErrorType_DeviceLost: + errorTypeName = "Device lost"; + break; + default: + UNREACHABLE(); + return; + } + dawn::ErrorLog() << errorTypeName << " error: " << message; + }, + nullptr); + queue = device.GetQueue(); + + // The hacky pipeline to render a triangle. + utils::ComboRenderPipelineDescriptor pipelineDesc; + pipelineDesc.vertex.module = utils::CreateShaderModule(device, R"( + @stage(vertex) fn main(@builtin(vertex_index) VertexIndex : u32) + -> @builtin(position) vec4<f32> { + var pos = array<vec2<f32>, 3>( + vec2<f32>( 0.0, 0.5), + vec2<f32>(-0.5, -0.5), + vec2<f32>( 0.5, -0.5) + ); + return vec4<f32>(pos[VertexIndex], 0.0, 1.0); + })"); + pipelineDesc.cFragment.module = utils::CreateShaderModule(device, R"( + @stage(fragment) fn main() -> @location(0) vec4<f32> { + return vec4<f32>(1.0, 0.0, 0.0, 1.0); + })"); + // BGRA shouldn't be hardcoded. Consider having a map[format -> pipeline]. + pipelineDesc.cTargets[0].format = wgpu::TextureFormat::BGRA8Unorm; + trianglePipeline = device.CreateRenderPipeline(&pipelineDesc); + + // Craete the first window, since the example exits when there are no windows. + AddWindow(); + + while (windows.size() != 0) { + utils::ScopedAutoreleasePool pool; + glfwPollEvents(); + + for (auto it = windows.begin(); it != windows.end();) { + GLFWwindow* window = it->first; + + if (glfwWindowShouldClose(window)) { + glfwDestroyWindow(window); + it = windows.erase(it); + } else { + it++; + } + } + + for (auto& it : windows) { + WindowData* data = it.second.get(); + + SyncFromWindow(data); + if (!IsSameDescriptor(data->currentDesc, data->targetDesc) && !data->latched) { + data->swapchain = device.CreateSwapChain(data->surface, &data->targetDesc); + data->currentDesc = data->targetDesc; + } + UpdateTitle(data); + DoRender(data); + } + } +}
diff --git a/samples/dawn/OWNERS b/samples/dawn/OWNERS new file mode 100644 index 0000000..72e8ffc --- /dev/null +++ b/samples/dawn/OWNERS
@@ -0,0 +1 @@ +*
diff --git a/samples/dawn/SampleUtils.cpp b/samples/dawn/SampleUtils.cpp new file mode 100644 index 0000000..db14027 --- /dev/null +++ b/samples/dawn/SampleUtils.cpp
@@ -0,0 +1,279 @@ +// Copyright 2017 The Dawn Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include "SampleUtils.h" + +#include "GLFW/glfw3.h" +#include "dawn/common/Assert.h" +#include "dawn/common/Log.h" +#include "dawn/common/Platform.h" +#include "dawn/common/SystemUtils.h" +#include "dawn/dawn_proc.h" +#include "dawn/dawn_wsi.h" +#include "dawn/native/DawnNative.h" +#include "dawn/utils/BackendBinding.h" +#include "dawn/utils/GLFWUtils.h" +#include "dawn/utils/TerribleCommandBuffer.h" +#include "dawn/wire/WireClient.h" +#include "dawn/wire/WireServer.h" + +#include <algorithm> +#include <cstring> + +void PrintDeviceError(WGPUErrorType errorType, const char* message, void*) { + const char* errorTypeName = ""; + switch (errorType) { + case WGPUErrorType_Validation: + errorTypeName = "Validation"; + break; + case WGPUErrorType_OutOfMemory: + errorTypeName = "Out of memory"; + break; + case WGPUErrorType_Unknown: + errorTypeName = "Unknown"; + break; + case WGPUErrorType_DeviceLost: + errorTypeName = "Device lost"; + break; + default: + UNREACHABLE(); + return; + } + dawn::ErrorLog() << errorTypeName << " error: " << message; +} + +void PrintGLFWError(int code, const char* message) { + dawn::ErrorLog() << "GLFW error: " << code << " - " << message; +} + +enum class CmdBufType { + None, + Terrible, + // TODO(cwallez@chromium.org): double terrible cmdbuf +}; + +// Default to D3D12, Metal, Vulkan, OpenGL in that order as D3D12 and Metal are the preferred on +// their respective platforms, and Vulkan is preferred to OpenGL +#if defined(DAWN_ENABLE_BACKEND_D3D12) +static wgpu::BackendType backendType = wgpu::BackendType::D3D12; +#elif defined(DAWN_ENABLE_BACKEND_METAL) +static wgpu::BackendType backendType = wgpu::BackendType::Metal; +#elif defined(DAWN_ENABLE_BACKEND_VULKAN) +static wgpu::BackendType backendType = wgpu::BackendType::Vulkan; +#elif defined(DAWN_ENABLE_BACKEND_OPENGLES) +static wgpu::BackendType backendType = wgpu::BackendType::OpenGLES; +#elif defined(DAWN_ENABLE_BACKEND_DESKTOP_GL) +static wgpu::BackendType backendType = wgpu::BackendType::OpenGL; +#else +# error +#endif + +static CmdBufType cmdBufType = CmdBufType::Terrible; +static std::unique_ptr<dawn::native::Instance> instance; +static utils::BackendBinding* binding = nullptr; + +static GLFWwindow* window = nullptr; + +static dawn::wire::WireServer* wireServer = nullptr; +static dawn::wire::WireClient* wireClient = nullptr; +static utils::TerribleCommandBuffer* c2sBuf = nullptr; +static utils::TerribleCommandBuffer* s2cBuf = nullptr; + +wgpu::Device CreateCppDawnDevice() { + ScopedEnvironmentVar angleDefaultPlatform; + if (GetEnvironmentVar("ANGLE_DEFAULT_PLATFORM").first.empty()) { + angleDefaultPlatform.Set("ANGLE_DEFAULT_PLATFORM", "swiftshader"); + } + + glfwSetErrorCallback(PrintGLFWError); + if (!glfwInit()) { + return wgpu::Device(); + } + + // Create the test window and discover adapters using it (esp. for OpenGL) + utils::SetupGLFWWindowHintsForBackend(backendType); + glfwWindowHint(GLFW_COCOA_RETINA_FRAMEBUFFER, GLFW_FALSE); + window = glfwCreateWindow(640, 480, "Dawn window", nullptr, nullptr); + if (!window) { + return wgpu::Device(); + } + + instance = std::make_unique<dawn::native::Instance>(); + utils::DiscoverAdapter(instance.get(), window, backendType); + + // Get an adapter for the backend to use, and create the device. + dawn::native::Adapter backendAdapter; + { + std::vector<dawn::native::Adapter> adapters = instance->GetAdapters(); + auto adapterIt = std::find_if(adapters.begin(), adapters.end(), + [](const dawn::native::Adapter adapter) -> bool { + wgpu::AdapterProperties properties; + adapter.GetProperties(&properties); + return properties.backendType == backendType; + }); + ASSERT(adapterIt != adapters.end()); + backendAdapter = *adapterIt; + } + + WGPUDevice backendDevice = backendAdapter.CreateDevice(); + DawnProcTable backendProcs = dawn::native::GetProcs(); + + binding = utils::CreateBinding(backendType, window, backendDevice); + if (binding == nullptr) { + return wgpu::Device(); + } + + // Choose whether to use the backend procs and devices directly, or set up the wire. + WGPUDevice cDevice = nullptr; + DawnProcTable procs; + + switch (cmdBufType) { + case CmdBufType::None: + procs = backendProcs; + cDevice = backendDevice; + break; + + case CmdBufType::Terrible: { + c2sBuf = new utils::TerribleCommandBuffer(); + s2cBuf = new utils::TerribleCommandBuffer(); + + dawn::wire::WireServerDescriptor serverDesc = {}; + serverDesc.procs = &backendProcs; + serverDesc.serializer = s2cBuf; + + wireServer = new dawn::wire::WireServer(serverDesc); + c2sBuf->SetHandler(wireServer); + + dawn::wire::WireClientDescriptor clientDesc = {}; + clientDesc.serializer = c2sBuf; + + wireClient = new dawn::wire::WireClient(clientDesc); + procs = dawn::wire::client::GetProcs(); + s2cBuf->SetHandler(wireClient); + + auto deviceReservation = wireClient->ReserveDevice(); + wireServer->InjectDevice(backendDevice, deviceReservation.id, + deviceReservation.generation); + + cDevice = deviceReservation.device; + } break; + } + + dawnProcSetProcs(&procs); + procs.deviceSetUncapturedErrorCallback(cDevice, PrintDeviceError, nullptr); + return wgpu::Device::Acquire(cDevice); +} + +uint64_t GetSwapChainImplementation() { + return binding->GetSwapChainImplementation(); +} + +wgpu::TextureFormat GetPreferredSwapChainTextureFormat() { + DoFlush(); + return static_cast<wgpu::TextureFormat>(binding->GetPreferredSwapChainTextureFormat()); +} + +wgpu::SwapChain GetSwapChain(const wgpu::Device& device) { + wgpu::SwapChainDescriptor swapChainDesc; + swapChainDesc.implementation = GetSwapChainImplementation(); + return device.CreateSwapChain(nullptr, &swapChainDesc); +} + +wgpu::TextureView CreateDefaultDepthStencilView(const wgpu::Device& device) { + wgpu::TextureDescriptor descriptor; + descriptor.dimension = wgpu::TextureDimension::e2D; + descriptor.size.width = 640; + descriptor.size.height = 480; + descriptor.size.depthOrArrayLayers = 1; + descriptor.sampleCount = 1; + descriptor.format = wgpu::TextureFormat::Depth24PlusStencil8; + descriptor.mipLevelCount = 1; + descriptor.usage = wgpu::TextureUsage::RenderAttachment; + auto depthStencilTexture = device.CreateTexture(&descriptor); + return depthStencilTexture.CreateView(); +} + +bool InitSample(int argc, const char** argv) { + for (int i = 1; i < argc; i++) { + if (std::string("-b") == argv[i] || std::string("--backend") == argv[i]) { + i++; + if (i < argc && std::string("d3d12") == argv[i]) { + backendType = wgpu::BackendType::D3D12; + continue; + } + if (i < argc && std::string("metal") == argv[i]) { + backendType = wgpu::BackendType::Metal; + continue; + } + if (i < argc && std::string("null") == argv[i]) { + backendType = wgpu::BackendType::Null; + continue; + } + if (i < argc && std::string("opengl") == argv[i]) { + backendType = wgpu::BackendType::OpenGL; + continue; + } + if (i < argc && std::string("opengles") == argv[i]) { + backendType = wgpu::BackendType::OpenGLES; + continue; + } + if (i < argc && std::string("vulkan") == argv[i]) { + backendType = wgpu::BackendType::Vulkan; + continue; + } + fprintf(stderr, + "--backend expects a backend name (opengl, opengles, metal, d3d12, null, " + "vulkan)\n"); + return false; + } + if (std::string("-c") == argv[i] || std::string("--command-buffer") == argv[i]) { + i++; + if (i < argc && std::string("none") == argv[i]) { + cmdBufType = CmdBufType::None; + continue; + } + if (i < argc && std::string("terrible") == argv[i]) { + cmdBufType = CmdBufType::Terrible; + continue; + } + fprintf(stderr, "--command-buffer expects a command buffer name (none, terrible)\n"); + return false; + } + if (std::string("-h") == argv[i] || std::string("--help") == argv[i]) { + printf("Usage: %s [-b BACKEND] [-c COMMAND_BUFFER]\n", argv[0]); + printf(" BACKEND is one of: d3d12, metal, null, opengl, opengles, vulkan\n"); + printf(" COMMAND_BUFFER is one of: none, terrible\n"); + return false; + } + } + return true; +} + +void DoFlush() { + if (cmdBufType == CmdBufType::Terrible) { + bool c2sSuccess = c2sBuf->Flush(); + bool s2cSuccess = s2cBuf->Flush(); + + ASSERT(c2sSuccess && s2cSuccess); + } + glfwPollEvents(); +} + +bool ShouldQuit() { + return glfwWindowShouldClose(window); +} + +GLFWwindow* GetGLFWWindow() { + return window; +}
diff --git a/samples/dawn/SampleUtils.h b/samples/dawn/SampleUtils.h new file mode 100644 index 0000000..8c6fdd7 --- /dev/null +++ b/samples/dawn/SampleUtils.h
@@ -0,0 +1,29 @@ +// Copyright 2017 The Dawn Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include <dawn/dawn_wsi.h> +#include <dawn/webgpu_cpp.h> + +bool InitSample(int argc, const char** argv); +void DoFlush(); +bool ShouldQuit(); + +struct GLFWwindow; +struct GLFWwindow* GetGLFWWindow(); + +wgpu::Device CreateCppDawnDevice(); +uint64_t GetSwapChainImplementation(); +wgpu::TextureFormat GetPreferredSwapChainTextureFormat(); +wgpu::SwapChain GetSwapChain(const wgpu::Device& device); +wgpu::TextureView CreateDefaultDepthStencilView(const wgpu::Device& device);
diff --git a/scripts/dawn_component.gni b/scripts/dawn_component.gni new file mode 100644 index 0000000..8a69794 --- /dev/null +++ b/scripts/dawn_component.gni
@@ -0,0 +1,140 @@ +# Copyright 2019 The Dawn Authors +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +import("//build_overrides/build.gni") +import("dawn_features.gni") +import("dawn_overrides_with_defaults.gni") + +############################################################################### +# Template to produce a component for one of Dawn's libraries. +############################################################################### + +# Template that produces static and shared versions of the same library as well +# as a target similar to Chromium's component targets. +# - The shared version exports symbols and has dependent import the symbols +# as libdawn_${name}.so. If the target name matches the package directory +# name, then the shared library target will be named 'shared', otherwise +# '${target_name}_shared'. +# - The static library doesn't export symbols nor make dependents import them. +# If the target name matches the package directory name, then the static +# library target will be named 'static', otherwise '${target_name}_static'. +# - The libname target is similar to a Chromium component and is an alias for +# either the static or the shared library. +# +# The DEFINE_PREFIX must be provided and must match the respective "_export.h" +# file. +# +# Example usage: +# +# dawn_component("my_library") { +# // my_library_export.h must use the MY_LIBRARY_IMPLEMENTATION and +# // MY_LIBRARY_SHARED_LIBRARY macros. +# DEFINE_PREFIX = "MY_LIBRARY" +# +# sources = [...] +# deps = [...] +# configs = [...] +# } +# +# executable("foo") { +# deps = [ ":my_library_shared" ] // or :my_library for the same effect +# } +template("dawn_component") { + # Copy the target_name in the local scope so it doesn't get shadowed in the + # definition of targets. + name = target_name + + prefix = "${name}_" + + # Remove prefix if the target name matches directory + if (get_label_info(get_label_info(":$target_name", "dir"), "name") == name) { + prefix = "" + } + + # The config that will apply to dependents of the shared library so they know + # they should "import" the symbols + config("${prefix}shared_public_config") { + defines = [ "${invoker.DEFINE_PREFIX}_SHARED_LIBRARY" ] + + # Executable needs an rpath to find our shared libraries on OSX and Linux + if (is_mac) { + ldflags = [ + "-rpath", + "@executable_path/", + ] + } + if ((is_linux || is_chromeos) && dawn_has_build) { + configs = [ "//build/config/gcc:rpath_for_built_shared_libraries" ] + } + } + + shared_library("${prefix}shared") { + # The "tool" for creating shared libraries will automatically add the "lib" prefix + output_name = "dawn_${name}" + + # Copy all variables except "configs", which has a default value + forward_variables_from(invoker, "*", [ "configs" ]) + if (defined(invoker.configs)) { + configs += invoker.configs + } + + # Tell dependents where to find this shared library + if (is_mac) { + ldflags = [ + "-install_name", + "@rpath/lib${name}.dylib", + ] + } + + # Use the config that makes the ${DEFINE_PREFIX}_EXPORT macro do something + if (!defined(public_configs)) { + public_configs = [] + } + public_configs += [ ":${prefix}shared_public_config" ] + + # Tell sources of this library to export the symbols (and not import) + if (!defined(defines)) { + defines = [] + } + defines += [ "${invoker.DEFINE_PREFIX}_IMPLEMENTATION" ] + + # Chromium adds a config that uses a special linker script that removes + # all symbols except JNI ones. Remove this config so that our + # shared_library symbols are visible. This matches what Chromium's + # component template does. + if (build_with_chromium && is_android) { + configs -= [ "//build/config/android:hide_all_but_jni_onload" ] + } + } + + static_library("${prefix}static") { + output_name = "dawn_${name}_static" + + complete_static_lib = dawn_complete_static_libs + + # Copy all variables except "configs", which has a default value + forward_variables_from(invoker, "*", [ "configs" ]) + if (defined(invoker.configs)) { + configs += invoker.configs + } + } + + group(name) { + if (is_component_build) { + public_deps = [ ":${prefix}shared" ] + } else { + public_deps = [ ":${prefix}static" ] + } + } +}
diff --git a/scripts/dawn_features.gni b/scripts/dawn_features.gni new file mode 100644 index 0000000..234791c --- /dev/null +++ b/scripts/dawn_features.gni
@@ -0,0 +1,98 @@ +# Copyright 2018 The Dawn Authors +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +import("//build_overrides/build.gni") + +if (build_with_chromium) { + import("//build/config/ozone.gni") + import("//build/config/sanitizers/sanitizers.gni") + + dawn_use_x11 = ozone_platform_x11 +} else { + declare_args() { + # Whether Dawn should enable X11 support. + dawn_use_x11 = is_linux && !is_chromeos + } +} + +# Enable the compilation for UWP +dawn_is_winuwp = is_win && target_os == "winuwp" + +declare_args() { + dawn_use_angle = true + + # Enables SwiftShader as the fallback adapter. Requires dawn_swiftshader_dir + # to be set to take effect. + dawn_use_swiftshader = true +} + +declare_args() { + # Enable Dawn's ASSERTs even in release builds + dawn_always_assert = false + + # Should the Dawn static libraries be fully linked vs. GN's default of + # treating them as source sets. This is useful for people using Dawn + # standalone to produce static libraries to use in their projects. + dawn_complete_static_libs = false + + # Enables the compilation of Dawn's D3D12 backend + dawn_enable_d3d12 = is_win + + # Enables the compilation of Dawn's Metal backend + dawn_enable_metal = is_mac + + # Enables the compilation of Dawn's Null backend + # (required for unittests, obviously non-conformant) + dawn_enable_null = true + + # Enables the compilation of Dawn's OpenGL backend + # (best effort, non-conformant) + dawn_enable_desktop_gl = is_linux && !is_chromeos + + # Enables the compilation of Dawn's OpenGLES backend + # (WebGPU/Compat subset) + # Disables OpenGLES when compiling for UWP, since UWP only supports d3d + dawn_enable_opengles = + (is_linux && !is_chromeos) || (is_win && !dawn_is_winuwp) + + # Enables the compilation of Dawn's Vulkan backend + # Disables vulkan when compiling for UWP, since UWP only supports d3d + dawn_enable_vulkan = is_linux || is_chromeos || (is_win && !dawn_is_winuwp) || + is_fuchsia || is_android || dawn_use_swiftshader + + # Enables error injection for faking failures to native API calls + dawn_enable_error_injection = + is_debug || (build_with_chromium && use_fuzzing_engine) +} + +# GN does not allow reading a variable defined in the same declare_args(). +# Put them in two separate declare_args() when setting the value of one +# argument based on another. +declare_args() { + # Uses our built version of the Vulkan validation layers + dawn_enable_vulkan_validation_layers = + dawn_enable_vulkan && ((is_linux && !is_chromeos) || is_win || is_mac) + + # Uses our built version of the Vulkan loader on platforms where we can't + # assume to have one present at the system level. + dawn_enable_vulkan_loader = + dawn_enable_vulkan && (is_mac || (is_linux && !is_android)) +} + +# UWP only supports CoreWindow for windowing +dawn_supports_glfw_for_windowing = + (is_win && !dawn_is_winuwp) || (is_linux && !is_chromeos) || is_mac + +# Much of the GL backend code is shared, so define a convenience var. +dawn_enable_opengl = dawn_enable_opengles || dawn_enable_desktop_gl
diff --git a/scripts/dawn_overrides_with_defaults.gni b/scripts/dawn_overrides_with_defaults.gni new file mode 100644 index 0000000..46f44ef --- /dev/null +++ b/scripts/dawn_overrides_with_defaults.gni
@@ -0,0 +1,80 @@ +# Copyright 2018 The Dawn Authors +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +# This files imports the overrides for Dawn but sets the defaults so that +# projects including Dawn don't have to set dirs if they happen to use the +# same. +# It takes advantage of GN's variable scoping rules to define global variables +# inside if constructs. + +import("//build_overrides/dawn.gni") + +if (!defined(dawn_standalone)) { + dawn_standalone = false +} + +if (!defined(dawn_has_build)) { + dawn_has_build = true +} + +if (!defined(dawn_root)) { + dawn_root = get_path_info("..", "abspath") +} +dawn_gen_root = get_path_info("${dawn_root}", "gen_dir") + +if (!defined(dawn_jinja2_dir)) { + dawn_jinja2_dir = "//third_party/jinja2" +} + +if (!defined(dawn_glfw_dir)) { + dawn_glfw_dir = "//third_party/glfw" +} + +if (!defined(dawn_googletest_dir)) { + dawn_googletest_dir = "//third_party/googletest" +} + +if (!defined(dawn_spirv_tools_dir)) { + dawn_spirv_tools_dir = "//third_party/vulkan-deps/spirv-tools/src" +} + +if (!defined(dawn_swiftshader_dir)) { + # Default to swiftshader not being available. + dawn_swiftshader_dir = "" +} + +if (!defined(dawn_vulkan_headers_dir)) { + dawn_vulkan_headers_dir = "//third_party/vulkan-deps/vulkan-headers/src" + if (dawn_standalone) { + dawn_vulkan_headers_dir = + "${dawn_root}/third_party/vulkan-deps/vulkan-headers/src" + } +} + +if (!defined(dawn_vulkan_loader_dir)) { + # Default to the Vulkan loader not being available except in standalone. + dawn_vulkan_loader_dir = "" + if (dawn_standalone) { + dawn_vulkan_loader_dir = "//third_party/vulkan-deps/vulkan-loader/src" + } +} + +if (!defined(dawn_vulkan_validation_layers_dir)) { + # Default to VVLs not being available. + dawn_vulkan_validation_layers_dir = "" +} + +if (!defined(dawn_abseil_dir)) { + dawn_abseil_dir = "//third_party/abseil-cpp" +}
diff --git a/scripts/extract.py b/scripts/extract.py new file mode 100644 index 0000000..ed263f4 --- /dev/null +++ b/scripts/extract.py
@@ -0,0 +1,182 @@ +# Copyright (c) 2015, Google Inc. +# +# Permission to use, copy, modify, and/or distribute this software for any +# purpose with or without fee is hereby granted, provided that the above +# copyright notice and this permission notice appear in all copies. +# +# THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES +# WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF +# MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY +# SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES +# WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION +# OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN +# CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. +"""Extracts archives.""" + +import hashlib +import optparse +import os +import os.path +import tarfile +import shutil +import sys +import zipfile + + +def CheckedJoin(output, path): + """ + CheckedJoin returns os.path.join(output, path). It does sanity checks to + ensure the resulting path is under output, but shouldn't be used on untrusted + input. + """ + path = os.path.normpath(path) + if os.path.isabs(path) or path.startswith('.'): + raise ValueError(path) + return os.path.join(output, path) + + +class FileEntry(object): + def __init__(self, path, mode, fileobj): + self.path = path + self.mode = mode + self.fileobj = fileobj + + +class SymlinkEntry(object): + def __init__(self, path, mode, target): + self.path = path + self.mode = mode + self.target = target + + +def IterateZip(path): + """ + IterateZip opens the zip file at path and returns a generator of entry objects + for each file in it. + """ + with zipfile.ZipFile(path, 'r') as zip_file: + for info in zip_file.infolist(): + if info.filename.endswith('/'): + continue + yield FileEntry(info.filename, None, zip_file.open(info)) + + +def IterateTar(path, compression): + """ + IterateTar opens the tar.gz or tar.bz2 file at path and returns a generator of + entry objects for each file in it. + """ + with tarfile.open(path, 'r:' + compression) as tar_file: + for info in tar_file: + if info.isdir(): + pass + elif info.issym(): + yield SymlinkEntry(info.name, None, info.linkname) + elif info.isfile(): + yield FileEntry(info.name, info.mode, + tar_file.extractfile(info)) + else: + raise ValueError('Unknown entry type "%s"' % (info.name, )) + + +def main(args): + parser = optparse.OptionParser(usage='Usage: %prog ARCHIVE OUTPUT') + parser.add_option('--no-prefix', + dest='no_prefix', + action='store_true', + help='Do not remove a prefix from paths in the archive.') + options, args = parser.parse_args(args) + + if len(args) != 2: + parser.print_help() + return 1 + + archive, output = args + + if not os.path.exists(archive): + # Skip archives that weren't downloaded. + return 0 + + with open(archive, 'rb') as f: + sha256 = hashlib.sha256() + while True: + chunk = f.read(1024 * 1024) + if not chunk: + break + sha256.update(chunk) + digest = sha256.hexdigest() + + stamp_path = os.path.join(output, ".dawn_archive_digest") + if os.path.exists(stamp_path): + with open(stamp_path) as f: + if f.read().strip() == digest: + print("Already up-to-date.") + return 0 + + if archive.endswith('.zip'): + entries = IterateZip(archive) + elif archive.endswith('.tar.gz'): + entries = IterateTar(archive, 'gz') + elif archive.endswith('.tar.bz2'): + entries = IterateTar(archive, 'bz2') + else: + raise ValueError(archive) + + try: + if os.path.exists(output): + print("Removing %s" % (output, )) + shutil.rmtree(output) + + print("Extracting %s to %s" % (archive, output)) + prefix = None + num_extracted = 0 + for entry in entries: + # Even on Windows, zip files must always use forward slashes. + if '\\' in entry.path or entry.path.startswith('/'): + raise ValueError(entry.path) + + if not options.no_prefix: + new_prefix, rest = entry.path.split('/', 1) + + # Ensure the archive is consistent. + if prefix is None: + prefix = new_prefix + if prefix != new_prefix: + raise ValueError((prefix, new_prefix)) + else: + rest = entry.path + + # Extract the file into the output directory. + fixed_path = CheckedJoin(output, rest) + if not os.path.isdir(os.path.dirname(fixed_path)): + os.makedirs(os.path.dirname(fixed_path)) + if isinstance(entry, FileEntry): + with open(fixed_path, 'wb') as out: + shutil.copyfileobj(entry.fileobj, out) + elif isinstance(entry, SymlinkEntry): + os.symlink(entry.target, fixed_path) + else: + raise TypeError('unknown entry type') + + # Fix up permissions if needbe. + # TODO(davidben): To be extra tidy, this should only track the execute bit + # as in git. + if entry.mode is not None: + os.chmod(fixed_path, entry.mode) + + # Print every 100 files, so bots do not time out on large archives. + num_extracted += 1 + if num_extracted % 100 == 0: + print("Extracted %d files..." % (num_extracted, )) + finally: + entries.close() + + with open(stamp_path, 'w') as f: + f.write(digest) + + print("Done. Extracted %d files." % (num_extracted, )) + return 0 + + +if __name__ == '__main__': + sys.exit(main(sys.argv[1:]))
diff --git a/scripts/perf_test_runner.py b/scripts/perf_test_runner.py new file mode 100755 index 0000000..157d449 --- /dev/null +++ b/scripts/perf_test_runner.py
@@ -0,0 +1,155 @@ +#!/usr/bin/env python3 +# +# Copyright 2019 The Dawn Authors +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +# Based on Angle's perf_test_runner.py + +import glob +import subprocess +import sys +import os +import re + +base_path = os.path.abspath( + os.path.join(os.path.dirname(os.path.abspath(__file__)), '..')) + +# Look for a [Rr]elease build. +perftests_paths = glob.glob('out/*elease*') +metric = 'wall_time' +max_experiments = 10 + +binary_name = 'dawn_perf_tests' +if sys.platform == 'win32': + binary_name += '.exe' + +scores = [] + + +def mean(data): + """Return the sample arithmetic mean of data.""" + n = len(data) + if n < 1: + raise ValueError('mean requires at least one data point') + return float(sum(data)) / float(n) # in Python 2 use sum(data)/float(n) + + +def sum_of_square_deviations(data, c): + """Return sum of square deviations of sequence data.""" + ss = sum((float(x) - c)**2 for x in data) + return ss + + +def coefficient_of_variation(data): + """Calculates the population coefficient of variation.""" + n = len(data) + if n < 2: + raise ValueError('variance requires at least two data points') + c = mean(data) + ss = sum_of_square_deviations(data, c) + pvar = ss / n # the population variance + stddev = (pvar**0.5) # population standard deviation + return stddev / c + + +def truncated_list(data, n): + """Compute a truncated list, n is truncation size""" + if len(data) < n * 2: + raise ValueError('list not large enough to truncate') + return sorted(data)[n:-n] + + +def truncated_mean(data, n): + """Compute a truncated mean, n is truncation size""" + return mean(truncated_list(data, n)) + + +def truncated_cov(data, n): + """Compute a truncated coefficient of variation, n is truncation size""" + return coefficient_of_variation(truncated_list(data, n)) + + +# Find most recent binary +newest_binary = None +newest_mtime = None + +for path in perftests_paths: + binary_path = os.path.join(base_path, path, binary_name) + if os.path.exists(binary_path): + binary_mtime = os.path.getmtime(binary_path) + if (newest_binary is None) or (binary_mtime > newest_mtime): + newest_binary = binary_path + newest_mtime = binary_mtime + +perftests_path = newest_binary + +if perftests_path == None or not os.path.exists(perftests_path): + print('Cannot find Release %s!' % binary_name) + sys.exit(1) + +if len(sys.argv) >= 2: + test_name = sys.argv[1] + +print('Using test executable: ' + perftests_path) +print('Test name: ' + test_name) + + +def get_results(metric, extra_args=[]): + process = subprocess.Popen( + [perftests_path, '--gtest_filter=' + test_name] + extra_args, + stdout=subprocess.PIPE, + stderr=subprocess.PIPE) + output, err = process.communicate() + + m = re.search(r'Running (\d+) tests', output) + if m and int(m.group(1)) > 1: + print("Found more than one test result in output:") + print(output) + sys.exit(3) + + pattern = metric + r'.*= ([0-9.]+)' + m = re.findall(pattern, output) + if not m: + print("Did not find the metric '%s' in the test output:" % metric) + print(output) + sys.exit(1) + + return [float(value) for value in m] + + +# Calibrate the number of steps +steps = get_results("steps", ["--calibration"])[0] +print("running with %d steps." % steps) + +# Loop 'max_experiments' times, running the tests. +for experiment in range(max_experiments): + experiment_scores = get_results(metric, ["--override-steps", str(steps)]) + + for score in experiment_scores: + sys.stdout.write("%s: %.2f" % (metric, score)) + scores.append(score) + + if (len(scores) > 1): + sys.stdout.write(", mean: %.2f" % mean(scores)) + sys.stdout.write(", variation: %.2f%%" % + (coefficient_of_variation(scores) * 100.0)) + + if (len(scores) > 7): + truncation_n = len(scores) >> 3 + sys.stdout.write(", truncated mean: %.2f" % + truncated_mean(scores, truncation_n)) + sys.stdout.write(", variation: %.2f%%" % + (truncated_cov(scores, truncation_n) * 100.0)) + + print("")
diff --git a/scripts/standalone-with-node.gclient b/scripts/standalone-with-node.gclient new file mode 100644 index 0000000..b695f8a --- /dev/null +++ b/scripts/standalone-with-node.gclient
@@ -0,0 +1,13 @@ +# Copy this file to <dawn clone dir>/.gclient to bootstrap gclient in a +# standalone checkout of Dawn that also compiles dawn_node. + +solutions = [ + { "name" : ".", + "url" : "https://dawn.googlesource.com/dawn", + "deps_file" : "DEPS", + "managed" : False, + "custom_vars" : { + "dawn_node" : True, + } + }, +]
diff --git a/scripts/standalone.gclient b/scripts/standalone.gclient new file mode 100644 index 0000000..86a7f0c --- /dev/null +++ b/scripts/standalone.gclient
@@ -0,0 +1,10 @@ +# Copy this file to <dawn clone dir>/.gclient to bootstrap gclient in a +# standalone checkout of Dawn. + +solutions = [ + { "name" : ".", + "url" : "https://dawn.googlesource.com/dawn", + "deps_file" : "DEPS", + "managed" : False, + }, +]
diff --git a/src/Dummy.cpp b/src/Dummy.cpp new file mode 100644 index 0000000..5959a87 --- /dev/null +++ b/src/Dummy.cpp
@@ -0,0 +1,18 @@ +// Copyright 2020 The Dawn Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +// CMake requires that targets contain at least on file. This file is used when we want to create +// empty targets. + +int someSymbolToMakeXCodeHappy = 0;
diff --git a/src/dawn/BUILD.gn b/src/dawn/BUILD.gn new file mode 100644 index 0000000..67991ad --- /dev/null +++ b/src/dawn/BUILD.gn
@@ -0,0 +1,99 @@ +# Copyright 2019 The Dawn Authors +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +import("../../scripts/dawn_overrides_with_defaults.gni") + +import("${dawn_root}/generator/dawn_generator.gni") +import("${dawn_root}/scripts/dawn_component.gni") + +############################################################################### +# Dawn C++ wrapper +############################################################################### + +dawn_json_generator("cpp_gen") { + target = "cpp" + outputs = [ "src/dawn/webgpu_cpp.cpp" ] +} + +source_set("cpp") { + deps = [ + ":cpp_gen", + "${dawn_root}/include/dawn:cpp_headers", + ] + sources = get_target_outputs(":cpp_gen") +} + +############################################################################### +# Dawn proc +############################################################################### + +dawn_json_generator("proc_gen") { + target = "proc" + outputs = [ + "src/dawn/dawn_proc.c", + "src/dawn/dawn_thread_dispatch_proc.cpp", + ] +} + +dawn_component("proc") { + DEFINE_PREFIX = "WGPU" + + public_deps = [ "${dawn_root}/include/dawn:headers" ] + deps = [ ":proc_gen" ] + sources = get_target_outputs(":proc_gen") + sources += [ + "${dawn_root}/include/dawn/dawn_proc.h", + "${dawn_root}/include/dawn/dawn_thread_dispatch_proc.h", + ] +} + +############################################################################### +# Other generated files (upstream header, emscripten header, emscripten bits) +############################################################################### + +dawn_json_generator("webgpu_headers_gen") { + target = "webgpu_headers" + outputs = [ "webgpu-headers/webgpu.h" ] +} + +dawn_json_generator("emscripten_bits_gen") { + target = "emscripten_bits" + outputs = [ + "emscripten-bits/webgpu.h", + "emscripten-bits/webgpu_cpp.h", + "emscripten-bits/webgpu_cpp.cpp", + "emscripten-bits/webgpu_struct_info.json", + "emscripten-bits/library_webgpu_enum_tables.js", + ] +} + +################################################################################ +# Build target aliases +# TODO(crbug.com/dawn/1275) - remove these +################################################################################ +group("dawncpp") { + public_deps = [ ":cpp" ] +} +group("dawncpp_headers") { + public_deps = [ "${dawn_root}/include/dawn:cpp_headers" ] +} +group("dawn_proc") { + public_deps = [ ":proc" ] +} +group("dawn_headers") { + public_deps = [ "${dawn_root}/include/dawn:headers" ] +} +group("dawn_cpp") { + public_deps = [ ":cpp" ] +}
diff --git a/src/dawn/CMakeLists.txt b/src/dawn/CMakeLists.txt new file mode 100644 index 0000000..578e61c --- /dev/null +++ b/src/dawn/CMakeLists.txt
@@ -0,0 +1,144 @@ +# Copyright 2020 The Dawn Authors +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +############################################################################### +# Dawn projects +############################################################################### + +add_subdirectory(common) +add_subdirectory(platform) +add_subdirectory(native) +add_subdirectory(wire) +# TODO(dawn:269): Remove once the implementation-based swapchains are removed. +add_subdirectory(utils) + +if (DAWN_BUILD_NODE_BINDINGS) + set(NODE_BINDING_DEPS + ${NODE_ADDON_API_DIR} + ${NODE_API_HEADERS_DIR} + ${WEBGPU_IDL_PATH} + ) + foreach(DEP ${NODE_BINDING_DEPS}) + if (NOT EXISTS ${DEP}) + message(FATAL_ERROR + "DAWN_BUILD_NODE_BINDINGS requires missing dependency '${DEP}'\n" + "Please follow the 'Fetch dependencies' instructions at:\n" + "./src/dawn/node/README.md" + ) + endif() + endforeach() + if (NOT CMAKE_POSITION_INDEPENDENT_CODE) + message(FATAL_ERROR "DAWN_BUILD_NODE_BINDINGS requires building with DAWN_ENABLE_PIC") + endif() + + add_subdirectory(node) +endif() + +############################################################################### +# Dawn headers +############################################################################### + +DawnJSONGenerator( + TARGET "headers" + PRINT_NAME "Dawn headers" + RESULT_VARIABLE "DAWN_HEADERS_GEN_SOURCES" +) + +# Headers only INTERFACE library with generated headers don't work in CMake +# because the GENERATED property is local to a directory. Instead we make a +# STATIC library with a Dummy cpp file. +# +# INTERFACE libraries can only have INTERFACE sources so the sources get added +# to the dependant's list of sources. If these dependents are in another +# directory, they don't see the GENERATED property and fail to configure +# because the file doesn't exist on disk. +add_library(dawn_headers STATIC ${DAWN_DUMMY_FILE}) +target_sources(dawn_headers PRIVATE + "${DAWN_INCLUDE_DIR}/dawn/dawn_wsi.h" + ${DAWN_HEADERS_GEN_SOURCES} +) +target_link_libraries(dawn_headers INTERFACE dawn_public_config) + +############################################################################### +# Dawn C++ headers +############################################################################### + +DawnJSONGenerator( + TARGET "cpp_headers" + PRINT_NAME "Dawn C++ headers" + RESULT_VARIABLE "DAWNCPP_HEADERS_GEN_SOURCES" +) + +# This headers only library needs to be a STATIC library, see comment for +# dawn_headers above. +add_library(dawncpp_headers STATIC ${DAWN_DUMMY_FILE}) +target_sources(dawncpp_headers PRIVATE + "${DAWN_INCLUDE_DIR}/dawn/EnumClassBitmasks.h" + ${DAWNCPP_HEADERS_GEN_SOURCES} +) +target_link_libraries(dawncpp_headers INTERFACE dawn_headers) + +############################################################################### +# Dawn C++ wrapper +############################################################################### + +DawnJSONGenerator( + TARGET "cpp" + PRINT_NAME "Dawn C++ wrapper" + RESULT_VARIABLE "DAWNCPP_GEN_SOURCES" +) + +add_library(dawncpp STATIC ${DAWN_DUMMY_FILE}) +target_sources(dawncpp PRIVATE ${DAWNCPP_GEN_SOURCES}) +target_link_libraries(dawncpp PUBLIC dawncpp_headers) + +############################################################################### +# libdawn_proc +############################################################################### + +DawnJSONGenerator( + TARGET "proc" + PRINT_NAME "Dawn C++ wrapper" + RESULT_VARIABLE "DAWNPROC_GEN_SOURCES" +) + +add_library(dawn_proc ${DAWN_DUMMY_FILE}) +target_compile_definitions(dawn_proc PRIVATE "WGPU_IMPLEMENTATION") +if(BUILD_SHARED_LIBS) + target_compile_definitions(dawn_proc PRIVATE "WGPU_SHARED_LIBRARY") +endif() +target_sources(dawn_proc PRIVATE ${DAWNPROC_GEN_SOURCES}) +target_link_libraries(dawn_proc PUBLIC dawn_headers) + +############################################################################### +# Other generated files (upstream header, emscripten header, emscripten bits) +############################################################################### + +DawnJSONGenerator( + TARGET "webgpu_headers" + PRINT_NAME "WebGPU headers" + RESULT_VARIABLE "WEBGPU_HEADERS_GEN_SOURCES" +) +add_custom_target(webgpu_headers_gen + DEPENDS ${WEBGPU_HEADERS_GEN_SOURCES} +) + +DawnJSONGenerator( + TARGET "emscripten_bits" + PRINT_NAME "Emscripten WebGPU bits" + RESULT_VARIABLE "EMSCRIPTEN_BITS_GEN_SOURCES" +) +add_custom_target(emscripten_bits_gen + DEPENDS ${EMSCRIPTEN_BITS_GEN_SOURCES} +)
diff --git a/src/dawn/common/Alloc.h b/src/dawn/common/Alloc.h new file mode 100644 index 0000000..940d5ff --- /dev/null +++ b/src/dawn/common/Alloc.h
@@ -0,0 +1,33 @@ +// Copyright 2020 The Dawn Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#ifndef COMMON_ALLOC_H_ +#define COMMON_ALLOC_H_ + +#include <cstddef> +#include <new> + +template <typename T> +T* AllocNoThrow(size_t count) { +#if defined(ADDRESS_SANITIZER) + if (count * sizeof(T) >= 0x70000000) { + // std::nothrow isn't implemented on ASAN and it has a 2GB allocation limit. + // Catch large allocations and error out so fuzzers make progress. + return nullptr; + } +#endif + return new (std::nothrow) T[count]; +} + +#endif // COMMON_ALLOC_H_
diff --git a/src/dawn/common/Assert.cpp b/src/dawn/common/Assert.cpp new file mode 100644 index 0000000..95d2efd --- /dev/null +++ b/src/dawn/common/Assert.cpp
@@ -0,0 +1,31 @@ +// Copyright 2017 The Dawn Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include "dawn/common/Assert.h" +#include "dawn/common/Log.h" + +#include <cstdlib> + +void HandleAssertionFailure(const char* file, + const char* function, + int line, + const char* condition) { + dawn::ErrorLog() << "Assertion failure at " << file << ":" << line << " (" << function + << "): " << condition; +#if defined(DAWN_ABORT_ON_ASSERT) + abort(); +#else + DAWN_BREAKPOINT(); +#endif +}
diff --git a/src/dawn/common/Assert.h b/src/dawn/common/Assert.h new file mode 100644 index 0000000..e7961d7 --- /dev/null +++ b/src/dawn/common/Assert.h
@@ -0,0 +1,80 @@ +// Copyright 2017 The Dawn Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#ifndef COMMON_ASSERT_H_ +#define COMMON_ASSERT_H_ + +#include "dawn/common/Compiler.h" + +// Dawn asserts to be used instead of the regular C stdlib assert function (if you don't use assert +// yet, you should start now!). In debug ASSERT(condition) will trigger an error, otherwise in +// release it does nothing at runtime. +// +// In case of name clashes (with for example a testing library), you can define the +// DAWN_SKIP_ASSERT_SHORTHANDS to only define the DAWN_ prefixed macros. +// +// These asserts feature: +// - Logging of the error with file, line and function information. +// - Breaking in the debugger when an assert is triggered and a debugger is attached. +// - Use the assert information to help the compiler optimizer in release builds. + +// MSVC triggers a warning in /W4 for do {} while(0). SDL worked around this by using (0,0) and +// points out that it looks like an owl face. +#if defined(DAWN_COMPILER_MSVC) +# define DAWN_ASSERT_LOOP_CONDITION (0, 0) +#else +# define DAWN_ASSERT_LOOP_CONDITION (0) +#endif + +// DAWN_ASSERT_CALLSITE_HELPER generates the actual assert code. In Debug it does what you would +// expect of an assert and in release it tries to give hints to make the compiler generate better +// code. +#if defined(DAWN_ENABLE_ASSERTS) +# define DAWN_ASSERT_CALLSITE_HELPER(file, func, line, condition) \ + do { \ + if (!(condition)) { \ + HandleAssertionFailure(file, func, line, #condition); \ + } \ + } while (DAWN_ASSERT_LOOP_CONDITION) +#else +# if defined(DAWN_COMPILER_MSVC) +# define DAWN_ASSERT_CALLSITE_HELPER(file, func, line, condition) __assume(condition) +# elif defined(DAWN_COMPILER_CLANG) && defined(__builtin_assume) +# define DAWN_ASSERT_CALLSITE_HELPER(file, func, line, condition) __builtin_assume(condition) +# else +# define DAWN_ASSERT_CALLSITE_HELPER(file, func, line, condition) \ + do { \ + DAWN_UNUSED(sizeof(condition)); \ + } while (DAWN_ASSERT_LOOP_CONDITION) +# endif +#endif + +#define DAWN_ASSERT(condition) DAWN_ASSERT_CALLSITE_HELPER(__FILE__, __func__, __LINE__, condition) +#define DAWN_UNREACHABLE() \ + do { \ + DAWN_ASSERT(DAWN_ASSERT_LOOP_CONDITION && "Unreachable code hit"); \ + DAWN_BUILTIN_UNREACHABLE(); \ + } while (DAWN_ASSERT_LOOP_CONDITION) + +#if !defined(DAWN_SKIP_ASSERT_SHORTHANDS) +# define ASSERT DAWN_ASSERT +# define UNREACHABLE DAWN_UNREACHABLE +#endif + +void HandleAssertionFailure(const char* file, + const char* function, + int line, + const char* condition); + +#endif // COMMON_ASSERT_H_
diff --git a/src/dawn/common/BUILD.gn b/src/dawn/common/BUILD.gn new file mode 100644 index 0000000..d0b0086 --- /dev/null +++ b/src/dawn/common/BUILD.gn
@@ -0,0 +1,262 @@ +# Copyright 2019 The Dawn Authors +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +import("../../../scripts/dawn_overrides_with_defaults.gni") + +import("//build_overrides/build.gni") +import("${dawn_root}/generator/dawn_generator.gni") +import("${dawn_root}/scripts/dawn_features.gni") + +# Use Chromium's dcheck_always_on when available so that we respect it when +# running tests on the GPU builders +if (build_with_chromium) { + import("//build/config/dcheck_always_on.gni") +} else { + dcheck_always_on = false +} + +if (build_with_chromium) { + import("//build/config/sanitizers/sanitizers.gni") +} else { + use_fuzzing_engine = false +} + +############################################################################### +# Common dawn configs +############################################################################### + +config("internal_config") { + include_dirs = [ + "${target_gen_dir}/../../../src", + "${dawn_root}/src", + ] + + defines = [] + if (dawn_always_assert || dcheck_always_on || is_debug || + use_fuzzing_engine) { + defines += [ "DAWN_ENABLE_ASSERTS" ] + } + + if (use_fuzzing_engine) { + # Does a hard abort when an assertion fails so that fuzzers catch and parse the failure. + defines += [ "DAWN_ABORT_ON_ASSERT" ] + } + + if (dawn_enable_d3d12) { + defines += [ "DAWN_ENABLE_BACKEND_D3D12" ] + } + if (dawn_enable_metal) { + defines += [ "DAWN_ENABLE_BACKEND_METAL" ] + } + if (dawn_enable_null) { + defines += [ "DAWN_ENABLE_BACKEND_NULL" ] + } + if (dawn_enable_opengl) { + defines += [ "DAWN_ENABLE_BACKEND_OPENGL" ] + } + if (dawn_enable_desktop_gl) { + defines += [ "DAWN_ENABLE_BACKEND_DESKTOP_GL" ] + } + if (dawn_enable_opengles) { + defines += [ "DAWN_ENABLE_BACKEND_OPENGLES" ] + } + if (dawn_enable_vulkan) { + defines += [ "DAWN_ENABLE_BACKEND_VULKAN" ] + } + + if (dawn_use_x11) { + defines += [ "DAWN_USE_X11" ] + } + + if (dawn_enable_error_injection) { + defines += [ "DAWN_ENABLE_ERROR_INJECTION" ] + } + + # Only internal Dawn targets can use this config, this means only targets in + # this BUILD.gn file and related subdirs. + visibility = [ + "${dawn_root}/samples/dawn/*", + "${dawn_root}/src/dawn/*", + ] + + cflags = [] + if (is_clang) { + cflags += [ "-Wno-shadow" ] + } + + # Enable more warnings that were found when using Dawn in other projects. + # Add them only when building in standalone because we control which clang + # version we use. Otherwise we risk breaking projects depending on Dawn when + # the use a different clang version. + if (dawn_standalone && is_clang) { + cflags += [ + "-Wconditional-uninitialized", + "-Wcstring-format-directive", + "-Wc++11-narrowing", + "-Wdeprecated-copy", + "-Wdeprecated-copy-dtor", + "-Wduplicate-enum", + "-Wextra-semi-stmt", + "-Wimplicit-fallthrough", + "-Winconsistent-missing-destructor-override", + "-Winvalid-offsetof", + "-Wmissing-field-initializers", + "-Wnon-c-typedef-for-linkage", + "-Wpessimizing-move", + "-Wrange-loop-analysis", + "-Wredundant-move", + "-Wshadow-field", + "-Wstrict-prototypes", + "-Wtautological-unsigned-zero-compare", + "-Wunreachable-code-aggressive", + "-Wunused-but-set-variable", + ] + + if (is_win) { + cflags += [ + # clang-cl doesn't know -pedantic, pass it explicitly to the clang driver + "/clang:-pedantic", + + # Allow the use of __uuidof() + "-Wno-language-extension-token", + ] + } else { + cflags += [ "-pedantic" ] + } + } + + if (!is_clang && is_win) { + # Dawn extends wgpu enums with internal enums. + # MSVC considers these invalid switch values. crbug.com/dawn/397. + cflags += [ "/wd4063" ] + + # MSVC things that a switch over all the enum values of an enum class is + # not sufficient to cover all control paths. Turn off this warning so that + # the respective clang warning tells us where to add switch cases + # (otherwise we have to add default: UNREACHABLE() that silences clang too) + cflags += [ "/wd4715" ] + + # MSVC emits warnings when using constructs deprecated in C++17. Silence + # them until they are fixed. + # TODO(dawn:824): Fix all uses of C++ features deprecated in C++17. + defines += [ "_SILENCE_ALL_CXX17_DEPRECATION_WARNINGS" ] + if (dawn_is_winuwp) { + # /ZW makes sure we don't add calls that are forbidden in UWP. + # and /EHsc is required to be used in combination with it, + # even if it is already added by the windows GN defaults, + # we still add it to make every /ZW paired with a /EHsc + cflags_cc = [ + "/ZW:nostdlib", + "/EHsc", + ] + } + } +} + +############################################################################### +# Common dawn library +############################################################################### + +dawn_generator("dawn_version_gen") { + script = "${dawn_root}/generator/dawn_version_generator.py" + args = [ + "--dawn-dir", + rebase_path("${dawn_root}", root_build_dir), + ] + outputs = [ "src/dawn/common/Version_autogen.h" ] +} + +# This GN file is discovered by all Chromium builds, but common doesn't support +# all of Chromium's OSes so we explicitly make the target visible only on +# systems we know Dawn is able to compile on. +if (is_win || is_linux || is_chromeos || is_mac || is_fuchsia || is_android) { + static_library("common") { + sources = [ + "Alloc.h", + "Assert.cpp", + "Assert.h", + "BitSetIterator.h", + "Compiler.h", + "ConcurrentCache.h", + "Constants.h", + "CoreFoundationRef.h", + "DynamicLib.cpp", + "DynamicLib.h", + "GPUInfo.cpp", + "GPUInfo.h", + "HashUtils.h", + "IOKitRef.h", + "LinkedList.h", + "Log.cpp", + "Log.h", + "Math.cpp", + "Math.h", + "NSRef.h", + "NonCopyable.h", + "PlacementAllocated.h", + "Platform.h", + "Preprocessor.h", + "RefBase.h", + "RefCounted.cpp", + "RefCounted.h", + "Result.cpp", + "Result.h", + "SerialMap.h", + "SerialQueue.h", + "SerialStorage.h", + "SlabAllocator.cpp", + "SlabAllocator.h", + "StackContainer.h", + "SwapChainUtils.h", + "SystemUtils.cpp", + "SystemUtils.h", + "TypeTraits.h", + "TypedInteger.h", + "UnderlyingType.h", + "ityp_array.h", + "ityp_bitset.h", + "ityp_span.h", + "ityp_stack_vec.h", + "ityp_vector.h", + "vulkan_platform.h", + "xlib_with_undefs.h", + ] + + public_deps = [ ":dawn_version_gen" ] + + if (is_mac) { + sources += [ "SystemUtils_mac.mm" ] + } + + public_configs = [ ":internal_config" ] + deps = [ + "${dawn_root}/include/dawn:cpp_headers", + "${dawn_root}/include/dawn:headers", + ] + + if (is_win) { + sources += [ + "WindowsUtils.cpp", + "WindowsUtils.h", + "windows_with_undefs.h", + ] + } + if (dawn_enable_vulkan) { + public_deps += [ "${dawn_vulkan_headers_dir}:vulkan_headers" ] + } + if (is_android) { + libs = [ "log" ] + } + } +}
diff --git a/src/dawn/common/BitSetIterator.h b/src/dawn/common/BitSetIterator.h new file mode 100644 index 0000000..f14a76c --- /dev/null +++ b/src/dawn/common/BitSetIterator.h
@@ -0,0 +1,139 @@ +// Copyright 2017 The Dawn Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#ifndef COMMON_BITSETITERATOR_H_ +#define COMMON_BITSETITERATOR_H_ + +#include "dawn/common/Assert.h" +#include "dawn/common/Math.h" +#include "dawn/common/UnderlyingType.h" + +#include <bitset> +#include <limits> + +// This is ANGLE's BitSetIterator class with a customizable return type +// TODO(crbug.com/dawn/306): it could be optimized, in particular when N <= 64 + +template <typename T> +T roundUp(const T value, const T alignment) { + auto temp = value + alignment - static_cast<T>(1); + return temp - temp % alignment; +} + +template <size_t N, typename T> +class BitSetIterator final { + public: + BitSetIterator(const std::bitset<N>& bitset); + BitSetIterator(const BitSetIterator& other); + BitSetIterator& operator=(const BitSetIterator& other); + + class Iterator final { + public: + Iterator(const std::bitset<N>& bits); + Iterator& operator++(); + + bool operator==(const Iterator& other) const; + bool operator!=(const Iterator& other) const; + + T operator*() const { + using U = UnderlyingType<T>; + ASSERT(static_cast<U>(mCurrentBit) <= std::numeric_limits<U>::max()); + return static_cast<T>(static_cast<U>(mCurrentBit)); + } + + private: + unsigned long getNextBit(); + + static constexpr size_t kBitsPerWord = sizeof(uint32_t) * 8; + std::bitset<N> mBits; + unsigned long mCurrentBit; + unsigned long mOffset; + }; + + Iterator begin() const { + return Iterator(mBits); + } + Iterator end() const { + return Iterator(std::bitset<N>(0)); + } + + private: + const std::bitset<N> mBits; +}; + +template <size_t N, typename T> +BitSetIterator<N, T>::BitSetIterator(const std::bitset<N>& bitset) : mBits(bitset) { +} + +template <size_t N, typename T> +BitSetIterator<N, T>::BitSetIterator(const BitSetIterator& other) : mBits(other.mBits) { +} + +template <size_t N, typename T> +BitSetIterator<N, T>& BitSetIterator<N, T>::operator=(const BitSetIterator& other) { + mBits = other.mBits; + return *this; +} + +template <size_t N, typename T> +BitSetIterator<N, T>::Iterator::Iterator(const std::bitset<N>& bits) + : mBits(bits), mCurrentBit(0), mOffset(0) { + if (bits.any()) { + mCurrentBit = getNextBit(); + } else { + mOffset = static_cast<unsigned long>(roundUp(N, kBitsPerWord)); + } +} + +template <size_t N, typename T> +typename BitSetIterator<N, T>::Iterator& BitSetIterator<N, T>::Iterator::operator++() { + DAWN_ASSERT(mBits.any()); + mBits.set(mCurrentBit - mOffset, 0); + mCurrentBit = getNextBit(); + return *this; +} + +template <size_t N, typename T> +bool BitSetIterator<N, T>::Iterator::operator==(const Iterator& other) const { + return mOffset == other.mOffset && mBits == other.mBits; +} + +template <size_t N, typename T> +bool BitSetIterator<N, T>::Iterator::operator!=(const Iterator& other) const { + return !(*this == other); +} + +template <size_t N, typename T> +unsigned long BitSetIterator<N, T>::Iterator::getNextBit() { + static std::bitset<N> wordMask(std::numeric_limits<uint32_t>::max()); + + while (mOffset < N) { + uint32_t wordBits = static_cast<uint32_t>((mBits & wordMask).to_ulong()); + if (wordBits != 0ul) { + return ScanForward(wordBits) + mOffset; + } + + mBits >>= kBitsPerWord; + mOffset += kBitsPerWord; + } + return 0; +} + +// Helper to avoid needing to specify the template parameter size +template <size_t N> +BitSetIterator<N, uint32_t> IterateBitSet(const std::bitset<N>& bitset) { + return BitSetIterator<N, uint32_t>(bitset); +} + +#endif // COMMON_BITSETITERATOR_H_
diff --git a/src/dawn/common/CMakeLists.txt b/src/dawn/common/CMakeLists.txt new file mode 100644 index 0000000..1c28e71 --- /dev/null +++ b/src/dawn/common/CMakeLists.txt
@@ -0,0 +1,91 @@ +# Copyright 2020 The Dawn Authors +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +DawnGenerator( + SCRIPT "${Dawn_SOURCE_DIR}/generator/dawn_version_generator.py" + PRINT_NAME "Dawn version based utilities" + ARGS "--dawn-dir" + "${Dawn_SOURCE_DIR}" + RESULT_VARIABLE "DAWN_VERSION_AUTOGEN_SOURCES" +) + +add_library(dawn_common STATIC ${DAWN_DUMMY_FILE}) +target_sources(dawn_common PRIVATE + ${DAWN_VERSION_AUTOGEN_SOURCES} + "Alloc.h" + "Assert.cpp" + "Assert.h" + "BitSetIterator.h" + "Compiler.h" + "ConcurrentCache.h" + "Constants.h" + "CoreFoundationRef.h" + "DynamicLib.cpp" + "DynamicLib.h" + "GPUInfo.cpp" + "GPUInfo.h" + "HashUtils.h" + "IOKitRef.h" + "LinkedList.h" + "Log.cpp" + "Log.h" + "Math.cpp" + "Math.h" + "NSRef.h" + "NonCopyable.h" + "PlacementAllocated.h" + "Platform.h" + "Preprocessor.h" + "RefBase.h" + "RefCounted.cpp" + "RefCounted.h" + "Result.cpp" + "Result.h" + "SerialMap.h" + "SerialQueue.h" + "SerialStorage.h" + "SlabAllocator.cpp" + "SlabAllocator.h" + "StackContainer.h" + "SwapChainUtils.h" + "SystemUtils.cpp" + "SystemUtils.h" + "TypeTraits.h" + "TypedInteger.h" + "UnderlyingType.h" + "ityp_array.h" + "ityp_bitset.h" + "ityp_span.h" + "ityp_stack_vec.h" + "ityp_vector.h" + "vulkan_platform.h" + "xlib_with_undefs.h" +) + +if (WIN32) + target_sources(dawn_common PRIVATE + "WindowsUtils.cpp" + "WindowsUtils.h" + "windows_with_undefs.h" + ) +elseif(APPLE) + target_sources(dawn_common PRIVATE + "SystemUtils_mac.mm" + ) +endif() + +target_link_libraries(dawn_common PUBLIC dawncpp_headers PRIVATE dawn_internal_config) + +# TODO Android Log support +# TODO Vulkan headers support
diff --git a/src/dawn/common/Compiler.h b/src/dawn/common/Compiler.h new file mode 100644 index 0000000..ae4f5c0 --- /dev/null +++ b/src/dawn/common/Compiler.h
@@ -0,0 +1,97 @@ +// Copyright 2017 The Dawn Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#ifndef COMMON_COMPILER_H_ +#define COMMON_COMPILER_H_ + +// Defines macros for compiler-specific functionality +// - DAWN_COMPILER_[CLANG|GCC|MSVC]: Compiler detection +// - DAWN_BREAKPOINT(): Raises an exception and breaks in the debugger +// - DAWN_BUILTIN_UNREACHABLE(): Hints the compiler that a code path is unreachable +// - DAWN_(UN)?LIKELY(EXPR): Where available, hints the compiler that the expression will be true +// (resp. false) to help it generate code that leads to better branch prediction. +// - DAWN_UNUSED(EXPR): Prevents unused variable/expression warnings on EXPR. +// - DAWN_UNUSED_FUNC(FUNC): Prevents unused function warnings on FUNC. +// - DAWN_DECLARE_UNUSED: Prevents unused function warnings a subsequent declaration. +// Both DAWN_UNUSED_FUNC and DAWN_DECLARE_UNUSED may be necessary, e.g. to suppress clang's +// unneeded-internal-declaration warning. + +// Clang and GCC, check for __clang__ too to catch clang-cl masquarading as MSVC +#if defined(__GNUC__) || defined(__clang__) +# if defined(__clang__) +# define DAWN_COMPILER_CLANG +# else +# define DAWN_COMPILER_GCC +# endif + +# if defined(__i386__) || defined(__x86_64__) +# define DAWN_BREAKPOINT() __asm__ __volatile__("int $3\n\t") +# else +// TODO(cwallez@chromium.org): Implement breakpoint on all supported architectures +# define DAWN_BREAKPOINT() +# endif + +# define DAWN_BUILTIN_UNREACHABLE() __builtin_unreachable() +# define DAWN_LIKELY(x) __builtin_expect(!!(x), 1) +# define DAWN_UNLIKELY(x) __builtin_expect(!!(x), 0) + +# if !defined(__has_cpp_attribute) +# define __has_cpp_attribute(name) 0 +# endif + +# define DAWN_DECLARE_UNUSED __attribute__((unused)) +# if defined(NDEBUG) +# define DAWN_FORCE_INLINE inline __attribute__((always_inline)) +# endif +# define DAWN_NOINLINE __attribute__((noinline)) + +// MSVC +#elif defined(_MSC_VER) +# define DAWN_COMPILER_MSVC + +extern void __cdecl __debugbreak(void); +# define DAWN_BREAKPOINT() __debugbreak() + +# define DAWN_BUILTIN_UNREACHABLE() __assume(false) + +# define DAWN_DECLARE_UNUSED +# if defined(NDEBUG) +# define DAWN_FORCE_INLINE __forceinline +# endif +# define DAWN_NOINLINE __declspec(noinline) + +#else +# error "Unsupported compiler" +#endif + +// It seems that (void) EXPR works on all compilers to silence the unused variable warning. +#define DAWN_UNUSED(EXPR) (void)EXPR +// Likewise using static asserting on sizeof(&FUNC) seems to make it tagged as used +#define DAWN_UNUSED_FUNC(FUNC) static_assert(sizeof(&FUNC) == sizeof(void (*)())) + +// Add noop replacements for macros for features that aren't supported by the compiler. +#if !defined(DAWN_LIKELY) +# define DAWN_LIKELY(X) X +#endif +#if !defined(DAWN_UNLIKELY) +# define DAWN_UNLIKELY(X) X +#endif +#if !defined(DAWN_FORCE_INLINE) +# define DAWN_FORCE_INLINE inline +#endif +#if !defined(DAWN_NOINLINE) +# define DAWN_NOINLINE +#endif + +#endif // COMMON_COMPILER_H_
diff --git a/src/dawn/common/ConcurrentCache.h b/src/dawn/common/ConcurrentCache.h new file mode 100644 index 0000000..e11b646 --- /dev/null +++ b/src/dawn/common/ConcurrentCache.h
@@ -0,0 +1,54 @@ +// Copyright 2021 The Dawn Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#ifndef COMMON_CONCURRENT_CACHE_H_ +#define COMMON_CONCURRENT_CACHE_H_ + +#include "dawn/common/NonCopyable.h" + +#include <mutex> +#include <unordered_set> +#include <utility> + +template <typename T> +class ConcurrentCache : public NonMovable { + public: + ConcurrentCache() = default; + + T* Find(T* object) { + std::lock_guard<std::mutex> lock(mMutex); + auto iter = mCache.find(object); + if (iter == mCache.end()) { + return nullptr; + } + return *iter; + } + + std::pair<T*, bool> Insert(T* object) { + std::lock_guard<std::mutex> lock(mMutex); + auto [value, inserted] = mCache.insert(object); + return {*value, inserted}; + } + + size_t Erase(T* object) { + std::lock_guard<std::mutex> lock(mMutex); + return mCache.erase(object); + } + + private: + std::mutex mMutex; + std::unordered_set<T*, typename T::HashFunc, typename T::EqualityFunc> mCache; +}; + +#endif
diff --git a/src/dawn/common/Constants.h b/src/dawn/common/Constants.h new file mode 100644 index 0000000..13b5995 --- /dev/null +++ b/src/dawn/common/Constants.h
@@ -0,0 +1,68 @@ +// Copyright 2017 The Dawn Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#ifndef COMMON_CONSTANTS_H_ +#define COMMON_CONSTANTS_H_ + +#include <cstdint> + +static constexpr uint32_t kMaxBindGroups = 4u; +static constexpr uint8_t kMaxVertexAttributes = 16u; +static constexpr uint8_t kMaxVertexBuffers = 8u; +static constexpr uint32_t kMaxVertexBufferArrayStride = 2048u; +static constexpr uint32_t kNumStages = 3; +static constexpr uint8_t kMaxColorAttachments = 8u; +static constexpr uint32_t kTextureBytesPerRowAlignment = 256u; +static constexpr uint32_t kMaxInterStageShaderComponents = 60u; +static constexpr uint32_t kMaxInterStageShaderVariables = kMaxInterStageShaderComponents / 4; + +// Per stage limits +static constexpr uint32_t kMaxSampledTexturesPerShaderStage = 16; +static constexpr uint32_t kMaxSamplersPerShaderStage = 16; +static constexpr uint32_t kMaxStorageBuffersPerShaderStage = 8; +static constexpr uint32_t kMaxStorageTexturesPerShaderStage = 4; +static constexpr uint32_t kMaxUniformBuffersPerShaderStage = 12; + +// Per pipeline layout limits +static constexpr uint32_t kMaxDynamicUniformBuffersPerPipelineLayout = 8u; +static constexpr uint32_t kMaxDynamicStorageBuffersPerPipelineLayout = 4u; + +// Indirect command sizes +static constexpr uint64_t kDispatchIndirectSize = 3 * sizeof(uint32_t); +static constexpr uint64_t kDrawIndirectSize = 4 * sizeof(uint32_t); +static constexpr uint64_t kDrawIndexedIndirectSize = 5 * sizeof(uint32_t); + +// Non spec defined constants. +static constexpr float kLodMin = 0.0; +static constexpr float kLodMax = 1000.0; + +// Offset alignment for CopyB2B. Strictly speaking this alignment is required only +// on macOS, but we decide to do it on all platforms. +static constexpr uint64_t kCopyBufferToBufferOffsetAlignment = 4u; + +// The maximum size of visibilityResultBuffer is 256KB on Metal, to fit the restriction, limit the +// maximum size of query set to 64KB. The size of a query is 8-bytes, the maximum query count is 64 +// * 1024 / 8. +static constexpr uint32_t kMaxQueryCount = 8192u; + +// An external texture occupies multiple binding slots. These are the per-external-texture bindings +// needed. +static constexpr uint8_t kSampledTexturesPerExternalTexture = 4u; +static constexpr uint8_t kSamplersPerExternalTexture = 1u; +static constexpr uint8_t kUniformsPerExternalTexture = 1u; + +// A spec defined constant but that doesn't have a name. +static constexpr uint32_t kMaxBindingNumber = 65535; + +#endif // COMMON_CONSTANTS_H_
diff --git a/src/dawn/common/CoreFoundationRef.h b/src/dawn/common/CoreFoundationRef.h new file mode 100644 index 0000000..e6cafbe --- /dev/null +++ b/src/dawn/common/CoreFoundationRef.h
@@ -0,0 +1,46 @@ +// Copyright 2020 The Dawn Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#ifndef COMMON_COREFOUNDATIONREF_H_ +#define COMMON_COREFOUNDATIONREF_H_ + +#include "dawn/common/RefBase.h" + +#include <CoreFoundation/CoreFoundation.h> + +template <typename T> +struct CoreFoundationRefTraits { + static constexpr T kNullValue = nullptr; + static void Reference(T value) { + CFRetain(value); + } + static void Release(T value) { + CFRelease(value); + } +}; + +template <typename T> +class CFRef : public RefBase<T, CoreFoundationRefTraits<T>> { + public: + using RefBase<T, CoreFoundationRefTraits<T>>::RefBase; +}; + +template <typename T> +CFRef<T> AcquireCFRef(T pointee) { + CFRef<T> ref; + ref.Acquire(pointee); + return ref; +} + +#endif // COMMON_COREFOUNDATIONREF_H_
diff --git a/src/dawn/common/DynamicLib.cpp b/src/dawn/common/DynamicLib.cpp new file mode 100644 index 0000000..ab4f2d7 --- /dev/null +++ b/src/dawn/common/DynamicLib.cpp
@@ -0,0 +1,106 @@ +// Copyright 2017 The Dawn Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include "dawn/common/DynamicLib.h" + +#include "dawn/common/Platform.h" + +#if DAWN_PLATFORM_WINDOWS +# include "dawn/common/windows_with_undefs.h" +# if DAWN_PLATFORM_WINUWP +# include "dawn/common/WindowsUtils.h" +# endif +#elif DAWN_PLATFORM_POSIX +# include <dlfcn.h> +#else +# error "Unsupported platform for DynamicLib" +#endif + +DynamicLib::~DynamicLib() { + Close(); +} + +DynamicLib::DynamicLib(DynamicLib&& other) { + std::swap(mHandle, other.mHandle); +} + +DynamicLib& DynamicLib::operator=(DynamicLib&& other) { + std::swap(mHandle, other.mHandle); + return *this; +} + +bool DynamicLib::Valid() const { + return mHandle != nullptr; +} + +bool DynamicLib::Open(const std::string& filename, std::string* error) { +#if DAWN_PLATFORM_WINDOWS +# if DAWN_PLATFORM_WINUWP + mHandle = LoadPackagedLibrary(UTF8ToWStr(filename.c_str()).c_str(), 0); +# else + mHandle = LoadLibraryA(filename.c_str()); +# endif + if (mHandle == nullptr && error != nullptr) { + *error = "Windows Error: " + std::to_string(GetLastError()); + } +#elif DAWN_PLATFORM_POSIX + mHandle = dlopen(filename.c_str(), RTLD_NOW); + + if (mHandle == nullptr && error != nullptr) { + *error = dlerror(); + } +#else +# error "Unsupported platform for DynamicLib" +#endif + + return mHandle != nullptr; +} + +void DynamicLib::Close() { + if (mHandle == nullptr) { + return; + } + +#if DAWN_PLATFORM_WINDOWS + FreeLibrary(static_cast<HMODULE>(mHandle)); +#elif DAWN_PLATFORM_POSIX + dlclose(mHandle); +#else +# error "Unsupported platform for DynamicLib" +#endif + + mHandle = nullptr; +} + +void* DynamicLib::GetProc(const std::string& procName, std::string* error) const { + void* proc = nullptr; + +#if DAWN_PLATFORM_WINDOWS + proc = reinterpret_cast<void*>(GetProcAddress(static_cast<HMODULE>(mHandle), procName.c_str())); + + if (proc == nullptr && error != nullptr) { + *error = "Windows Error: " + std::to_string(GetLastError()); + } +#elif DAWN_PLATFORM_POSIX + proc = reinterpret_cast<void*>(dlsym(mHandle, procName.c_str())); + + if (proc == nullptr && error != nullptr) { + *error = dlerror(); + } +#else +# error "Unsupported platform for DynamicLib" +#endif + + return proc; +}
diff --git a/src/dawn/common/DynamicLib.h b/src/dawn/common/DynamicLib.h new file mode 100644 index 0000000..66d846e --- /dev/null +++ b/src/dawn/common/DynamicLib.h
@@ -0,0 +1,54 @@ +// Copyright 2017 The Dawn Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#ifndef COMMON_DYNAMICLIB_H_ +#define COMMON_DYNAMICLIB_H_ + +#include "dawn/common/Assert.h" + +#include <string> +#include <type_traits> + +class DynamicLib { + public: + DynamicLib() = default; + ~DynamicLib(); + + DynamicLib(const DynamicLib&) = delete; + DynamicLib& operator=(const DynamicLib&) = delete; + + DynamicLib(DynamicLib&& other); + DynamicLib& operator=(DynamicLib&& other); + + bool Valid() const; + + bool Open(const std::string& filename, std::string* error = nullptr); + void Close(); + + void* GetProc(const std::string& procName, std::string* error = nullptr) const; + + template <typename T> + bool GetProc(T** proc, const std::string& procName, std::string* error = nullptr) const { + ASSERT(proc != nullptr); + static_assert(std::is_function<T>::value); + + *proc = reinterpret_cast<T*>(GetProc(procName, error)); + return *proc != nullptr; + } + + private: + void* mHandle = nullptr; +}; + +#endif // COMMON_DYNAMICLIB_H_
diff --git a/src/dawn/common/GPUInfo.cpp b/src/dawn/common/GPUInfo.cpp new file mode 100644 index 0000000..ddd8459 --- /dev/null +++ b/src/dawn/common/GPUInfo.cpp
@@ -0,0 +1,108 @@ +// Copyright 2019 The Dawn Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include "dawn/common/GPUInfo.h" + +#include "dawn/common/Assert.h" + +#include <algorithm> +#include <array> + +namespace gpu_info { + namespace { + // Intel + // Referenced from the following Mesa source code: + // https://github.com/mesa3d/mesa/blob/master/include/pci_ids/i965_pci_ids.h + // gen9 + const std::array<uint32_t, 25> Skylake = { + {0x1902, 0x1906, 0x190A, 0x190B, 0x190E, 0x1912, 0x1913, 0x1915, 0x1916, + 0x1917, 0x191A, 0x191B, 0x191D, 0x191E, 0x1921, 0x1923, 0x1926, 0x1927, + 0x192A, 0x192B, 0x192D, 0x1932, 0x193A, 0x193B, 0x193D}}; + // gen9p5 + const std::array<uint32_t, 20> Kabylake = { + {0x5916, 0x5913, 0x5906, 0x5926, 0x5921, 0x5915, 0x590E, 0x591E, 0x5912, 0x5917, + 0x5902, 0x591B, 0x593B, 0x590B, 0x591A, 0x590A, 0x591D, 0x5908, 0x5923, 0x5927}}; + const std::array<uint32_t, 17> Coffeelake = { + {0x87CA, 0x3E90, 0x3E93, 0x3E99, 0x3E9C, 0x3E91, 0x3E92, 0x3E96, 0x3E98, 0x3E9A, 0x3E9B, + 0x3E94, 0x3EA9, 0x3EA5, 0x3EA6, 0x3EA7, 0x3EA8}}; + const std::array<uint32_t, 5> Whiskylake = {{0x3EA1, 0x3EA4, 0x3EA0, 0x3EA3, 0x3EA2}}; + const std::array<uint32_t, 21> Cometlake = { + {0x9B21, 0x9BA0, 0x9BA2, 0x9BA4, 0x9BA5, 0x9BA8, 0x9BAA, 0x9BAB, 0x9BAC, 0x9B41, 0x9BC0, + 0x9BC2, 0x9BC4, 0x9BC5, 0x9BC6, 0x9BC8, 0x9BCA, 0x9BCB, 0x9BCC, 0x9BE6, 0x9BF6}}; + + // According to Intel graphics driver version schema, build number is generated from the + // last two fields. + // See https://www.intel.com/content/www/us/en/support/articles/000005654/graphics.html for + // more details. + uint32_t GetIntelD3DDriverBuildNumber(const D3DDriverVersion& driverVersion) { + return driverVersion[2] * 10000 + driverVersion[3]; + } + + } // anonymous namespace + + bool IsAMD(PCIVendorID vendorId) { + return vendorId == kVendorID_AMD; + } + bool IsARM(PCIVendorID vendorId) { + return vendorId == kVendorID_ARM; + } + bool IsImgTec(PCIVendorID vendorId) { + return vendorId == kVendorID_ImgTec; + } + bool IsIntel(PCIVendorID vendorId) { + return vendorId == kVendorID_Intel; + } + bool IsMesa(PCIVendorID vendorId) { + return vendorId == kVendorID_Mesa; + } + bool IsNvidia(PCIVendorID vendorId) { + return vendorId == kVendorID_Nvidia; + } + bool IsQualcomm(PCIVendorID vendorId) { + return vendorId == kVendorID_Qualcomm; + } + bool IsSwiftshader(PCIVendorID vendorId, PCIDeviceID deviceId) { + return vendorId == kVendorID_Google && deviceId == kDeviceID_Swiftshader; + } + bool IsWARP(PCIVendorID vendorId, PCIDeviceID deviceId) { + return vendorId == kVendorID_Microsoft && deviceId == kDeviceID_WARP; + } + + int CompareD3DDriverVersion(PCIVendorID vendorId, + const D3DDriverVersion& version1, + const D3DDriverVersion& version2) { + if (IsIntel(vendorId)) { + uint32_t buildNumber1 = GetIntelD3DDriverBuildNumber(version1); + uint32_t buildNumber2 = GetIntelD3DDriverBuildNumber(version2); + return buildNumber1 < buildNumber2 ? -1 : (buildNumber1 == buildNumber2 ? 0 : 1); + } + + // TODO(crbug.com/dawn/823): support other GPU vendors + UNREACHABLE(); + return 0; + } + + // Intel GPUs + bool IsSkylake(PCIDeviceID deviceId) { + return std::find(Skylake.cbegin(), Skylake.cend(), deviceId) != Skylake.cend(); + } + bool IsKabylake(PCIDeviceID deviceId) { + return std::find(Kabylake.cbegin(), Kabylake.cend(), deviceId) != Kabylake.cend(); + } + bool IsCoffeelake(PCIDeviceID deviceId) { + return (std::find(Coffeelake.cbegin(), Coffeelake.cend(), deviceId) != Coffeelake.cend()) || + (std::find(Whiskylake.cbegin(), Whiskylake.cend(), deviceId) != Whiskylake.cend()) || + (std::find(Cometlake.cbegin(), Cometlake.cend(), deviceId) != Cometlake.cend()); + } +} // namespace gpu_info
diff --git a/src/dawn/common/GPUInfo.h b/src/dawn/common/GPUInfo.h new file mode 100644 index 0000000..26c9103 --- /dev/null +++ b/src/dawn/common/GPUInfo.h
@@ -0,0 +1,66 @@ +// Copyright 2019 The Dawn Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#ifndef COMMON_GPUINFO_H +#define COMMON_GPUINFO_H + +#include <array> +#include <cstdint> + +using PCIVendorID = uint32_t; +using PCIDeviceID = uint32_t; + +namespace gpu_info { + + static constexpr PCIVendorID kVendorID_AMD = 0x1002; + static constexpr PCIVendorID kVendorID_ARM = 0x13B5; + static constexpr PCIVendorID kVendorID_ImgTec = 0x1010; + static constexpr PCIVendorID kVendorID_Intel = 0x8086; + static constexpr PCIVendorID kVendorID_Mesa = 0x10005; + static constexpr PCIVendorID kVendorID_Nvidia = 0x10DE; + static constexpr PCIVendorID kVendorID_Qualcomm = 0x5143; + static constexpr PCIVendorID kVendorID_Google = 0x1AE0; + static constexpr PCIVendorID kVendorID_Microsoft = 0x1414; + + static constexpr PCIDeviceID kDeviceID_Swiftshader = 0xC0DE; + static constexpr PCIDeviceID kDeviceID_WARP = 0x8c; + + bool IsAMD(PCIVendorID vendorId); + bool IsARM(PCIVendorID vendorId); + bool IsImgTec(PCIVendorID vendorId); + bool IsIntel(PCIVendorID vendorId); + bool IsMesa(PCIVendorID vendorId); + bool IsNvidia(PCIVendorID vendorId); + bool IsQualcomm(PCIVendorID vendorId); + bool IsSwiftshader(PCIVendorID vendorId, PCIDeviceID deviceId); + bool IsWARP(PCIVendorID vendorId, PCIDeviceID deviceId); + + using D3DDriverVersion = std::array<uint16_t, 4>; + + // Do comparison between two driver versions. Currently we only support the comparison between + // Intel D3D driver versions. + // - Return -1 if build number of version1 is smaller + // - Return 1 if build number of version1 is bigger + // - Return 0 if version1 and version2 represent same driver version + int CompareD3DDriverVersion(PCIVendorID vendorId, + const D3DDriverVersion& version1, + const D3DDriverVersion& version2); + + // Intel architectures + bool IsSkylake(PCIDeviceID deviceId); + bool IsKabylake(PCIDeviceID deviceId); + bool IsCoffeelake(PCIDeviceID deviceId); + +} // namespace gpu_info +#endif // COMMON_GPUINFO_H
diff --git a/src/dawn/common/HashUtils.h b/src/dawn/common/HashUtils.h new file mode 100644 index 0000000..e59e8c5 --- /dev/null +++ b/src/dawn/common/HashUtils.h
@@ -0,0 +1,101 @@ +// Copyright 2018 The Dawn Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#ifndef COMMON_HASHUTILS_H_ +#define COMMON_HASHUTILS_H_ + +#include "dawn/common/Platform.h" +#include "dawn/common/TypedInteger.h" +#include "dawn/common/ityp_bitset.h" + +#include <bitset> +#include <functional> + +// Wrapper around std::hash to make it a templated function instead of a functor. It is marginally +// nicer, and avoids adding to the std namespace to add hashing of other types. +template <typename T> +size_t Hash(const T& value) { + return std::hash<T>()(value); +} + +// Add hashing of TypedIntegers +template <typename Tag, typename T> +size_t Hash(const TypedInteger<Tag, T>& value) { + return Hash(static_cast<T>(value)); +} + +// When hashing sparse structures we want to iteratively build a hash value with only parts of the +// data. HashCombine "hashes" together an existing hash and hashable values. +// +// Example usage to compute the hash of a mask and values corresponding to the mask: +// +// size_t hash = Hash(mask): +// for (uint32_t i : IterateBitSet(mask)) { HashCombine(&hash, hashables[i]); } +// return hash; +template <typename T> +void HashCombine(size_t* hash, const T& value) { +#if defined(DAWN_PLATFORM_64_BIT) + const size_t offset = 0x9e3779b97f4a7c16; +#elif defined(DAWN_PLATFORM_32_BIT) + const size_t offset = 0x9e3779b9; +#else +# error "Unsupported platform" +#endif + *hash ^= Hash(value) + offset + (*hash << 6) + (*hash >> 2); +} + +template <typename T, typename... Args> +void HashCombine(size_t* hash, const T& value, const Args&... args) { + HashCombine(hash, value); + HashCombine(hash, args...); +} + +// Workaround a bug between clang++ and libstdlibc++ by defining our own hashing for bitsets. +// When _GLIBCXX_DEBUG is enabled libstdc++ wraps containers into debug containers. For bitset this +// means what is normally std::bitset is defined as std::__cxx1988::bitset and is replaced by the +// debug version of bitset. +// When hashing, std::hash<std::bitset> proxies the call to std::hash<std::__cxx1998::bitset> and +// fails on clang because the latter tries to access the private _M_getdata member of the bitset. +// It looks like it should work because the non-debug bitset declares +// +// friend struct std::hash<bitset> // bitset is the name of the class itself +// +// which should friend std::hash<std::__cxx1998::bitset> but somehow doesn't work on clang. +#if defined(_GLIBCXX_DEBUG) +template <size_t N> +size_t Hash(const std::bitset<N>& value) { + constexpr size_t kWindowSize = sizeof(unsigned long long); + + std::bitset<N> bits = value; + size_t hash = 0; + for (size_t processedBits = 0; processedBits < N; processedBits += kWindowSize) { + HashCombine(&hash, bits.to_ullong()); + bits >>= kWindowSize; + } + + return hash; +} +#endif + +namespace std { + template <typename Index, size_t N> + struct hash<ityp::bitset<Index, N>> { + public: + size_t operator()(const ityp::bitset<Index, N>& value) const { + return Hash(static_cast<const std::bitset<N>&>(value)); + } + }; +} // namespace std + +#endif // COMMON_HASHUTILS_H_
diff --git a/src/dawn/common/IOKitRef.h b/src/dawn/common/IOKitRef.h new file mode 100644 index 0000000..4ff4413 --- /dev/null +++ b/src/dawn/common/IOKitRef.h
@@ -0,0 +1,46 @@ +// Copyright 2020 The Dawn Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#ifndef COMMON_IOKITREF_H_ +#define COMMON_IOKITREF_H_ + +#include "dawn/common/RefBase.h" + +#include <IOKit/IOKitLib.h> + +template <typename T> +struct IOKitRefTraits { + static constexpr T kNullValue = IO_OBJECT_NULL; + static void Reference(T value) { + IOObjectRetain(value); + } + static void Release(T value) { + IOObjectRelease(value); + } +}; + +template <typename T> +class IORef : public RefBase<T, IOKitRefTraits<T>> { + public: + using RefBase<T, IOKitRefTraits<T>>::RefBase; +}; + +template <typename T> +IORef<T> AcquireIORef(T pointee) { + IORef<T> ref; + ref.Acquire(pointee); + return ref; +} + +#endif // COMMON_IOKITREF_H_
diff --git a/src/dawn/common/LinkedList.h b/src/dawn/common/LinkedList.h new file mode 100644 index 0000000..673f596 --- /dev/null +++ b/src/dawn/common/LinkedList.h
@@ -0,0 +1,274 @@ +// Copyright (c) 2009 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +// This file is a copy of Chromium's /src/base/containers/linked_list.h with the following +// modifications: +// - Added iterators for ranged based iterations +// - Added in list check before removing node to prevent segfault, now returns true iff removed +// - Added MoveInto functionality for moving list elements to another list + +#ifndef COMMON_LINKED_LIST_H +#define COMMON_LINKED_LIST_H + +#include "dawn/common/Assert.h" + +// Simple LinkedList type. (See the Q&A section to understand how this +// differs from std::list). +// +// To use, start by declaring the class which will be contained in the linked +// list, as extending LinkNode (this gives it next/previous pointers). +// +// class MyNodeType : public LinkNode<MyNodeType> { +// ... +// }; +// +// Next, to keep track of the list's head/tail, use a LinkedList instance: +// +// LinkedList<MyNodeType> list; +// +// To add elements to the list, use any of LinkedList::Append, +// LinkNode::InsertBefore, or LinkNode::InsertAfter: +// +// LinkNode<MyNodeType>* n1 = ...; +// LinkNode<MyNodeType>* n2 = ...; +// LinkNode<MyNodeType>* n3 = ...; +// +// list.Append(n1); +// list.Append(n3); +// n3->InsertBefore(n3); +// +// Lastly, to iterate through the linked list forwards: +// +// for (LinkNode<MyNodeType>* node = list.head(); +// node != list.end(); +// node = node->next()) { +// MyNodeType* value = node->value(); +// ... +// } +// +// for (LinkNode<MyNodeType*> node : list) { +// MyNodeType* value = node->value(); +// ... +// } +// +// Or to iterate the linked list backwards: +// +// for (LinkNode<MyNodeType>* node = list.tail(); +// node != list.end(); +// node = node->previous()) { +// MyNodeType* value = node->value(); +// ... +// } +// +// Questions and Answers: +// +// Q. Should I use std::list or base::LinkedList? +// +// A. The main reason to use base::LinkedList over std::list is +// performance. If you don't care about the performance differences +// then use an STL container, as it makes for better code readability. +// +// Comparing the performance of base::LinkedList<T> to std::list<T*>: +// +// * Erasing an element of type T* from base::LinkedList<T> is +// an O(1) operation. Whereas for std::list<T*> it is O(n). +// That is because with std::list<T*> you must obtain an +// iterator to the T* element before you can call erase(iterator). +// +// * Insertion operations with base::LinkedList<T> never require +// heap allocations. +// +// Q. How does base::LinkedList implementation differ from std::list? +// +// A. Doubly-linked lists are made up of nodes that contain "next" and +// "previous" pointers that reference other nodes in the list. +// +// With base::LinkedList<T>, the type being inserted already reserves +// space for the "next" and "previous" pointers (base::LinkNode<T>*). +// Whereas with std::list<T> the type can be anything, so the implementation +// needs to glue on the "next" and "previous" pointers using +// some internal node type. + +// Forward declarations of the types in order for recursive referencing and friending. +template <typename T> +class LinkNode; +template <typename T> +class LinkedList; + +template <typename T> +class LinkNode { + public: + LinkNode() : previous_(nullptr), next_(nullptr) { + } + LinkNode(LinkNode<T>* previous, LinkNode<T>* next) : previous_(previous), next_(next) { + } + + LinkNode(LinkNode<T>&& rhs) { + next_ = rhs.next_; + rhs.next_ = nullptr; + previous_ = rhs.previous_; + rhs.previous_ = nullptr; + + // If the node belongs to a list, next_ and previous_ are both non-null. + // Otherwise, they are both null. + if (next_) { + next_->previous_ = this; + previous_->next_ = this; + } + } + + // Insert |this| into the linked list, before |e|. + void InsertBefore(LinkNode<T>* e) { + this->next_ = e; + this->previous_ = e->previous_; + e->previous_->next_ = this; + e->previous_ = this; + } + + // Insert |this| into the linked list, after |e|. + void InsertAfter(LinkNode<T>* e) { + this->next_ = e->next_; + this->previous_ = e; + e->next_->previous_ = this; + e->next_ = this; + } + + // Check if |this| is in a list. + bool IsInList() const { + ASSERT((this->previous_ == nullptr) == (this->next_ == nullptr)); + return this->next_ != nullptr; + } + + // Remove |this| from the linked list. Returns true iff removed from a list. + bool RemoveFromList() { + if (!IsInList()) { + return false; + } + + this->previous_->next_ = this->next_; + this->next_->previous_ = this->previous_; + // next() and previous() return non-null if and only this node is not in any list. + this->next_ = nullptr; + this->previous_ = nullptr; + return true; + } + + LinkNode<T>* previous() const { + return previous_; + } + + LinkNode<T>* next() const { + return next_; + } + + // Cast from the node-type to the value type. + const T* value() const { + return static_cast<const T*>(this); + } + + T* value() { + return static_cast<T*>(this); + } + + private: + friend class LinkedList<T>; + LinkNode<T>* previous_; + LinkNode<T>* next_; +}; + +template <typename T> +class LinkedList { + public: + // The "root" node is self-referential, and forms the basis of a circular + // list (root_.next() will point back to the start of the list, + // and root_->previous() wraps around to the end of the list). + LinkedList() : root_(&root_, &root_) { + } + + ~LinkedList() { + // If any LinkNodes still exist in the LinkedList, there will be outstanding references to + // root_ even after it has been freed. We should remove root_ from the list to prevent any + // future access. + root_.RemoveFromList(); + } + + // Appends |e| to the end of the linked list. + void Append(LinkNode<T>* e) { + e->InsertBefore(&root_); + } + + // Moves all elements (in order) of the list and appends them into |l| leaving the list empty. + void MoveInto(LinkedList<T>* l) { + if (empty()) { + return; + } + l->root_.previous_->next_ = root_.next_; + root_.next_->previous_ = l->root_.previous_; + l->root_.previous_ = root_.previous_; + root_.previous_->next_ = &l->root_; + + root_.next_ = &root_; + root_.previous_ = &root_; + } + + LinkNode<T>* head() const { + return root_.next(); + } + + LinkNode<T>* tail() const { + return root_.previous(); + } + + const LinkNode<T>* end() const { + return &root_; + } + + bool empty() const { + return head() == end(); + } + + private: + LinkNode<T> root_; +}; + +template <typename T> +class LinkedListIterator { + public: + LinkedListIterator(LinkNode<T>* node) : current_(node), next_(node->next()) { + } + + // We keep an early reference to the next node in the list so that even if the current element + // is modified or removed from the list, we have a valid next node. + LinkedListIterator<T> const& operator++() { + current_ = next_; + next_ = current_->next(); + return *this; + } + + bool operator!=(const LinkedListIterator<T>& other) const { + return current_ != other.current_; + } + + LinkNode<T>* operator*() const { + return current_; + } + + private: + LinkNode<T>* current_; + LinkNode<T>* next_; +}; + +template <typename T> +LinkedListIterator<T> begin(LinkedList<T>& l) { + return LinkedListIterator<T>(l.head()); +} + +// Free end function does't use LinkedList<T>::end because of it's const nature. Instead we wrap +// around from tail. +template <typename T> +LinkedListIterator<T> end(LinkedList<T>& l) { + return LinkedListIterator<T>(l.tail()->next()); +} + +#endif // COMMON_LINKED_LIST_H
diff --git a/src/dawn/common/Log.cpp b/src/dawn/common/Log.cpp new file mode 100644 index 0000000..b85094b7 --- /dev/null +++ b/src/dawn/common/Log.cpp
@@ -0,0 +1,116 @@ +// Copyright 2019 The Dawn Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include "dawn/common/Log.h" + +#include "dawn/common/Assert.h" +#include "dawn/common/Platform.h" + +#include <cstdio> + +#if defined(DAWN_PLATFORM_ANDROID) +# include <android/log.h> +#endif + +namespace dawn { + + namespace { + + const char* SeverityName(LogSeverity severity) { + switch (severity) { + case LogSeverity::Debug: + return "Debug"; + case LogSeverity::Info: + return "Info"; + case LogSeverity::Warning: + return "Warning"; + case LogSeverity::Error: + return "Error"; + default: + UNREACHABLE(); + return ""; + } + } + +#if defined(DAWN_PLATFORM_ANDROID) + android_LogPriority AndroidLogPriority(LogSeverity severity) { + switch (severity) { + case LogSeverity::Debug: + return ANDROID_LOG_INFO; + case LogSeverity::Info: + return ANDROID_LOG_INFO; + case LogSeverity::Warning: + return ANDROID_LOG_WARN; + case LogSeverity::Error: + return ANDROID_LOG_ERROR; + default: + UNREACHABLE(); + return ANDROID_LOG_ERROR; + } + } +#endif // defined(DAWN_PLATFORM_ANDROID) + + } // anonymous namespace + + LogMessage::LogMessage(LogSeverity severity) : mSeverity(severity) { + } + + LogMessage::~LogMessage() { + std::string fullMessage = mStream.str(); + + // If this message has been moved, its stream is empty. + if (fullMessage.empty()) { + return; + } + + const char* severityName = SeverityName(mSeverity); + +#if defined(DAWN_PLATFORM_ANDROID) + android_LogPriority androidPriority = AndroidLogPriority(mSeverity); + __android_log_print(androidPriority, "Dawn", "%s: %s\n", severityName, fullMessage.c_str()); +#else // defined(DAWN_PLATFORM_ANDROID) + FILE* outputStream = stdout; + if (mSeverity == LogSeverity::Warning || mSeverity == LogSeverity::Error) { + outputStream = stderr; + } + + // Note: we use fprintf because <iostream> includes static initializers. + fprintf(outputStream, "%s: %s\n", severityName, fullMessage.c_str()); + fflush(outputStream); +#endif // defined(DAWN_PLATFORM_ANDROID) + } + + LogMessage DebugLog() { + return {LogSeverity::Debug}; + } + + LogMessage InfoLog() { + return {LogSeverity::Info}; + } + + LogMessage WarningLog() { + return {LogSeverity::Warning}; + } + + LogMessage ErrorLog() { + return {LogSeverity::Error}; + } + + LogMessage DebugLog(const char* file, const char* function, int line) { + LogMessage message = DebugLog(); + message << file << ":" << line << "(" << function << ")"; + return message; + } + +} // namespace dawn
diff --git a/src/dawn/common/Log.h b/src/dawn/common/Log.h new file mode 100644 index 0000000..0504af6 --- /dev/null +++ b/src/dawn/common/Log.h
@@ -0,0 +1,95 @@ +// Copyright 2019 The Dawn Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#ifndef COMMON_LOG_H_ +#define COMMON_LOG_H_ + +// Dawn targets shouldn't use iostream or printf directly for several reasons: +// - iostream adds static initializers which we want to avoid. +// - printf and iostream don't show up in logcat on Android so printf debugging doesn't work but +// log-message debugging does. +// - log severity helps provide intent compared to a printf. +// +// Logging should in general be avoided: errors should go through the regular WebGPU error reporting +// mechanism and others form of logging should (TODO: eventually) go through the logging dependency +// injection, so for example they show up in Chromium's about:gpu page. Nonetheless there are some +// cases where logging is necessary and when this file was first introduced we needed to replace all +// uses of iostream so we could see them in Android's logcat. +// +// Regular logging is done using the [Debug|Info|Warning|Error]Log() function this way: +// +// InfoLog() << things << that << ostringstream << supports; // No need for a std::endl or "\n" +// +// It creates a LogMessage object that isn't stored anywhere and gets its destructor called +// immediately which outputs the stored ostringstream in the right place. +// +// This file also contains DAWN_DEBUG for "printf debugging" which works on Android and +// additionally outputs the file, line and function name. Use it this way: +// +// // Pepper this throughout code to get a log of the execution +// DAWN_DEBUG(); +// +// // Get more information +// DAWN_DEBUG() << texture.GetFormat(); + +#include <sstream> + +namespace dawn { + + // Log levels mostly used to signal intent where the log message is produced and used to route + // the message to the correct output. + enum class LogSeverity { + Debug, + Info, + Warning, + Error, + }; + + // Essentially an ostringstream that will print itself in its destructor. + class LogMessage { + public: + LogMessage(LogSeverity severity); + ~LogMessage(); + + LogMessage(LogMessage&& other) = default; + LogMessage& operator=(LogMessage&& other) = default; + + template <typename T> + LogMessage& operator<<(T&& value) { + mStream << value; + return *this; + } + + private: + LogMessage(const LogMessage& other) = delete; + LogMessage& operator=(const LogMessage& other) = delete; + + LogSeverity mSeverity; + std::ostringstream mStream; + }; + + // Short-hands to create a LogMessage with the respective severity. + LogMessage DebugLog(); + LogMessage InfoLog(); + LogMessage WarningLog(); + LogMessage ErrorLog(); + + // DAWN_DEBUG is a helper macro that creates a DebugLog and outputs file/line/function + // information + LogMessage DebugLog(const char* file, const char* function, int line); +#define DAWN_DEBUG() ::dawn::DebugLog(__FILE__, __func__, __LINE__) + +} // namespace dawn + +#endif // COMMON_LOG_H_
diff --git a/src/dawn/common/Math.cpp b/src/dawn/common/Math.cpp new file mode 100644 index 0000000..bd936a8 --- /dev/null +++ b/src/dawn/common/Math.cpp
@@ -0,0 +1,160 @@ +// Copyright 2017 The Dawn Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include "dawn/common/Math.h" + +#include "dawn/common/Assert.h" +#include "dawn/common/Platform.h" + +#include <algorithm> +#include <cmath> +#include <limits> + +#if defined(DAWN_COMPILER_MSVC) +# include <intrin.h> +#endif + +uint32_t ScanForward(uint32_t bits) { + ASSERT(bits != 0); +#if defined(DAWN_COMPILER_MSVC) + unsigned long firstBitIndex = 0ul; + unsigned char ret = _BitScanForward(&firstBitIndex, bits); + ASSERT(ret != 0); + return firstBitIndex; +#else + return static_cast<uint32_t>(__builtin_ctz(bits)); +#endif +} + +uint32_t Log2(uint32_t value) { + ASSERT(value != 0); +#if defined(DAWN_COMPILER_MSVC) + unsigned long firstBitIndex = 0ul; + unsigned char ret = _BitScanReverse(&firstBitIndex, value); + ASSERT(ret != 0); + return firstBitIndex; +#else + return 31 - static_cast<uint32_t>(__builtin_clz(value)); +#endif +} + +uint32_t Log2(uint64_t value) { + ASSERT(value != 0); +#if defined(DAWN_COMPILER_MSVC) +# if defined(DAWN_PLATFORM_64_BIT) + unsigned long firstBitIndex = 0ul; + unsigned char ret = _BitScanReverse64(&firstBitIndex, value); + ASSERT(ret != 0); + return firstBitIndex; +# else // defined(DAWN_PLATFORM_64_BIT) + unsigned long firstBitIndex = 0ul; + if (_BitScanReverse(&firstBitIndex, value >> 32)) { + return firstBitIndex + 32; + } + unsigned char ret = _BitScanReverse(&firstBitIndex, value & 0xFFFFFFFF); + ASSERT(ret != 0); + return firstBitIndex; +# endif // defined(DAWN_PLATFORM_64_BIT) +#else // defined(DAWN_COMPILER_MSVC) + return 63 - static_cast<uint32_t>(__builtin_clzll(value)); +#endif // defined(DAWN_COMPILER_MSVC) +} + +uint64_t NextPowerOfTwo(uint64_t n) { + if (n <= 1) { + return 1; + } + + return 1ull << (Log2(n - 1) + 1); +} + +bool IsPowerOfTwo(uint64_t n) { + ASSERT(n != 0); + return (n & (n - 1)) == 0; +} + +bool IsPtrAligned(const void* ptr, size_t alignment) { + ASSERT(IsPowerOfTwo(alignment)); + ASSERT(alignment != 0); + return (reinterpret_cast<size_t>(ptr) & (alignment - 1)) == 0; +} + +bool IsAligned(uint32_t value, size_t alignment) { + ASSERT(alignment <= UINT32_MAX); + ASSERT(IsPowerOfTwo(alignment)); + ASSERT(alignment != 0); + uint32_t alignment32 = static_cast<uint32_t>(alignment); + return (value & (alignment32 - 1)) == 0; +} + +uint16_t Float32ToFloat16(float fp32) { + uint32_t fp32i = BitCast<uint32_t>(fp32); + uint32_t sign16 = (fp32i & 0x80000000) >> 16; + uint32_t mantissaAndExponent = fp32i & 0x7FFFFFFF; + + if (mantissaAndExponent > 0x7F800000) { // NaN + return 0x7FFF; + } else if (mantissaAndExponent > 0x47FFEFFF) { // Infinity + return static_cast<uint16_t>(sign16 | 0x7C00); + } else if (mantissaAndExponent < 0x38800000) { // Denormal + uint32_t mantissa = (mantissaAndExponent & 0x007FFFFF) | 0x00800000; + int32_t exponent = 113 - (mantissaAndExponent >> 23); + + if (exponent < 24) { + mantissaAndExponent = mantissa >> exponent; + } else { + mantissaAndExponent = 0; + } + + return static_cast<uint16_t>( + sign16 | (mantissaAndExponent + 0x00000FFF + ((mantissaAndExponent >> 13) & 1)) >> 13); + } else { + return static_cast<uint16_t>(sign16 | (mantissaAndExponent + 0xC8000000 + 0x00000FFF + + ((mantissaAndExponent >> 13) & 1)) >> + 13); + } +} + +float Float16ToFloat32(uint16_t fp16) { + uint32_t tmp = (fp16 & 0x7fff) << 13 | (fp16 & 0x8000) << 16; + float tmp2 = *reinterpret_cast<float*>(&tmp); + return pow(2, 127 - 15) * tmp2; +} + +bool IsFloat16NaN(uint16_t fp16) { + return (fp16 & 0x7FFF) > 0x7C00; +} + +// Based on the Khronos Data Format Specification 1.2 Section 13.3 sRGB transfer functions +float SRGBToLinear(float srgb) { + // sRGB is always used in unsigned normalized formats so clamp to [0.0, 1.0] + if (srgb <= 0.0f) { + return 0.0f; + } else if (srgb > 1.0f) { + return 1.0f; + } + + if (srgb < 0.04045f) { + return srgb / 12.92f; + } else { + return std::pow((srgb + 0.055f) / 1.055f, 2.4f); + } +} + +uint64_t RoundUp(uint64_t n, uint64_t m) { + ASSERT(m > 0); + ASSERT(n > 0); + ASSERT(m <= std::numeric_limits<uint64_t>::max() - n); + return ((n + m - 1) / m) * m; +}
diff --git a/src/dawn/common/Math.h b/src/dawn/common/Math.h new file mode 100644 index 0000000..9ef02d0 --- /dev/null +++ b/src/dawn/common/Math.h
@@ -0,0 +1,107 @@ +// Copyright 2017 The Dawn Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#ifndef COMMON_MATH_H_ +#define COMMON_MATH_H_ + +#include "dawn/common/Assert.h" + +#include <cstddef> +#include <cstdint> +#include <cstring> + +#include <limits> +#include <type_traits> + +// The following are not valid for 0 +uint32_t ScanForward(uint32_t bits); +uint32_t Log2(uint32_t value); +uint32_t Log2(uint64_t value); +bool IsPowerOfTwo(uint64_t n); +uint64_t RoundUp(uint64_t n, uint64_t m); + +constexpr uint32_t ConstexprLog2(uint64_t v) { + return v <= 1 ? 0 : 1 + ConstexprLog2(v / 2); +} + +constexpr uint32_t ConstexprLog2Ceil(uint64_t v) { + return v <= 1 ? 0 : ConstexprLog2(v - 1) + 1; +} + +inline uint32_t Log2Ceil(uint32_t v) { + return v <= 1 ? 0 : Log2(v - 1) + 1; +} + +inline uint32_t Log2Ceil(uint64_t v) { + return v <= 1 ? 0 : Log2(v - 1) + 1; +} + +uint64_t NextPowerOfTwo(uint64_t n); +bool IsPtrAligned(const void* ptr, size_t alignment); +void* AlignVoidPtr(void* ptr, size_t alignment); +bool IsAligned(uint32_t value, size_t alignment); + +template <typename T> +T Align(T value, size_t alignment) { + ASSERT(value <= std::numeric_limits<T>::max() - (alignment - 1)); + ASSERT(IsPowerOfTwo(alignment)); + ASSERT(alignment != 0); + T alignmentT = static_cast<T>(alignment); + return (value + (alignmentT - 1)) & ~(alignmentT - 1); +} + +template <typename T> +DAWN_FORCE_INLINE T* AlignPtr(T* ptr, size_t alignment) { + ASSERT(IsPowerOfTwo(alignment)); + ASSERT(alignment != 0); + return reinterpret_cast<T*>((reinterpret_cast<size_t>(ptr) + (alignment - 1)) & + ~(alignment - 1)); +} + +template <typename T> +DAWN_FORCE_INLINE const T* AlignPtr(const T* ptr, size_t alignment) { + ASSERT(IsPowerOfTwo(alignment)); + ASSERT(alignment != 0); + return reinterpret_cast<const T*>((reinterpret_cast<size_t>(ptr) + (alignment - 1)) & + ~(alignment - 1)); +} + +template <typename destType, typename sourceType> +destType BitCast(const sourceType& source) { + static_assert(sizeof(destType) == sizeof(sourceType), "BitCast: cannot lose precision."); + destType output; + std::memcpy(&output, &source, sizeof(destType)); + return output; +} + +uint16_t Float32ToFloat16(float fp32); +float Float16ToFloat32(uint16_t fp16); +bool IsFloat16NaN(uint16_t fp16); + +template <typename T> +T FloatToUnorm(float value) { + return static_cast<T>(value * static_cast<float>(std::numeric_limits<T>::max())); +} + +float SRGBToLinear(float srgb); + +template <typename T1, + typename T2, + typename Enable = typename std::enable_if<sizeof(T1) == sizeof(T2)>::type> +constexpr bool IsSubset(T1 subset, T2 set) { + T2 bitsAlsoInSet = subset & set; + return bitsAlsoInSet == subset; +} + +#endif // COMMON_MATH_H_
diff --git a/src/dawn/common/NSRef.h b/src/dawn/common/NSRef.h new file mode 100644 index 0000000..5bf4914 --- /dev/null +++ b/src/dawn/common/NSRef.h
@@ -0,0 +1,123 @@ +// Copyright 2020 The Dawn Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#ifndef COMMON_NSREF_H_ +#define COMMON_NSREF_H_ + +#include "dawn/common/RefBase.h" + +#import <Foundation/NSObject.h> + +#if !defined(__OBJC__) +# error "NSRef can only be used in Objective C/C++ code." +#endif + +// This file contains smart pointers that automatically reference and release Objective C objects +// and prototocals in a manner very similar to Ref<>. Note that NSRef<> and NSPRef's constructor add +// a reference to the object by default, so the pattern to get a reference for a newly created +// NSObject is the following: +// +// NSRef<NSFoo> foo = AcquireNSRef([NSFoo alloc]); +// +// NSRef overloads -> and * but these operators don't work extremely well with Objective C's +// features. For example automatic dereferencing when doing the following doesn't work: +// +// NSFoo* foo; +// foo.member = 1; +// someVar = foo.member; +// +// Instead use the message passing syntax: +// +// NSRef<NSFoo> foo; +// [*foo setMember: 1]; +// someVar = [*foo member]; +// +// Also did you notive the extra '*' in the example above? That's because Objective C's message +// passing doesn't automatically call a C++ operator to dereference smart pointers (like -> does) so +// we have to dereference manually using '*'. In some cases the extra * or message passing syntax +// can get a bit annoying so instead a local "naked" pointer can be borrowed from the NSRef. This +// would change the syntax overload in the following: +// +// NSRef<NSFoo> foo; +// [*foo setA:1]; +// [*foo setB:2]; +// [*foo setC:3]; +// +// Into (note access to members of ObjC classes referenced via pointer is done with . and not ->): +// +// NSRef<NSFoo> fooRef; +// NSFoo* foo = fooRef.Get(); +// foo.a = 1; +// foo.b = 2; +// boo.c = 3; +// +// Which can be subjectively easier to read. + +template <typename T> +struct NSRefTraits { + static constexpr T kNullValue = nullptr; + static void Reference(T value) { + [value retain]; + } + static void Release(T value) { + [value release]; + } +}; + +template <typename T> +class NSRef : public RefBase<T*, NSRefTraits<T*>> { + public: + using RefBase<T*, NSRefTraits<T*>>::RefBase; + + const T* operator*() const { + return this->Get(); + } + + T* operator*() { + return this->Get(); + } +}; + +template <typename T> +NSRef<T> AcquireNSRef(T* pointee) { + NSRef<T> ref; + ref.Acquire(pointee); + return ref; +} + +// This is a RefBase<> for an Objective C protocol (hence the P). Objective C protocols must always +// be referenced with id<ProtocolName> and not just ProtocolName* so they cannot use NSRef<> +// itself. That's what the P in NSPRef stands for: Protocol. +template <typename T> +class NSPRef : public RefBase<T, NSRefTraits<T>> { + public: + using RefBase<T, NSRefTraits<T>>::RefBase; + + const T operator*() const { + return this->Get(); + } + + T operator*() { + return this->Get(); + } +}; + +template <typename T> +NSPRef<T> AcquireNSPRef(T pointee) { + NSPRef<T> ref; + ref.Acquire(pointee); + return ref; +} + +#endif // COMMON_NSREF_H_
diff --git a/src/dawn/common/NonCopyable.h b/src/dawn/common/NonCopyable.h new file mode 100644 index 0000000..2d217df --- /dev/null +++ b/src/dawn/common/NonCopyable.h
@@ -0,0 +1,43 @@ +// Copyright 2021 The Dawn Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#ifndef COMMON_NONCOPYABLE_H_ +#define COMMON_NONCOPYABLE_H_ + +// A base class to make a class non-copyable. +class NonCopyable { + protected: + constexpr NonCopyable() = default; + ~NonCopyable() = default; + + NonCopyable(NonCopyable&&) = default; + NonCopyable& operator=(NonCopyable&&) = default; + + private: + NonCopyable(const NonCopyable&) = delete; + void operator=(const NonCopyable&) = delete; +}; + +// A base class to make a class non-movable. +class NonMovable : NonCopyable { + protected: + constexpr NonMovable() = default; + ~NonMovable() = default; + + private: + NonMovable(NonMovable&&) = delete; + void operator=(NonMovable&&) = delete; +}; + +#endif
diff --git a/src/dawn/common/PlacementAllocated.h b/src/dawn/common/PlacementAllocated.h new file mode 100644 index 0000000..6c715ca --- /dev/null +++ b/src/dawn/common/PlacementAllocated.h
@@ -0,0 +1,42 @@ +// Copyright 2020 The Dawn Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#ifndef COMMON_PLACEMENTALLOCATED_H_ +#define COMMON_PLACEMENTALLOCATED_H_ + +#include <cstddef> + +class PlacementAllocated { + public: + // Delete the default new operator so this can only be created with placement new. + void* operator new(size_t) = delete; + + void* operator new(size_t size, void* ptr) { + // Pass through the pointer of the allocation. This is essentially the default + // placement-new implementation, but we must define it if we delete the default + // new operator. + return ptr; + } + + void operator delete(void* ptr) { + // Object is placement-allocated. Don't free the memory. + } + + void operator delete(void*, void*) { + // This is added to match new(size_t size, void* ptr) + // Otherwise it triggers C4291 warning in MSVC + } +}; + +#endif // COMMON_PLACEMENTALLOCATED_H_
diff --git a/src/dawn/common/Platform.h b/src/dawn/common/Platform.h new file mode 100644 index 0000000..f947102 --- /dev/null +++ b/src/dawn/common/Platform.h
@@ -0,0 +1,82 @@ +// Copyright 2017 The Dawn Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#ifndef COMMON_PLATFORM_H_ +#define COMMON_PLATFORM_H_ + +#if defined(_WIN32) || defined(_WIN64) +# include <winapifamily.h> +# define DAWN_PLATFORM_WINDOWS 1 +# if WINAPI_FAMILY == WINAPI_FAMILY_DESKTOP_APP +# define DAWN_PLATFORM_WIN32 1 +# elif WINAPI_FAMILY == WINAPI_FAMILY_PC_APP +# define DAWN_PLATFORM_WINUWP 1 +# else +# error "Unsupported Windows platform." +# endif + +#elif defined(__linux__) +# define DAWN_PLATFORM_LINUX 1 +# define DAWN_PLATFORM_POSIX 1 +# if defined(__ANDROID__) +# define DAWN_PLATFORM_ANDROID 1 +# endif + +#elif defined(__APPLE__) +# define DAWN_PLATFORM_APPLE 1 +# define DAWN_PLATFORM_POSIX 1 +# include <TargetConditionals.h> +# if TARGET_OS_IPHONE +# define DAWN_PLATFORM_IOS +# elif TARGET_OS_MAC +# define DAWN_PLATFORM_MACOS +# else +# error "Unsupported Apple platform." +# endif + +#elif defined(__Fuchsia__) +# define DAWN_PLATFORM_FUCHSIA 1 +# define DAWN_PLATFORM_POSIX 1 + +#elif defined(__EMSCRIPTEN__) +# define DAWN_PLATFORM_EMSCRIPTEN 1 +# define DAWN_PLATFORM_POSIX 1 + +#else +# error "Unsupported platform." +#endif + +// Distinguish mips32. +#if defined(__mips__) && (_MIPS_SIM == _ABIO32) && !defined(__mips32__) +# define __mips32__ +#endif + +// Distinguish mips64. +#if defined(__mips__) && (_MIPS_SIM == _ABI64) && !defined(__mips64__) +# define __mips64__ +#endif + +#if defined(_WIN64) || defined(__aarch64__) || defined(__x86_64__) || defined(__mips64__) || \ + defined(__s390x__) || defined(__PPC64__) +# define DAWN_PLATFORM_64_BIT 1 +static_assert(sizeof(sizeof(char)) == 8, "Expect sizeof(size_t) == 8"); +#elif defined(_WIN32) || defined(__arm__) || defined(__i386__) || defined(__mips32__) || \ + defined(__s390__) || defined(__EMSCRIPTEN__) +# define DAWN_PLATFORM_32_BIT 1 +static_assert(sizeof(sizeof(char)) == 4, "Expect sizeof(size_t) == 4"); +#else +# error "Unsupported platform" +#endif + +#endif // COMMON_PLATFORM_H_
diff --git a/src/dawn/common/Preprocessor.h b/src/dawn/common/Preprocessor.h new file mode 100644 index 0000000..4eef736 --- /dev/null +++ b/src/dawn/common/Preprocessor.h
@@ -0,0 +1,70 @@ +// Copyright 2021 The Dawn Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#ifndef COMMON_PREPROCESSOR_H_ +#define COMMON_PREPROCESSOR_H_ + +// DAWN_PP_GET_HEAD: get the first element of a __VA_ARGS__ without triggering empty +// __VA_ARGS__ warnings. +#define DAWN_INTERNAL_PP_GET_HEAD(firstParam, ...) firstParam +#define DAWN_PP_GET_HEAD(...) DAWN_INTERNAL_PP_GET_HEAD(__VA_ARGS__, dummyArg) + +// DAWN_PP_CONCATENATE: Concatenate tokens, first expanding the arguments passed in. +#define DAWN_PP_CONCATENATE(arg1, arg2) DAWN_PP_CONCATENATE_1(arg1, arg2) +#define DAWN_PP_CONCATENATE_1(arg1, arg2) DAWN_PP_CONCATENATE_2(arg1, arg2) +#define DAWN_PP_CONCATENATE_2(arg1, arg2) arg1##arg2 + +// DAWN_PP_EXPAND: Needed to help expand __VA_ARGS__ out on MSVC +#define DAWN_PP_EXPAND(...) __VA_ARGS__ + +// Implementation of DAWN_PP_FOR_EACH, called by concatenating DAWN_PP_FOR_EACH_ with a number. +#define DAWN_PP_FOR_EACH_1(func, x) func(x) +#define DAWN_PP_FOR_EACH_2(func, x, ...) \ + func(x) DAWN_PP_EXPAND(DAWN_PP_EXPAND(DAWN_PP_FOR_EACH_1)(func, __VA_ARGS__)) +#define DAWN_PP_FOR_EACH_3(func, x, ...) \ + func(x) DAWN_PP_EXPAND(DAWN_PP_EXPAND(DAWN_PP_FOR_EACH_2)(func, __VA_ARGS__)) +#define DAWN_PP_FOR_EACH_4(func, x, ...) \ + func(x) DAWN_PP_EXPAND(DAWN_PP_EXPAND(DAWN_PP_FOR_EACH_3)(func, __VA_ARGS__)) +#define DAWN_PP_FOR_EACH_5(func, x, ...) \ + func(x) DAWN_PP_EXPAND(DAWN_PP_EXPAND(DAWN_PP_FOR_EACH_4)(func, __VA_ARGS__)) +#define DAWN_PP_FOR_EACH_6(func, x, ...) \ + func(x) DAWN_PP_EXPAND(DAWN_PP_EXPAND(DAWN_PP_FOR_EACH_5)(func, __VA_ARGS__)) +#define DAWN_PP_FOR_EACH_7(func, x, ...) \ + func(x) DAWN_PP_EXPAND(DAWN_PP_EXPAND(DAWN_PP_FOR_EACH_6)(func, __VA_ARGS__)) +#define DAWN_PP_FOR_EACH_8(func, x, ...) \ + func(x) DAWN_PP_EXPAND(DAWN_PP_EXPAND(DAWN_PP_FOR_EACH_7)(func, __VA_ARGS__)) + +// Implementation for DAWN_PP_FOR_EACH. Get the number of args in __VA_ARGS__ so we can concat +// DAWN_PP_FOR_EACH_ and N. +// ex.) DAWN_PP_FOR_EACH_NARG(a, b, c) -> +// DAWN_PP_FOR_EACH_NARG(a, b, c, DAWN_PP_FOR_EACH_RSEQ()) -> +// DAWN_PP_FOR_EACH_NARG_(a, b, c, 8, 7, 6, 5, 4, 3, 2, 1, 0) -> +// DAWN_PP_FOR_EACH_ARG_N(a, b, c, 8, 7, 6, 5, 4, 3, 2, 1, 0) -> +// DAWN_PP_FOR_EACH_ARG_N( , , , , , , , , N) -> +// 3 +#define DAWN_PP_FOR_EACH_NARG(...) DAWN_PP_FOR_EACH_NARG_(__VA_ARGS__, DAWN_PP_FOR_EACH_RSEQ()) +#define DAWN_PP_FOR_EACH_NARG_(...) \ + DAWN_PP_EXPAND(DAWN_PP_EXPAND(DAWN_PP_FOR_EACH_ARG_N)(__VA_ARGS__)) +#define DAWN_PP_FOR_EACH_ARG_N(_1, _2, _3, _4, _5, _6, _7, _8, N, ...) N +#define DAWN_PP_FOR_EACH_RSEQ() 8, 7, 6, 5, 4, 3, 2, 1, 0 + +// Implementation for DAWN_PP_FOR_EACH. +// Creates a call to DAWN_PP_FOR_EACH_X where X is 1, 2, ..., etc. +#define DAWN_PP_FOR_EACH_(N, func, ...) DAWN_PP_CONCATENATE(DAWN_PP_FOR_EACH_, N)(func, __VA_ARGS__) + +// DAWN_PP_FOR_EACH: Apply |func| to each argument in |x| and __VA_ARGS__ +#define DAWN_PP_FOR_EACH(func, ...) \ + DAWN_PP_FOR_EACH_(DAWN_PP_FOR_EACH_NARG(__VA_ARGS__), func, __VA_ARGS__) + +#endif // COMMON_PREPROCESSOR_H_
diff --git a/src/dawn/common/RefBase.h b/src/dawn/common/RefBase.h new file mode 100644 index 0000000..5d10789 --- /dev/null +++ b/src/dawn/common/RefBase.h
@@ -0,0 +1,183 @@ +// Copyright 2020 The Dawn Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#ifndef COMMON_REFBASE_H_ +#define COMMON_REFBASE_H_ + +#include "dawn/common/Assert.h" +#include "dawn/common/Compiler.h" + +#include <type_traits> +#include <utility> + +// A common class for various smart-pointers acting on referenceable/releasable pointer-like +// objects. Logic for each specialization can be customized using a Traits type that looks +// like the following: +// +// struct { +// static constexpr T kNullValue = ...; +// static void Reference(T value) { ... } +// static void Release(T value) { ... } +// }; +// +// RefBase supports +template <typename T, typename Traits> +class RefBase { + public: + // Default constructor and destructor. + RefBase() : mValue(Traits::kNullValue) { + } + + ~RefBase() { + Release(mValue); + } + + // Constructors from nullptr. + constexpr RefBase(std::nullptr_t) : RefBase() { + } + + RefBase<T, Traits>& operator=(std::nullptr_t) { + Set(Traits::kNullValue); + return *this; + } + + // Constructors from a value T. + RefBase(T value) : mValue(value) { + Reference(value); + } + + RefBase<T, Traits>& operator=(const T& value) { + Set(value); + return *this; + } + + // Constructors from a RefBase<T> + RefBase(const RefBase<T, Traits>& other) : mValue(other.mValue) { + Reference(other.mValue); + } + + RefBase<T, Traits>& operator=(const RefBase<T, Traits>& other) { + Set(other.mValue); + return *this; + } + + RefBase(RefBase<T, Traits>&& other) { + mValue = other.Detach(); + } + + RefBase<T, Traits>& operator=(RefBase<T, Traits>&& other) { + if (&other != this) { + Release(mValue); + mValue = other.Detach(); + } + return *this; + } + + // Constructors from a RefBase<U>. Note that in the *-assignment operators this cannot be the + // same as `other` because overload resolution rules would have chosen the *-assignement + // operators defined with `other` == RefBase<T, Traits>. + template <typename U, typename UTraits, typename = typename std::is_convertible<U, T>::type> + RefBase(const RefBase<U, UTraits>& other) : mValue(other.mValue) { + Reference(other.mValue); + } + + template <typename U, typename UTraits, typename = typename std::is_convertible<U, T>::type> + RefBase<T, Traits>& operator=(const RefBase<U, UTraits>& other) { + Set(other.mValue); + return *this; + } + + template <typename U, typename UTraits, typename = typename std::is_convertible<U, T>::type> + RefBase(RefBase<U, UTraits>&& other) { + mValue = other.Detach(); + } + + template <typename U, typename UTraits, typename = typename std::is_convertible<U, T>::type> + RefBase<T, Traits>& operator=(RefBase<U, UTraits>&& other) { + Release(mValue); + mValue = other.Detach(); + return *this; + } + + // Comparison operators. + bool operator==(const T& other) const { + return mValue == other; + } + + bool operator!=(const T& other) const { + return mValue != other; + } + + const T operator->() const { + return mValue; + } + T operator->() { + return mValue; + } + + // Smart pointer methods. + const T& Get() const { + return mValue; + } + T& Get() { + return mValue; + } + + [[nodiscard]] T Detach() { + T value{std::move(mValue)}; + mValue = Traits::kNullValue; + return value; + } + + void Acquire(T value) { + Release(mValue); + mValue = value; + } + + [[nodiscard]] T* InitializeInto() { + ASSERT(mValue == Traits::kNullValue); + return &mValue; + } + + private: + // Friend is needed so that instances of RefBase<U> can call Reference and Release on + // RefBase<T>. + template <typename U, typename UTraits> + friend class RefBase; + + static void Reference(T value) { + if (value != Traits::kNullValue) { + Traits::Reference(value); + } + } + static void Release(T value) { + if (value != Traits::kNullValue) { + Traits::Release(value); + } + } + + void Set(T value) { + if (mValue != value) { + // Ensure that the new value is referenced before the old is released to prevent any + // transitive frees that may affect the new value. + Reference(value); + Release(mValue); + mValue = value; + } + } + + T mValue; +}; + +#endif // COMMON_REFBASE_H_
diff --git a/src/dawn/common/RefCounted.cpp b/src/dawn/common/RefCounted.cpp new file mode 100644 index 0000000..6950d13 --- /dev/null +++ b/src/dawn/common/RefCounted.cpp
@@ -0,0 +1,86 @@ +// Copyright 2017 The Dawn Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include "dawn/common/RefCounted.h" + +#include "dawn/common/Assert.h" + +#include <cstddef> + +static constexpr size_t kPayloadBits = 1; +static constexpr uint64_t kPayloadMask = (uint64_t(1) << kPayloadBits) - 1; +static constexpr uint64_t kRefCountIncrement = (uint64_t(1) << kPayloadBits); + +RefCounted::RefCounted(uint64_t payload) : mRefCount(kRefCountIncrement + payload) { + ASSERT((payload & kPayloadMask) == payload); +} + +uint64_t RefCounted::GetRefCountForTesting() const { + return mRefCount >> kPayloadBits; +} + +uint64_t RefCounted::GetRefCountPayload() const { + // We only care about the payload bits of the refcount. These never change after + // initialization so we can use the relaxed memory order. The order doesn't guarantee + // anything except the atomicity of the load, which is enough since any past values of the + // atomic will have the correct payload bits. + return kPayloadMask & mRefCount.load(std::memory_order_relaxed); +} + +void RefCounted::Reference() { + ASSERT((mRefCount & ~kPayloadMask) != 0); + + // The relaxed ordering guarantees only the atomicity of the update, which is enough here + // because the reference we are copying from still exists and makes sure other threads + // don't delete `this`. + // See the explanation in the Boost documentation: + // https://www.boost.org/doc/libs/1_55_0/doc/html/atomic/usage_examples.html + mRefCount.fetch_add(kRefCountIncrement, std::memory_order_relaxed); +} + +void RefCounted::Release() { + ASSERT((mRefCount & ~kPayloadMask) != 0); + + // The release fence here is to make sure all accesses to the object on a thread A + // happen-before the object is deleted on a thread B. The release memory order ensures that + // all accesses on thread A happen-before the refcount is decreased and the atomic variable + // makes sure the refcount decrease in A happens-before the refcount decrease in B. Finally + // the acquire fence in the destruction case makes sure the refcount decrease in B + // happens-before the `delete this`. + // + // See the explanation in the Boost documentation: + // https://www.boost.org/doc/libs/1_55_0/doc/html/atomic/usage_examples.html + uint64_t previousRefCount = mRefCount.fetch_sub(kRefCountIncrement, std::memory_order_release); + + // Check that the previous reference count was strictly less than 2, ignoring payload bits. + if (previousRefCount < 2 * kRefCountIncrement) { + // Note that on ARM64 this will generate a `dmb ish` instruction which is a global + // memory barrier, when an acquire load on mRefCount (using the `ldar` instruction) + // should be enough and could end up being faster. + std::atomic_thread_fence(std::memory_order_acquire); + DeleteThis(); + } +} + +void RefCounted::APIReference() { + Reference(); +} + +void RefCounted::APIRelease() { + Release(); +} + +void RefCounted::DeleteThis() { + delete this; +}
diff --git a/src/dawn/common/RefCounted.h b/src/dawn/common/RefCounted.h new file mode 100644 index 0000000..65f37b9 --- /dev/null +++ b/src/dawn/common/RefCounted.h
@@ -0,0 +1,69 @@ +// Copyright 2017 The Dawn Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#ifndef COMMON_REFCOUNTED_H_ +#define COMMON_REFCOUNTED_H_ + +#include "dawn/common/RefBase.h" + +#include <atomic> +#include <cstdint> + +class RefCounted { + public: + RefCounted(uint64_t payload = 0); + + uint64_t GetRefCountForTesting() const; + uint64_t GetRefCountPayload() const; + + void Reference(); + void Release(); + + void APIReference(); + void APIRelease(); + + protected: + virtual ~RefCounted() = default; + // A Derived class may override this if they require a custom deleter. + virtual void DeleteThis(); + + private: + std::atomic<uint64_t> mRefCount; +}; + +template <typename T> +struct RefCountedTraits { + static constexpr T* kNullValue = nullptr; + static void Reference(T* value) { + value->Reference(); + } + static void Release(T* value) { + value->Release(); + } +}; + +template <typename T> +class Ref : public RefBase<T*, RefCountedTraits<T>> { + public: + using RefBase<T*, RefCountedTraits<T>>::RefBase; +}; + +template <typename T> +Ref<T> AcquireRef(T* pointee) { + Ref<T> ref; + ref.Acquire(pointee); + return ref; +} + +#endif // COMMON_REFCOUNTED_H_
diff --git a/src/dawn/common/Result.cpp b/src/dawn/common/Result.cpp new file mode 100644 index 0000000..2101e47 --- /dev/null +++ b/src/dawn/common/Result.cpp
@@ -0,0 +1,30 @@ +// Copyright 2019 The Dawn Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include "dawn/common/Result.h" + +// Implementation details of the tagged pointer Results +namespace detail { + + intptr_t MakePayload(const void* pointer, PayloadType type) { + intptr_t payload = reinterpret_cast<intptr_t>(pointer); + ASSERT((payload & 3) == 0); + return payload | type; + } + + PayloadType GetPayloadType(intptr_t payload) { + return static_cast<PayloadType>(payload & 3); + } + +} // namespace detail
diff --git a/src/dawn/common/Result.h b/src/dawn/common/Result.h new file mode 100644 index 0000000..5566829 --- /dev/null +++ b/src/dawn/common/Result.h
@@ -0,0 +1,526 @@ +// Copyright 2018 The Dawn Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#ifndef COMMON_RESULT_H_ +#define COMMON_RESULT_H_ + +#include "dawn/common/Assert.h" +#include "dawn/common/Compiler.h" + +#include <cstddef> +#include <cstdint> +#include <memory> +#include <type_traits> +#include <utility> + +// Result<T, E> is the following sum type (Haskell notation): +// +// data Result T E = Success T | Error E | Empty +// +// It is meant to be used as the return type of functions that might fail. The reason for the Empty +// case is that a Result should never be discarded, only destructured (its error or success moved +// out) or moved into a different Result. The Empty case tags Results that have been moved out and +// Result's destructor should ASSERT on it being Empty. +// +// Since C++ doesn't have efficient sum types for the special cases we care about, we provide +// template specializations for them. + +template <typename T, typename E> +class Result; + +// The interface of Result<T, E> should look like the following. +// public: +// Result(T&& success); +// Result(std::unique_ptr<E> error); +// +// Result(Result<T, E>&& other); +// Result<T, E>& operator=(Result<T, E>&& other); +// +// ~Result(); +// +// bool IsError() const; +// bool IsSuccess() const; +// +// T&& AcquireSuccess(); +// std::unique_ptr<E> AcquireError(); + +// Specialization of Result for returning errors only via pointers. It is basically a pointer +// where nullptr is both Success and Empty. +template <typename E> +class [[nodiscard]] Result<void, E> { + public: + Result(); + Result(std::unique_ptr<E> error); + + Result(Result<void, E> && other); + Result<void, E>& operator=(Result<void, E>&& other); + + ~Result(); + + bool IsError() const; + bool IsSuccess() const; + + void AcquireSuccess(); + std::unique_ptr<E> AcquireError(); + + private: + std::unique_ptr<E> mError; +}; + +// Uses SFINAE to try to get alignof(T) but fallback to Default if T isn't defined. +template <typename T, size_t Default, typename = size_t> +constexpr size_t alignof_if_defined_else_default = Default; + +template <typename T, size_t Default> +constexpr size_t alignof_if_defined_else_default<T, Default, decltype(alignof(T))> = alignof(T); + +// Specialization of Result when both the error an success are pointers. It is implemented as a +// tagged pointer. The tag for Success is 0 so that returning the value is fastest. + +namespace detail { + // Utility functions to manipulate the tagged pointer. Some of them don't need to be templated + // but we really want them inlined so we keep them in the headers + enum PayloadType { + Success = 0, + Error = 1, + Empty = 2, + }; + + intptr_t MakePayload(const void* pointer, PayloadType type); + PayloadType GetPayloadType(intptr_t payload); + + template <typename T> + static T* GetSuccessFromPayload(intptr_t payload); + template <typename E> + static E* GetErrorFromPayload(intptr_t payload); + + constexpr static intptr_t kEmptyPayload = Empty; +} // namespace detail + +template <typename T, typename E> +class [[nodiscard]] Result<T*, E> { + public: + static_assert(alignof_if_defined_else_default<T, 4> >= 4, + "Result<T*, E*> reserves two bits for tagging pointers"); + static_assert(alignof_if_defined_else_default<E, 4> >= 4, + "Result<T*, E*> reserves two bits for tagging pointers"); + + Result(T * success); + Result(std::unique_ptr<E> error); + + // Support returning a Result<T*, E*> from a Result<TChild*, E*> + template <typename TChild> + Result(Result<TChild*, E> && other); + template <typename TChild> + Result<T*, E>& operator=(Result<TChild*, E>&& other); + + ~Result(); + + bool IsError() const; + bool IsSuccess() const; + + T* AcquireSuccess(); + std::unique_ptr<E> AcquireError(); + + private: + template <typename T2, typename E2> + friend class Result; + + intptr_t mPayload = detail::kEmptyPayload; +}; + +template <typename T, typename E> +class [[nodiscard]] Result<const T*, E> { + public: + static_assert(alignof_if_defined_else_default<T, 4> >= 4, + "Result<T*, E*> reserves two bits for tagging pointers"); + static_assert(alignof_if_defined_else_default<E, 4> >= 4, + "Result<T*, E*> reserves two bits for tagging pointers"); + + Result(const T* success); + Result(std::unique_ptr<E> error); + + Result(Result<const T*, E> && other); + Result<const T*, E>& operator=(Result<const T*, E>&& other); + + ~Result(); + + bool IsError() const; + bool IsSuccess() const; + + const T* AcquireSuccess(); + std::unique_ptr<E> AcquireError(); + + private: + intptr_t mPayload = detail::kEmptyPayload; +}; + +template <typename T> +class Ref; + +template <typename T, typename E> +class [[nodiscard]] Result<Ref<T>, E> { + public: + static_assert(alignof_if_defined_else_default<T, 4> >= 4, + "Result<Ref<T>, E> reserves two bits for tagging pointers"); + static_assert(alignof_if_defined_else_default<E, 4> >= 4, + "Result<Ref<T>, E> reserves two bits for tagging pointers"); + + template <typename U> + Result(Ref<U> && success); + template <typename U> + Result(const Ref<U>& success); + Result(std::unique_ptr<E> error); + + template <typename U> + Result(Result<Ref<U>, E> && other); + template <typename U> + Result<Ref<U>, E>& operator=(Result<Ref<U>, E>&& other); + + ~Result(); + + bool IsError() const; + bool IsSuccess() const; + + Ref<T> AcquireSuccess(); + std::unique_ptr<E> AcquireError(); + + private: + template <typename T2, typename E2> + friend class Result; + + intptr_t mPayload = detail::kEmptyPayload; +}; + +// Catchall definition of Result<T, E> implemented as a tagged struct. It could be improved to use +// a tagged union instead if it turns out to be a hotspot. T and E must be movable and default +// constructible. +template <typename T, typename E> +class [[nodiscard]] Result { + public: + Result(T && success); + Result(std::unique_ptr<E> error); + + Result(Result<T, E> && other); + Result<T, E>& operator=(Result<T, E>&& other); + + ~Result(); + + bool IsError() const; + bool IsSuccess() const; + + T&& AcquireSuccess(); + std::unique_ptr<E> AcquireError(); + + private: + enum PayloadType { + Success = 0, + Error = 1, + Acquired = 2, + }; + PayloadType mType; + + std::unique_ptr<E> mError; + T mSuccess; +}; + +// Implementation of Result<void, E> +template <typename E> +Result<void, E>::Result() { +} + +template <typename E> +Result<void, E>::Result(std::unique_ptr<E> error) : mError(std::move(error)) { +} + +template <typename E> +Result<void, E>::Result(Result<void, E>&& other) : mError(std::move(other.mError)) { +} + +template <typename E> +Result<void, E>& Result<void, E>::operator=(Result<void, E>&& other) { + ASSERT(mError == nullptr); + mError = std::move(other.mError); + return *this; +} + +template <typename E> +Result<void, E>::~Result() { + ASSERT(mError == nullptr); +} + +template <typename E> +bool Result<void, E>::IsError() const { + return mError != nullptr; +} + +template <typename E> +bool Result<void, E>::IsSuccess() const { + return mError == nullptr; +} + +template <typename E> +void Result<void, E>::AcquireSuccess() { +} + +template <typename E> +std::unique_ptr<E> Result<void, E>::AcquireError() { + return std::move(mError); +} + +// Implementation details of the tagged pointer Results +namespace detail { + + template <typename T> + T* GetSuccessFromPayload(intptr_t payload) { + ASSERT(GetPayloadType(payload) == Success); + return reinterpret_cast<T*>(payload); + } + + template <typename E> + E* GetErrorFromPayload(intptr_t payload) { + ASSERT(GetPayloadType(payload) == Error); + return reinterpret_cast<E*>(payload ^ 1); + } + +} // namespace detail + +// Implementation of Result<T*, E> +template <typename T, typename E> +Result<T*, E>::Result(T* success) : mPayload(detail::MakePayload(success, detail::Success)) { +} + +template <typename T, typename E> +Result<T*, E>::Result(std::unique_ptr<E> error) + : mPayload(detail::MakePayload(error.release(), detail::Error)) { +} + +template <typename T, typename E> +template <typename TChild> +Result<T*, E>::Result(Result<TChild*, E>&& other) : mPayload(other.mPayload) { + other.mPayload = detail::kEmptyPayload; + static_assert(std::is_same<T, TChild>::value || std::is_base_of<T, TChild>::value); +} + +template <typename T, typename E> +template <typename TChild> +Result<T*, E>& Result<T*, E>::operator=(Result<TChild*, E>&& other) { + ASSERT(mPayload == detail::kEmptyPayload); + static_assert(std::is_same<T, TChild>::value || std::is_base_of<T, TChild>::value); + mPayload = other.mPayload; + other.mPayload = detail::kEmptyPayload; + return *this; +} + +template <typename T, typename E> +Result<T*, E>::~Result() { + ASSERT(mPayload == detail::kEmptyPayload); +} + +template <typename T, typename E> +bool Result<T*, E>::IsError() const { + return detail::GetPayloadType(mPayload) == detail::Error; +} + +template <typename T, typename E> +bool Result<T*, E>::IsSuccess() const { + return detail::GetPayloadType(mPayload) == detail::Success; +} + +template <typename T, typename E> +T* Result<T*, E>::AcquireSuccess() { + T* success = detail::GetSuccessFromPayload<T>(mPayload); + mPayload = detail::kEmptyPayload; + return success; +} + +template <typename T, typename E> +std::unique_ptr<E> Result<T*, E>::AcquireError() { + std::unique_ptr<E> error(detail::GetErrorFromPayload<E>(mPayload)); + mPayload = detail::kEmptyPayload; + return std::move(error); +} + +// Implementation of Result<const T*, E*> +template <typename T, typename E> +Result<const T*, E>::Result(const T* success) + : mPayload(detail::MakePayload(success, detail::Success)) { +} + +template <typename T, typename E> +Result<const T*, E>::Result(std::unique_ptr<E> error) + : mPayload(detail::MakePayload(error.release(), detail::Error)) { +} + +template <typename T, typename E> +Result<const T*, E>::Result(Result<const T*, E>&& other) : mPayload(other.mPayload) { + other.mPayload = detail::kEmptyPayload; +} + +template <typename T, typename E> +Result<const T*, E>& Result<const T*, E>::operator=(Result<const T*, E>&& other) { + ASSERT(mPayload == detail::kEmptyPayload); + mPayload = other.mPayload; + other.mPayload = detail::kEmptyPayload; + return *this; +} + +template <typename T, typename E> +Result<const T*, E>::~Result() { + ASSERT(mPayload == detail::kEmptyPayload); +} + +template <typename T, typename E> +bool Result<const T*, E>::IsError() const { + return detail::GetPayloadType(mPayload) == detail::Error; +} + +template <typename T, typename E> +bool Result<const T*, E>::IsSuccess() const { + return detail::GetPayloadType(mPayload) == detail::Success; +} + +template <typename T, typename E> +const T* Result<const T*, E>::AcquireSuccess() { + T* success = detail::GetSuccessFromPayload<T>(mPayload); + mPayload = detail::kEmptyPayload; + return success; +} + +template <typename T, typename E> +std::unique_ptr<E> Result<const T*, E>::AcquireError() { + std::unique_ptr<E> error(detail::GetErrorFromPayload<E>(mPayload)); + mPayload = detail::kEmptyPayload; + return std::move(error); +} + +// Implementation of Result<Ref<T>, E> +template <typename T, typename E> +template <typename U> +Result<Ref<T>, E>::Result(Ref<U>&& success) + : mPayload(detail::MakePayload(success.Detach(), detail::Success)) { + static_assert(std::is_convertible<U*, T*>::value); +} + +template <typename T, typename E> +template <typename U> +Result<Ref<T>, E>::Result(const Ref<U>& success) : Result(Ref<U>(success)) { +} + +template <typename T, typename E> +Result<Ref<T>, E>::Result(std::unique_ptr<E> error) + : mPayload(detail::MakePayload(error.release(), detail::Error)) { +} + +template <typename T, typename E> +template <typename U> +Result<Ref<T>, E>::Result(Result<Ref<U>, E>&& other) : mPayload(other.mPayload) { + static_assert(std::is_convertible<U*, T*>::value); + other.mPayload = detail::kEmptyPayload; +} + +template <typename T, typename E> +template <typename U> +Result<Ref<U>, E>& Result<Ref<T>, E>::operator=(Result<Ref<U>, E>&& other) { + static_assert(std::is_convertible<U*, T*>::value); + ASSERT(mPayload == detail::kEmptyPayload); + mPayload = other.mPayload; + other.mPayload = detail::kEmptyPayload; + return *this; +} + +template <typename T, typename E> +Result<Ref<T>, E>::~Result() { + ASSERT(mPayload == detail::kEmptyPayload); +} + +template <typename T, typename E> +bool Result<Ref<T>, E>::IsError() const { + return detail::GetPayloadType(mPayload) == detail::Error; +} + +template <typename T, typename E> +bool Result<Ref<T>, E>::IsSuccess() const { + return detail::GetPayloadType(mPayload) == detail::Success; +} + +template <typename T, typename E> +Ref<T> Result<Ref<T>, E>::AcquireSuccess() { + ASSERT(IsSuccess()); + Ref<T> success = AcquireRef(detail::GetSuccessFromPayload<T>(mPayload)); + mPayload = detail::kEmptyPayload; + return success; +} + +template <typename T, typename E> +std::unique_ptr<E> Result<Ref<T>, E>::AcquireError() { + ASSERT(IsError()); + std::unique_ptr<E> error(detail::GetErrorFromPayload<E>(mPayload)); + mPayload = detail::kEmptyPayload; + return std::move(error); +} + +// Implementation of Result<T, E> +template <typename T, typename E> +Result<T, E>::Result(T&& success) : mType(Success), mSuccess(std::move(success)) { +} + +template <typename T, typename E> +Result<T, E>::Result(std::unique_ptr<E> error) : mType(Error), mError(std::move(error)) { +} + +template <typename T, typename E> +Result<T, E>::~Result() { + ASSERT(mType == Acquired); +} + +template <typename T, typename E> +Result<T, E>::Result(Result<T, E>&& other) + : mType(other.mType), mError(std::move(other.mError)), mSuccess(std::move(other.mSuccess)) { + other.mType = Acquired; +} +template <typename T, typename E> +Result<T, E>& Result<T, E>::operator=(Result<T, E>&& other) { + mType = other.mType; + mError = std::move(other.mError); + mSuccess = std::move(other.mSuccess); + other.mType = Acquired; + return *this; +} + +template <typename T, typename E> +bool Result<T, E>::IsError() const { + return mType == Error; +} + +template <typename T, typename E> +bool Result<T, E>::IsSuccess() const { + return mType == Success; +} + +template <typename T, typename E> +T&& Result<T, E>::AcquireSuccess() { + ASSERT(mType == Success); + mType = Acquired; + return std::move(mSuccess); +} + +template <typename T, typename E> +std::unique_ptr<E> Result<T, E>::AcquireError() { + ASSERT(mType == Error); + mType = Acquired; + return std::move(mError); +} + +#endif // COMMON_RESULT_H_
diff --git a/src/dawn/common/SerialMap.h b/src/dawn/common/SerialMap.h new file mode 100644 index 0000000..750f16e --- /dev/null +++ b/src/dawn/common/SerialMap.h
@@ -0,0 +1,76 @@ +// Copyright 2018 The Dawn Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#ifndef COMMON_SERIALMAP_H_ +#define COMMON_SERIALMAP_H_ + +#include "dawn/common/SerialStorage.h" + +#include <map> +#include <vector> + +template <typename Serial, typename Value> +class SerialMap; + +template <typename SerialT, typename ValueT> +struct SerialStorageTraits<SerialMap<SerialT, ValueT>> { + using Serial = SerialT; + using Value = ValueT; + using Storage = std::map<Serial, std::vector<Value>>; + using StorageIterator = typename Storage::iterator; + using ConstStorageIterator = typename Storage::const_iterator; +}; + +// SerialMap stores a map from Serial to Value. +// Unlike SerialQueue, items may be enqueued with Serials in any +// arbitrary order. SerialMap provides useful iterators for iterating +// through Value items in order of increasing Serial. +template <typename Serial, typename Value> +class SerialMap : public SerialStorage<SerialMap<Serial, Value>> { + public: + void Enqueue(const Value& value, Serial serial); + void Enqueue(Value&& value, Serial serial); + void Enqueue(const std::vector<Value>& values, Serial serial); + void Enqueue(std::vector<Value>&& values, Serial serial); +}; + +// SerialMap + +template <typename Serial, typename Value> +void SerialMap<Serial, Value>::Enqueue(const Value& value, Serial serial) { + this->mStorage[serial].emplace_back(value); +} + +template <typename Serial, typename Value> +void SerialMap<Serial, Value>::Enqueue(Value&& value, Serial serial) { + this->mStorage[serial].emplace_back(value); +} + +template <typename Serial, typename Value> +void SerialMap<Serial, Value>::Enqueue(const std::vector<Value>& values, Serial serial) { + DAWN_ASSERT(values.size() > 0); + for (const Value& value : values) { + Enqueue(value, serial); + } +} + +template <typename Serial, typename Value> +void SerialMap<Serial, Value>::Enqueue(std::vector<Value>&& values, Serial serial) { + DAWN_ASSERT(values.size() > 0); + for (const Value& value : values) { + Enqueue(value, serial); + } +} + +#endif // COMMON_SERIALMAP_H_
diff --git a/src/dawn/common/SerialQueue.h b/src/dawn/common/SerialQueue.h new file mode 100644 index 0000000..3e33f1e --- /dev/null +++ b/src/dawn/common/SerialQueue.h
@@ -0,0 +1,85 @@ +// Copyright 2017 The Dawn Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#ifndef COMMON_SERIALQUEUE_H_ +#define COMMON_SERIALQUEUE_H_ + +#include "dawn/common/SerialStorage.h" + +#include <vector> + +template <typename Serial, typename Value> +class SerialQueue; + +template <typename SerialT, typename ValueT> +struct SerialStorageTraits<SerialQueue<SerialT, ValueT>> { + using Serial = SerialT; + using Value = ValueT; + using SerialPair = std::pair<Serial, std::vector<Value>>; + using Storage = std::vector<SerialPair>; + using StorageIterator = typename Storage::iterator; + using ConstStorageIterator = typename Storage::const_iterator; +}; + +// SerialQueue stores an associative list mapping a Serial to Value. +// It enforces that the Serials enqueued are strictly non-decreasing. +// This makes it very efficient iterate or clear all items added up +// to some Serial value because they are stored contiguously in memory. +template <typename Serial, typename Value> +class SerialQueue : public SerialStorage<SerialQueue<Serial, Value>> { + public: + // The serial must be given in (not strictly) increasing order. + void Enqueue(const Value& value, Serial serial); + void Enqueue(Value&& value, Serial serial); + void Enqueue(const std::vector<Value>& values, Serial serial); + void Enqueue(std::vector<Value>&& values, Serial serial); +}; + +// SerialQueue + +template <typename Serial, typename Value> +void SerialQueue<Serial, Value>::Enqueue(const Value& value, Serial serial) { + DAWN_ASSERT(this->Empty() || this->mStorage.back().first <= serial); + + if (this->Empty() || this->mStorage.back().first < serial) { + this->mStorage.emplace_back(serial, std::vector<Value>{}); + } + this->mStorage.back().second.push_back(value); +} + +template <typename Serial, typename Value> +void SerialQueue<Serial, Value>::Enqueue(Value&& value, Serial serial) { + DAWN_ASSERT(this->Empty() || this->mStorage.back().first <= serial); + + if (this->Empty() || this->mStorage.back().first < serial) { + this->mStorage.emplace_back(serial, std::vector<Value>{}); + } + this->mStorage.back().second.push_back(std::move(value)); +} + +template <typename Serial, typename Value> +void SerialQueue<Serial, Value>::Enqueue(const std::vector<Value>& values, Serial serial) { + DAWN_ASSERT(values.size() > 0); + DAWN_ASSERT(this->Empty() || this->mStorage.back().first <= serial); + this->mStorage.emplace_back(serial, values); +} + +template <typename Serial, typename Value> +void SerialQueue<Serial, Value>::Enqueue(std::vector<Value>&& values, Serial serial) { + DAWN_ASSERT(values.size() > 0); + DAWN_ASSERT(this->Empty() || this->mStorage.back().first <= serial); + this->mStorage.emplace_back(serial, values); +} + +#endif // COMMON_SERIALQUEUE_H_
diff --git a/src/dawn/common/SerialStorage.h b/src/dawn/common/SerialStorage.h new file mode 100644 index 0000000..8a103f5 --- /dev/null +++ b/src/dawn/common/SerialStorage.h
@@ -0,0 +1,322 @@ +// Copyright 2018 The Dawn Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#ifndef COMMON_SERIALSTORAGE_H_ +#define COMMON_SERIALSTORAGE_H_ + +#include "dawn/common/Assert.h" + +#include <cstdint> +#include <utility> + +template <typename T> +struct SerialStorageTraits {}; + +template <typename Derived> +class SerialStorage { + protected: + using Serial = typename SerialStorageTraits<Derived>::Serial; + using Value = typename SerialStorageTraits<Derived>::Value; + using Storage = typename SerialStorageTraits<Derived>::Storage; + using StorageIterator = typename SerialStorageTraits<Derived>::StorageIterator; + using ConstStorageIterator = typename SerialStorageTraits<Derived>::ConstStorageIterator; + + public: + class Iterator { + public: + Iterator(StorageIterator start); + Iterator& operator++(); + + bool operator==(const Iterator& other) const; + bool operator!=(const Iterator& other) const; + Value& operator*() const; + + private: + StorageIterator mStorageIterator; + // Special case the mSerialIterator when it should be equal to mStorageIterator.begin() + // otherwise we could ask mStorageIterator.begin() when mStorageIterator is mStorage.end() + // which is invalid. mStorageIterator.begin() is tagged with a nullptr. + Value* mSerialIterator; + }; + + class ConstIterator { + public: + ConstIterator(ConstStorageIterator start); + ConstIterator& operator++(); + + bool operator==(const ConstIterator& other) const; + bool operator!=(const ConstIterator& other) const; + const Value& operator*() const; + + private: + ConstStorageIterator mStorageIterator; + const Value* mSerialIterator; + }; + + class BeginEnd { + public: + BeginEnd(StorageIterator start, StorageIterator end); + + Iterator begin() const; + Iterator end() const; + + private: + StorageIterator mStartIt; + StorageIterator mEndIt; + }; + + class ConstBeginEnd { + public: + ConstBeginEnd(ConstStorageIterator start, ConstStorageIterator end); + + ConstIterator begin() const; + ConstIterator end() const; + + private: + ConstStorageIterator mStartIt; + ConstStorageIterator mEndIt; + }; + + // Derived classes may specialize constraits for elements stored + // Ex.) SerialQueue enforces that the serial must be given in (not strictly) + // increasing order + template <typename... Params> + void Enqueue(Params&&... args, Serial serial) { + Derived::Enqueue(std::forward<Params>(args)..., serial); + } + + bool Empty() const; + + // The UpTo variants of Iterate and Clear affect all values associated to a serial + // that is smaller OR EQUAL to the given serial. Iterating is done like so: + // for (const T& value : queue.IterateAll()) { stuff(T); } + ConstBeginEnd IterateAll() const; + ConstBeginEnd IterateUpTo(Serial serial) const; + BeginEnd IterateAll(); + BeginEnd IterateUpTo(Serial serial); + + void Clear(); + void ClearUpTo(Serial serial); + + Serial FirstSerial() const; + Serial LastSerial() const; + + protected: + // Returns the first StorageIterator that a serial bigger than serial. + ConstStorageIterator FindUpTo(Serial serial) const; + StorageIterator FindUpTo(Serial serial); + Storage mStorage; +}; + +// SerialStorage + +template <typename Derived> +bool SerialStorage<Derived>::Empty() const { + return mStorage.empty(); +} + +template <typename Derived> +typename SerialStorage<Derived>::ConstBeginEnd SerialStorage<Derived>::IterateAll() const { + return {mStorage.begin(), mStorage.end()}; +} + +template <typename Derived> +typename SerialStorage<Derived>::ConstBeginEnd SerialStorage<Derived>::IterateUpTo( + Serial serial) const { + return {mStorage.begin(), FindUpTo(serial)}; +} + +template <typename Derived> +typename SerialStorage<Derived>::BeginEnd SerialStorage<Derived>::IterateAll() { + return {mStorage.begin(), mStorage.end()}; +} + +template <typename Derived> +typename SerialStorage<Derived>::BeginEnd SerialStorage<Derived>::IterateUpTo(Serial serial) { + return {mStorage.begin(), FindUpTo(serial)}; +} + +template <typename Derived> +void SerialStorage<Derived>::Clear() { + mStorage.clear(); +} + +template <typename Derived> +void SerialStorage<Derived>::ClearUpTo(Serial serial) { + mStorage.erase(mStorage.begin(), FindUpTo(serial)); +} + +template <typename Derived> +typename SerialStorage<Derived>::Serial SerialStorage<Derived>::FirstSerial() const { + DAWN_ASSERT(!Empty()); + return mStorage.begin()->first; +} + +template <typename Derived> +typename SerialStorage<Derived>::Serial SerialStorage<Derived>::LastSerial() const { + DAWN_ASSERT(!Empty()); + return mStorage.back().first; +} + +template <typename Derived> +typename SerialStorage<Derived>::ConstStorageIterator SerialStorage<Derived>::FindUpTo( + Serial serial) const { + auto it = mStorage.begin(); + while (it != mStorage.end() && it->first <= serial) { + it++; + } + return it; +} + +template <typename Derived> +typename SerialStorage<Derived>::StorageIterator SerialStorage<Derived>::FindUpTo(Serial serial) { + auto it = mStorage.begin(); + while (it != mStorage.end() && it->first <= serial) { + it++; + } + return it; +} + +// SerialStorage::BeginEnd + +template <typename Derived> +SerialStorage<Derived>::BeginEnd::BeginEnd(typename SerialStorage<Derived>::StorageIterator start, + typename SerialStorage<Derived>::StorageIterator end) + : mStartIt(start), mEndIt(end) { +} + +template <typename Derived> +typename SerialStorage<Derived>::Iterator SerialStorage<Derived>::BeginEnd::begin() const { + return {mStartIt}; +} + +template <typename Derived> +typename SerialStorage<Derived>::Iterator SerialStorage<Derived>::BeginEnd::end() const { + return {mEndIt}; +} + +// SerialStorage::Iterator + +template <typename Derived> +SerialStorage<Derived>::Iterator::Iterator(typename SerialStorage<Derived>::StorageIterator start) + : mStorageIterator(start), mSerialIterator(nullptr) { +} + +template <typename Derived> +typename SerialStorage<Derived>::Iterator& SerialStorage<Derived>::Iterator::operator++() { + Value* vectorData = mStorageIterator->second.data(); + + if (mSerialIterator == nullptr) { + mSerialIterator = vectorData + 1; + } else { + mSerialIterator++; + } + + if (mSerialIterator >= vectorData + mStorageIterator->second.size()) { + mSerialIterator = nullptr; + mStorageIterator++; + } + + return *this; +} + +template <typename Derived> +bool SerialStorage<Derived>::Iterator::operator==( + const typename SerialStorage<Derived>::Iterator& other) const { + return other.mStorageIterator == mStorageIterator && other.mSerialIterator == mSerialIterator; +} + +template <typename Derived> +bool SerialStorage<Derived>::Iterator::operator!=( + const typename SerialStorage<Derived>::Iterator& other) const { + return !(*this == other); +} + +template <typename Derived> +typename SerialStorage<Derived>::Value& SerialStorage<Derived>::Iterator::operator*() const { + if (mSerialIterator == nullptr) { + return *mStorageIterator->second.begin(); + } + return *mSerialIterator; +} + +// SerialStorage::ConstBeginEnd + +template <typename Derived> +SerialStorage<Derived>::ConstBeginEnd::ConstBeginEnd( + typename SerialStorage<Derived>::ConstStorageIterator start, + typename SerialStorage<Derived>::ConstStorageIterator end) + : mStartIt(start), mEndIt(end) { +} + +template <typename Derived> +typename SerialStorage<Derived>::ConstIterator SerialStorage<Derived>::ConstBeginEnd::begin() + const { + return {mStartIt}; +} + +template <typename Derived> +typename SerialStorage<Derived>::ConstIterator SerialStorage<Derived>::ConstBeginEnd::end() const { + return {mEndIt}; +} + +// SerialStorage::ConstIterator + +template <typename Derived> +SerialStorage<Derived>::ConstIterator::ConstIterator( + typename SerialStorage<Derived>::ConstStorageIterator start) + : mStorageIterator(start), mSerialIterator(nullptr) { +} + +template <typename Derived> +typename SerialStorage<Derived>::ConstIterator& +SerialStorage<Derived>::ConstIterator::operator++() { + const Value* vectorData = mStorageIterator->second.data(); + + if (mSerialIterator == nullptr) { + mSerialIterator = vectorData + 1; + } else { + mSerialIterator++; + } + + if (mSerialIterator >= vectorData + mStorageIterator->second.size()) { + mSerialIterator = nullptr; + mStorageIterator++; + } + + return *this; +} + +template <typename Derived> +bool SerialStorage<Derived>::ConstIterator::operator==( + const typename SerialStorage<Derived>::ConstIterator& other) const { + return other.mStorageIterator == mStorageIterator && other.mSerialIterator == mSerialIterator; +} + +template <typename Derived> +bool SerialStorage<Derived>::ConstIterator::operator!=( + const typename SerialStorage<Derived>::ConstIterator& other) const { + return !(*this == other); +} + +template <typename Derived> +const typename SerialStorage<Derived>::Value& SerialStorage<Derived>::ConstIterator::operator*() + const { + if (mSerialIterator == nullptr) { + return *mStorageIterator->second.begin(); + } + return *mSerialIterator; +} + +#endif // COMMON_SERIALSTORAGE_H_
diff --git a/src/dawn/common/SlabAllocator.cpp b/src/dawn/common/SlabAllocator.cpp new file mode 100644 index 0000000..d680ee3 --- /dev/null +++ b/src/dawn/common/SlabAllocator.cpp
@@ -0,0 +1,247 @@ +// Copyright 2020 The Dawn Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include "dawn/common/SlabAllocator.h" + +#include "dawn/common/Assert.h" +#include "dawn/common/Math.h" + +#include <algorithm> +#include <cstdlib> +#include <limits> +#include <new> + +// IndexLinkNode + +SlabAllocatorImpl::IndexLinkNode::IndexLinkNode(Index index, Index nextIndex) + : index(index), nextIndex(nextIndex) { +} + +// Slab + +SlabAllocatorImpl::Slab::Slab(char allocation[], IndexLinkNode* head) + : allocation(allocation), freeList(head), prev(nullptr), next(nullptr), blocksInUse(0) { +} + +SlabAllocatorImpl::Slab::Slab(Slab&& rhs) = default; + +SlabAllocatorImpl::SentinelSlab::SentinelSlab() : Slab(nullptr, nullptr) { +} + +SlabAllocatorImpl::SentinelSlab::SentinelSlab(SentinelSlab&& rhs) = default; + +SlabAllocatorImpl::SentinelSlab::~SentinelSlab() { + Slab* slab = this->next; + while (slab != nullptr) { + Slab* next = slab->next; + ASSERT(slab->blocksInUse == 0); + // Delete the slab's allocation. The slab is allocated inside slab->allocation. + delete[] slab->allocation; + slab = next; + } +} + +// SlabAllocatorImpl + +SlabAllocatorImpl::Index SlabAllocatorImpl::kInvalidIndex = + std::numeric_limits<SlabAllocatorImpl::Index>::max(); + +SlabAllocatorImpl::SlabAllocatorImpl(Index blocksPerSlab, + uint32_t objectSize, + uint32_t objectAlignment) + : mAllocationAlignment(std::max(static_cast<uint32_t>(alignof(Slab)), objectAlignment)), + mSlabBlocksOffset(Align(sizeof(Slab), objectAlignment)), + mIndexLinkNodeOffset(Align(objectSize, alignof(IndexLinkNode))), + mBlockStride(Align(mIndexLinkNodeOffset + sizeof(IndexLinkNode), objectAlignment)), + mBlocksPerSlab(blocksPerSlab), + mTotalAllocationSize( + // required allocation size + static_cast<size_t>(mSlabBlocksOffset) + mBlocksPerSlab * mBlockStride + + // Pad the allocation size by mAllocationAlignment so that the aligned allocation still + // fulfills the required size. + mAllocationAlignment) { + ASSERT(IsPowerOfTwo(mAllocationAlignment)); +} + +SlabAllocatorImpl::SlabAllocatorImpl(SlabAllocatorImpl&& rhs) + : mAllocationAlignment(rhs.mAllocationAlignment), + mSlabBlocksOffset(rhs.mSlabBlocksOffset), + mIndexLinkNodeOffset(rhs.mIndexLinkNodeOffset), + mBlockStride(rhs.mBlockStride), + mBlocksPerSlab(rhs.mBlocksPerSlab), + mTotalAllocationSize(rhs.mTotalAllocationSize), + mAvailableSlabs(std::move(rhs.mAvailableSlabs)), + mFullSlabs(std::move(rhs.mFullSlabs)), + mRecycledSlabs(std::move(rhs.mRecycledSlabs)) { +} + +SlabAllocatorImpl::~SlabAllocatorImpl() = default; + +SlabAllocatorImpl::IndexLinkNode* SlabAllocatorImpl::OffsetFrom( + IndexLinkNode* node, + std::make_signed_t<Index> offset) const { + return reinterpret_cast<IndexLinkNode*>(reinterpret_cast<char*>(node) + + static_cast<intptr_t>(mBlockStride) * offset); +} + +SlabAllocatorImpl::IndexLinkNode* SlabAllocatorImpl::NodeFromObject(void* object) const { + return reinterpret_cast<SlabAllocatorImpl::IndexLinkNode*>(static_cast<char*>(object) + + mIndexLinkNodeOffset); +} + +void* SlabAllocatorImpl::ObjectFromNode(IndexLinkNode* node) const { + return static_cast<void*>(reinterpret_cast<char*>(node) - mIndexLinkNodeOffset); +} + +bool SlabAllocatorImpl::IsNodeInSlab(Slab* slab, IndexLinkNode* node) const { + char* firstObjectPtr = reinterpret_cast<char*>(slab) + mSlabBlocksOffset; + IndexLinkNode* firstNode = NodeFromObject(firstObjectPtr); + IndexLinkNode* lastNode = OffsetFrom(firstNode, mBlocksPerSlab - 1); + return node >= firstNode && node <= lastNode && node->index < mBlocksPerSlab; +} + +void SlabAllocatorImpl::PushFront(Slab* slab, IndexLinkNode* node) const { + ASSERT(IsNodeInSlab(slab, node)); + + IndexLinkNode* head = slab->freeList; + if (head == nullptr) { + node->nextIndex = kInvalidIndex; + } else { + ASSERT(IsNodeInSlab(slab, head)); + node->nextIndex = head->index; + } + slab->freeList = node; + + ASSERT(slab->blocksInUse != 0); + slab->blocksInUse--; +} + +SlabAllocatorImpl::IndexLinkNode* SlabAllocatorImpl::PopFront(Slab* slab) const { + ASSERT(slab->freeList != nullptr); + + IndexLinkNode* head = slab->freeList; + if (head->nextIndex == kInvalidIndex) { + slab->freeList = nullptr; + } else { + ASSERT(IsNodeInSlab(slab, head)); + slab->freeList = OffsetFrom(head, head->nextIndex - head->index); + ASSERT(IsNodeInSlab(slab, slab->freeList)); + } + + ASSERT(slab->blocksInUse < mBlocksPerSlab); + slab->blocksInUse++; + return head; +} + +void SlabAllocatorImpl::SentinelSlab::Prepend(SlabAllocatorImpl::Slab* slab) { + if (this->next != nullptr) { + this->next->prev = slab; + } + slab->prev = this; + slab->next = this->next; + this->next = slab; +} + +void SlabAllocatorImpl::Slab::Splice() { + SlabAllocatorImpl::Slab* originalPrev = this->prev; + SlabAllocatorImpl::Slab* originalNext = this->next; + + this->prev = nullptr; + this->next = nullptr; + + ASSERT(originalPrev != nullptr); + + // Set the originalNext's prev pointer. + if (originalNext != nullptr) { + originalNext->prev = originalPrev; + } + + // Now, set the originalNext as the originalPrev's new next. + originalPrev->next = originalNext; +} + +void* SlabAllocatorImpl::Allocate() { + if (mAvailableSlabs.next == nullptr) { + GetNewSlab(); + } + + Slab* slab = mAvailableSlabs.next; + IndexLinkNode* node = PopFront(slab); + ASSERT(node != nullptr); + + // Move full slabs to a separate list, so allocate can always return quickly. + if (slab->blocksInUse == mBlocksPerSlab) { + slab->Splice(); + mFullSlabs.Prepend(slab); + } + + return ObjectFromNode(node); +} + +void SlabAllocatorImpl::Deallocate(void* ptr) { + IndexLinkNode* node = NodeFromObject(ptr); + + ASSERT(node->index < mBlocksPerSlab); + void* firstAllocation = ObjectFromNode(OffsetFrom(node, -node->index)); + Slab* slab = reinterpret_cast<Slab*>(static_cast<char*>(firstAllocation) - mSlabBlocksOffset); + ASSERT(slab != nullptr); + + bool slabWasFull = slab->blocksInUse == mBlocksPerSlab; + + ASSERT(slab->blocksInUse != 0); + PushFront(slab, node); + + if (slabWasFull) { + // Slab is in the full list. Move it to the recycled list. + ASSERT(slab->freeList != nullptr); + slab->Splice(); + mRecycledSlabs.Prepend(slab); + } + + // TODO(crbug.com/dawn/825): Occasionally prune slabs if |blocksInUse == 0|. + // Doing so eagerly hurts performance. +} + +void SlabAllocatorImpl::GetNewSlab() { + // Should only be called when there are no available slabs. + ASSERT(mAvailableSlabs.next == nullptr); + + if (mRecycledSlabs.next != nullptr) { + // If the recycled list is non-empty, swap their contents. + std::swap(mAvailableSlabs.next, mRecycledSlabs.next); + + // We swapped the next pointers, so the prev pointer is wrong. + // Update it here. + mAvailableSlabs.next->prev = &mAvailableSlabs; + ASSERT(mRecycledSlabs.next == nullptr); + return; + } + + // TODO(crbug.com/dawn/824): Use aligned_alloc when possible. It should be available with + // C++17 but on macOS it also requires macOS 10.15 to work. + char* allocation = new char[mTotalAllocationSize]; + char* alignedPtr = AlignPtr(allocation, mAllocationAlignment); + + char* dataStart = alignedPtr + mSlabBlocksOffset; + + IndexLinkNode* node = NodeFromObject(dataStart); + for (uint32_t i = 0; i < mBlocksPerSlab; ++i) { + new (OffsetFrom(node, i)) IndexLinkNode(i, i + 1); + } + + IndexLinkNode* lastNode = OffsetFrom(node, mBlocksPerSlab - 1); + lastNode->nextIndex = kInvalidIndex; + + mAvailableSlabs.Prepend(new (alignedPtr) Slab(allocation, node)); +}
diff --git a/src/dawn/common/SlabAllocator.h b/src/dawn/common/SlabAllocator.h new file mode 100644 index 0000000..58d2d94 --- /dev/null +++ b/src/dawn/common/SlabAllocator.h
@@ -0,0 +1,184 @@ +// Copyright 2020 The Dawn Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#ifndef COMMON_SLABALLOCATOR_H_ +#define COMMON_SLABALLOCATOR_H_ + +#include "dawn/common/PlacementAllocated.h" + +#include <cstdint> +#include <type_traits> +#include <utility> + +// The SlabAllocator allocates objects out of one or more fixed-size contiguous "slabs" of memory. +// This makes it very quick to allocate and deallocate fixed-size objects because the allocator only +// needs to index an offset into pre-allocated memory. It is similar to a pool-allocator that +// recycles memory from previous allocations, except multiple allocations are hosted contiguously in +// one large slab. +// +// Internally, the SlabAllocator stores slabs as a linked list to avoid extra indirections indexing +// into an std::vector. To service an allocation request, the allocator only needs to know the first +// currently available slab. There are three backing linked lists: AVAILABLE, FULL, and RECYCLED. +// A slab that is AVAILABLE can be used to immediately service allocation requests. Once it has no +// remaining space, it is moved to the FULL state. When a FULL slab sees any deallocations, it is +// moved to the RECYCLED state. The RECYCLED state is separate from the AVAILABLE state so that +// deallocations don't immediately prepend slabs to the AVAILABLE list, and change the current slab +// servicing allocations. When the AVAILABLE list becomes empty is it swapped with the RECYCLED +// list. +// +// Allocated objects are placement-allocated with some extra info at the end (we'll call the Object +// plus the extra bytes a "block") used to specify the constant index of the block in its parent +// slab, as well as the index of the next available block. So, following the block next-indices +// forms a linked list of free blocks. +// +// Slab creation: When a new slab is allocated, sufficient memory is allocated for it, and then the +// slab metadata plus all of its child blocks are placement-allocated into the memory. Indices and +// next-indices are initialized to form the free-list of blocks. +// +// Allocation: When an object is allocated, if there is no space available in an existing slab, a +// new slab is created (or an old slab is recycled). The first block of the slab is removed and +// returned. +// +// Deallocation: When an object is deallocated, it can compute the pointer to its parent slab +// because it stores the index of its own allocation. That block is then prepended to the slab's +// free list. +class SlabAllocatorImpl { + public: + // Allocations host their current index and the index of the next free block. + // Because this is an index, and not a byte offset, it can be much smaller than a size_t. + // TODO(crbug.com/dawn/825): Is uint8_t sufficient? + using Index = uint16_t; + + SlabAllocatorImpl(SlabAllocatorImpl&& rhs); + + protected: + // This is essentially a singly linked list using indices instead of pointers, + // so we store the index of "this" in |this->index|. + struct IndexLinkNode : PlacementAllocated { + IndexLinkNode(Index index, Index nextIndex); + + const Index index; // The index of this block in the slab. + Index nextIndex; // The index of the next available block. kInvalidIndex, if none. + }; + + struct Slab : PlacementAllocated { + // A slab is placement-allocated into an aligned pointer from a separate allocation. + // Ownership of the allocation is transferred to the slab on creation. + // | ---------- allocation --------- | + // | pad | Slab | data ------------> | + Slab(char allocation[], IndexLinkNode* head); + Slab(Slab&& rhs); + + void Splice(); + + char* allocation; + IndexLinkNode* freeList; + Slab* prev; + Slab* next; + Index blocksInUse; + }; + + SlabAllocatorImpl(Index blocksPerSlab, uint32_t objectSize, uint32_t objectAlignment); + ~SlabAllocatorImpl(); + + // Allocate a new block of memory. + void* Allocate(); + + // Deallocate a block of memory. + void Deallocate(void* ptr); + + private: + // The maximum value is reserved to indicate the end of the list. + static Index kInvalidIndex; + + // Get the IndexLinkNode |offset| slots away. + IndexLinkNode* OffsetFrom(IndexLinkNode* node, std::make_signed_t<Index> offset) const; + + // Compute the pointer to the IndexLinkNode from an allocated object. + IndexLinkNode* NodeFromObject(void* object) const; + + // Compute the pointer to the object from an IndexLinkNode. + void* ObjectFromNode(IndexLinkNode* node) const; + + bool IsNodeInSlab(Slab* slab, IndexLinkNode* node) const; + + // The Slab stores a linked-list of free allocations. + // PushFront/PopFront adds/removes an allocation from the free list. + void PushFront(Slab* slab, IndexLinkNode* node) const; + IndexLinkNode* PopFront(Slab* slab) const; + + // Replace the current slab with a new one, and chain the old one off of it. + // Both slabs may still be used for for allocation/deallocation, but older slabs + // will be a little slower to get allocations from. + void GetNewSlab(); + + const uint32_t mAllocationAlignment; + + // | Slab | pad | Obj | pad | Node | pad | Obj | pad | Node | pad | .... + // | -----------| mSlabBlocksOffset + // | | ---------------------- | mBlockStride + // | | ----------| mIndexLinkNodeOffset + // | --------------------------------------> (mSlabBlocksOffset + mBlocksPerSlab * mBlockStride) + + // A Slab is metadata, followed by the aligned memory to allocate out of. |mSlabBlocksOffset| is + // the offset to the start of the aligned memory region. + const uint32_t mSlabBlocksOffset; + + // The IndexLinkNode is stored after the Allocation itself. This is the offset to it. + const uint32_t mIndexLinkNodeOffset; + + // Because alignment of allocations may introduce padding, |mBlockStride| is the + // distance between aligned blocks of (Allocation + IndexLinkNode) + const uint32_t mBlockStride; + + const Index mBlocksPerSlab; // The total number of blocks in a slab. + + const size_t mTotalAllocationSize; + + struct SentinelSlab : Slab { + SentinelSlab(); + ~SentinelSlab(); + + SentinelSlab(SentinelSlab&& rhs); + + void Prepend(Slab* slab); + }; + + SentinelSlab mAvailableSlabs; // Available slabs to service allocations. + SentinelSlab mFullSlabs; // Full slabs. Stored here so we can skip checking them. + SentinelSlab mRecycledSlabs; // Recycled slabs. Not immediately added to |mAvailableSlabs| so + // we don't thrash the current "active" slab. +}; + +template <typename T> +class SlabAllocator : public SlabAllocatorImpl { + public: + SlabAllocator(size_t totalObjectBytes, + uint32_t objectSize = sizeof(T), + uint32_t objectAlignment = alignof(T)) + : SlabAllocatorImpl(totalObjectBytes / objectSize, objectSize, objectAlignment) { + } + + template <typename... Args> + T* Allocate(Args&&... args) { + void* ptr = SlabAllocatorImpl::Allocate(); + return new (ptr) T(std::forward<Args>(args)...); + } + + void Deallocate(T* object) { + SlabAllocatorImpl::Deallocate(object); + } +}; + +#endif // COMMON_SLABALLOCATOR_H_
diff --git a/src/dawn/common/StackContainer.h b/src/dawn/common/StackContainer.h new file mode 100644 index 0000000..4de688f --- /dev/null +++ b/src/dawn/common/StackContainer.h
@@ -0,0 +1,262 @@ +// Copyright (c) 2012 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +// This file is a modified copy of Chromium's /src/base/containers/stack_container.h + +#ifndef COMMON_STACKCONTAINER_H_ +#define COMMON_STACKCONTAINER_H_ + +#include "dawn/common/Compiler.h" + +#include <cstddef> +#include <vector> + +// This allocator can be used with STL containers to provide a stack buffer +// from which to allocate memory and overflows onto the heap. This stack buffer +// would be allocated on the stack and allows us to avoid heap operations in +// some situations. +// +// STL likes to make copies of allocators, so the allocator itself can't hold +// the data. Instead, we make the creator responsible for creating a +// StackAllocator::Source which contains the data. Copying the allocator +// merely copies the pointer to this shared source, so all allocators created +// based on our allocator will share the same stack buffer. +// +// This stack buffer implementation is very simple. The first allocation that +// fits in the stack buffer will use the stack buffer. Any subsequent +// allocations will not use the stack buffer, even if there is unused room. +// This makes it appropriate for array-like containers, but the caller should +// be sure to reserve() in the container up to the stack buffer size. Otherwise +// the container will allocate a small array which will "use up" the stack +// buffer. +template <typename T, size_t stack_capacity> +class StackAllocator : public std::allocator<T> { + public: + typedef typename std::allocator<T>::pointer pointer; + typedef typename std::allocator<T>::size_type size_type; + + // Backing store for the allocator. The container owner is responsible for + // maintaining this for as long as any containers using this allocator are + // live. + struct Source { + Source() : used_stack_buffer_(false) { + } + + // Casts the buffer in its right type. + T* stack_buffer() { + return reinterpret_cast<T*>(stack_buffer_); + } + const T* stack_buffer() const { + return reinterpret_cast<const T*>(&stack_buffer_); + } + + // The buffer itself. It is not of type T because we don't want the + // constructors and destructors to be automatically called. Define a POD + // buffer of the right size instead. + alignas(T) char stack_buffer_[sizeof(T[stack_capacity])]; +#if defined(DAWN_COMPILER_GCC) && !defined(__x86_64__) && !defined(__i386__) + static_assert(alignof(T) <= 16, "http://crbug.com/115612"); +#endif + + // Set when the stack buffer is used for an allocation. We do not track + // how much of the buffer is used, only that somebody is using it. + bool used_stack_buffer_; + }; + + // Used by containers when they want to refer to an allocator of type U. + template <typename U> + struct rebind { + typedef StackAllocator<U, stack_capacity> other; + }; + + // For the straight up copy c-tor, we can share storage. + StackAllocator(const StackAllocator<T, stack_capacity>& rhs) + : std::allocator<T>(), source_(rhs.source_) { + } + + // ISO C++ requires the following constructor to be defined, + // and std::vector in VC++2008SP1 Release fails with an error + // in the class _Container_base_aux_alloc_real (from <xutility>) + // if the constructor does not exist. + // For this constructor, we cannot share storage; there's + // no guarantee that the Source buffer of Ts is large enough + // for Us. + // TODO: If we were fancy pants, perhaps we could share storage + // iff sizeof(T) == sizeof(U). + template <typename U, size_t other_capacity> + StackAllocator(const StackAllocator<U, other_capacity>& other) : source_(nullptr) { + } + + // This constructor must exist. It creates a default allocator that doesn't + // actually have a stack buffer. glibc's std::string() will compare the + // current allocator against the default-constructed allocator, so this + // should be fast. + StackAllocator() : source_(nullptr) { + } + + explicit StackAllocator(Source* source) : source_(source) { + } + + // Actually do the allocation. Use the stack buffer if nobody has used it yet + // and the size requested fits. Otherwise, fall through to the standard + // allocator. + pointer allocate(size_type n) { + if (source_ && !source_->used_stack_buffer_ && n <= stack_capacity) { + source_->used_stack_buffer_ = true; + return source_->stack_buffer(); + } else { + return std::allocator<T>::allocate(n); + } + } + + // Free: when trying to free the stack buffer, just mark it as free. For + // non-stack-buffer pointers, just fall though to the standard allocator. + void deallocate(pointer p, size_type n) { + if (source_ && p == source_->stack_buffer()) + source_->used_stack_buffer_ = false; + else + std::allocator<T>::deallocate(p, n); + } + + private: + Source* source_; +}; + +// A wrapper around STL containers that maintains a stack-sized buffer that the +// initial capacity of the vector is based on. Growing the container beyond the +// stack capacity will transparently overflow onto the heap. The container must +// support reserve(). +// +// This will not work with std::string since some implementations allocate +// more bytes than requested in calls to reserve(), forcing the allocation onto +// the heap. http://crbug.com/709273 +// +// WATCH OUT: the ContainerType MUST use the proper StackAllocator for this +// type. This object is really intended to be used only internally. You'll want +// to use the wrappers below for different types. +template <typename TContainerType, size_t stack_capacity> +class StackContainer { + public: + typedef TContainerType ContainerType; + typedef typename ContainerType::value_type ContainedType; + typedef StackAllocator<ContainedType, stack_capacity> Allocator; + + // Allocator must be constructed before the container! + StackContainer() : allocator_(&stack_data_), container_(allocator_) { + // Make the container use the stack allocation by reserving our buffer size + // before doing anything else. + container_.reserve(stack_capacity); + } + + // Getters for the actual container. + // + // Danger: any copies of this made using the copy constructor must have + // shorter lifetimes than the source. The copy will share the same allocator + // and therefore the same stack buffer as the original. Use std::copy to + // copy into a "real" container for longer-lived objects. + ContainerType& container() { + return container_; + } + const ContainerType& container() const { + return container_; + } + + // Support operator-> to get to the container. This allows nicer syntax like: + // StackContainer<...> foo; + // std::sort(foo->begin(), foo->end()); + ContainerType* operator->() { + return &container_; + } + const ContainerType* operator->() const { + return &container_; + } + + // Retrieves the stack source so that that unit tests can verify that the + // buffer is being used properly. + const typename Allocator::Source& stack_data() const { + return stack_data_; + } + + protected: + typename Allocator::Source stack_data_; + Allocator allocator_; + ContainerType container_; + + private: + StackContainer(const StackContainer& rhs) = delete; + StackContainer& operator=(const StackContainer& rhs) = delete; + StackContainer(StackContainer&& rhs) = delete; + StackContainer& operator=(StackContainer&& rhs) = delete; +}; + +// Range-based iteration support for StackContainer. +template <typename TContainerType, size_t stack_capacity> +auto begin(const StackContainer<TContainerType, stack_capacity>& stack_container) + -> decltype(begin(stack_container.container())) { + return begin(stack_container.container()); +} + +template <typename TContainerType, size_t stack_capacity> +auto begin(StackContainer<TContainerType, stack_capacity>& stack_container) + -> decltype(begin(stack_container.container())) { + return begin(stack_container.container()); +} + +template <typename TContainerType, size_t stack_capacity> +auto end(StackContainer<TContainerType, stack_capacity>& stack_container) + -> decltype(end(stack_container.container())) { + return end(stack_container.container()); +} + +template <typename TContainerType, size_t stack_capacity> +auto end(const StackContainer<TContainerType, stack_capacity>& stack_container) + -> decltype(end(stack_container.container())) { + return end(stack_container.container()); +} + +// StackVector ----------------------------------------------------------------- + +// Example: +// StackVector<int, 16> foo; +// foo->push_back(22); // we have overloaded operator-> +// foo[0] = 10; // as well as operator[] +template <typename T, size_t stack_capacity> +class StackVector + : public StackContainer<std::vector<T, StackAllocator<T, stack_capacity>>, stack_capacity> { + public: + StackVector() + : StackContainer<std::vector<T, StackAllocator<T, stack_capacity>>, stack_capacity>() { + } + + // We need to put this in STL containers sometimes, which requires a copy + // constructor. We can't call the regular copy constructor because that will + // take the stack buffer from the original. Here, we create an empty object + // and make a stack buffer of its own. + StackVector(const StackVector<T, stack_capacity>& other) + : StackContainer<std::vector<T, StackAllocator<T, stack_capacity>>, stack_capacity>() { + this->container().assign(other->begin(), other->end()); + } + + StackVector<T, stack_capacity>& operator=(const StackVector<T, stack_capacity>& other) { + this->container().assign(other->begin(), other->end()); + return *this; + } + + // Vectors are commonly indexed, which isn't very convenient even with + // operator-> (using "->at()" does exception stuff we don't want). + T& operator[](size_t i) { + return this->container().operator[](i); + } + const T& operator[](size_t i) const { + return this->container().operator[](i); + } + + private: + // StackVector(const StackVector& rhs) = delete; + // StackVector& operator=(const StackVector& rhs) = delete; + StackVector(StackVector&& rhs) = delete; + StackVector& operator=(StackVector&& rhs) = delete; +}; + +#endif // COMMON_STACKCONTAINER_H_
diff --git a/src/dawn/common/SwapChainUtils.h b/src/dawn/common/SwapChainUtils.h new file mode 100644 index 0000000..c1ad5f2 --- /dev/null +++ b/src/dawn/common/SwapChainUtils.h
@@ -0,0 +1,40 @@ +// Copyright 2017 The Dawn Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#ifndef COMMON_SWAPCHAINUTILS_H_ +#define COMMON_SWAPCHAINUTILS_H_ + +#include "dawn/dawn_wsi.h" + +template <typename T> +DawnSwapChainImplementation CreateSwapChainImplementation(T* swapChain) { + DawnSwapChainImplementation impl = {}; + impl.userData = swapChain; + impl.Init = [](void* userData, void* wsiContext) { + auto* ctx = static_cast<typename T::WSIContext*>(wsiContext); + reinterpret_cast<T*>(userData)->Init(ctx); + }; + impl.Destroy = [](void* userData) { delete reinterpret_cast<T*>(userData); }; + impl.Configure = [](void* userData, WGPUTextureFormat format, WGPUTextureUsage allowedUsage, + uint32_t width, uint32_t height) { + return static_cast<T*>(userData)->Configure(format, allowedUsage, width, height); + }; + impl.GetNextTexture = [](void* userData, DawnSwapChainNextTexture* nextTexture) { + return static_cast<T*>(userData)->GetNextTexture(nextTexture); + }; + impl.Present = [](void* userData) { return static_cast<T*>(userData)->Present(); }; + return impl; +} + +#endif // COMMON_SWAPCHAINUTILS_H_
diff --git a/src/dawn/common/SystemUtils.cpp b/src/dawn/common/SystemUtils.cpp new file mode 100644 index 0000000..a5ce0f1 --- /dev/null +++ b/src/dawn/common/SystemUtils.cpp
@@ -0,0 +1,229 @@ +// Copyright 2019 The Dawn Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include "dawn/common/SystemUtils.h" + +#include "dawn/common/Assert.h" +#include "dawn/common/Log.h" + +#if defined(DAWN_PLATFORM_WINDOWS) +# include <Windows.h> +# include <vector> +#elif defined(DAWN_PLATFORM_LINUX) +# include <dlfcn.h> +# include <limits.h> +# include <unistd.h> +# include <cstdlib> +#elif defined(DAWN_PLATFORM_MACOS) || defined(DAWN_PLATFORM_IOS) +# include <dlfcn.h> +# include <mach-o/dyld.h> +# include <vector> +#endif + +#include <array> + +#if defined(DAWN_PLATFORM_WINDOWS) +const char* GetPathSeparator() { + return "\\"; +} + +std::pair<std::string, bool> GetEnvironmentVar(const char* variableName) { + // First pass a size of 0 to get the size of variable value. + DWORD sizeWithNullTerminator = GetEnvironmentVariableA(variableName, nullptr, 0); + if (sizeWithNullTerminator == 0) { + DWORD err = GetLastError(); + if (err != ERROR_ENVVAR_NOT_FOUND) { + dawn::WarningLog() << "GetEnvironmentVariableA failed with code " << err; + } + return std::make_pair(std::string(), false); + } + + // Then get variable value with its actual size. + std::vector<char> buffer(sizeWithNullTerminator); + DWORD sizeStored = + GetEnvironmentVariableA(variableName, buffer.data(), static_cast<DWORD>(buffer.size())); + if (sizeStored + 1 != sizeWithNullTerminator) { + DWORD err = GetLastError(); + if (err) { + dawn::WarningLog() << "GetEnvironmentVariableA failed with code " << err; + } + return std::make_pair(std::string(), false); + } + return std::make_pair(std::string(buffer.data(), sizeStored), true); +} + +bool SetEnvironmentVar(const char* variableName, const char* value) { + return SetEnvironmentVariableA(variableName, value) == TRUE; +} +#elif defined(DAWN_PLATFORM_POSIX) +const char* GetPathSeparator() { + return "/"; +} + +std::pair<std::string, bool> GetEnvironmentVar(const char* variableName) { + char* value = getenv(variableName); + return value == nullptr ? std::make_pair(std::string(), false) + : std::make_pair(std::string(value), true); +} + +bool SetEnvironmentVar(const char* variableName, const char* value) { + if (value == nullptr) { + return unsetenv(variableName) == 0; + } + return setenv(variableName, value, 1) == 0; +} +#else +# error "Implement Get/SetEnvironmentVar for your platform." +#endif + +#if defined(DAWN_PLATFORM_WINDOWS) +std::optional<std::string> GetHModulePath(HMODULE module) { + std::array<char, MAX_PATH> executableFileBuf; + DWORD executablePathLen = GetModuleFileNameA(nullptr, executableFileBuf.data(), + static_cast<DWORD>(executableFileBuf.size())); + if (executablePathLen == 0) { + return {}; + } + return executableFileBuf.data(); +} +std::optional<std::string> GetExecutablePath() { + return GetHModulePath(nullptr); +} +#elif defined(DAWN_PLATFORM_LINUX) +std::optional<std::string> GetExecutablePath() { + std::array<char, PATH_MAX> path; + ssize_t result = readlink("/proc/self/exe", path.data(), PATH_MAX - 1); + if (result < 0 || static_cast<size_t>(result) >= PATH_MAX - 1) { + return {}; + } + + path[result] = '\0'; + return path.data(); +} +#elif defined(DAWN_PLATFORM_MACOS) || defined(DAWN_PLATFORM_IOS) +std::optional<std::string> GetExecutablePath() { + uint32_t size = 0; + _NSGetExecutablePath(nullptr, &size); + + std::vector<char> buffer(size + 1); + if (_NSGetExecutablePath(buffer.data(), &size) != 0) { + return {}; + } + + buffer[size] = '\0'; + return buffer.data(); +} +#elif defined(DAWN_PLATFORM_FUCHSIA) +std::optional<std::string> GetExecutablePath() { + // TODO: Implement on Fuchsia + return {}; +} +#elif defined(DAWN_PLATFORM_EMSCRIPTEN) +std::optional<std::string> GetExecutablePath() { + return {}; +} +#else +# error "Implement GetExecutablePath for your platform." +#endif + +std::optional<std::string> GetExecutableDirectory() { + std::optional<std::string> exePath = GetExecutablePath(); + if (!exePath) { + return {}; + } + size_t lastPathSepLoc = exePath->find_last_of(GetPathSeparator()); + if (lastPathSepLoc == std::string::npos) { + return {}; + } + return exePath->substr(0, lastPathSepLoc + 1); +} + +#if defined(DAWN_PLATFORM_LINUX) || defined(DAWN_PLATFORM_MACOS) || defined(DAWN_PLATFORM_IOS) +std::optional<std::string> GetModulePath() { + static int placeholderSymbol = 0; + Dl_info dlInfo; + if (dladdr(&placeholderSymbol, &dlInfo) == 0) { + return {}; + } + + std::array<char, PATH_MAX> absolutePath; + if (realpath(dlInfo.dli_fname, absolutePath.data()) == NULL) { + return {}; + } + return absolutePath.data(); +} +#elif defined(DAWN_PLATFORM_WINDOWS) +std::optional<std::string> GetModulePath() { + static int placeholderSymbol = 0; + HMODULE module = nullptr; +// GetModuleHandleEx is unavailable on UWP +# if defined(DAWN_IS_WINUWP) + return {}; +# else + if (!GetModuleHandleExA( + GET_MODULE_HANDLE_EX_FLAG_FROM_ADDRESS | GET_MODULE_HANDLE_EX_FLAG_UNCHANGED_REFCOUNT, + reinterpret_cast<LPCSTR>(&placeholderSymbol), &module)) { + return {}; + } +# endif + return GetHModulePath(module); +} +#elif defined(DAWN_PLATFORM_FUCHSIA) +std::optional<std::string> GetModulePath() { + return {}; +} +#elif defined(DAWN_PLATFORM_EMSCRIPTEN) +std::optional<std::string> GetModulePath() { + return {}; +} +#else +# error "Implement GetModulePath for your platform." +#endif + +std::optional<std::string> GetModuleDirectory() { + std::optional<std::string> modPath = GetModulePath(); + if (!modPath) { + return {}; + } + size_t lastPathSepLoc = modPath->find_last_of(GetPathSeparator()); + if (lastPathSepLoc == std::string::npos) { + return {}; + } + return modPath->substr(0, lastPathSepLoc + 1); +} + +// ScopedEnvironmentVar + +ScopedEnvironmentVar::ScopedEnvironmentVar(const char* variableName, const char* value) + : mName(variableName), + mOriginalValue(GetEnvironmentVar(variableName)), + mIsSet(SetEnvironmentVar(variableName, value)) { +} + +ScopedEnvironmentVar::~ScopedEnvironmentVar() { + if (mIsSet) { + bool success = SetEnvironmentVar( + mName.c_str(), mOriginalValue.second ? mOriginalValue.first.c_str() : nullptr); + // If we set the environment variable in the constructor, we should never fail restoring it. + ASSERT(success); + } +} + +bool ScopedEnvironmentVar::Set(const char* variableName, const char* value) { + ASSERT(!mIsSet); + mName = variableName; + mOriginalValue = GetEnvironmentVar(variableName); + mIsSet = SetEnvironmentVar(variableName, value); + return mIsSet; +}
diff --git a/src/dawn/common/SystemUtils.h b/src/dawn/common/SystemUtils.h new file mode 100644 index 0000000..bb59966 --- /dev/null +++ b/src/dawn/common/SystemUtils.h
@@ -0,0 +1,57 @@ +// Copyright 2019 The Dawn Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#ifndef COMMON_SYSTEMUTILS_H_ +#define COMMON_SYSTEMUTILS_H_ + +#include "dawn/common/Platform.h" + +#include <optional> +#include <string> + +const char* GetPathSeparator(); +// Returns a pair of the environment variable's value, and a boolean indicating whether the variable +// was present. +std::pair<std::string, bool> GetEnvironmentVar(const char* variableName); +bool SetEnvironmentVar(const char* variableName, const char* value); +// Directories are always returned with a trailing path separator. +// May return std::nullopt if the path is too long, there is no current +// module (statically linked into executable), or the function is not +// implemented on the platform. +std::optional<std::string> GetExecutableDirectory(); +std::optional<std::string> GetModuleDirectory(); + +#ifdef DAWN_PLATFORM_MACOS +void GetMacOSVersion(int32_t* majorVersion, int32_t* minorVersion = nullptr); +bool IsMacOSVersionAtLeast(uint32_t majorVersion, uint32_t minorVersion = 0); +#endif + +class ScopedEnvironmentVar { + public: + ScopedEnvironmentVar() = default; + ScopedEnvironmentVar(const char* variableName, const char* value); + ~ScopedEnvironmentVar(); + + ScopedEnvironmentVar(const ScopedEnvironmentVar& rhs) = delete; + ScopedEnvironmentVar& operator=(const ScopedEnvironmentVar& rhs) = delete; + + bool Set(const char* variableName, const char* value); + + private: + std::string mName; + std::pair<std::string, bool> mOriginalValue; + bool mIsSet = false; +}; + +#endif // COMMON_SYSTEMUTILS_H_
diff --git a/src/dawn/common/SystemUtils_mac.mm b/src/dawn/common/SystemUtils_mac.mm new file mode 100644 index 0000000..b706c20 --- /dev/null +++ b/src/dawn/common/SystemUtils_mac.mm
@@ -0,0 +1,33 @@ +// Copyright 2021 The Dawn Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include "dawn/common/SystemUtils.h" + +#include "dawn/common/Assert.h" + +#import <Foundation/NSProcessInfo.h> + +void GetMacOSVersion(int32_t* majorVersion, int32_t* minorVersion) { + NSOperatingSystemVersion version = [[NSProcessInfo processInfo] operatingSystemVersion]; + ASSERT(majorVersion != nullptr); + *majorVersion = version.majorVersion; + if (minorVersion != nullptr) { + *minorVersion = version.minorVersion; + } +} + +bool IsMacOSVersionAtLeast(uint32_t majorVersion, uint32_t minorVersion) { + return + [NSProcessInfo.processInfo isOperatingSystemAtLeastVersion:{majorVersion, minorVersion, 0}]; +}
diff --git a/src/dawn/common/TypeTraits.h b/src/dawn/common/TypeTraits.h new file mode 100644 index 0000000..3348b89 --- /dev/null +++ b/src/dawn/common/TypeTraits.h
@@ -0,0 +1,34 @@ +// Copyright 2020 The Dawn Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#ifndef COMMON_TYPETRAITS_H_ +#define COMMON_TYPETRAITS_H_ + +#include <type_traits> + +template <typename LHS, typename RHS = LHS, typename T = void> +struct HasEqualityOperator { + static constexpr const bool value = false; +}; + +template <typename LHS, typename RHS> +struct HasEqualityOperator< + LHS, + RHS, + std::enable_if_t< + std::is_same<decltype(std::declval<LHS>() == std::declval<RHS>()), bool>::value>> { + static constexpr const bool value = true; +}; + +#endif // COMMON_TYPE_TRAITS_H_
diff --git a/src/dawn/common/TypedInteger.h b/src/dawn/common/TypedInteger.h new file mode 100644 index 0000000..6669d14 --- /dev/null +++ b/src/dawn/common/TypedInteger.h
@@ -0,0 +1,262 @@ +// Copyright 2020 The Dawn Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#ifndef COMMON_TYPEDINTEGER_H_ +#define COMMON_TYPEDINTEGER_H_ + +#include "dawn/common/Assert.h" +#include "dawn/common/UnderlyingType.h" + +#include <limits> +#include <type_traits> + +// TypedInteger is helper class that provides additional type safety in Debug. +// - Integers of different (Tag, BaseIntegerType) may not be used interoperably +// - Allows casts only to the underlying type. +// - Integers of the same (Tag, BaseIntegerType) may be compared or assigned. +// This class helps ensure that the many types of indices in Dawn aren't mixed up and used +// interchangably. +// In Release builds, when DAWN_ENABLE_ASSERTS is not defined, TypedInteger is a passthrough +// typedef of the underlying type. +// +// Example: +// using UintA = TypedInteger<struct TypeA, uint32_t>; +// using UintB = TypedInteger<struct TypeB, uint32_t>; +// +// in Release: +// using UintA = uint32_t; +// using UintB = uint32_t; +// +// in Debug: +// using UintA = detail::TypedIntegerImpl<struct TypeA, uint32_t>; +// using UintB = detail::TypedIntegerImpl<struct TypeB, uint32_t>; +// +// Assignment, construction, comparison, and arithmetic with TypedIntegerImpl are allowed +// only for typed integers of exactly the same type. Further, they must be +// created / cast explicitly; there is no implicit conversion. +// +// UintA a(2); +// uint32_t aValue = static_cast<uint32_t>(a); +// +namespace detail { + template <typename Tag, typename T> + class TypedIntegerImpl; +} // namespace detail + +template <typename Tag, typename T, typename = std::enable_if_t<std::is_integral<T>::value>> +#if defined(DAWN_ENABLE_ASSERTS) +using TypedInteger = detail::TypedIntegerImpl<Tag, T>; +#else +using TypedInteger = T; +#endif + +namespace detail { + template <typename Tag, typename T> + class alignas(T) TypedIntegerImpl { + static_assert(std::is_integral<T>::value, "TypedInteger must be integral"); + T mValue; + + public: + constexpr TypedIntegerImpl() : mValue(0) { + static_assert(alignof(TypedIntegerImpl) == alignof(T)); + static_assert(sizeof(TypedIntegerImpl) == sizeof(T)); + } + + // Construction from non-narrowing integral types. + template <typename I, + typename = std::enable_if_t< + std::is_integral<I>::value && + std::numeric_limits<I>::max() <= std::numeric_limits<T>::max() && + std::numeric_limits<I>::min() >= std::numeric_limits<T>::min()>> + explicit constexpr TypedIntegerImpl(I rhs) : mValue(static_cast<T>(rhs)) { + } + + // Allow explicit casts only to the underlying type. If you're casting out of an + // TypedInteger, you should know what what you're doing, and exactly what type you + // expect. + explicit constexpr operator T() const { + return static_cast<T>(this->mValue); + } + +// Same-tag TypedInteger comparison operators +#define TYPED_COMPARISON(op) \ + constexpr bool operator op(const TypedIntegerImpl& rhs) const { \ + return mValue op rhs.mValue; \ + } + TYPED_COMPARISON(<) + TYPED_COMPARISON(<=) + TYPED_COMPARISON(>) + TYPED_COMPARISON(>=) + TYPED_COMPARISON(==) + TYPED_COMPARISON(!=) +#undef TYPED_COMPARISON + + // Increment / decrement operators for for-loop iteration + constexpr TypedIntegerImpl& operator++() { + ASSERT(this->mValue < std::numeric_limits<T>::max()); + ++this->mValue; + return *this; + } + + constexpr TypedIntegerImpl operator++(int) { + TypedIntegerImpl ret = *this; + + ASSERT(this->mValue < std::numeric_limits<T>::max()); + ++this->mValue; + return ret; + } + + constexpr TypedIntegerImpl& operator--() { + assert(this->mValue > std::numeric_limits<T>::min()); + --this->mValue; + return *this; + } + + constexpr TypedIntegerImpl operator--(int) { + TypedIntegerImpl ret = *this; + + ASSERT(this->mValue > std::numeric_limits<T>::min()); + --this->mValue; + return ret; + } + + template <typename T2 = T> + static constexpr std::enable_if_t<std::is_unsigned<T2>::value, decltype(T(0) + T2(0))> + AddImpl(TypedIntegerImpl<Tag, T> lhs, TypedIntegerImpl<Tag, T2> rhs) { + static_assert(std::is_same<T, T2>::value); + + // Overflow would wrap around + ASSERT(lhs.mValue + rhs.mValue >= lhs.mValue); + return lhs.mValue + rhs.mValue; + } + + template <typename T2 = T> + static constexpr std::enable_if_t<std::is_signed<T2>::value, decltype(T(0) + T2(0))> + AddImpl(TypedIntegerImpl<Tag, T> lhs, TypedIntegerImpl<Tag, T2> rhs) { + static_assert(std::is_same<T, T2>::value); + + if (lhs.mValue > 0) { + // rhs is positive: |rhs| is at most the distance between max and |lhs|. + // rhs is negative: (positive + negative) won't overflow + ASSERT(rhs.mValue <= std::numeric_limits<T>::max() - lhs.mValue); + } else { + // rhs is postive: (negative + positive) won't underflow + // rhs is negative: |rhs| isn't less than the (negative) distance between min + // and |lhs| + ASSERT(rhs.mValue >= std::numeric_limits<T>::min() - lhs.mValue); + } + return lhs.mValue + rhs.mValue; + } + + template <typename T2 = T> + static constexpr std::enable_if_t<std::is_unsigned<T>::value, decltype(T(0) - T2(0))> + SubImpl(TypedIntegerImpl<Tag, T> lhs, TypedIntegerImpl<Tag, T2> rhs) { + static_assert(std::is_same<T, T2>::value); + + // Overflow would wrap around + ASSERT(lhs.mValue - rhs.mValue <= lhs.mValue); + return lhs.mValue - rhs.mValue; + } + + template <typename T2 = T> + static constexpr std::enable_if_t<std::is_signed<T>::value, decltype(T(0) - T2(0))> SubImpl( + TypedIntegerImpl<Tag, T> lhs, + TypedIntegerImpl<Tag, T2> rhs) { + static_assert(std::is_same<T, T2>::value); + + if (lhs.mValue > 0) { + // rhs is positive: positive minus positive won't overflow + // rhs is negative: |rhs| isn't less than the (negative) distance between |lhs| + // and max. + ASSERT(rhs.mValue >= lhs.mValue - std::numeric_limits<T>::max()); + } else { + // rhs is positive: |rhs| is at most the distance between min and |lhs| + // rhs is negative: negative minus negative won't overflow + ASSERT(rhs.mValue <= lhs.mValue - std::numeric_limits<T>::min()); + } + return lhs.mValue - rhs.mValue; + } + + template <typename T2 = T> + constexpr std::enable_if_t<std::is_signed<T2>::value, TypedIntegerImpl> operator-() const { + static_assert(std::is_same<T, T2>::value); + // The negation of the most negative value cannot be represented. + ASSERT(this->mValue != std::numeric_limits<T>::min()); + return TypedIntegerImpl(-this->mValue); + } + + constexpr TypedIntegerImpl operator+(TypedIntegerImpl rhs) const { + auto result = AddImpl(*this, rhs); + static_assert(std::is_same<T, decltype(result)>::value, "Use ityp::Add instead."); + return TypedIntegerImpl(result); + } + + constexpr TypedIntegerImpl operator-(TypedIntegerImpl rhs) const { + auto result = SubImpl(*this, rhs); + static_assert(std::is_same<T, decltype(result)>::value, "Use ityp::Sub instead."); + return TypedIntegerImpl(result); + } + }; + +} // namespace detail + +namespace std { + + template <typename Tag, typename T> + class numeric_limits<detail::TypedIntegerImpl<Tag, T>> : public numeric_limits<T> { + public: + static detail::TypedIntegerImpl<Tag, T> max() noexcept { + return detail::TypedIntegerImpl<Tag, T>(std::numeric_limits<T>::max()); + } + static detail::TypedIntegerImpl<Tag, T> min() noexcept { + return detail::TypedIntegerImpl<Tag, T>(std::numeric_limits<T>::min()); + } + }; + +} // namespace std + +namespace ityp { + + // These helpers below are provided since the default arithmetic operators for small integer + // types like uint8_t and uint16_t return integers, not their same type. To avoid lots of + // casting or conditional code between Release/Debug. Callsites should use ityp::Add(a, b) and + // ityp::Sub(a, b) instead. + + template <typename Tag, typename T> + constexpr ::detail::TypedIntegerImpl<Tag, T> Add(::detail::TypedIntegerImpl<Tag, T> lhs, + ::detail::TypedIntegerImpl<Tag, T> rhs) { + return ::detail::TypedIntegerImpl<Tag, T>( + static_cast<T>(::detail::TypedIntegerImpl<Tag, T>::AddImpl(lhs, rhs))); + } + + template <typename Tag, typename T> + constexpr ::detail::TypedIntegerImpl<Tag, T> Sub(::detail::TypedIntegerImpl<Tag, T> lhs, + ::detail::TypedIntegerImpl<Tag, T> rhs) { + return ::detail::TypedIntegerImpl<Tag, T>( + static_cast<T>(::detail::TypedIntegerImpl<Tag, T>::SubImpl(lhs, rhs))); + } + + template <typename T> + constexpr std::enable_if_t<std::is_integral<T>::value, T> Add(T lhs, T rhs) { + return static_cast<T>(lhs + rhs); + } + + template <typename T> + constexpr std::enable_if_t<std::is_integral<T>::value, T> Sub(T lhs, T rhs) { + return static_cast<T>(lhs - rhs); + } + +} // namespace ityp + +#endif // COMMON_TYPEDINTEGER_H_
diff --git a/src/dawn/common/UnderlyingType.h b/src/dawn/common/UnderlyingType.h new file mode 100644 index 0000000..09c72c0 --- /dev/null +++ b/src/dawn/common/UnderlyingType.h
@@ -0,0 +1,51 @@ +// Copyright 2020 The Dawn Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#ifndef COMMON_UNDERLYINGTYPE_H_ +#define COMMON_UNDERLYINGTYPE_H_ + +#include <type_traits> + +// UnderlyingType is similar to std::underlying_type_t. It is a passthrough for already +// integer types which simplifies getting the underlying primitive type for an arbitrary +// template parameter. It includes a specialization for detail::TypedIntegerImpl which yields +// the wrapped integer type. +namespace detail { + template <typename T, typename Enable = void> + struct UnderlyingTypeImpl; + + template <typename I> + struct UnderlyingTypeImpl<I, typename std::enable_if_t<std::is_integral<I>::value>> { + using type = I; + }; + + template <typename E> + struct UnderlyingTypeImpl<E, typename std::enable_if_t<std::is_enum<E>::value>> { + using type = std::underlying_type_t<E>; + }; + + // Forward declare the TypedInteger impl. + template <typename Tag, typename T> + class TypedIntegerImpl; + + template <typename Tag, typename I> + struct UnderlyingTypeImpl<TypedIntegerImpl<Tag, I>> { + using type = typename UnderlyingTypeImpl<I>::type; + }; +} // namespace detail + +template <typename T> +using UnderlyingType = typename detail::UnderlyingTypeImpl<T>::type; + +#endif // COMMON_UNDERLYINGTYPE_H_
diff --git a/src/dawn/common/WindowsUtils.cpp b/src/dawn/common/WindowsUtils.cpp new file mode 100644 index 0000000..fd924f4 --- /dev/null +++ b/src/dawn/common/WindowsUtils.cpp
@@ -0,0 +1,43 @@ +// Copyright 2021 The Dawn Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include "dawn/common/WindowsUtils.h" + +#include "dawn/common/windows_with_undefs.h" + +#include <memory> + +std::string WCharToUTF8(const wchar_t* input) { + // The -1 argument asks WideCharToMultiByte to use the null terminator to know the size of + // input. It will return a size that includes the null terminator. + int requiredSize = WideCharToMultiByte(CP_UTF8, 0, input, -1, nullptr, 0, nullptr, nullptr); + + std::string result; + result.resize(requiredSize - 1); + WideCharToMultiByte(CP_UTF8, 0, input, -1, result.data(), requiredSize, nullptr, nullptr); + + return result; +} + +std::wstring UTF8ToWStr(const char* input) { + // The -1 argument asks MultiByteToWideChar to use the null terminator to know the size of + // input. It will return a size that includes the null terminator. + int requiredSize = MultiByteToWideChar(CP_UTF8, 0, input, -1, nullptr, 0); + + std::wstring result; + result.resize(requiredSize - 1); + MultiByteToWideChar(CP_UTF8, 0, input, -1, result.data(), requiredSize); + + return result; +}
diff --git a/src/dawn/common/WindowsUtils.h b/src/dawn/common/WindowsUtils.h new file mode 100644 index 0000000..3ab916b --- /dev/null +++ b/src/dawn/common/WindowsUtils.h
@@ -0,0 +1,24 @@ +// Copyright 2021 The Dawn Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#ifndef COMMON_WINDOWSUTILS_H_ +#define COMMON_WINDOWSUTILS_H_ + +#include <string> + +std::string WCharToUTF8(const wchar_t* input); + +std::wstring UTF8ToWStr(const char* input); + +#endif // COMMON_WINDOWSUTILS_H_
diff --git a/src/dawn/common/ityp_array.h b/src/dawn/common/ityp_array.h new file mode 100644 index 0000000..c7db71a --- /dev/null +++ b/src/dawn/common/ityp_array.h
@@ -0,0 +1,98 @@ +// Copyright 2020 The Dawn Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#ifndef COMMON_ITYP_ARRAY_H_ +#define COMMON_ITYP_ARRAY_H_ + +#include "dawn/common/TypedInteger.h" +#include "dawn/common/UnderlyingType.h" + +#include <array> +#include <cstddef> +#include <type_traits> + +namespace ityp { + + // ityp::array is a helper class that wraps std::array with the restriction that + // indices must be a particular type |Index|. Dawn uses multiple flat maps of + // index-->data, and this class helps ensure an indices cannot be passed interchangably + // to a flat map of a different type. + template <typename Index, typename Value, size_t Size> + class array : private std::array<Value, Size> { + using I = UnderlyingType<Index>; + using Base = std::array<Value, Size>; + + static_assert(Size <= std::numeric_limits<I>::max()); + + public: + constexpr array() = default; + + template <typename... Values> + constexpr array(Values&&... values) : Base{std::forward<Values>(values)...} { + } + + Value& operator[](Index i) { + I index = static_cast<I>(i); + ASSERT(index >= 0 && index < I(Size)); + return Base::operator[](index); + } + + constexpr const Value& operator[](Index i) const { + I index = static_cast<I>(i); + ASSERT(index >= 0 && index < I(Size)); + return Base::operator[](index); + } + + Value& at(Index i) { + I index = static_cast<I>(i); + ASSERT(index >= 0 && index < I(Size)); + return Base::at(index); + } + + constexpr const Value& at(Index i) const { + I index = static_cast<I>(i); + ASSERT(index >= 0 && index < I(Size)); + return Base::at(index); + } + + typename Base::iterator begin() noexcept { + return Base::begin(); + } + + typename Base::const_iterator begin() const noexcept { + return Base::begin(); + } + + typename Base::iterator end() noexcept { + return Base::end(); + } + + typename Base::const_iterator end() const noexcept { + return Base::end(); + } + + constexpr Index size() const { + return Index(I(Size)); + } + + using Base::back; + using Base::data; + using Base::empty; + using Base::fill; + using Base::front; + }; + +} // namespace ityp + +#endif // COMMON_ITYP_ARRAY_H_
diff --git a/src/dawn/common/ityp_bitset.h b/src/dawn/common/ityp_bitset.h new file mode 100644 index 0000000..9c27cfe --- /dev/null +++ b/src/dawn/common/ityp_bitset.h
@@ -0,0 +1,188 @@ +// Copyright 2020 The Dawn Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#ifndef COMMON_ITYP_BITSET_H_ +#define COMMON_ITYP_BITSET_H_ + +#include "dawn/common/BitSetIterator.h" +#include "dawn/common/TypedInteger.h" +#include "dawn/common/UnderlyingType.h" + +namespace ityp { + + // ityp::bitset is a helper class that wraps std::bitset with the restriction that + // indices must be a particular type |Index|. + template <typename Index, size_t N> + class bitset : private std::bitset<N> { + using I = UnderlyingType<Index>; + using Base = std::bitset<N>; + + static_assert(sizeof(I) <= sizeof(size_t)); + + constexpr bitset(const Base& rhs) : Base(rhs) { + } + + public: + using reference = typename Base::reference; + + constexpr bitset() noexcept : Base() { + } + + constexpr bitset(unsigned long long value) noexcept : Base(value) { + } + + constexpr bool operator[](Index i) const { + return Base::operator[](static_cast<I>(i)); + } + + typename Base::reference operator[](Index i) { + return Base::operator[](static_cast<I>(i)); + } + + bool test(Index i) const { + return Base::test(static_cast<I>(i)); + } + + using Base::all; + using Base::any; + using Base::count; + using Base::none; + using Base::size; + + bool operator==(const bitset& other) const noexcept { + return Base::operator==(static_cast<const Base&>(other)); + } + + bool operator!=(const bitset& other) const noexcept { + return Base::operator!=(static_cast<const Base&>(other)); + } + + bitset& operator&=(const bitset& other) noexcept { + return static_cast<bitset&>(Base::operator&=(static_cast<const Base&>(other))); + } + + bitset& operator|=(const bitset& other) noexcept { + return static_cast<bitset&>(Base::operator|=(static_cast<const Base&>(other))); + } + + bitset& operator^=(const bitset& other) noexcept { + return static_cast<bitset&>(Base::operator^=(static_cast<const Base&>(other))); + } + + bitset operator~() const noexcept { + return bitset(*this).flip(); + } + + bitset& set() noexcept { + return static_cast<bitset&>(Base::set()); + } + + bitset& set(Index i, bool value = true) { + return static_cast<bitset&>(Base::set(static_cast<I>(i), value)); + } + + bitset& reset() noexcept { + return static_cast<bitset&>(Base::reset()); + } + + bitset& reset(Index i) { + return static_cast<bitset&>(Base::reset(static_cast<I>(i))); + } + + bitset& flip() noexcept { + return static_cast<bitset&>(Base::flip()); + } + + bitset& flip(Index i) { + return static_cast<bitset&>(Base::flip(static_cast<I>(i))); + } + + using Base::to_string; + using Base::to_ullong; + using Base::to_ulong; + + friend bitset operator&(const bitset& lhs, const bitset& rhs) noexcept { + return bitset(static_cast<const Base&>(lhs) & static_cast<const Base&>(rhs)); + } + + friend bitset operator|(const bitset& lhs, const bitset& rhs) noexcept { + return bitset(static_cast<const Base&>(lhs) | static_cast<const Base&>(rhs)); + } + + friend bitset operator^(const bitset& lhs, const bitset& rhs) noexcept { + return bitset(static_cast<const Base&>(lhs) ^ static_cast<const Base&>(rhs)); + } + + friend BitSetIterator<N, Index> IterateBitSet(const bitset& bitset) { + return BitSetIterator<N, Index>(static_cast<const Base&>(bitset)); + } + + friend struct std::hash<bitset>; + }; + +} // namespace ityp + +// Assume we have bitset of at most 64 bits +// Returns i which is the next integer of the index of the highest bit +// i == 0 if there is no bit set to true +// i == 1 if only the least significant bit (at index 0) is the bit set to true with the +// highest index +// ... +// i == 64 if the most significant bit (at index 64) is the bit set to true with the highest +// index +template <typename Index, size_t N> +Index GetHighestBitIndexPlusOne(const ityp::bitset<Index, N>& bitset) { + using I = UnderlyingType<Index>; +#if defined(DAWN_COMPILER_MSVC) + if constexpr (N > 32) { +# if defined(DAWN_PLATFORM_64_BIT) + unsigned long firstBitIndex = 0ul; + unsigned char ret = _BitScanReverse64(&firstBitIndex, bitset.to_ullong()); + if (ret == 0) { + return Index(static_cast<I>(0)); + } + return Index(static_cast<I>(firstBitIndex + 1)); +# else // defined(DAWN_PLATFORM_64_BIT) + if (bitset.none()) { + return Index(static_cast<I>(0)); + } + for (size_t i = 0u; i < N; i++) { + if (bitset.test(Index(static_cast<I>(N - 1 - i)))) { + return Index(static_cast<I>(N - i)); + } + } + UNREACHABLE(); +# endif // defined(DAWN_PLATFORM_64_BIT) + } else { + unsigned long firstBitIndex = 0ul; + unsigned char ret = _BitScanReverse(&firstBitIndex, bitset.to_ulong()); + if (ret == 0) { + return Index(static_cast<I>(0)); + } + return Index(static_cast<I>(firstBitIndex + 1)); + } +#else // defined(DAWN_COMPILER_MSVC) + if (bitset.none()) { + return Index(static_cast<I>(0)); + } + if constexpr (N > 32) { + return Index( + static_cast<I>(64 - static_cast<uint32_t>(__builtin_clzll(bitset.to_ullong())))); + } else { + return Index(static_cast<I>(32 - static_cast<uint32_t>(__builtin_clz(bitset.to_ulong())))); + } +#endif // defined(DAWN_COMPILER_MSVC) +} + +#endif // COMMON_ITYP_BITSET_H_
diff --git a/src/dawn/common/ityp_span.h b/src/dawn/common/ityp_span.h new file mode 100644 index 0000000..c73f983 --- /dev/null +++ b/src/dawn/common/ityp_span.h
@@ -0,0 +1,103 @@ +// Copyright 2020 The Dawn Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#ifndef COMMON_ITYP_SPAN_H_ +#define COMMON_ITYP_SPAN_H_ + +#include "dawn/common/TypedInteger.h" +#include "dawn/common/UnderlyingType.h" + +#include <type_traits> + +namespace ityp { + + // ityp::span is a helper class that wraps an unowned packed array of type |Value|. + // It stores the size and pointer to first element. It has the restriction that + // indices must be a particular type |Index|. This provides a type-safe way to index + // raw pointers. + template <typename Index, typename Value> + class span { + using I = UnderlyingType<Index>; + + public: + constexpr span() : mData(nullptr), mSize(0) { + } + constexpr span(Value* data, Index size) : mData(data), mSize(size) { + } + + constexpr Value& operator[](Index i) const { + ASSERT(i < mSize); + return mData[static_cast<I>(i)]; + } + + Value* data() noexcept { + return mData; + } + + const Value* data() const noexcept { + return mData; + } + + Value* begin() noexcept { + return mData; + } + + const Value* begin() const noexcept { + return mData; + } + + Value* end() noexcept { + return mData + static_cast<I>(mSize); + } + + const Value* end() const noexcept { + return mData + static_cast<I>(mSize); + } + + Value& front() { + ASSERT(mData != nullptr); + ASSERT(static_cast<I>(mSize) >= 0); + return *mData; + } + + const Value& front() const { + ASSERT(mData != nullptr); + ASSERT(static_cast<I>(mSize) >= 0); + return *mData; + } + + Value& back() { + ASSERT(mData != nullptr); + ASSERT(static_cast<I>(mSize) >= 0); + return *(mData + static_cast<I>(mSize) - 1); + } + + const Value& back() const { + ASSERT(mData != nullptr); + ASSERT(static_cast<I>(mSize) >= 0); + return *(mData + static_cast<I>(mSize) - 1); + } + + Index size() const { + return mSize; + } + + private: + Value* mData; + Index mSize; + }; + +} // namespace ityp + +#endif // COMMON_ITYP_SPAN_H_
diff --git a/src/dawn/common/ityp_stack_vec.h b/src/dawn/common/ityp_stack_vec.h new file mode 100644 index 0000000..47c437e --- /dev/null +++ b/src/dawn/common/ityp_stack_vec.h
@@ -0,0 +1,103 @@ +// Copyright 2020 The Dawn Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#ifndef COMMON_ITYP_STACK_VEC_H_ +#define COMMON_ITYP_STACK_VEC_H_ + +#include "dawn/common/Assert.h" +#include "dawn/common/StackContainer.h" +#include "dawn/common/UnderlyingType.h" + +namespace ityp { + + template <typename Index, typename Value, size_t StaticCapacity> + class stack_vec : private StackVector<Value, StaticCapacity> { + using I = UnderlyingType<Index>; + using Base = StackVector<Value, StaticCapacity>; + using VectorBase = std::vector<Value, StackAllocator<Value, StaticCapacity>>; + static_assert(StaticCapacity <= std::numeric_limits<I>::max()); + + public: + stack_vec() : Base() { + } + stack_vec(Index size) : Base() { + this->container().resize(static_cast<I>(size)); + } + + Value& operator[](Index i) { + ASSERT(i < size()); + return Base::operator[](static_cast<I>(i)); + } + + constexpr const Value& operator[](Index i) const { + ASSERT(i < size()); + return Base::operator[](static_cast<I>(i)); + } + + void resize(Index size) { + this->container().resize(static_cast<I>(size)); + } + + void reserve(Index size) { + this->container().reserve(static_cast<I>(size)); + } + + Value* data() { + return this->container().data(); + } + + const Value* data() const { + return this->container().data(); + } + + typename VectorBase::iterator begin() noexcept { + return this->container().begin(); + } + + typename VectorBase::const_iterator begin() const noexcept { + return this->container().begin(); + } + + typename VectorBase::iterator end() noexcept { + return this->container().end(); + } + + typename VectorBase::const_iterator end() const noexcept { + return this->container().end(); + } + + typename VectorBase::reference front() { + return this->container().front(); + } + + typename VectorBase::const_reference front() const { + return this->container().front(); + } + + typename VectorBase::reference back() { + return this->container().back(); + } + + typename VectorBase::const_reference back() const { + return this->container().back(); + } + + Index size() const { + return Index(static_cast<I>(this->container().size())); + } + }; + +} // namespace ityp + +#endif // COMMON_ITYP_STACK_VEC_H_
diff --git a/src/dawn/common/ityp_vector.h b/src/dawn/common/ityp_vector.h new file mode 100644 index 0000000..9d83adf --- /dev/null +++ b/src/dawn/common/ityp_vector.h
@@ -0,0 +1,108 @@ +// Copyright 2020 The Dawn Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#ifndef COMMON_ITYP_VECTOR_H_ +#define COMMON_ITYP_VECTOR_H_ + +#include "dawn/common/TypedInteger.h" +#include "dawn/common/UnderlyingType.h" + +#include <type_traits> +#include <vector> + +namespace ityp { + + // ityp::vector is a helper class that wraps std::vector with the restriction that + // indices must be a particular type |Index|. + template <typename Index, typename Value> + class vector : public std::vector<Value> { + using I = UnderlyingType<Index>; + using Base = std::vector<Value>; + + private: + // Disallow access to base constructors and untyped index/size-related operators. + using Base::Base; + using Base::operator=; + using Base::operator[]; + using Base::at; + using Base::reserve; + using Base::resize; + using Base::size; + + public: + vector() : Base() { + } + + explicit vector(Index size) : Base(static_cast<I>(size)) { + } + + vector(Index size, const Value& init) : Base(static_cast<I>(size), init) { + } + + vector(const vector& rhs) : Base(static_cast<const Base&>(rhs)) { + } + + vector(vector&& rhs) : Base(static_cast<Base&&>(rhs)) { + } + + vector(std::initializer_list<Value> init) : Base(init) { + } + + vector& operator=(const vector& rhs) { + Base::operator=(static_cast<const Base&>(rhs)); + return *this; + } + + vector& operator=(vector&& rhs) noexcept { + Base::operator=(static_cast<Base&&>(rhs)); + return *this; + } + + Value& operator[](Index i) { + ASSERT(i >= Index(0) && i < size()); + return Base::operator[](static_cast<I>(i)); + } + + constexpr const Value& operator[](Index i) const { + ASSERT(i >= Index(0) && i < size()); + return Base::operator[](static_cast<I>(i)); + } + + Value& at(Index i) { + ASSERT(i >= Index(0) && i < size()); + return Base::at(static_cast<I>(i)); + } + + constexpr const Value& at(Index i) const { + ASSERT(i >= Index(0) && i < size()); + return Base::at(static_cast<I>(i)); + } + + constexpr Index size() const { + ASSERT(std::numeric_limits<I>::max() >= Base::size()); + return Index(static_cast<I>(Base::size())); + } + + void resize(Index size) { + Base::resize(static_cast<I>(size)); + } + + void reserve(Index size) { + Base::reserve(static_cast<I>(size)); + } + }; + +} // namespace ityp + +#endif // COMMON_ITYP_VECTOR_H_
diff --git a/src/dawn/common/vulkan_platform.h b/src/dawn/common/vulkan_platform.h new file mode 100644 index 0000000..620034f --- /dev/null +++ b/src/dawn/common/vulkan_platform.h
@@ -0,0 +1,206 @@ +// Copyright 2017 The Dawn Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#ifndef COMMON_VULKANPLATFORM_H_ +#define COMMON_VULKANPLATFORM_H_ + +#if !defined(DAWN_ENABLE_BACKEND_VULKAN) +# error "vulkan_platform.h included without the Vulkan backend enabled" +#endif +#if defined(VULKAN_CORE_H_) +# error "vulkan.h included before vulkan_platform.h" +#endif + +#include "dawn/common/Platform.h" + +#include <cstddef> +#include <cstdint> + +// vulkan.h defines non-dispatchable handles to opaque pointers on 64bit architectures and uint64_t +// on 32bit architectures. This causes a problem in 32bit where the handles cannot be used to +// distinguish between overloads of the same function. +// Change the definition of non-dispatchable handles to be opaque structures containing a uint64_t +// and overload the comparison operators between themselves and VK_NULL_HANDLE (which will be +// redefined to be nullptr). This keeps the type-safety of having the handles be different types +// (like vulkan.h on 64 bit) but makes sure the types are different on 32 bit architectures. + +#if defined(DAWN_PLATFORM_64_BIT) +# define DAWN_DEFINE_NATIVE_NON_DISPATCHABLE_HANDLE(object) using object = struct object##_T*; +// This function is needed because MSVC doesn't accept reinterpret_cast from uint64_t from uint64_t +// TODO(cwallez@chromium.org): Remove this once we rework vulkan_platform.h +template <typename T> +T NativeNonDispatachableHandleFromU64(uint64_t u64) { + return reinterpret_cast<T>(u64); +} +#elif defined(DAWN_PLATFORM_32_BIT) +# define DAWN_DEFINE_NATIVE_NON_DISPATCHABLE_HANDLE(object) using object = uint64_t; +template <typename T> +T NativeNonDispatachableHandleFromU64(uint64_t u64) { + return u64; +} +#else +# error "Unsupported platform" +#endif + +// Define a dummy Vulkan handle for use before we include vulkan.h +DAWN_DEFINE_NATIVE_NON_DISPATCHABLE_HANDLE(VkSomeHandle) + +// Find out the alignment of native handles. Logically we would use alignof(VkSomeHandleNative) so +// why bother with the wrapper struct? It turns out that on Linux Intel x86 alignof(uint64_t) is 8 +// but alignof(struct{uint64_t a;}) is 4. This is because this Intel ABI doesn't say anything about +// double-word alignment so for historical reasons compilers violated the standard and use an +// alignment of 4 for uint64_t (and double) inside structures. +// See https://stackoverflow.com/questions/44877185 +// One way to get the alignment inside structures of a type is to look at the alignment of it +// wrapped in a structure. Hence VkSameHandleNativeWrappe + +namespace dawn::native::vulkan { + + namespace detail { + template <typename T> + struct WrapperStruct { + T member; + }; + + template <typename T> + static constexpr size_t AlignOfInStruct = alignof(WrapperStruct<T>); + + static constexpr size_t kNativeVkHandleAlignment = AlignOfInStruct<VkSomeHandle>; + static constexpr size_t kUint64Alignment = AlignOfInStruct<uint64_t>; + + // Simple handle types that supports "nullptr_t" as a 0 value. + template <typename Tag, typename HandleType> + class alignas(detail::kNativeVkHandleAlignment) VkHandle { + public: + // Default constructor and assigning of VK_NULL_HANDLE + VkHandle() = default; + VkHandle(std::nullptr_t) { + } + + // Use default copy constructor/assignment + VkHandle(const VkHandle<Tag, HandleType>& other) = default; + VkHandle& operator=(const VkHandle<Tag, HandleType>&) = default; + + // Comparisons between handles + bool operator==(VkHandle<Tag, HandleType> other) const { + return mHandle == other.mHandle; + } + bool operator!=(VkHandle<Tag, HandleType> other) const { + return mHandle != other.mHandle; + } + + // Comparisons between handles and VK_NULL_HANDLE + bool operator==(std::nullptr_t) const { + return mHandle == 0; + } + bool operator!=(std::nullptr_t) const { + return mHandle != 0; + } + + // Implicit conversion to real Vulkan types. + operator HandleType() const { + return GetHandle(); + } + + HandleType GetHandle() const { + return mHandle; + } + + HandleType& operator*() { + return mHandle; + } + + static VkHandle<Tag, HandleType> CreateFromHandle(HandleType handle) { + return VkHandle{handle}; + } + + private: + explicit VkHandle(HandleType handle) : mHandle(handle) { + } + + HandleType mHandle = 0; + }; + } // namespace detail + + static constexpr std::nullptr_t VK_NULL_HANDLE = nullptr; + + template <typename Tag, typename HandleType> + HandleType* AsVkArray(detail::VkHandle<Tag, HandleType>* handle) { + return reinterpret_cast<HandleType*>(handle); + } + +} // namespace dawn::native::vulkan + +#define VK_DEFINE_NON_DISPATCHABLE_HANDLE(object) \ + DAWN_DEFINE_NATIVE_NON_DISPATCHABLE_HANDLE(object) \ + namespace dawn::native::vulkan { \ + using object = detail::VkHandle<struct VkTag##object, ::object>; \ + static_assert(sizeof(object) == sizeof(uint64_t)); \ + static_assert(alignof(object) == detail::kUint64Alignment); \ + static_assert(sizeof(object) == sizeof(::object)); \ + static_assert(alignof(object) == detail::kNativeVkHandleAlignment); \ + } // namespace dawn::native::vulkan + +// Import additional parts of Vulkan that are supported on our architecture and preemptively include +// headers that vulkan.h includes that we have "undefs" for. Note that some of the VK_USE_PLATFORM_* +// defines are defined already in the Vulkan-Header BUILD.gn, but are needed when building with +// CMake, hence they cannot be removed at the moment. +#if defined(DAWN_PLATFORM_WINDOWS) +# ifndef VK_USE_PLATFORM_WIN32_KHR +# define VK_USE_PLATFORM_WIN32_KHR +# endif +# include "dawn/common/windows_with_undefs.h" +#endif // DAWN_PLATFORM_WINDOWS + +#if defined(DAWN_USE_X11) +# define VK_USE_PLATFORM_XLIB_KHR +# ifndef VK_USE_PLATFORM_XCB_KHR +# define VK_USE_PLATFORM_XCB_KHR +# endif +# include "dawn/common/xlib_with_undefs.h" +#endif // defined(DAWN_USE_X11) + +#if defined(DAWN_ENABLE_BACKEND_METAL) +# ifndef VK_USE_PLATFORM_METAL_EXT +# define VK_USE_PLATFORM_METAL_EXT +# endif +#endif // defined(DAWN_ENABLE_BACKEND_METAL) + +#if defined(DAWN_PLATFORM_ANDROID) +# ifndef VK_USE_PLATFORM_ANDROID_KHR +# define VK_USE_PLATFORM_ANDROID_KHR +# endif +#endif // defined(DAWN_PLATFORM_ANDROID) + +#if defined(DAWN_PLATFORM_FUCHSIA) +# ifndef VK_USE_PLATFORM_FUCHSIA +# define VK_USE_PLATFORM_FUCHSIA +# endif +#endif // defined(DAWN_PLATFORM_FUCHSIA) + +// The actual inclusion of vulkan.h! +#define VK_NO_PROTOTYPES +#include <vulkan/vulkan.h> + +// Redefine VK_NULL_HANDLE for better type safety where possible. +#undef VK_NULL_HANDLE +#if defined(DAWN_PLATFORM_64_BIT) +static constexpr std::nullptr_t VK_NULL_HANDLE = nullptr; +#elif defined(DAWN_PLATFORM_32_BIT) +static constexpr uint64_t VK_NULL_HANDLE = 0; +#else +# error "Unsupported platform" +#endif + +#endif // COMMON_VULKANPLATFORM_H_
diff --git a/src/dawn/common/windows_with_undefs.h b/src/dawn/common/windows_with_undefs.h new file mode 100644 index 0000000..686da9f --- /dev/null +++ b/src/dawn/common/windows_with_undefs.h
@@ -0,0 +1,38 @@ +// Copyright 2018 The Dawn Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#ifndef COMMON_WINDOWS_WITH_UNDEFS_H_ +#define COMMON_WINDOWS_WITH_UNDEFS_H_ + +#include "dawn/common/Platform.h" + +#if !defined(DAWN_PLATFORM_WINDOWS) +# error "windows_with_undefs.h included on non-Windows" +#endif + +// This header includes <windows.h> but removes all the extra defines that conflict with identifiers +// in internal code. It should never be included in something that is part of the public interface. +#include <windows.h> + +// Macros defined for ANSI / Unicode support +#undef CreateWindow +#undef GetMessage + +// Macros defined to produce compiler intrinsics +#undef MemoryBarrier + +// Macro defined as an alias of GetTickCount +#undef GetCurrentTime + +#endif // COMMON_WINDOWS_WITH_UNDEFS_H_
diff --git a/src/dawn/common/xlib_with_undefs.h b/src/dawn/common/xlib_with_undefs.h new file mode 100644 index 0000000..7ac5a62 --- /dev/null +++ b/src/dawn/common/xlib_with_undefs.h
@@ -0,0 +1,40 @@ +// Copyright 2019 The Dawn Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#ifndef COMMON_XLIB_WITH_UNDEFS_H_ +#define COMMON_XLIB_WITH_UNDEFS_H_ + +#include "dawn/common/Platform.h" + +#if !defined(DAWN_PLATFORM_LINUX) +# error "xlib_with_undefs.h included on non-Linux" +#endif + +// This header includes <X11/Xlib.h> but removes all the extra defines that conflict with +// identifiers in internal code. It should never be included in something that is part of the public +// interface. +#include <X11/Xlib.h> + +// Xlib-xcb.h technically includes Xlib.h but we separate the includes to make it more clear what +// the problem is if one of these two includes fail. +#include <X11/Xlib-xcb.h> + +#undef Success +#undef None +#undef Always +#undef Bool + +using XErrorHandler = int (*)(Display*, XErrorEvent*); + +#endif // COMMON_XLIB_WITH_UNDEFS_H_
diff --git a/src/dawn/fuzzers/BUILD.gn b/src/dawn/fuzzers/BUILD.gn new file mode 100644 index 0000000..f7ea2a0 --- /dev/null +++ b/src/dawn/fuzzers/BUILD.gn
@@ -0,0 +1,124 @@ +# Copyright 2018 The Dawn Authors +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +import("//build_overrides/build.gni") +import("../../../scripts/dawn_overrides_with_defaults.gni") + +# We only have libfuzzer in Chromium builds but if we build fuzzer targets only +# there, we would risk breaking fuzzer targets all the time when making changes +# to Dawn. To avoid that, we make fuzzer targets compile in standalone builds +# as well with a dawn_fuzzer_test target that acts like Chromium's fuzzer_test. +# +# The standalone fuzzer targets are able to run a single fuzzer input which +# could help reproduce fuzzer crashes more easily because you don't need a +# whole Chromium checkout. + +if (build_with_chromium) { + import("//testing/libfuzzer/fuzzer_test.gni") + + # In Chromium build we just proxy everything to the real fuzzer_test + template("dawn_fuzzer_test") { + fuzzer_test(target_name) { + forward_variables_from(invoker, "*") + } + } +} else { + import("//testing/test.gni") + + # In standalone build we do something similar to fuzzer_test. + template("dawn_fuzzer_test") { + test(target_name) { + forward_variables_from(invoker, + [ + "asan_options", + "cflags", + "cflags_cc", + "check_includes", + "defines", + "deps", + "include_dirs", + "sources", + ]) + + if (defined(asan_options)) { + not_needed([ "asan_options" ]) + } + + if (!defined(configs)) { + configs = [] + } + + # Weirdly fuzzer_test uses a special variable for additional configs. + if (defined(invoker.additional_configs)) { + configs += invoker.additional_configs + } + + sources += [ "StandaloneFuzzerMain.cpp" ] + } + } +} + +static_library("dawn_wire_server_fuzzer_common") { + sources = [ + "DawnWireServerFuzzer.cpp", + "DawnWireServerFuzzer.h", + ] + public_deps = [ + "${dawn_root}/src/dawn:cpp", + "${dawn_root}/src/dawn:proc", + "${dawn_root}/src/dawn/common", + "${dawn_root}/src/dawn/native:static", + "${dawn_root}/src/dawn/utils", + "${dawn_root}/src/dawn/wire:static", + ] +} + +dawn_fuzzer_test("dawn_wire_server_and_frontend_fuzzer") { + sources = [ "DawnWireServerAndFrontendFuzzer.cpp" ] + + deps = [ ":dawn_wire_server_fuzzer_common" ] + + additional_configs = [ "${dawn_root}/src/dawn/common:internal_config" ] +} + +if (is_win) { + dawn_fuzzer_test("dawn_wire_server_and_d3d12_backend_fuzzer") { + sources = [ "DawnWireServerAndD3D12BackendFuzzer.cpp" ] + + deps = [ ":dawn_wire_server_fuzzer_common" ] + + additional_configs = [ "${dawn_root}/src/dawn/common:internal_config" ] + } +} + +dawn_fuzzer_test("dawn_wire_server_and_vulkan_backend_fuzzer") { + sources = [ "DawnWireServerAndVulkanBackendFuzzer.cpp" ] + + deps = [ ":dawn_wire_server_fuzzer_common" ] + + additional_configs = [ "${dawn_root}/src/dawn/common:internal_config" ] +} + +# A group target to build all the fuzzers +group("fuzzers") { + testonly = true + deps = [ + ":dawn_wire_server_and_frontend_fuzzer", + ":dawn_wire_server_and_vulkan_backend_fuzzer", + ] + + if (is_win) { + deps += [ ":dawn_wire_server_and_d3d12_backend_fuzzer" ] + } +}
diff --git a/src/dawn/fuzzers/DawnWireServerAndD3D12BackendFuzzer.cpp b/src/dawn/fuzzers/DawnWireServerAndD3D12BackendFuzzer.cpp new file mode 100644 index 0000000..2eff9b4 --- /dev/null +++ b/src/dawn/fuzzers/DawnWireServerAndD3D12BackendFuzzer.cpp
@@ -0,0 +1,44 @@ +// Copyright 2020 The Dawn Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include "DawnWireServerFuzzer.h" + +#include "dawn/native/DawnNative.h" +#include "testing/libfuzzer/libfuzzer_exports.h" + +extern "C" int LLVMFuzzerInitialize(int* argc, char*** argv) { + return DawnWireServerFuzzer::Initialize(argc, argv); +} + +extern "C" int LLVMFuzzerTestOneInput(const uint8_t* data, size_t size) { + return DawnWireServerFuzzer::Run( + data, size, + [](dawn::native::Instance* instance) { + std::vector<dawn::native::Adapter> adapters = instance->GetAdapters(); + + wgpu::Device device; + for (dawn::native::Adapter adapter : adapters) { + wgpu::AdapterProperties properties; + adapter.GetProperties(&properties); + + if (properties.backendType == wgpu::BackendType::D3D12 && + properties.adapterType == wgpu::AdapterType::CPU) { + device = wgpu::Device::Acquire(adapter.CreateDevice()); + break; + } + } + return device; + }, + true /* supportsErrorInjection */); +}
diff --git a/src/dawn/fuzzers/DawnWireServerAndFrontendFuzzer.cpp b/src/dawn/fuzzers/DawnWireServerAndFrontendFuzzer.cpp new file mode 100644 index 0000000..26e1cce --- /dev/null +++ b/src/dawn/fuzzers/DawnWireServerAndFrontendFuzzer.cpp
@@ -0,0 +1,46 @@ +// Copyright 2018 The Dawn Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include "DawnWireServerFuzzer.h" + +#include "dawn/common/Assert.h" +#include "dawn/native/DawnNative.h" +#include "testing/libfuzzer/libfuzzer_exports.h" + +extern "C" int LLVMFuzzerInitialize(int* argc, char*** argv) { + return DawnWireServerFuzzer::Initialize(argc, argv); +} + +extern "C" int LLVMFuzzerTestOneInput(const uint8_t* data, size_t size) { + return DawnWireServerFuzzer::Run( + data, size, + [](dawn::native::Instance* instance) { + std::vector<dawn::native::Adapter> adapters = instance->GetAdapters(); + + wgpu::Device nullDevice; + for (dawn::native::Adapter adapter : adapters) { + wgpu::AdapterProperties properties; + adapter.GetProperties(&properties); + + if (properties.backendType == wgpu::BackendType::Null) { + nullDevice = wgpu::Device::Acquire(adapter.CreateDevice()); + break; + } + } + + ASSERT(nullDevice.Get() != nullptr); + return nullDevice; + }, + false /* supportsErrorInjection */); +}
diff --git a/src/dawn/fuzzers/DawnWireServerAndVulkanBackendFuzzer.cpp b/src/dawn/fuzzers/DawnWireServerAndVulkanBackendFuzzer.cpp new file mode 100644 index 0000000..157ce01 --- /dev/null +++ b/src/dawn/fuzzers/DawnWireServerAndVulkanBackendFuzzer.cpp
@@ -0,0 +1,44 @@ +// Copyright 2019 The Dawn Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include "DawnWireServerFuzzer.h" + +#include "dawn/native/DawnNative.h" +#include "testing/libfuzzer/libfuzzer_exports.h" + +extern "C" int LLVMFuzzerInitialize(int* argc, char*** argv) { + return DawnWireServerFuzzer::Initialize(argc, argv); +} + +extern "C" int LLVMFuzzerTestOneInput(const uint8_t* data, size_t size) { + return DawnWireServerFuzzer::Run( + data, size, + [](dawn::native::Instance* instance) { + std::vector<dawn::native::Adapter> adapters = instance->GetAdapters(); + + wgpu::Device device; + for (dawn::native::Adapter adapter : adapters) { + wgpu::AdapterProperties properties; + adapter.GetProperties(&properties); + + if (properties.backendType == wgpu::BackendType::Vulkan && + properties.adapterType == wgpu::AdapterType::CPU) { + device = wgpu::Device::Acquire(adapter.CreateDevice()); + break; + } + } + return device; + }, + true /* supportsErrorInjection */); +}
diff --git a/src/dawn/fuzzers/DawnWireServerFuzzer.cpp b/src/dawn/fuzzers/DawnWireServerFuzzer.cpp new file mode 100644 index 0000000..bf35518 --- /dev/null +++ b/src/dawn/fuzzers/DawnWireServerFuzzer.cpp
@@ -0,0 +1,141 @@ +// Copyright 2019 The Dawn Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include "DawnWireServerFuzzer.h" + +#include "dawn/common/Assert.h" +#include "dawn/common/Log.h" +#include "dawn/common/SystemUtils.h" +#include "dawn/dawn_proc.h" +#include "dawn/native/DawnNative.h" +#include "dawn/utils/SystemUtils.h" +#include "dawn/webgpu_cpp.h" +#include "dawn/wire/WireServer.h" + +#include <fstream> +#include <vector> + +namespace { + + class DevNull : public dawn::wire::CommandSerializer { + public: + size_t GetMaximumAllocationSize() const override { + // Some fuzzer bots have a 2GB allocation limit. Pick a value reasonably below that. + return 1024 * 1024 * 1024; + } + void* GetCmdSpace(size_t size) override { + if (size > buf.size()) { + buf.resize(size); + } + return buf.data(); + } + bool Flush() override { + return true; + } + + private: + std::vector<char> buf; + }; + + std::unique_ptr<dawn::native::Instance> sInstance; + WGPUProcDeviceCreateSwapChain sOriginalDeviceCreateSwapChain = nullptr; + + bool sCommandsComplete = false; + + WGPUSwapChain ErrorDeviceCreateSwapChain(WGPUDevice device, + WGPUSurface surface, + const WGPUSwapChainDescriptor*) { + WGPUSwapChainDescriptor desc = {}; + // A 0 implementation will trigger a swapchain creation error. + desc.implementation = 0; + return sOriginalDeviceCreateSwapChain(device, surface, &desc); + } + +} // namespace + +int DawnWireServerFuzzer::Initialize(int* argc, char*** argv) { + // TODO(crbug.com/1038952): The Instance must be static because destructing the vkInstance with + // Swiftshader crashes libFuzzer. When this is fixed, move this into Run so that error injection + // for adapter discovery can be fuzzed. + sInstance = std::make_unique<dawn::native::Instance>(); + sInstance->DiscoverDefaultAdapters(); + + return 0; +} + +int DawnWireServerFuzzer::Run(const uint8_t* data, + size_t size, + MakeDeviceFn MakeDevice, + bool supportsErrorInjection) { + // We require at least the injected error index. + if (size < sizeof(uint64_t)) { + return 0; + } + + // Get and consume the injected error index. + uint64_t injectedErrorIndex = *reinterpret_cast<const uint64_t*>(data); + data += sizeof(uint64_t); + size -= sizeof(uint64_t); + + if (supportsErrorInjection) { + dawn::native::EnableErrorInjector(); + + // Clear the error injector since it has the previous run's call counts. + dawn::native::ClearErrorInjector(); + + dawn::native::InjectErrorAt(injectedErrorIndex); + } + + DawnProcTable procs = dawn::native::GetProcs(); + + // Swapchains receive a pointer to an implementation. The fuzzer will pass garbage in so we + // intercept calls to create swapchains and make sure they always return error swapchains. + // This is ok for fuzzing because embedders of dawn_wire would always define their own + // swapchain handling. + sOriginalDeviceCreateSwapChain = procs.deviceCreateSwapChain; + procs.deviceCreateSwapChain = ErrorDeviceCreateSwapChain; + + dawnProcSetProcs(&procs); + + wgpu::Device device = MakeDevice(sInstance.get()); + if (!device) { + // We should only ever fail device creation if an error was injected. + ASSERT(supportsErrorInjection); + return 0; + } + + DevNull devNull; + dawn::wire::WireServerDescriptor serverDesc = {}; + serverDesc.procs = &procs; + serverDesc.serializer = &devNull; + + std::unique_ptr<dawn::wire::WireServer> wireServer(new dawn_wire::WireServer(serverDesc)); + wireServer->InjectDevice(device.Get(), 1, 0); + + wireServer->HandleCommands(reinterpret_cast<const char*>(data), size); + + // Wait for all previous commands before destroying the server. + // TODO(enga): Improve this when we improve/finalize how processing events happens. + { + device.GetQueue().OnSubmittedWorkDone( + 0u, [](WGPUQueueWorkDoneStatus, void*) { sCommandsComplete = true; }, nullptr); + while (!sCommandsComplete) { + device.Tick(); + utils::USleep(100); + } + } + + wireServer = nullptr; + return 0; +}
diff --git a/src/dawn/fuzzers/DawnWireServerFuzzer.h b/src/dawn/fuzzers/DawnWireServerFuzzer.h new file mode 100644 index 0000000..83b6d3a --- /dev/null +++ b/src/dawn/fuzzers/DawnWireServerFuzzer.h
@@ -0,0 +1,34 @@ +// Copyright 2019 The Dawn Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include "dawn/webgpu_cpp.h" + +#include <cstdint> +#include <functional> + +namespace dawn::native { + + class Instance; + +} // namespace dawn::native + +namespace DawnWireServerFuzzer { + + using MakeDeviceFn = std::function<wgpu::Device(dawn::native::Instance*)>; + + int Initialize(int* argc, char*** argv); + + int Run(const uint8_t* data, size_t size, MakeDeviceFn MakeDevice, bool supportsErrorInjection); + +} // namespace DawnWireServerFuzzer
diff --git a/src/dawn/fuzzers/StandaloneFuzzerMain.cpp b/src/dawn/fuzzers/StandaloneFuzzerMain.cpp new file mode 100644 index 0000000..3341199 --- /dev/null +++ b/src/dawn/fuzzers/StandaloneFuzzerMain.cpp
@@ -0,0 +1,68 @@ +// Copyright 2018 The Dawn Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include <cstdint> +#include <cstdlib> +#include <iostream> +#include <vector> + +extern "C" int LLVMFuzzerInitialize(int* argc, char*** argv); +extern "C" int LLVMFuzzerTestOneInput(const uint8_t* data, size_t size); + +int main(int argc, char** argv) { + if (LLVMFuzzerInitialize(&argc, &argv)) { + std::cerr << "Failed to initialize fuzzer target" << std::endl; + return 1; + } + + if (argc != 2) { + std::cout << "Usage: <standalone reproducer> [options] FILE" << std::endl; + return 1; + } + + std::cout << "WARNING: this is just a best-effort reproducer for fuzzer issues in standalone " + << "Dawn builds. For the real fuzzer, please build inside Chromium." << std::endl; + + const char* filename = argv[1]; + std::cout << "Reproducing using file: " << filename << std::endl; + + std::vector<char> data; + { + FILE* file = fopen(filename, "rb"); + if (!file) { + std::cerr << "Failed to open " << filename << std::endl; + return 1; + } + + fseek(file, 0, SEEK_END); + long tellFileSize = ftell(file); + if (tellFileSize <= 0) { + std::cerr << "Input file of incorrect size: " << filename << std::endl; + return 1; + } + fseek(file, 0, SEEK_SET); + + size_t fileSize = static_cast<size_t>(tellFileSize); + data.resize(fileSize); + + size_t bytesRead = fread(data.data(), sizeof(char), fileSize, file); + fclose(file); + if (bytesRead != fileSize) { + std::cerr << "Failed to read " << filename << std::endl; + return 1; + } + } + + return LLVMFuzzerTestOneInput(reinterpret_cast<const uint8_t*>(data.data()), data.size()); +}
diff --git a/src/dawn/native/Adapter.cpp b/src/dawn/native/Adapter.cpp new file mode 100644 index 0000000..4c000ac --- /dev/null +++ b/src/dawn/native/Adapter.cpp
@@ -0,0 +1,227 @@ +// Copyright 2018 The Dawn Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include "dawn/native/Adapter.h" + +#include "dawn/common/Constants.h" +#include "dawn/native/Device.h" +#include "dawn/native/Instance.h" +#include "dawn/native/ValidationUtils_autogen.h" + +namespace dawn::native { + + AdapterBase::AdapterBase(InstanceBase* instance, wgpu::BackendType backend) + : mInstance(instance), mBackend(backend) { + mSupportedFeatures.EnableFeature(Feature::DawnNative); + mSupportedFeatures.EnableFeature(Feature::DawnInternalUsages); + } + + MaybeError AdapterBase::Initialize() { + DAWN_TRY_CONTEXT(InitializeImpl(), "initializing adapter (backend=%s)", mBackend); + DAWN_TRY_CONTEXT( + InitializeSupportedFeaturesImpl(), + "gathering supported features for \"%s\" - \"%s\" (vendorId=%#06x deviceId=%#06x " + "backend=%s type=%s)", + mName, mDriverDescription, mVendorId, mDeviceId, mBackend, mAdapterType); + DAWN_TRY_CONTEXT( + InitializeSupportedLimitsImpl(&mLimits), + "gathering supported limits for \"%s\" - \"%s\" (vendorId=%#06x deviceId=%#06x " + "backend=%s type=%s)", + mName, mDriverDescription, mVendorId, mDeviceId, mBackend, mAdapterType); + + // Enforce internal Dawn constants. + mLimits.v1.maxVertexBufferArrayStride = + std::min(mLimits.v1.maxVertexBufferArrayStride, kMaxVertexBufferArrayStride); + mLimits.v1.maxBindGroups = std::min(mLimits.v1.maxBindGroups, kMaxBindGroups); + mLimits.v1.maxVertexAttributes = + std::min(mLimits.v1.maxVertexAttributes, uint32_t(kMaxVertexAttributes)); + mLimits.v1.maxVertexBuffers = + std::min(mLimits.v1.maxVertexBuffers, uint32_t(kMaxVertexBuffers)); + mLimits.v1.maxInterStageShaderComponents = + std::min(mLimits.v1.maxInterStageShaderComponents, kMaxInterStageShaderComponents); + mLimits.v1.maxSampledTexturesPerShaderStage = std::min( + mLimits.v1.maxSampledTexturesPerShaderStage, kMaxSampledTexturesPerShaderStage); + mLimits.v1.maxSamplersPerShaderStage = + std::min(mLimits.v1.maxSamplersPerShaderStage, kMaxSamplersPerShaderStage); + mLimits.v1.maxStorageBuffersPerShaderStage = + std::min(mLimits.v1.maxStorageBuffersPerShaderStage, kMaxStorageBuffersPerShaderStage); + mLimits.v1.maxStorageTexturesPerShaderStage = std::min( + mLimits.v1.maxStorageTexturesPerShaderStage, kMaxStorageTexturesPerShaderStage); + mLimits.v1.maxUniformBuffersPerShaderStage = + std::min(mLimits.v1.maxUniformBuffersPerShaderStage, kMaxUniformBuffersPerShaderStage); + mLimits.v1.maxDynamicUniformBuffersPerPipelineLayout = + std::min(mLimits.v1.maxDynamicUniformBuffersPerPipelineLayout, + kMaxDynamicUniformBuffersPerPipelineLayout); + mLimits.v1.maxDynamicStorageBuffersPerPipelineLayout = + std::min(mLimits.v1.maxDynamicStorageBuffersPerPipelineLayout, + kMaxDynamicStorageBuffersPerPipelineLayout); + + return {}; + } + + bool AdapterBase::APIGetLimits(SupportedLimits* limits) const { + return GetLimits(limits); + } + + void AdapterBase::APIGetProperties(AdapterProperties* properties) const { + properties->vendorID = mVendorId; + properties->deviceID = mDeviceId; + properties->name = mName.c_str(); + properties->driverDescription = mDriverDescription.c_str(); + properties->adapterType = mAdapterType; + properties->backendType = mBackend; + } + + bool AdapterBase::APIHasFeature(wgpu::FeatureName feature) const { + return mSupportedFeatures.IsEnabled(feature); + } + + size_t AdapterBase::APIEnumerateFeatures(wgpu::FeatureName* features) const { + return mSupportedFeatures.EnumerateFeatures(features); + } + + DeviceBase* AdapterBase::APICreateDevice(const DeviceDescriptor* descriptor) { + DeviceDescriptor defaultDesc = {}; + if (descriptor == nullptr) { + descriptor = &defaultDesc; + } + auto result = CreateDeviceInternal(descriptor); + if (result.IsError()) { + mInstance->ConsumedError(result.AcquireError()); + return nullptr; + } + return result.AcquireSuccess().Detach(); + } + + void AdapterBase::APIRequestDevice(const DeviceDescriptor* descriptor, + WGPURequestDeviceCallback callback, + void* userdata) { + static constexpr DeviceDescriptor kDefaultDescriptor = {}; + if (descriptor == nullptr) { + descriptor = &kDefaultDescriptor; + } + auto result = CreateDeviceInternal(descriptor); + + if (result.IsError()) { + std::unique_ptr<ErrorData> errorData = result.AcquireError(); + // TODO(crbug.com/dawn/1122): Call callbacks only on wgpuInstanceProcessEvents + callback(WGPURequestDeviceStatus_Error, nullptr, + errorData->GetFormattedMessage().c_str(), userdata); + return; + } + + Ref<DeviceBase> device = result.AcquireSuccess(); + + WGPURequestDeviceStatus status = + device == nullptr ? WGPURequestDeviceStatus_Unknown : WGPURequestDeviceStatus_Success; + // TODO(crbug.com/dawn/1122): Call callbacks only on wgpuInstanceProcessEvents + callback(status, ToAPI(device.Detach()), nullptr, userdata); + } + + uint32_t AdapterBase::GetVendorId() const { + return mVendorId; + } + + uint32_t AdapterBase::GetDeviceId() const { + return mDeviceId; + } + + wgpu::BackendType AdapterBase::GetBackendType() const { + return mBackend; + } + + InstanceBase* AdapterBase::GetInstance() const { + return mInstance; + } + + FeaturesSet AdapterBase::GetSupportedFeatures() const { + return mSupportedFeatures; + } + + bool AdapterBase::SupportsAllRequiredFeatures( + const ityp::span<size_t, const wgpu::FeatureName>& features) const { + for (wgpu::FeatureName f : features) { + if (!mSupportedFeatures.IsEnabled(f)) { + return false; + } + } + return true; + } + + WGPUDeviceProperties AdapterBase::GetAdapterProperties() const { + WGPUDeviceProperties adapterProperties = {}; + adapterProperties.deviceID = mDeviceId; + adapterProperties.vendorID = mVendorId; + adapterProperties.adapterType = static_cast<WGPUAdapterType>(mAdapterType); + + mSupportedFeatures.InitializeDeviceProperties(&adapterProperties); + // This is OK for now because there are no limit feature structs. + // If we add additional structs, the caller will need to provide memory + // to store them (ex. by calling GetLimits directly instead). Currently, + // we keep this function as it's only used internally in Chromium to + // send the adapter properties across the wire. + GetLimits(FromAPI(&adapterProperties.limits)); + return adapterProperties; + } + + bool AdapterBase::GetLimits(SupportedLimits* limits) const { + ASSERT(limits != nullptr); + if (limits->nextInChain != nullptr) { + return false; + } + if (mUseTieredLimits) { + limits->limits = ApplyLimitTiers(mLimits.v1); + } else { + limits->limits = mLimits.v1; + } + return true; + } + + ResultOrError<Ref<DeviceBase>> AdapterBase::CreateDeviceInternal( + const DeviceDescriptor* descriptor) { + ASSERT(descriptor != nullptr); + + for (uint32_t i = 0; i < descriptor->requiredFeaturesCount; ++i) { + wgpu::FeatureName f = descriptor->requiredFeatures[i]; + DAWN_TRY(ValidateFeatureName(f)); + DAWN_INVALID_IF(!mSupportedFeatures.IsEnabled(f), + "Requested feature %s is not supported.", f); + } + + if (descriptor->requiredLimits != nullptr) { + DAWN_TRY_CONTEXT( + ValidateLimits(mUseTieredLimits ? ApplyLimitTiers(mLimits.v1) : mLimits.v1, + descriptor->requiredLimits->limits), + "validating required limits"); + + DAWN_INVALID_IF(descriptor->requiredLimits->nextInChain != nullptr, + "nextInChain is not nullptr."); + } + return CreateDeviceImpl(descriptor); + } + + void AdapterBase::SetUseTieredLimits(bool useTieredLimits) { + mUseTieredLimits = useTieredLimits; + } + + void AdapterBase::ResetInternalDeviceForTesting() { + mInstance->ConsumedError(ResetInternalDeviceForTestingImpl()); + } + + MaybeError AdapterBase::ResetInternalDeviceForTestingImpl() { + return DAWN_INTERNAL_ERROR( + "ResetInternalDeviceForTesting should only be used with the D3D12 backend."); + } + +} // namespace dawn::native
diff --git a/src/dawn/native/Adapter.h b/src/dawn/native/Adapter.h new file mode 100644 index 0000000..bd66c8b --- /dev/null +++ b/src/dawn/native/Adapter.h
@@ -0,0 +1,99 @@ +// Copyright 2018 The Dawn Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#ifndef DAWNNATIVE_ADAPTER_H_ +#define DAWNNATIVE_ADAPTER_H_ + +#include "dawn/native/DawnNative.h" + +#include "dawn/common/RefCounted.h" +#include "dawn/common/ityp_span.h" +#include "dawn/native/Error.h" +#include "dawn/native/Features.h" +#include "dawn/native/Limits.h" +#include "dawn/native/dawn_platform.h" + +#include <string> + +namespace dawn::native { + + class DeviceBase; + + class AdapterBase : public RefCounted { + public: + AdapterBase(InstanceBase* instance, wgpu::BackendType backend); + virtual ~AdapterBase() = default; + + MaybeError Initialize(); + + // WebGPU API + bool APIGetLimits(SupportedLimits* limits) const; + void APIGetProperties(AdapterProperties* properties) const; + bool APIHasFeature(wgpu::FeatureName feature) const; + size_t APIEnumerateFeatures(wgpu::FeatureName* features) const; + void APIRequestDevice(const DeviceDescriptor* descriptor, + WGPURequestDeviceCallback callback, + void* userdata); + DeviceBase* APICreateDevice(const DeviceDescriptor* descriptor = nullptr); + + uint32_t GetVendorId() const; + uint32_t GetDeviceId() const; + wgpu::BackendType GetBackendType() const; + InstanceBase* GetInstance() const; + + void ResetInternalDeviceForTesting(); + + FeaturesSet GetSupportedFeatures() const; + bool SupportsAllRequiredFeatures( + const ityp::span<size_t, const wgpu::FeatureName>& features) const; + WGPUDeviceProperties GetAdapterProperties() const; + + bool GetLimits(SupportedLimits* limits) const; + + void SetUseTieredLimits(bool useTieredLimits); + + virtual bool SupportsExternalImages() const = 0; + + protected: + uint32_t mVendorId = 0xFFFFFFFF; + uint32_t mDeviceId = 0xFFFFFFFF; + std::string mName; + wgpu::AdapterType mAdapterType = wgpu::AdapterType::Unknown; + std::string mDriverDescription; + FeaturesSet mSupportedFeatures; + + private: + virtual ResultOrError<Ref<DeviceBase>> CreateDeviceImpl( + const DeviceDescriptor* descriptor) = 0; + + virtual MaybeError InitializeImpl() = 0; + + // Check base WebGPU features and discover supported featurees. + virtual MaybeError InitializeSupportedFeaturesImpl() = 0; + + // Check base WebGPU limits and populate supported limits. + virtual MaybeError InitializeSupportedLimitsImpl(CombinedLimits* limits) = 0; + + ResultOrError<Ref<DeviceBase>> CreateDeviceInternal(const DeviceDescriptor* descriptor); + + virtual MaybeError ResetInternalDeviceForTestingImpl(); + InstanceBase* mInstance = nullptr; + wgpu::BackendType mBackend; + CombinedLimits mLimits; + bool mUseTieredLimits = false; + }; + +} // namespace dawn::native + +#endif // DAWNNATIVE_ADAPTER_H_
diff --git a/src/dawn/native/AsyncTask.cpp b/src/dawn/native/AsyncTask.cpp new file mode 100644 index 0000000..a1e2948 --- /dev/null +++ b/src/dawn/native/AsyncTask.cpp
@@ -0,0 +1,65 @@ +#include "dawn/native/AsyncTask.h" + +#include "dawn/platform/DawnPlatform.h" + +namespace dawn::native { + + AsyncTaskManager::AsyncTaskManager(dawn::platform::WorkerTaskPool* workerTaskPool) + : mWorkerTaskPool(workerTaskPool) { + } + + void AsyncTaskManager::PostTask(AsyncTask asyncTask) { + // If these allocations becomes expensive, we can slab-allocate tasks. + Ref<WaitableTask> waitableTask = AcquireRef(new WaitableTask()); + waitableTask->taskManager = this; + waitableTask->asyncTask = std::move(asyncTask); + + { + // We insert new waitableTask objects into mPendingTasks in main thread (PostTask()), + // and we may remove waitableTask objects from mPendingTasks in either main thread + // (WaitAllPendingTasks()) or sub-thread (TaskCompleted), so mPendingTasks should be + // protected by a mutex. + std::lock_guard<std::mutex> lock(mPendingTasksMutex); + mPendingTasks.emplace(waitableTask.Get(), waitableTask); + } + + // Ref the task since it is accessed inside the worker function. + // The worker function will acquire and release the task upon completion. + waitableTask->Reference(); + waitableTask->waitableEvent = + mWorkerTaskPool->PostWorkerTask(DoWaitableTask, waitableTask.Get()); + } + + void AsyncTaskManager::HandleTaskCompletion(WaitableTask* task) { + std::lock_guard<std::mutex> lock(mPendingTasksMutex); + auto iter = mPendingTasks.find(task); + if (iter != mPendingTasks.end()) { + mPendingTasks.erase(iter); + } + } + + void AsyncTaskManager::WaitAllPendingTasks() { + std::unordered_map<WaitableTask*, Ref<WaitableTask>> allPendingTasks; + + { + std::lock_guard<std::mutex> lock(mPendingTasksMutex); + allPendingTasks.swap(mPendingTasks); + } + + for (auto& [_, task] : allPendingTasks) { + task->waitableEvent->Wait(); + } + } + + bool AsyncTaskManager::HasPendingTasks() { + std::lock_guard<std::mutex> lock(mPendingTasksMutex); + return !mPendingTasks.empty(); + } + + void AsyncTaskManager::DoWaitableTask(void* task) { + Ref<WaitableTask> waitableTask = AcquireRef(static_cast<WaitableTask*>(task)); + waitableTask->asyncTask(); + waitableTask->taskManager->HandleTaskCompletion(waitableTask.Get()); + } + +} // namespace dawn::native
diff --git a/src/dawn/native/AsyncTask.h b/src/dawn/native/AsyncTask.h new file mode 100644 index 0000000..ca2edd0 --- /dev/null +++ b/src/dawn/native/AsyncTask.h
@@ -0,0 +1,65 @@ +// Copyright 2021 The Dawn Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#ifndef DAWNNATIVE_ASYC_TASK_H_ +#define DAWNNATIVE_ASYC_TASK_H_ + +#include <functional> +#include <memory> +#include <mutex> +#include <unordered_map> + +#include "dawn/common/RefCounted.h" + +namespace dawn::platform { + class WaitableEvent; + class WorkerTaskPool; +} // namespace dawn::platform + +namespace dawn::native { + + // TODO(crbug.com/dawn/826): we'll add additional things to AsyncTask in the future, like + // Cancel() and RunNow(). Cancelling helps avoid running the task's body when we are just + // shutting down the device. RunNow() could be used for more advanced scenarios, for example + // always doing ShaderModule initial compilation asynchronously, but being able to steal the + // task if we need it for synchronous pipeline compilation. + using AsyncTask = std::function<void()>; + + class AsyncTaskManager { + public: + explicit AsyncTaskManager(dawn::platform::WorkerTaskPool* workerTaskPool); + + void PostTask(AsyncTask asyncTask); + void WaitAllPendingTasks(); + bool HasPendingTasks(); + + private: + class WaitableTask : public RefCounted { + public: + AsyncTask asyncTask; + AsyncTaskManager* taskManager; + std::unique_ptr<dawn::platform::WaitableEvent> waitableEvent; + }; + + static void DoWaitableTask(void* task); + void HandleTaskCompletion(WaitableTask* task); + + std::mutex mPendingTasksMutex; + std::unordered_map<WaitableTask*, Ref<WaitableTask>> mPendingTasks; + dawn::platform::WorkerTaskPool* mWorkerTaskPool; + }; + +} // namespace dawn::native + +#endif
diff --git a/src/dawn/native/AttachmentState.cpp b/src/dawn/native/AttachmentState.cpp new file mode 100644 index 0000000..1e38d9d --- /dev/null +++ b/src/dawn/native/AttachmentState.cpp
@@ -0,0 +1,175 @@ +// Copyright 2019 The Dawn Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include "dawn/native/AttachmentState.h" + +#include "dawn/common/BitSetIterator.h" +#include "dawn/native/Device.h" +#include "dawn/native/ObjectContentHasher.h" +#include "dawn/native/Texture.h" + +namespace dawn::native { + + AttachmentStateBlueprint::AttachmentStateBlueprint( + const RenderBundleEncoderDescriptor* descriptor) + : mSampleCount(descriptor->sampleCount) { + ASSERT(descriptor->colorFormatsCount <= kMaxColorAttachments); + for (ColorAttachmentIndex i(uint8_t(0)); + i < ColorAttachmentIndex(static_cast<uint8_t>(descriptor->colorFormatsCount)); ++i) { + wgpu::TextureFormat format = descriptor->colorFormats[static_cast<uint8_t>(i)]; + if (format != wgpu::TextureFormat::Undefined) { + mColorAttachmentsSet.set(i); + mColorFormats[i] = format; + } + } + mDepthStencilFormat = descriptor->depthStencilFormat; + } + + AttachmentStateBlueprint::AttachmentStateBlueprint(const RenderPipelineDescriptor* descriptor) + : mSampleCount(descriptor->multisample.count) { + if (descriptor->fragment != nullptr) { + ASSERT(descriptor->fragment->targetCount <= kMaxColorAttachments); + for (ColorAttachmentIndex i(uint8_t(0)); + i < ColorAttachmentIndex(static_cast<uint8_t>(descriptor->fragment->targetCount)); + ++i) { + wgpu::TextureFormat format = + descriptor->fragment->targets[static_cast<uint8_t>(i)].format; + if (format != wgpu::TextureFormat::Undefined) { + mColorAttachmentsSet.set(i); + mColorFormats[i] = format; + } + } + } + if (descriptor->depthStencil != nullptr) { + mDepthStencilFormat = descriptor->depthStencil->format; + } + } + + AttachmentStateBlueprint::AttachmentStateBlueprint(const RenderPassDescriptor* descriptor) { + for (ColorAttachmentIndex i(uint8_t(0)); + i < ColorAttachmentIndex(static_cast<uint8_t>(descriptor->colorAttachmentCount)); + ++i) { + TextureViewBase* attachment = + descriptor->colorAttachments[static_cast<uint8_t>(i)].view; + if (attachment == nullptr) { + continue; + } + mColorAttachmentsSet.set(i); + mColorFormats[i] = attachment->GetFormat().format; + if (mSampleCount == 0) { + mSampleCount = attachment->GetTexture()->GetSampleCount(); + } else { + ASSERT(mSampleCount == attachment->GetTexture()->GetSampleCount()); + } + } + if (descriptor->depthStencilAttachment != nullptr) { + TextureViewBase* attachment = descriptor->depthStencilAttachment->view; + mDepthStencilFormat = attachment->GetFormat().format; + if (mSampleCount == 0) { + mSampleCount = attachment->GetTexture()->GetSampleCount(); + } else { + ASSERT(mSampleCount == attachment->GetTexture()->GetSampleCount()); + } + } + ASSERT(mSampleCount > 0); + } + + AttachmentStateBlueprint::AttachmentStateBlueprint(const AttachmentStateBlueprint& rhs) = + default; + + size_t AttachmentStateBlueprint::HashFunc::operator()( + const AttachmentStateBlueprint* attachmentState) const { + size_t hash = 0; + + // Hash color formats + HashCombine(&hash, attachmentState->mColorAttachmentsSet); + for (ColorAttachmentIndex i : IterateBitSet(attachmentState->mColorAttachmentsSet)) { + HashCombine(&hash, attachmentState->mColorFormats[i]); + } + + // Hash depth stencil attachment + HashCombine(&hash, attachmentState->mDepthStencilFormat); + + // Hash sample count + HashCombine(&hash, attachmentState->mSampleCount); + + return hash; + } + + bool AttachmentStateBlueprint::EqualityFunc::operator()( + const AttachmentStateBlueprint* a, + const AttachmentStateBlueprint* b) const { + // Check set attachments + if (a->mColorAttachmentsSet != b->mColorAttachmentsSet) { + return false; + } + + // Check color formats + for (ColorAttachmentIndex i : IterateBitSet(a->mColorAttachmentsSet)) { + if (a->mColorFormats[i] != b->mColorFormats[i]) { + return false; + } + } + + // Check depth stencil format + if (a->mDepthStencilFormat != b->mDepthStencilFormat) { + return false; + } + + // Check sample count + if (a->mSampleCount != b->mSampleCount) { + return false; + } + + return true; + } + + AttachmentState::AttachmentState(DeviceBase* device, const AttachmentStateBlueprint& blueprint) + : AttachmentStateBlueprint(blueprint), ObjectBase(device) { + } + + AttachmentState::~AttachmentState() { + GetDevice()->UncacheAttachmentState(this); + } + + size_t AttachmentState::ComputeContentHash() { + // TODO(dawn:549): skip this traversal and reuse the blueprint. + return AttachmentStateBlueprint::HashFunc()(this); + } + + ityp::bitset<ColorAttachmentIndex, kMaxColorAttachments> + AttachmentState::GetColorAttachmentsMask() const { + return mColorAttachmentsSet; + } + + wgpu::TextureFormat AttachmentState::GetColorAttachmentFormat( + ColorAttachmentIndex index) const { + ASSERT(mColorAttachmentsSet[index]); + return mColorFormats[index]; + } + + bool AttachmentState::HasDepthStencilAttachment() const { + return mDepthStencilFormat != wgpu::TextureFormat::Undefined; + } + + wgpu::TextureFormat AttachmentState::GetDepthStencilFormat() const { + ASSERT(HasDepthStencilAttachment()); + return mDepthStencilFormat; + } + + uint32_t AttachmentState::GetSampleCount() const { + return mSampleCount; + } + +} // namespace dawn::native
diff --git a/src/dawn/native/AttachmentState.h b/src/dawn/native/AttachmentState.h new file mode 100644 index 0000000..21eff85 --- /dev/null +++ b/src/dawn/native/AttachmentState.h
@@ -0,0 +1,83 @@ +// Copyright 2019 The Dawn Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#ifndef DAWNNATIVE_ATTACHMENTSTATE_H_ +#define DAWNNATIVE_ATTACHMENTSTATE_H_ + +#include "dawn/common/Constants.h" +#include "dawn/common/ityp_array.h" +#include "dawn/common/ityp_bitset.h" +#include "dawn/native/CachedObject.h" +#include "dawn/native/IntegerTypes.h" +#include "dawn/native/ObjectBase.h" + +#include "dawn/native/dawn_platform.h" + +#include <array> +#include <bitset> + +namespace dawn::native { + + class DeviceBase; + + // AttachmentStateBlueprint and AttachmentState are separated so the AttachmentState + // can be constructed by copying the blueprint state instead of traversing descriptors. + // Also, AttachmentStateBlueprint does not need a refcount like AttachmentState. + class AttachmentStateBlueprint { + public: + // Note: Descriptors must be validated before the AttachmentState is constructed. + explicit AttachmentStateBlueprint(const RenderBundleEncoderDescriptor* descriptor); + explicit AttachmentStateBlueprint(const RenderPipelineDescriptor* descriptor); + explicit AttachmentStateBlueprint(const RenderPassDescriptor* descriptor); + + AttachmentStateBlueprint(const AttachmentStateBlueprint& rhs); + + // Functors necessary for the unordered_set<AttachmentState*>-based cache. + struct HashFunc { + size_t operator()(const AttachmentStateBlueprint* attachmentState) const; + }; + struct EqualityFunc { + bool operator()(const AttachmentStateBlueprint* a, + const AttachmentStateBlueprint* b) const; + }; + + protected: + ityp::bitset<ColorAttachmentIndex, kMaxColorAttachments> mColorAttachmentsSet; + ityp::array<ColorAttachmentIndex, wgpu::TextureFormat, kMaxColorAttachments> mColorFormats; + // Default (texture format Undefined) indicates there is no depth stencil attachment. + wgpu::TextureFormat mDepthStencilFormat = wgpu::TextureFormat::Undefined; + uint32_t mSampleCount = 0; + }; + + class AttachmentState final : public AttachmentStateBlueprint, + public ObjectBase, + public CachedObject { + public: + AttachmentState(DeviceBase* device, const AttachmentStateBlueprint& blueprint); + + ityp::bitset<ColorAttachmentIndex, kMaxColorAttachments> GetColorAttachmentsMask() const; + wgpu::TextureFormat GetColorAttachmentFormat(ColorAttachmentIndex index) const; + bool HasDepthStencilAttachment() const; + wgpu::TextureFormat GetDepthStencilFormat() const; + uint32_t GetSampleCount() const; + + size_t ComputeContentHash() override; + + private: + ~AttachmentState() override; + }; + +} // namespace dawn::native + +#endif // DAWNNATIVE_ATTACHMENTSTATE_H_
diff --git a/src/dawn/native/BUILD.gn b/src/dawn/native/BUILD.gn new file mode 100644 index 0000000..5d97a8e --- /dev/null +++ b/src/dawn/native/BUILD.gn
@@ -0,0 +1,773 @@ +# Copyright 2020 The Dawn Authors +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +import("../../../scripts/dawn_overrides_with_defaults.gni") + +import("//build_overrides/build.gni") +import("${dawn_root}/generator/dawn_generator.gni") +import("${dawn_root}/scripts/dawn_component.gni") +import("${dawn_root}/scripts/dawn_features.gni") + +# Import mac_deployment_target +if (is_mac) { + if (dawn_has_build) { + import("//build/config/mac/mac_sdk.gni") + } else { + mac_deployment_target = "10.11.0" + } +} + +# The VVLs are an optional dependency, only use it if the path has been set. +enable_vulkan_validation_layers = dawn_enable_vulkan_validation_layers && + dawn_vulkan_validation_layers_dir != "" +if (enable_vulkan_validation_layers) { + import("//build_overrides/vulkan_validation_layers.gni") +} + +# ANGLE is an optional dependency; only use it if the path has been set. +use_angle = dawn_use_angle && defined(dawn_angle_dir) + +# Swiftshader is an optional dependency, only use it if the path has been set. +use_swiftshader = dawn_use_swiftshader && dawn_swiftshader_dir != "" +if (use_swiftshader) { + assert(dawn_enable_vulkan, + "dawn_use_swiftshader requires dawn_enable_vulkan=true") + import("${dawn_swiftshader_dir}/src/Vulkan/vulkan.gni") +} + +# The Vulkan loader is an optional dependency, only use it if the path has been +# set. +if (dawn_enable_vulkan) { + enable_vulkan_loader = + dawn_enable_vulkan_loader && dawn_vulkan_loader_dir != "" +} + +group("abseil") { + # When build_with_chromium=true we need to include "//third_party/abseil-cpp:absl" while + # it's beneficial to be more specific with standalone Dawn, especially when it comes to + # including it as a dependency in other projects (such as Skia). + if (build_with_chromium) { + public_deps = [ "$dawn_abseil_dir:absl" ] + } else { + public_deps = [ "${dawn_root}/third_party/gn/abseil-cpp:str_format" ] + } +} + +config("internal") { + configs = [ "${dawn_root}/src/dawn/common:internal_config" ] + + # Suppress warnings that Metal isn't in the deployment target of Chrome: + # initialization of the Metal backend is behind a IsMetalSupported check so + # Dawn won't call Metal functions on macOS 10.10. + # At the time this is written Chromium supports 10.10.0 and above, so if we + # aren't on 10.11 it means we are on 10.11 and above, and Metal is available. + # Skipping this check on 10.11 and above is important as it allows getting + # proper compilation warning when using 10.12 and above feature for example. + # TODO(crbug.com/1004024): Consider using API_AVAILABLE annotations on all + # metal code in dawn once crbug.com/1004024 is sorted out if Chromium still + # supports 10.10 then. + if (is_mac && mac_deployment_target == "10.10.0") { + cflags_objcc = [ "-Wno-unguarded-availability" ] + } +} + +config("weak_framework") { + if (is_mac && dawn_enable_metal) { + weak_frameworks = [ "Metal.framework" ] + } +} + +# Config that adds the @executable_path rpath if needed so that Swiftshader or the Vulkan loader are found. +config("vulkan_rpath") { + if (is_mac && dawn_enable_vulkan && + (use_swiftshader || enable_vulkan_loader)) { + ldflags = [ + "-rpath", + "@executable_path/", + ] + } +} + +dawn_json_generator("utils_gen") { + target = "native_utils" + outputs = [ + "src/dawn/native/ChainUtils_autogen.h", + "src/dawn/native/ChainUtils_autogen.cpp", + "src/dawn/native/ProcTable.cpp", + "src/dawn/native/dawn_platform_autogen.h", + "src/dawn/native/wgpu_structs_autogen.h", + "src/dawn/native/wgpu_structs_autogen.cpp", + "src/dawn/native/ValidationUtils_autogen.h", + "src/dawn/native/ValidationUtils_autogen.cpp", + "src/dawn/native/webgpu_absl_format_autogen.h", + "src/dawn/native/webgpu_absl_format_autogen.cpp", + "src/dawn/native/ObjectType_autogen.h", + "src/dawn/native/ObjectType_autogen.cpp", + ] +} + +if (dawn_enable_opengl) { + dawn_generator("opengl_loader_gen") { + script = "${dawn_root}/generator/opengl_loader_generator.py" + args = [ + "--gl-xml", + rebase_path("${dawn_root}/third_party/khronos/gl.xml", root_build_dir), + "--supported-extensions", + rebase_path("opengl/supported_extensions.json", root_build_dir), + ] + outputs = [ + "src/dawn/native/opengl/OpenGLFunctionsBase_autogen.cpp", + "src/dawn/native/opengl/OpenGLFunctionsBase_autogen.h", + "src/dawn/native/opengl/opengl_platform_autogen.h", + ] + } +} + +# Public dawn native headers so they can be publicly visible for +# dependencies of dawn native +source_set("headers") { + public_deps = [ "${dawn_root}/include/dawn:cpp_headers" ] + all_dependent_configs = [ "${dawn_root}/include/dawn:public" ] + sources = [ + "${dawn_root}/include/dawn/native/DawnNative.h", + "${dawn_root}/include/dawn/native/dawn_native_export.h", + + # Include all backend's public headers so that dependencies can include + # them even when the backends are disabled. + "${dawn_root}/include/dawn/native/D3D12Backend.h", + "${dawn_root}/include/dawn/native/MetalBackend.h", + "${dawn_root}/include/dawn/native/NullBackend.h", + "${dawn_root}/include/dawn/native/OpenGLBackend.h", + "${dawn_root}/include/dawn/native/VulkanBackend.h", + ] +} + +# The meat of the compilation for dawn native so that we can cheaply have +# shared_library / static_library versions of it. It compiles all the files +# except those that define exported symbols. +source_set("sources") { + deps = [ + ":headers", + ":utils_gen", + "${dawn_root}/src/dawn/common", + "${dawn_spirv_tools_dir}:spvtools_opt", + "${dawn_spirv_tools_dir}:spvtools_val", + "${dawn_root}/src/tint:libtint", + ] + defines = [] + libs = [] + data_deps = [] + + configs += [ ":internal" ] + + # Dependencies that are needed to compile dawn native entry points in + # FooBackend.cpp need to be public deps so they are propagated to the + # dawn native target + public_deps = [ + ":abseil", + "${dawn_root}/src/dawn/platform", + ] + + sources = get_target_outputs(":utils_gen") + sources += [ + "Adapter.cpp", + "Adapter.h", + "AsyncTask.cpp", + "AsyncTask.h", + "AttachmentState.cpp", + "AttachmentState.h", + "BackendConnection.cpp", + "BackendConnection.h", + "BindGroup.cpp", + "BindGroup.h", + "BindGroupLayout.cpp", + "BindGroupLayout.h", + "BindGroupTracker.h", + "BindingInfo.cpp", + "BindingInfo.h", + "BuddyAllocator.cpp", + "BuddyAllocator.h", + "BuddyMemoryAllocator.cpp", + "BuddyMemoryAllocator.h", + "Buffer.cpp", + "Buffer.h", + "CacheKey.cpp", + "CacheKey.h", + "CachedObject.cpp", + "CachedObject.h", + "CallbackTaskManager.cpp", + "CallbackTaskManager.h", + "CommandAllocator.cpp", + "CommandAllocator.h", + "CommandBuffer.cpp", + "CommandBuffer.h", + "CommandBufferStateTracker.cpp", + "CommandBufferStateTracker.h", + "CommandEncoder.cpp", + "CommandEncoder.h", + "CommandValidation.cpp", + "CommandValidation.h", + "Commands.cpp", + "Commands.h", + "CompilationMessages.cpp", + "CompilationMessages.h", + "ComputePassEncoder.cpp", + "ComputePassEncoder.h", + "ComputePipeline.cpp", + "ComputePipeline.h", + "CopyTextureForBrowserHelper.cpp", + "CopyTextureForBrowserHelper.h", + "CreatePipelineAsyncTask.cpp", + "CreatePipelineAsyncTask.h", + "Device.cpp", + "Device.h", + "DynamicUploader.cpp", + "DynamicUploader.h", + "EncodingContext.cpp", + "EncodingContext.h", + "EnumClassBitmasks.h", + "EnumMaskIterator.h", + "Error.cpp", + "Error.h", + "ErrorData.cpp", + "ErrorData.h", + "ErrorInjector.cpp", + "ErrorInjector.h", + "ErrorScope.cpp", + "ErrorScope.h", + "ExternalTexture.cpp", + "ExternalTexture.h", + "Features.cpp", + "Features.h", + "Format.cpp", + "Format.h", + "Forward.h", + "IndirectDrawMetadata.cpp", + "IndirectDrawMetadata.h", + "IndirectDrawValidationEncoder.cpp", + "IndirectDrawValidationEncoder.h", + "Instance.cpp", + "Instance.h", + "IntegerTypes.h", + "InternalPipelineStore.cpp", + "InternalPipelineStore.h", + "Limits.cpp", + "Limits.h", + "ObjectBase.cpp", + "ObjectBase.h", + "ObjectContentHasher.cpp", + "ObjectContentHasher.h", + "PassResourceUsage.h", + "PassResourceUsageTracker.cpp", + "PassResourceUsageTracker.h", + "PerStage.cpp", + "PerStage.h", + "PersistentCache.cpp", + "PersistentCache.h", + "Pipeline.cpp", + "Pipeline.h", + "PipelineLayout.cpp", + "PipelineLayout.h", + "PooledResourceMemoryAllocator.cpp", + "PooledResourceMemoryAllocator.h", + "ProgrammableEncoder.cpp", + "ProgrammableEncoder.h", + "QueryHelper.cpp", + "QueryHelper.h", + "QuerySet.cpp", + "QuerySet.h", + "Queue.cpp", + "Queue.h", + "RenderBundle.cpp", + "RenderBundle.h", + "RenderBundleEncoder.cpp", + "RenderBundleEncoder.h", + "RenderEncoderBase.cpp", + "RenderEncoderBase.h", + "RenderPassEncoder.cpp", + "RenderPassEncoder.h", + "RenderPipeline.cpp", + "RenderPipeline.h", + "ResourceHeap.h", + "ResourceHeapAllocator.h", + "ResourceMemoryAllocation.cpp", + "ResourceMemoryAllocation.h", + "RingBufferAllocator.cpp", + "RingBufferAllocator.h", + "Sampler.cpp", + "Sampler.h", + "ScratchBuffer.cpp", + "ScratchBuffer.h", + "ShaderModule.cpp", + "ShaderModule.h", + "StagingBuffer.cpp", + "StagingBuffer.h", + "Subresource.cpp", + "Subresource.h", + "SubresourceStorage.h", + "Surface.cpp", + "Surface.h", + "SwapChain.cpp", + "SwapChain.h", + "Texture.cpp", + "Texture.h", + "TintUtils.cpp", + "TintUtils.h", + "ToBackend.h", + "Toggles.cpp", + "Toggles.h", + "VertexFormat.cpp", + "VertexFormat.h", + "dawn_platform.h", + "utils/WGPUHelpers.cpp", + "utils/WGPUHelpers.h", + "webgpu_absl_format.cpp", + "webgpu_absl_format.h", + ] + + if (dawn_use_x11) { + libs += [ "X11" ] + sources += [ + "XlibXcbFunctions.cpp", + "XlibXcbFunctions.h", + ] + } + + # Only win32 app needs to link with user32.lib + # In UWP, all availiable APIs are defined in WindowsApp.lib + if (is_win && !dawn_is_winuwp) { + libs += [ "user32.lib" ] + } + + if (dawn_is_winuwp && is_debug) { + # DXGIGetDebugInterface1 is defined in dxgi.lib + # But this API is tagged as a development-only capability + # which implies that linking to this function will cause + # the application to fail Windows store certification + # So we only link to it in debug build when compiling for UWP. + # In win32 we load dxgi.dll using LoadLibrary + # so no need for static linking. + libs += [ "dxgi.lib" ] + } + + # TODO(dawn:766): + # Should link dxcompiler.lib and WinPixEventRuntime_UAP.lib in UWP + # Somehow use dxcompiler.lib makes CoreApp unable to activate + # WinPIX should be added as third party tools and linked statically + + if (dawn_enable_d3d12) { + libs += [ "dxguid.lib" ] + sources += [ + "d3d12/AdapterD3D12.cpp", + "d3d12/AdapterD3D12.h", + "d3d12/BackendD3D12.cpp", + "d3d12/BackendD3D12.h", + "d3d12/BindGroupD3D12.cpp", + "d3d12/BindGroupD3D12.h", + "d3d12/BindGroupLayoutD3D12.cpp", + "d3d12/BindGroupLayoutD3D12.h", + "d3d12/BufferD3D12.cpp", + "d3d12/BufferD3D12.h", + "d3d12/CPUDescriptorHeapAllocationD3D12.cpp", + "d3d12/CPUDescriptorHeapAllocationD3D12.h", + "d3d12/CommandAllocatorManager.cpp", + "d3d12/CommandAllocatorManager.h", + "d3d12/CommandBufferD3D12.cpp", + "d3d12/CommandBufferD3D12.h", + "d3d12/CommandRecordingContext.cpp", + "d3d12/CommandRecordingContext.h", + "d3d12/ComputePipelineD3D12.cpp", + "d3d12/ComputePipelineD3D12.h", + "d3d12/D3D11on12Util.cpp", + "d3d12/D3D11on12Util.h", + "d3d12/D3D12Error.cpp", + "d3d12/D3D12Error.h", + "d3d12/D3D12Info.cpp", + "d3d12/D3D12Info.h", + "d3d12/DeviceD3D12.cpp", + "d3d12/DeviceD3D12.h", + "d3d12/Forward.h", + "d3d12/GPUDescriptorHeapAllocationD3D12.cpp", + "d3d12/GPUDescriptorHeapAllocationD3D12.h", + "d3d12/HeapAllocatorD3D12.cpp", + "d3d12/HeapAllocatorD3D12.h", + "d3d12/HeapD3D12.cpp", + "d3d12/HeapD3D12.h", + "d3d12/IntegerTypes.h", + "d3d12/NativeSwapChainImplD3D12.cpp", + "d3d12/NativeSwapChainImplD3D12.h", + "d3d12/PageableD3D12.cpp", + "d3d12/PageableD3D12.h", + "d3d12/PipelineLayoutD3D12.cpp", + "d3d12/PipelineLayoutD3D12.h", + "d3d12/PlatformFunctions.cpp", + "d3d12/PlatformFunctions.h", + "d3d12/QuerySetD3D12.cpp", + "d3d12/QuerySetD3D12.h", + "d3d12/QueueD3D12.cpp", + "d3d12/QueueD3D12.h", + "d3d12/RenderPassBuilderD3D12.cpp", + "d3d12/RenderPassBuilderD3D12.h", + "d3d12/RenderPipelineD3D12.cpp", + "d3d12/RenderPipelineD3D12.h", + "d3d12/ResidencyManagerD3D12.cpp", + "d3d12/ResidencyManagerD3D12.h", + "d3d12/ResourceAllocatorManagerD3D12.cpp", + "d3d12/ResourceAllocatorManagerD3D12.h", + "d3d12/ResourceHeapAllocationD3D12.cpp", + "d3d12/ResourceHeapAllocationD3D12.h", + "d3d12/SamplerD3D12.cpp", + "d3d12/SamplerD3D12.h", + "d3d12/SamplerHeapCacheD3D12.cpp", + "d3d12/SamplerHeapCacheD3D12.h", + "d3d12/ShaderModuleD3D12.cpp", + "d3d12/ShaderModuleD3D12.h", + "d3d12/ShaderVisibleDescriptorAllocatorD3D12.cpp", + "d3d12/ShaderVisibleDescriptorAllocatorD3D12.h", + "d3d12/StagingBufferD3D12.cpp", + "d3d12/StagingBufferD3D12.h", + "d3d12/StagingDescriptorAllocatorD3D12.cpp", + "d3d12/StagingDescriptorAllocatorD3D12.h", + "d3d12/SwapChainD3D12.cpp", + "d3d12/SwapChainD3D12.h", + "d3d12/TextureCopySplitter.cpp", + "d3d12/TextureCopySplitter.h", + "d3d12/TextureD3D12.cpp", + "d3d12/TextureD3D12.h", + "d3d12/UtilsD3D12.cpp", + "d3d12/UtilsD3D12.h", + "d3d12/d3d12_platform.h", + ] + } + + if (dawn_enable_metal) { + frameworks = [ + "Cocoa.framework", + "IOKit.framework", + "IOSurface.framework", + "QuartzCore.framework", + ] + sources += [ + "Surface_metal.mm", + "metal/BackendMTL.h", + "metal/BackendMTL.mm", + "metal/BindGroupLayoutMTL.h", + "metal/BindGroupLayoutMTL.mm", + "metal/BindGroupMTL.h", + "metal/BindGroupMTL.mm", + "metal/BufferMTL.h", + "metal/BufferMTL.mm", + "metal/CommandBufferMTL.h", + "metal/CommandBufferMTL.mm", + "metal/CommandRecordingContext.h", + "metal/CommandRecordingContext.mm", + "metal/ComputePipelineMTL.h", + "metal/ComputePipelineMTL.mm", + "metal/DeviceMTL.h", + "metal/DeviceMTL.mm", + "metal/Forward.h", + "metal/PipelineLayoutMTL.h", + "metal/PipelineLayoutMTL.mm", + "metal/QuerySetMTL.h", + "metal/QuerySetMTL.mm", + "metal/QueueMTL.h", + "metal/QueueMTL.mm", + "metal/RenderPipelineMTL.h", + "metal/RenderPipelineMTL.mm", + "metal/SamplerMTL.h", + "metal/SamplerMTL.mm", + "metal/ShaderModuleMTL.h", + "metal/ShaderModuleMTL.mm", + "metal/StagingBufferMTL.h", + "metal/StagingBufferMTL.mm", + "metal/SwapChainMTL.h", + "metal/SwapChainMTL.mm", + "metal/TextureMTL.h", + "metal/TextureMTL.mm", + "metal/UtilsMetal.h", + "metal/UtilsMetal.mm", + ] + } + + if (dawn_enable_null) { + sources += [ + "null/DeviceNull.cpp", + "null/DeviceNull.h", + ] + } + + if (dawn_enable_opengl || dawn_enable_vulkan) { + sources += [ + "SpirvValidation.cpp", + "SpirvValidation.h", + ] + } + + if (dawn_enable_opengl) { + public_deps += [ + ":opengl_loader_gen", + "${dawn_root}/third_party/khronos:khronos_platform", + ] + sources += get_target_outputs(":opengl_loader_gen") + sources += [ + "opengl/BackendGL.cpp", + "opengl/BackendGL.h", + "opengl/BindGroupGL.cpp", + "opengl/BindGroupGL.h", + "opengl/BindGroupLayoutGL.cpp", + "opengl/BindGroupLayoutGL.h", + "opengl/BufferGL.cpp", + "opengl/BufferGL.h", + "opengl/CommandBufferGL.cpp", + "opengl/CommandBufferGL.h", + "opengl/ComputePipelineGL.cpp", + "opengl/ComputePipelineGL.h", + "opengl/DeviceGL.cpp", + "opengl/DeviceGL.h", + "opengl/Forward.h", + "opengl/GLFormat.cpp", + "opengl/GLFormat.h", + "opengl/NativeSwapChainImplGL.cpp", + "opengl/NativeSwapChainImplGL.h", + "opengl/OpenGLFunctions.cpp", + "opengl/OpenGLFunctions.h", + "opengl/OpenGLVersion.cpp", + "opengl/OpenGLVersion.h", + "opengl/PersistentPipelineStateGL.cpp", + "opengl/PersistentPipelineStateGL.h", + "opengl/PipelineGL.cpp", + "opengl/PipelineGL.h", + "opengl/PipelineLayoutGL.cpp", + "opengl/PipelineLayoutGL.h", + "opengl/QuerySetGL.cpp", + "opengl/QuerySetGL.h", + "opengl/QueueGL.cpp", + "opengl/QueueGL.h", + "opengl/RenderPipelineGL.cpp", + "opengl/RenderPipelineGL.h", + "opengl/SamplerGL.cpp", + "opengl/SamplerGL.h", + "opengl/ShaderModuleGL.cpp", + "opengl/ShaderModuleGL.h", + "opengl/SwapChainGL.cpp", + "opengl/SwapChainGL.h", + "opengl/TextureGL.cpp", + "opengl/TextureGL.h", + "opengl/UtilsGL.cpp", + "opengl/UtilsGL.h", + "opengl/opengl_platform.h", + ] + } + + if (dawn_enable_vulkan) { + public_deps += [ "${dawn_vulkan_headers_dir}:vulkan_headers" ] + sources += [ + "vulkan/AdapterVk.cpp", + "vulkan/AdapterVk.h", + "vulkan/BackendVk.cpp", + "vulkan/BackendVk.h", + "vulkan/BindGroupLayoutVk.cpp", + "vulkan/BindGroupLayoutVk.h", + "vulkan/BindGroupVk.cpp", + "vulkan/BindGroupVk.h", + "vulkan/BufferVk.cpp", + "vulkan/BufferVk.h", + "vulkan/CommandBufferVk.cpp", + "vulkan/CommandBufferVk.h", + "vulkan/CommandRecordingContext.h", + "vulkan/ComputePipelineVk.cpp", + "vulkan/ComputePipelineVk.h", + "vulkan/DescriptorSetAllocation.h", + "vulkan/DescriptorSetAllocator.cpp", + "vulkan/DescriptorSetAllocator.h", + "vulkan/DeviceVk.cpp", + "vulkan/DeviceVk.h", + "vulkan/ExternalHandle.h", + "vulkan/FencedDeleter.cpp", + "vulkan/FencedDeleter.h", + "vulkan/Forward.h", + "vulkan/NativeSwapChainImplVk.cpp", + "vulkan/NativeSwapChainImplVk.h", + "vulkan/PipelineLayoutVk.cpp", + "vulkan/PipelineLayoutVk.h", + "vulkan/QuerySetVk.cpp", + "vulkan/QuerySetVk.h", + "vulkan/QueueVk.cpp", + "vulkan/QueueVk.h", + "vulkan/RenderPassCache.cpp", + "vulkan/RenderPassCache.h", + "vulkan/RenderPipelineVk.cpp", + "vulkan/RenderPipelineVk.h", + "vulkan/ResourceHeapVk.cpp", + "vulkan/ResourceHeapVk.h", + "vulkan/ResourceMemoryAllocatorVk.cpp", + "vulkan/ResourceMemoryAllocatorVk.h", + "vulkan/SamplerVk.cpp", + "vulkan/SamplerVk.h", + "vulkan/ShaderModuleVk.cpp", + "vulkan/ShaderModuleVk.h", + "vulkan/StagingBufferVk.cpp", + "vulkan/StagingBufferVk.h", + "vulkan/SwapChainVk.cpp", + "vulkan/SwapChainVk.h", + "vulkan/TextureVk.cpp", + "vulkan/TextureVk.h", + "vulkan/UtilsVulkan.cpp", + "vulkan/UtilsVulkan.h", + "vulkan/VulkanError.cpp", + "vulkan/VulkanError.h", + "vulkan/VulkanExtensions.cpp", + "vulkan/VulkanExtensions.h", + "vulkan/VulkanFunctions.cpp", + "vulkan/VulkanFunctions.h", + "vulkan/VulkanInfo.cpp", + "vulkan/VulkanInfo.h", + "vulkan/external_memory/MemoryService.h", + "vulkan/external_semaphore/SemaphoreService.h", + ] + + if (is_chromeos) { + sources += [ + "vulkan/external_memory/MemoryServiceDmaBuf.cpp", + "vulkan/external_semaphore/SemaphoreServiceFD.cpp", + ] + defines += [ "DAWN_USE_SYNC_FDS" ] + } else if (is_linux) { + sources += [ + "vulkan/external_memory/MemoryServiceOpaqueFD.cpp", + "vulkan/external_semaphore/SemaphoreServiceFD.cpp", + ] + } else if (is_fuchsia) { + sources += [ + "vulkan/external_memory/MemoryServiceZirconHandle.cpp", + "vulkan/external_semaphore/SemaphoreServiceZirconHandle.cpp", + ] + } else { + sources += [ + "vulkan/external_memory/MemoryServiceNull.cpp", + "vulkan/external_semaphore/SemaphoreServiceNull.cpp", + ] + } + if (build_with_chromium && is_fuchsia) { + # Necessary to ensure that the Vulkan libraries will be in the + # final Fuchsia package. + data_deps = [ + "//third_party/fuchsia-sdk:vulkan_base", + "//third_party/fuchsia-sdk:vulkan_validation", + + # NOTE: The line below is a work around for http://crbug.com/1001081 + "//third_party/fuchsia-sdk/sdk:trace_engine", + ] + } + if (dawn_is_winuwp) { + defines += [ "DAWN_IS_WINUWP" ] + } + if (enable_vulkan_validation_layers) { + defines += [ + "DAWN_ENABLE_VULKAN_VALIDATION_LAYERS", + "DAWN_VK_DATA_DIR=\"$vulkan_data_subdir\"", + ] + } + if (enable_vulkan_loader) { + data_deps += [ "${dawn_vulkan_loader_dir}:libvulkan" ] + } + if (use_swiftshader) { + data_deps += + [ "${dawn_swiftshader_dir}/src/Vulkan:swiftshader_libvulkan" ] + defines += [ "DAWN_ENABLE_SWIFTSHADER" ] + } + } + + if (use_angle) { + data_deps += [ + "${dawn_angle_dir}:libEGL", + "${dawn_angle_dir}:libGLESv2", + ] + } +} + +# The static and shared libraries for dawn_native. Most of the files are +# already compiled in dawn_native_sources, but we still need to compile +# files defining exported symbols. +dawn_component("native") { + DEFINE_PREFIX = "DAWN_NATIVE" + + #Make headers publically visible + public_deps = [ ":headers" ] + + deps = [ + ":sources", + "${dawn_root}/src/dawn/common", + ] + sources = [ "DawnNative.cpp" ] + configs = [ ":internal" ] + public_configs = [ + ":weak_framework", + ":vulkan_rpath", + ] + + if (dawn_enable_d3d12) { + sources += [ "d3d12/D3D12Backend.cpp" ] + } + if (dawn_enable_metal) { + sources += [ "metal/MetalBackend.mm" ] + } + if (dawn_enable_null) { + sources += [ "null/NullBackend.cpp" ] + } + if (dawn_enable_opengl) { + sources += [ "opengl/OpenGLBackend.cpp" ] + } + if (dawn_enable_vulkan) { + sources += [ "vulkan/VulkanBackend.cpp" ] + + if (enable_vulkan_validation_layers) { + data_deps = + [ "${dawn_vulkan_validation_layers_dir}:vulkan_validation_layers" ] + if (!is_android) { + data_deps += + [ "${dawn_vulkan_validation_layers_dir}:vulkan_gen_json_files" ] + } + } + } +} + +dawn_json_generator("webgpu_dawn_native_proc_gen") { + target = "webgpu_dawn_native_proc" + outputs = [ "src/dawn/native/webgpu_dawn_native_proc.cpp" ] +} + +dawn_component("webgpu_dawn") { + # For a single library - build `webgpu_dawn_shared` with GN args: + # dawn_complete_static_libs = true - to package a single lib + # + # is_debug = false + # - setting this to true makes library over 50Mb + # + # use_custom_libcxx = false + # - Otherwise, libc++ symbols may conflict if the + # library is used outside of Chromium. + # + # dawn_use_swiftshader = false + # angle_enable_swiftshader = false + # - SwiftShader can't be built without use_custom_libcxx. + # It should be built separately. + DEFINE_PREFIX = "WGPU" + + sources = get_target_outputs(":webgpu_dawn_native_proc_gen") + deps = [ + ":static", + ":webgpu_dawn_native_proc_gen", + ] +}
diff --git a/src/dawn/native/BackendConnection.cpp b/src/dawn/native/BackendConnection.cpp new file mode 100644 index 0000000..abcc271 --- /dev/null +++ b/src/dawn/native/BackendConnection.cpp
@@ -0,0 +1,36 @@ +// Copyright 2018 The Dawn Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include "dawn/native/BackendConnection.h" + +namespace dawn::native { + + BackendConnection::BackendConnection(InstanceBase* instance, wgpu::BackendType type) + : mInstance(instance), mType(type) { + } + + wgpu::BackendType BackendConnection::GetType() const { + return mType; + } + + InstanceBase* BackendConnection::GetInstance() const { + return mInstance; + } + + ResultOrError<std::vector<Ref<AdapterBase>>> BackendConnection::DiscoverAdapters( + const AdapterDiscoveryOptionsBase* options) { + return DAWN_FORMAT_VALIDATION_ERROR("DiscoverAdapters not implemented for this backend."); + } + +} // namespace dawn::native
diff --git a/src/dawn/native/BackendConnection.h b/src/dawn/native/BackendConnection.h new file mode 100644 index 0000000..2879fad --- /dev/null +++ b/src/dawn/native/BackendConnection.h
@@ -0,0 +1,50 @@ +// Copyright 2018 The Dawn Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#ifndef DAWNNATIVE_BACKENDCONNECTION_H_ +#define DAWNNATIVE_BACKENDCONNECTION_H_ + +#include "dawn/native/Adapter.h" +#include "dawn/native/DawnNative.h" + +#include <memory> + +namespace dawn::native { + + // An common interface for all backends. Mostly used to create adapters for a particular + // backend. + class BackendConnection { + public: + BackendConnection(InstanceBase* instance, wgpu::BackendType type); + virtual ~BackendConnection() = default; + + wgpu::BackendType GetType() const; + InstanceBase* GetInstance() const; + + // Returns all the adapters for the system that can be created by the backend, without extra + // options (such as debug adapters, custom driver libraries, etc.) + virtual std::vector<Ref<AdapterBase>> DiscoverDefaultAdapters() = 0; + + // Returns new adapters created with the backend-specific options. + virtual ResultOrError<std::vector<Ref<AdapterBase>>> DiscoverAdapters( + const AdapterDiscoveryOptionsBase* options); + + private: + InstanceBase* mInstance = nullptr; + wgpu::BackendType mType; + }; + +} // namespace dawn::native + +#endif // DAWNNATIVE_BACKENDCONNECTION_H_
diff --git a/src/dawn/native/BindGroup.cpp b/src/dawn/native/BindGroup.cpp new file mode 100644 index 0000000..503e613 --- /dev/null +++ b/src/dawn/native/BindGroup.cpp
@@ -0,0 +1,545 @@ +// Copyright 2017 The Dawn Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include "dawn/native/BindGroup.h" + +#include "dawn/common/Assert.h" +#include "dawn/common/Math.h" +#include "dawn/common/ityp_bitset.h" +#include "dawn/native/BindGroupLayout.h" +#include "dawn/native/Buffer.h" +#include "dawn/native/ChainUtils_autogen.h" +#include "dawn/native/Device.h" +#include "dawn/native/ExternalTexture.h" +#include "dawn/native/ObjectBase.h" +#include "dawn/native/ObjectType_autogen.h" +#include "dawn/native/Sampler.h" +#include "dawn/native/Texture.h" + +namespace dawn::native { + + namespace { + + // Helper functions to perform binding-type specific validation + + MaybeError ValidateBufferBinding(const DeviceBase* device, + const BindGroupEntry& entry, + const BindingInfo& bindingInfo) { + DAWN_INVALID_IF(entry.buffer == nullptr, "Binding entry buffer not set."); + + DAWN_INVALID_IF(entry.sampler != nullptr || entry.textureView != nullptr, + "Expected only buffer to be set for binding entry."); + + DAWN_INVALID_IF(entry.nextInChain != nullptr, "nextInChain must be nullptr."); + + DAWN_TRY(device->ValidateObject(entry.buffer)); + + ASSERT(bindingInfo.bindingType == BindingInfoType::Buffer); + + wgpu::BufferUsage requiredUsage; + uint64_t maxBindingSize; + uint64_t requiredBindingAlignment; + switch (bindingInfo.buffer.type) { + case wgpu::BufferBindingType::Uniform: + requiredUsage = wgpu::BufferUsage::Uniform; + maxBindingSize = device->GetLimits().v1.maxUniformBufferBindingSize; + requiredBindingAlignment = + device->GetLimits().v1.minUniformBufferOffsetAlignment; + break; + case wgpu::BufferBindingType::Storage: + case wgpu::BufferBindingType::ReadOnlyStorage: + requiredUsage = wgpu::BufferUsage::Storage; + maxBindingSize = device->GetLimits().v1.maxStorageBufferBindingSize; + requiredBindingAlignment = + device->GetLimits().v1.minStorageBufferOffsetAlignment; + break; + case kInternalStorageBufferBinding: + requiredUsage = kInternalStorageBuffer; + maxBindingSize = device->GetLimits().v1.maxStorageBufferBindingSize; + requiredBindingAlignment = + device->GetLimits().v1.minStorageBufferOffsetAlignment; + break; + case wgpu::BufferBindingType::Undefined: + UNREACHABLE(); + } + + uint64_t bufferSize = entry.buffer->GetSize(); + + // Handle wgpu::WholeSize, avoiding overflows. + DAWN_INVALID_IF(entry.offset > bufferSize, + "Binding offset (%u) is larger than the size (%u) of %s.", entry.offset, + bufferSize, entry.buffer); + + uint64_t bindingSize = + (entry.size == wgpu::kWholeSize) ? bufferSize - entry.offset : entry.size; + + DAWN_INVALID_IF(bindingSize > bufferSize, + "Binding size (%u) is larger than the size (%u) of %s.", bindingSize, + bufferSize, entry.buffer); + + DAWN_INVALID_IF(bindingSize == 0, "Binding size is zero"); + + // Note that no overflow can happen because we already checked that + // bufferSize >= bindingSize + DAWN_INVALID_IF( + entry.offset > bufferSize - bindingSize, + "Binding range (offset: %u, size: %u) doesn't fit in the size (%u) of %s.", + entry.offset, bufferSize, bindingSize, entry.buffer); + + DAWN_INVALID_IF(!IsAligned(entry.offset, requiredBindingAlignment), + "Offset (%u) does not satisfy the minimum %s alignment (%u).", + entry.offset, bindingInfo.buffer.type, requiredBindingAlignment); + + DAWN_INVALID_IF(!(entry.buffer->GetUsage() & requiredUsage), + "Binding usage (%s) of %s doesn't match expected usage (%s).", + entry.buffer->GetUsage(), entry.buffer, requiredUsage); + + DAWN_INVALID_IF(bindingSize < bindingInfo.buffer.minBindingSize, + "Binding size (%u) is smaller than the minimum binding size (%u).", + bindingSize, bindingInfo.buffer.minBindingSize); + + DAWN_INVALID_IF(bindingSize > maxBindingSize, + "Binding size (%u) is larger than the maximum binding size (%u).", + bindingSize, maxBindingSize); + + return {}; + } + + MaybeError ValidateTextureBinding(DeviceBase* device, + const BindGroupEntry& entry, + const BindingInfo& bindingInfo) { + DAWN_INVALID_IF(entry.textureView == nullptr, "Binding entry textureView not set."); + + DAWN_INVALID_IF(entry.sampler != nullptr || entry.buffer != nullptr, + "Expected only textureView to be set for binding entry."); + + DAWN_INVALID_IF(entry.nextInChain != nullptr, "nextInChain must be nullptr."); + + DAWN_TRY(device->ValidateObject(entry.textureView)); + + TextureViewBase* view = entry.textureView; + + Aspect aspect = view->GetAspects(); + DAWN_INVALID_IF(!HasOneBit(aspect), "Multiple aspects (%s) selected in %s.", aspect, + view); + + TextureBase* texture = view->GetTexture(); + switch (bindingInfo.bindingType) { + case BindingInfoType::Texture: { + SampleTypeBit supportedTypes = + texture->GetFormat().GetAspectInfo(aspect).supportedSampleTypes; + SampleTypeBit requiredType = + SampleTypeToSampleTypeBit(bindingInfo.texture.sampleType); + + DAWN_INVALID_IF( + !(texture->GetUsage() & wgpu::TextureUsage::TextureBinding), + "Usage (%s) of %s doesn't include TextureUsage::TextureBinding.", + texture->GetUsage(), texture); + + DAWN_INVALID_IF( + texture->IsMultisampledTexture() != bindingInfo.texture.multisampled, + "Sample count (%u) of %s doesn't match expectation (multisampled: %d).", + texture->GetSampleCount(), texture, bindingInfo.texture.multisampled); + + DAWN_INVALID_IF( + (supportedTypes & requiredType) == 0, + "None of the supported sample types (%s) of %s match the expected sample " + "types (%s).", + supportedTypes, texture, requiredType); + + DAWN_INVALID_IF( + entry.textureView->GetDimension() != bindingInfo.texture.viewDimension, + "Dimension (%s) of %s doesn't match the expected dimension (%s).", + entry.textureView->GetDimension(), entry.textureView, + bindingInfo.texture.viewDimension); + break; + } + case BindingInfoType::StorageTexture: { + DAWN_INVALID_IF( + !(texture->GetUsage() & wgpu::TextureUsage::StorageBinding), + "Usage (%s) of %s doesn't include TextureUsage::StorageBinding.", + texture->GetUsage(), texture); + + ASSERT(!texture->IsMultisampledTexture()); + + DAWN_INVALID_IF( + texture->GetFormat().format != bindingInfo.storageTexture.format, + "Format (%s) of %s expected to be (%s).", texture->GetFormat().format, + texture, bindingInfo.storageTexture.format); + + DAWN_INVALID_IF( + entry.textureView->GetDimension() != + bindingInfo.storageTexture.viewDimension, + "Dimension (%s) of %s doesn't match the expected dimension (%s).", + entry.textureView->GetDimension(), entry.textureView, + bindingInfo.storageTexture.viewDimension); + + DAWN_INVALID_IF(entry.textureView->GetLevelCount() != 1, + "mipLevelCount (%u) of %s expected to be 1.", + entry.textureView->GetLevelCount(), entry.textureView); + break; + } + default: + UNREACHABLE(); + break; + } + + return {}; + } + + MaybeError ValidateSamplerBinding(const DeviceBase* device, + const BindGroupEntry& entry, + const BindingInfo& bindingInfo) { + DAWN_INVALID_IF(entry.sampler == nullptr, "Binding entry sampler not set."); + + DAWN_INVALID_IF(entry.textureView != nullptr || entry.buffer != nullptr, + "Expected only sampler to be set for binding entry."); + + DAWN_INVALID_IF(entry.nextInChain != nullptr, "nextInChain must be nullptr."); + + DAWN_TRY(device->ValidateObject(entry.sampler)); + + ASSERT(bindingInfo.bindingType == BindingInfoType::Sampler); + + switch (bindingInfo.sampler.type) { + case wgpu::SamplerBindingType::NonFiltering: + DAWN_INVALID_IF( + entry.sampler->IsFiltering(), + "Filtering sampler %s is incompatible with non-filtering sampler " + "binding.", + entry.sampler); + [[fallthrough]]; + case wgpu::SamplerBindingType::Filtering: + DAWN_INVALID_IF( + entry.sampler->IsComparison(), + "Comparison sampler %s is incompatible with non-comparison sampler " + "binding.", + entry.sampler); + break; + case wgpu::SamplerBindingType::Comparison: + DAWN_INVALID_IF( + !entry.sampler->IsComparison(), + "Non-comparison sampler %s is imcompatible with comparison sampler " + "binding.", + entry.sampler); + break; + default: + UNREACHABLE(); + break; + } + + return {}; + } + + MaybeError ValidateExternalTextureBinding( + const DeviceBase* device, + const BindGroupEntry& entry, + const ExternalTextureBindingEntry* externalTextureBindingEntry, + const ExternalTextureBindingExpansionMap& expansions) { + DAWN_INVALID_IF(externalTextureBindingEntry == nullptr, + "Binding entry external texture not set."); + + DAWN_INVALID_IF( + entry.sampler != nullptr || entry.textureView != nullptr || entry.buffer != nullptr, + "Expected only external texture to be set for binding entry."); + + DAWN_INVALID_IF( + expansions.find(BindingNumber(entry.binding)) == expansions.end(), + "External texture binding entry %u is not present in the bind group layout.", + entry.binding); + + DAWN_TRY(ValidateSingleSType(externalTextureBindingEntry->nextInChain, + wgpu::SType::ExternalTextureBindingEntry)); + + DAWN_TRY(device->ValidateObject(externalTextureBindingEntry->externalTexture)); + + return {}; + } + + } // anonymous namespace + + MaybeError ValidateBindGroupDescriptor(DeviceBase* device, + const BindGroupDescriptor* descriptor) { + DAWN_INVALID_IF(descriptor->nextInChain != nullptr, "nextInChain must be nullptr."); + + DAWN_TRY(device->ValidateObject(descriptor->layout)); + + DAWN_INVALID_IF( + descriptor->entryCount != descriptor->layout->GetUnexpandedBindingCount(), + "Number of entries (%u) did not match the number of entries (%u) specified in %s." + "\nExpected layout: %s", + descriptor->entryCount, static_cast<uint32_t>(descriptor->layout->GetBindingCount()), + descriptor->layout, descriptor->layout->EntriesToString()); + + const BindGroupLayoutBase::BindingMap& bindingMap = descriptor->layout->GetBindingMap(); + ASSERT(bindingMap.size() <= kMaxBindingsPerPipelineLayout); + + ityp::bitset<BindingIndex, kMaxBindingsPerPipelineLayout> bindingsSet; + for (uint32_t i = 0; i < descriptor->entryCount; ++i) { + const BindGroupEntry& entry = descriptor->entries[i]; + + const auto& it = bindingMap.find(BindingNumber(entry.binding)); + DAWN_INVALID_IF(it == bindingMap.end(), + "In entries[%u], binding index %u not present in the bind group layout." + "\nExpected layout: %s", + i, entry.binding, descriptor->layout->EntriesToString()); + + BindingIndex bindingIndex = it->second; + ASSERT(bindingIndex < descriptor->layout->GetBindingCount()); + + DAWN_INVALID_IF(bindingsSet[bindingIndex], + "In entries[%u], binding index %u already used by a previous entry", i, + entry.binding); + + bindingsSet.set(bindingIndex); + + // Below this block we validate entries based on the bind group layout, in which + // external textures have been expanded into their underlying contents. For this reason + // we must identify external texture binding entries by checking the bind group entry + // itself. + // TODO:(dawn:1293): Store external textures in + // BindGroupLayoutBase::BindingDataPointers::bindings so checking external textures can + // be moved in the switch below. + const ExternalTextureBindingEntry* externalTextureBindingEntry = nullptr; + FindInChain(entry.nextInChain, &externalTextureBindingEntry); + if (externalTextureBindingEntry != nullptr) { + DAWN_TRY(ValidateExternalTextureBinding( + device, entry, externalTextureBindingEntry, + descriptor->layout->GetExternalTextureBindingExpansionMap())); + continue; + } + + const BindingInfo& bindingInfo = descriptor->layout->GetBindingInfo(bindingIndex); + + // Perform binding-type specific validation. + switch (bindingInfo.bindingType) { + case BindingInfoType::Buffer: + DAWN_TRY_CONTEXT(ValidateBufferBinding(device, entry, bindingInfo), + "validating entries[%u] as a Buffer." + "\nExpected entry layout: %s", + i, bindingInfo); + break; + case BindingInfoType::Texture: + case BindingInfoType::StorageTexture: + DAWN_TRY_CONTEXT(ValidateTextureBinding(device, entry, bindingInfo), + "validating entries[%u] as a Texture." + "\nExpected entry layout: %s", + i, bindingInfo); + break; + case BindingInfoType::Sampler: + DAWN_TRY_CONTEXT(ValidateSamplerBinding(device, entry, bindingInfo), + "validating entries[%u] as a Sampler." + "\nExpected entry layout: %s", + i, bindingInfo); + break; + case BindingInfoType::ExternalTexture: + UNREACHABLE(); + break; + } + } + + // This should always be true because + // - numBindings has to match between the bind group and its layout. + // - Each binding must be set at most once + // + // We don't validate the equality because it wouldn't be possible to cover it with a test. + ASSERT(bindingsSet.count() == descriptor->layout->GetUnexpandedBindingCount()); + + return {}; + } // anonymous namespace + + // BindGroup + + BindGroupBase::BindGroupBase(DeviceBase* device, + const BindGroupDescriptor* descriptor, + void* bindingDataStart) + : ApiObjectBase(device, descriptor->label), + mLayout(descriptor->layout), + mBindingData(mLayout->ComputeBindingDataPointers(bindingDataStart)) { + for (BindingIndex i{0}; i < mLayout->GetBindingCount(); ++i) { + // TODO(enga): Shouldn't be needed when bindings are tightly packed. + // This is to fill Ref<ObjectBase> holes with nullptrs. + new (&mBindingData.bindings[i]) Ref<ObjectBase>(); + } + + for (uint32_t i = 0; i < descriptor->entryCount; ++i) { + const BindGroupEntry& entry = descriptor->entries[i]; + + BindingIndex bindingIndex = + descriptor->layout->GetBindingIndex(BindingNumber(entry.binding)); + ASSERT(bindingIndex < mLayout->GetBindingCount()); + + // Only a single binding type should be set, so once we found it we can skip to the + // next loop iteration. + + if (entry.buffer != nullptr) { + ASSERT(mBindingData.bindings[bindingIndex] == nullptr); + mBindingData.bindings[bindingIndex] = entry.buffer; + mBindingData.bufferData[bindingIndex].offset = entry.offset; + uint64_t bufferSize = (entry.size == wgpu::kWholeSize) + ? entry.buffer->GetSize() - entry.offset + : entry.size; + mBindingData.bufferData[bindingIndex].size = bufferSize; + continue; + } + + if (entry.textureView != nullptr) { + ASSERT(mBindingData.bindings[bindingIndex] == nullptr); + mBindingData.bindings[bindingIndex] = entry.textureView; + continue; + } + + if (entry.sampler != nullptr) { + ASSERT(mBindingData.bindings[bindingIndex] == nullptr); + mBindingData.bindings[bindingIndex] = entry.sampler; + continue; + } + + // Here we unpack external texture bindings into multiple additional bindings for the + // external texture's contents. New binding locations previously determined in the bind + // group layout are created in this bind group and filled with the external texture's + // underlying resources. + const ExternalTextureBindingEntry* externalTextureBindingEntry = nullptr; + FindInChain(entry.nextInChain, &externalTextureBindingEntry); + if (externalTextureBindingEntry != nullptr) { + mBoundExternalTextures.push_back(externalTextureBindingEntry->externalTexture); + + ExternalTextureBindingExpansionMap expansions = + mLayout->GetExternalTextureBindingExpansionMap(); + ExternalTextureBindingExpansionMap::iterator it = + expansions.find(BindingNumber(entry.binding)); + + ASSERT(it != expansions.end()); + + BindingIndex plane0BindingIndex = + descriptor->layout->GetBindingIndex(it->second.plane0); + BindingIndex plane1BindingIndex = + descriptor->layout->GetBindingIndex(it->second.plane1); + BindingIndex paramsBindingIndex = + descriptor->layout->GetBindingIndex(it->second.params); + + ASSERT(mBindingData.bindings[plane0BindingIndex] == nullptr); + + mBindingData.bindings[plane0BindingIndex] = + externalTextureBindingEntry->externalTexture->GetTextureViews()[0]; + + ASSERT(mBindingData.bindings[plane1BindingIndex] == nullptr); + mBindingData.bindings[plane1BindingIndex] = + externalTextureBindingEntry->externalTexture->GetTextureViews()[1]; + + ASSERT(mBindingData.bindings[paramsBindingIndex] == nullptr); + mBindingData.bindings[paramsBindingIndex] = + externalTextureBindingEntry->externalTexture->GetParamsBuffer(); + mBindingData.bufferData[paramsBindingIndex].offset = 0; + mBindingData.bufferData[paramsBindingIndex].size = + sizeof(dawn_native::ExternalTextureParams); + + continue; + } + } + + uint32_t packedIdx = 0; + for (BindingIndex bindingIndex{0}; bindingIndex < descriptor->layout->GetBufferCount(); + ++bindingIndex) { + if (descriptor->layout->GetBindingInfo(bindingIndex).buffer.minBindingSize == 0) { + mBindingData.unverifiedBufferSizes[packedIdx] = + mBindingData.bufferData[bindingIndex].size; + ++packedIdx; + } + } + + TrackInDevice(); + } + + BindGroupBase::BindGroupBase(DeviceBase* device) : ApiObjectBase(device, kLabelNotImplemented) { + TrackInDevice(); + } + + BindGroupBase::~BindGroupBase() = default; + + void BindGroupBase::DestroyImpl() { + if (mLayout != nullptr) { + ASSERT(!IsError()); + for (BindingIndex i{0}; i < mLayout->GetBindingCount(); ++i) { + mBindingData.bindings[i].~Ref<ObjectBase>(); + } + } + } + + void BindGroupBase::DeleteThis() { + // Add another ref to the layout so that if this is the last ref, the layout + // is destroyed after the bind group. The bind group is slab-allocated inside + // memory owned by the layout (except for the null backend). + Ref<BindGroupLayoutBase> layout = mLayout; + ApiObjectBase::DeleteThis(); + } + + BindGroupBase::BindGroupBase(DeviceBase* device, ObjectBase::ErrorTag tag) + : ApiObjectBase(device, tag), mBindingData() { + } + + // static + BindGroupBase* BindGroupBase::MakeError(DeviceBase* device) { + return new BindGroupBase(device, ObjectBase::kError); + } + + ObjectType BindGroupBase::GetType() const { + return ObjectType::BindGroup; + } + + BindGroupLayoutBase* BindGroupBase::GetLayout() { + ASSERT(!IsError()); + return mLayout.Get(); + } + + const BindGroupLayoutBase* BindGroupBase::GetLayout() const { + ASSERT(!IsError()); + return mLayout.Get(); + } + + const ityp::span<uint32_t, uint64_t>& BindGroupBase::GetUnverifiedBufferSizes() const { + ASSERT(!IsError()); + return mBindingData.unverifiedBufferSizes; + } + + BufferBinding BindGroupBase::GetBindingAsBufferBinding(BindingIndex bindingIndex) { + ASSERT(!IsError()); + ASSERT(bindingIndex < mLayout->GetBindingCount()); + ASSERT(mLayout->GetBindingInfo(bindingIndex).bindingType == BindingInfoType::Buffer); + BufferBase* buffer = static_cast<BufferBase*>(mBindingData.bindings[bindingIndex].Get()); + return {buffer, mBindingData.bufferData[bindingIndex].offset, + mBindingData.bufferData[bindingIndex].size}; + } + + SamplerBase* BindGroupBase::GetBindingAsSampler(BindingIndex bindingIndex) const { + ASSERT(!IsError()); + ASSERT(bindingIndex < mLayout->GetBindingCount()); + ASSERT(mLayout->GetBindingInfo(bindingIndex).bindingType == BindingInfoType::Sampler); + return static_cast<SamplerBase*>(mBindingData.bindings[bindingIndex].Get()); + } + + TextureViewBase* BindGroupBase::GetBindingAsTextureView(BindingIndex bindingIndex) { + ASSERT(!IsError()); + ASSERT(bindingIndex < mLayout->GetBindingCount()); + ASSERT(mLayout->GetBindingInfo(bindingIndex).bindingType == BindingInfoType::Texture || + mLayout->GetBindingInfo(bindingIndex).bindingType == + BindingInfoType::StorageTexture); + return static_cast<TextureViewBase*>(mBindingData.bindings[bindingIndex].Get()); + } + + const std::vector<Ref<ExternalTextureBase>>& BindGroupBase::GetBoundExternalTextures() const { + return mBoundExternalTextures; + } + +} // namespace dawn::native
diff --git a/src/dawn/native/BindGroup.h b/src/dawn/native/BindGroup.h new file mode 100644 index 0000000..7ba883a --- /dev/null +++ b/src/dawn/native/BindGroup.h
@@ -0,0 +1,96 @@ +// Copyright 2017 The Dawn Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#ifndef DAWNNATIVE_BINDGROUP_H_ +#define DAWNNATIVE_BINDGROUP_H_ + +#include "dawn/common/Constants.h" +#include "dawn/common/Math.h" +#include "dawn/native/BindGroupLayout.h" +#include "dawn/native/Error.h" +#include "dawn/native/Forward.h" +#include "dawn/native/ObjectBase.h" + +#include "dawn/native/dawn_platform.h" + +#include <array> + +namespace dawn::native { + + class DeviceBase; + + MaybeError ValidateBindGroupDescriptor(DeviceBase* device, + const BindGroupDescriptor* descriptor); + + struct BufferBinding { + BufferBase* buffer; + uint64_t offset; + uint64_t size; + }; + + class BindGroupBase : public ApiObjectBase { + public: + static BindGroupBase* MakeError(DeviceBase* device); + + ObjectType GetType() const override; + + BindGroupLayoutBase* GetLayout(); + const BindGroupLayoutBase* GetLayout() const; + BufferBinding GetBindingAsBufferBinding(BindingIndex bindingIndex); + SamplerBase* GetBindingAsSampler(BindingIndex bindingIndex) const; + TextureViewBase* GetBindingAsTextureView(BindingIndex bindingIndex); + const ityp::span<uint32_t, uint64_t>& GetUnverifiedBufferSizes() const; + const std::vector<Ref<ExternalTextureBase>>& GetBoundExternalTextures() const; + + protected: + // To save memory, the size of a bind group is dynamically determined and the bind group is + // placement-allocated into memory big enough to hold the bind group with its + // dynamically-sized bindings after it. The pointer of the memory of the beginning of the + // binding data should be passed as |bindingDataStart|. + BindGroupBase(DeviceBase* device, + const BindGroupDescriptor* descriptor, + void* bindingDataStart); + + // Helper to instantiate BindGroupBase. We pass in |derived| because BindGroupBase may not + // be first in the allocation. The binding data is stored after the Derived class. + template <typename Derived> + BindGroupBase(Derived* derived, DeviceBase* device, const BindGroupDescriptor* descriptor) + : BindGroupBase(device, + descriptor, + AlignPtr(reinterpret_cast<char*>(derived) + sizeof(Derived), + descriptor->layout->GetBindingDataAlignment())) { + static_assert(std::is_base_of<BindGroupBase, Derived>::value); + } + + // Constructor used only for mocking and testing. + BindGroupBase(DeviceBase* device); + void DestroyImpl() override; + + ~BindGroupBase() override; + + private: + BindGroupBase(DeviceBase* device, ObjectBase::ErrorTag tag); + void DeleteThis() override; + + Ref<BindGroupLayoutBase> mLayout; + BindGroupLayoutBase::BindingDataPointers mBindingData; + + // TODO:(dawn:1293): Store external textures in + // BindGroupLayoutBase::BindingDataPointers::bindings + std::vector<Ref<ExternalTextureBase>> mBoundExternalTextures; + }; + +} // namespace dawn::native + +#endif // DAWNNATIVE_BINDGROUP_H_
diff --git a/src/dawn/native/BindGroupLayout.cpp b/src/dawn/native/BindGroupLayout.cpp new file mode 100644 index 0000000..201aecc --- /dev/null +++ b/src/dawn/native/BindGroupLayout.cpp
@@ -0,0 +1,676 @@ +// Copyright 2017 The Dawn Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include "dawn/native/BindGroupLayout.h" + +#include "dawn/common/BitSetIterator.h" + +#include "dawn/native/ChainUtils_autogen.h" +#include "dawn/native/Device.h" +#include "dawn/native/ObjectBase.h" +#include "dawn/native/ObjectContentHasher.h" +#include "dawn/native/ObjectType_autogen.h" +#include "dawn/native/PerStage.h" +#include "dawn/native/ValidationUtils_autogen.h" + +#include <algorithm> +#include <functional> +#include <set> + +namespace dawn::native { + + namespace { + MaybeError ValidateStorageTextureFormat(DeviceBase* device, + wgpu::TextureFormat storageTextureFormat) { + const Format* format = nullptr; + DAWN_TRY_ASSIGN(format, device->GetInternalFormat(storageTextureFormat)); + + ASSERT(format != nullptr); + DAWN_INVALID_IF(!format->supportsStorageUsage, + "Texture format (%s) does not support storage textures.", + storageTextureFormat); + + return {}; + } + + MaybeError ValidateStorageTextureViewDimension(wgpu::TextureViewDimension dimension) { + switch (dimension) { + case wgpu::TextureViewDimension::Cube: + case wgpu::TextureViewDimension::CubeArray: + return DAWN_FORMAT_VALIDATION_ERROR( + "%s texture views cannot be used as storage textures.", dimension); + + case wgpu::TextureViewDimension::e1D: + case wgpu::TextureViewDimension::e2D: + case wgpu::TextureViewDimension::e2DArray: + case wgpu::TextureViewDimension::e3D: + return {}; + + case wgpu::TextureViewDimension::Undefined: + break; + } + UNREACHABLE(); + } + + MaybeError ValidateBindGroupLayoutEntry(DeviceBase* device, + const BindGroupLayoutEntry& entry, + bool allowInternalBinding) { + DAWN_TRY(ValidateShaderStage(entry.visibility)); + + int bindingMemberCount = 0; + BindingInfoType bindingType; + wgpu::ShaderStage allowedStages = kAllStages; + + if (entry.buffer.type != wgpu::BufferBindingType::Undefined) { + bindingMemberCount++; + bindingType = BindingInfoType::Buffer; + const BufferBindingLayout& buffer = entry.buffer; + + // The kInternalStorageBufferBinding is used internally and not a value + // in wgpu::BufferBindingType. + if (buffer.type == kInternalStorageBufferBinding) { + DAWN_INVALID_IF(!allowInternalBinding, "Internal binding types are disallowed"); + } else { + DAWN_TRY(ValidateBufferBindingType(buffer.type)); + } + + if (buffer.type == wgpu::BufferBindingType::Storage || + buffer.type == kInternalStorageBufferBinding) { + allowedStages &= ~wgpu::ShaderStage::Vertex; + } + } + + if (entry.sampler.type != wgpu::SamplerBindingType::Undefined) { + bindingMemberCount++; + bindingType = BindingInfoType::Sampler; + DAWN_TRY(ValidateSamplerBindingType(entry.sampler.type)); + } + + if (entry.texture.sampleType != wgpu::TextureSampleType::Undefined) { + bindingMemberCount++; + bindingType = BindingInfoType::Texture; + const TextureBindingLayout& texture = entry.texture; + DAWN_TRY(ValidateTextureSampleType(texture.sampleType)); + + // viewDimension defaults to 2D if left undefined, needs validation otherwise. + wgpu::TextureViewDimension viewDimension = wgpu::TextureViewDimension::e2D; + if (texture.viewDimension != wgpu::TextureViewDimension::Undefined) { + DAWN_TRY(ValidateTextureViewDimension(texture.viewDimension)); + viewDimension = texture.viewDimension; + } + + DAWN_INVALID_IF( + texture.multisampled && viewDimension != wgpu::TextureViewDimension::e2D, + "View dimension (%s) for a multisampled texture bindings was not %s.", + viewDimension, wgpu::TextureViewDimension::e2D); + } + + if (entry.storageTexture.access != wgpu::StorageTextureAccess::Undefined) { + bindingMemberCount++; + bindingType = BindingInfoType::StorageTexture; + const StorageTextureBindingLayout& storageTexture = entry.storageTexture; + DAWN_TRY(ValidateStorageTextureAccess(storageTexture.access)); + DAWN_TRY(ValidateStorageTextureFormat(device, storageTexture.format)); + + // viewDimension defaults to 2D if left undefined, needs validation otherwise. + if (storageTexture.viewDimension != wgpu::TextureViewDimension::Undefined) { + DAWN_TRY(ValidateTextureViewDimension(storageTexture.viewDimension)); + DAWN_TRY(ValidateStorageTextureViewDimension(storageTexture.viewDimension)); + } + + if (storageTexture.access == wgpu::StorageTextureAccess::WriteOnly) { + allowedStages &= ~wgpu::ShaderStage::Vertex; + } + } + + const ExternalTextureBindingLayout* externalTextureBindingLayout = nullptr; + FindInChain(entry.nextInChain, &externalTextureBindingLayout); + if (externalTextureBindingLayout != nullptr) { + bindingMemberCount++; + bindingType = BindingInfoType::ExternalTexture; + } + + DAWN_INVALID_IF(bindingMemberCount == 0, + "BindGroupLayoutEntry had none of buffer, sampler, texture, " + "storageTexture, or externalTexture set"); + + DAWN_INVALID_IF(bindingMemberCount != 1, + "BindGroupLayoutEntry had more than one of buffer, sampler, texture, " + "storageTexture, or externalTexture set"); + + DAWN_INVALID_IF( + !IsSubset(entry.visibility, allowedStages), + "%s bindings cannot be used with a visibility of %s. Only %s are allowed.", + bindingType, entry.visibility, allowedStages); + + return {}; + } + + BindGroupLayoutEntry CreateSampledTextureBindingForExternalTexture( + uint32_t binding, + wgpu::ShaderStage visibility) { + BindGroupLayoutEntry entry; + entry.binding = binding; + entry.visibility = visibility; + entry.texture.viewDimension = wgpu::TextureViewDimension::e2D; + entry.texture.multisampled = false; + entry.texture.sampleType = wgpu::TextureSampleType::Float; + return entry; + } + + BindGroupLayoutEntry CreateUniformBindingForExternalTexture(uint32_t binding, + wgpu::ShaderStage visibility) { + BindGroupLayoutEntry entry; + entry.binding = binding; + entry.visibility = visibility; + entry.buffer.hasDynamicOffset = false; + entry.buffer.type = wgpu::BufferBindingType::Uniform; + return entry; + } + + std::vector<BindGroupLayoutEntry> ExtractAndExpandBglEntries( + const BindGroupLayoutDescriptor* descriptor, + BindingCounts* bindingCounts, + ExternalTextureBindingExpansionMap* externalTextureBindingExpansions) { + std::vector<BindGroupLayoutEntry> expandedOutput; + + // When new bgl entries are created, we use binding numbers larger than + // kMaxBindingNumber to ensure there are no collisions. + uint32_t nextOpenBindingNumberForNewEntry = kMaxBindingNumber + 1; + for (uint32_t i = 0; i < descriptor->entryCount; i++) { + const BindGroupLayoutEntry& entry = descriptor->entries[i]; + const ExternalTextureBindingLayout* externalTextureBindingLayout = nullptr; + FindInChain(entry.nextInChain, &externalTextureBindingLayout); + // External textures are expanded from a texture_external into two sampled texture + // bindings and one uniform buffer binding. The original binding number is used + // for the first sampled texture. + if (externalTextureBindingLayout != nullptr) { + for (SingleShaderStage stage : IterateStages(entry.visibility)) { + // External textures are not fully implemented, which means that expanding + // the external texture at this time will not occupy the same number of + // binding slots as defined in the WebGPU specification. Here we prematurely + // increment the binding counts for an additional sampled textures and a + // sampler so that an external texture will occupy the correct number of + // slots for correct validation of shader binding limits. + // TODO:(dawn:1082): Consider removing this and instead making a change to + // the validation. + constexpr uint32_t kUnimplementedSampledTexturesPerExternalTexture = 2; + constexpr uint32_t kUnimplementedSamplersPerExternalTexture = 1; + bindingCounts->perStage[stage].sampledTextureCount += + kUnimplementedSampledTexturesPerExternalTexture; + bindingCounts->perStage[stage].samplerCount += + kUnimplementedSamplersPerExternalTexture; + } + + dawn_native::ExternalTextureBindingExpansion bindingExpansion; + + BindGroupLayoutEntry plane0Entry = + CreateSampledTextureBindingForExternalTexture(entry.binding, + entry.visibility); + bindingExpansion.plane0 = BindingNumber(plane0Entry.binding); + expandedOutput.push_back(plane0Entry); + + BindGroupLayoutEntry plane1Entry = + CreateSampledTextureBindingForExternalTexture( + nextOpenBindingNumberForNewEntry++, entry.visibility); + bindingExpansion.plane1 = BindingNumber(plane1Entry.binding); + expandedOutput.push_back(plane1Entry); + + BindGroupLayoutEntry paramsEntry = CreateUniformBindingForExternalTexture( + nextOpenBindingNumberForNewEntry++, entry.visibility); + bindingExpansion.params = BindingNumber(paramsEntry.binding); + expandedOutput.push_back(paramsEntry); + + externalTextureBindingExpansions->insert( + {BindingNumber(entry.binding), bindingExpansion}); + } else { + expandedOutput.push_back(entry); + } + } + + return expandedOutput; + } + } // anonymous namespace + + MaybeError ValidateBindGroupLayoutDescriptor(DeviceBase* device, + const BindGroupLayoutDescriptor* descriptor, + bool allowInternalBinding) { + DAWN_INVALID_IF(descriptor->nextInChain != nullptr, "nextInChain must be nullptr"); + + std::set<BindingNumber> bindingsSet; + BindingCounts bindingCounts = {}; + + for (uint32_t i = 0; i < descriptor->entryCount; ++i) { + const BindGroupLayoutEntry& entry = descriptor->entries[i]; + BindingNumber bindingNumber = BindingNumber(entry.binding); + + DAWN_INVALID_IF(bindingNumber > kMaxBindingNumberTyped, + "Binding number (%u) exceeds the maximum binding number (%u).", + uint32_t(bindingNumber), uint32_t(kMaxBindingNumberTyped)); + DAWN_INVALID_IF(bindingsSet.count(bindingNumber) != 0, + "On entries[%u]: binding index (%u) was specified by a previous entry.", + i, entry.binding); + + DAWN_TRY_CONTEXT(ValidateBindGroupLayoutEntry(device, entry, allowInternalBinding), + "validating entries[%u]", i); + + IncrementBindingCounts(&bindingCounts, entry); + + bindingsSet.insert(bindingNumber); + } + + DAWN_TRY_CONTEXT(ValidateBindingCounts(bindingCounts), "validating binding counts"); + + return {}; + } + + namespace { + + bool operator!=(const BindingInfo& a, const BindingInfo& b) { + if (a.visibility != b.visibility || a.bindingType != b.bindingType) { + return true; + } + + switch (a.bindingType) { + case BindingInfoType::Buffer: + return a.buffer.type != b.buffer.type || + a.buffer.hasDynamicOffset != b.buffer.hasDynamicOffset || + a.buffer.minBindingSize != b.buffer.minBindingSize; + case BindingInfoType::Sampler: + return a.sampler.type != b.sampler.type; + case BindingInfoType::Texture: + return a.texture.sampleType != b.texture.sampleType || + a.texture.viewDimension != b.texture.viewDimension || + a.texture.multisampled != b.texture.multisampled; + case BindingInfoType::StorageTexture: + return a.storageTexture.access != b.storageTexture.access || + a.storageTexture.viewDimension != b.storageTexture.viewDimension || + a.storageTexture.format != b.storageTexture.format; + case BindingInfoType::ExternalTexture: + return false; + } + UNREACHABLE(); + } + + bool IsBufferBinding(const BindGroupLayoutEntry& binding) { + return binding.buffer.type != wgpu::BufferBindingType::Undefined; + } + + bool BindingHasDynamicOffset(const BindGroupLayoutEntry& binding) { + if (binding.buffer.type != wgpu::BufferBindingType::Undefined) { + return binding.buffer.hasDynamicOffset; + } + return false; + } + + BindingInfo CreateBindGroupLayoutInfo(const BindGroupLayoutEntry& binding) { + BindingInfo bindingInfo; + bindingInfo.binding = BindingNumber(binding.binding); + bindingInfo.visibility = binding.visibility; + + if (binding.buffer.type != wgpu::BufferBindingType::Undefined) { + bindingInfo.bindingType = BindingInfoType::Buffer; + bindingInfo.buffer = binding.buffer; + } else if (binding.sampler.type != wgpu::SamplerBindingType::Undefined) { + bindingInfo.bindingType = BindingInfoType::Sampler; + bindingInfo.sampler = binding.sampler; + } else if (binding.texture.sampleType != wgpu::TextureSampleType::Undefined) { + bindingInfo.bindingType = BindingInfoType::Texture; + bindingInfo.texture = binding.texture; + + if (binding.texture.viewDimension == wgpu::TextureViewDimension::Undefined) { + bindingInfo.texture.viewDimension = wgpu::TextureViewDimension::e2D; + } + } else if (binding.storageTexture.access != wgpu::StorageTextureAccess::Undefined) { + bindingInfo.bindingType = BindingInfoType::StorageTexture; + bindingInfo.storageTexture = binding.storageTexture; + + if (binding.storageTexture.viewDimension == wgpu::TextureViewDimension::Undefined) { + bindingInfo.storageTexture.viewDimension = wgpu::TextureViewDimension::e2D; + } + } else { + const ExternalTextureBindingLayout* externalTextureBindingLayout = nullptr; + FindInChain(binding.nextInChain, &externalTextureBindingLayout); + if (externalTextureBindingLayout != nullptr) { + bindingInfo.bindingType = BindingInfoType::ExternalTexture; + } + } + + return bindingInfo; + } + + bool SortBindingsCompare(const BindGroupLayoutEntry& a, const BindGroupLayoutEntry& b) { + const bool aIsBuffer = IsBufferBinding(a); + const bool bIsBuffer = IsBufferBinding(b); + if (aIsBuffer != bIsBuffer) { + // Always place buffers first. + return aIsBuffer; + } + + if (aIsBuffer) { + bool aHasDynamicOffset = BindingHasDynamicOffset(a); + bool bHasDynamicOffset = BindingHasDynamicOffset(b); + ASSERT(bIsBuffer); + if (aHasDynamicOffset != bHasDynamicOffset) { + // Buffers with dynamic offsets should come before those without. + // This makes it easy to iterate over the dynamic buffer bindings + // [0, dynamicBufferCount) during validation. + return aHasDynamicOffset; + } + if (aHasDynamicOffset) { + ASSERT(bHasDynamicOffset); + ASSERT(a.binding != b.binding); + // Above, we ensured that dynamic buffers are first. Now, ensure that + // dynamic buffer bindings are in increasing order. This is because dynamic + // buffer offsets are applied in increasing order of binding number. + return a.binding < b.binding; + } + } + + // This applies some defaults and gives us a single value to check for the binding type. + BindingInfo aInfo = CreateBindGroupLayoutInfo(a); + BindingInfo bInfo = CreateBindGroupLayoutInfo(b); + + // Sort by type. + if (aInfo.bindingType != bInfo.bindingType) { + return aInfo.bindingType < bInfo.bindingType; + } + + if (a.visibility != b.visibility) { + return a.visibility < b.visibility; + } + + switch (aInfo.bindingType) { + case BindingInfoType::Buffer: + if (aInfo.buffer.minBindingSize != bInfo.buffer.minBindingSize) { + return aInfo.buffer.minBindingSize < bInfo.buffer.minBindingSize; + } + break; + case BindingInfoType::Sampler: + if (aInfo.sampler.type != bInfo.sampler.type) { + return aInfo.sampler.type < bInfo.sampler.type; + } + break; + case BindingInfoType::Texture: + if (aInfo.texture.multisampled != bInfo.texture.multisampled) { + return aInfo.texture.multisampled < bInfo.texture.multisampled; + } + if (aInfo.texture.viewDimension != bInfo.texture.viewDimension) { + return aInfo.texture.viewDimension < bInfo.texture.viewDimension; + } + if (aInfo.texture.sampleType != bInfo.texture.sampleType) { + return aInfo.texture.sampleType < bInfo.texture.sampleType; + } + break; + case BindingInfoType::StorageTexture: + if (aInfo.storageTexture.access != bInfo.storageTexture.access) { + return aInfo.storageTexture.access < bInfo.storageTexture.access; + } + if (aInfo.storageTexture.viewDimension != bInfo.storageTexture.viewDimension) { + return aInfo.storageTexture.viewDimension < + bInfo.storageTexture.viewDimension; + } + if (aInfo.storageTexture.format != bInfo.storageTexture.format) { + return aInfo.storageTexture.format < bInfo.storageTexture.format; + } + break; + case BindingInfoType::ExternalTexture: + break; + } + return a.binding < b.binding; + } + + // This is a utility function to help ASSERT that the BGL-binding comparator places buffers + // first. + bool CheckBufferBindingsFirst(ityp::span<BindingIndex, const BindingInfo> bindings) { + BindingIndex lastBufferIndex{0}; + BindingIndex firstNonBufferIndex = std::numeric_limits<BindingIndex>::max(); + for (BindingIndex i{0}; i < bindings.size(); ++i) { + if (bindings[i].bindingType == BindingInfoType::Buffer) { + lastBufferIndex = std::max(i, lastBufferIndex); + } else { + firstNonBufferIndex = std::min(i, firstNonBufferIndex); + } + } + + // If there are no buffers, then |lastBufferIndex| is initialized to 0 and + // |firstNonBufferIndex| gets set to 0. + return firstNonBufferIndex >= lastBufferIndex; + } + + } // namespace + + // BindGroupLayoutBase + + BindGroupLayoutBase::BindGroupLayoutBase(DeviceBase* device, + const BindGroupLayoutDescriptor* descriptor, + PipelineCompatibilityToken pipelineCompatibilityToken, + ApiObjectBase::UntrackedByDeviceTag tag) + : ApiObjectBase(device, descriptor->label), + mPipelineCompatibilityToken(pipelineCompatibilityToken), + mUnexpandedBindingCount(descriptor->entryCount) { + std::vector<BindGroupLayoutEntry> sortedBindings = ExtractAndExpandBglEntries( + descriptor, &mBindingCounts, &mExternalTextureBindingExpansionMap); + + std::sort(sortedBindings.begin(), sortedBindings.end(), SortBindingsCompare); + + for (uint32_t i = 0; i < sortedBindings.size(); ++i) { + const BindGroupLayoutEntry& binding = sortedBindings[static_cast<uint32_t>(i)]; + + mBindingInfo.push_back(CreateBindGroupLayoutInfo(binding)); + + if (IsBufferBinding(binding)) { + // Buffers must be contiguously packed at the start of the binding info. + ASSERT(GetBufferCount() == BindingIndex(i)); + } + IncrementBindingCounts(&mBindingCounts, binding); + + const auto& [_, inserted] = mBindingMap.emplace(BindingNumber(binding.binding), i); + ASSERT(inserted); + } + ASSERT(CheckBufferBindingsFirst({mBindingInfo.data(), GetBindingCount()})); + ASSERT(mBindingInfo.size() <= kMaxBindingsPerPipelineLayoutTyped); + } + + BindGroupLayoutBase::BindGroupLayoutBase(DeviceBase* device, + const BindGroupLayoutDescriptor* descriptor, + PipelineCompatibilityToken pipelineCompatibilityToken) + : BindGroupLayoutBase(device, descriptor, pipelineCompatibilityToken, kUntrackedByDevice) { + TrackInDevice(); + } + + BindGroupLayoutBase::BindGroupLayoutBase(DeviceBase* device, ObjectBase::ErrorTag tag) + : ApiObjectBase(device, tag) { + } + + BindGroupLayoutBase::BindGroupLayoutBase(DeviceBase* device) + : ApiObjectBase(device, kLabelNotImplemented) { + TrackInDevice(); + } + + BindGroupLayoutBase::~BindGroupLayoutBase() = default; + + void BindGroupLayoutBase::DestroyImpl() { + if (IsCachedReference()) { + // Do not uncache the actual cached object if we are a blueprint. + GetDevice()->UncacheBindGroupLayout(this); + } + } + + // static + BindGroupLayoutBase* BindGroupLayoutBase::MakeError(DeviceBase* device) { + return new BindGroupLayoutBase(device, ObjectBase::kError); + } + + ObjectType BindGroupLayoutBase::GetType() const { + return ObjectType::BindGroupLayout; + } + + const BindGroupLayoutBase::BindingMap& BindGroupLayoutBase::GetBindingMap() const { + ASSERT(!IsError()); + return mBindingMap; + } + + bool BindGroupLayoutBase::HasBinding(BindingNumber bindingNumber) const { + return mBindingMap.count(bindingNumber) != 0; + } + + BindingIndex BindGroupLayoutBase::GetBindingIndex(BindingNumber bindingNumber) const { + ASSERT(!IsError()); + const auto& it = mBindingMap.find(bindingNumber); + ASSERT(it != mBindingMap.end()); + return it->second; + } + + size_t BindGroupLayoutBase::ComputeContentHash() { + ObjectContentHasher recorder; + recorder.Record(mPipelineCompatibilityToken); + + // std::map is sorted by key, so two BGLs constructed in different orders + // will still record the same. + for (const auto [id, index] : mBindingMap) { + recorder.Record(id, index); + + const BindingInfo& info = mBindingInfo[index]; + recorder.Record(info.buffer.hasDynamicOffset, info.visibility, info.bindingType, + info.buffer.type, info.buffer.minBindingSize, info.sampler.type, + info.texture.sampleType, info.texture.viewDimension, + info.texture.multisampled, info.storageTexture.access, + info.storageTexture.format, info.storageTexture.viewDimension); + } + + return recorder.GetContentHash(); + } + + bool BindGroupLayoutBase::EqualityFunc::operator()(const BindGroupLayoutBase* a, + const BindGroupLayoutBase* b) const { + return a->IsLayoutEqual(b); + } + + BindingIndex BindGroupLayoutBase::GetBindingCount() const { + return mBindingInfo.size(); + } + + BindingIndex BindGroupLayoutBase::GetBufferCount() const { + return BindingIndex(mBindingCounts.bufferCount); + } + + BindingIndex BindGroupLayoutBase::GetDynamicBufferCount() const { + // This is a binding index because dynamic buffers are packed at the front of the binding + // info. + return static_cast<BindingIndex>(mBindingCounts.dynamicStorageBufferCount + + mBindingCounts.dynamicUniformBufferCount); + } + + uint32_t BindGroupLayoutBase::GetUnverifiedBufferCount() const { + return mBindingCounts.unverifiedBufferCount; + } + + uint32_t BindGroupLayoutBase::GetExternalTextureBindingCount() const { + return mExternalTextureBindingExpansionMap.size(); + } + + const BindingCounts& BindGroupLayoutBase::GetBindingCountInfo() const { + return mBindingCounts; + } + + const ExternalTextureBindingExpansionMap& + BindGroupLayoutBase::GetExternalTextureBindingExpansionMap() const { + return mExternalTextureBindingExpansionMap; + } + + uint32_t BindGroupLayoutBase::GetUnexpandedBindingCount() const { + return mUnexpandedBindingCount; + } + + bool BindGroupLayoutBase::IsLayoutEqual(const BindGroupLayoutBase* other, + bool excludePipelineCompatibiltyToken) const { + if (!excludePipelineCompatibiltyToken && + GetPipelineCompatibilityToken() != other->GetPipelineCompatibilityToken()) { + return false; + } + if (GetBindingCount() != other->GetBindingCount()) { + return false; + } + for (BindingIndex i{0}; i < GetBindingCount(); ++i) { + if (mBindingInfo[i] != other->mBindingInfo[i]) { + return false; + } + } + return mBindingMap == other->mBindingMap; + } + + PipelineCompatibilityToken BindGroupLayoutBase::GetPipelineCompatibilityToken() const { + return mPipelineCompatibilityToken; + } + + size_t BindGroupLayoutBase::GetBindingDataSize() const { + // | ------ buffer-specific ----------| ------------ object pointers -------------| + // | --- offsets + sizes -------------| --------------- Ref<ObjectBase> ----------| + // Followed by: + // |---------buffer size array--------| + // |-uint64_t[mUnverifiedBufferCount]-| + size_t objectPointerStart = mBindingCounts.bufferCount * sizeof(BufferBindingData); + ASSERT(IsAligned(objectPointerStart, alignof(Ref<ObjectBase>))); + size_t bufferSizeArrayStart = + Align(objectPointerStart + mBindingCounts.totalCount * sizeof(Ref<ObjectBase>), + sizeof(uint64_t)); + ASSERT(IsAligned(bufferSizeArrayStart, alignof(uint64_t))); + return bufferSizeArrayStart + mBindingCounts.unverifiedBufferCount * sizeof(uint64_t); + } + + BindGroupLayoutBase::BindingDataPointers BindGroupLayoutBase::ComputeBindingDataPointers( + void* dataStart) const { + BufferBindingData* bufferData = reinterpret_cast<BufferBindingData*>(dataStart); + auto bindings = reinterpret_cast<Ref<ObjectBase>*>(bufferData + mBindingCounts.bufferCount); + uint64_t* unverifiedBufferSizes = AlignPtr( + reinterpret_cast<uint64_t*>(bindings + mBindingCounts.totalCount), sizeof(uint64_t)); + + ASSERT(IsPtrAligned(bufferData, alignof(BufferBindingData))); + ASSERT(IsPtrAligned(bindings, alignof(Ref<ObjectBase>))); + ASSERT(IsPtrAligned(unverifiedBufferSizes, alignof(uint64_t))); + + return {{bufferData, GetBufferCount()}, + {bindings, GetBindingCount()}, + {unverifiedBufferSizes, mBindingCounts.unverifiedBufferCount}}; + } + + bool BindGroupLayoutBase::IsStorageBufferBinding(BindingIndex bindingIndex) const { + ASSERT(bindingIndex < GetBufferCount()); + switch (GetBindingInfo(bindingIndex).buffer.type) { + case wgpu::BufferBindingType::Uniform: + return false; + case kInternalStorageBufferBinding: + case wgpu::BufferBindingType::Storage: + case wgpu::BufferBindingType::ReadOnlyStorage: + return true; + case wgpu::BufferBindingType::Undefined: + UNREACHABLE(); + } + } + + std::string BindGroupLayoutBase::EntriesToString() const { + std::string entries = "["; + std::string sep = ""; + const BindGroupLayoutBase::BindingMap& bindingMap = GetBindingMap(); + for (const auto [bindingNumber, bindingIndex] : bindingMap) { + const BindingInfo& bindingInfo = GetBindingInfo(bindingIndex); + entries += absl::StrFormat("%s%s", sep, bindingInfo); + sep = ", "; + } + entries += "]"; + return entries; + } + +} // namespace dawn::native
diff --git a/src/dawn/native/BindGroupLayout.h b/src/dawn/native/BindGroupLayout.h new file mode 100644 index 0000000..5b91a2f --- /dev/null +++ b/src/dawn/native/BindGroupLayout.h
@@ -0,0 +1,170 @@ +// Copyright 2017 The Dawn Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#ifndef DAWNNATIVE_BINDGROUPLAYOUT_H_ +#define DAWNNATIVE_BINDGROUPLAYOUT_H_ + +#include "dawn/common/Constants.h" +#include "dawn/common/Math.h" +#include "dawn/common/SlabAllocator.h" +#include "dawn/common/ityp_span.h" +#include "dawn/common/ityp_vector.h" +#include "dawn/native/BindingInfo.h" +#include "dawn/native/CachedObject.h" +#include "dawn/native/Error.h" +#include "dawn/native/Forward.h" +#include "dawn/native/ObjectBase.h" + +#include "dawn/native/dawn_platform.h" + +#include <bitset> +#include <map> + +namespace dawn::native { + // TODO(dawn:1082): Minor optimization to use BindingIndex instead of BindingNumber + struct ExternalTextureBindingExpansion { + BindingNumber plane0; + BindingNumber plane1; + BindingNumber params; + }; + + using ExternalTextureBindingExpansionMap = + std::map<BindingNumber, ExternalTextureBindingExpansion>; + + MaybeError ValidateBindGroupLayoutDescriptor(DeviceBase* device, + const BindGroupLayoutDescriptor* descriptor, + bool allowInternalBinding = false); + + // Bindings are specified as a |BindingNumber| in the BindGroupLayoutDescriptor. + // These numbers may be arbitrary and sparse. Internally, Dawn packs these numbers + // into a packed range of |BindingIndex| integers. + class BindGroupLayoutBase : public ApiObjectBase, public CachedObject { + public: + BindGroupLayoutBase(DeviceBase* device, + const BindGroupLayoutDescriptor* descriptor, + PipelineCompatibilityToken pipelineCompatibilityToken, + ApiObjectBase::UntrackedByDeviceTag tag); + BindGroupLayoutBase(DeviceBase* device, + const BindGroupLayoutDescriptor* descriptor, + PipelineCompatibilityToken pipelineCompatibilityToken); + ~BindGroupLayoutBase() override; + + static BindGroupLayoutBase* MakeError(DeviceBase* device); + + ObjectType GetType() const override; + + // A map from the BindingNumber to its packed BindingIndex. + using BindingMap = std::map<BindingNumber, BindingIndex>; + + const BindingInfo& GetBindingInfo(BindingIndex bindingIndex) const { + ASSERT(!IsError()); + ASSERT(bindingIndex < mBindingInfo.size()); + return mBindingInfo[bindingIndex]; + } + const BindingMap& GetBindingMap() const; + bool HasBinding(BindingNumber bindingNumber) const; + BindingIndex GetBindingIndex(BindingNumber bindingNumber) const; + + // Functions necessary for the unordered_set<BGLBase*>-based cache. + size_t ComputeContentHash() override; + + struct EqualityFunc { + bool operator()(const BindGroupLayoutBase* a, const BindGroupLayoutBase* b) const; + }; + + BindingIndex GetBindingCount() const; + // Returns |BindingIndex| because buffers are packed at the front. + BindingIndex GetBufferCount() const; + // Returns |BindingIndex| because dynamic buffers are packed at the front. + BindingIndex GetDynamicBufferCount() const; + uint32_t GetUnverifiedBufferCount() const; + + // Used to get counts and validate them in pipeline layout creation. Other getters + // should be used to get typed integer counts. + const BindingCounts& GetBindingCountInfo() const; + + uint32_t GetExternalTextureBindingCount() const; + + // Used to specify unpacked external texture binding slots when transforming shader modules. + const ExternalTextureBindingExpansionMap& GetExternalTextureBindingExpansionMap() const; + + uint32_t GetUnexpandedBindingCount() const; + + // Tests that the BindingInfo of two bind groups are equal, + // ignoring their compatibility groups. + bool IsLayoutEqual(const BindGroupLayoutBase* other, + bool excludePipelineCompatibiltyToken = false) const; + PipelineCompatibilityToken GetPipelineCompatibilityToken() const; + + struct BufferBindingData { + uint64_t offset; + uint64_t size; + }; + + struct BindingDataPointers { + ityp::span<BindingIndex, BufferBindingData> const bufferData = {}; + ityp::span<BindingIndex, Ref<ObjectBase>> const bindings = {}; + ityp::span<uint32_t, uint64_t> const unverifiedBufferSizes = {}; + }; + + // Compute the amount of space / alignment required to store bindings for a bind group of + // this layout. + size_t GetBindingDataSize() const; + static constexpr size_t GetBindingDataAlignment() { + static_assert(alignof(Ref<ObjectBase>) <= alignof(BufferBindingData)); + return alignof(BufferBindingData); + } + + BindingDataPointers ComputeBindingDataPointers(void* dataStart) const; + + bool IsStorageBufferBinding(BindingIndex bindingIndex) const; + + // Returns a detailed string representation of the layout entries for use in error messages. + std::string EntriesToString() const; + + protected: + // Constructor used only for mocking and testing. + BindGroupLayoutBase(DeviceBase* device); + void DestroyImpl() override; + + template <typename BindGroup> + SlabAllocator<BindGroup> MakeFrontendBindGroupAllocator(size_t size) { + return SlabAllocator<BindGroup>( + size, // bytes + Align(sizeof(BindGroup), GetBindingDataAlignment()) + GetBindingDataSize(), // size + std::max(alignof(BindGroup), GetBindingDataAlignment()) // alignment + ); + } + + private: + BindGroupLayoutBase(DeviceBase* device, ObjectBase::ErrorTag tag); + + BindingCounts mBindingCounts = {}; + ityp::vector<BindingIndex, BindingInfo> mBindingInfo; + + // Map from BindGroupLayoutEntry.binding to packed indices. + BindingMap mBindingMap; + + ExternalTextureBindingExpansionMap mExternalTextureBindingExpansionMap; + + // Non-0 if this BindGroupLayout was created as part of a default PipelineLayout. + const PipelineCompatibilityToken mPipelineCompatibilityToken = + PipelineCompatibilityToken(0); + + uint32_t mUnexpandedBindingCount; + }; + +} // namespace dawn::native + +#endif // DAWNNATIVE_BINDGROUPLAYOUT_H_
diff --git a/src/dawn/native/BindGroupTracker.h b/src/dawn/native/BindGroupTracker.h new file mode 100644 index 0000000..72d0cf4 --- /dev/null +++ b/src/dawn/native/BindGroupTracker.h
@@ -0,0 +1,142 @@ +// Copyright 2019 The Dawn Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#ifndef DAWNNATIVE_BINDGROUPTRACKER_H_ +#define DAWNNATIVE_BINDGROUPTRACKER_H_ + +#include "dawn/common/Constants.h" +#include "dawn/native/BindGroupLayout.h" +#include "dawn/native/Pipeline.h" +#include "dawn/native/PipelineLayout.h" + +#include <array> +#include <bitset> + +namespace dawn::native { + + // Keeps track of the dirty bind groups so they can be lazily applied when we know the + // pipeline state or it changes. + // |DynamicOffset| is a template parameter because offsets in Vulkan are uint32_t but uint64_t + // in other backends. + template <bool CanInheritBindGroups, typename DynamicOffset> + class BindGroupTrackerBase { + public: + void OnSetBindGroup(BindGroupIndex index, + BindGroupBase* bindGroup, + uint32_t dynamicOffsetCount, + uint32_t* dynamicOffsets) { + ASSERT(index < kMaxBindGroupsTyped); + + if (mBindGroupLayoutsMask[index]) { + // It is okay to only dirty bind groups that are used by the current pipeline + // layout. If the pipeline layout changes, then the bind groups it uses will + // become dirty. + + if (mBindGroups[index] != bindGroup) { + mDirtyBindGroups.set(index); + mDirtyBindGroupsObjectChangedOrIsDynamic.set(index); + } + + if (dynamicOffsetCount > 0) { + mDirtyBindGroupsObjectChangedOrIsDynamic.set(index); + } + } + + mBindGroups[index] = bindGroup; + mDynamicOffsetCounts[index] = dynamicOffsetCount; + SetDynamicOffsets(mDynamicOffsets[index].data(), dynamicOffsetCount, dynamicOffsets); + } + + void OnSetPipeline(PipelineBase* pipeline) { + mPipelineLayout = pipeline->GetLayout(); + } + + protected: + // The Derived class should call this before it applies bind groups. + void BeforeApply() { + if (mLastAppliedPipelineLayout == mPipelineLayout) { + return; + } + + // Use the bind group layout mask to avoid marking unused bind groups as dirty. + mBindGroupLayoutsMask = mPipelineLayout->GetBindGroupLayoutsMask(); + + // Changing the pipeline layout sets bind groups as dirty. If CanInheritBindGroups, + // the first |k| matching bind groups may be inherited. + if (CanInheritBindGroups && mLastAppliedPipelineLayout != nullptr) { + // Dirty bind groups that cannot be inherited. + BindGroupLayoutMask dirtiedGroups = + ~mPipelineLayout->InheritedGroupsMask(mLastAppliedPipelineLayout); + + mDirtyBindGroups |= dirtiedGroups; + mDirtyBindGroupsObjectChangedOrIsDynamic |= dirtiedGroups; + + // Clear any bind groups not in the mask. + mDirtyBindGroups &= mBindGroupLayoutsMask; + mDirtyBindGroupsObjectChangedOrIsDynamic &= mBindGroupLayoutsMask; + } else { + mDirtyBindGroups = mBindGroupLayoutsMask; + mDirtyBindGroupsObjectChangedOrIsDynamic = mBindGroupLayoutsMask; + } + } + + // The Derived class should call this after it applies bind groups. + void AfterApply() { + // Reset all dirty bind groups. Dirty bind groups not in the bind group layout mask + // will be dirtied again by the next pipeline change. + mDirtyBindGroups.reset(); + mDirtyBindGroupsObjectChangedOrIsDynamic.reset(); + // Keep track of the last applied pipeline layout. This allows us to avoid computing + // the intersection of the dirty bind groups and bind group layout mask in next Draw + // or Dispatch (which is very hot code) until the layout is changed again. + mLastAppliedPipelineLayout = mPipelineLayout; + } + + BindGroupLayoutMask mDirtyBindGroups = 0; + BindGroupLayoutMask mDirtyBindGroupsObjectChangedOrIsDynamic = 0; + BindGroupLayoutMask mBindGroupLayoutsMask = 0; + ityp::array<BindGroupIndex, BindGroupBase*, kMaxBindGroups> mBindGroups = {}; + ityp::array<BindGroupIndex, uint32_t, kMaxBindGroups> mDynamicOffsetCounts = {}; + ityp::array<BindGroupIndex, + std::array<DynamicOffset, kMaxDynamicBuffersPerPipelineLayout>, + kMaxBindGroups> + mDynamicOffsets = {}; + + // |mPipelineLayout| is the current pipeline layout set on the command buffer. + // |mLastAppliedPipelineLayout| is the last pipeline layout for which we applied changes + // to the bind group bindings. + PipelineLayoutBase* mPipelineLayout = nullptr; + PipelineLayoutBase* mLastAppliedPipelineLayout = nullptr; + + private: + // We have two overloads here because offsets in Vulkan are uint32_t but uint64_t + // in other backends. + static void SetDynamicOffsets(uint64_t* data, + uint32_t dynamicOffsetCount, + uint32_t* dynamicOffsets) { + for (uint32_t i = 0; i < dynamicOffsetCount; ++i) { + data[i] = static_cast<uint64_t>(dynamicOffsets[i]); + } + } + + static void SetDynamicOffsets(uint32_t* data, + uint32_t dynamicOffsetCount, + uint32_t* dynamicOffsets) { + memcpy(data, dynamicOffsets, sizeof(uint32_t) * dynamicOffsetCount); + } + }; + +} // namespace dawn::native + +#endif // DAWNNATIVE_BINDGROUPTRACKER_H_
diff --git a/src/dawn/native/BindingInfo.cpp b/src/dawn/native/BindingInfo.cpp new file mode 100644 index 0000000..009735c --- /dev/null +++ b/src/dawn/native/BindingInfo.cpp
@@ -0,0 +1,195 @@ +// Copyright 2020 The Dawn Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include "dawn/native/BindingInfo.h" + +#include "dawn/native/ChainUtils_autogen.h" + +namespace dawn::native { + + void IncrementBindingCounts(BindingCounts* bindingCounts, const BindGroupLayoutEntry& entry) { + bindingCounts->totalCount += 1; + + uint32_t PerStageBindingCounts::*perStageBindingCountMember = nullptr; + + if (entry.buffer.type != wgpu::BufferBindingType::Undefined) { + ++bindingCounts->bufferCount; + const BufferBindingLayout& buffer = entry.buffer; + + if (buffer.minBindingSize == 0) { + ++bindingCounts->unverifiedBufferCount; + } + + switch (buffer.type) { + case wgpu::BufferBindingType::Uniform: + if (buffer.hasDynamicOffset) { + ++bindingCounts->dynamicUniformBufferCount; + } + perStageBindingCountMember = &PerStageBindingCounts::uniformBufferCount; + break; + + case wgpu::BufferBindingType::Storage: + case kInternalStorageBufferBinding: + case wgpu::BufferBindingType::ReadOnlyStorage: + if (buffer.hasDynamicOffset) { + ++bindingCounts->dynamicStorageBufferCount; + } + perStageBindingCountMember = &PerStageBindingCounts::storageBufferCount; + break; + + case wgpu::BufferBindingType::Undefined: + // Can't get here due to the enclosing if statement. + UNREACHABLE(); + break; + } + } else if (entry.sampler.type != wgpu::SamplerBindingType::Undefined) { + perStageBindingCountMember = &PerStageBindingCounts::samplerCount; + } else if (entry.texture.sampleType != wgpu::TextureSampleType::Undefined) { + perStageBindingCountMember = &PerStageBindingCounts::sampledTextureCount; + } else if (entry.storageTexture.access != wgpu::StorageTextureAccess::Undefined) { + perStageBindingCountMember = &PerStageBindingCounts::storageTextureCount; + } else { + const ExternalTextureBindingLayout* externalTextureBindingLayout; + FindInChain(entry.nextInChain, &externalTextureBindingLayout); + if (externalTextureBindingLayout != nullptr) { + perStageBindingCountMember = &PerStageBindingCounts::externalTextureCount; + } + } + + ASSERT(perStageBindingCountMember != nullptr); + for (SingleShaderStage stage : IterateStages(entry.visibility)) { + ++(bindingCounts->perStage[stage].*perStageBindingCountMember); + } + } + + void AccumulateBindingCounts(BindingCounts* bindingCounts, const BindingCounts& rhs) { + bindingCounts->totalCount += rhs.totalCount; + bindingCounts->bufferCount += rhs.bufferCount; + bindingCounts->unverifiedBufferCount += rhs.unverifiedBufferCount; + bindingCounts->dynamicUniformBufferCount += rhs.dynamicUniformBufferCount; + bindingCounts->dynamicStorageBufferCount += rhs.dynamicStorageBufferCount; + + for (SingleShaderStage stage : IterateStages(kAllStages)) { + bindingCounts->perStage[stage].sampledTextureCount += + rhs.perStage[stage].sampledTextureCount; + bindingCounts->perStage[stage].samplerCount += rhs.perStage[stage].samplerCount; + bindingCounts->perStage[stage].storageBufferCount += + rhs.perStage[stage].storageBufferCount; + bindingCounts->perStage[stage].storageTextureCount += + rhs.perStage[stage].storageTextureCount; + bindingCounts->perStage[stage].uniformBufferCount += + rhs.perStage[stage].uniformBufferCount; + bindingCounts->perStage[stage].externalTextureCount += + rhs.perStage[stage].externalTextureCount; + } + } + + MaybeError ValidateBindingCounts(const BindingCounts& bindingCounts) { + DAWN_INVALID_IF( + bindingCounts.dynamicUniformBufferCount > kMaxDynamicUniformBuffersPerPipelineLayout, + "The number of dynamic uniform buffers (%u) exceeds the maximum per-pipeline-layout " + "limit (%u).", + bindingCounts.dynamicUniformBufferCount, kMaxDynamicUniformBuffersPerPipelineLayout); + + DAWN_INVALID_IF( + bindingCounts.dynamicStorageBufferCount > kMaxDynamicStorageBuffersPerPipelineLayout, + "The number of dynamic storage buffers (%u) exceeds the maximum per-pipeline-layout " + "limit (%u).", + bindingCounts.dynamicStorageBufferCount, kMaxDynamicStorageBuffersPerPipelineLayout); + + for (SingleShaderStage stage : IterateStages(kAllStages)) { + DAWN_INVALID_IF( + bindingCounts.perStage[stage].sampledTextureCount > + kMaxSampledTexturesPerShaderStage, + "The number of sampled textures (%u) in the %s stage exceeds the maximum " + "per-stage limit (%u).", + bindingCounts.perStage[stage].sampledTextureCount, stage, + kMaxSampledTexturesPerShaderStage); + + // The per-stage number of external textures is bound by the maximum sampled textures + // per stage. + DAWN_INVALID_IF( + bindingCounts.perStage[stage].externalTextureCount > + kMaxSampledTexturesPerShaderStage / kSampledTexturesPerExternalTexture, + "The number of external textures (%u) in the %s stage exceeds the maximum " + "per-stage limit (%u).", + bindingCounts.perStage[stage].externalTextureCount, stage, + kMaxSampledTexturesPerShaderStage / kSampledTexturesPerExternalTexture); + + DAWN_INVALID_IF( + bindingCounts.perStage[stage].sampledTextureCount + + (bindingCounts.perStage[stage].externalTextureCount * + kSampledTexturesPerExternalTexture) > + kMaxSampledTexturesPerShaderStage, + "The combination of sampled textures (%u) and external textures (%u) in the %s " + "stage exceeds the maximum per-stage limit (%u).", + bindingCounts.perStage[stage].sampledTextureCount, + bindingCounts.perStage[stage].externalTextureCount, stage, + kMaxSampledTexturesPerShaderStage); + + DAWN_INVALID_IF( + bindingCounts.perStage[stage].samplerCount > kMaxSamplersPerShaderStage, + "The number of samplers (%u) in the %s stage exceeds the maximum per-stage limit " + "(%u).", + bindingCounts.perStage[stage].samplerCount, stage, kMaxSamplersPerShaderStage); + + DAWN_INVALID_IF( + bindingCounts.perStage[stage].samplerCount + + (bindingCounts.perStage[stage].externalTextureCount * + kSamplersPerExternalTexture) > + kMaxSamplersPerShaderStage, + "The combination of samplers (%u) and external textures (%u) in the %s stage " + "exceeds the maximum per-stage limit (%u).", + bindingCounts.perStage[stage].samplerCount, + bindingCounts.perStage[stage].externalTextureCount, stage, + kMaxSamplersPerShaderStage); + + DAWN_INVALID_IF( + bindingCounts.perStage[stage].storageBufferCount > kMaxStorageBuffersPerShaderStage, + "The number of storage buffers (%u) in the %s stage exceeds the maximum per-stage " + "limit (%u).", + bindingCounts.perStage[stage].storageBufferCount, stage, + kMaxStorageBuffersPerShaderStage); + + DAWN_INVALID_IF( + bindingCounts.perStage[stage].storageTextureCount > + kMaxStorageTexturesPerShaderStage, + "The number of storage textures (%u) in the %s stage exceeds the maximum per-stage " + "limit (%u).", + bindingCounts.perStage[stage].storageTextureCount, stage, + kMaxStorageTexturesPerShaderStage); + + DAWN_INVALID_IF( + bindingCounts.perStage[stage].uniformBufferCount > kMaxUniformBuffersPerShaderStage, + "The number of uniform buffers (%u) in the %s stage exceeds the maximum per-stage " + "limit (%u).", + bindingCounts.perStage[stage].uniformBufferCount, stage, + kMaxUniformBuffersPerShaderStage); + + DAWN_INVALID_IF( + bindingCounts.perStage[stage].uniformBufferCount + + (bindingCounts.perStage[stage].externalTextureCount * + kUniformsPerExternalTexture) > + kMaxUniformBuffersPerShaderStage, + "The combination of uniform buffers (%u) and external textures (%u) in the %s " + "stage exceeds the maximum per-stage limit (%u).", + bindingCounts.perStage[stage].uniformBufferCount, + bindingCounts.perStage[stage].externalTextureCount, stage, + kMaxUniformBuffersPerShaderStage); + } + + return {}; + } + +} // namespace dawn::native
diff --git a/src/dawn/native/BindingInfo.h b/src/dawn/native/BindingInfo.h new file mode 100644 index 0000000..027ce52 --- /dev/null +++ b/src/dawn/native/BindingInfo.h
@@ -0,0 +1,98 @@ +// Copyright 2020 The Dawn Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#ifndef DAWNNATIVE_BINDINGINFO_H_ +#define DAWNNATIVE_BINDINGINFO_H_ + +#include "dawn/common/Constants.h" +#include "dawn/common/ityp_array.h" +#include "dawn/native/Error.h" +#include "dawn/native/Format.h" +#include "dawn/native/IntegerTypes.h" +#include "dawn/native/PerStage.h" + +#include "dawn/native/dawn_platform.h" + +#include <cstdint> + +namespace dawn::native { + + // Not a real WebGPU limit, but the sum of the two limits is useful for internal optimizations. + static constexpr uint32_t kMaxDynamicBuffersPerPipelineLayout = + kMaxDynamicUniformBuffersPerPipelineLayout + kMaxDynamicStorageBuffersPerPipelineLayout; + + static constexpr BindingIndex kMaxDynamicBuffersPerPipelineLayoutTyped = + BindingIndex(kMaxDynamicBuffersPerPipelineLayout); + + // Not a real WebGPU limit, but used to optimize parts of Dawn which expect valid usage of the + // API. There should never be more bindings than the max per stage, for each stage. + static constexpr uint32_t kMaxBindingsPerPipelineLayout = + 3 * (kMaxSampledTexturesPerShaderStage + kMaxSamplersPerShaderStage + + kMaxStorageBuffersPerShaderStage + kMaxStorageTexturesPerShaderStage + + kMaxUniformBuffersPerShaderStage); + + static constexpr BindingIndex kMaxBindingsPerPipelineLayoutTyped = + BindingIndex(kMaxBindingsPerPipelineLayout); + + // TODO(enga): Figure out a good number for this. + static constexpr uint32_t kMaxOptimalBindingsPerGroup = 32; + + enum class BindingInfoType { Buffer, Sampler, Texture, StorageTexture, ExternalTexture }; + + struct BindingInfo { + BindingNumber binding; + wgpu::ShaderStage visibility; + + BindingInfoType bindingType; + + // TODO(dawn:527): These four values could be made into a union. + BufferBindingLayout buffer; + SamplerBindingLayout sampler; + TextureBindingLayout texture; + StorageTextureBindingLayout storageTexture; + }; + + struct BindingSlot { + BindGroupIndex group; + BindingNumber binding; + }; + + struct PerStageBindingCounts { + uint32_t sampledTextureCount; + uint32_t samplerCount; + uint32_t storageBufferCount; + uint32_t storageTextureCount; + uint32_t uniformBufferCount; + uint32_t externalTextureCount; + }; + + struct BindingCounts { + uint32_t totalCount; + uint32_t bufferCount; + uint32_t unverifiedBufferCount; // Buffers with minimum buffer size unspecified + uint32_t dynamicUniformBufferCount; + uint32_t dynamicStorageBufferCount; + PerStage<PerStageBindingCounts> perStage; + }; + + void IncrementBindingCounts(BindingCounts* bindingCounts, const BindGroupLayoutEntry& entry); + void AccumulateBindingCounts(BindingCounts* bindingCounts, const BindingCounts& rhs); + MaybeError ValidateBindingCounts(const BindingCounts& bindingCounts); + + // For buffer size validation + using RequiredBufferSizes = ityp::array<BindGroupIndex, std::vector<uint64_t>, kMaxBindGroups>; + +} // namespace dawn::native + +#endif // DAWNNATIVE_BINDINGINFO_H_
diff --git a/src/dawn/native/BuddyAllocator.cpp b/src/dawn/native/BuddyAllocator.cpp new file mode 100644 index 0000000..76d7a65 --- /dev/null +++ b/src/dawn/native/BuddyAllocator.cpp
@@ -0,0 +1,264 @@ +// Copyright 2019 The Dawn Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include "dawn/native/BuddyAllocator.h" + +#include "dawn/common/Assert.h" +#include "dawn/common/Math.h" + +namespace dawn::native { + + BuddyAllocator::BuddyAllocator(uint64_t maxSize) : mMaxBlockSize(maxSize) { + ASSERT(IsPowerOfTwo(maxSize)); + + mFreeLists.resize(Log2(mMaxBlockSize) + 1); + + // Insert the level0 free block. + mRoot = new BuddyBlock(maxSize, /*offset*/ 0); + mFreeLists[0] = {mRoot}; + } + + BuddyAllocator::~BuddyAllocator() { + if (mRoot) { + DeleteBlock(mRoot); + } + } + + uint64_t BuddyAllocator::ComputeTotalNumOfFreeBlocksForTesting() const { + return ComputeNumOfFreeBlocks(mRoot); + } + + uint64_t BuddyAllocator::ComputeNumOfFreeBlocks(BuddyBlock* block) const { + if (block->mState == BlockState::Free) { + return 1; + } else if (block->mState == BlockState::Split) { + return ComputeNumOfFreeBlocks(block->split.pLeft) + + ComputeNumOfFreeBlocks(block->split.pLeft->pBuddy); + } + return 0; + } + + uint32_t BuddyAllocator::ComputeLevelFromBlockSize(uint64_t blockSize) const { + // Every level in the buddy system can be indexed by order-n where n = log2(blockSize). + // However, mFreeList zero-indexed by level. + // For example, blockSize=4 is Level1 if MAX_BLOCK is 8. + return Log2(mMaxBlockSize) - Log2(blockSize); + } + + uint64_t BuddyAllocator::GetNextFreeAlignedBlock(size_t allocationBlockLevel, + uint64_t alignment) const { + ASSERT(IsPowerOfTwo(alignment)); + // The current level is the level that corresponds to the allocation size. The free list may + // not contain a block at that level until a larger one gets allocated (and splits). + // Continue to go up the tree until such a larger block exists. + // + // Even if the block exists at the level, it cannot be used if it's offset is unaligned. + // When the alignment is also a power-of-two, we simply use the next free block whose size + // is greater than or equal to the alignment value. + // + // After one 8-byte allocation: + // + // Level -------------------------------- + // 0 32 | S | + // -------------------------------- + // 1 16 | S | F2 | S - split + // -------------------------------- F - free + // 2 8 | Aa | F1 | | A - allocated + // -------------------------------- + // + // Allocate(size=8, alignment=8) will be satisfied by using F1. + // Allocate(size=8, alignment=4) will be satified by using F1. + // Allocate(size=8, alignment=16) will be satisified by using F2. + // + for (size_t ii = 0; ii <= allocationBlockLevel; ++ii) { + size_t currLevel = allocationBlockLevel - ii; + BuddyBlock* freeBlock = mFreeLists[currLevel].head; + if (freeBlock && (freeBlock->mOffset % alignment == 0)) { + return currLevel; + } + } + return kInvalidOffset; // No free block exists at any level. + } + + // Inserts existing free block into the free-list. + // Called by allocate upon splitting to insert a child block into a free-list. + // Note: Always insert into the head of the free-list. As when a larger free block at a lower + // level was split, there were no smaller free blocks at a higher level to allocate. + void BuddyAllocator::InsertFreeBlock(BuddyBlock* block, size_t level) { + ASSERT(block->mState == BlockState::Free); + + // Inserted block is now the front (no prev). + block->free.pPrev = nullptr; + + // Old head is now the inserted block's next. + block->free.pNext = mFreeLists[level].head; + + // Block already in HEAD position (ex. right child was inserted first). + if (mFreeLists[level].head != nullptr) { + // Old head's previous is the inserted block. + mFreeLists[level].head->free.pPrev = block; + } + + mFreeLists[level].head = block; + } + + void BuddyAllocator::RemoveFreeBlock(BuddyBlock* block, size_t level) { + ASSERT(block->mState == BlockState::Free); + + if (mFreeLists[level].head == block) { + // Block is in HEAD position. + mFreeLists[level].head = mFreeLists[level].head->free.pNext; + } else { + // Block is after HEAD position. + BuddyBlock* pPrev = block->free.pPrev; + BuddyBlock* pNext = block->free.pNext; + + ASSERT(pPrev != nullptr); + ASSERT(pPrev->mState == BlockState::Free); + + pPrev->free.pNext = pNext; + + if (pNext != nullptr) { + ASSERT(pNext->mState == BlockState::Free); + pNext->free.pPrev = pPrev; + } + } + } + + uint64_t BuddyAllocator::Allocate(uint64_t allocationSize, uint64_t alignment) { + if (allocationSize == 0 || allocationSize > mMaxBlockSize) { + return kInvalidOffset; + } + + // Compute the level + const uint32_t allocationSizeToLevel = ComputeLevelFromBlockSize(allocationSize); + + ASSERT(allocationSizeToLevel < mFreeLists.size()); + + uint64_t currBlockLevel = GetNextFreeAlignedBlock(allocationSizeToLevel, alignment); + + // Error when no free blocks exist (allocator is full) + if (currBlockLevel == kInvalidOffset) { + return kInvalidOffset; + } + + // Split free blocks level-by-level. + // Terminate when the current block level is equal to the computed level of the requested + // allocation. + BuddyBlock* currBlock = mFreeLists[currBlockLevel].head; + + for (; currBlockLevel < allocationSizeToLevel; currBlockLevel++) { + ASSERT(currBlock->mState == BlockState::Free); + + // Remove curr block (about to be split). + RemoveFreeBlock(currBlock, currBlockLevel); + + // Create two free child blocks (the buddies). + const uint64_t nextLevelSize = currBlock->mSize / 2; + BuddyBlock* leftChildBlock = new BuddyBlock(nextLevelSize, currBlock->mOffset); + BuddyBlock* rightChildBlock = + new BuddyBlock(nextLevelSize, currBlock->mOffset + nextLevelSize); + + // Remember the parent to merge these back upon de-allocation. + rightChildBlock->pParent = currBlock; + leftChildBlock->pParent = currBlock; + + // Make them buddies. + leftChildBlock->pBuddy = rightChildBlock; + rightChildBlock->pBuddy = leftChildBlock; + + // Insert the children back into the free list into the next level. + // The free list does not require a specific order. However, an order is specified as + // it's ideal to allocate lower addresses first by having the leftmost child in HEAD. + InsertFreeBlock(rightChildBlock, currBlockLevel + 1); + InsertFreeBlock(leftChildBlock, currBlockLevel + 1); + + // Curr block is now split. + currBlock->mState = BlockState::Split; + currBlock->split.pLeft = leftChildBlock; + + // Decend down into the next level. + currBlock = leftChildBlock; + } + + // Remove curr block from free-list (now allocated). + RemoveFreeBlock(currBlock, currBlockLevel); + currBlock->mState = BlockState::Allocated; + + return currBlock->mOffset; + } + + void BuddyAllocator::Deallocate(uint64_t offset) { + BuddyBlock* curr = mRoot; + + // TODO(crbug.com/dawn/827): Optimize de-allocation. + // Passing allocationSize directly will avoid the following level-by-level search; + // however, it requires the size information to be stored outside the allocator. + + // Search for the free block node that corresponds to the block offset. + size_t currBlockLevel = 0; + while (curr->mState == BlockState::Split) { + if (offset < curr->split.pLeft->pBuddy->mOffset) { + curr = curr->split.pLeft; + } else { + curr = curr->split.pLeft->pBuddy; + } + + currBlockLevel++; + } + + ASSERT(curr->mState == BlockState::Allocated); + + // Ensure the block is at the correct level + ASSERT(currBlockLevel == ComputeLevelFromBlockSize(curr->mSize)); + + // Mark curr free so we can merge. + curr->mState = BlockState::Free; + + // Merge the buddies (LevelN-to-Level0). + while (currBlockLevel > 0 && curr->pBuddy->mState == BlockState::Free) { + // Remove the buddy. + RemoveFreeBlock(curr->pBuddy, currBlockLevel); + + BuddyBlock* parent = curr->pParent; + + // The buddies were inserted in a specific order but + // could be deleted in any order. + DeleteBlock(curr->pBuddy); + DeleteBlock(curr); + + // Parent is now free. + parent->mState = BlockState::Free; + + // Ascend up to the next level (parent block). + curr = parent; + currBlockLevel--; + } + + InsertFreeBlock(curr, currBlockLevel); + } + + // Helper which deletes a block in the tree recursively (post-order). + void BuddyAllocator::DeleteBlock(BuddyBlock* block) { + ASSERT(block != nullptr); + + if (block->mState == BlockState::Split) { + // Delete the pair in same order we inserted. + DeleteBlock(block->split.pLeft->pBuddy); + DeleteBlock(block->split.pLeft); + } + delete block; + } + +} // namespace dawn::native
diff --git a/src/dawn/native/BuddyAllocator.h b/src/dawn/native/BuddyAllocator.h new file mode 100644 index 0000000..31c8b0b --- /dev/null +++ b/src/dawn/native/BuddyAllocator.h
@@ -0,0 +1,117 @@ +// Copyright 2019 The Dawn Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#ifndef DAWNNATIVE_BUDDYALLOCATOR_H_ +#define DAWNNATIVE_BUDDYALLOCATOR_H_ + +#include <cstddef> +#include <cstdint> +#include <limits> +#include <vector> + +namespace dawn::native { + + // Buddy allocator uses the buddy memory allocation technique to satisfy an allocation request. + // Memory is split into halves until just large enough to fit to the request. This + // requires the allocation size to be a power-of-two value. The allocator "allocates" a block by + // returning the starting offset whose size is guaranteed to be greater than or equal to the + // allocation size. To deallocate, the same offset is used to find the corresponding block. + // + // Internally, it manages a free list to track free blocks in a full binary tree. + // Every index in the free list corresponds to a level in the tree. That level also determines + // the size of the block to be used to satisfy the request. The first level (index=0) represents + // the root whose size is also called the max block size. + // + class BuddyAllocator { + public: + BuddyAllocator(uint64_t maxSize); + ~BuddyAllocator(); + + // Required methods. + uint64_t Allocate(uint64_t allocationSize, uint64_t alignment = 1); + void Deallocate(uint64_t offset); + + // For testing purposes only. + uint64_t ComputeTotalNumOfFreeBlocksForTesting() const; + + static constexpr uint64_t kInvalidOffset = std::numeric_limits<uint64_t>::max(); + + private: + uint32_t ComputeLevelFromBlockSize(uint64_t blockSize) const; + uint64_t GetNextFreeAlignedBlock(size_t allocationBlockLevel, uint64_t alignment) const; + + enum class BlockState { Free, Split, Allocated }; + + struct BuddyBlock { + BuddyBlock(uint64_t size, uint64_t offset) + : mOffset(offset), mSize(size), mState(BlockState::Free) { + free.pPrev = nullptr; + free.pNext = nullptr; + } + + uint64_t mOffset; + uint64_t mSize; + + // Pointer to this block's buddy, iff parent is split. + // Used to quickly merge buddy blocks upon de-allocate. + BuddyBlock* pBuddy = nullptr; + BuddyBlock* pParent = nullptr; + + // Track whether this block has been split or not. + BlockState mState; + + struct FreeLinks { + BuddyBlock* pPrev; + BuddyBlock* pNext; + }; + + struct SplitLink { + BuddyBlock* pLeft; + }; + + union { + // Used upon allocation. + // Avoids searching for the next free block. + FreeLinks free; + + // Used upon de-allocation. + // Had this block split upon allocation, it and it's buddy is to be deleted. + SplitLink split; + }; + }; + + void InsertFreeBlock(BuddyBlock* block, size_t level); + void RemoveFreeBlock(BuddyBlock* block, size_t level); + void DeleteBlock(BuddyBlock* block); + + uint64_t ComputeNumOfFreeBlocks(BuddyBlock* block) const; + + // Keep track the head and tail (for faster insertion/removal). + struct BlockList { + BuddyBlock* head = nullptr; // First free block in level. + // TODO(crbug.com/dawn/827): Track the tail. + }; + + BuddyBlock* mRoot = nullptr; // Used to deallocate non-free blocks. + + uint64_t mMaxBlockSize = 0; + + // List of linked-lists of free blocks where the index is a level that + // corresponds to a power-of-two sized block. + std::vector<BlockList> mFreeLists; + }; + +} // namespace dawn::native + +#endif // DAWNNATIVE_BUDDYALLOCATOR_H_
diff --git a/src/dawn/native/BuddyMemoryAllocator.cpp b/src/dawn/native/BuddyMemoryAllocator.cpp new file mode 100644 index 0000000..faee03e --- /dev/null +++ b/src/dawn/native/BuddyMemoryAllocator.cpp
@@ -0,0 +1,120 @@ +// Copyright 2019 The Dawn Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include "dawn/native/BuddyMemoryAllocator.h" + +#include "dawn/common/Math.h" +#include "dawn/native/ResourceHeapAllocator.h" + +namespace dawn::native { + + BuddyMemoryAllocator::BuddyMemoryAllocator(uint64_t maxSystemSize, + uint64_t memoryBlockSize, + ResourceHeapAllocator* heapAllocator) + : mMemoryBlockSize(memoryBlockSize), + mBuddyBlockAllocator(maxSystemSize), + mHeapAllocator(heapAllocator) { + ASSERT(memoryBlockSize <= maxSystemSize); + ASSERT(IsPowerOfTwo(mMemoryBlockSize)); + ASSERT(maxSystemSize % mMemoryBlockSize == 0); + + mTrackedSubAllocations.resize(maxSystemSize / mMemoryBlockSize); + } + + uint64_t BuddyMemoryAllocator::GetMemoryIndex(uint64_t offset) const { + ASSERT(offset != BuddyAllocator::kInvalidOffset); + return offset / mMemoryBlockSize; + } + + ResultOrError<ResourceMemoryAllocation> BuddyMemoryAllocator::Allocate(uint64_t allocationSize, + uint64_t alignment) { + ResourceMemoryAllocation invalidAllocation = ResourceMemoryAllocation{}; + + if (allocationSize == 0) { + return std::move(invalidAllocation); + } + + // Check the unaligned size to avoid overflowing NextPowerOfTwo. + if (allocationSize > mMemoryBlockSize) { + return std::move(invalidAllocation); + } + + // Round allocation size to nearest power-of-two. + allocationSize = NextPowerOfTwo(allocationSize); + + // Allocation cannot exceed the memory size. + if (allocationSize > mMemoryBlockSize) { + return std::move(invalidAllocation); + } + + // Attempt to sub-allocate a block of the requested size. + const uint64_t blockOffset = mBuddyBlockAllocator.Allocate(allocationSize, alignment); + if (blockOffset == BuddyAllocator::kInvalidOffset) { + return std::move(invalidAllocation); + } + + const uint64_t memoryIndex = GetMemoryIndex(blockOffset); + if (mTrackedSubAllocations[memoryIndex].refcount == 0) { + // Transfer ownership to this allocator + std::unique_ptr<ResourceHeapBase> memory; + DAWN_TRY_ASSIGN(memory, mHeapAllocator->AllocateResourceHeap(mMemoryBlockSize)); + mTrackedSubAllocations[memoryIndex] = {/*refcount*/ 0, std::move(memory)}; + } + + mTrackedSubAllocations[memoryIndex].refcount++; + + AllocationInfo info; + info.mBlockOffset = blockOffset; + info.mMethod = AllocationMethod::kSubAllocated; + + // Allocation offset is always local to the memory. + const uint64_t memoryOffset = blockOffset % mMemoryBlockSize; + + return ResourceMemoryAllocation{ + info, memoryOffset, mTrackedSubAllocations[memoryIndex].mMemoryAllocation.get()}; + } + + void BuddyMemoryAllocator::Deallocate(const ResourceMemoryAllocation& allocation) { + const AllocationInfo info = allocation.GetInfo(); + + ASSERT(info.mMethod == AllocationMethod::kSubAllocated); + + const uint64_t memoryIndex = GetMemoryIndex(info.mBlockOffset); + + ASSERT(mTrackedSubAllocations[memoryIndex].refcount > 0); + mTrackedSubAllocations[memoryIndex].refcount--; + + if (mTrackedSubAllocations[memoryIndex].refcount == 0) { + mHeapAllocator->DeallocateResourceHeap( + std::move(mTrackedSubAllocations[memoryIndex].mMemoryAllocation)); + } + + mBuddyBlockAllocator.Deallocate(info.mBlockOffset); + } + + uint64_t BuddyMemoryAllocator::GetMemoryBlockSize() const { + return mMemoryBlockSize; + } + + uint64_t BuddyMemoryAllocator::ComputeTotalNumOfHeapsForTesting() const { + uint64_t count = 0; + for (const TrackedSubAllocations& allocation : mTrackedSubAllocations) { + if (allocation.refcount > 0) { + count++; + } + } + return count; + } + +} // namespace dawn::native
diff --git a/src/dawn/native/BuddyMemoryAllocator.h b/src/dawn/native/BuddyMemoryAllocator.h new file mode 100644 index 0000000..7fcfe71 --- /dev/null +++ b/src/dawn/native/BuddyMemoryAllocator.h
@@ -0,0 +1,74 @@ +// Copyright 2019 The Dawn Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#ifndef DAWNNATIVE_BUDDYMEMORYALLOCATOR_H_ +#define DAWNNATIVE_BUDDYMEMORYALLOCATOR_H_ + +#include "dawn/native/BuddyAllocator.h" +#include "dawn/native/Error.h" +#include "dawn/native/ResourceMemoryAllocation.h" + +#include <memory> +#include <vector> + +namespace dawn::native { + + class ResourceHeapAllocator; + + // BuddyMemoryAllocator uses the buddy allocator to sub-allocate blocks of device + // memory created by MemoryAllocator clients. It creates a very large buddy system + // where backing device memory blocks equal a specified level in the system. + // + // Upon sub-allocating, the offset gets mapped to device memory by computing the corresponding + // memory index and should the memory not exist, it is created. If two sub-allocations share the + // same memory index, the memory refcount is incremented to ensure de-allocating one doesn't + // release the other prematurely. + // + // The MemoryAllocator should return ResourceHeaps that are all compatible with each other. + // It should also outlive all the resources that are in the buddy allocator. + class BuddyMemoryAllocator { + public: + BuddyMemoryAllocator(uint64_t maxSystemSize, + uint64_t memoryBlockSize, + ResourceHeapAllocator* heapAllocator); + ~BuddyMemoryAllocator() = default; + + ResultOrError<ResourceMemoryAllocation> Allocate(uint64_t allocationSize, + uint64_t alignment); + void Deallocate(const ResourceMemoryAllocation& allocation); + + uint64_t GetMemoryBlockSize() const; + + // For testing purposes. + uint64_t ComputeTotalNumOfHeapsForTesting() const; + + private: + uint64_t GetMemoryIndex(uint64_t offset) const; + + uint64_t mMemoryBlockSize = 0; + + BuddyAllocator mBuddyBlockAllocator; + ResourceHeapAllocator* mHeapAllocator; + + struct TrackedSubAllocations { + size_t refcount = 0; + std::unique_ptr<ResourceHeapBase> mMemoryAllocation; + }; + + std::vector<TrackedSubAllocations> mTrackedSubAllocations; + }; + +} // namespace dawn::native + +#endif // DAWNNATIVE_BUDDYMEMORYALLOCATOR_H_
diff --git a/src/dawn/native/Buffer.cpp b/src/dawn/native/Buffer.cpp new file mode 100644 index 0000000..f324597 --- /dev/null +++ b/src/dawn/native/Buffer.cpp
@@ -0,0 +1,562 @@ +// Copyright 2017 The Dawn Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include "dawn/native/Buffer.h" + +#include "dawn/common/Alloc.h" +#include "dawn/common/Assert.h" +#include "dawn/native/Commands.h" +#include "dawn/native/Device.h" +#include "dawn/native/DynamicUploader.h" +#include "dawn/native/ErrorData.h" +#include "dawn/native/ObjectType_autogen.h" +#include "dawn/native/Queue.h" +#include "dawn/native/ValidationUtils_autogen.h" + +#include <cstdio> +#include <cstring> +#include <utility> + +namespace dawn::native { + + namespace { + struct MapRequestTask : QueueBase::TaskInFlight { + MapRequestTask(Ref<BufferBase> buffer, MapRequestID id) + : buffer(std::move(buffer)), id(id) { + } + void Finish() override { + buffer->OnMapRequestCompleted(id, WGPUBufferMapAsyncStatus_Success); + } + void HandleDeviceLoss() override { + buffer->OnMapRequestCompleted(id, WGPUBufferMapAsyncStatus_DeviceLost); + } + ~MapRequestTask() override = default; + + private: + Ref<BufferBase> buffer; + MapRequestID id; + }; + + class ErrorBuffer final : public BufferBase { + public: + ErrorBuffer(DeviceBase* device, const BufferDescriptor* descriptor) + : BufferBase(device, descriptor, ObjectBase::kError) { + if (descriptor->mappedAtCreation) { + // Check that the size can be used to allocate an mFakeMappedData. A malloc(0) + // is invalid, and on 32bit systems we should avoid a narrowing conversion that + // would make size = 1 << 32 + 1 allocate one byte. + bool isValidSize = + descriptor->size != 0 && + descriptor->size < uint64_t(std::numeric_limits<size_t>::max()); + + if (isValidSize) { + mFakeMappedData = + std::unique_ptr<uint8_t[]>(AllocNoThrow<uint8_t>(descriptor->size)); + } + // Since error buffers in this case may allocate memory, we need to track them + // for destruction on the device. + TrackInDevice(); + } + } + + private: + bool IsCPUWritableAtCreation() const override { + UNREACHABLE(); + } + + MaybeError MapAtCreationImpl() override { + UNREACHABLE(); + } + + MaybeError MapAsyncImpl(wgpu::MapMode mode, size_t offset, size_t size) override { + UNREACHABLE(); + } + + void* GetMappedPointerImpl() override { + return mFakeMappedData.get(); + } + + void UnmapImpl() override { + mFakeMappedData.reset(); + } + + std::unique_ptr<uint8_t[]> mFakeMappedData; + }; + + } // anonymous namespace + + MaybeError ValidateBufferDescriptor(DeviceBase*, const BufferDescriptor* descriptor) { + DAWN_INVALID_IF(descriptor->nextInChain != nullptr, "nextInChain must be nullptr"); + DAWN_TRY(ValidateBufferUsage(descriptor->usage)); + + wgpu::BufferUsage usage = descriptor->usage; + + DAWN_INVALID_IF(usage == wgpu::BufferUsage::None, "Buffer usages must not be 0."); + + const wgpu::BufferUsage kMapWriteAllowedUsages = + wgpu::BufferUsage::MapWrite | wgpu::BufferUsage::CopySrc; + DAWN_INVALID_IF( + usage & wgpu::BufferUsage::MapWrite && !IsSubset(usage, kMapWriteAllowedUsages), + "Buffer usages (%s) is invalid. If a buffer usage contains %s the only other allowed " + "usage is %s.", + usage, wgpu::BufferUsage::MapWrite, wgpu::BufferUsage::CopySrc); + + const wgpu::BufferUsage kMapReadAllowedUsages = + wgpu::BufferUsage::MapRead | wgpu::BufferUsage::CopyDst; + DAWN_INVALID_IF( + usage & wgpu::BufferUsage::MapRead && !IsSubset(usage, kMapReadAllowedUsages), + "Buffer usages (%s) is invalid. If a buffer usage contains %s the only other allowed " + "usage is %s.", + usage, wgpu::BufferUsage::MapRead, wgpu::BufferUsage::CopyDst); + + DAWN_INVALID_IF(descriptor->mappedAtCreation && descriptor->size % 4 != 0, + "Buffer is mapped at creation but its size (%u) is not a multiple of 4.", + descriptor->size); + + return {}; + } + + // Buffer + + BufferBase::BufferBase(DeviceBase* device, const BufferDescriptor* descriptor) + : ApiObjectBase(device, descriptor->label), + mSize(descriptor->size), + mUsage(descriptor->usage), + mState(BufferState::Unmapped) { + // Add readonly storage usage if the buffer has a storage usage. The validation rules in + // ValidateSyncScopeResourceUsage will make sure we don't use both at the same time. + if (mUsage & wgpu::BufferUsage::Storage) { + mUsage |= kReadOnlyStorageBuffer; + } + + // The query resolve buffer need to be used as a storage buffer in the internal compute + // pipeline which does timestamp uint conversion for timestamp query, it requires the buffer + // has Storage usage in the binding group. Implicitly add an InternalStorage usage which is + // only compatible with InternalStorageBuffer binding type in BGL. It shouldn't be + // compatible with StorageBuffer binding type and the query resolve buffer cannot be bound + // as storage buffer if it's created without Storage usage. + if (mUsage & wgpu::BufferUsage::QueryResolve) { + mUsage |= kInternalStorageBuffer; + } + + // We also add internal storage usage for Indirect buffers for some transformations before + // DispatchIndirect calls on the backend (e.g. validations, support of [[num_workgroups]] on + // D3D12), since these transformations involve binding them as storage buffers for use in a + // compute pass. + if (mUsage & wgpu::BufferUsage::Indirect) { + mUsage |= kInternalStorageBuffer; + } + + TrackInDevice(); + } + + BufferBase::BufferBase(DeviceBase* device, + const BufferDescriptor* descriptor, + ObjectBase::ErrorTag tag) + : ApiObjectBase(device, tag), mSize(descriptor->size), mState(BufferState::Unmapped) { + if (descriptor->mappedAtCreation) { + mState = BufferState::MappedAtCreation; + mMapOffset = 0; + mMapSize = mSize; + } + } + + BufferBase::BufferBase(DeviceBase* device, BufferState state) + : ApiObjectBase(device, kLabelNotImplemented), mState(state) { + TrackInDevice(); + } + + BufferBase::~BufferBase() { + ASSERT(mState == BufferState::Unmapped || mState == BufferState::Destroyed); + } + + void BufferBase::DestroyImpl() { + if (mState == BufferState::Mapped) { + UnmapInternal(WGPUBufferMapAsyncStatus_DestroyedBeforeCallback); + } else if (mState == BufferState::MappedAtCreation) { + if (mStagingBuffer != nullptr) { + mStagingBuffer.reset(); + } else if (mSize != 0) { + UnmapInternal(WGPUBufferMapAsyncStatus_DestroyedBeforeCallback); + } + } + mState = BufferState::Destroyed; + } + + // static + BufferBase* BufferBase::MakeError(DeviceBase* device, const BufferDescriptor* descriptor) { + return new ErrorBuffer(device, descriptor); + } + + ObjectType BufferBase::GetType() const { + return ObjectType::Buffer; + } + + uint64_t BufferBase::GetSize() const { + ASSERT(!IsError()); + return mSize; + } + + uint64_t BufferBase::GetAllocatedSize() const { + ASSERT(!IsError()); + // The backend must initialize this value. + ASSERT(mAllocatedSize != 0); + return mAllocatedSize; + } + + wgpu::BufferUsage BufferBase::GetUsage() const { + ASSERT(!IsError()); + return mUsage; + } + + MaybeError BufferBase::MapAtCreation() { + DAWN_TRY(MapAtCreationInternal()); + + void* ptr; + size_t size; + if (mSize == 0) { + return {}; + } else if (mStagingBuffer) { + // If there is a staging buffer for initialization, clear its contents directly. + // It should be exactly as large as the buffer allocation. + ptr = mStagingBuffer->GetMappedPointer(); + size = mStagingBuffer->GetSize(); + ASSERT(size == GetAllocatedSize()); + } else { + // Otherwise, the buffer is directly mappable on the CPU. + ptr = GetMappedPointerImpl(); + size = GetAllocatedSize(); + } + + DeviceBase* device = GetDevice(); + if (device->IsToggleEnabled(Toggle::LazyClearResourceOnFirstUse)) { + memset(ptr, uint8_t(0u), size); + SetIsDataInitialized(); + device->IncrementLazyClearCountForTesting(); + } else if (device->IsToggleEnabled(Toggle::NonzeroClearResourcesOnCreationForTesting)) { + memset(ptr, uint8_t(1u), size); + } + + return {}; + } + + MaybeError BufferBase::MapAtCreationInternal() { + ASSERT(!IsError()); + mMapOffset = 0; + mMapSize = mSize; + + // 0-sized buffers are not supposed to be written to. Return back any non-null pointer. + // Skip handling 0-sized buffers so we don't try to map them in the backend. + if (mSize != 0) { + // Mappable buffers don't use a staging buffer and are just as if mapped through + // MapAsync. + if (IsCPUWritableAtCreation()) { + DAWN_TRY(MapAtCreationImpl()); + } else { + // If any of these fail, the buffer will be deleted and replaced with an error + // buffer. The staging buffer is used to return mappable data to inititalize the + // buffer contents. Allocate one as large as the real buffer size so that every byte + // is initialized. + // TODO(crbug.com/dawn/828): Suballocate and reuse memory from a larger staging + // buffer so we don't create many small buffers. + DAWN_TRY_ASSIGN(mStagingBuffer, + GetDevice()->CreateStagingBuffer(GetAllocatedSize())); + } + } + + // Only set the state to mapped at creation if we did no fail any point in this helper. + // Otherwise, if we override the default unmapped state before succeeding to create a + // staging buffer, we will have issues when we try to destroy the buffer. + mState = BufferState::MappedAtCreation; + return {}; + } + + MaybeError BufferBase::ValidateCanUseOnQueueNow() const { + ASSERT(!IsError()); + + switch (mState) { + case BufferState::Destroyed: + return DAWN_FORMAT_VALIDATION_ERROR("%s used in submit while destroyed.", this); + case BufferState::Mapped: + case BufferState::MappedAtCreation: + return DAWN_FORMAT_VALIDATION_ERROR("%s used in submit while mapped.", this); + case BufferState::Unmapped: + return {}; + } + UNREACHABLE(); + } + + void BufferBase::CallMapCallback(MapRequestID mapID, WGPUBufferMapAsyncStatus status) { + ASSERT(!IsError()); + if (mMapCallback != nullptr && mapID == mLastMapID) { + // Tag the callback as fired before firing it, otherwise it could fire a second time if + // for example buffer.Unmap() is called inside the application-provided callback. + WGPUBufferMapCallback callback = mMapCallback; + mMapCallback = nullptr; + + if (GetDevice()->IsLost()) { + callback(WGPUBufferMapAsyncStatus_DeviceLost, mMapUserdata); + } else { + callback(status, mMapUserdata); + } + } + } + + void BufferBase::APIMapAsync(wgpu::MapMode mode, + size_t offset, + size_t size, + WGPUBufferMapCallback callback, + void* userdata) { + // Handle the defaulting of size required by WebGPU, even if in webgpu_cpp.h it is not + // possible to default the function argument (because there is the callback later in the + // argument list) + if ((size == wgpu::kWholeMapSize) && (offset <= mSize)) { + size = mSize - offset; + } + + WGPUBufferMapAsyncStatus status; + if (GetDevice()->ConsumedError(ValidateMapAsync(mode, offset, size, &status), + "calling %s.MapAsync(%s, %u, %u, ...).", this, mode, offset, + size)) { + if (callback) { + callback(status, userdata); + } + return; + } + ASSERT(!IsError()); + + mLastMapID++; + mMapMode = mode; + mMapOffset = offset; + mMapSize = size; + mMapCallback = callback; + mMapUserdata = userdata; + mState = BufferState::Mapped; + + if (GetDevice()->ConsumedError(MapAsyncImpl(mode, offset, size))) { + CallMapCallback(mLastMapID, WGPUBufferMapAsyncStatus_DeviceLost); + return; + } + std::unique_ptr<MapRequestTask> request = + std::make_unique<MapRequestTask>(this, mLastMapID); + GetDevice()->GetQueue()->TrackTask(std::move(request), + GetDevice()->GetPendingCommandSerial()); + } + + void* BufferBase::APIGetMappedRange(size_t offset, size_t size) { + return GetMappedRange(offset, size, true); + } + + const void* BufferBase::APIGetConstMappedRange(size_t offset, size_t size) { + return GetMappedRange(offset, size, false); + } + + void* BufferBase::GetMappedRange(size_t offset, size_t size, bool writable) { + if (!CanGetMappedRange(writable, offset, size)) { + return nullptr; + } + + if (mStagingBuffer != nullptr) { + return static_cast<uint8_t*>(mStagingBuffer->GetMappedPointer()) + offset; + } + if (mSize == 0) { + return reinterpret_cast<uint8_t*>(intptr_t(0xCAFED00D)); + } + uint8_t* start = static_cast<uint8_t*>(GetMappedPointerImpl()); + return start == nullptr ? nullptr : start + offset; + } + + void BufferBase::APIDestroy() { + Destroy(); + } + + MaybeError BufferBase::CopyFromStagingBuffer() { + ASSERT(mStagingBuffer); + if (mSize == 0) { + // Staging buffer is not created if zero size. + ASSERT(mStagingBuffer == nullptr); + return {}; + } + + DAWN_TRY(GetDevice()->CopyFromStagingToBuffer(mStagingBuffer.get(), 0, this, 0, + GetAllocatedSize())); + + DynamicUploader* uploader = GetDevice()->GetDynamicUploader(); + uploader->ReleaseStagingBuffer(std::move(mStagingBuffer)); + + return {}; + } + + void BufferBase::APIUnmap() { + if (GetDevice()->ConsumedError(ValidateUnmap(), "calling %s.Unmap().", this)) { + return; + } + Unmap(); + } + + void BufferBase::Unmap() { + UnmapInternal(WGPUBufferMapAsyncStatus_UnmappedBeforeCallback); + } + + void BufferBase::UnmapInternal(WGPUBufferMapAsyncStatus callbackStatus) { + if (mState == BufferState::Mapped) { + // A map request can only be called once, so this will fire only if the request wasn't + // completed before the Unmap. + // Callbacks are not fired if there is no callback registered, so this is correct for + // mappedAtCreation = true. + CallMapCallback(mLastMapID, callbackStatus); + UnmapImpl(); + + mMapCallback = nullptr; + mMapUserdata = 0; + } else if (mState == BufferState::MappedAtCreation) { + if (mStagingBuffer != nullptr) { + GetDevice()->ConsumedError(CopyFromStagingBuffer()); + } else if (mSize != 0) { + UnmapImpl(); + } + } + + mState = BufferState::Unmapped; + } + + MaybeError BufferBase::ValidateMapAsync(wgpu::MapMode mode, + size_t offset, + size_t size, + WGPUBufferMapAsyncStatus* status) const { + *status = WGPUBufferMapAsyncStatus_DeviceLost; + DAWN_TRY(GetDevice()->ValidateIsAlive()); + + *status = WGPUBufferMapAsyncStatus_Error; + DAWN_TRY(GetDevice()->ValidateObject(this)); + + DAWN_INVALID_IF(uint64_t(offset) > mSize, + "Mapping offset (%u) is larger than the size (%u) of %s.", offset, mSize, + this); + + DAWN_INVALID_IF(offset % 8 != 0, "Offset (%u) must be a multiple of 8.", offset); + DAWN_INVALID_IF(size % 4 != 0, "Size (%u) must be a multiple of 4.", size); + + DAWN_INVALID_IF(uint64_t(size) > mSize - uint64_t(offset), + "Mapping range (offset:%u, size: %u) doesn't fit in the size (%u) of %s.", + offset, size, mSize, this); + + switch (mState) { + case BufferState::Mapped: + case BufferState::MappedAtCreation: + return DAWN_FORMAT_VALIDATION_ERROR("%s is already mapped.", this); + case BufferState::Destroyed: + return DAWN_FORMAT_VALIDATION_ERROR("%s is destroyed.", this); + case BufferState::Unmapped: + break; + } + + bool isReadMode = mode & wgpu::MapMode::Read; + bool isWriteMode = mode & wgpu::MapMode::Write; + DAWN_INVALID_IF(!(isReadMode ^ isWriteMode), "Map mode (%s) is not one of %s or %s.", mode, + wgpu::MapMode::Write, wgpu::MapMode::Read); + + if (mode & wgpu::MapMode::Read) { + DAWN_INVALID_IF(!(mUsage & wgpu::BufferUsage::MapRead), + "The buffer usages (%s) do not contain %s.", mUsage, + wgpu::BufferUsage::MapRead); + } else { + ASSERT(mode & wgpu::MapMode::Write); + DAWN_INVALID_IF(!(mUsage & wgpu::BufferUsage::MapWrite), + "The buffer usages (%s) do not contain %s.", mUsage, + wgpu::BufferUsage::MapWrite); + } + + *status = WGPUBufferMapAsyncStatus_Success; + return {}; + } + + bool BufferBase::CanGetMappedRange(bool writable, size_t offset, size_t size) const { + if (offset % 8 != 0 || size % 4 != 0) { + return false; + } + + if (size > mMapSize || offset < mMapOffset) { + return false; + } + + size_t offsetInMappedRange = offset - mMapOffset; + if (offsetInMappedRange > mMapSize - size) { + return false; + } + + // Note that: + // + // - We don't check that the device is alive because the application can ask for the + // mapped pointer before it knows, and even Dawn knows, that the device was lost, and + // still needs to work properly. + // - We don't check that the object is alive because we need to return mapped pointers + // for error buffers too. + + switch (mState) { + // Writeable Buffer::GetMappedRange is always allowed when mapped at creation. + case BufferState::MappedAtCreation: + return true; + + case BufferState::Mapped: + ASSERT(bool(mMapMode & wgpu::MapMode::Read) ^ + bool(mMapMode & wgpu::MapMode::Write)); + return !writable || (mMapMode & wgpu::MapMode::Write); + + case BufferState::Unmapped: + case BufferState::Destroyed: + return false; + } + UNREACHABLE(); + } + + MaybeError BufferBase::ValidateUnmap() const { + DAWN_TRY(GetDevice()->ValidateIsAlive()); + + switch (mState) { + case BufferState::Mapped: + case BufferState::MappedAtCreation: + // A buffer may be in the Mapped state if it was created with mappedAtCreation + // even if it did not have a mappable usage. + return {}; + case BufferState::Unmapped: + return DAWN_FORMAT_VALIDATION_ERROR("%s is unmapped.", this); + case BufferState::Destroyed: + return DAWN_FORMAT_VALIDATION_ERROR("%s is destroyed.", this); + } + UNREACHABLE(); + } + + void BufferBase::OnMapRequestCompleted(MapRequestID mapID, WGPUBufferMapAsyncStatus status) { + CallMapCallback(mapID, status); + } + + bool BufferBase::NeedsInitialization() const { + return !mIsDataInitialized && + GetDevice()->IsToggleEnabled(Toggle::LazyClearResourceOnFirstUse); + } + + bool BufferBase::IsDataInitialized() const { + return mIsDataInitialized; + } + + void BufferBase::SetIsDataInitialized() { + mIsDataInitialized = true; + } + + bool BufferBase::IsFullBufferRange(uint64_t offset, uint64_t size) const { + return offset == 0 && size == GetSize(); + } + +} // namespace dawn::native
diff --git a/src/dawn/native/Buffer.h b/src/dawn/native/Buffer.h new file mode 100644 index 0000000..2a9759f --- /dev/null +++ b/src/dawn/native/Buffer.h
@@ -0,0 +1,135 @@ +// Copyright 2017 The Dawn Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#ifndef DAWNNATIVE_BUFFER_H_ +#define DAWNNATIVE_BUFFER_H_ + +#include "dawn/native/Error.h" +#include "dawn/native/Forward.h" +#include "dawn/native/IntegerTypes.h" +#include "dawn/native/ObjectBase.h" + +#include "dawn/native/dawn_platform.h" + +#include <memory> + +namespace dawn::native { + + struct CopyTextureToBufferCmd; + + enum class MapType : uint32_t; + + MaybeError ValidateBufferDescriptor(DeviceBase* device, const BufferDescriptor* descriptor); + + static constexpr wgpu::BufferUsage kReadOnlyBufferUsages = + wgpu::BufferUsage::MapRead | wgpu::BufferUsage::CopySrc | wgpu::BufferUsage::Index | + wgpu::BufferUsage::Vertex | wgpu::BufferUsage::Uniform | kReadOnlyStorageBuffer | + wgpu::BufferUsage::Indirect; + + static constexpr wgpu::BufferUsage kMappableBufferUsages = + wgpu::BufferUsage::MapRead | wgpu::BufferUsage::MapWrite; + + class BufferBase : public ApiObjectBase { + public: + enum class BufferState { + Unmapped, + Mapped, + MappedAtCreation, + Destroyed, + }; + BufferBase(DeviceBase* device, const BufferDescriptor* descriptor); + + static BufferBase* MakeError(DeviceBase* device, const BufferDescriptor* descriptor); + + ObjectType GetType() const override; + + uint64_t GetSize() const; + uint64_t GetAllocatedSize() const; + wgpu::BufferUsage GetUsage() const; + + MaybeError MapAtCreation(); + void OnMapRequestCompleted(MapRequestID mapID, WGPUBufferMapAsyncStatus status); + + MaybeError ValidateCanUseOnQueueNow() const; + + bool IsFullBufferRange(uint64_t offset, uint64_t size) const; + bool NeedsInitialization() const; + bool IsDataInitialized() const; + void SetIsDataInitialized(); + + void* GetMappedRange(size_t offset, size_t size, bool writable = true); + void Unmap(); + + // Dawn API + void APIMapAsync(wgpu::MapMode mode, + size_t offset, + size_t size, + WGPUBufferMapCallback callback, + void* userdata); + void* APIGetMappedRange(size_t offset, size_t size); + const void* APIGetConstMappedRange(size_t offset, size_t size); + void APIUnmap(); + void APIDestroy(); + + protected: + BufferBase(DeviceBase* device, + const BufferDescriptor* descriptor, + ObjectBase::ErrorTag tag); + + // Constructor used only for mocking and testing. + BufferBase(DeviceBase* device, BufferState state); + void DestroyImpl() override; + + ~BufferBase() override; + + MaybeError MapAtCreationInternal(); + + uint64_t mAllocatedSize = 0; + + private: + virtual MaybeError MapAtCreationImpl() = 0; + virtual MaybeError MapAsyncImpl(wgpu::MapMode mode, size_t offset, size_t size) = 0; + virtual void UnmapImpl() = 0; + virtual void* GetMappedPointerImpl() = 0; + + virtual bool IsCPUWritableAtCreation() const = 0; + MaybeError CopyFromStagingBuffer(); + void CallMapCallback(MapRequestID mapID, WGPUBufferMapAsyncStatus status); + + MaybeError ValidateMapAsync(wgpu::MapMode mode, + size_t offset, + size_t size, + WGPUBufferMapAsyncStatus* status) const; + MaybeError ValidateUnmap() const; + bool CanGetMappedRange(bool writable, size_t offset, size_t size) const; + void UnmapInternal(WGPUBufferMapAsyncStatus callbackStatus); + + uint64_t mSize = 0; + wgpu::BufferUsage mUsage = wgpu::BufferUsage::None; + BufferState mState; + bool mIsDataInitialized = false; + + std::unique_ptr<StagingBufferBase> mStagingBuffer; + + WGPUBufferMapCallback mMapCallback = nullptr; + void* mMapUserdata = 0; + MapRequestID mLastMapID = MapRequestID(0); + wgpu::MapMode mMapMode = wgpu::MapMode::None; + size_t mMapOffset = 0; + size_t mMapSize = 0; + }; + +} // namespace dawn::native + +#endif // DAWNNATIVE_BUFFER_H_
diff --git a/src/dawn/native/CMakeLists.txt b/src/dawn/native/CMakeLists.txt new file mode 100644 index 0000000..90610a4 --- /dev/null +++ b/src/dawn/native/CMakeLists.txt
@@ -0,0 +1,556 @@ +# Copyright 2020 The Dawn Authors +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +DawnJSONGenerator( + TARGET "native_utils" + PRINT_NAME "Dawn native utilities" + RESULT_VARIABLE "DAWN_NATIVE_UTILS_GEN_SOURCES" +) + +add_library(dawn_native ${DAWN_DUMMY_FILE}) + +target_compile_definitions(dawn_native PRIVATE "DAWN_NATIVE_IMPLEMENTATION") +if(BUILD_SHARED_LIBS) + target_compile_definitions(dawn_native PRIVATE "DAWN_NATIVE_SHARED_LIBRARY") +endif() + +target_sources(dawn_native PRIVATE + "${DAWN_INCLUDE_DIR}/dawn/native/DawnNative.h" + "${DAWN_INCLUDE_DIR}/dawn/native/dawn_native_export.h" + ${DAWN_NATIVE_UTILS_GEN_SOURCES} + "Adapter.cpp" + "Adapter.h" + "AsyncTask.cpp" + "AsyncTask.h" + "AttachmentState.cpp" + "AttachmentState.h" + "BackendConnection.cpp" + "BackendConnection.h" + "BindGroup.cpp" + "BindGroup.h" + "BindGroupLayout.cpp" + "BindGroupLayout.h" + "BindGroupTracker.h" + "BindingInfo.cpp" + "BindingInfo.h" + "BuddyAllocator.cpp" + "BuddyAllocator.h" + "BuddyMemoryAllocator.cpp" + "BuddyMemoryAllocator.h" + "Buffer.cpp" + "Buffer.h" + "CachedObject.cpp" + "CachedObject.h" + "CacheKey.cpp" + "CacheKey.h" + "CallbackTaskManager.cpp" + "CallbackTaskManager.h" + "CommandAllocator.cpp" + "CommandAllocator.h" + "CommandBuffer.cpp" + "CommandBuffer.h" + "CommandBufferStateTracker.cpp" + "CommandBufferStateTracker.h" + "CommandEncoder.cpp" + "CommandEncoder.h" + "CommandValidation.cpp" + "CommandValidation.h" + "Commands.cpp" + "Commands.h" + "CompilationMessages.cpp" + "CompilationMessages.h" + "ComputePassEncoder.cpp" + "ComputePassEncoder.h" + "ComputePipeline.cpp" + "ComputePipeline.h" + "CopyTextureForBrowserHelper.cpp" + "CopyTextureForBrowserHelper.h" + "CreatePipelineAsyncTask.cpp" + "CreatePipelineAsyncTask.h" + "Device.cpp" + "Device.h" + "DynamicUploader.cpp" + "DynamicUploader.h" + "EncodingContext.cpp" + "EncodingContext.h" + "EnumClassBitmasks.h" + "EnumMaskIterator.h" + "Error.cpp" + "Error.h" + "ErrorData.cpp" + "ErrorData.h" + "ErrorInjector.cpp" + "ErrorInjector.h" + "ErrorScope.cpp" + "ErrorScope.h" + "Features.cpp" + "Features.h" + "ExternalTexture.cpp" + "ExternalTexture.h" + "IndirectDrawMetadata.cpp" + "IndirectDrawMetadata.h" + "IndirectDrawValidationEncoder.cpp" + "IndirectDrawValidationEncoder.h" + "ObjectContentHasher.cpp" + "ObjectContentHasher.h" + "Format.cpp" + "Format.h" + "Forward.h" + "Instance.cpp" + "Instance.h" + "InternalPipelineStore.cpp" + "InternalPipelineStore.h" + "IntegerTypes.h" + "Limits.cpp" + "Limits.h" + "ObjectBase.cpp" + "ObjectBase.h" + "PassResourceUsage.h" + "PassResourceUsageTracker.cpp" + "PassResourceUsageTracker.h" + "PersistentCache.cpp" + "PersistentCache.h" + "PerStage.cpp" + "PerStage.h" + "Pipeline.cpp" + "Pipeline.h" + "PipelineLayout.cpp" + "PipelineLayout.h" + "PooledResourceMemoryAllocator.cpp" + "PooledResourceMemoryAllocator.h" + "ProgrammableEncoder.cpp" + "ProgrammableEncoder.h" + "QueryHelper.cpp" + "QueryHelper.h" + "QuerySet.cpp" + "QuerySet.h" + "Queue.cpp" + "Queue.h" + "RenderBundle.cpp" + "RenderBundle.h" + "RenderBundleEncoder.cpp" + "RenderBundleEncoder.h" + "RenderEncoderBase.cpp" + "RenderEncoderBase.h" + "RenderPassEncoder.cpp" + "RenderPassEncoder.h" + "RenderPipeline.cpp" + "RenderPipeline.h" + "ResourceHeap.h" + "ResourceHeapAllocator.h" + "ResourceMemoryAllocation.cpp" + "ResourceMemoryAllocation.h" + "RingBufferAllocator.cpp" + "RingBufferAllocator.h" + "Sampler.cpp" + "Sampler.h" + "ScratchBuffer.cpp" + "ScratchBuffer.h" + "ShaderModule.cpp" + "ShaderModule.h" + "StagingBuffer.cpp" + "StagingBuffer.h" + "Subresource.cpp" + "Subresource.h" + "SubresourceStorage.h" + "Surface.cpp" + "Surface.h" + "SwapChain.cpp" + "SwapChain.h" + "Texture.cpp" + "Texture.h" + "TintUtils.cpp" + "TintUtils.h" + "ToBackend.h" + "Toggles.cpp" + "Toggles.h" + "VertexFormat.cpp" + "VertexFormat.h" + "dawn_platform.h" + "webgpu_absl_format.cpp" + "webgpu_absl_format.h" + "utils/WGPUHelpers.cpp" + "utils/WGPUHelpers.h" +) +target_link_libraries(dawn_native + PUBLIC dawncpp_headers + PRIVATE dawn_common + dawn_platform + dawn_internal_config + libtint + SPIRV-Tools-opt + absl_strings + absl_str_format_internal +) + +target_include_directories(dawn_native PRIVATE ${DAWN_ABSEIL_DIR}) + +if (DAWN_USE_X11) + find_package(X11 REQUIRED) + target_link_libraries(dawn_native PRIVATE ${X11_LIBRARIES}) + target_include_directories(dawn_native PRIVATE ${X11_INCLUDE_DIR}) + target_sources(dawn_native PRIVATE + "XlibXcbFunctions.cpp" + "XlibXcbFunctions.h" + ) +endif() + +# Only win32 app needs to link with user32.lib +# In UWP, all availiable APIs are defined in WindowsApp.lib +# and is automatically linked when WINDOWS_STORE set +if (WIN32 AND NOT WINDOWS_STORE) + target_link_libraries(dawn_native PRIVATE user32.lib) +endif() + +# DXGIGetDebugInterface1 is defined in dxgi.lib +# But this API is tagged as a development-only capability +# which implies that linking to this function will cause +# the application to fail Windows store certification +# So we only link to it in debug build when compiling for UWP. +# In win32 we load dxgi.dll using LoadLibrary +# so no need for static linking. +if (WINDOWS_STORE) + target_link_libraries(dawn_native PRIVATE debug dxgi.lib) +endif() + +if (DAWN_ENABLE_D3D12) + target_sources(dawn_native PRIVATE + "${DAWN_INCLUDE_DIR}/dawn/native/D3D12Backend.h" + "d3d12/AdapterD3D12.cpp" + "d3d12/AdapterD3D12.h" + "d3d12/BackendD3D12.cpp" + "d3d12/BackendD3D12.h" + "d3d12/BindGroupD3D12.cpp" + "d3d12/BindGroupD3D12.h" + "d3d12/BindGroupLayoutD3D12.cpp" + "d3d12/BindGroupLayoutD3D12.h" + "d3d12/BufferD3D12.cpp" + "d3d12/BufferD3D12.h" + "d3d12/CPUDescriptorHeapAllocationD3D12.cpp" + "d3d12/CPUDescriptorHeapAllocationD3D12.h" + "d3d12/CommandAllocatorManager.cpp" + "d3d12/CommandAllocatorManager.h" + "d3d12/CommandBufferD3D12.cpp" + "d3d12/CommandBufferD3D12.h" + "d3d12/CommandRecordingContext.cpp" + "d3d12/CommandRecordingContext.h" + "d3d12/ComputePipelineD3D12.cpp" + "d3d12/ComputePipelineD3D12.h" + "d3d12/D3D11on12Util.cpp" + "d3d12/D3D11on12Util.h" + "d3d12/D3D12Error.cpp" + "d3d12/D3D12Error.h" + "d3d12/D3D12Info.cpp" + "d3d12/D3D12Info.h" + "d3d12/DeviceD3D12.cpp" + "d3d12/DeviceD3D12.h" + "d3d12/Forward.h" + "d3d12/GPUDescriptorHeapAllocationD3D12.cpp" + "d3d12/GPUDescriptorHeapAllocationD3D12.h" + "d3d12/HeapAllocatorD3D12.cpp" + "d3d12/HeapAllocatorD3D12.h" + "d3d12/HeapD3D12.cpp" + "d3d12/HeapD3D12.h" + "d3d12/IntegerTypes.h" + "d3d12/NativeSwapChainImplD3D12.cpp" + "d3d12/NativeSwapChainImplD3D12.h" + "d3d12/PageableD3D12.cpp" + "d3d12/PageableD3D12.h" + "d3d12/PipelineLayoutD3D12.cpp" + "d3d12/PipelineLayoutD3D12.h" + "d3d12/PlatformFunctions.cpp" + "d3d12/PlatformFunctions.h" + "d3d12/QuerySetD3D12.cpp" + "d3d12/QuerySetD3D12.h" + "d3d12/QueueD3D12.cpp" + "d3d12/QueueD3D12.h" + "d3d12/RenderPassBuilderD3D12.cpp" + "d3d12/RenderPassBuilderD3D12.h" + "d3d12/RenderPipelineD3D12.cpp" + "d3d12/RenderPipelineD3D12.h" + "d3d12/ResidencyManagerD3D12.cpp" + "d3d12/ResidencyManagerD3D12.h" + "d3d12/ResourceAllocatorManagerD3D12.cpp" + "d3d12/ResourceAllocatorManagerD3D12.h" + "d3d12/ResourceHeapAllocationD3D12.cpp" + "d3d12/ResourceHeapAllocationD3D12.h" + "d3d12/SamplerD3D12.cpp" + "d3d12/SamplerD3D12.h" + "d3d12/SamplerHeapCacheD3D12.cpp" + "d3d12/SamplerHeapCacheD3D12.h" + "d3d12/ShaderModuleD3D12.cpp" + "d3d12/ShaderModuleD3D12.h" + "d3d12/ShaderVisibleDescriptorAllocatorD3D12.cpp" + "d3d12/ShaderVisibleDescriptorAllocatorD3D12.h" + "d3d12/StagingBufferD3D12.cpp" + "d3d12/StagingBufferD3D12.h" + "d3d12/StagingDescriptorAllocatorD3D12.cpp" + "d3d12/StagingDescriptorAllocatorD3D12.h" + "d3d12/SwapChainD3D12.cpp" + "d3d12/SwapChainD3D12.h" + "d3d12/TextureCopySplitter.cpp" + "d3d12/TextureCopySplitter.h" + "d3d12/TextureD3D12.cpp" + "d3d12/TextureD3D12.h" + "d3d12/UtilsD3D12.cpp" + "d3d12/UtilsD3D12.h" + "d3d12/d3d12_platform.h" + ) + target_link_libraries(dawn_native PRIVATE dxguid.lib) +endif() + +if (DAWN_ENABLE_METAL) + target_sources(dawn_native PRIVATE + "${DAWN_INCLUDE_DIR}/dawn/native/MetalBackend.h" + "Surface_metal.mm" + "metal/BackendMTL.h" + "metal/BackendMTL.mm" + "metal/BindGroupLayoutMTL.h" + "metal/BindGroupLayoutMTL.mm" + "metal/BindGroupMTL.h" + "metal/BindGroupMTL.mm" + "metal/BufferMTL.h" + "metal/BufferMTL.mm" + "metal/CommandBufferMTL.h" + "metal/CommandBufferMTL.mm" + "metal/CommandRecordingContext.h" + "metal/CommandRecordingContext.mm" + "metal/ComputePipelineMTL.h" + "metal/ComputePipelineMTL.mm" + "metal/DeviceMTL.h" + "metal/DeviceMTL.mm" + "metal/Forward.h" + "metal/PipelineLayoutMTL.h" + "metal/PipelineLayoutMTL.mm" + "metal/QueueMTL.h" + "metal/QueueMTL.mm" + "metal/QuerySetMTL.h" + "metal/QuerySetMTL.mm" + "metal/RenderPipelineMTL.h" + "metal/RenderPipelineMTL.mm" + "metal/SamplerMTL.h" + "metal/SamplerMTL.mm" + "metal/ShaderModuleMTL.h" + "metal/ShaderModuleMTL.mm" + "metal/StagingBufferMTL.h" + "metal/StagingBufferMTL.mm" + "metal/SwapChainMTL.h" + "metal/SwapChainMTL.mm" + "metal/TextureMTL.h" + "metal/TextureMTL.mm" + "metal/UtilsMetal.h" + "metal/UtilsMetal.mm" + ) + target_link_libraries(dawn_native PRIVATE + "-framework Cocoa" + "-framework IOKit" + "-framework IOSurface" + "-framework QuartzCore" + "-framework Metal" + ) +endif() + +if (DAWN_ENABLE_NULL) + target_sources(dawn_native PRIVATE + "${DAWN_INCLUDE_DIR}/dawn/native/NullBackend.h" + "null/DeviceNull.cpp" + "null/DeviceNull.h" + ) +endif() + +if (DAWN_ENABLE_OPENGL OR DAWN_ENABLE_VULKAN) + target_sources(dawn_native PRIVATE + "SpirvValidation.cpp" + "SpirvValidation.h" + ) +endif() + +if (DAWN_ENABLE_OPENGL) + DawnGenerator( + SCRIPT "${Dawn_SOURCE_DIR}/generator/opengl_loader_generator.py" + PRINT_NAME "OpenGL function loader" + ARGS "--gl-xml" + "${Dawn_SOURCE_DIR}/third_party/khronos/gl.xml" + "--supported-extensions" + "${Dawn_SOURCE_DIR}/src/dawn/native/opengl/supported_extensions.json" + RESULT_VARIABLE "DAWN_NATIVE_OPENGL_AUTOGEN_SOURCES" + ) + + target_sources(dawn_native PRIVATE + "${DAWN_INCLUDE_DIR}/dawn/native/OpenGLBackend.h" + ${DAWN_NATIVE_OPENGL_AUTOGEN_SOURCES} + "opengl/BackendGL.cpp" + "opengl/BackendGL.h" + "opengl/BindGroupGL.cpp" + "opengl/BindGroupGL.h" + "opengl/BindGroupLayoutGL.cpp" + "opengl/BindGroupLayoutGL.h" + "opengl/BufferGL.cpp" + "opengl/BufferGL.h" + "opengl/CommandBufferGL.cpp" + "opengl/CommandBufferGL.h" + "opengl/ComputePipelineGL.cpp" + "opengl/ComputePipelineGL.h" + "opengl/DeviceGL.cpp" + "opengl/DeviceGL.h" + "opengl/Forward.h" + "opengl/GLFormat.cpp" + "opengl/GLFormat.h" + "opengl/NativeSwapChainImplGL.cpp" + "opengl/NativeSwapChainImplGL.h" + "opengl/OpenGLFunctions.cpp" + "opengl/OpenGLFunctions.h" + "opengl/OpenGLVersion.cpp" + "opengl/OpenGLVersion.h" + "opengl/PersistentPipelineStateGL.cpp" + "opengl/PersistentPipelineStateGL.h" + "opengl/PipelineGL.cpp" + "opengl/PipelineGL.h" + "opengl/PipelineLayoutGL.cpp" + "opengl/PipelineLayoutGL.h" + "opengl/QuerySetGL.cpp" + "opengl/QuerySetGL.h" + "opengl/QueueGL.cpp" + "opengl/QueueGL.h" + "opengl/RenderPipelineGL.cpp" + "opengl/RenderPipelineGL.h" + "opengl/SamplerGL.cpp" + "opengl/SamplerGL.h" + "opengl/ShaderModuleGL.cpp" + "opengl/ShaderModuleGL.h" + "opengl/SwapChainGL.cpp" + "opengl/SwapChainGL.h" + "opengl/TextureGL.cpp" + "opengl/TextureGL.h" + "opengl/UtilsGL.cpp" + "opengl/UtilsGL.h" + "opengl/opengl_platform.h" + ) + + target_link_libraries(dawn_native PRIVATE dawn_khronos_platform) +endif() + +if (DAWN_ENABLE_VULKAN) + target_sources(dawn_native PRIVATE + "${DAWN_INCLUDE_DIR}/dawn/native/VulkanBackend.h" + "vulkan/AdapterVk.cpp" + "vulkan/AdapterVk.h" + "vulkan/BackendVk.cpp" + "vulkan/BackendVk.h" + "vulkan/BindGroupLayoutVk.cpp" + "vulkan/BindGroupLayoutVk.h" + "vulkan/BindGroupVk.cpp" + "vulkan/BindGroupVk.h" + "vulkan/BufferVk.cpp" + "vulkan/BufferVk.h" + "vulkan/CommandBufferVk.cpp" + "vulkan/CommandBufferVk.h" + "vulkan/CommandRecordingContext.h" + "vulkan/ComputePipelineVk.cpp" + "vulkan/ComputePipelineVk.h" + "vulkan/DescriptorSetAllocation.h" + "vulkan/DescriptorSetAllocator.cpp" + "vulkan/DescriptorSetAllocator.h" + "vulkan/DeviceVk.cpp" + "vulkan/DeviceVk.h" + "vulkan/ExternalHandle.h" + "vulkan/FencedDeleter.cpp" + "vulkan/FencedDeleter.h" + "vulkan/Forward.h" + "vulkan/NativeSwapChainImplVk.cpp" + "vulkan/NativeSwapChainImplVk.h" + "vulkan/PipelineLayoutVk.cpp" + "vulkan/PipelineLayoutVk.h" + "vulkan/QuerySetVk.cpp" + "vulkan/QuerySetVk.h" + "vulkan/QueueVk.cpp" + "vulkan/QueueVk.h" + "vulkan/RenderPassCache.cpp" + "vulkan/RenderPassCache.h" + "vulkan/RenderPipelineVk.cpp" + "vulkan/RenderPipelineVk.h" + "vulkan/ResourceHeapVk.cpp" + "vulkan/ResourceHeapVk.h" + "vulkan/ResourceMemoryAllocatorVk.cpp" + "vulkan/ResourceMemoryAllocatorVk.h" + "vulkan/SamplerVk.cpp" + "vulkan/SamplerVk.h" + "vulkan/ShaderModuleVk.cpp" + "vulkan/ShaderModuleVk.h" + "vulkan/StagingBufferVk.cpp" + "vulkan/StagingBufferVk.h" + "vulkan/SwapChainVk.cpp" + "vulkan/SwapChainVk.h" + "vulkan/TextureVk.cpp" + "vulkan/TextureVk.h" + "vulkan/UtilsVulkan.cpp" + "vulkan/UtilsVulkan.h" + "vulkan/VulkanError.cpp" + "vulkan/VulkanError.h" + "vulkan/VulkanExtensions.cpp" + "vulkan/VulkanExtensions.h" + "vulkan/VulkanFunctions.cpp" + "vulkan/VulkanFunctions.h" + "vulkan/VulkanInfo.cpp" + "vulkan/VulkanInfo.h" + "vulkan/external_memory/MemoryService.h" + "vulkan/external_semaphore/SemaphoreService.h" + ) + + target_link_libraries(dawn_native PUBLIC Vulkan-Headers) + + if (UNIX AND NOT APPLE) + target_sources(dawn_native PRIVATE + "vulkan/external_memory/MemoryServiceOpaqueFD.cpp" + "vulkan/external_semaphore/SemaphoreServiceFD.cpp" + ) + else() + target_sources(dawn_native PRIVATE + "vulkan/external_memory/MemoryServiceNull.cpp" + "vulkan/external_semaphore/SemaphoreServiceNull.cpp" + ) + endif() +endif() + +# TODO how to do the component build in CMake? +target_sources(dawn_native PRIVATE "DawnNative.cpp") +if (DAWN_ENABLE_D3D12) + target_sources(dawn_native PRIVATE "d3d12/D3D12Backend.cpp") +endif() +if (DAWN_ENABLE_METAL) + target_sources(dawn_native PRIVATE "metal/MetalBackend.mm") +endif() +if (DAWN_ENABLE_NULL) + target_sources(dawn_native PRIVATE "null/NullBackend.cpp") +endif() +if (DAWN_ENABLE_OPENGL) + target_sources(dawn_native PRIVATE "opengl/OpenGLBackend.cpp") +endif() +if (DAWN_ENABLE_VULKAN) + target_sources(dawn_native PRIVATE "vulkan/VulkanBackend.cpp") +endif() + +DawnJSONGenerator( + TARGET "webgpu_dawn_native_proc" + PRINT_NAME "Dawn native WebGPU procs" + RESULT_VARIABLE "WEBGPU_DAWN_NATIVE_PROC_GEN" +) + +add_library(webgpu_dawn ${DAWN_DUMMY_FILE}) +target_link_libraries(webgpu_dawn PRIVATE dawn_native) +target_compile_definitions(webgpu_dawn PRIVATE "WGPU_IMPLEMENTATION") +if(BUILD_SHARED_LIBS) + target_compile_definitions(webgpu_dawn PRIVATE "WGPU_SHARED_LIBRARY") +endif() +target_sources(webgpu_dawn PRIVATE ${WEBGPU_DAWN_NATIVE_PROC_GEN})
diff --git a/src/dawn/native/CacheKey.cpp b/src/dawn/native/CacheKey.cpp new file mode 100644 index 0000000..3495577 --- /dev/null +++ b/src/dawn/native/CacheKey.cpp
@@ -0,0 +1,32 @@ +// Copyright 2022 The Dawn Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include "dawn/native/CacheKey.h" + +namespace dawn::native { + + template <> + void CacheKeySerializer<std::string>::Serialize(CacheKey* key, const std::string& t) { + key->Record(static_cast<size_t>(t.length())); + key->insert(key->end(), t.begin(), t.end()); + } + + template <> + void CacheKeySerializer<CacheKey>::Serialize(CacheKey* key, const CacheKey& t) { + // For nested cache keys, we do not record the length, and just copy the key so that it + // appears we just flatten the keys into a single key. + key->insert(key->end(), t.begin(), t.end()); + } + +} // namespace dawn::native
diff --git a/src/dawn/native/CacheKey.h b/src/dawn/native/CacheKey.h new file mode 100644 index 0000000..ce21f6d --- /dev/null +++ b/src/dawn/native/CacheKey.h
@@ -0,0 +1,98 @@ +// Copyright 2022 The Dawn Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#ifndef DAWNNATIVE_CACHE_KEY_H_ +#define DAWNNATIVE_CACHE_KEY_H_ + +#include <limits> +#include <string> +#include <type_traits> +#include <vector> + +#include "dawn/common/Assert.h" + +namespace dawn::native { + + // Forward declare CacheKey class because of co-dependency. + class CacheKey; + + // Overridable serializer struct that should be implemented for cache key serializable + // types/classes. + template <typename T, typename SFINAE = void> + class CacheKeySerializer { + public: + static void Serialize(CacheKey* key, const T& t); + }; + + class CacheKey : public std::vector<uint8_t> { + public: + using std::vector<uint8_t>::vector; + + template <typename T> + CacheKey& Record(const T& t) { + CacheKeySerializer<T>::Serialize(this, t); + return *this; + } + template <typename T, typename... Args> + CacheKey& Record(const T& t, const Args&... args) { + CacheKeySerializer<T>::Serialize(this, t); + return Record(args...); + } + + // Records iterables by prepending the number of elements. Some common iterables are have a + // CacheKeySerializer implemented to avoid needing to split them out when recording, i.e. + // strings and CacheKeys, but they fundamentally do the same as this function. + template <typename IterableT> + CacheKey& RecordIterable(const IterableT& iterable) { + // Always record the size of generic iterables as a size_t for now. + Record(static_cast<size_t>(iterable.size())); + for (auto it = iterable.begin(); it != iterable.end(); ++it) { + Record(*it); + } + return *this; + } + template <typename Ptr> + CacheKey& RecordIterable(const Ptr* ptr, size_t n) { + Record(n); + for (size_t i = 0; i < n; ++i) { + Record(ptr[i]); + } + return *this; + } + }; + + // Specialized overload for fundamental types. + template <typename T> + class CacheKeySerializer<T, std::enable_if_t<std::is_fundamental_v<T>>> { + public: + static void Serialize(CacheKey* key, const T t) { + const char* it = reinterpret_cast<const char*>(&t); + key->insert(key->end(), it, (it + sizeof(T))); + } + }; + + // Specialized overload for string literals. Note we drop the null-terminator. + template <size_t N> + class CacheKeySerializer<char[N]> { + public: + static void Serialize(CacheKey* key, const char (&t)[N]) { + static_assert(N > 0); + key->Record(static_cast<size_t>(N)); + key->insert(key->end(), t, t + N); + } + }; + +} // namespace dawn::native + +#endif // DAWNNATIVE_CACHE_KEY_H_
diff --git a/src/dawn/native/CachedObject.cpp b/src/dawn/native/CachedObject.cpp new file mode 100644 index 0000000..e7e7cd8 --- /dev/null +++ b/src/dawn/native/CachedObject.cpp
@@ -0,0 +1,53 @@ +// Copyright 2019 The Dawn Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include "dawn/native/CachedObject.h" + +#include "dawn/common/Assert.h" +#include "dawn/native/Device.h" + +namespace dawn::native { + + bool CachedObject::IsCachedReference() const { + return mIsCachedReference; + } + + void CachedObject::SetIsCachedReference() { + mIsCachedReference = true; + } + + size_t CachedObject::HashFunc::operator()(const CachedObject* obj) const { + return obj->GetContentHash(); + } + + size_t CachedObject::GetContentHash() const { + ASSERT(mIsContentHashInitialized); + return mContentHash; + } + + void CachedObject::SetContentHash(size_t contentHash) { + ASSERT(!mIsContentHashInitialized); + mContentHash = contentHash; + mIsContentHashInitialized = true; + } + + const CacheKey& CachedObject::GetCacheKey() const { + return mCacheKey; + } + + CacheKey* CachedObject::GetCacheKey() { + return &mCacheKey; + } + +} // namespace dawn::native
diff --git a/src/dawn/native/CachedObject.h b/src/dawn/native/CachedObject.h new file mode 100644 index 0000000..7d28ae8 --- /dev/null +++ b/src/dawn/native/CachedObject.h
@@ -0,0 +1,65 @@ +// Copyright 2019 The Dawn Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#ifndef DAWNNATIVE_CACHED_OBJECT_H_ +#define DAWNNATIVE_CACHED_OBJECT_H_ + +#include "dawn/native/CacheKey.h" +#include "dawn/native/Forward.h" + +#include <cstddef> +#include <string> + +namespace dawn::native { + + // Some objects are cached so that instead of creating new duplicate objects, + // we increase the refcount of an existing object. + // When an object is successfully created, the device should call + // SetIsCachedReference() and insert the object into the cache. + class CachedObject { + public: + bool IsCachedReference() const; + + // Functor necessary for the unordered_set<CachedObject*>-based cache. + struct HashFunc { + size_t operator()(const CachedObject* obj) const; + }; + + size_t GetContentHash() const; + void SetContentHash(size_t contentHash); + + // Returns the cache key for the object only, i.e. without device/adapter information. + const CacheKey& GetCacheKey() const; + + protected: + // Protected accessor for derived classes to access and modify the key. + CacheKey* GetCacheKey(); + + private: + friend class DeviceBase; + void SetIsCachedReference(); + + bool mIsCachedReference = false; + + // Called by ObjectContentHasher upon creation to record the object. + virtual size_t ComputeContentHash() = 0; + + size_t mContentHash = 0; + bool mIsContentHashInitialized = false; + CacheKey mCacheKey; + }; + +} // namespace dawn::native + +#endif // DAWNNATIVE_CACHED_OBJECT_H_
diff --git a/src/dawn/native/CallbackTaskManager.cpp b/src/dawn/native/CallbackTaskManager.cpp new file mode 100644 index 0000000..a8be5cc --- /dev/null +++ b/src/dawn/native/CallbackTaskManager.cpp
@@ -0,0 +1,37 @@ +// Copyright 2021 The Dawn Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include "dawn/native/CallbackTaskManager.h" + +namespace dawn::native { + + bool CallbackTaskManager::IsEmpty() { + std::lock_guard<std::mutex> lock(mCallbackTaskQueueMutex); + return mCallbackTaskQueue.empty(); + } + + std::vector<std::unique_ptr<CallbackTask>> CallbackTaskManager::AcquireCallbackTasks() { + std::lock_guard<std::mutex> lock(mCallbackTaskQueueMutex); + + std::vector<std::unique_ptr<CallbackTask>> allTasks; + allTasks.swap(mCallbackTaskQueue); + return allTasks; + } + + void CallbackTaskManager::AddCallbackTask(std::unique_ptr<CallbackTask> callbackTask) { + std::lock_guard<std::mutex> lock(mCallbackTaskQueueMutex); + mCallbackTaskQueue.push_back(std::move(callbackTask)); + } + +} // namespace dawn::native
diff --git a/src/dawn/native/CallbackTaskManager.h b/src/dawn/native/CallbackTaskManager.h new file mode 100644 index 0000000..37fddd4 --- /dev/null +++ b/src/dawn/native/CallbackTaskManager.h
@@ -0,0 +1,45 @@ +// Copyright 2021 The Dawn Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#ifndef DAWNNATIVE_CALLBACK_TASK_MANAGER_H_ +#define DAWNNATIVE_CALLBACK_TASK_MANAGER_H_ + +#include <memory> +#include <mutex> +#include <vector> + +namespace dawn::native { + + struct CallbackTask { + public: + virtual ~CallbackTask() = default; + virtual void Finish() = 0; + virtual void HandleShutDown() = 0; + virtual void HandleDeviceLoss() = 0; + }; + + class CallbackTaskManager { + public: + void AddCallbackTask(std::unique_ptr<CallbackTask> callbackTask); + bool IsEmpty(); + std::vector<std::unique_ptr<CallbackTask>> AcquireCallbackTasks(); + + private: + std::mutex mCallbackTaskQueueMutex; + std::vector<std::unique_ptr<CallbackTask>> mCallbackTaskQueue; + }; + +} // namespace dawn::native + +#endif
diff --git a/src/dawn/native/CommandAllocator.cpp b/src/dawn/native/CommandAllocator.cpp new file mode 100644 index 0000000..5d36aad --- /dev/null +++ b/src/dawn/native/CommandAllocator.cpp
@@ -0,0 +1,228 @@ +// Copyright 2017 The Dawn Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include "dawn/native/CommandAllocator.h" + +#include "dawn/common/Assert.h" +#include "dawn/common/Math.h" + +#include <algorithm> +#include <climits> +#include <cstdlib> +#include <utility> + +namespace dawn::native { + + // TODO(cwallez@chromium.org): figure out a way to have more type safety for the iterator + + CommandIterator::CommandIterator() { + Reset(); + } + + CommandIterator::~CommandIterator() { + ASSERT(IsEmpty()); + } + + CommandIterator::CommandIterator(CommandIterator&& other) { + if (!other.IsEmpty()) { + mBlocks = std::move(other.mBlocks); + other.Reset(); + } + Reset(); + } + + CommandIterator& CommandIterator::operator=(CommandIterator&& other) { + ASSERT(IsEmpty()); + if (!other.IsEmpty()) { + mBlocks = std::move(other.mBlocks); + other.Reset(); + } + Reset(); + return *this; + } + + CommandIterator::CommandIterator(CommandAllocator allocator) + : mBlocks(allocator.AcquireBlocks()) { + Reset(); + } + + void CommandIterator::AcquireCommandBlocks(std::vector<CommandAllocator> allocators) { + ASSERT(IsEmpty()); + mBlocks.clear(); + for (CommandAllocator& allocator : allocators) { + CommandBlocks blocks = allocator.AcquireBlocks(); + if (!blocks.empty()) { + mBlocks.reserve(mBlocks.size() + blocks.size()); + for (BlockDef& block : blocks) { + mBlocks.push_back(std::move(block)); + } + } + } + Reset(); + } + + bool CommandIterator::NextCommandIdInNewBlock(uint32_t* commandId) { + mCurrentBlock++; + if (mCurrentBlock >= mBlocks.size()) { + Reset(); + *commandId = detail::kEndOfBlock; + return false; + } + mCurrentPtr = AlignPtr(mBlocks[mCurrentBlock].block, alignof(uint32_t)); + return NextCommandId(commandId); + } + + void CommandIterator::Reset() { + mCurrentBlock = 0; + + if (mBlocks.empty()) { + // This will case the first NextCommandId call to try to move to the next block and stop + // the iteration immediately, without special casing the initialization. + mCurrentPtr = reinterpret_cast<uint8_t*>(&mEndOfBlock); + mBlocks.emplace_back(); + mBlocks[0].size = sizeof(mEndOfBlock); + mBlocks[0].block = mCurrentPtr; + } else { + mCurrentPtr = AlignPtr(mBlocks[0].block, alignof(uint32_t)); + } + } + + void CommandIterator::MakeEmptyAsDataWasDestroyed() { + if (IsEmpty()) { + return; + } + + for (BlockDef& block : mBlocks) { + free(block.block); + } + mBlocks.clear(); + Reset(); + ASSERT(IsEmpty()); + } + + bool CommandIterator::IsEmpty() const { + return mBlocks[0].block == reinterpret_cast<const uint8_t*>(&mEndOfBlock); + } + + // Potential TODO(crbug.com/dawn/835): + // - Host the size and pointer to next block in the block itself to avoid having an allocation + // in the vector + // - Assume T's alignof is, say 64bits, static assert it, and make commandAlignment a constant + // in Allocate + // - Be able to optimize allocation to one block, for command buffers expected to live long to + // avoid cache misses + // - Better block allocation, maybe have Dawn API to say command buffer is going to have size + // close to another + + CommandAllocator::CommandAllocator() { + ResetPointers(); + } + + CommandAllocator::~CommandAllocator() { + Reset(); + } + + CommandAllocator::CommandAllocator(CommandAllocator&& other) + : mBlocks(std::move(other.mBlocks)), mLastAllocationSize(other.mLastAllocationSize) { + other.mBlocks.clear(); + if (!other.IsEmpty()) { + mCurrentPtr = other.mCurrentPtr; + mEndPtr = other.mEndPtr; + } else { + ResetPointers(); + } + other.Reset(); + } + + CommandAllocator& CommandAllocator::operator=(CommandAllocator&& other) { + Reset(); + if (!other.IsEmpty()) { + std::swap(mBlocks, other.mBlocks); + mLastAllocationSize = other.mLastAllocationSize; + mCurrentPtr = other.mCurrentPtr; + mEndPtr = other.mEndPtr; + } + other.Reset(); + return *this; + } + + void CommandAllocator::Reset() { + for (BlockDef& block : mBlocks) { + free(block.block); + } + mBlocks.clear(); + mLastAllocationSize = kDefaultBaseAllocationSize; + ResetPointers(); + } + + bool CommandAllocator::IsEmpty() const { + return mCurrentPtr == reinterpret_cast<const uint8_t*>(&mDummyEnum[0]); + } + + CommandBlocks&& CommandAllocator::AcquireBlocks() { + ASSERT(mCurrentPtr != nullptr && mEndPtr != nullptr); + ASSERT(IsPtrAligned(mCurrentPtr, alignof(uint32_t))); + ASSERT(mCurrentPtr + sizeof(uint32_t) <= mEndPtr); + *reinterpret_cast<uint32_t*>(mCurrentPtr) = detail::kEndOfBlock; + + mCurrentPtr = nullptr; + mEndPtr = nullptr; + return std::move(mBlocks); + } + + uint8_t* CommandAllocator::AllocateInNewBlock(uint32_t commandId, + size_t commandSize, + size_t commandAlignment) { + // When there is not enough space, we signal the kEndOfBlock, so that the iterator knows + // to move to the next one. kEndOfBlock on the last block means the end of the commands. + uint32_t* idAlloc = reinterpret_cast<uint32_t*>(mCurrentPtr); + *idAlloc = detail::kEndOfBlock; + + // We'll request a block that can contain at least the command ID, the command and an + // additional ID to contain the kEndOfBlock tag. + size_t requestedBlockSize = commandSize + kWorstCaseAdditionalSize; + + // The computation of the request could overflow. + if (DAWN_UNLIKELY(requestedBlockSize <= commandSize)) { + return nullptr; + } + + if (DAWN_UNLIKELY(!GetNewBlock(requestedBlockSize))) { + return nullptr; + } + return Allocate(commandId, commandSize, commandAlignment); + } + + bool CommandAllocator::GetNewBlock(size_t minimumSize) { + // Allocate blocks doubling sizes each time, to a maximum of 16k (or at least minimumSize). + mLastAllocationSize = + std::max(minimumSize, std::min(mLastAllocationSize * 2, size_t(16384))); + + uint8_t* block = static_cast<uint8_t*>(malloc(mLastAllocationSize)); + if (DAWN_UNLIKELY(block == nullptr)) { + return false; + } + + mBlocks.push_back({mLastAllocationSize, block}); + mCurrentPtr = AlignPtr(block, alignof(uint32_t)); + mEndPtr = block + mLastAllocationSize; + return true; + } + + void CommandAllocator::ResetPointers() { + mCurrentPtr = reinterpret_cast<uint8_t*>(&mDummyEnum[0]); + mEndPtr = reinterpret_cast<uint8_t*>(&mDummyEnum[1]); + } + +} // namespace dawn::native
diff --git a/src/dawn/native/CommandAllocator.h b/src/dawn/native/CommandAllocator.h new file mode 100644 index 0000000..9d2b471 --- /dev/null +++ b/src/dawn/native/CommandAllocator.h
@@ -0,0 +1,273 @@ +// Copyright 2017 The Dawn Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#ifndef DAWNNATIVE_COMMAND_ALLOCATOR_H_ +#define DAWNNATIVE_COMMAND_ALLOCATOR_H_ + +#include "dawn/common/Assert.h" +#include "dawn/common/Math.h" +#include "dawn/common/NonCopyable.h" + +#include <cstddef> +#include <cstdint> +#include <vector> + +namespace dawn::native { + + // Allocation for command buffers should be fast. To avoid doing an allocation per command + // or to avoid copying commands when reallocing, we use a linear allocator in a growing set + // of large memory blocks. We also use this to have the format to be (u32 commandId, command), + // so that iteration over the commands is easy. + + // Usage of the allocator and iterator: + // CommandAllocator allocator; + // DrawCommand* cmd = allocator.Allocate<DrawCommand>(CommandType::Draw); + // // Fill command + // // Repeat allocation and filling commands + // + // CommandIterator commands(allocator); + // CommandType type; + // while(commands.NextCommandId(&type)) { + // switch(type) { + // case CommandType::Draw: + // DrawCommand* draw = commands.NextCommand<DrawCommand>(); + // // Do the draw + // break; + // // other cases + // } + // } + + // Note that you need to extract the commands from the CommandAllocator before destroying it + // and must tell the CommandIterator when the allocated commands have been processed for + // deletion. + + // These are the lists of blocks, should not be used directly, only through CommandAllocator + // and CommandIterator + struct BlockDef { + size_t size; + uint8_t* block; + }; + using CommandBlocks = std::vector<BlockDef>; + + namespace detail { + constexpr uint32_t kEndOfBlock = std::numeric_limits<uint32_t>::max(); + constexpr uint32_t kAdditionalData = std::numeric_limits<uint32_t>::max() - 1; + } // namespace detail + + class CommandAllocator; + + class CommandIterator : public NonCopyable { + public: + CommandIterator(); + ~CommandIterator(); + + CommandIterator(CommandIterator&& other); + CommandIterator& operator=(CommandIterator&& other); + + // Shorthand constructor for acquiring CommandBlocks from a single CommandAllocator. + explicit CommandIterator(CommandAllocator allocator); + + void AcquireCommandBlocks(std::vector<CommandAllocator> allocators); + + template <typename E> + bool NextCommandId(E* commandId) { + return NextCommandId(reinterpret_cast<uint32_t*>(commandId)); + } + template <typename T> + T* NextCommand() { + return static_cast<T*>(NextCommand(sizeof(T), alignof(T))); + } + template <typename T> + T* NextData(size_t count) { + return static_cast<T*>(NextData(sizeof(T) * count, alignof(T))); + } + + // Sets iterator to the beginning of the commands without emptying the list. This method can + // be used if iteration was stopped early and the iterator needs to be restarted. + void Reset(); + + // This method must to be called after commands have been deleted. This indicates that the + // commands have been submitted and they are no longer valid. + void MakeEmptyAsDataWasDestroyed(); + + private: + bool IsEmpty() const; + + DAWN_FORCE_INLINE bool NextCommandId(uint32_t* commandId) { + uint8_t* idPtr = AlignPtr(mCurrentPtr, alignof(uint32_t)); + ASSERT(idPtr + sizeof(uint32_t) <= + mBlocks[mCurrentBlock].block + mBlocks[mCurrentBlock].size); + + uint32_t id = *reinterpret_cast<uint32_t*>(idPtr); + + if (id != detail::kEndOfBlock) { + mCurrentPtr = idPtr + sizeof(uint32_t); + *commandId = id; + return true; + } + return NextCommandIdInNewBlock(commandId); + } + + bool NextCommandIdInNewBlock(uint32_t* commandId); + + DAWN_FORCE_INLINE void* NextCommand(size_t commandSize, size_t commandAlignment) { + uint8_t* commandPtr = AlignPtr(mCurrentPtr, commandAlignment); + ASSERT(commandPtr + sizeof(commandSize) <= + mBlocks[mCurrentBlock].block + mBlocks[mCurrentBlock].size); + + mCurrentPtr = commandPtr + commandSize; + return commandPtr; + } + + DAWN_FORCE_INLINE void* NextData(size_t dataSize, size_t dataAlignment) { + uint32_t id; + bool hasId = NextCommandId(&id); + ASSERT(hasId); + ASSERT(id == detail::kAdditionalData); + + return NextCommand(dataSize, dataAlignment); + } + + CommandBlocks mBlocks; + uint8_t* mCurrentPtr = nullptr; + size_t mCurrentBlock = 0; + // Used to avoid a special case for empty iterators. + uint32_t mEndOfBlock = detail::kEndOfBlock; + }; + + class CommandAllocator : public NonCopyable { + public: + CommandAllocator(); + ~CommandAllocator(); + + // NOTE: A moved-from CommandAllocator is reset to its initial empty state. + CommandAllocator(CommandAllocator&&); + CommandAllocator& operator=(CommandAllocator&&); + + // Frees all blocks held by the allocator and restores it to its initial empty state. + void Reset(); + + bool IsEmpty() const; + + template <typename T, typename E> + T* Allocate(E commandId) { + static_assert(sizeof(E) == sizeof(uint32_t)); + static_assert(alignof(E) == alignof(uint32_t)); + static_assert(alignof(T) <= kMaxSupportedAlignment); + T* result = reinterpret_cast<T*>( + Allocate(static_cast<uint32_t>(commandId), sizeof(T), alignof(T))); + if (!result) { + return nullptr; + } + new (result) T; + return result; + } + + template <typename T> + T* AllocateData(size_t count) { + static_assert(alignof(T) <= kMaxSupportedAlignment); + T* result = reinterpret_cast<T*>(AllocateData(sizeof(T) * count, alignof(T))); + if (!result) { + return nullptr; + } + for (size_t i = 0; i < count; i++) { + new (result + i) T; + } + return result; + } + + private: + // This is used for some internal computations and can be any power of two as long as code + // using the CommandAllocator passes the static_asserts. + static constexpr size_t kMaxSupportedAlignment = 8; + + // To avoid checking for overflows at every step of the computations we compute an upper + // bound of the space that will be needed in addition to the command data. + static constexpr size_t kWorstCaseAdditionalSize = + sizeof(uint32_t) + kMaxSupportedAlignment + alignof(uint32_t) + sizeof(uint32_t); + + // The default value of mLastAllocationSize. + static constexpr size_t kDefaultBaseAllocationSize = 2048; + + friend CommandIterator; + CommandBlocks&& AcquireBlocks(); + + DAWN_FORCE_INLINE uint8_t* Allocate(uint32_t commandId, + size_t commandSize, + size_t commandAlignment) { + ASSERT(mCurrentPtr != nullptr); + ASSERT(mEndPtr != nullptr); + ASSERT(commandId != detail::kEndOfBlock); + + // It should always be possible to allocate one id, for kEndOfBlock tagging, + ASSERT(IsPtrAligned(mCurrentPtr, alignof(uint32_t))); + ASSERT(mEndPtr >= mCurrentPtr); + ASSERT(static_cast<size_t>(mEndPtr - mCurrentPtr) >= sizeof(uint32_t)); + + // The memory after the ID will contain the following: + // - the current ID + // - padding to align the command, maximum kMaxSupportedAlignment + // - the command of size commandSize + // - padding to align the next ID, maximum alignof(uint32_t) + // - the next ID of size sizeof(uint32_t) + + // This can't overflow because by construction mCurrentPtr always has space for the next + // ID. + size_t remainingSize = static_cast<size_t>(mEndPtr - mCurrentPtr); + + // The good case were we have enough space for the command data and upper bound of the + // extra required space. + if ((remainingSize >= kWorstCaseAdditionalSize) && + (remainingSize - kWorstCaseAdditionalSize >= commandSize)) { + uint32_t* idAlloc = reinterpret_cast<uint32_t*>(mCurrentPtr); + *idAlloc = commandId; + + uint8_t* commandAlloc = AlignPtr(mCurrentPtr + sizeof(uint32_t), commandAlignment); + mCurrentPtr = AlignPtr(commandAlloc + commandSize, alignof(uint32_t)); + + return commandAlloc; + } + return AllocateInNewBlock(commandId, commandSize, commandAlignment); + } + + uint8_t* AllocateInNewBlock(uint32_t commandId, + size_t commandSize, + size_t commandAlignment); + + DAWN_FORCE_INLINE uint8_t* AllocateData(size_t commandSize, size_t commandAlignment) { + return Allocate(detail::kAdditionalData, commandSize, commandAlignment); + } + + bool GetNewBlock(size_t minimumSize); + + void ResetPointers(); + + CommandBlocks mBlocks; + size_t mLastAllocationSize = kDefaultBaseAllocationSize; + + // Data used for the block range at initialization so that the first call to Allocate sees + // there is not enough space and calls GetNewBlock. This avoids having to special case the + // initialization in Allocate. + uint32_t mDummyEnum[1] = {0}; + + // Pointers to the current range of allocation in the block. Guaranteed to allow for at + // least one uint32_t if not nullptr, so that the special kEndOfBlock command id can always + // be written. Nullptr iff the blocks were moved out. + uint8_t* mCurrentPtr = nullptr; + uint8_t* mEndPtr = nullptr; + }; + +} // namespace dawn::native + +#endif // DAWNNATIVE_COMMAND_ALLOCATOR_H_
diff --git a/src/dawn/native/CommandBuffer.cpp b/src/dawn/native/CommandBuffer.cpp new file mode 100644 index 0000000..f8c7836 --- /dev/null +++ b/src/dawn/native/CommandBuffer.cpp
@@ -0,0 +1,245 @@ +// Copyright 2017 The Dawn Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include "dawn/native/CommandBuffer.h" + +#include "dawn/common/BitSetIterator.h" +#include "dawn/native/Buffer.h" +#include "dawn/native/CommandEncoder.h" +#include "dawn/native/CommandValidation.h" +#include "dawn/native/Commands.h" +#include "dawn/native/Format.h" +#include "dawn/native/ObjectType_autogen.h" +#include "dawn/native/Texture.h" + +namespace dawn::native { + + CommandBufferBase::CommandBufferBase(CommandEncoder* encoder, + const CommandBufferDescriptor* descriptor) + : ApiObjectBase(encoder->GetDevice(), descriptor->label), + mCommands(encoder->AcquireCommands()), + mResourceUsages(encoder->AcquireResourceUsages()) { + TrackInDevice(); + } + + CommandBufferBase::CommandBufferBase(DeviceBase* device) + : ApiObjectBase(device, kLabelNotImplemented) { + TrackInDevice(); + } + + CommandBufferBase::CommandBufferBase(DeviceBase* device, ObjectBase::ErrorTag tag) + : ApiObjectBase(device, tag) { + } + + // static + CommandBufferBase* CommandBufferBase::MakeError(DeviceBase* device) { + return new CommandBufferBase(device, ObjectBase::kError); + } + + ObjectType CommandBufferBase::GetType() const { + return ObjectType::CommandBuffer; + } + + MaybeError CommandBufferBase::ValidateCanUseInSubmitNow() const { + ASSERT(!IsError()); + + DAWN_INVALID_IF(!IsAlive(), "%s cannot be submitted more than once.", this); + return {}; + } + + void CommandBufferBase::DestroyImpl() { + FreeCommands(&mCommands); + mResourceUsages = {}; + } + + const CommandBufferResourceUsage& CommandBufferBase::GetResourceUsages() const { + return mResourceUsages; + } + + CommandIterator* CommandBufferBase::GetCommandIteratorForTesting() { + return &mCommands; + } + + bool IsCompleteSubresourceCopiedTo(const TextureBase* texture, + const Extent3D copySize, + const uint32_t mipLevel) { + Extent3D extent = texture->GetMipLevelPhysicalSize(mipLevel); + + switch (texture->GetDimension()) { + case wgpu::TextureDimension::e1D: + return extent.width == copySize.width; + case wgpu::TextureDimension::e2D: + return extent.width == copySize.width && extent.height == copySize.height; + case wgpu::TextureDimension::e3D: + return extent.width == copySize.width && extent.height == copySize.height && + extent.depthOrArrayLayers == copySize.depthOrArrayLayers; + } + } + + SubresourceRange GetSubresourcesAffectedByCopy(const TextureCopy& copy, + const Extent3D& copySize) { + switch (copy.texture->GetDimension()) { + case wgpu::TextureDimension::e1D: + ASSERT(copy.origin.z == 0 && copySize.depthOrArrayLayers == 1); + ASSERT(copy.mipLevel == 0); + return {copy.aspect, {0, 1}, {0, 1}}; + case wgpu::TextureDimension::e2D: + return { + copy.aspect, {copy.origin.z, copySize.depthOrArrayLayers}, {copy.mipLevel, 1}}; + case wgpu::TextureDimension::e3D: + return {copy.aspect, {0, 1}, {copy.mipLevel, 1}}; + } + } + + void LazyClearRenderPassAttachments(BeginRenderPassCmd* renderPass) { + for (ColorAttachmentIndex i : + IterateBitSet(renderPass->attachmentState->GetColorAttachmentsMask())) { + auto& attachmentInfo = renderPass->colorAttachments[i]; + TextureViewBase* view = attachmentInfo.view.Get(); + bool hasResolveTarget = attachmentInfo.resolveTarget != nullptr; + + ASSERT(view->GetLayerCount() == 1); + ASSERT(view->GetLevelCount() == 1); + SubresourceRange range = view->GetSubresourceRange(); + + // If the loadOp is Load, but the subresource is not initialized, use Clear instead. + if (attachmentInfo.loadOp == wgpu::LoadOp::Load && + !view->GetTexture()->IsSubresourceContentInitialized(range)) { + attachmentInfo.loadOp = wgpu::LoadOp::Clear; + attachmentInfo.clearColor = {0.f, 0.f, 0.f, 0.f}; + } + + if (hasResolveTarget) { + // We need to set the resolve target to initialized so that it does not get + // cleared later in the pipeline. The texture will be resolved from the + // source color attachment, which will be correctly initialized. + TextureViewBase* resolveView = attachmentInfo.resolveTarget.Get(); + ASSERT(resolveView->GetLayerCount() == 1); + ASSERT(resolveView->GetLevelCount() == 1); + resolveView->GetTexture()->SetIsSubresourceContentInitialized( + true, resolveView->GetSubresourceRange()); + } + + switch (attachmentInfo.storeOp) { + case wgpu::StoreOp::Store: + view->GetTexture()->SetIsSubresourceContentInitialized(true, range); + break; + + case wgpu::StoreOp::Discard: + view->GetTexture()->SetIsSubresourceContentInitialized(false, range); + break; + + case wgpu::StoreOp::Undefined: + UNREACHABLE(); + break; + } + } + + if (renderPass->attachmentState->HasDepthStencilAttachment()) { + auto& attachmentInfo = renderPass->depthStencilAttachment; + TextureViewBase* view = attachmentInfo.view.Get(); + ASSERT(view->GetLayerCount() == 1); + ASSERT(view->GetLevelCount() == 1); + SubresourceRange range = view->GetSubresourceRange(); + + SubresourceRange depthRange = range; + depthRange.aspects = range.aspects & Aspect::Depth; + + SubresourceRange stencilRange = range; + stencilRange.aspects = range.aspects & Aspect::Stencil; + + // If the depth stencil texture has not been initialized, we want to use loadop + // clear to init the contents to 0's + if (!view->GetTexture()->IsSubresourceContentInitialized(depthRange) && + attachmentInfo.depthLoadOp == wgpu::LoadOp::Load) { + attachmentInfo.clearDepth = 0.0f; + attachmentInfo.depthLoadOp = wgpu::LoadOp::Clear; + } + + if (!view->GetTexture()->IsSubresourceContentInitialized(stencilRange) && + attachmentInfo.stencilLoadOp == wgpu::LoadOp::Load) { + attachmentInfo.clearStencil = 0u; + attachmentInfo.stencilLoadOp = wgpu::LoadOp::Clear; + } + + view->GetTexture()->SetIsSubresourceContentInitialized( + attachmentInfo.depthStoreOp == wgpu::StoreOp::Store, depthRange); + + view->GetTexture()->SetIsSubresourceContentInitialized( + attachmentInfo.stencilStoreOp == wgpu::StoreOp::Store, stencilRange); + } + } + + bool IsFullBufferOverwrittenInTextureToBufferCopy(const CopyTextureToBufferCmd* copy) { + ASSERT(copy != nullptr); + + if (copy->destination.offset > 0) { + // The copy doesn't touch the start of the buffer. + return false; + } + + const TextureBase* texture = copy->source.texture.Get(); + const TexelBlockInfo& blockInfo = + texture->GetFormat().GetAspectInfo(copy->source.aspect).block; + const uint64_t widthInBlocks = copy->copySize.width / blockInfo.width; + const uint64_t heightInBlocks = copy->copySize.height / blockInfo.height; + const bool multiSlice = copy->copySize.depthOrArrayLayers > 1; + const bool multiRow = multiSlice || heightInBlocks > 1; + + if (multiSlice && copy->destination.rowsPerImage > heightInBlocks) { + // There are gaps between slices that aren't overwritten + return false; + } + + const uint64_t copyTextureDataSizePerRow = widthInBlocks * blockInfo.byteSize; + if (multiRow && copy->destination.bytesPerRow > copyTextureDataSizePerRow) { + // There are gaps between rows that aren't overwritten + return false; + } + + // After the above checks, we're sure the copy has no gaps. + // Now, compute the total number of bytes written. + const uint64_t writtenBytes = + ComputeRequiredBytesInCopy(blockInfo, copy->copySize, copy->destination.bytesPerRow, + copy->destination.rowsPerImage) + .AcquireSuccess(); + if (!copy->destination.buffer->IsFullBufferRange(copy->destination.offset, writtenBytes)) { + // The written bytes don't cover the whole buffer. + return false; + } + + return true; + } + + std::array<float, 4> ConvertToFloatColor(dawn::native::Color color) { + const std::array<float, 4> outputValue = { + static_cast<float>(color.r), static_cast<float>(color.g), static_cast<float>(color.b), + static_cast<float>(color.a)}; + return outputValue; + } + std::array<int32_t, 4> ConvertToSignedIntegerColor(dawn::native::Color color) { + const std::array<int32_t, 4> outputValue = { + static_cast<int32_t>(color.r), static_cast<int32_t>(color.g), + static_cast<int32_t>(color.b), static_cast<int32_t>(color.a)}; + return outputValue; + } + + std::array<uint32_t, 4> ConvertToUnsignedIntegerColor(dawn::native::Color color) { + const std::array<uint32_t, 4> outputValue = { + static_cast<uint32_t>(color.r), static_cast<uint32_t>(color.g), + static_cast<uint32_t>(color.b), static_cast<uint32_t>(color.a)}; + return outputValue; + } + +} // namespace dawn::native
diff --git a/src/dawn/native/CommandBuffer.h b/src/dawn/native/CommandBuffer.h new file mode 100644 index 0000000..3d9d71a --- /dev/null +++ b/src/dawn/native/CommandBuffer.h
@@ -0,0 +1,76 @@ +// Copyright 2017 The Dawn Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#ifndef DAWNNATIVE_COMMANDBUFFER_H_ +#define DAWNNATIVE_COMMANDBUFFER_H_ + +#include "dawn/native/dawn_platform.h" + +#include "dawn/native/CommandAllocator.h" +#include "dawn/native/Error.h" +#include "dawn/native/Forward.h" +#include "dawn/native/ObjectBase.h" +#include "dawn/native/PassResourceUsage.h" +#include "dawn/native/Texture.h" + +namespace dawn::native { + + struct BeginRenderPassCmd; + struct CopyTextureToBufferCmd; + struct TextureCopy; + + class CommandBufferBase : public ApiObjectBase { + public: + CommandBufferBase(CommandEncoder* encoder, const CommandBufferDescriptor* descriptor); + + static CommandBufferBase* MakeError(DeviceBase* device); + + ObjectType GetType() const override; + + MaybeError ValidateCanUseInSubmitNow() const; + + const CommandBufferResourceUsage& GetResourceUsages() const; + + CommandIterator* GetCommandIteratorForTesting(); + + protected: + // Constructor used only for mocking and testing. + CommandBufferBase(DeviceBase* device); + void DestroyImpl() override; + + CommandIterator mCommands; + + private: + CommandBufferBase(DeviceBase* device, ObjectBase::ErrorTag tag); + + CommandBufferResourceUsage mResourceUsages; + }; + + bool IsCompleteSubresourceCopiedTo(const TextureBase* texture, + const Extent3D copySize, + const uint32_t mipLevel); + SubresourceRange GetSubresourcesAffectedByCopy(const TextureCopy& copy, + const Extent3D& copySize); + + void LazyClearRenderPassAttachments(BeginRenderPassCmd* renderPass); + + bool IsFullBufferOverwrittenInTextureToBufferCopy(const CopyTextureToBufferCmd* copy); + + std::array<float, 4> ConvertToFloatColor(dawn::native::Color color); + std::array<int32_t, 4> ConvertToSignedIntegerColor(dawn::native::Color color); + std::array<uint32_t, 4> ConvertToUnsignedIntegerColor(dawn::native::Color color); + +} // namespace dawn::native + +#endif // DAWNNATIVE_COMMANDBUFFER_H_
diff --git a/src/dawn/native/CommandBufferStateTracker.cpp b/src/dawn/native/CommandBufferStateTracker.cpp new file mode 100644 index 0000000..ee164c7 --- /dev/null +++ b/src/dawn/native/CommandBufferStateTracker.cpp
@@ -0,0 +1,421 @@ +// Copyright 2017 The Dawn Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include "dawn/native/CommandBufferStateTracker.h" + +#include "dawn/common/Assert.h" +#include "dawn/common/BitSetIterator.h" +#include "dawn/native/BindGroup.h" +#include "dawn/native/ComputePassEncoder.h" +#include "dawn/native/ComputePipeline.h" +#include "dawn/native/Forward.h" +#include "dawn/native/ObjectType_autogen.h" +#include "dawn/native/PipelineLayout.h" +#include "dawn/native/RenderPipeline.h" + +// TODO(dawn:563): None of the error messages in this file include the buffer objects they are +// validating against. It would be nice to improve that, but difficult to do without incurring +// additional tracking costs. + +namespace dawn::native { + + namespace { + bool BufferSizesAtLeastAsBig(const ityp::span<uint32_t, uint64_t> unverifiedBufferSizes, + const std::vector<uint64_t>& pipelineMinBufferSizes) { + ASSERT(unverifiedBufferSizes.size() == pipelineMinBufferSizes.size()); + + for (uint32_t i = 0; i < unverifiedBufferSizes.size(); ++i) { + if (unverifiedBufferSizes[i] < pipelineMinBufferSizes[i]) { + return false; + } + } + + return true; + } + } // namespace + + enum ValidationAspect { + VALIDATION_ASPECT_PIPELINE, + VALIDATION_ASPECT_BIND_GROUPS, + VALIDATION_ASPECT_VERTEX_BUFFERS, + VALIDATION_ASPECT_INDEX_BUFFER, + + VALIDATION_ASPECT_COUNT + }; + static_assert(VALIDATION_ASPECT_COUNT == CommandBufferStateTracker::kNumAspects); + + static constexpr CommandBufferStateTracker::ValidationAspects kDispatchAspects = + 1 << VALIDATION_ASPECT_PIPELINE | 1 << VALIDATION_ASPECT_BIND_GROUPS; + + static constexpr CommandBufferStateTracker::ValidationAspects kDrawAspects = + 1 << VALIDATION_ASPECT_PIPELINE | 1 << VALIDATION_ASPECT_BIND_GROUPS | + 1 << VALIDATION_ASPECT_VERTEX_BUFFERS; + + static constexpr CommandBufferStateTracker::ValidationAspects kDrawIndexedAspects = + 1 << VALIDATION_ASPECT_PIPELINE | 1 << VALIDATION_ASPECT_BIND_GROUPS | + 1 << VALIDATION_ASPECT_VERTEX_BUFFERS | 1 << VALIDATION_ASPECT_INDEX_BUFFER; + + static constexpr CommandBufferStateTracker::ValidationAspects kLazyAspects = + 1 << VALIDATION_ASPECT_BIND_GROUPS | 1 << VALIDATION_ASPECT_VERTEX_BUFFERS | + 1 << VALIDATION_ASPECT_INDEX_BUFFER; + + MaybeError CommandBufferStateTracker::ValidateCanDispatch() { + return ValidateOperation(kDispatchAspects); + } + + MaybeError CommandBufferStateTracker::ValidateCanDraw() { + return ValidateOperation(kDrawAspects); + } + + MaybeError CommandBufferStateTracker::ValidateCanDrawIndexed() { + return ValidateOperation(kDrawIndexedAspects); + } + + MaybeError CommandBufferStateTracker::ValidateBufferInRangeForVertexBuffer( + uint32_t vertexCount, + uint32_t firstVertex) { + RenderPipelineBase* lastRenderPipeline = GetRenderPipeline(); + + const ityp::bitset<VertexBufferSlot, kMaxVertexBuffers>& + vertexBufferSlotsUsedAsVertexBuffer = + lastRenderPipeline->GetVertexBufferSlotsUsedAsVertexBuffer(); + + for (auto usedSlotVertex : IterateBitSet(vertexBufferSlotsUsedAsVertexBuffer)) { + const VertexBufferInfo& vertexBuffer = + lastRenderPipeline->GetVertexBuffer(usedSlotVertex); + uint64_t arrayStride = vertexBuffer.arrayStride; + uint64_t bufferSize = mVertexBufferSizes[usedSlotVertex]; + + if (arrayStride == 0) { + DAWN_INVALID_IF(vertexBuffer.usedBytesInStride > bufferSize, + "Bound vertex buffer size (%u) at slot %u with an arrayStride of 0 " + "is smaller than the required size for all attributes (%u)", + bufferSize, static_cast<uint8_t>(usedSlotVertex), + vertexBuffer.usedBytesInStride); + } else { + uint64_t strideCount = static_cast<uint64_t>(firstVertex) + vertexCount; + if (strideCount != 0u) { + uint64_t requiredSize = + (strideCount - 1u) * arrayStride + vertexBuffer.lastStride; + // firstVertex and vertexCount are in uint32_t, + // arrayStride must not be larger than kMaxVertexBufferArrayStride, which is + // currently 2048, and vertexBuffer.lastStride = max(attribute.offset + + // sizeof(attribute.format)) with attribute.offset being no larger than + // kMaxVertexBufferArrayStride, so by doing checks in uint64_t we avoid + // overflows. + DAWN_INVALID_IF( + requiredSize > bufferSize, + "Vertex range (first: %u, count: %u) requires a larger buffer (%u) than " + "the " + "bound buffer size (%u) of the vertex buffer at slot %u with stride %u.", + firstVertex, vertexCount, requiredSize, bufferSize, + static_cast<uint8_t>(usedSlotVertex), arrayStride); + } + } + } + + return {}; + } + + MaybeError CommandBufferStateTracker::ValidateBufferInRangeForInstanceBuffer( + uint32_t instanceCount, + uint32_t firstInstance) { + RenderPipelineBase* lastRenderPipeline = GetRenderPipeline(); + + const ityp::bitset<VertexBufferSlot, kMaxVertexBuffers>& + vertexBufferSlotsUsedAsInstanceBuffer = + lastRenderPipeline->GetVertexBufferSlotsUsedAsInstanceBuffer(); + + for (auto usedSlotInstance : IterateBitSet(vertexBufferSlotsUsedAsInstanceBuffer)) { + const VertexBufferInfo& vertexBuffer = + lastRenderPipeline->GetVertexBuffer(usedSlotInstance); + uint64_t arrayStride = vertexBuffer.arrayStride; + uint64_t bufferSize = mVertexBufferSizes[usedSlotInstance]; + if (arrayStride == 0) { + DAWN_INVALID_IF(vertexBuffer.usedBytesInStride > bufferSize, + "Bound vertex buffer size (%u) at slot %u with an arrayStride of 0 " + "is smaller than the required size for all attributes (%u)", + bufferSize, static_cast<uint8_t>(usedSlotInstance), + vertexBuffer.usedBytesInStride); + } else { + uint64_t strideCount = static_cast<uint64_t>(firstInstance) + instanceCount; + if (strideCount != 0u) { + uint64_t requiredSize = + (strideCount - 1u) * arrayStride + vertexBuffer.lastStride; + // firstInstance and instanceCount are in uint32_t, + // arrayStride must not be larger than kMaxVertexBufferArrayStride, which is + // currently 2048, and vertexBuffer.lastStride = max(attribute.offset + + // sizeof(attribute.format)) with attribute.offset being no larger than + // kMaxVertexBufferArrayStride, so by doing checks in uint64_t we avoid + // overflows. + DAWN_INVALID_IF( + requiredSize > bufferSize, + "Instance range (first: %u, count: %u) requires a larger buffer (%u) than " + "the " + "bound buffer size (%u) of the vertex buffer at slot %u with stride %u.", + firstInstance, instanceCount, requiredSize, bufferSize, + static_cast<uint8_t>(usedSlotInstance), arrayStride); + } + } + } + + return {}; + } + + MaybeError CommandBufferStateTracker::ValidateIndexBufferInRange(uint32_t indexCount, + uint32_t firstIndex) { + // Validate the range of index buffer + // firstIndex and indexCount are in uint32_t, while IndexFormatSize is 2 (for + // wgpu::IndexFormat::Uint16) or 4 (for wgpu::IndexFormat::Uint32), so by doing checks in + // uint64_t we avoid overflows. + DAWN_INVALID_IF( + (static_cast<uint64_t>(firstIndex) + indexCount) * IndexFormatSize(mIndexFormat) > + mIndexBufferSize, + "Index range (first: %u, count: %u, format: %s) does not fit in index buffer size " + "(%u).", + firstIndex, indexCount, mIndexFormat, mIndexBufferSize); + return {}; + } + + MaybeError CommandBufferStateTracker::ValidateOperation(ValidationAspects requiredAspects) { + // Fast return-true path if everything is good + ValidationAspects missingAspects = requiredAspects & ~mAspects; + if (missingAspects.none()) { + return {}; + } + + // Generate an error immediately if a non-lazy aspect is missing as computing lazy aspects + // requires the pipeline to be set. + DAWN_TRY(CheckMissingAspects(missingAspects & ~kLazyAspects)); + + RecomputeLazyAspects(missingAspects); + + DAWN_TRY(CheckMissingAspects(requiredAspects & ~mAspects)); + + return {}; + } + + void CommandBufferStateTracker::RecomputeLazyAspects(ValidationAspects aspects) { + ASSERT(mAspects[VALIDATION_ASPECT_PIPELINE]); + ASSERT((aspects & ~kLazyAspects).none()); + + if (aspects[VALIDATION_ASPECT_BIND_GROUPS]) { + bool matches = true; + + for (BindGroupIndex i : IterateBitSet(mLastPipelineLayout->GetBindGroupLayoutsMask())) { + if (mBindgroups[i] == nullptr || + mLastPipelineLayout->GetBindGroupLayout(i) != mBindgroups[i]->GetLayout() || + !BufferSizesAtLeastAsBig(mBindgroups[i]->GetUnverifiedBufferSizes(), + (*mMinBufferSizes)[i])) { + matches = false; + break; + } + } + + if (matches) { + mAspects.set(VALIDATION_ASPECT_BIND_GROUPS); + } + } + + if (aspects[VALIDATION_ASPECT_VERTEX_BUFFERS]) { + RenderPipelineBase* lastRenderPipeline = GetRenderPipeline(); + + const ityp::bitset<VertexBufferSlot, kMaxVertexBuffers>& requiredVertexBuffers = + lastRenderPipeline->GetVertexBufferSlotsUsed(); + if (IsSubset(requiredVertexBuffers, mVertexBufferSlotsUsed)) { + mAspects.set(VALIDATION_ASPECT_VERTEX_BUFFERS); + } + } + + if (aspects[VALIDATION_ASPECT_INDEX_BUFFER] && mIndexBufferSet) { + RenderPipelineBase* lastRenderPipeline = GetRenderPipeline(); + if (!IsStripPrimitiveTopology(lastRenderPipeline->GetPrimitiveTopology()) || + mIndexFormat == lastRenderPipeline->GetStripIndexFormat()) { + mAspects.set(VALIDATION_ASPECT_INDEX_BUFFER); + } + } + } + + MaybeError CommandBufferStateTracker::CheckMissingAspects(ValidationAspects aspects) { + if (!aspects.any()) { + return {}; + } + + DAWN_INVALID_IF(aspects[VALIDATION_ASPECT_PIPELINE], "No pipeline set."); + + if (DAWN_UNLIKELY(aspects[VALIDATION_ASPECT_INDEX_BUFFER])) { + DAWN_INVALID_IF(!mIndexBufferSet, "Index buffer was not set."); + + RenderPipelineBase* lastRenderPipeline = GetRenderPipeline(); + wgpu::IndexFormat pipelineIndexFormat = lastRenderPipeline->GetStripIndexFormat(); + + if (IsStripPrimitiveTopology(lastRenderPipeline->GetPrimitiveTopology())) { + DAWN_INVALID_IF( + pipelineIndexFormat == wgpu::IndexFormat::Undefined, + "%s has a strip primitive topology (%s) but a strip index format of %s, which " + "prevents it for being used for indexed draw calls.", + lastRenderPipeline, lastRenderPipeline->GetPrimitiveTopology(), + pipelineIndexFormat); + + DAWN_INVALID_IF( + mIndexFormat != pipelineIndexFormat, + "Strip index format (%s) of %s does not match index buffer format (%s).", + pipelineIndexFormat, lastRenderPipeline, mIndexFormat); + } + + // The chunk of code above should be similar to the one in |RecomputeLazyAspects|. + // It returns the first invalid state found. We shouldn't be able to reach this line + // because to have invalid aspects one of the above conditions must have failed earlier. + // If this is reached, make sure lazy aspects and the error checks above are consistent. + UNREACHABLE(); + return DAWN_FORMAT_VALIDATION_ERROR("Index buffer is invalid."); + } + + // TODO(dawn:563): Indicate which slots were not set. + DAWN_INVALID_IF(aspects[VALIDATION_ASPECT_VERTEX_BUFFERS], + "Vertex buffer slots required by %s were not set.", GetRenderPipeline()); + + if (DAWN_UNLIKELY(aspects[VALIDATION_ASPECT_BIND_GROUPS])) { + for (BindGroupIndex i : IterateBitSet(mLastPipelineLayout->GetBindGroupLayoutsMask())) { + ASSERT(HasPipeline()); + + DAWN_INVALID_IF(mBindgroups[i] == nullptr, "No bind group set at index %u.", + static_cast<uint32_t>(i)); + + BindGroupLayoutBase* requiredBGL = mLastPipelineLayout->GetBindGroupLayout(i); + BindGroupLayoutBase* currentBGL = mBindgroups[i]->GetLayout(); + + DAWN_INVALID_IF( + requiredBGL->GetPipelineCompatibilityToken() != PipelineCompatibilityToken(0) && + currentBGL->GetPipelineCompatibilityToken() != + requiredBGL->GetPipelineCompatibilityToken(), + "The current pipeline (%s) was created with a default layout, and is not " + "compatible with the %s at index %u which uses a %s that was not created by " + "the pipeline. Either use the bind group layout returned by calling " + "getBindGroupLayout(%u) on the pipeline when creating the bind group, or " + "provide an explicit pipeline layout when creating the pipeline.", + mLastPipeline, mBindgroups[i], static_cast<uint32_t>(i), currentBGL, + static_cast<uint32_t>(i)); + + DAWN_INVALID_IF( + requiredBGL->GetPipelineCompatibilityToken() == PipelineCompatibilityToken(0) && + currentBGL->GetPipelineCompatibilityToken() != + PipelineCompatibilityToken(0), + "%s at index %u uses a %s which was created as part of the default layout for " + "a different pipeline than the current one (%s), and as a result is not " + "compatible. Use an explicit bind group layout when creating bind groups and " + "an explicit pipeline layout when creating pipelines to share bind groups " + "between pipelines.", + mBindgroups[i], static_cast<uint32_t>(i), currentBGL, mLastPipeline); + + DAWN_INVALID_IF( + mLastPipelineLayout->GetBindGroupLayout(i) != mBindgroups[i]->GetLayout(), + "Bind group layout %s of pipeline layout %s does not match layout %s of bind " + "group %s at index %u.", + requiredBGL, mLastPipelineLayout, currentBGL, mBindgroups[i], + static_cast<uint32_t>(i)); + + // TODO(dawn:563): Report the binding sizes and which ones are failing. + DAWN_INVALID_IF(!BufferSizesAtLeastAsBig(mBindgroups[i]->GetUnverifiedBufferSizes(), + (*mMinBufferSizes)[i]), + "Binding sizes are too small for bind group %s at index %u", + mBindgroups[i], static_cast<uint32_t>(i)); + } + + // The chunk of code above should be similar to the one in |RecomputeLazyAspects|. + // It returns the first invalid state found. We shouldn't be able to reach this line + // because to have invalid aspects one of the above conditions must have failed earlier. + // If this is reached, make sure lazy aspects and the error checks above are consistent. + UNREACHABLE(); + return DAWN_FORMAT_VALIDATION_ERROR("Bind groups are invalid."); + } + + UNREACHABLE(); + } + + void CommandBufferStateTracker::SetComputePipeline(ComputePipelineBase* pipeline) { + SetPipelineCommon(pipeline); + } + + void CommandBufferStateTracker::SetRenderPipeline(RenderPipelineBase* pipeline) { + SetPipelineCommon(pipeline); + } + + void CommandBufferStateTracker::SetBindGroup(BindGroupIndex index, + BindGroupBase* bindgroup, + uint32_t dynamicOffsetCount, + const uint32_t* dynamicOffsets) { + mBindgroups[index] = bindgroup; + mDynamicOffsets[index].assign(dynamicOffsets, dynamicOffsets + dynamicOffsetCount); + mAspects.reset(VALIDATION_ASPECT_BIND_GROUPS); + } + + void CommandBufferStateTracker::SetIndexBuffer(wgpu::IndexFormat format, uint64_t size) { + mIndexBufferSet = true; + mIndexFormat = format; + mIndexBufferSize = size; + } + + void CommandBufferStateTracker::SetVertexBuffer(VertexBufferSlot slot, uint64_t size) { + mVertexBufferSlotsUsed.set(slot); + mVertexBufferSizes[slot] = size; + } + + void CommandBufferStateTracker::SetPipelineCommon(PipelineBase* pipeline) { + mLastPipeline = pipeline; + mLastPipelineLayout = pipeline != nullptr ? pipeline->GetLayout() : nullptr; + mMinBufferSizes = pipeline != nullptr ? &pipeline->GetMinBufferSizes() : nullptr; + + mAspects.set(VALIDATION_ASPECT_PIPELINE); + + // Reset lazy aspects so they get recomputed on the next operation. + mAspects &= ~kLazyAspects; + } + + BindGroupBase* CommandBufferStateTracker::GetBindGroup(BindGroupIndex index) const { + return mBindgroups[index]; + } + + const std::vector<uint32_t>& CommandBufferStateTracker::GetDynamicOffsets( + BindGroupIndex index) const { + return mDynamicOffsets[index]; + } + + bool CommandBufferStateTracker::HasPipeline() const { + return mLastPipeline != nullptr; + } + + RenderPipelineBase* CommandBufferStateTracker::GetRenderPipeline() const { + ASSERT(HasPipeline() && mLastPipeline->GetType() == ObjectType::RenderPipeline); + return static_cast<RenderPipelineBase*>(mLastPipeline); + } + + ComputePipelineBase* CommandBufferStateTracker::GetComputePipeline() const { + ASSERT(HasPipeline() && mLastPipeline->GetType() == ObjectType::ComputePipeline); + return static_cast<ComputePipelineBase*>(mLastPipeline); + } + + PipelineLayoutBase* CommandBufferStateTracker::GetPipelineLayout() const { + return mLastPipelineLayout; + } + + wgpu::IndexFormat CommandBufferStateTracker::GetIndexFormat() const { + return mIndexFormat; + } + + uint64_t CommandBufferStateTracker::GetIndexBufferSize() const { + return mIndexBufferSize; + } + +} // namespace dawn::native
diff --git a/src/dawn/native/CommandBufferStateTracker.h b/src/dawn/native/CommandBufferStateTracker.h new file mode 100644 index 0000000..b68e27a --- /dev/null +++ b/src/dawn/native/CommandBufferStateTracker.h
@@ -0,0 +1,86 @@ +// Copyright 2017 The Dawn Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#ifndef DAWNNATIVE_COMMANDBUFFERSTATETRACKER_H +#define DAWNNATIVE_COMMANDBUFFERSTATETRACKER_H + +#include "dawn/common/Constants.h" +#include "dawn/common/ityp_array.h" +#include "dawn/common/ityp_bitset.h" +#include "dawn/native/BindingInfo.h" +#include "dawn/native/Error.h" +#include "dawn/native/Forward.h" + +namespace dawn::native { + + class CommandBufferStateTracker { + public: + // Non-state-modifying validation functions + MaybeError ValidateCanDispatch(); + MaybeError ValidateCanDraw(); + MaybeError ValidateCanDrawIndexed(); + MaybeError ValidateBufferInRangeForVertexBuffer(uint32_t vertexCount, uint32_t firstVertex); + MaybeError ValidateBufferInRangeForInstanceBuffer(uint32_t instanceCount, + uint32_t firstInstance); + MaybeError ValidateIndexBufferInRange(uint32_t indexCount, uint32_t firstIndex); + + // State-modifying methods + void SetComputePipeline(ComputePipelineBase* pipeline); + void SetRenderPipeline(RenderPipelineBase* pipeline); + void SetBindGroup(BindGroupIndex index, + BindGroupBase* bindgroup, + uint32_t dynamicOffsetCount, + const uint32_t* dynamicOffsets); + void SetIndexBuffer(wgpu::IndexFormat format, uint64_t size); + void SetVertexBuffer(VertexBufferSlot slot, uint64_t size); + + static constexpr size_t kNumAspects = 4; + using ValidationAspects = std::bitset<kNumAspects>; + + BindGroupBase* GetBindGroup(BindGroupIndex index) const; + const std::vector<uint32_t>& GetDynamicOffsets(BindGroupIndex index) const; + bool HasPipeline() const; + RenderPipelineBase* GetRenderPipeline() const; + ComputePipelineBase* GetComputePipeline() const; + PipelineLayoutBase* GetPipelineLayout() const; + wgpu::IndexFormat GetIndexFormat() const; + uint64_t GetIndexBufferSize() const; + + private: + MaybeError ValidateOperation(ValidationAspects requiredAspects); + void RecomputeLazyAspects(ValidationAspects aspects); + MaybeError CheckMissingAspects(ValidationAspects aspects); + + void SetPipelineCommon(PipelineBase* pipeline); + + ValidationAspects mAspects; + + ityp::array<BindGroupIndex, BindGroupBase*, kMaxBindGroups> mBindgroups = {}; + ityp::array<BindGroupIndex, std::vector<uint32_t>, kMaxBindGroups> mDynamicOffsets = {}; + ityp::bitset<VertexBufferSlot, kMaxVertexBuffers> mVertexBufferSlotsUsed; + bool mIndexBufferSet = false; + wgpu::IndexFormat mIndexFormat; + uint64_t mIndexBufferSize = 0; + + ityp::array<VertexBufferSlot, uint64_t, kMaxVertexBuffers> mVertexBufferSizes = {}; + + PipelineLayoutBase* mLastPipelineLayout = nullptr; + PipelineBase* mLastPipeline = nullptr; + + const RequiredBufferSizes* mMinBufferSizes = nullptr; + }; + +} // namespace dawn::native + +#endif // DAWNNATIVE_COMMANDBUFFERSTATETRACKER_H
diff --git a/src/dawn/native/CommandEncoder.cpp b/src/dawn/native/CommandEncoder.cpp new file mode 100644 index 0000000..7f516ab --- /dev/null +++ b/src/dawn/native/CommandEncoder.cpp
@@ -0,0 +1,1422 @@ +// Copyright 2019 The Dawn Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include "dawn/native/CommandEncoder.h" + +#include "dawn/common/BitSetIterator.h" +#include "dawn/common/Math.h" +#include "dawn/native/BindGroup.h" +#include "dawn/native/Buffer.h" +#include "dawn/native/ChainUtils_autogen.h" +#include "dawn/native/CommandBuffer.h" +#include "dawn/native/CommandBufferStateTracker.h" +#include "dawn/native/CommandValidation.h" +#include "dawn/native/Commands.h" +#include "dawn/native/ComputePassEncoder.h" +#include "dawn/native/Device.h" +#include "dawn/native/ErrorData.h" +#include "dawn/native/ObjectType_autogen.h" +#include "dawn/native/QueryHelper.h" +#include "dawn/native/QuerySet.h" +#include "dawn/native/Queue.h" +#include "dawn/native/RenderPassEncoder.h" +#include "dawn/native/RenderPipeline.h" +#include "dawn/native/ValidationUtils_autogen.h" +#include "dawn/platform/DawnPlatform.h" +#include "dawn/platform/tracing/TraceEvent.h" + +namespace dawn::native { + + namespace { + + bool HasDeprecatedColor(const RenderPassColorAttachment& attachment) { + return !std::isnan(attachment.clearColor.r) || !std::isnan(attachment.clearColor.g) || + !std::isnan(attachment.clearColor.b) || !std::isnan(attachment.clearColor.a); + } + + MaybeError ValidateB2BCopyAlignment(uint64_t dataSize, + uint64_t srcOffset, + uint64_t dstOffset) { + // Copy size must be a multiple of 4 bytes on macOS. + DAWN_INVALID_IF(dataSize % 4 != 0, "Copy size (%u) is not a multiple of 4.", dataSize); + + // SourceOffset and destinationOffset must be multiples of 4 bytes on macOS. + DAWN_INVALID_IF( + srcOffset % 4 != 0 || dstOffset % 4 != 0, + "Source offset (%u) or destination offset (%u) is not a multiple of 4 bytes,", + srcOffset, dstOffset); + + return {}; + } + + MaybeError ValidateTextureSampleCountInBufferCopyCommands(const TextureBase* texture) { + DAWN_INVALID_IF(texture->GetSampleCount() > 1, + "%s sample count (%u) is not 1 when copying to or from a buffer.", + texture, texture->GetSampleCount()); + + return {}; + } + + MaybeError ValidateLinearTextureCopyOffset(const TextureDataLayout& layout, + const TexelBlockInfo& blockInfo, + const bool hasDepthOrStencil) { + if (hasDepthOrStencil) { + // For depth-stencil texture, buffer offset must be a multiple of 4. + DAWN_INVALID_IF(layout.offset % 4 != 0, + "Offset (%u) is not a multiple of 4 for depth/stencil texture.", + layout.offset); + } else { + DAWN_INVALID_IF(layout.offset % blockInfo.byteSize != 0, + "Offset (%u) is not a multiple of the texel block byte size (%u).", + layout.offset, blockInfo.byteSize); + } + return {}; + } + + MaybeError ValidateTextureDepthStencilToBufferCopyRestrictions( + const ImageCopyTexture& src) { + Aspect aspectUsed; + DAWN_TRY_ASSIGN(aspectUsed, SingleAspectUsedByImageCopyTexture(src)); + if (aspectUsed == Aspect::Depth) { + switch (src.texture->GetFormat().format) { + case wgpu::TextureFormat::Depth24Plus: + case wgpu::TextureFormat::Depth24PlusStencil8: + case wgpu::TextureFormat::Depth24UnormStencil8: + return DAWN_FORMAT_VALIDATION_ERROR( + "The depth aspect of %s format %s cannot be selected in a texture to " + "buffer copy.", + src.texture, src.texture->GetFormat().format); + case wgpu::TextureFormat::Depth32Float: + case wgpu::TextureFormat::Depth16Unorm: + case wgpu::TextureFormat::Depth32FloatStencil8: + break; + + default: + UNREACHABLE(); + } + } + + return {}; + } + + MaybeError ValidateAttachmentArrayLayersAndLevelCount(const TextureViewBase* attachment) { + // Currently we do not support layered rendering. + DAWN_INVALID_IF(attachment->GetLayerCount() > 1, + "The layer count (%u) of %s used as attachment is greater than 1.", + attachment->GetLayerCount(), attachment); + + DAWN_INVALID_IF(attachment->GetLevelCount() > 1, + "The mip level count (%u) of %s used as attachment is greater than 1.", + attachment->GetLevelCount(), attachment); + + return {}; + } + + MaybeError ValidateOrSetAttachmentSize(const TextureViewBase* attachment, + uint32_t* width, + uint32_t* height) { + const Extent3D& attachmentSize = + attachment->GetTexture()->GetMipLevelVirtualSize(attachment->GetBaseMipLevel()); + + if (*width == 0) { + DAWN_ASSERT(*height == 0); + *width = attachmentSize.width; + *height = attachmentSize.height; + DAWN_ASSERT(*width != 0 && *height != 0); + } else { + DAWN_INVALID_IF( + *width != attachmentSize.width || *height != attachmentSize.height, + "Attachment %s size (width: %u, height: %u) does not match the size of the " + "other attachments (width: %u, height: %u).", + attachment, attachmentSize.width, attachmentSize.height, *width, *height); + } + + return {}; + } + + MaybeError ValidateOrSetColorAttachmentSampleCount(const TextureViewBase* colorAttachment, + uint32_t* sampleCount) { + if (*sampleCount == 0) { + *sampleCount = colorAttachment->GetTexture()->GetSampleCount(); + DAWN_ASSERT(*sampleCount != 0); + } else { + DAWN_INVALID_IF( + *sampleCount != colorAttachment->GetTexture()->GetSampleCount(), + "Color attachment %s sample count (%u) does not match the sample count of the " + "other attachments (%u).", + colorAttachment, colorAttachment->GetTexture()->GetSampleCount(), *sampleCount); + } + + return {}; + } + + MaybeError ValidateResolveTarget(const DeviceBase* device, + const RenderPassColorAttachment& colorAttachment, + UsageValidationMode usageValidationMode) { + if (colorAttachment.resolveTarget == nullptr) { + return {}; + } + + const TextureViewBase* resolveTarget = colorAttachment.resolveTarget; + const TextureViewBase* attachment = colorAttachment.view; + DAWN_TRY(device->ValidateObject(colorAttachment.resolveTarget)); + DAWN_TRY(ValidateCanUseAs(colorAttachment.resolveTarget->GetTexture(), + wgpu::TextureUsage::RenderAttachment, usageValidationMode)); + + DAWN_INVALID_IF( + !attachment->GetTexture()->IsMultisampledTexture(), + "Cannot set %s as a resolve target when the color attachment %s has a sample " + "count of 1.", + resolveTarget, attachment); + + DAWN_INVALID_IF(resolveTarget->GetTexture()->IsMultisampledTexture(), + "Cannot use %s as resolve target. Sample count (%u) is greater than 1.", + resolveTarget, resolveTarget->GetTexture()->GetSampleCount()); + + DAWN_INVALID_IF(resolveTarget->GetLayerCount() > 1, + "The resolve target %s array layer count (%u) is not 1.", resolveTarget, + resolveTarget->GetLayerCount()); + + DAWN_INVALID_IF(resolveTarget->GetLevelCount() > 1, + "The resolve target %s mip level count (%u) is not 1.", resolveTarget, + resolveTarget->GetLevelCount()); + + const Extent3D& colorTextureSize = + attachment->GetTexture()->GetMipLevelVirtualSize(attachment->GetBaseMipLevel()); + const Extent3D& resolveTextureSize = + resolveTarget->GetTexture()->GetMipLevelVirtualSize( + resolveTarget->GetBaseMipLevel()); + DAWN_INVALID_IF( + colorTextureSize.width != resolveTextureSize.width || + colorTextureSize.height != resolveTextureSize.height, + "The Resolve target %s size (width: %u, height: %u) does not match the color " + "attachment %s size (width: %u, height: %u).", + resolveTarget, resolveTextureSize.width, resolveTextureSize.height, attachment, + colorTextureSize.width, colorTextureSize.height); + + wgpu::TextureFormat resolveTargetFormat = resolveTarget->GetFormat().format; + DAWN_INVALID_IF( + resolveTargetFormat != attachment->GetFormat().format, + "The resolve target %s format (%s) does not match the color attachment %s format " + "(%s).", + resolveTarget, resolveTargetFormat, attachment, attachment->GetFormat().format); + DAWN_INVALID_IF( + !resolveTarget->GetFormat().supportsResolveTarget, + "The resolve target %s format (%s) does not support being used as resolve target.", + resolveTarget, resolveTargetFormat); + + return {}; + } + + MaybeError ValidateRenderPassColorAttachment( + DeviceBase* device, + const RenderPassColorAttachment& colorAttachment, + uint32_t* width, + uint32_t* height, + uint32_t* sampleCount, + UsageValidationMode usageValidationMode) { + TextureViewBase* attachment = colorAttachment.view; + if (attachment == nullptr) { + return {}; + } + DAWN_TRY(device->ValidateObject(attachment)); + DAWN_TRY(ValidateCanUseAs(attachment->GetTexture(), + wgpu::TextureUsage::RenderAttachment, usageValidationMode)); + + DAWN_INVALID_IF(!(attachment->GetAspects() & Aspect::Color) || + !attachment->GetFormat().isRenderable, + "The color attachment %s format (%s) is not color renderable.", + attachment, attachment->GetFormat().format); + + DAWN_TRY(ValidateLoadOp(colorAttachment.loadOp)); + DAWN_TRY(ValidateStoreOp(colorAttachment.storeOp)); + DAWN_INVALID_IF(colorAttachment.loadOp == wgpu::LoadOp::Undefined, + "loadOp must be set."); + DAWN_INVALID_IF(colorAttachment.storeOp == wgpu::StoreOp::Undefined, + "storeOp must be set."); + + // TODO(dawn:1269): Remove after the deprecation period. + bool useClearColor = HasDeprecatedColor(colorAttachment); + const dawn::native::Color& clearValue = + useClearColor ? colorAttachment.clearColor : colorAttachment.clearValue; + if (useClearColor) { + device->EmitDeprecationWarning( + "clearColor is deprecated, prefer using clearValue instead."); + } + + if (colorAttachment.loadOp == wgpu::LoadOp::Clear) { + DAWN_INVALID_IF(std::isnan(clearValue.r) || std::isnan(clearValue.g) || + std::isnan(clearValue.b) || std::isnan(clearValue.a), + "Color clear value (%s) contain a NaN.", &clearValue); + } + + DAWN_TRY(ValidateOrSetColorAttachmentSampleCount(attachment, sampleCount)); + + DAWN_TRY(ValidateResolveTarget(device, colorAttachment, usageValidationMode)); + + DAWN_TRY(ValidateAttachmentArrayLayersAndLevelCount(attachment)); + DAWN_TRY(ValidateOrSetAttachmentSize(attachment, width, height)); + + return {}; + } + + MaybeError ValidateRenderPassDepthStencilAttachment( + DeviceBase* device, + const RenderPassDepthStencilAttachment* depthStencilAttachment, + uint32_t* width, + uint32_t* height, + uint32_t* sampleCount, + UsageValidationMode usageValidationMode) { + DAWN_ASSERT(depthStencilAttachment != nullptr); + + TextureViewBase* attachment = depthStencilAttachment->view; + DAWN_TRY(device->ValidateObject(attachment)); + DAWN_TRY(ValidateCanUseAs(attachment->GetTexture(), + wgpu::TextureUsage::RenderAttachment, usageValidationMode)); + + const Format& format = attachment->GetFormat(); + DAWN_INVALID_IF( + !format.HasDepthOrStencil(), + "The depth stencil attachment %s format (%s) is not a depth stencil format.", + attachment, format.format); + + DAWN_INVALID_IF(!format.isRenderable, + "The depth stencil attachment %s format (%s) is not renderable.", + attachment, format.format); + + DAWN_INVALID_IF(attachment->GetAspects() != format.aspects, + "The depth stencil attachment %s must encompass all aspects.", + attachment); + + DAWN_INVALID_IF( + attachment->GetAspects() == (Aspect::Depth | Aspect::Stencil) && + depthStencilAttachment->depthReadOnly != + depthStencilAttachment->stencilReadOnly, + "depthReadOnly (%u) and stencilReadOnly (%u) must be the same when texture aspect " + "is 'all'.", + depthStencilAttachment->depthReadOnly, depthStencilAttachment->stencilReadOnly); + + // Read only, or depth doesn't exist. + if (depthStencilAttachment->depthReadOnly || + !IsSubset(Aspect::Depth, attachment->GetAspects())) { + if (depthStencilAttachment->depthLoadOp == wgpu::LoadOp::Load && + depthStencilAttachment->depthStoreOp == wgpu::StoreOp::Store) { + // TODO(dawn:1269): Remove this branch after the deprecation period. + device->EmitDeprecationWarning( + "Setting depthLoadOp and depthStoreOp when " + "the attachment has no depth aspect or depthReadOnly is true is " + "deprecated."); + } else { + DAWN_INVALID_IF(depthStencilAttachment->depthLoadOp != wgpu::LoadOp::Undefined, + "depthLoadOp (%s) must not be set if the attachment (%s) has " + "no depth aspect or depthReadOnly (%u) is true.", + depthStencilAttachment->depthLoadOp, attachment, + depthStencilAttachment->depthReadOnly); + DAWN_INVALID_IF( + depthStencilAttachment->depthStoreOp != wgpu::StoreOp::Undefined, + "depthStoreOp (%s) must not be set if the attachment (%s) has no depth " + "aspect or depthReadOnly (%u) is true.", + depthStencilAttachment->depthStoreOp, attachment, + depthStencilAttachment->depthReadOnly); + } + } else { + DAWN_TRY(ValidateLoadOp(depthStencilAttachment->depthLoadOp)); + DAWN_INVALID_IF(depthStencilAttachment->depthLoadOp == wgpu::LoadOp::Undefined, + "depthLoadOp must be set if the attachment (%s) has a depth aspect " + "and depthReadOnly (%u) is false.", + attachment, depthStencilAttachment->depthReadOnly); + DAWN_TRY(ValidateStoreOp(depthStencilAttachment->depthStoreOp)); + DAWN_INVALID_IF(depthStencilAttachment->depthStoreOp == wgpu::StoreOp::Undefined, + "depthStoreOp must be set if the attachment (%s) has a depth " + "aspect and depthReadOnly (%u) is false.", + attachment, depthStencilAttachment->depthReadOnly); + } + + // Read only, or stencil doesn't exist. + if (depthStencilAttachment->stencilReadOnly || + !IsSubset(Aspect::Stencil, attachment->GetAspects())) { + if (depthStencilAttachment->stencilLoadOp == wgpu::LoadOp::Load && + depthStencilAttachment->stencilStoreOp == wgpu::StoreOp::Store) { + // TODO(dawn:1269): Remove this branch after the deprecation period. + device->EmitDeprecationWarning( + "Setting stencilLoadOp and stencilStoreOp when " + "the attachment has no stencil aspect or stencilReadOnly is true is " + "deprecated."); + } else { + DAWN_INVALID_IF( + depthStencilAttachment->stencilLoadOp != wgpu::LoadOp::Undefined, + "stencilLoadOp (%s) must not be set if the attachment (%s) has no stencil " + "aspect or stencilReadOnly (%u) is true.", + depthStencilAttachment->stencilLoadOp, attachment, + depthStencilAttachment->stencilReadOnly); + DAWN_INVALID_IF( + depthStencilAttachment->stencilStoreOp != wgpu::StoreOp::Undefined, + "stencilStoreOp (%s) must not be set if the attachment (%s) has no stencil " + "aspect or stencilReadOnly (%u) is true.", + depthStencilAttachment->stencilStoreOp, attachment, + depthStencilAttachment->stencilReadOnly); + } + } else { + DAWN_TRY(ValidateLoadOp(depthStencilAttachment->stencilLoadOp)); + DAWN_INVALID_IF( + depthStencilAttachment->stencilLoadOp == wgpu::LoadOp::Undefined, + "stencilLoadOp (%s) must be set if the attachment (%s) has a stencil " + "aspect and stencilReadOnly (%u) is false.", + depthStencilAttachment->stencilLoadOp, attachment, + depthStencilAttachment->stencilReadOnly); + DAWN_TRY(ValidateStoreOp(depthStencilAttachment->stencilStoreOp)); + DAWN_INVALID_IF( + depthStencilAttachment->stencilStoreOp == wgpu::StoreOp::Undefined, + "stencilStoreOp (%s) must be set if the attachment (%s) has a stencil " + "aspect and stencilReadOnly (%u) is false.", + depthStencilAttachment->stencilStoreOp, attachment, + depthStencilAttachment->stencilReadOnly); + } + + if (!std::isnan(depthStencilAttachment->clearDepth)) { + // TODO(dawn:1269): Remove this branch after the deprecation period. + device->EmitDeprecationWarning( + "clearDepth is deprecated, prefer depthClearValue instead."); + } else { + DAWN_INVALID_IF(depthStencilAttachment->depthLoadOp == wgpu::LoadOp::Clear && + std::isnan(depthStencilAttachment->depthClearValue), + "depthClearValue is NaN."); + } + + // TODO(dawn:1269): Remove after the deprecation period. + if (depthStencilAttachment->stencilClearValue == 0 && + depthStencilAttachment->clearStencil != 0) { + device->EmitDeprecationWarning( + "clearStencil is deprecated, prefer stencilClearValue instead."); + } + + // *sampleCount == 0 must only happen when there is no color attachment. In that case we + // do not need to validate the sample count of the depth stencil attachment. + const uint32_t depthStencilSampleCount = attachment->GetTexture()->GetSampleCount(); + if (*sampleCount != 0) { + DAWN_INVALID_IF( + depthStencilSampleCount != *sampleCount, + "The depth stencil attachment %s sample count (%u) does not match the sample " + "count of the other attachments (%u).", + attachment, depthStencilSampleCount, *sampleCount); + } else { + *sampleCount = depthStencilSampleCount; + } + + DAWN_TRY(ValidateAttachmentArrayLayersAndLevelCount(attachment)); + DAWN_TRY(ValidateOrSetAttachmentSize(attachment, width, height)); + + return {}; + } + + MaybeError ValidateRenderPassDescriptor(DeviceBase* device, + const RenderPassDescriptor* descriptor, + uint32_t* width, + uint32_t* height, + uint32_t* sampleCount, + UsageValidationMode usageValidationMode) { + DAWN_INVALID_IF( + descriptor->colorAttachmentCount > kMaxColorAttachments, + "Color attachment count (%u) exceeds the maximum number of color attachments (%u).", + descriptor->colorAttachmentCount, kMaxColorAttachments); + + bool isAllColorAttachmentNull = true; + for (uint32_t i = 0; i < descriptor->colorAttachmentCount; ++i) { + DAWN_TRY_CONTEXT(ValidateRenderPassColorAttachment( + device, descriptor->colorAttachments[i], width, height, + sampleCount, usageValidationMode), + "validating colorAttachments[%u].", i); + if (descriptor->colorAttachments[i].view) { + isAllColorAttachmentNull = false; + } + } + + if (descriptor->depthStencilAttachment != nullptr) { + DAWN_TRY_CONTEXT(ValidateRenderPassDepthStencilAttachment( + device, descriptor->depthStencilAttachment, width, height, + sampleCount, usageValidationMode), + "validating depthStencilAttachment."); + } else { + DAWN_INVALID_IF( + isAllColorAttachmentNull, + "No color or depthStencil attachments specified. At least one is required."); + } + + if (descriptor->occlusionQuerySet != nullptr) { + DAWN_TRY(device->ValidateObject(descriptor->occlusionQuerySet)); + + DAWN_INVALID_IF( + descriptor->occlusionQuerySet->GetQueryType() != wgpu::QueryType::Occlusion, + "The occlusionQuerySet %s type (%s) is not %s.", descriptor->occlusionQuerySet, + descriptor->occlusionQuerySet->GetQueryType(), wgpu::QueryType::Occlusion); + } + + if (descriptor->timestampWriteCount > 0) { + DAWN_ASSERT(descriptor->timestampWrites != nullptr); + + // Record the query set and query index used on render passes for validating query + // index overwrite. The TrackQueryAvailability of + // RenderPassResourceUsageTracker is not used here because the timestampWrites are + // not validated and encoded one by one, but encoded together after passing the + // validation. + QueryAvailabilityMap usedQueries; + for (uint32_t i = 0; i < descriptor->timestampWriteCount; ++i) { + QuerySetBase* querySet = descriptor->timestampWrites[i].querySet; + DAWN_ASSERT(querySet != nullptr); + uint32_t queryIndex = descriptor->timestampWrites[i].queryIndex; + DAWN_TRY_CONTEXT(ValidateTimestampQuery(device, querySet, queryIndex), + "validating querySet and queryIndex of timestampWrites[%u].", + i); + DAWN_TRY_CONTEXT(ValidateRenderPassTimestampLocation( + descriptor->timestampWrites[i].location), + "validating location of timestampWrites[%u].", i); + + auto checkIt = usedQueries.find(querySet); + DAWN_INVALID_IF(checkIt != usedQueries.end() && checkIt->second[queryIndex], + "Query index %u of %s is written to twice in a render pass.", + queryIndex, querySet); + + // Gets the iterator for that querySet or create a new vector of bool set to + // false if the querySet wasn't registered. + auto addIt = usedQueries.emplace(querySet, querySet->GetQueryCount()).first; + addIt->second[queryIndex] = true; + } + } + + DAWN_INVALID_IF(descriptor->colorAttachmentCount == 0 && + descriptor->depthStencilAttachment == nullptr, + "Render pass has no attachments."); + + return {}; + } + + MaybeError ValidateComputePassDescriptor(const DeviceBase* device, + const ComputePassDescriptor* descriptor) { + if (descriptor == nullptr) { + return {}; + } + + if (descriptor->timestampWriteCount > 0) { + DAWN_ASSERT(descriptor->timestampWrites != nullptr); + + for (uint32_t i = 0; i < descriptor->timestampWriteCount; ++i) { + DAWN_ASSERT(descriptor->timestampWrites[i].querySet != nullptr); + DAWN_TRY_CONTEXT( + ValidateTimestampQuery(device, descriptor->timestampWrites[i].querySet, + descriptor->timestampWrites[i].queryIndex), + "validating querySet and queryIndex of timestampWrites[%u].", i); + DAWN_TRY_CONTEXT(ValidateComputePassTimestampLocation( + descriptor->timestampWrites[i].location), + "validating location of timestampWrites[%u].", i); + } + } + + return {}; + } + + MaybeError ValidateQuerySetResolve(const QuerySetBase* querySet, + uint32_t firstQuery, + uint32_t queryCount, + const BufferBase* destination, + uint64_t destinationOffset) { + DAWN_INVALID_IF(firstQuery >= querySet->GetQueryCount(), + "First query (%u) exceeds the number of queries (%u) in %s.", + firstQuery, querySet->GetQueryCount(), querySet); + + DAWN_INVALID_IF( + queryCount > querySet->GetQueryCount() - firstQuery, + "The query range (firstQuery: %u, queryCount: %u) exceeds the number of queries " + "(%u) in %s.", + firstQuery, queryCount, querySet->GetQueryCount(), querySet); + + DAWN_INVALID_IF(destinationOffset % 256 != 0, + "The destination buffer %s offset (%u) is not a multiple of 256.", + destination, destinationOffset); + + uint64_t bufferSize = destination->GetSize(); + // The destination buffer must have enough storage, from destination offset, to contain + // the result of resolved queries + bool fitsInBuffer = destinationOffset <= bufferSize && + (static_cast<uint64_t>(queryCount) * sizeof(uint64_t) <= + (bufferSize - destinationOffset)); + DAWN_INVALID_IF( + !fitsInBuffer, + "The resolved %s data size (%u) would not fit in %s with size %u at the offset %u.", + querySet, static_cast<uint64_t>(queryCount) * sizeof(uint64_t), destination, + bufferSize, destinationOffset); + + return {}; + } + + MaybeError EncodeTimestampsToNanosecondsConversion(CommandEncoder* encoder, + QuerySetBase* querySet, + uint32_t firstQuery, + uint32_t queryCount, + BufferBase* destination, + uint64_t destinationOffset) { + DeviceBase* device = encoder->GetDevice(); + + // The availability got from query set is a reference to vector<bool>, need to covert + // bool to uint32_t due to a user input in pipeline must not contain a bool type in + // WGSL. + std::vector<uint32_t> availability{querySet->GetQueryAvailability().begin(), + querySet->GetQueryAvailability().end()}; + + // Timestamp availability storage buffer + BufferDescriptor availabilityDesc = {}; + availabilityDesc.usage = wgpu::BufferUsage::Storage | wgpu::BufferUsage::CopyDst; + availabilityDesc.size = querySet->GetQueryCount() * sizeof(uint32_t); + Ref<BufferBase> availabilityBuffer; + DAWN_TRY_ASSIGN(availabilityBuffer, device->CreateBuffer(&availabilityDesc)); + + DAWN_TRY(device->GetQueue()->WriteBuffer(availabilityBuffer.Get(), 0, + availability.data(), + availability.size() * sizeof(uint32_t))); + + // Timestamp params uniform buffer + TimestampParams params(firstQuery, queryCount, static_cast<uint32_t>(destinationOffset), + device->GetTimestampPeriodInNS()); + + BufferDescriptor parmsDesc = {}; + parmsDesc.usage = wgpu::BufferUsage::Uniform | wgpu::BufferUsage::CopyDst; + parmsDesc.size = sizeof(params); + Ref<BufferBase> paramsBuffer; + DAWN_TRY_ASSIGN(paramsBuffer, device->CreateBuffer(&parmsDesc)); + + DAWN_TRY( + device->GetQueue()->WriteBuffer(paramsBuffer.Get(), 0, ¶ms, sizeof(params))); + + return EncodeConvertTimestampsToNanoseconds( + encoder, destination, availabilityBuffer.Get(), paramsBuffer.Get()); + } + + bool IsReadOnlyDepthStencilAttachment( + const RenderPassDepthStencilAttachment* depthStencilAttachment) { + DAWN_ASSERT(depthStencilAttachment != nullptr); + Aspect aspects = depthStencilAttachment->view->GetAspects(); + DAWN_ASSERT(IsSubset(aspects, Aspect::Depth | Aspect::Stencil)); + + if ((aspects & Aspect::Depth) && !depthStencilAttachment->depthReadOnly) { + return false; + } + if (aspects & Aspect::Stencil && !depthStencilAttachment->stencilReadOnly) { + return false; + } + return true; + } + + } // namespace + + MaybeError ValidateCommandEncoderDescriptor(const DeviceBase* device, + const CommandEncoderDescriptor* descriptor) { + DAWN_TRY(ValidateSingleSType(descriptor->nextInChain, + wgpu::SType::DawnEncoderInternalUsageDescriptor)); + + const DawnEncoderInternalUsageDescriptor* internalUsageDesc = nullptr; + FindInChain(descriptor->nextInChain, &internalUsageDesc); + + DAWN_INVALID_IF(internalUsageDesc != nullptr && + !device->APIHasFeature(wgpu::FeatureName::DawnInternalUsages), + "%s is not available.", wgpu::FeatureName::DawnInternalUsages); + return {}; + } + + // static + Ref<CommandEncoder> CommandEncoder::Create(DeviceBase* device, + const CommandEncoderDescriptor* descriptor) { + return AcquireRef(new CommandEncoder(device, descriptor)); + } + + // static + CommandEncoder* CommandEncoder::MakeError(DeviceBase* device) { + return new CommandEncoder(device, ObjectBase::kError); + } + + CommandEncoder::CommandEncoder(DeviceBase* device, const CommandEncoderDescriptor* descriptor) + : ApiObjectBase(device, descriptor->label), mEncodingContext(device, this) { + TrackInDevice(); + + const DawnEncoderInternalUsageDescriptor* internalUsageDesc = nullptr; + FindInChain(descriptor->nextInChain, &internalUsageDesc); + + if (internalUsageDesc != nullptr && internalUsageDesc->useInternalUsages) { + mUsageValidationMode = UsageValidationMode::Internal; + } else { + mUsageValidationMode = UsageValidationMode::Default; + } + } + + CommandEncoder::CommandEncoder(DeviceBase* device, ObjectBase::ErrorTag tag) + : ApiObjectBase(device, tag), + mEncodingContext(device, this), + mUsageValidationMode(UsageValidationMode::Default) { + mEncodingContext.HandleError(DAWN_FORMAT_VALIDATION_ERROR("%s is invalid.", this)); + } + + ObjectType CommandEncoder::GetType() const { + return ObjectType::CommandEncoder; + } + + void CommandEncoder::DestroyImpl() { + mEncodingContext.Destroy(); + } + + CommandBufferResourceUsage CommandEncoder::AcquireResourceUsages() { + return CommandBufferResourceUsage{ + mEncodingContext.AcquireRenderPassUsages(), mEncodingContext.AcquireComputePassUsages(), + std::move(mTopLevelBuffers), std::move(mTopLevelTextures), std::move(mUsedQuerySets)}; + } + + CommandIterator CommandEncoder::AcquireCommands() { + return mEncodingContext.AcquireCommands(); + } + + void CommandEncoder::TrackUsedQuerySet(QuerySetBase* querySet) { + mUsedQuerySets.insert(querySet); + } + + void CommandEncoder::TrackQueryAvailability(QuerySetBase* querySet, uint32_t queryIndex) { + DAWN_ASSERT(querySet != nullptr); + + if (GetDevice()->IsValidationEnabled()) { + TrackUsedQuerySet(querySet); + } + + // Set the query at queryIndex to available for resolving in query set. + querySet->SetQueryAvailability(queryIndex, true); + } + + // Implementation of the API's command recording methods + + ComputePassEncoder* CommandEncoder::APIBeginComputePass( + const ComputePassDescriptor* descriptor) { + return BeginComputePass(descriptor).Detach(); + } + + Ref<ComputePassEncoder> CommandEncoder::BeginComputePass( + const ComputePassDescriptor* descriptor) { + DeviceBase* device = GetDevice(); + + std::vector<TimestampWrite> timestampWritesAtBeginning; + std::vector<TimestampWrite> timestampWritesAtEnd; + bool success = mEncodingContext.TryEncode( + this, + [&](CommandAllocator* allocator) -> MaybeError { + DAWN_TRY(ValidateComputePassDescriptor(device, descriptor)); + + BeginComputePassCmd* cmd = + allocator->Allocate<BeginComputePassCmd>(Command::BeginComputePass); + + if (descriptor == nullptr) { + return {}; + } + + // Split the timestampWrites used in BeginComputePassCmd and EndComputePassCmd + for (uint32_t i = 0; i < descriptor->timestampWriteCount; i++) { + QuerySetBase* querySet = descriptor->timestampWrites[i].querySet; + uint32_t queryIndex = descriptor->timestampWrites[i].queryIndex; + + switch (descriptor->timestampWrites[i].location) { + case wgpu::ComputePassTimestampLocation::Beginning: + timestampWritesAtBeginning.push_back({querySet, queryIndex}); + break; + case wgpu::ComputePassTimestampLocation::End: + timestampWritesAtEnd.push_back({querySet, queryIndex}); + break; + default: + break; + } + + TrackQueryAvailability(querySet, queryIndex); + } + + cmd->timestampWrites = std::move(timestampWritesAtBeginning); + + return {}; + }, + "encoding %s.BeginComputePass(%s).", this, descriptor); + + if (success) { + const ComputePassDescriptor defaultDescriptor = {}; + if (descriptor == nullptr) { + descriptor = &defaultDescriptor; + } + + Ref<ComputePassEncoder> passEncoder = ComputePassEncoder::Create( + device, descriptor, this, &mEncodingContext, std::move(timestampWritesAtEnd)); + mEncodingContext.EnterPass(passEncoder.Get()); + return passEncoder; + } + + return ComputePassEncoder::MakeError(device, this, &mEncodingContext); + } + + RenderPassEncoder* CommandEncoder::APIBeginRenderPass(const RenderPassDescriptor* descriptor) { + return BeginRenderPass(descriptor).Detach(); + } + + Ref<RenderPassEncoder> CommandEncoder::BeginRenderPass(const RenderPassDescriptor* descriptor) { + DeviceBase* device = GetDevice(); + + RenderPassResourceUsageTracker usageTracker; + + uint32_t width = 0; + uint32_t height = 0; + bool depthReadOnly = false; + bool stencilReadOnly = false; + Ref<AttachmentState> attachmentState; + std::vector<TimestampWrite> timestampWritesAtBeginning; + std::vector<TimestampWrite> timestampWritesAtEnd; + bool success = mEncodingContext.TryEncode( + this, + [&](CommandAllocator* allocator) -> MaybeError { + uint32_t sampleCount = 0; + + DAWN_TRY(ValidateRenderPassDescriptor(device, descriptor, &width, &height, + &sampleCount, mUsageValidationMode)); + + ASSERT(width > 0 && height > 0 && sampleCount > 0); + + mEncodingContext.WillBeginRenderPass(); + BeginRenderPassCmd* cmd = + allocator->Allocate<BeginRenderPassCmd>(Command::BeginRenderPass); + + cmd->attachmentState = device->GetOrCreateAttachmentState(descriptor); + attachmentState = cmd->attachmentState; + + // Split the timestampWrites used in BeginRenderPassCmd and EndRenderPassCmd + for (uint32_t i = 0; i < descriptor->timestampWriteCount; i++) { + QuerySetBase* querySet = descriptor->timestampWrites[i].querySet; + uint32_t queryIndex = descriptor->timestampWrites[i].queryIndex; + + switch (descriptor->timestampWrites[i].location) { + case wgpu::RenderPassTimestampLocation::Beginning: + timestampWritesAtBeginning.push_back({querySet, queryIndex}); + break; + case wgpu::RenderPassTimestampLocation::End: + timestampWritesAtEnd.push_back({querySet, queryIndex}); + break; + default: + break; + } + + TrackQueryAvailability(querySet, queryIndex); + // Track the query availability with true on render pass again for rewrite + // validation and query reset on Vulkan + usageTracker.TrackQueryAvailability(querySet, queryIndex); + } + + for (ColorAttachmentIndex index : + IterateBitSet(cmd->attachmentState->GetColorAttachmentsMask())) { + uint8_t i = static_cast<uint8_t>(index); + TextureViewBase* view = descriptor->colorAttachments[i].view; + TextureViewBase* resolveTarget = descriptor->colorAttachments[i].resolveTarget; + + cmd->colorAttachments[index].view = view; + cmd->colorAttachments[index].resolveTarget = resolveTarget; + cmd->colorAttachments[index].loadOp = descriptor->colorAttachments[i].loadOp; + cmd->colorAttachments[index].storeOp = descriptor->colorAttachments[i].storeOp; + + cmd->colorAttachments[index].clearColor = + HasDeprecatedColor(descriptor->colorAttachments[i]) + ? descriptor->colorAttachments[i].clearColor + : descriptor->colorAttachments[i].clearValue; + + usageTracker.TextureViewUsedAs(view, wgpu::TextureUsage::RenderAttachment); + + if (resolveTarget != nullptr) { + usageTracker.TextureViewUsedAs(resolveTarget, + wgpu::TextureUsage::RenderAttachment); + } + } + + if (cmd->attachmentState->HasDepthStencilAttachment()) { + TextureViewBase* view = descriptor->depthStencilAttachment->view; + + cmd->depthStencilAttachment.view = view; + + if (!std::isnan(descriptor->depthStencilAttachment->clearDepth)) { + // TODO(dawn:1269): Remove this branch after the deprecation period. + cmd->depthStencilAttachment.clearDepth = + descriptor->depthStencilAttachment->clearDepth; + } else { + cmd->depthStencilAttachment.clearDepth = + descriptor->depthStencilAttachment->depthClearValue; + } + + if (descriptor->depthStencilAttachment->stencilClearValue == 0 && + descriptor->depthStencilAttachment->clearStencil != 0) { + // TODO(dawn:1269): Remove this branch after the deprecation period. + cmd->depthStencilAttachment.clearStencil = + descriptor->depthStencilAttachment->clearStencil; + } else { + cmd->depthStencilAttachment.clearStencil = + descriptor->depthStencilAttachment->stencilClearValue; + } + + cmd->depthStencilAttachment.depthReadOnly = + descriptor->depthStencilAttachment->depthReadOnly; + cmd->depthStencilAttachment.stencilReadOnly = + descriptor->depthStencilAttachment->stencilReadOnly; + + if (descriptor->depthStencilAttachment->depthReadOnly || + !IsSubset(Aspect::Depth, + descriptor->depthStencilAttachment->view->GetAspects())) { + cmd->depthStencilAttachment.depthLoadOp = wgpu::LoadOp::Load; + cmd->depthStencilAttachment.depthStoreOp = wgpu::StoreOp::Store; + } else { + cmd->depthStencilAttachment.depthLoadOp = + descriptor->depthStencilAttachment->depthLoadOp; + cmd->depthStencilAttachment.depthStoreOp = + descriptor->depthStencilAttachment->depthStoreOp; + } + + if (descriptor->depthStencilAttachment->stencilReadOnly || + !IsSubset(Aspect::Stencil, + descriptor->depthStencilAttachment->view->GetAspects())) { + cmd->depthStencilAttachment.stencilLoadOp = wgpu::LoadOp::Load; + cmd->depthStencilAttachment.stencilStoreOp = wgpu::StoreOp::Store; + } else { + cmd->depthStencilAttachment.stencilLoadOp = + descriptor->depthStencilAttachment->stencilLoadOp; + cmd->depthStencilAttachment.stencilStoreOp = + descriptor->depthStencilAttachment->stencilStoreOp; + } + + if (IsReadOnlyDepthStencilAttachment(descriptor->depthStencilAttachment)) { + usageTracker.TextureViewUsedAs(view, kReadOnlyRenderAttachment); + } else { + usageTracker.TextureViewUsedAs(view, wgpu::TextureUsage::RenderAttachment); + } + + depthReadOnly = descriptor->depthStencilAttachment->depthReadOnly; + stencilReadOnly = descriptor->depthStencilAttachment->stencilReadOnly; + } + + cmd->width = width; + cmd->height = height; + + cmd->occlusionQuerySet = descriptor->occlusionQuerySet; + + cmd->timestampWrites = std::move(timestampWritesAtBeginning); + + return {}; + }, + "encoding %s.BeginRenderPass(%s).", this, descriptor); + + if (success) { + Ref<RenderPassEncoder> passEncoder = RenderPassEncoder::Create( + device, descriptor, this, &mEncodingContext, std::move(usageTracker), + std::move(attachmentState), std::move(timestampWritesAtEnd), width, height, + depthReadOnly, stencilReadOnly); + mEncodingContext.EnterPass(passEncoder.Get()); + return passEncoder; + } + + return RenderPassEncoder::MakeError(device, this, &mEncodingContext); + } + + void CommandEncoder::APICopyBufferToBuffer(BufferBase* source, + uint64_t sourceOffset, + BufferBase* destination, + uint64_t destinationOffset, + uint64_t size) { + mEncodingContext.TryEncode( + this, + [&](CommandAllocator* allocator) -> MaybeError { + if (GetDevice()->IsValidationEnabled()) { + DAWN_TRY(GetDevice()->ValidateObject(source)); + DAWN_TRY(GetDevice()->ValidateObject(destination)); + + DAWN_INVALID_IF(source == destination, + "Source and destination are the same buffer (%s).", source); + + DAWN_TRY_CONTEXT(ValidateCopySizeFitsInBuffer(source, sourceOffset, size), + "validating source %s copy size.", source); + DAWN_TRY_CONTEXT( + ValidateCopySizeFitsInBuffer(destination, destinationOffset, size), + "validating destination %s copy size.", destination); + DAWN_TRY(ValidateB2BCopyAlignment(size, sourceOffset, destinationOffset)); + + DAWN_TRY_CONTEXT(ValidateCanUseAs(source, wgpu::BufferUsage::CopySrc), + "validating source %s usage.", source); + DAWN_TRY_CONTEXT(ValidateCanUseAs(destination, wgpu::BufferUsage::CopyDst), + "validating destination %s usage.", destination); + + mTopLevelBuffers.insert(source); + mTopLevelBuffers.insert(destination); + } + + CopyBufferToBufferCmd* copy = + allocator->Allocate<CopyBufferToBufferCmd>(Command::CopyBufferToBuffer); + copy->source = source; + copy->sourceOffset = sourceOffset; + copy->destination = destination; + copy->destinationOffset = destinationOffset; + copy->size = size; + + return {}; + }, + "encoding %s.CopyBufferToBuffer(%s, %u, %s, %u, %u).", this, source, sourceOffset, + destination, destinationOffset, size); + } + + void CommandEncoder::APICopyBufferToTexture(const ImageCopyBuffer* source, + const ImageCopyTexture* destination, + const Extent3D* copySize) { + mEncodingContext.TryEncode( + this, + [&](CommandAllocator* allocator) -> MaybeError { + if (GetDevice()->IsValidationEnabled()) { + DAWN_TRY(ValidateImageCopyBuffer(GetDevice(), *source)); + DAWN_TRY_CONTEXT(ValidateCanUseAs(source->buffer, wgpu::BufferUsage::CopySrc), + "validating source %s usage.", source->buffer); + + DAWN_TRY(ValidateImageCopyTexture(GetDevice(), *destination, *copySize)); + DAWN_TRY_CONTEXT( + ValidateCanUseAs(destination->texture, wgpu::TextureUsage::CopyDst, + mUsageValidationMode), + "validating destination %s usage.", destination->texture); + DAWN_TRY(ValidateTextureSampleCountInBufferCopyCommands(destination->texture)); + + DAWN_TRY(ValidateLinearToDepthStencilCopyRestrictions(*destination)); + // We validate texture copy range before validating linear texture data, + // because in the latter we divide copyExtent.width by blockWidth and + // copyExtent.height by blockHeight while the divisibility conditions are + // checked in validating texture copy range. + DAWN_TRY(ValidateTextureCopyRange(GetDevice(), *destination, *copySize)); + } + const TexelBlockInfo& blockInfo = + destination->texture->GetFormat().GetAspectInfo(destination->aspect).block; + if (GetDevice()->IsValidationEnabled()) { + DAWN_TRY(ValidateLinearTextureCopyOffset( + source->layout, blockInfo, + destination->texture->GetFormat().HasDepthOrStencil())); + DAWN_TRY(ValidateLinearTextureData(source->layout, source->buffer->GetSize(), + blockInfo, *copySize)); + + mTopLevelBuffers.insert(source->buffer); + mTopLevelTextures.insert(destination->texture); + } + + TextureDataLayout srcLayout = source->layout; + ApplyDefaultTextureDataLayoutOptions(&srcLayout, blockInfo, *copySize); + + CopyBufferToTextureCmd* copy = + allocator->Allocate<CopyBufferToTextureCmd>(Command::CopyBufferToTexture); + copy->source.buffer = source->buffer; + copy->source.offset = srcLayout.offset; + copy->source.bytesPerRow = srcLayout.bytesPerRow; + copy->source.rowsPerImage = srcLayout.rowsPerImage; + copy->destination.texture = destination->texture; + copy->destination.origin = destination->origin; + copy->destination.mipLevel = destination->mipLevel; + copy->destination.aspect = + ConvertAspect(destination->texture->GetFormat(), destination->aspect); + copy->copySize = *copySize; + + return {}; + }, + "encoding %s.CopyBufferToTexture(%s, %s, %s).", this, source->buffer, + destination->texture, copySize); + } + + void CommandEncoder::APICopyTextureToBuffer(const ImageCopyTexture* source, + const ImageCopyBuffer* destination, + const Extent3D* copySize) { + mEncodingContext.TryEncode( + this, + [&](CommandAllocator* allocator) -> MaybeError { + if (GetDevice()->IsValidationEnabled()) { + DAWN_TRY(ValidateImageCopyTexture(GetDevice(), *source, *copySize)); + DAWN_TRY_CONTEXT(ValidateCanUseAs(source->texture, wgpu::TextureUsage::CopySrc, + mUsageValidationMode), + "validating source %s usage.", source->texture); + DAWN_TRY(ValidateTextureSampleCountInBufferCopyCommands(source->texture)); + DAWN_TRY(ValidateTextureDepthStencilToBufferCopyRestrictions(*source)); + + DAWN_TRY(ValidateImageCopyBuffer(GetDevice(), *destination)); + DAWN_TRY_CONTEXT( + ValidateCanUseAs(destination->buffer, wgpu::BufferUsage::CopyDst), + "validating destination %s usage.", destination->buffer); + + // We validate texture copy range before validating linear texture data, + // because in the latter we divide copyExtent.width by blockWidth and + // copyExtent.height by blockHeight while the divisibility conditions are + // checked in validating texture copy range. + DAWN_TRY(ValidateTextureCopyRange(GetDevice(), *source, *copySize)); + } + const TexelBlockInfo& blockInfo = + source->texture->GetFormat().GetAspectInfo(source->aspect).block; + if (GetDevice()->IsValidationEnabled()) { + DAWN_TRY(ValidateLinearTextureCopyOffset( + destination->layout, blockInfo, + source->texture->GetFormat().HasDepthOrStencil())); + DAWN_TRY(ValidateLinearTextureData( + destination->layout, destination->buffer->GetSize(), blockInfo, *copySize)); + + mTopLevelTextures.insert(source->texture); + mTopLevelBuffers.insert(destination->buffer); + } + + TextureDataLayout dstLayout = destination->layout; + ApplyDefaultTextureDataLayoutOptions(&dstLayout, blockInfo, *copySize); + + CopyTextureToBufferCmd* copy = + allocator->Allocate<CopyTextureToBufferCmd>(Command::CopyTextureToBuffer); + copy->source.texture = source->texture; + copy->source.origin = source->origin; + copy->source.mipLevel = source->mipLevel; + copy->source.aspect = ConvertAspect(source->texture->GetFormat(), source->aspect); + copy->destination.buffer = destination->buffer; + copy->destination.offset = dstLayout.offset; + copy->destination.bytesPerRow = dstLayout.bytesPerRow; + copy->destination.rowsPerImage = dstLayout.rowsPerImage; + copy->copySize = *copySize; + + return {}; + }, + "encoding %s.CopyTextureToBuffer(%s, %s, %s).", this, source->texture, + destination->buffer, copySize); + } + + void CommandEncoder::APICopyTextureToTexture(const ImageCopyTexture* source, + const ImageCopyTexture* destination, + const Extent3D* copySize) { + APICopyTextureToTextureHelper<false>(source, destination, copySize); + } + + void CommandEncoder::APICopyTextureToTextureInternal(const ImageCopyTexture* source, + const ImageCopyTexture* destination, + const Extent3D* copySize) { + APICopyTextureToTextureHelper<true>(source, destination, copySize); + } + + template <bool Internal> + void CommandEncoder::APICopyTextureToTextureHelper(const ImageCopyTexture* source, + const ImageCopyTexture* destination, + const Extent3D* copySize) { + mEncodingContext.TryEncode( + this, + [&](CommandAllocator* allocator) -> MaybeError { + if (GetDevice()->IsValidationEnabled()) { + DAWN_TRY(GetDevice()->ValidateObject(source->texture)); + DAWN_TRY(GetDevice()->ValidateObject(destination->texture)); + + DAWN_TRY_CONTEXT(ValidateImageCopyTexture(GetDevice(), *source, *copySize), + "validating source %s.", source->texture); + DAWN_TRY_CONTEXT(ValidateImageCopyTexture(GetDevice(), *destination, *copySize), + "validating destination %s.", destination->texture); + + DAWN_TRY( + ValidateTextureToTextureCopyRestrictions(*source, *destination, *copySize)); + + DAWN_TRY_CONTEXT(ValidateTextureCopyRange(GetDevice(), *source, *copySize), + "validating source %s copy range.", source->texture); + DAWN_TRY_CONTEXT(ValidateTextureCopyRange(GetDevice(), *destination, *copySize), + "validating source %s copy range.", destination->texture); + + // For internal usages (CopyToCopyInternal) we don't care if the user has added + // CopySrc as a usage for this texture, but we will always add it internally. + if (Internal) { + DAWN_TRY(ValidateCanUseAs(source->texture, wgpu::TextureUsage::CopySrc, + UsageValidationMode::Internal)); + DAWN_TRY(ValidateCanUseAs(destination->texture, wgpu::TextureUsage::CopyDst, + UsageValidationMode::Internal)); + } else { + DAWN_TRY(ValidateCanUseAs(source->texture, wgpu::TextureUsage::CopySrc, + mUsageValidationMode)); + DAWN_TRY(ValidateCanUseAs(destination->texture, wgpu::TextureUsage::CopyDst, + mUsageValidationMode)); + } + + mTopLevelTextures.insert(source->texture); + mTopLevelTextures.insert(destination->texture); + } + + CopyTextureToTextureCmd* copy = + allocator->Allocate<CopyTextureToTextureCmd>(Command::CopyTextureToTexture); + copy->source.texture = source->texture; + copy->source.origin = source->origin; + copy->source.mipLevel = source->mipLevel; + copy->source.aspect = ConvertAspect(source->texture->GetFormat(), source->aspect); + copy->destination.texture = destination->texture; + copy->destination.origin = destination->origin; + copy->destination.mipLevel = destination->mipLevel; + copy->destination.aspect = + ConvertAspect(destination->texture->GetFormat(), destination->aspect); + copy->copySize = *copySize; + + return {}; + }, + "encoding %s.CopyTextureToTexture(%s, %s, %s).", this, source->texture, + destination->texture, copySize); + } + + void CommandEncoder::APIClearBuffer(BufferBase* buffer, uint64_t offset, uint64_t size) { + mEncodingContext.TryEncode( + this, + [&](CommandAllocator* allocator) -> MaybeError { + if (GetDevice()->IsValidationEnabled()) { + DAWN_TRY(GetDevice()->ValidateObject(buffer)); + + uint64_t bufferSize = buffer->GetSize(); + DAWN_INVALID_IF(offset > bufferSize, + "Buffer offset (%u) is larger than the size (%u) of %s.", + offset, bufferSize, buffer); + + uint64_t remainingSize = bufferSize - offset; + if (size == wgpu::kWholeSize) { + size = remainingSize; + } else { + DAWN_INVALID_IF(size > remainingSize, + "Buffer range (offset: %u, size: %u) doesn't fit in " + "the size (%u) of %s.", + offset, size, bufferSize, buffer); + } + + DAWN_TRY_CONTEXT(ValidateCanUseAs(buffer, wgpu::BufferUsage::CopyDst), + "validating buffer %s usage.", buffer); + + // Size must be a multiple of 4 bytes on macOS. + DAWN_INVALID_IF(size % 4 != 0, "Fill size (%u) is not a multiple of 4 bytes.", + size); + + // Offset must be multiples of 4 bytes on macOS. + DAWN_INVALID_IF(offset % 4 != 0, "Offset (%u) is not a multiple of 4 bytes,", + offset); + + mTopLevelBuffers.insert(buffer); + } else { + if (size == wgpu::kWholeSize) { + DAWN_ASSERT(buffer->GetSize() >= offset); + size = buffer->GetSize() - offset; + } + } + + ClearBufferCmd* cmd = allocator->Allocate<ClearBufferCmd>(Command::ClearBuffer); + cmd->buffer = buffer; + cmd->offset = offset; + cmd->size = size; + + return {}; + }, + "encoding %s.ClearBuffer(%s, %u, %u).", this, buffer, offset, size); + } + + void CommandEncoder::APIInjectValidationError(const char* message) { + if (mEncodingContext.CheckCurrentEncoder(this)) { + mEncodingContext.HandleError(DAWN_VALIDATION_ERROR(message)); + } + } + + void CommandEncoder::APIInsertDebugMarker(const char* groupLabel) { + mEncodingContext.TryEncode( + this, + [&](CommandAllocator* allocator) -> MaybeError { + InsertDebugMarkerCmd* cmd = + allocator->Allocate<InsertDebugMarkerCmd>(Command::InsertDebugMarker); + cmd->length = strlen(groupLabel); + + char* label = allocator->AllocateData<char>(cmd->length + 1); + memcpy(label, groupLabel, cmd->length + 1); + + return {}; + }, + "encoding %s.InsertDebugMarker(\"%s\").", this, groupLabel); + } + + void CommandEncoder::APIPopDebugGroup() { + mEncodingContext.TryEncode( + this, + [&](CommandAllocator* allocator) -> MaybeError { + if (GetDevice()->IsValidationEnabled()) { + DAWN_INVALID_IF( + mDebugGroupStackSize == 0, + "PopDebugGroup called when no debug groups are currently pushed."); + } + allocator->Allocate<PopDebugGroupCmd>(Command::PopDebugGroup); + mDebugGroupStackSize--; + mEncodingContext.PopDebugGroupLabel(); + + return {}; + }, + "encoding %s.PopDebugGroup().", this); + } + + void CommandEncoder::APIPushDebugGroup(const char* groupLabel) { + mEncodingContext.TryEncode( + this, + [&](CommandAllocator* allocator) -> MaybeError { + PushDebugGroupCmd* cmd = + allocator->Allocate<PushDebugGroupCmd>(Command::PushDebugGroup); + cmd->length = strlen(groupLabel); + + char* label = allocator->AllocateData<char>(cmd->length + 1); + memcpy(label, groupLabel, cmd->length + 1); + + mDebugGroupStackSize++; + mEncodingContext.PushDebugGroupLabel(groupLabel); + + return {}; + }, + "encoding %s.PushDebugGroup(\"%s\").", this, groupLabel); + } + + void CommandEncoder::APIResolveQuerySet(QuerySetBase* querySet, + uint32_t firstQuery, + uint32_t queryCount, + BufferBase* destination, + uint64_t destinationOffset) { + mEncodingContext.TryEncode( + this, + [&](CommandAllocator* allocator) -> MaybeError { + if (GetDevice()->IsValidationEnabled()) { + DAWN_TRY(GetDevice()->ValidateObject(querySet)); + DAWN_TRY(GetDevice()->ValidateObject(destination)); + + DAWN_TRY(ValidateQuerySetResolve(querySet, firstQuery, queryCount, destination, + destinationOffset)); + + DAWN_TRY(ValidateCanUseAs(destination, wgpu::BufferUsage::QueryResolve)); + + TrackUsedQuerySet(querySet); + mTopLevelBuffers.insert(destination); + } + + ResolveQuerySetCmd* cmd = + allocator->Allocate<ResolveQuerySetCmd>(Command::ResolveQuerySet); + cmd->querySet = querySet; + cmd->firstQuery = firstQuery; + cmd->queryCount = queryCount; + cmd->destination = destination; + cmd->destinationOffset = destinationOffset; + + // Encode internal compute pipeline for timestamp query + if (querySet->GetQueryType() == wgpu::QueryType::Timestamp && + !GetDevice()->IsToggleEnabled(Toggle::DisableTimestampQueryConversion)) { + DAWN_TRY(EncodeTimestampsToNanosecondsConversion( + this, querySet, firstQuery, queryCount, destination, destinationOffset)); + } + + return {}; + }, + "encoding %s.ResolveQuerySet(%s, %u, %u, %s, %u).", this, querySet, firstQuery, + queryCount, destination, destinationOffset); + } + + void CommandEncoder::APIWriteBuffer(BufferBase* buffer, + uint64_t bufferOffset, + const uint8_t* data, + uint64_t size) { + mEncodingContext.TryEncode( + this, + [&](CommandAllocator* allocator) -> MaybeError { + if (GetDevice()->IsValidationEnabled()) { + DAWN_TRY(ValidateWriteBuffer(GetDevice(), buffer, bufferOffset, size)); + } + + WriteBufferCmd* cmd = allocator->Allocate<WriteBufferCmd>(Command::WriteBuffer); + cmd->buffer = buffer; + cmd->offset = bufferOffset; + cmd->size = size; + + uint8_t* inlinedData = allocator->AllocateData<uint8_t>(size); + memcpy(inlinedData, data, size); + + mTopLevelBuffers.insert(buffer); + + return {}; + }, + "encoding %s.WriteBuffer(%s, %u, ..., %u).", this, buffer, bufferOffset, size); + } + + void CommandEncoder::APIWriteTimestamp(QuerySetBase* querySet, uint32_t queryIndex) { + mEncodingContext.TryEncode( + this, + [&](CommandAllocator* allocator) -> MaybeError { + if (GetDevice()->IsValidationEnabled()) { + DAWN_TRY(ValidateTimestampQuery(GetDevice(), querySet, queryIndex)); + } + + TrackQueryAvailability(querySet, queryIndex); + + WriteTimestampCmd* cmd = + allocator->Allocate<WriteTimestampCmd>(Command::WriteTimestamp); + cmd->querySet = querySet; + cmd->queryIndex = queryIndex; + + return {}; + }, + "encoding %s.WriteTimestamp(%s, %u).", this, querySet, queryIndex); + } + + CommandBufferBase* CommandEncoder::APIFinish(const CommandBufferDescriptor* descriptor) { + Ref<CommandBufferBase> commandBuffer; + if (GetDevice()->ConsumedError(Finish(descriptor), &commandBuffer)) { + return CommandBufferBase::MakeError(GetDevice()); + } + ASSERT(!IsError()); + return commandBuffer.Detach(); + } + + ResultOrError<Ref<CommandBufferBase>> CommandEncoder::Finish( + const CommandBufferDescriptor* descriptor) { + DeviceBase* device = GetDevice(); + + // Even if mEncodingContext.Finish() validation fails, calling it will mutate the internal + // state of the encoding context. The internal state is set to finished, and subsequent + // calls to encode commands will generate errors. + DAWN_TRY(mEncodingContext.Finish()); + DAWN_TRY(device->ValidateIsAlive()); + + if (device->IsValidationEnabled()) { + DAWN_TRY(ValidateFinish()); + } + + const CommandBufferDescriptor defaultDescriptor = {}; + if (descriptor == nullptr) { + descriptor = &defaultDescriptor; + } + + return device->CreateCommandBuffer(this, descriptor); + } + + // Implementation of the command buffer validation that can be precomputed before submit + MaybeError CommandEncoder::ValidateFinish() const { + TRACE_EVENT0(GetDevice()->GetPlatform(), Validation, "CommandEncoder::ValidateFinish"); + DAWN_TRY(GetDevice()->ValidateObject(this)); + + for (const RenderPassResourceUsage& passUsage : mEncodingContext.GetRenderPassUsages()) { + DAWN_TRY_CONTEXT(ValidateSyncScopeResourceUsage(passUsage), + "validating render pass usage."); + } + + for (const ComputePassResourceUsage& passUsage : mEncodingContext.GetComputePassUsages()) { + for (const SyncScopeResourceUsage& scope : passUsage.dispatchUsages) { + DAWN_TRY_CONTEXT(ValidateSyncScopeResourceUsage(scope), + "validating compute pass usage."); + } + } + + DAWN_INVALID_IF( + mDebugGroupStackSize != 0, + "PushDebugGroup called %u time(s) without a corresponding PopDebugGroup prior to " + "calling Finish.", + mDebugGroupStackSize); + + return {}; + } + +} // namespace dawn::native
diff --git a/src/dawn/native/CommandEncoder.h b/src/dawn/native/CommandEncoder.h new file mode 100644 index 0000000..59e19c6 --- /dev/null +++ b/src/dawn/native/CommandEncoder.h
@@ -0,0 +1,122 @@ +// Copyright 2019 The Dawn Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#ifndef DAWNNATIVE_COMMANDENCODER_H_ +#define DAWNNATIVE_COMMANDENCODER_H_ + +#include "dawn/native/dawn_platform.h" + +#include "dawn/native/EncodingContext.h" +#include "dawn/native/Error.h" +#include "dawn/native/ObjectBase.h" +#include "dawn/native/PassResourceUsage.h" + +#include <string> + +namespace dawn::native { + + enum class UsageValidationMode; + + MaybeError ValidateCommandEncoderDescriptor(const DeviceBase* device, + const CommandEncoderDescriptor* descriptor); + + class CommandEncoder final : public ApiObjectBase { + public: + static Ref<CommandEncoder> Create(DeviceBase* device, + const CommandEncoderDescriptor* descriptor); + static CommandEncoder* MakeError(DeviceBase* device); + + ObjectType GetType() const override; + + CommandIterator AcquireCommands(); + CommandBufferResourceUsage AcquireResourceUsages(); + + void TrackUsedQuerySet(QuerySetBase* querySet); + void TrackQueryAvailability(QuerySetBase* querySet, uint32_t queryIndex); + + // Dawn API + ComputePassEncoder* APIBeginComputePass(const ComputePassDescriptor* descriptor); + RenderPassEncoder* APIBeginRenderPass(const RenderPassDescriptor* descriptor); + + void APICopyBufferToBuffer(BufferBase* source, + uint64_t sourceOffset, + BufferBase* destination, + uint64_t destinationOffset, + uint64_t size); + void APICopyBufferToTexture(const ImageCopyBuffer* source, + const ImageCopyTexture* destination, + const Extent3D* copySize); + void APICopyTextureToBuffer(const ImageCopyTexture* source, + const ImageCopyBuffer* destination, + const Extent3D* copySize); + void APICopyTextureToTexture(const ImageCopyTexture* source, + const ImageCopyTexture* destination, + const Extent3D* copySize); + void APICopyTextureToTextureInternal(const ImageCopyTexture* source, + const ImageCopyTexture* destination, + const Extent3D* copySize); + void APIClearBuffer(BufferBase* destination, uint64_t destinationOffset, uint64_t size); + + void APIInjectValidationError(const char* message); + void APIInsertDebugMarker(const char* groupLabel); + void APIPopDebugGroup(); + void APIPushDebugGroup(const char* groupLabel); + + void APIResolveQuerySet(QuerySetBase* querySet, + uint32_t firstQuery, + uint32_t queryCount, + BufferBase* destination, + uint64_t destinationOffset); + void APIWriteBuffer(BufferBase* buffer, + uint64_t bufferOffset, + const uint8_t* data, + uint64_t size); + void APIWriteTimestamp(QuerySetBase* querySet, uint32_t queryIndex); + + CommandBufferBase* APIFinish(const CommandBufferDescriptor* descriptor = nullptr); + + Ref<ComputePassEncoder> BeginComputePass(const ComputePassDescriptor* descriptor = nullptr); + Ref<RenderPassEncoder> BeginRenderPass(const RenderPassDescriptor* descriptor); + ResultOrError<Ref<CommandBufferBase>> Finish( + const CommandBufferDescriptor* descriptor = nullptr); + + private: + CommandEncoder(DeviceBase* device, const CommandEncoderDescriptor* descriptor); + CommandEncoder(DeviceBase* device, ObjectBase::ErrorTag tag); + + void DestroyImpl() override; + + // Helper to be able to implement both APICopyTextureToTexture and + // APICopyTextureToTextureInternal. The only difference between both + // copies, is that the Internal one will also check internal usage. + template <bool Internal> + void APICopyTextureToTextureHelper(const ImageCopyTexture* source, + const ImageCopyTexture* destination, + const Extent3D* copySize); + + MaybeError ValidateFinish() const; + + EncodingContext mEncodingContext; + std::set<BufferBase*> mTopLevelBuffers; + std::set<TextureBase*> mTopLevelTextures; + std::set<QuerySetBase*> mUsedQuerySets; + + uint64_t mDebugGroupStackSize = 0; + + UsageValidationMode mUsageValidationMode; + }; + +} // namespace dawn::native + +#endif // DAWNNATIVE_COMMANDENCODER_H_
diff --git a/src/dawn/native/CommandValidation.cpp b/src/dawn/native/CommandValidation.cpp new file mode 100644 index 0000000..44fbdf8 --- /dev/null +++ b/src/dawn/native/CommandValidation.cpp
@@ -0,0 +1,496 @@ +// Copyright 2019 The Dawn Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include "dawn/native/CommandValidation.h" + +#include "dawn/common/BitSetIterator.h" +#include "dawn/native/BindGroup.h" +#include "dawn/native/Buffer.h" +#include "dawn/native/CommandBufferStateTracker.h" +#include "dawn/native/Commands.h" +#include "dawn/native/Device.h" +#include "dawn/native/PassResourceUsage.h" +#include "dawn/native/QuerySet.h" +#include "dawn/native/RenderBundle.h" +#include "dawn/native/RenderPipeline.h" +#include "dawn/native/ValidationUtils_autogen.h" + +namespace dawn::native { + + // Performs validation of the "synchronization scope" rules of WebGPU. + MaybeError ValidateSyncScopeResourceUsage(const SyncScopeResourceUsage& scope) { + // Buffers can only be used as single-write or multiple read. + for (size_t i = 0; i < scope.bufferUsages.size(); ++i) { + const wgpu::BufferUsage usage = scope.bufferUsages[i]; + bool readOnly = IsSubset(usage, kReadOnlyBufferUsages); + bool singleUse = wgpu::HasZeroOrOneBits(usage); + + DAWN_INVALID_IF(!readOnly && !singleUse, + "%s usage (%s) includes writable usage and another usage in the same " + "synchronization scope.", + scope.buffers[i], usage); + } + + // Check that every single subresource is used as either a single-write usage or a + // combination of readonly usages. + for (size_t i = 0; i < scope.textureUsages.size(); ++i) { + const TextureSubresourceUsage& textureUsage = scope.textureUsages[i]; + MaybeError error = {}; + textureUsage.Iterate([&](const SubresourceRange&, const wgpu::TextureUsage& usage) { + bool readOnly = IsSubset(usage, kReadOnlyTextureUsages); + bool singleUse = wgpu::HasZeroOrOneBits(usage); + if (!readOnly && !singleUse && !error.IsError()) { + error = DAWN_FORMAT_VALIDATION_ERROR( + "%s usage (%s) includes writable usage and another usage in the same " + "synchronization scope.", + scope.textures[i], usage); + } + }); + DAWN_TRY(std::move(error)); + } + return {}; + } + + MaybeError ValidateTimestampQuery(const DeviceBase* device, + const QuerySetBase* querySet, + uint32_t queryIndex) { + DAWN_TRY(device->ValidateObject(querySet)); + + DAWN_INVALID_IF(querySet->GetQueryType() != wgpu::QueryType::Timestamp, + "The type of %s is not %s.", querySet, wgpu::QueryType::Timestamp); + + DAWN_INVALID_IF(queryIndex >= querySet->GetQueryCount(), + "Query index (%u) exceeds the number of queries (%u) in %s.", queryIndex, + querySet->GetQueryCount(), querySet); + + return {}; + } + + MaybeError ValidateWriteBuffer(const DeviceBase* device, + const BufferBase* buffer, + uint64_t bufferOffset, + uint64_t size) { + DAWN_TRY(device->ValidateObject(buffer)); + + DAWN_INVALID_IF(bufferOffset % 4 != 0, "BufferOffset (%u) is not a multiple of 4.", + bufferOffset); + + DAWN_INVALID_IF(size % 4 != 0, "Size (%u) is not a multiple of 4.", size); + + uint64_t bufferSize = buffer->GetSize(); + DAWN_INVALID_IF(bufferOffset > bufferSize || size > (bufferSize - bufferOffset), + "Write range (bufferOffset: %u, size: %u) does not fit in %s size (%u).", + bufferOffset, size, buffer, bufferSize); + + DAWN_INVALID_IF(!(buffer->GetUsage() & wgpu::BufferUsage::CopyDst), + "%s usage (%s) does not include %s.", buffer, buffer->GetUsage(), + wgpu::BufferUsage::CopyDst); + + return {}; + } + + bool IsRangeOverlapped(uint32_t startA, uint32_t startB, uint32_t length) { + uint32_t maxStart = std::max(startA, startB); + uint32_t minStart = std::min(startA, startB); + return static_cast<uint64_t>(minStart) + static_cast<uint64_t>(length) > + static_cast<uint64_t>(maxStart); + } + + template <typename A, typename B> + DAWN_FORCE_INLINE uint64_t Safe32x32(A a, B b) { + static_assert(std::is_same<A, uint32_t>::value, "'a' must be uint32_t"); + static_assert(std::is_same<B, uint32_t>::value, "'b' must be uint32_t"); + return uint64_t(a) * uint64_t(b); + } + + ResultOrError<uint64_t> ComputeRequiredBytesInCopy(const TexelBlockInfo& blockInfo, + const Extent3D& copySize, + uint32_t bytesPerRow, + uint32_t rowsPerImage) { + ASSERT(copySize.width % blockInfo.width == 0); + ASSERT(copySize.height % blockInfo.height == 0); + uint32_t widthInBlocks = copySize.width / blockInfo.width; + uint32_t heightInBlocks = copySize.height / blockInfo.height; + uint64_t bytesInLastRow = Safe32x32(widthInBlocks, blockInfo.byteSize); + + if (copySize.depthOrArrayLayers == 0) { + return 0; + } + + // Check for potential overflows for the rest of the computations. We have the following + // inequalities: + // + // bytesInLastRow <= bytesPerRow + // heightInBlocks <= rowsPerImage + // + // So: + // + // bytesInLastImage = bytesPerRow * (heightInBlocks - 1) + bytesInLastRow + // <= bytesPerRow * heightInBlocks + // <= bytesPerRow * rowsPerImage + // <= bytesPerImage + // + // This means that if the computation of depth * bytesPerImage doesn't overflow, none of the + // computations for requiredBytesInCopy will. (and it's not a very pessimizing check) + ASSERT(copySize.depthOrArrayLayers <= 1 || (bytesPerRow != wgpu::kCopyStrideUndefined && + rowsPerImage != wgpu::kCopyStrideUndefined)); + uint64_t bytesPerImage = Safe32x32(bytesPerRow, rowsPerImage); + DAWN_INVALID_IF( + bytesPerImage > std::numeric_limits<uint64_t>::max() / copySize.depthOrArrayLayers, + "The number of bytes per image (%u) exceeds the maximum (%u) when copying %u images.", + bytesPerImage, std::numeric_limits<uint64_t>::max() / copySize.depthOrArrayLayers, + copySize.depthOrArrayLayers); + + uint64_t requiredBytesInCopy = bytesPerImage * (copySize.depthOrArrayLayers - 1); + if (heightInBlocks > 0) { + ASSERT(heightInBlocks <= 1 || bytesPerRow != wgpu::kCopyStrideUndefined); + uint64_t bytesInLastImage = Safe32x32(bytesPerRow, heightInBlocks - 1) + bytesInLastRow; + requiredBytesInCopy += bytesInLastImage; + } + return requiredBytesInCopy; + } + + MaybeError ValidateCopySizeFitsInBuffer(const Ref<BufferBase>& buffer, + uint64_t offset, + uint64_t size) { + uint64_t bufferSize = buffer->GetSize(); + bool fitsInBuffer = offset <= bufferSize && (size <= (bufferSize - offset)); + DAWN_INVALID_IF(!fitsInBuffer, + "Copy range (offset: %u, size: %u) does not fit in %s size (%u).", offset, + size, buffer.Get(), bufferSize); + + return {}; + } + + // Replace wgpu::kCopyStrideUndefined with real values, so backends don't have to think about + // it. + void ApplyDefaultTextureDataLayoutOptions(TextureDataLayout* layout, + const TexelBlockInfo& blockInfo, + const Extent3D& copyExtent) { + ASSERT(layout != nullptr); + ASSERT(copyExtent.height % blockInfo.height == 0); + uint32_t heightInBlocks = copyExtent.height / blockInfo.height; + + if (layout->bytesPerRow == wgpu::kCopyStrideUndefined) { + ASSERT(copyExtent.width % blockInfo.width == 0); + uint32_t widthInBlocks = copyExtent.width / blockInfo.width; + uint32_t bytesInLastRow = widthInBlocks * blockInfo.byteSize; + + ASSERT(heightInBlocks <= 1 && copyExtent.depthOrArrayLayers <= 1); + layout->bytesPerRow = Align(bytesInLastRow, kTextureBytesPerRowAlignment); + } + if (layout->rowsPerImage == wgpu::kCopyStrideUndefined) { + ASSERT(copyExtent.depthOrArrayLayers <= 1); + layout->rowsPerImage = heightInBlocks; + } + } + + MaybeError ValidateLinearTextureData(const TextureDataLayout& layout, + uint64_t byteSize, + const TexelBlockInfo& blockInfo, + const Extent3D& copyExtent) { + ASSERT(copyExtent.height % blockInfo.height == 0); + uint32_t heightInBlocks = copyExtent.height / blockInfo.height; + + // TODO(dawn:563): Right now kCopyStrideUndefined will be formatted as a large value in the + // validation message. Investigate ways to make it print as a more readable symbol. + DAWN_INVALID_IF( + copyExtent.depthOrArrayLayers > 1 && + (layout.bytesPerRow == wgpu::kCopyStrideUndefined || + layout.rowsPerImage == wgpu::kCopyStrideUndefined), + "Copy depth (%u) is > 1, but bytesPerRow (%u) or rowsPerImage (%u) are not specified.", + copyExtent.depthOrArrayLayers, layout.bytesPerRow, layout.rowsPerImage); + + DAWN_INVALID_IF(heightInBlocks > 1 && layout.bytesPerRow == wgpu::kCopyStrideUndefined, + "HeightInBlocks (%u) is > 1, but bytesPerRow is not specified.", + heightInBlocks); + + // Validation for other members in layout: + ASSERT(copyExtent.width % blockInfo.width == 0); + uint32_t widthInBlocks = copyExtent.width / blockInfo.width; + ASSERT(Safe32x32(widthInBlocks, blockInfo.byteSize) <= + std::numeric_limits<uint32_t>::max()); + uint32_t bytesInLastRow = widthInBlocks * blockInfo.byteSize; + + // These != wgpu::kCopyStrideUndefined checks are technically redundant with the > checks, + // but they should get optimized out. + DAWN_INVALID_IF( + layout.bytesPerRow != wgpu::kCopyStrideUndefined && bytesInLastRow > layout.bytesPerRow, + "The byte size of each row (%u) is > bytesPerRow (%u).", bytesInLastRow, + layout.bytesPerRow); + + DAWN_INVALID_IF(layout.rowsPerImage != wgpu::kCopyStrideUndefined && + heightInBlocks > layout.rowsPerImage, + "The height of each image in blocks (%u) is > rowsPerImage (%u).", + heightInBlocks, layout.rowsPerImage); + + // We compute required bytes in copy after validating texel block alignments + // because the divisibility conditions are necessary for the algorithm to be valid, + // also the bytesPerRow bound is necessary to avoid overflows. + uint64_t requiredBytesInCopy; + DAWN_TRY_ASSIGN(requiredBytesInCopy, + ComputeRequiredBytesInCopy(blockInfo, copyExtent, layout.bytesPerRow, + layout.rowsPerImage)); + + bool fitsInData = + layout.offset <= byteSize && (requiredBytesInCopy <= (byteSize - layout.offset)); + DAWN_INVALID_IF( + !fitsInData, + "Required size for texture data layout (%u) exceeds the linear data size (%u) with " + "offset (%u).", + requiredBytesInCopy, byteSize, layout.offset); + + return {}; + } + + MaybeError ValidateImageCopyBuffer(DeviceBase const* device, + const ImageCopyBuffer& imageCopyBuffer) { + DAWN_TRY(device->ValidateObject(imageCopyBuffer.buffer)); + if (imageCopyBuffer.layout.bytesPerRow != wgpu::kCopyStrideUndefined) { + DAWN_INVALID_IF(imageCopyBuffer.layout.bytesPerRow % kTextureBytesPerRowAlignment != 0, + "bytesPerRow (%u) is not a multiple of %u.", + imageCopyBuffer.layout.bytesPerRow, kTextureBytesPerRowAlignment); + } + + return {}; + } + + MaybeError ValidateImageCopyTexture(DeviceBase const* device, + const ImageCopyTexture& textureCopy, + const Extent3D& copySize) { + const TextureBase* texture = textureCopy.texture; + DAWN_TRY(device->ValidateObject(texture)); + + DAWN_INVALID_IF(textureCopy.mipLevel >= texture->GetNumMipLevels(), + "MipLevel (%u) is greater than the number of mip levels (%u) in %s.", + textureCopy.mipLevel, texture->GetNumMipLevels(), texture); + + DAWN_TRY(ValidateTextureAspect(textureCopy.aspect)); + DAWN_INVALID_IF( + SelectFormatAspects(texture->GetFormat(), textureCopy.aspect) == Aspect::None, + "%s format (%s) does not have the selected aspect (%s).", texture, + texture->GetFormat().format, textureCopy.aspect); + + if (texture->GetSampleCount() > 1 || texture->GetFormat().HasDepthOrStencil()) { + Extent3D subresourceSize = texture->GetMipLevelPhysicalSize(textureCopy.mipLevel); + ASSERT(texture->GetDimension() == wgpu::TextureDimension::e2D); + DAWN_INVALID_IF( + textureCopy.origin.x != 0 || textureCopy.origin.y != 0 || + subresourceSize.width != copySize.width || + subresourceSize.height != copySize.height, + "Copy origin (%s) and size (%s) does not cover the entire subresource (origin: " + "[x: 0, y: 0], size: %s) of %s. The entire subresource must be copied when the " + "format (%s) is a depth/stencil format or the sample count (%u) is > 1.", + &textureCopy.origin, ©Size, &subresourceSize, texture, + texture->GetFormat().format, texture->GetSampleCount()); + } + + return {}; + } + + MaybeError ValidateTextureCopyRange(DeviceBase const* device, + const ImageCopyTexture& textureCopy, + const Extent3D& copySize) { + const TextureBase* texture = textureCopy.texture; + + // Validation for the copy being in-bounds: + Extent3D mipSize = texture->GetMipLevelPhysicalSize(textureCopy.mipLevel); + // For 1D/2D textures, include the array layer as depth so it can be checked with other + // dimensions. + if (texture->GetDimension() != wgpu::TextureDimension::e3D) { + mipSize.depthOrArrayLayers = texture->GetArrayLayers(); + } + // All texture dimensions are in uint32_t so by doing checks in uint64_t we avoid + // overflows. + DAWN_INVALID_IF( + static_cast<uint64_t>(textureCopy.origin.x) + static_cast<uint64_t>(copySize.width) > + static_cast<uint64_t>(mipSize.width) || + static_cast<uint64_t>(textureCopy.origin.y) + + static_cast<uint64_t>(copySize.height) > + static_cast<uint64_t>(mipSize.height) || + static_cast<uint64_t>(textureCopy.origin.z) + + static_cast<uint64_t>(copySize.depthOrArrayLayers) > + static_cast<uint64_t>(mipSize.depthOrArrayLayers), + "Texture copy range (origin: %s, copySize: %s) touches outside of %s mip level %u " + "size (%s).", + &textureCopy.origin, ©Size, texture, textureCopy.mipLevel, &mipSize); + + // Validation for the texel block alignments: + const Format& format = textureCopy.texture->GetFormat(); + if (format.isCompressed) { + const TexelBlockInfo& blockInfo = format.GetAspectInfo(textureCopy.aspect).block; + DAWN_INVALID_IF( + textureCopy.origin.x % blockInfo.width != 0, + "Texture copy origin.x (%u) is not a multiple of compressed texture format block " + "width (%u).", + textureCopy.origin.x, blockInfo.width); + DAWN_INVALID_IF( + textureCopy.origin.y % blockInfo.height != 0, + "Texture copy origin.y (%u) is not a multiple of compressed texture format block " + "height (%u).", + textureCopy.origin.y, blockInfo.height); + DAWN_INVALID_IF( + copySize.width % blockInfo.width != 0, + "copySize.width (%u) is not a multiple of compressed texture format block width " + "(%u).", + copySize.width, blockInfo.width); + DAWN_INVALID_IF( + copySize.height % blockInfo.height != 0, + "copySize.height (%u) is not a multiple of compressed texture format block " + "height (%u).", + copySize.height, blockInfo.height); + } + + return {}; + } + + // Always returns a single aspect (color, stencil, depth, or ith plane for multi-planar + // formats). + ResultOrError<Aspect> SingleAspectUsedByImageCopyTexture(const ImageCopyTexture& view) { + const Format& format = view.texture->GetFormat(); + switch (view.aspect) { + case wgpu::TextureAspect::All: { + DAWN_INVALID_IF( + !HasOneBit(format.aspects), + "More than a single aspect (%s) is selected for multi-planar format (%s) in " + "%s <-> linear data copy.", + view.aspect, format.format, view.texture); + + Aspect single = format.aspects; + return single; + } + case wgpu::TextureAspect::DepthOnly: + ASSERT(format.aspects & Aspect::Depth); + return Aspect::Depth; + case wgpu::TextureAspect::StencilOnly: + ASSERT(format.aspects & Aspect::Stencil); + return Aspect::Stencil; + case wgpu::TextureAspect::Plane0Only: + case wgpu::TextureAspect::Plane1Only: + break; + } + UNREACHABLE(); + } + + MaybeError ValidateLinearToDepthStencilCopyRestrictions(const ImageCopyTexture& dst) { + Aspect aspectUsed; + DAWN_TRY_ASSIGN(aspectUsed, SingleAspectUsedByImageCopyTexture(dst)); + + const Format& format = dst.texture->GetFormat(); + switch (format.format) { + case wgpu::TextureFormat::Depth16Unorm: + return {}; + default: + DAWN_INVALID_IF(aspectUsed == Aspect::Depth, + "Cannot copy into the depth aspect of %s with format %s.", + dst.texture, format.format); + break; + } + + return {}; + } + + MaybeError ValidateTextureToTextureCopyCommonRestrictions(const ImageCopyTexture& src, + const ImageCopyTexture& dst, + const Extent3D& copySize) { + const uint32_t srcSamples = src.texture->GetSampleCount(); + const uint32_t dstSamples = dst.texture->GetSampleCount(); + + DAWN_INVALID_IF( + srcSamples != dstSamples, + "Source %s sample count (%u) and destination %s sample count (%u) does not match.", + src.texture, srcSamples, dst.texture, dstSamples); + + // Metal cannot select a single aspect for texture-to-texture copies. + const Format& format = src.texture->GetFormat(); + DAWN_INVALID_IF( + SelectFormatAspects(format, src.aspect) != format.aspects, + "Source %s aspect (%s) doesn't select all the aspects of the source format (%s).", + src.texture, src.aspect, format.format); + + DAWN_INVALID_IF( + SelectFormatAspects(format, dst.aspect) != format.aspects, + "Destination %s aspect (%s) doesn't select all the aspects of the destination format " + "(%s).", + dst.texture, dst.aspect, format.format); + + if (src.texture == dst.texture) { + switch (src.texture->GetDimension()) { + case wgpu::TextureDimension::e1D: + ASSERT(src.mipLevel == 0 && src.origin.z == 0 && dst.origin.z == 0); + return DAWN_FORMAT_VALIDATION_ERROR("Copy is from %s to itself.", src.texture); + + case wgpu::TextureDimension::e2D: + DAWN_INVALID_IF(src.mipLevel == dst.mipLevel && + IsRangeOverlapped(src.origin.z, dst.origin.z, + copySize.depthOrArrayLayers), + "Copy source and destination are overlapping layer ranges " + "([%u, %u) and [%u, %u)) of %s mip level %u", + src.origin.z, src.origin.z + copySize.depthOrArrayLayers, + dst.origin.z, dst.origin.z + copySize.depthOrArrayLayers, + src.texture, src.mipLevel); + break; + + case wgpu::TextureDimension::e3D: + DAWN_INVALID_IF(src.mipLevel == dst.mipLevel, + "Copy is from %s mip level %u to itself.", src.texture, + src.mipLevel); + break; + } + } + + return {}; + } + + MaybeError ValidateTextureToTextureCopyRestrictions(const ImageCopyTexture& src, + const ImageCopyTexture& dst, + const Extent3D& copySize) { + // Metal requires texture-to-texture copies happens between texture formats that equal to + // each other or only have diff on srgb-ness. + DAWN_INVALID_IF( + !src.texture->GetFormat().CopyCompatibleWith(dst.texture->GetFormat()), + "Source %s format (%s) and destination %s format (%s) are not copy compatible.", + src.texture, src.texture->GetFormat().format, dst.texture, + dst.texture->GetFormat().format); + + return ValidateTextureToTextureCopyCommonRestrictions(src, dst, copySize); + } + + MaybeError ValidateCanUseAs(const TextureBase* texture, + wgpu::TextureUsage usage, + UsageValidationMode mode) { + ASSERT(wgpu::HasZeroOrOneBits(usage)); + switch (mode) { + case UsageValidationMode::Default: + DAWN_INVALID_IF(!(texture->GetUsage() & usage), "%s usage (%s) doesn't include %s.", + texture, texture->GetUsage(), usage); + break; + case UsageValidationMode::Internal: + DAWN_INVALID_IF(!(texture->GetInternalUsage() & usage), + "%s internal usage (%s) doesn't include %s.", texture, + texture->GetInternalUsage(), usage); + break; + } + + return {}; + } + + MaybeError ValidateCanUseAs(const BufferBase* buffer, wgpu::BufferUsage usage) { + ASSERT(wgpu::HasZeroOrOneBits(usage)); + DAWN_INVALID_IF(!(buffer->GetUsage() & usage), "%s usage (%s) doesn't include %s.", buffer, + buffer->GetUsage(), usage); + return {}; + } + +} // namespace dawn::native
diff --git a/src/dawn/native/CommandValidation.h b/src/dawn/native/CommandValidation.h new file mode 100644 index 0000000..1cae7cc --- /dev/null +++ b/src/dawn/native/CommandValidation.h
@@ -0,0 +1,90 @@ +// Copyright 2019 The Dawn Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#ifndef DAWNNATIVE_COMMANDVALIDATION_H_ +#define DAWNNATIVE_COMMANDVALIDATION_H_ + +#include "dawn/native/CommandAllocator.h" +#include "dawn/native/Error.h" +#include "dawn/native/Texture.h" + +#include <vector> + +namespace dawn::native { + + class QuerySetBase; + struct SyncScopeResourceUsage; + struct TexelBlockInfo; + + MaybeError ValidateSyncScopeResourceUsage(const SyncScopeResourceUsage& usage); + + MaybeError ValidateTimestampQuery(const DeviceBase* device, + const QuerySetBase* querySet, + uint32_t queryIndex); + + MaybeError ValidateWriteBuffer(const DeviceBase* device, + const BufferBase* buffer, + uint64_t bufferOffset, + uint64_t size); + + ResultOrError<uint64_t> ComputeRequiredBytesInCopy(const TexelBlockInfo& blockInfo, + const Extent3D& copySize, + uint32_t bytesPerRow, + uint32_t rowsPerImage); + + void ApplyDefaultTextureDataLayoutOptions(TextureDataLayout* layout, + const TexelBlockInfo& blockInfo, + const Extent3D& copyExtent); + MaybeError ValidateLinearTextureData(const TextureDataLayout& layout, + uint64_t byteSize, + const TexelBlockInfo& blockInfo, + const Extent3D& copyExtent); + MaybeError ValidateTextureCopyRange(DeviceBase const* device, + const ImageCopyTexture& imageCopyTexture, + const Extent3D& copySize); + ResultOrError<Aspect> SingleAspectUsedByImageCopyTexture(const ImageCopyTexture& view); + MaybeError ValidateLinearToDepthStencilCopyRestrictions(const ImageCopyTexture& dst); + + MaybeError ValidateImageCopyBuffer(DeviceBase const* device, + const ImageCopyBuffer& imageCopyBuffer); + MaybeError ValidateImageCopyTexture(DeviceBase const* device, + const ImageCopyTexture& imageCopyTexture, + const Extent3D& copySize); + + MaybeError ValidateCopySizeFitsInBuffer(const Ref<BufferBase>& buffer, + uint64_t offset, + uint64_t size); + + bool IsRangeOverlapped(uint32_t startA, uint32_t startB, uint32_t length); + + MaybeError ValidateTextureToTextureCopyCommonRestrictions(const ImageCopyTexture& src, + const ImageCopyTexture& dst, + const Extent3D& copySize); + MaybeError ValidateTextureToTextureCopyRestrictions(const ImageCopyTexture& src, + const ImageCopyTexture& dst, + const Extent3D& copySize); + + enum class UsageValidationMode { + Default, + Internal, + }; + + MaybeError ValidateCanUseAs(const TextureBase* texture, + wgpu::TextureUsage usage, + UsageValidationMode mode); + MaybeError ValidateCanUseAs(const BufferBase* buffer, wgpu::BufferUsage usage); + +} // namespace dawn::native + +#endif // DAWNNATIVE_COMMANDVALIDATION_H_
diff --git a/src/dawn/native/Commands.cpp b/src/dawn/native/Commands.cpp new file mode 100644 index 0000000..3337cbd --- /dev/null +++ b/src/dawn/native/Commands.cpp
@@ -0,0 +1,365 @@ +// Copyright 2018 The Dawn Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include "dawn/native/Commands.h" + +#include "dawn/native/BindGroup.h" +#include "dawn/native/Buffer.h" +#include "dawn/native/CommandAllocator.h" +#include "dawn/native/ComputePipeline.h" +#include "dawn/native/QuerySet.h" +#include "dawn/native/RenderBundle.h" +#include "dawn/native/RenderPipeline.h" +#include "dawn/native/Texture.h" + +namespace dawn::native { + + void FreeCommands(CommandIterator* commands) { + commands->Reset(); + + Command type; + while (commands->NextCommandId(&type)) { + switch (type) { + case Command::BeginComputePass: { + BeginComputePassCmd* begin = commands->NextCommand<BeginComputePassCmd>(); + begin->~BeginComputePassCmd(); + break; + } + case Command::BeginOcclusionQuery: { + BeginOcclusionQueryCmd* begin = commands->NextCommand<BeginOcclusionQueryCmd>(); + begin->~BeginOcclusionQueryCmd(); + break; + } + case Command::BeginRenderPass: { + BeginRenderPassCmd* begin = commands->NextCommand<BeginRenderPassCmd>(); + begin->~BeginRenderPassCmd(); + break; + } + case Command::CopyBufferToBuffer: { + CopyBufferToBufferCmd* copy = commands->NextCommand<CopyBufferToBufferCmd>(); + copy->~CopyBufferToBufferCmd(); + break; + } + case Command::CopyBufferToTexture: { + CopyBufferToTextureCmd* copy = commands->NextCommand<CopyBufferToTextureCmd>(); + copy->~CopyBufferToTextureCmd(); + break; + } + case Command::CopyTextureToBuffer: { + CopyTextureToBufferCmd* copy = commands->NextCommand<CopyTextureToBufferCmd>(); + copy->~CopyTextureToBufferCmd(); + break; + } + case Command::CopyTextureToTexture: { + CopyTextureToTextureCmd* copy = + commands->NextCommand<CopyTextureToTextureCmd>(); + copy->~CopyTextureToTextureCmd(); + break; + } + case Command::Dispatch: { + DispatchCmd* dispatch = commands->NextCommand<DispatchCmd>(); + dispatch->~DispatchCmd(); + break; + } + case Command::DispatchIndirect: { + DispatchIndirectCmd* dispatch = commands->NextCommand<DispatchIndirectCmd>(); + dispatch->~DispatchIndirectCmd(); + break; + } + case Command::Draw: { + DrawCmd* draw = commands->NextCommand<DrawCmd>(); + draw->~DrawCmd(); + break; + } + case Command::DrawIndexed: { + DrawIndexedCmd* draw = commands->NextCommand<DrawIndexedCmd>(); + draw->~DrawIndexedCmd(); + break; + } + case Command::DrawIndirect: { + DrawIndirectCmd* draw = commands->NextCommand<DrawIndirectCmd>(); + draw->~DrawIndirectCmd(); + break; + } + case Command::DrawIndexedIndirect: { + DrawIndexedIndirectCmd* draw = commands->NextCommand<DrawIndexedIndirectCmd>(); + draw->~DrawIndexedIndirectCmd(); + break; + } + case Command::EndComputePass: { + EndComputePassCmd* cmd = commands->NextCommand<EndComputePassCmd>(); + cmd->~EndComputePassCmd(); + break; + } + case Command::EndOcclusionQuery: { + EndOcclusionQueryCmd* cmd = commands->NextCommand<EndOcclusionQueryCmd>(); + cmd->~EndOcclusionQueryCmd(); + break; + } + case Command::EndRenderPass: { + EndRenderPassCmd* cmd = commands->NextCommand<EndRenderPassCmd>(); + cmd->~EndRenderPassCmd(); + break; + } + case Command::ExecuteBundles: { + ExecuteBundlesCmd* cmd = commands->NextCommand<ExecuteBundlesCmd>(); + auto bundles = commands->NextData<Ref<RenderBundleBase>>(cmd->count); + for (size_t i = 0; i < cmd->count; ++i) { + (&bundles[i])->~Ref<RenderBundleBase>(); + } + cmd->~ExecuteBundlesCmd(); + break; + } + case Command::ClearBuffer: { + ClearBufferCmd* cmd = commands->NextCommand<ClearBufferCmd>(); + cmd->~ClearBufferCmd(); + break; + } + case Command::InsertDebugMarker: { + InsertDebugMarkerCmd* cmd = commands->NextCommand<InsertDebugMarkerCmd>(); + commands->NextData<char>(cmd->length + 1); + cmd->~InsertDebugMarkerCmd(); + break; + } + case Command::PopDebugGroup: { + PopDebugGroupCmd* cmd = commands->NextCommand<PopDebugGroupCmd>(); + cmd->~PopDebugGroupCmd(); + break; + } + case Command::PushDebugGroup: { + PushDebugGroupCmd* cmd = commands->NextCommand<PushDebugGroupCmd>(); + commands->NextData<char>(cmd->length + 1); + cmd->~PushDebugGroupCmd(); + break; + } + case Command::ResolveQuerySet: { + ResolveQuerySetCmd* cmd = commands->NextCommand<ResolveQuerySetCmd>(); + cmd->~ResolveQuerySetCmd(); + break; + } + case Command::SetComputePipeline: { + SetComputePipelineCmd* cmd = commands->NextCommand<SetComputePipelineCmd>(); + cmd->~SetComputePipelineCmd(); + break; + } + case Command::SetRenderPipeline: { + SetRenderPipelineCmd* cmd = commands->NextCommand<SetRenderPipelineCmd>(); + cmd->~SetRenderPipelineCmd(); + break; + } + case Command::SetStencilReference: { + SetStencilReferenceCmd* cmd = commands->NextCommand<SetStencilReferenceCmd>(); + cmd->~SetStencilReferenceCmd(); + break; + } + case Command::SetViewport: { + SetViewportCmd* cmd = commands->NextCommand<SetViewportCmd>(); + cmd->~SetViewportCmd(); + break; + } + case Command::SetScissorRect: { + SetScissorRectCmd* cmd = commands->NextCommand<SetScissorRectCmd>(); + cmd->~SetScissorRectCmd(); + break; + } + case Command::SetBlendConstant: { + SetBlendConstantCmd* cmd = commands->NextCommand<SetBlendConstantCmd>(); + cmd->~SetBlendConstantCmd(); + break; + } + case Command::SetBindGroup: { + SetBindGroupCmd* cmd = commands->NextCommand<SetBindGroupCmd>(); + if (cmd->dynamicOffsetCount > 0) { + commands->NextData<uint32_t>(cmd->dynamicOffsetCount); + } + cmd->~SetBindGroupCmd(); + break; + } + case Command::SetIndexBuffer: { + SetIndexBufferCmd* cmd = commands->NextCommand<SetIndexBufferCmd>(); + cmd->~SetIndexBufferCmd(); + break; + } + case Command::SetVertexBuffer: { + SetVertexBufferCmd* cmd = commands->NextCommand<SetVertexBufferCmd>(); + cmd->~SetVertexBufferCmd(); + break; + } + case Command::WriteBuffer: { + WriteBufferCmd* write = commands->NextCommand<WriteBufferCmd>(); + commands->NextData<uint8_t>(write->size); + write->~WriteBufferCmd(); + break; + } + case Command::WriteTimestamp: { + WriteTimestampCmd* cmd = commands->NextCommand<WriteTimestampCmd>(); + cmd->~WriteTimestampCmd(); + break; + } + } + } + + commands->MakeEmptyAsDataWasDestroyed(); + } + + void SkipCommand(CommandIterator* commands, Command type) { + switch (type) { + case Command::BeginComputePass: + commands->NextCommand<BeginComputePassCmd>(); + break; + + case Command::BeginOcclusionQuery: + commands->NextCommand<BeginOcclusionQueryCmd>(); + break; + + case Command::BeginRenderPass: + commands->NextCommand<BeginRenderPassCmd>(); + break; + + case Command::CopyBufferToBuffer: + commands->NextCommand<CopyBufferToBufferCmd>(); + break; + + case Command::CopyBufferToTexture: + commands->NextCommand<CopyBufferToTextureCmd>(); + break; + + case Command::CopyTextureToBuffer: + commands->NextCommand<CopyTextureToBufferCmd>(); + break; + + case Command::CopyTextureToTexture: + commands->NextCommand<CopyTextureToTextureCmd>(); + break; + + case Command::Dispatch: + commands->NextCommand<DispatchCmd>(); + break; + + case Command::DispatchIndirect: + commands->NextCommand<DispatchIndirectCmd>(); + break; + + case Command::Draw: + commands->NextCommand<DrawCmd>(); + break; + + case Command::DrawIndexed: + commands->NextCommand<DrawIndexedCmd>(); + break; + + case Command::DrawIndirect: + commands->NextCommand<DrawIndirectCmd>(); + break; + + case Command::DrawIndexedIndirect: + commands->NextCommand<DrawIndexedIndirectCmd>(); + break; + + case Command::EndComputePass: + commands->NextCommand<EndComputePassCmd>(); + break; + + case Command::EndOcclusionQuery: + commands->NextCommand<EndOcclusionQueryCmd>(); + break; + + case Command::EndRenderPass: + commands->NextCommand<EndRenderPassCmd>(); + break; + + case Command::ExecuteBundles: { + auto* cmd = commands->NextCommand<ExecuteBundlesCmd>(); + commands->NextData<Ref<RenderBundleBase>>(cmd->count); + break; + } + + case Command::ClearBuffer: + commands->NextCommand<ClearBufferCmd>(); + break; + + case Command::InsertDebugMarker: { + InsertDebugMarkerCmd* cmd = commands->NextCommand<InsertDebugMarkerCmd>(); + commands->NextData<char>(cmd->length + 1); + break; + } + + case Command::PopDebugGroup: + commands->NextCommand<PopDebugGroupCmd>(); + break; + + case Command::PushDebugGroup: { + PushDebugGroupCmd* cmd = commands->NextCommand<PushDebugGroupCmd>(); + commands->NextData<char>(cmd->length + 1); + break; + } + + case Command::ResolveQuerySet: { + commands->NextCommand<ResolveQuerySetCmd>(); + break; + } + + case Command::SetComputePipeline: + commands->NextCommand<SetComputePipelineCmd>(); + break; + + case Command::SetRenderPipeline: + commands->NextCommand<SetRenderPipelineCmd>(); + break; + + case Command::SetStencilReference: + commands->NextCommand<SetStencilReferenceCmd>(); + break; + + case Command::SetViewport: + commands->NextCommand<SetViewportCmd>(); + break; + + case Command::SetScissorRect: + commands->NextCommand<SetScissorRectCmd>(); + break; + + case Command::SetBlendConstant: + commands->NextCommand<SetBlendConstantCmd>(); + break; + + case Command::SetBindGroup: { + SetBindGroupCmd* cmd = commands->NextCommand<SetBindGroupCmd>(); + if (cmd->dynamicOffsetCount > 0) { + commands->NextData<uint32_t>(cmd->dynamicOffsetCount); + } + break; + } + + case Command::SetIndexBuffer: + commands->NextCommand<SetIndexBufferCmd>(); + break; + + case Command::SetVertexBuffer: { + commands->NextCommand<SetVertexBufferCmd>(); + break; + } + + case Command::WriteBuffer: + commands->NextCommand<WriteBufferCmd>(); + break; + + case Command::WriteTimestamp: { + commands->NextCommand<WriteTimestampCmd>(); + break; + } + } + } + +} // namespace dawn::native
diff --git a/src/dawn/native/Commands.h b/src/dawn/native/Commands.h new file mode 100644 index 0000000..3c2d8ab --- /dev/null +++ b/src/dawn/native/Commands.h
@@ -0,0 +1,302 @@ +// Copyright 2017 The Dawn Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#ifndef DAWNNATIVE_COMMANDS_H_ +#define DAWNNATIVE_COMMANDS_H_ + +#include "dawn/common/Constants.h" + +#include "dawn/native/AttachmentState.h" +#include "dawn/native/BindingInfo.h" +#include "dawn/native/Texture.h" + +#include "dawn/native/dawn_platform.h" + +#include <array> +#include <bitset> + +namespace dawn::native { + + // Definition of the commands that are present in the CommandIterator given by the + // CommandBufferBuilder. There are not defined in CommandBuffer.h to break some header + // dependencies: Ref<Object> needs Object to be defined. + + enum class Command { + BeginComputePass, + BeginOcclusionQuery, + BeginRenderPass, + ClearBuffer, + CopyBufferToBuffer, + CopyBufferToTexture, + CopyTextureToBuffer, + CopyTextureToTexture, + Dispatch, + DispatchIndirect, + Draw, + DrawIndexed, + DrawIndirect, + DrawIndexedIndirect, + EndComputePass, + EndOcclusionQuery, + EndRenderPass, + ExecuteBundles, + InsertDebugMarker, + PopDebugGroup, + PushDebugGroup, + ResolveQuerySet, + SetComputePipeline, + SetRenderPipeline, + SetStencilReference, + SetViewport, + SetScissorRect, + SetBlendConstant, + SetBindGroup, + SetIndexBuffer, + SetVertexBuffer, + WriteBuffer, + WriteTimestamp, + }; + + struct TimestampWrite { + Ref<QuerySetBase> querySet; + uint32_t queryIndex; + }; + + struct BeginComputePassCmd { + std::vector<TimestampWrite> timestampWrites; + }; + + struct BeginOcclusionQueryCmd { + Ref<QuerySetBase> querySet; + uint32_t queryIndex; + }; + + struct RenderPassColorAttachmentInfo { + Ref<TextureViewBase> view; + Ref<TextureViewBase> resolveTarget; + wgpu::LoadOp loadOp; + wgpu::StoreOp storeOp; + dawn::native::Color clearColor; + }; + + struct RenderPassDepthStencilAttachmentInfo { + Ref<TextureViewBase> view; + wgpu::LoadOp depthLoadOp; + wgpu::StoreOp depthStoreOp; + wgpu::LoadOp stencilLoadOp; + wgpu::StoreOp stencilStoreOp; + float clearDepth; + uint32_t clearStencil; + bool depthReadOnly; + bool stencilReadOnly; + }; + + struct BeginRenderPassCmd { + Ref<AttachmentState> attachmentState; + ityp::array<ColorAttachmentIndex, RenderPassColorAttachmentInfo, kMaxColorAttachments> + colorAttachments; + RenderPassDepthStencilAttachmentInfo depthStencilAttachment; + + // Cache the width and height of all attachments for convenience + uint32_t width; + uint32_t height; + + Ref<QuerySetBase> occlusionQuerySet; + std::vector<TimestampWrite> timestampWrites; + }; + + struct BufferCopy { + Ref<BufferBase> buffer; + uint64_t offset; + uint32_t bytesPerRow; + uint32_t rowsPerImage; + }; + + struct TextureCopy { + Ref<TextureBase> texture; + uint32_t mipLevel; + Origin3D origin; // Texels / array layer + Aspect aspect; + }; + + struct CopyBufferToBufferCmd { + Ref<BufferBase> source; + uint64_t sourceOffset; + Ref<BufferBase> destination; + uint64_t destinationOffset; + uint64_t size; + }; + + struct CopyBufferToTextureCmd { + BufferCopy source; + TextureCopy destination; + Extent3D copySize; // Texels + }; + + struct CopyTextureToBufferCmd { + TextureCopy source; + BufferCopy destination; + Extent3D copySize; // Texels + }; + + struct CopyTextureToTextureCmd { + TextureCopy source; + TextureCopy destination; + Extent3D copySize; // Texels + }; + + struct DispatchCmd { + uint32_t x; + uint32_t y; + uint32_t z; + }; + + struct DispatchIndirectCmd { + Ref<BufferBase> indirectBuffer; + uint64_t indirectOffset; + }; + + struct DrawCmd { + uint32_t vertexCount; + uint32_t instanceCount; + uint32_t firstVertex; + uint32_t firstInstance; + }; + + struct DrawIndexedCmd { + uint32_t indexCount; + uint32_t instanceCount; + uint32_t firstIndex; + int32_t baseVertex; + uint32_t firstInstance; + }; + + struct DrawIndirectCmd { + Ref<BufferBase> indirectBuffer; + uint64_t indirectOffset; + }; + + struct DrawIndexedIndirectCmd { + Ref<BufferBase> indirectBuffer; + uint64_t indirectOffset; + }; + + struct EndComputePassCmd { + std::vector<TimestampWrite> timestampWrites; + }; + + struct EndOcclusionQueryCmd { + Ref<QuerySetBase> querySet; + uint32_t queryIndex; + }; + + struct EndRenderPassCmd { + std::vector<TimestampWrite> timestampWrites; + }; + + struct ExecuteBundlesCmd { + uint32_t count; + }; + + struct ClearBufferCmd { + Ref<BufferBase> buffer; + uint64_t offset; + uint64_t size; + }; + + struct InsertDebugMarkerCmd { + uint32_t length; + }; + + struct PopDebugGroupCmd {}; + + struct PushDebugGroupCmd { + uint32_t length; + }; + + struct ResolveQuerySetCmd { + Ref<QuerySetBase> querySet; + uint32_t firstQuery; + uint32_t queryCount; + Ref<BufferBase> destination; + uint64_t destinationOffset; + }; + + struct SetComputePipelineCmd { + Ref<ComputePipelineBase> pipeline; + }; + + struct SetRenderPipelineCmd { + Ref<RenderPipelineBase> pipeline; + }; + + struct SetStencilReferenceCmd { + uint32_t reference; + }; + + struct SetViewportCmd { + float x, y, width, height, minDepth, maxDepth; + }; + + struct SetScissorRectCmd { + uint32_t x, y, width, height; + }; + + struct SetBlendConstantCmd { + Color color; + }; + + struct SetBindGroupCmd { + BindGroupIndex index; + Ref<BindGroupBase> group; + uint32_t dynamicOffsetCount; + }; + + struct SetIndexBufferCmd { + Ref<BufferBase> buffer; + wgpu::IndexFormat format; + uint64_t offset; + uint64_t size; + }; + + struct SetVertexBufferCmd { + VertexBufferSlot slot; + Ref<BufferBase> buffer; + uint64_t offset; + uint64_t size; + }; + + struct WriteBufferCmd { + Ref<BufferBase> buffer; + uint64_t offset; + uint64_t size; + }; + + struct WriteTimestampCmd { + Ref<QuerySetBase> querySet; + uint32_t queryIndex; + }; + + // This needs to be called before the CommandIterator is freed so that the Ref<> present in + // the commands have a chance to run their destructor and remove internal references. + class CommandIterator; + void FreeCommands(CommandIterator* commands); + + // Helper function to allow skipping over a command when it is unimplemented, while still + // consuming the correct amount of data from the command iterator. + void SkipCommand(CommandIterator* commands, Command type); + +} // namespace dawn::native + +#endif // DAWNNATIVE_COMMANDS_H_
diff --git a/src/dawn/native/CompilationMessages.cpp b/src/dawn/native/CompilationMessages.cpp new file mode 100644 index 0000000..47c3d0b --- /dev/null +++ b/src/dawn/native/CompilationMessages.cpp
@@ -0,0 +1,201 @@ +// Copyright 2021 The Dawn Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include "dawn/native/CompilationMessages.h" + +#include "dawn/common/Assert.h" +#include "dawn/native/dawn_platform.h" + +#include <tint/tint.h> + +namespace dawn::native { + + namespace { + + WGPUCompilationMessageType tintSeverityToMessageType(tint::diag::Severity severity) { + switch (severity) { + case tint::diag::Severity::Note: + return WGPUCompilationMessageType_Info; + case tint::diag::Severity::Warning: + return WGPUCompilationMessageType_Warning; + default: + return WGPUCompilationMessageType_Error; + } + } + + } // anonymous namespace + + OwnedCompilationMessages::OwnedCompilationMessages() { + mCompilationInfo.nextInChain = 0; + mCompilationInfo.messageCount = 0; + mCompilationInfo.messages = nullptr; + } + + void OwnedCompilationMessages::AddMessageForTesting(std::string message, + wgpu::CompilationMessageType type, + uint64_t lineNum, + uint64_t linePos, + uint64_t offset, + uint64_t length) { + // Cannot add messages after GetCompilationInfo has been called. + ASSERT(mCompilationInfo.messages == nullptr); + + mMessageStrings.push_back(message); + mMessages.push_back({nullptr, nullptr, static_cast<WGPUCompilationMessageType>(type), + lineNum, linePos, offset, length}); + } + + void OwnedCompilationMessages::AddMessage(const tint::diag::Diagnostic& diagnostic) { + // Cannot add messages after GetCompilationInfo has been called. + ASSERT(mCompilationInfo.messages == nullptr); + + // Tint line and column values are 1-based. + uint64_t lineNum = diagnostic.source.range.begin.line; + uint64_t linePos = diagnostic.source.range.begin.column; + // The offset is 0-based. + uint64_t offset = 0; + uint64_t length = 0; + + if (lineNum && linePos && diagnostic.source.file) { + const auto& lines = diagnostic.source.file->content.lines; + size_t i = 0; + // To find the offset of the message position, loop through each of the first lineNum-1 + // lines and add it's length (+1 to account for the line break) to the offset. + for (; i < lineNum - 1; ++i) { + offset += lines[i].length() + 1; + } + + // If the end line is on a different line from the beginning line, add the length of the + // lines in between to the ending offset. + uint64_t endLineNum = diagnostic.source.range.end.line; + uint64_t endLinePos = diagnostic.source.range.end.column; + + // If the range has a valid start but the end it not specified, clamp it to the start. + if (endLineNum == 0 || endLinePos == 0) { + endLineNum = lineNum; + endLinePos = linePos; + } + + // Negative ranges aren't allowed + ASSERT(endLineNum >= lineNum); + + uint64_t endOffset = offset; + for (; i < endLineNum - 1; ++i) { + endOffset += lines[i].length() + 1; + } + + // Add the line positions to the offset and endOffset to get their final positions + // within the code string. + offset += linePos - 1; + endOffset += endLinePos - 1; + + // Negative ranges aren't allowed + ASSERT(endOffset >= offset); + + // The length of the message is the difference between the starting offset and the + // ending offset. + length = endOffset - offset; + } + + if (diagnostic.code) { + mMessageStrings.push_back(std::string(diagnostic.code) + ": " + diagnostic.message); + } else { + mMessageStrings.push_back(diagnostic.message); + } + + mMessages.push_back({nullptr, nullptr, tintSeverityToMessageType(diagnostic.severity), + lineNum, linePos, offset, length}); + } + + void OwnedCompilationMessages::AddMessages(const tint::diag::List& diagnostics) { + // Cannot add messages after GetCompilationInfo has been called. + ASSERT(mCompilationInfo.messages == nullptr); + + for (const auto& diag : diagnostics) { + AddMessage(diag); + } + + AddFormattedTintMessages(diagnostics); + } + + void OwnedCompilationMessages::ClearMessages() { + // Cannot clear messages after GetCompilationInfo has been called. + ASSERT(mCompilationInfo.messages == nullptr); + + mMessageStrings.clear(); + mMessages.clear(); + } + + const WGPUCompilationInfo* OwnedCompilationMessages::GetCompilationInfo() { + mCompilationInfo.messageCount = mMessages.size(); + mCompilationInfo.messages = mMessages.data(); + + // Ensure every message points at the correct message string. Cannot do this earlier, since + // vector reallocations may move the pointers around. + for (size_t i = 0; i < mCompilationInfo.messageCount; ++i) { + WGPUCompilationMessage& message = mMessages[i]; + std::string& messageString = mMessageStrings[i]; + message.message = messageString.c_str(); + } + + return &mCompilationInfo; + } + + const std::vector<std::string>& OwnedCompilationMessages::GetFormattedTintMessages() { + return mFormattedTintMessages; + } + + void OwnedCompilationMessages::AddFormattedTintMessages(const tint::diag::List& diagnostics) { + tint::diag::List messageList; + size_t warningCount = 0; + size_t errorCount = 0; + for (auto& diag : diagnostics) { + switch (diag.severity) { + case (tint::diag::Severity::Fatal): + case (tint::diag::Severity::Error): + case (tint::diag::Severity::InternalCompilerError): { + errorCount++; + messageList.add(tint::diag::Diagnostic(diag)); + break; + } + case (tint::diag::Severity::Warning): { + warningCount++; + messageList.add(tint::diag::Diagnostic(diag)); + break; + } + default: + break; + } + } + if (errorCount == 0 && warningCount == 0) { + return; + } + tint::diag::Formatter::Style style; + style.print_newline_at_end = false; + std::ostringstream t; + if (errorCount > 0) { + t << errorCount << " error(s) "; + if (warningCount > 0) { + t << "and "; + } + } + if (warningCount > 0) { + t << warningCount << " warning(s) "; + } + t << "generated while compiling the shader:" << std::endl + << tint::diag::Formatter{style}.format(messageList); + mFormattedTintMessages.push_back(t.str()); + } + +} // namespace dawn::native
diff --git a/src/dawn/native/CompilationMessages.h b/src/dawn/native/CompilationMessages.h new file mode 100644 index 0000000..92e3346 --- /dev/null +++ b/src/dawn/native/CompilationMessages.h
@@ -0,0 +1,62 @@ +// Copyright 2021 The Dawn Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#ifndef DAWNNATIVE_COMPILATIONMESSAGES_H_ +#define DAWNNATIVE_COMPILATIONMESSAGES_H_ + +#include "dawn/native/dawn_platform.h" + +#include "dawn/common/NonCopyable.h" + +#include <string> +#include <vector> + +namespace tint::diag { + class Diagnostic; + class List; +} // namespace tint::diag + +namespace dawn::native { + + class OwnedCompilationMessages : public NonCopyable { + public: + OwnedCompilationMessages(); + ~OwnedCompilationMessages() = default; + + void AddMessageForTesting( + std::string message, + wgpu::CompilationMessageType type = wgpu::CompilationMessageType::Info, + uint64_t lineNum = 0, + uint64_t linePos = 0, + uint64_t offset = 0, + uint64_t length = 0); + void AddMessages(const tint::diag::List& diagnostics); + void ClearMessages(); + + const WGPUCompilationInfo* GetCompilationInfo(); + const std::vector<std::string>& GetFormattedTintMessages(); + + private: + void AddMessage(const tint::diag::Diagnostic& diagnostic); + void AddFormattedTintMessages(const tint::diag::List& diagnostics); + + WGPUCompilationInfo mCompilationInfo; + std::vector<std::string> mMessageStrings; + std::vector<WGPUCompilationMessage> mMessages; + std::vector<std::string> mFormattedTintMessages; + }; + +} // namespace dawn::native + +#endif // DAWNNATIVE_COMPILATIONMESSAGES_H_
diff --git a/src/dawn/native/ComputePassEncoder.cpp b/src/dawn/native/ComputePassEncoder.cpp new file mode 100644 index 0000000..e825ef2 --- /dev/null +++ b/src/dawn/native/ComputePassEncoder.cpp
@@ -0,0 +1,485 @@ +// Copyright 2018 The Dawn Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include "dawn/native/ComputePassEncoder.h" + +#include "dawn/native/BindGroup.h" +#include "dawn/native/BindGroupLayout.h" +#include "dawn/native/Buffer.h" +#include "dawn/native/CommandEncoder.h" +#include "dawn/native/CommandValidation.h" +#include "dawn/native/Commands.h" +#include "dawn/native/ComputePipeline.h" +#include "dawn/native/Device.h" +#include "dawn/native/InternalPipelineStore.h" +#include "dawn/native/ObjectType_autogen.h" +#include "dawn/native/PassResourceUsageTracker.h" +#include "dawn/native/QuerySet.h" +#include "dawn/native/utils/WGPUHelpers.h" + +namespace dawn::native { + + namespace { + + ResultOrError<ComputePipelineBase*> GetOrCreateIndirectDispatchValidationPipeline( + DeviceBase* device) { + InternalPipelineStore* store = device->GetInternalPipelineStore(); + + if (store->dispatchIndirectValidationPipeline != nullptr) { + return store->dispatchIndirectValidationPipeline.Get(); + } + + // TODO(https://crbug.com/dawn/1108): Propagate validation feedback from this + // shader in various failure modes. + // Type 'bool' cannot be used in storage class 'uniform' as it is non-host-shareable. + Ref<ShaderModuleBase> shaderModule; + DAWN_TRY_ASSIGN(shaderModule, utils::CreateShaderModule(device, R"( + struct UniformParams { + maxComputeWorkgroupsPerDimension: u32; + clientOffsetInU32: u32; + enableValidation: u32; + duplicateNumWorkgroups: u32; + }; + + struct IndirectParams { + data: array<u32>; + }; + + struct ValidatedParams { + data: array<u32>; + }; + + @group(0) @binding(0) var<uniform> uniformParams: UniformParams; + @group(0) @binding(1) var<storage, read_write> clientParams: IndirectParams; + @group(0) @binding(2) var<storage, write> validatedParams: ValidatedParams; + + @stage(compute) @workgroup_size(1, 1, 1) + fn main() { + for (var i = 0u; i < 3u; i = i + 1u) { + var numWorkgroups = clientParams.data[uniformParams.clientOffsetInU32 + i]; + if (uniformParams.enableValidation > 0u && + numWorkgroups > uniformParams.maxComputeWorkgroupsPerDimension) { + numWorkgroups = 0u; + } + validatedParams.data[i] = numWorkgroups; + + if (uniformParams.duplicateNumWorkgroups > 0u) { + validatedParams.data[i + 3u] = numWorkgroups; + } + } + } + )")); + + Ref<BindGroupLayoutBase> bindGroupLayout; + DAWN_TRY_ASSIGN( + bindGroupLayout, + utils::MakeBindGroupLayout( + device, + { + {0, wgpu::ShaderStage::Compute, wgpu::BufferBindingType::Uniform}, + {1, wgpu::ShaderStage::Compute, kInternalStorageBufferBinding}, + {2, wgpu::ShaderStage::Compute, wgpu::BufferBindingType::Storage}, + }, + /* allowInternalBinding */ true)); + + Ref<PipelineLayoutBase> pipelineLayout; + DAWN_TRY_ASSIGN(pipelineLayout, + utils::MakeBasicPipelineLayout(device, bindGroupLayout)); + + ComputePipelineDescriptor computePipelineDescriptor = {}; + computePipelineDescriptor.layout = pipelineLayout.Get(); + computePipelineDescriptor.compute.module = shaderModule.Get(); + computePipelineDescriptor.compute.entryPoint = "main"; + + DAWN_TRY_ASSIGN(store->dispatchIndirectValidationPipeline, + device->CreateComputePipeline(&computePipelineDescriptor)); + + return store->dispatchIndirectValidationPipeline.Get(); + } + + } // namespace + + ComputePassEncoder::ComputePassEncoder(DeviceBase* device, + const ComputePassDescriptor* descriptor, + CommandEncoder* commandEncoder, + EncodingContext* encodingContext, + std::vector<TimestampWrite> timestampWritesAtEnd) + : ProgrammableEncoder(device, descriptor->label, encodingContext), + mCommandEncoder(commandEncoder), + mTimestampWritesAtEnd(std::move(timestampWritesAtEnd)) { + TrackInDevice(); + } + + // static + Ref<ComputePassEncoder> ComputePassEncoder::Create( + DeviceBase* device, + const ComputePassDescriptor* descriptor, + CommandEncoder* commandEncoder, + EncodingContext* encodingContext, + std::vector<TimestampWrite> timestampWritesAtEnd) { + return AcquireRef(new ComputePassEncoder(device, descriptor, commandEncoder, + encodingContext, std::move(timestampWritesAtEnd))); + } + + ComputePassEncoder::ComputePassEncoder(DeviceBase* device, + CommandEncoder* commandEncoder, + EncodingContext* encodingContext, + ErrorTag errorTag) + : ProgrammableEncoder(device, encodingContext, errorTag), mCommandEncoder(commandEncoder) { + } + + // static + Ref<ComputePassEncoder> ComputePassEncoder::MakeError(DeviceBase* device, + CommandEncoder* commandEncoder, + EncodingContext* encodingContext) { + return AcquireRef( + new ComputePassEncoder(device, commandEncoder, encodingContext, ObjectBase::kError)); + } + + void ComputePassEncoder::DestroyImpl() { + // Ensure that the pass has exited. This is done for passes only since validation requires + // they exit before destruction while bundles do not. + mEncodingContext->EnsurePassExited(this); + } + + ObjectType ComputePassEncoder::GetType() const { + return ObjectType::ComputePassEncoder; + } + + void ComputePassEncoder::APIEnd() { + if (mEncodingContext->TryEncode( + this, + [&](CommandAllocator* allocator) -> MaybeError { + if (IsValidationEnabled()) { + DAWN_TRY(ValidateProgrammableEncoderEnd()); + } + + EndComputePassCmd* cmd = + allocator->Allocate<EndComputePassCmd>(Command::EndComputePass); + // The query availability has already been updated at the beginning of compute + // pass, and no need to do update here. + cmd->timestampWrites = std::move(mTimestampWritesAtEnd); + + return {}; + }, + "encoding %s.End().", this)) { + mEncodingContext->ExitComputePass(this, mUsageTracker.AcquireResourceUsage()); + } + } + + void ComputePassEncoder::APIEndPass() { + GetDevice()->EmitDeprecationWarning("endPass() has been deprecated. Use end() instead."); + APIEnd(); + } + + void ComputePassEncoder::APIDispatch(uint32_t workgroupCountX, + uint32_t workgroupCountY, + uint32_t workgroupCountZ) { + mEncodingContext->TryEncode( + this, + [&](CommandAllocator* allocator) -> MaybeError { + if (IsValidationEnabled()) { + DAWN_TRY(mCommandBufferState.ValidateCanDispatch()); + + uint32_t workgroupsPerDimension = + GetDevice()->GetLimits().v1.maxComputeWorkgroupsPerDimension; + + DAWN_INVALID_IF(workgroupCountX > workgroupsPerDimension, + "Dispatch workgroup count X (%u) exceeds max compute " + "workgroups per dimension (%u).", + workgroupCountX, workgroupsPerDimension); + + DAWN_INVALID_IF(workgroupCountY > workgroupsPerDimension, + "Dispatch workgroup count Y (%u) exceeds max compute " + "workgroups per dimension (%u).", + workgroupCountY, workgroupsPerDimension); + + DAWN_INVALID_IF(workgroupCountZ > workgroupsPerDimension, + "Dispatch workgroup count Z (%u) exceeds max compute " + "workgroups per dimension (%u).", + workgroupCountZ, workgroupsPerDimension); + } + + // Record the synchronization scope for Dispatch, which is just the current + // bindgroups. + AddDispatchSyncScope(); + + DispatchCmd* dispatch = allocator->Allocate<DispatchCmd>(Command::Dispatch); + dispatch->x = workgroupCountX; + dispatch->y = workgroupCountY; + dispatch->z = workgroupCountZ; + + return {}; + }, + "encoding %s.Dispatch(%u, %u, %u).", this, workgroupCountX, workgroupCountY, + workgroupCountZ); + } + + ResultOrError<std::pair<Ref<BufferBase>, uint64_t>> + ComputePassEncoder::TransformIndirectDispatchBuffer(Ref<BufferBase> indirectBuffer, + uint64_t indirectOffset) { + DeviceBase* device = GetDevice(); + + const bool shouldDuplicateNumWorkgroups = + device->ShouldDuplicateNumWorkgroupsForDispatchIndirect( + mCommandBufferState.GetComputePipeline()); + if (!IsValidationEnabled() && !shouldDuplicateNumWorkgroups) { + return std::make_pair(indirectBuffer, indirectOffset); + } + + // Save the previous command buffer state so it can be restored after the + // validation inserts additional commands. + CommandBufferStateTracker previousState = mCommandBufferState; + + auto* const store = device->GetInternalPipelineStore(); + + Ref<ComputePipelineBase> validationPipeline; + DAWN_TRY_ASSIGN(validationPipeline, GetOrCreateIndirectDispatchValidationPipeline(device)); + + Ref<BindGroupLayoutBase> layout; + DAWN_TRY_ASSIGN(layout, validationPipeline->GetBindGroupLayout(0)); + + uint32_t storageBufferOffsetAlignment = + device->GetLimits().v1.minStorageBufferOffsetAlignment; + + // Let the offset be the indirectOffset, aligned down to |storageBufferOffsetAlignment|. + const uint32_t clientOffsetFromAlignedBoundary = + indirectOffset % storageBufferOffsetAlignment; + const uint64_t clientOffsetAlignedDown = indirectOffset - clientOffsetFromAlignedBoundary; + const uint64_t clientIndirectBindingOffset = clientOffsetAlignedDown; + + // Let the size of the binding be the additional offset, plus the size. + const uint64_t clientIndirectBindingSize = + kDispatchIndirectSize + clientOffsetFromAlignedBoundary; + + // Neither 'enableValidation' nor 'duplicateNumWorkgroups' can be declared as 'bool' as + // currently in WGSL type 'bool' cannot be used in storage class 'uniform' as 'it is + // non-host-shareable'. + struct UniformParams { + uint32_t maxComputeWorkgroupsPerDimension; + uint32_t clientOffsetInU32; + uint32_t enableValidation; + uint32_t duplicateNumWorkgroups; + }; + + // Create a uniform buffer to hold parameters for the shader. + Ref<BufferBase> uniformBuffer; + { + UniformParams params; + params.maxComputeWorkgroupsPerDimension = + device->GetLimits().v1.maxComputeWorkgroupsPerDimension; + params.clientOffsetInU32 = clientOffsetFromAlignedBoundary / sizeof(uint32_t); + params.enableValidation = static_cast<uint32_t>(IsValidationEnabled()); + params.duplicateNumWorkgroups = static_cast<uint32_t>(shouldDuplicateNumWorkgroups); + + DAWN_TRY_ASSIGN(uniformBuffer, utils::CreateBufferFromData( + device, wgpu::BufferUsage::Uniform, {params})); + } + + // Reserve space in the scratch buffer to hold the validated indirect params. + ScratchBuffer& scratchBuffer = store->scratchIndirectStorage; + const uint64_t scratchBufferSize = + shouldDuplicateNumWorkgroups ? 2 * kDispatchIndirectSize : kDispatchIndirectSize; + DAWN_TRY(scratchBuffer.EnsureCapacity(scratchBufferSize)); + Ref<BufferBase> validatedIndirectBuffer = scratchBuffer.GetBuffer(); + + Ref<BindGroupBase> validationBindGroup; + ASSERT(indirectBuffer->GetUsage() & kInternalStorageBuffer); + DAWN_TRY_ASSIGN(validationBindGroup, + utils::MakeBindGroup(device, layout, + { + {0, uniformBuffer}, + {1, indirectBuffer, clientIndirectBindingOffset, + clientIndirectBindingSize}, + {2, validatedIndirectBuffer, 0, scratchBufferSize}, + })); + + // Issue commands to validate the indirect buffer. + APISetPipeline(validationPipeline.Get()); + APISetBindGroup(0, validationBindGroup.Get()); + APIDispatch(1); + + // Restore the state. + RestoreCommandBufferState(std::move(previousState)); + + // Return the new indirect buffer and indirect buffer offset. + return std::make_pair(std::move(validatedIndirectBuffer), uint64_t(0)); + } + + void ComputePassEncoder::APIDispatchIndirect(BufferBase* indirectBuffer, + uint64_t indirectOffset) { + mEncodingContext->TryEncode( + this, + [&](CommandAllocator* allocator) -> MaybeError { + if (IsValidationEnabled()) { + DAWN_TRY(GetDevice()->ValidateObject(indirectBuffer)); + DAWN_TRY(ValidateCanUseAs(indirectBuffer, wgpu::BufferUsage::Indirect)); + DAWN_TRY(mCommandBufferState.ValidateCanDispatch()); + + DAWN_INVALID_IF(indirectOffset % 4 != 0, + "Indirect offset (%u) is not a multiple of 4.", indirectOffset); + + DAWN_INVALID_IF( + indirectOffset >= indirectBuffer->GetSize() || + indirectOffset + kDispatchIndirectSize > indirectBuffer->GetSize(), + "Indirect offset (%u) and dispatch size (%u) exceeds the indirect buffer " + "size (%u).", + indirectOffset, kDispatchIndirectSize, indirectBuffer->GetSize()); + } + + SyncScopeUsageTracker scope; + scope.BufferUsedAs(indirectBuffer, wgpu::BufferUsage::Indirect); + mUsageTracker.AddReferencedBuffer(indirectBuffer); + // TODO(crbug.com/dawn/1166): If validation is enabled, adding |indirectBuffer| + // is needed for correct usage validation even though it will only be bound for + // storage. This will unecessarily transition the |indirectBuffer| in + // the backend. + + Ref<BufferBase> indirectBufferRef = indirectBuffer; + + // Get applied indirect buffer with necessary changes on the original indirect + // buffer. For example, + // - Validate each indirect dispatch with a single dispatch to copy the indirect + // buffer params into a scratch buffer if they're valid, and otherwise zero them + // out. + // - Duplicate all the indirect dispatch parameters to support @num_workgroups on + // D3D12. + // - Directly return the original indirect dispatch buffer if we don't need any + // transformations on it. + // We could consider moving the validation earlier in the pass after the last + // last point the indirect buffer was used with writable usage, as well as batch + // validation for multiple dispatches into one, but inserting commands at + // arbitrary points in the past is not possible right now. + DAWN_TRY_ASSIGN(std::tie(indirectBufferRef, indirectOffset), + TransformIndirectDispatchBuffer(indirectBufferRef, indirectOffset)); + + // If we have created a new scratch dispatch indirect buffer in + // TransformIndirectDispatchBuffer(), we need to track it in mUsageTracker. + if (indirectBufferRef.Get() != indirectBuffer) { + // |indirectBufferRef| was replaced with a scratch buffer. Add it to the + // synchronization scope. + scope.BufferUsedAs(indirectBufferRef.Get(), wgpu::BufferUsage::Indirect); + mUsageTracker.AddReferencedBuffer(indirectBufferRef.Get()); + } + + AddDispatchSyncScope(std::move(scope)); + + DispatchIndirectCmd* dispatch = + allocator->Allocate<DispatchIndirectCmd>(Command::DispatchIndirect); + dispatch->indirectBuffer = std::move(indirectBufferRef); + dispatch->indirectOffset = indirectOffset; + return {}; + }, + "encoding %s.DispatchIndirect(%s, %u).", this, indirectBuffer, indirectOffset); + } + + void ComputePassEncoder::APISetPipeline(ComputePipelineBase* pipeline) { + mEncodingContext->TryEncode( + this, + [&](CommandAllocator* allocator) -> MaybeError { + if (IsValidationEnabled()) { + DAWN_TRY(GetDevice()->ValidateObject(pipeline)); + } + + mCommandBufferState.SetComputePipeline(pipeline); + + SetComputePipelineCmd* cmd = + allocator->Allocate<SetComputePipelineCmd>(Command::SetComputePipeline); + cmd->pipeline = pipeline; + + return {}; + }, + "encoding %s.SetPipeline(%s).", this, pipeline); + } + + void ComputePassEncoder::APISetBindGroup(uint32_t groupIndexIn, + BindGroupBase* group, + uint32_t dynamicOffsetCount, + const uint32_t* dynamicOffsets) { + mEncodingContext->TryEncode( + this, + [&](CommandAllocator* allocator) -> MaybeError { + BindGroupIndex groupIndex(groupIndexIn); + + if (IsValidationEnabled()) { + DAWN_TRY(ValidateSetBindGroup(groupIndex, group, dynamicOffsetCount, + dynamicOffsets)); + } + + mUsageTracker.AddResourcesReferencedByBindGroup(group); + RecordSetBindGroup(allocator, groupIndex, group, dynamicOffsetCount, + dynamicOffsets); + mCommandBufferState.SetBindGroup(groupIndex, group, dynamicOffsetCount, + dynamicOffsets); + + return {}; + }, + "encoding %s.SetBindGroup(%u, %s, %u, ...).", this, groupIndexIn, group, + dynamicOffsetCount); + } + + void ComputePassEncoder::APIWriteTimestamp(QuerySetBase* querySet, uint32_t queryIndex) { + mEncodingContext->TryEncode( + this, + [&](CommandAllocator* allocator) -> MaybeError { + if (IsValidationEnabled()) { + DAWN_TRY(ValidateTimestampQuery(GetDevice(), querySet, queryIndex)); + } + + mCommandEncoder->TrackQueryAvailability(querySet, queryIndex); + + WriteTimestampCmd* cmd = + allocator->Allocate<WriteTimestampCmd>(Command::WriteTimestamp); + cmd->querySet = querySet; + cmd->queryIndex = queryIndex; + + return {}; + }, + "encoding %s.WriteTimestamp(%s, %u).", this, querySet, queryIndex); + } + + void ComputePassEncoder::AddDispatchSyncScope(SyncScopeUsageTracker scope) { + PipelineLayoutBase* layout = mCommandBufferState.GetPipelineLayout(); + for (BindGroupIndex i : IterateBitSet(layout->GetBindGroupLayoutsMask())) { + scope.AddBindGroup(mCommandBufferState.GetBindGroup(i)); + } + mUsageTracker.AddDispatch(scope.AcquireSyncScopeUsage()); + } + + void ComputePassEncoder::RestoreCommandBufferState(CommandBufferStateTracker state) { + // Encode commands for the backend to restore the pipeline and bind groups. + if (state.HasPipeline()) { + APISetPipeline(state.GetComputePipeline()); + } + for (BindGroupIndex i(0); i < kMaxBindGroupsTyped; ++i) { + BindGroupBase* bg = state.GetBindGroup(i); + if (bg != nullptr) { + const std::vector<uint32_t>& offsets = state.GetDynamicOffsets(i); + if (offsets.empty()) { + APISetBindGroup(static_cast<uint32_t>(i), bg); + } else { + APISetBindGroup(static_cast<uint32_t>(i), bg, offsets.size(), offsets.data()); + } + } + } + + // Restore the frontend state tracking information. + mCommandBufferState = std::move(state); + } + + CommandBufferStateTracker* ComputePassEncoder::GetCommandBufferStateTrackerForTesting() { + return &mCommandBufferState; + } + +} // namespace dawn::native
diff --git a/src/dawn/native/ComputePassEncoder.h b/src/dawn/native/ComputePassEncoder.h new file mode 100644 index 0000000..16dd11d --- /dev/null +++ b/src/dawn/native/ComputePassEncoder.h
@@ -0,0 +1,98 @@ +// Copyright 2018 The Dawn Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#ifndef DAWNNATIVE_COMPUTEPASSENCODER_H_ +#define DAWNNATIVE_COMPUTEPASSENCODER_H_ + +#include "dawn/native/CommandBufferStateTracker.h" +#include "dawn/native/Error.h" +#include "dawn/native/Forward.h" +#include "dawn/native/PassResourceUsageTracker.h" +#include "dawn/native/ProgrammableEncoder.h" + +namespace dawn::native { + + class SyncScopeUsageTracker; + + class ComputePassEncoder final : public ProgrammableEncoder { + public: + static Ref<ComputePassEncoder> Create(DeviceBase* device, + const ComputePassDescriptor* descriptor, + CommandEncoder* commandEncoder, + EncodingContext* encodingContext, + std::vector<TimestampWrite> timestampWritesAtEnd); + static Ref<ComputePassEncoder> MakeError(DeviceBase* device, + CommandEncoder* commandEncoder, + EncodingContext* encodingContext); + + ObjectType GetType() const override; + + void APIEnd(); + void APIEndPass(); // TODO(dawn:1286): Remove after deprecation period. + + void APIDispatch(uint32_t workgroupCountX, + uint32_t workgroupCountY = 1, + uint32_t workgroupCountZ = 1); + void APIDispatchIndirect(BufferBase* indirectBuffer, uint64_t indirectOffset); + void APISetPipeline(ComputePipelineBase* pipeline); + + void APISetBindGroup(uint32_t groupIndex, + BindGroupBase* group, + uint32_t dynamicOffsetCount = 0, + const uint32_t* dynamicOffsets = nullptr); + + void APIWriteTimestamp(QuerySetBase* querySet, uint32_t queryIndex); + + CommandBufferStateTracker* GetCommandBufferStateTrackerForTesting(); + void RestoreCommandBufferStateForTesting(CommandBufferStateTracker state) { + RestoreCommandBufferState(std::move(state)); + } + + protected: + ComputePassEncoder(DeviceBase* device, + const ComputePassDescriptor* descriptor, + CommandEncoder* commandEncoder, + EncodingContext* encodingContext, + std::vector<TimestampWrite> timestampWritesAtEnd); + ComputePassEncoder(DeviceBase* device, + CommandEncoder* commandEncoder, + EncodingContext* encodingContext, + ErrorTag errorTag); + + private: + void DestroyImpl() override; + + ResultOrError<std::pair<Ref<BufferBase>, uint64_t>> TransformIndirectDispatchBuffer( + Ref<BufferBase> indirectBuffer, + uint64_t indirectOffset); + + void RestoreCommandBufferState(CommandBufferStateTracker state); + + CommandBufferStateTracker mCommandBufferState; + + // Adds the bindgroups used for the current dispatch to the SyncScopeResourceUsage and + // records it in mUsageTracker. + void AddDispatchSyncScope(SyncScopeUsageTracker scope = {}); + ComputePassResourceUsageTracker mUsageTracker; + + // For render and compute passes, the encoding context is borrowed from the command encoder. + // Keep a reference to the encoder to make sure the context isn't freed. + Ref<CommandEncoder> mCommandEncoder; + + std::vector<TimestampWrite> mTimestampWritesAtEnd; + }; + +} // namespace dawn::native + +#endif // DAWNNATIVE_COMPUTEPASSENCODER_H_
diff --git a/src/dawn/native/ComputePipeline.cpp b/src/dawn/native/ComputePipeline.cpp new file mode 100644 index 0000000..2de7f32 --- /dev/null +++ b/src/dawn/native/ComputePipeline.cpp
@@ -0,0 +1,96 @@ +// Copyright 2017 The Dawn Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include "dawn/native/ComputePipeline.h" + +#include "dawn/native/Device.h" +#include "dawn/native/ObjectContentHasher.h" +#include "dawn/native/ObjectType_autogen.h" + +namespace dawn::native { + + MaybeError ValidateComputePipelineDescriptor(DeviceBase* device, + const ComputePipelineDescriptor* descriptor) { + if (descriptor->nextInChain != nullptr) { + return DAWN_FORMAT_VALIDATION_ERROR("nextInChain must be nullptr."); + } + + if (descriptor->layout != nullptr) { + DAWN_TRY(device->ValidateObject(descriptor->layout)); + } + + return ValidateProgrammableStage( + device, descriptor->compute.module, descriptor->compute.entryPoint, + descriptor->compute.constantCount, descriptor->compute.constants, descriptor->layout, + SingleShaderStage::Compute); + } + + // ComputePipelineBase + + ComputePipelineBase::ComputePipelineBase(DeviceBase* device, + const ComputePipelineDescriptor* descriptor) + : PipelineBase(device, + descriptor->layout, + descriptor->label, + {{SingleShaderStage::Compute, descriptor->compute.module, + descriptor->compute.entryPoint, descriptor->compute.constantCount, + descriptor->compute.constants}}) { + SetContentHash(ComputeContentHash()); + TrackInDevice(); + } + + ComputePipelineBase::ComputePipelineBase(DeviceBase* device) : PipelineBase(device) { + TrackInDevice(); + } + + ComputePipelineBase::ComputePipelineBase(DeviceBase* device, ObjectBase::ErrorTag tag) + : PipelineBase(device, tag) { + } + + ComputePipelineBase::~ComputePipelineBase() = default; + + void ComputePipelineBase::DestroyImpl() { + if (IsCachedReference()) { + // Do not uncache the actual cached object if we are a blueprint. + GetDevice()->UncacheComputePipeline(this); + } + } + + // static + ComputePipelineBase* ComputePipelineBase::MakeError(DeviceBase* device) { + class ErrorComputePipeline final : public ComputePipelineBase { + public: + ErrorComputePipeline(DeviceBase* device) + : ComputePipelineBase(device, ObjectBase::kError) { + } + + MaybeError Initialize() override { + UNREACHABLE(); + return {}; + } + }; + + return new ErrorComputePipeline(device); + } + + ObjectType ComputePipelineBase::GetType() const { + return ObjectType::ComputePipeline; + } + + bool ComputePipelineBase::EqualityFunc::operator()(const ComputePipelineBase* a, + const ComputePipelineBase* b) const { + return PipelineBase::EqualForCache(a, b); + } + +} // namespace dawn::native
diff --git a/src/dawn/native/ComputePipeline.h b/src/dawn/native/ComputePipeline.h new file mode 100644 index 0000000..1bd97d1 --- /dev/null +++ b/src/dawn/native/ComputePipeline.h
@@ -0,0 +1,55 @@ +// Copyright 2017 The Dawn Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#ifndef DAWNNATIVE_COMPUTEPIPELINE_H_ +#define DAWNNATIVE_COMPUTEPIPELINE_H_ + +#include "dawn/common/NonCopyable.h" +#include "dawn/native/Forward.h" +#include "dawn/native/Pipeline.h" + +namespace dawn::native { + + class DeviceBase; + struct EntryPointMetadata; + + MaybeError ValidateComputePipelineDescriptor(DeviceBase* device, + const ComputePipelineDescriptor* descriptor); + + class ComputePipelineBase : public PipelineBase { + public: + ComputePipelineBase(DeviceBase* device, const ComputePipelineDescriptor* descriptor); + ~ComputePipelineBase() override; + + static ComputePipelineBase* MakeError(DeviceBase* device); + + ObjectType GetType() const override; + + // Functors necessary for the unordered_set<ComputePipelineBase*>-based cache. + struct EqualityFunc { + bool operator()(const ComputePipelineBase* a, const ComputePipelineBase* b) const; + }; + + protected: + // Constructor used only for mocking and testing. + ComputePipelineBase(DeviceBase* device); + void DestroyImpl() override; + + private: + ComputePipelineBase(DeviceBase* device, ObjectBase::ErrorTag tag); + }; + +} // namespace dawn::native + +#endif // DAWNNATIVE_COMPUTEPIPELINE_H_
diff --git a/src/dawn/native/CopyTextureForBrowserHelper.cpp b/src/dawn/native/CopyTextureForBrowserHelper.cpp new file mode 100644 index 0000000..a72cedc --- /dev/null +++ b/src/dawn/native/CopyTextureForBrowserHelper.cpp
@@ -0,0 +1,604 @@ +// Copyright 2020 The Dawn Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include "dawn/native/CopyTextureForBrowserHelper.h" + +#include "dawn/common/Log.h" +#include "dawn/native/BindGroup.h" +#include "dawn/native/BindGroupLayout.h" +#include "dawn/native/Buffer.h" +#include "dawn/native/CommandBuffer.h" +#include "dawn/native/CommandEncoder.h" +#include "dawn/native/CommandValidation.h" +#include "dawn/native/Device.h" +#include "dawn/native/InternalPipelineStore.h" +#include "dawn/native/Queue.h" +#include "dawn/native/RenderPassEncoder.h" +#include "dawn/native/RenderPipeline.h" +#include "dawn/native/Sampler.h" +#include "dawn/native/Texture.h" +#include "dawn/native/ValidationUtils_autogen.h" +#include "dawn/native/utils/WGPUHelpers.h" + +#include <unordered_set> + +namespace dawn::native { + namespace { + + static const char sCopyTextureForBrowserShader[] = R"( + struct GammaTransferParams { + G: f32; + A: f32; + B: f32; + C: f32; + D: f32; + E: f32; + F: f32; + padding: u32; + }; + + struct Uniforms { // offset align size + scale: vec2<f32>; // 0 8 8 + offset: vec2<f32>; // 8 8 8 + steps_mask: u32; // 16 4 4 + // implicit padding; // 20 12 + conversion_matrix: mat3x3<f32>; // 32 16 48 + gamma_decoding_params: GammaTransferParams; // 80 4 32 + gamma_encoding_params: GammaTransferParams; // 112 4 32 + gamma_decoding_for_dst_srgb_params: GammaTransferParams; // 144 4 32 + }; + + @binding(0) @group(0) var<uniform> uniforms : Uniforms; + + struct VertexOutputs { + @location(0) texcoords : vec2<f32>; + @builtin(position) position : vec4<f32>; + }; + + // Chromium uses unified equation to construct gamma decoding function + // and gamma encoding function. + // The logic is: + // if x < D + // linear = C * x + F + // nonlinear = pow(A * x + B, G) + E + // (https://source.chromium.org/chromium/chromium/src/+/main:ui/gfx/color_transform.cc;l=541) + // Expand the equation with sign() to make it handle all gamma conversions. + fn gamma_conversion(v: f32, params: GammaTransferParams) -> f32 { + // Linear part: C * x + F + if (abs(v) < params.D) { + return sign(v) * (params.C * abs(v) + params.F); + } + + // Gamma part: pow(A * x + B, G) + E + return sign(v) * (pow(params.A * abs(v) + params.B, params.G) + params.E); + } + + @stage(vertex) + fn vs_main( + @builtin(vertex_index) VertexIndex : u32 + ) -> VertexOutputs { + var texcoord = array<vec2<f32>, 3>( + vec2<f32>(-0.5, 0.0), + vec2<f32>( 1.5, 0.0), + vec2<f32>( 0.5, 2.0)); + + var output : VertexOutputs; + output.position = vec4<f32>((texcoord[VertexIndex] * 2.0 - vec2<f32>(1.0, 1.0)), 0.0, 1.0); + + // Y component of scale is calculated by the copySizeHeight / textureHeight. Only + // flipY case can get negative number. + var flipY = uniforms.scale.y < 0.0; + + // Texture coordinate takes top-left as origin point. We need to map the + // texture to triangle carefully. + if (flipY) { + // We need to get the mirror positions(mirrored based on y = 0.5) on flip cases. + // Adopt transform to src texture and then mapping it to triangle coord which + // do a +1 shift on Y dimension will help us got that mirror position perfectly. + output.texcoords = (texcoord[VertexIndex] * uniforms.scale + uniforms.offset) * + vec2<f32>(1.0, -1.0) + vec2<f32>(0.0, 1.0); + } else { + // For the normal case, we need to get the exact position. + // So mapping texture to triangle firstly then adopt the transform. + output.texcoords = (texcoord[VertexIndex] * + vec2<f32>(1.0, -1.0) + vec2<f32>(0.0, 1.0)) * + uniforms.scale + uniforms.offset; + } + + return output; + } + + @binding(1) @group(0) var mySampler: sampler; + @binding(2) @group(0) var myTexture: texture_2d<f32>; + + @stage(fragment) + fn fs_main( + @location(0) texcoord : vec2<f32> + ) -> @location(0) vec4<f32> { + // Clamp the texcoord and discard the out-of-bound pixels. + var clampedTexcoord = + clamp(texcoord, vec2<f32>(0.0, 0.0), vec2<f32>(1.0, 1.0)); + if (!all(clampedTexcoord == texcoord)) { + discard; + } + + // Swizzling of texture formats when sampling / rendering is handled by the + // hardware so we don't need special logic in this shader. This is covered by tests. + var color = textureSample(myTexture, mySampler, texcoord); + + let kUnpremultiplyStep = 0x01u; + let kDecodeToLinearStep = 0x02u; + let kConvertToDstGamutStep = 0x04u; + let kEncodeToGammaStep = 0x08u; + let kPremultiplyStep = 0x10u; + let kDecodeForSrgbDstFormat = 0x20u; + + // Unpremultiply step. Appling color space conversion op on premultiplied source texture + // also needs to unpremultiply first. + if (bool(uniforms.steps_mask & kUnpremultiplyStep)) { + if (color.a != 0.0) { + color = vec4<f32>(color.rgb / color.a, color.a); + } + } + + // Linearize the source color using the source color space’s + // transfer function if it is non-linear. + if (bool(uniforms.steps_mask & kDecodeToLinearStep)) { + color = vec4<f32>(gamma_conversion(color.r, uniforms.gamma_decoding_params), + gamma_conversion(color.g, uniforms.gamma_decoding_params), + gamma_conversion(color.b, uniforms.gamma_decoding_params), + color.a); + } + + // Convert unpremultiplied, linear source colors to the destination gamut by + // multiplying by a 3x3 matrix. Calculate transformFromXYZD50 * transformToXYZD50 + // in CPU side and upload the final result in uniforms. + if (bool(uniforms.steps_mask & kConvertToDstGamutStep)) { + color = vec4<f32>(uniforms.conversion_matrix * color.rgb, color.a); + } + + // Encode that color using the inverse of the destination color + // space’s transfer function if it is non-linear. + if (bool(uniforms.steps_mask & kEncodeToGammaStep)) { + color = vec4<f32>(gamma_conversion(color.r, uniforms.gamma_encoding_params), + gamma_conversion(color.g, uniforms.gamma_encoding_params), + gamma_conversion(color.b, uniforms.gamma_encoding_params), + color.a); + } + + // Premultiply step. + if (bool(uniforms.steps_mask & kPremultiplyStep)) { + color = vec4<f32>(color.rgb * color.a, color.a); + } + + // Decode for copying from non-srgb formats to srgb formats + if (bool(uniforms.steps_mask & kDecodeForSrgbDstFormat)) { + color = vec4<f32>(gamma_conversion(color.r, uniforms.gamma_decoding_for_dst_srgb_params), + gamma_conversion(color.g, uniforms.gamma_decoding_for_dst_srgb_params), + gamma_conversion(color.b, uniforms.gamma_decoding_for_dst_srgb_params), + color.a); + } + + return color; + } + )"; + + // Follow the same order of skcms_TransferFunction + // https://source.chromium.org/chromium/chromium/src/+/main:third_party/skia/include/third_party/skcms/skcms.h;l=46; + struct GammaTransferParams { + float G = 0.0; + float A = 0.0; + float B = 0.0; + float C = 0.0; + float D = 0.0; + float E = 0.0; + float F = 0.0; + uint32_t padding = 0; + }; + + struct Uniform { + float scaleX; + float scaleY; + float offsetX; + float offsetY; + uint32_t stepsMask = 0; + const std::array<uint32_t, 3> padding = {}; // 12 bytes padding + std::array<float, 12> conversionMatrix = {}; + GammaTransferParams gammaDecodingParams = {}; + GammaTransferParams gammaEncodingParams = {}; + GammaTransferParams gammaDecodingForDstSrgbParams = {}; + }; + static_assert(sizeof(Uniform) == 176); + + // TODO(crbug.com/dawn/856): Expand copyTextureForBrowser to support any + // non-depth, non-stencil, non-compressed texture format pair copy. + MaybeError ValidateCopyTextureFormatConversion(const wgpu::TextureFormat srcFormat, + const wgpu::TextureFormat dstFormat) { + switch (srcFormat) { + case wgpu::TextureFormat::BGRA8Unorm: + case wgpu::TextureFormat::RGBA8Unorm: + break; + default: + return DAWN_FORMAT_VALIDATION_ERROR( + "Source texture format (%s) is not supported.", srcFormat); + } + + switch (dstFormat) { + case wgpu::TextureFormat::R8Unorm: + case wgpu::TextureFormat::R16Float: + case wgpu::TextureFormat::R32Float: + case wgpu::TextureFormat::RG8Unorm: + case wgpu::TextureFormat::RG16Float: + case wgpu::TextureFormat::RG32Float: + case wgpu::TextureFormat::RGBA8Unorm: + case wgpu::TextureFormat::RGBA8UnormSrgb: + case wgpu::TextureFormat::BGRA8Unorm: + case wgpu::TextureFormat::BGRA8UnormSrgb: + case wgpu::TextureFormat::RGB10A2Unorm: + case wgpu::TextureFormat::RGBA16Float: + case wgpu::TextureFormat::RGBA32Float: + break; + default: + return DAWN_FORMAT_VALIDATION_ERROR( + "Destination texture format (%s) is not supported.", dstFormat); + } + + return {}; + } + + RenderPipelineBase* GetCachedPipeline(InternalPipelineStore* store, + wgpu::TextureFormat dstFormat) { + auto pipeline = store->copyTextureForBrowserPipelines.find(dstFormat); + if (pipeline != store->copyTextureForBrowserPipelines.end()) { + return pipeline->second.Get(); + } + return nullptr; + } + + ResultOrError<RenderPipelineBase*> GetOrCreateCopyTextureForBrowserPipeline( + DeviceBase* device, + wgpu::TextureFormat dstFormat) { + InternalPipelineStore* store = device->GetInternalPipelineStore(); + + if (GetCachedPipeline(store, dstFormat) == nullptr) { + // Create vertex shader module if not cached before. + if (store->copyTextureForBrowser == nullptr) { + DAWN_TRY_ASSIGN( + store->copyTextureForBrowser, + utils::CreateShaderModule(device, sCopyTextureForBrowserShader)); + } + + ShaderModuleBase* shaderModule = store->copyTextureForBrowser.Get(); + + // Prepare vertex stage. + VertexState vertex = {}; + vertex.module = shaderModule; + vertex.entryPoint = "vs_main"; + + // Prepare frgament stage. + FragmentState fragment = {}; + fragment.module = shaderModule; + fragment.entryPoint = "fs_main"; + + // Prepare color state. + ColorTargetState target = {}; + target.format = dstFormat; + + // Create RenderPipeline. + RenderPipelineDescriptor renderPipelineDesc = {}; + + // Generate the layout based on shader modules. + renderPipelineDesc.layout = nullptr; + + renderPipelineDesc.vertex = vertex; + renderPipelineDesc.fragment = &fragment; + + renderPipelineDesc.primitive.topology = wgpu::PrimitiveTopology::TriangleList; + + fragment.targetCount = 1; + fragment.targets = ⌖ + + Ref<RenderPipelineBase> pipeline; + DAWN_TRY_ASSIGN(pipeline, device->CreateRenderPipeline(&renderPipelineDesc)); + store->copyTextureForBrowserPipelines.insert({dstFormat, std::move(pipeline)}); + } + + return GetCachedPipeline(store, dstFormat); + } + } // anonymous namespace + + MaybeError ValidateCopyTextureForBrowser(DeviceBase* device, + const ImageCopyTexture* source, + const ImageCopyTexture* destination, + const Extent3D* copySize, + const CopyTextureForBrowserOptions* options) { + DAWN_TRY(device->ValidateObject(source->texture)); + DAWN_TRY(device->ValidateObject(destination->texture)); + + DAWN_INVALID_IF(source->texture->GetTextureState() == TextureBase::TextureState::Destroyed, + "Source texture %s is destroyed.", source->texture); + + DAWN_INVALID_IF( + destination->texture->GetTextureState() == TextureBase::TextureState::Destroyed, + "Destination texture %s is destroyed.", destination->texture); + + DAWN_TRY_CONTEXT(ValidateImageCopyTexture(device, *source, *copySize), + "validating the ImageCopyTexture for the source"); + DAWN_TRY_CONTEXT(ValidateImageCopyTexture(device, *destination, *copySize), + "validating the ImageCopyTexture for the destination"); + + DAWN_TRY_CONTEXT(ValidateTextureCopyRange(device, *source, *copySize), + "validating that the copy fits in the source"); + DAWN_TRY_CONTEXT(ValidateTextureCopyRange(device, *destination, *copySize), + "validating that the copy fits in the destination"); + + DAWN_TRY(ValidateTextureToTextureCopyCommonRestrictions(*source, *destination, *copySize)); + + DAWN_INVALID_IF(source->origin.z > 0, "Source has a non-zero z origin (%u).", + source->origin.z); + DAWN_INVALID_IF(copySize->depthOrArrayLayers > 1, + "Copy is for more than one array layer (%u)", copySize->depthOrArrayLayers); + + DAWN_INVALID_IF( + source->texture->GetSampleCount() > 1 || destination->texture->GetSampleCount() > 1, + "The source texture sample count (%u) or the destination texture sample count (%u) is " + "not 1.", + source->texture->GetSampleCount(), destination->texture->GetSampleCount()); + + DAWN_TRY(ValidateCanUseAs(source->texture, wgpu::TextureUsage::CopySrc, + UsageValidationMode::Default)); + DAWN_TRY(ValidateCanUseAs(source->texture, wgpu::TextureUsage::TextureBinding, + UsageValidationMode::Default)); + + DAWN_TRY(ValidateCanUseAs(destination->texture, wgpu::TextureUsage::CopyDst, + UsageValidationMode::Default)); + DAWN_TRY(ValidateCanUseAs(destination->texture, wgpu::TextureUsage::RenderAttachment, + UsageValidationMode::Default)); + + DAWN_TRY(ValidateCopyTextureFormatConversion(source->texture->GetFormat().format, + destination->texture->GetFormat().format)); + + DAWN_INVALID_IF(options->nextInChain != nullptr, "nextInChain must be nullptr"); + + DAWN_TRY(ValidateAlphaMode(options->srcAlphaMode)); + DAWN_TRY(ValidateAlphaMode(options->dstAlphaMode)); + + if (options->needsColorSpaceConversion) { + DAWN_INVALID_IF(options->srcTransferFunctionParameters == nullptr, + "srcTransferFunctionParameters is nullptr when doing color conversion"); + DAWN_INVALID_IF(options->conversionMatrix == nullptr, + "conversionMatrix is nullptr when doing color conversion"); + DAWN_INVALID_IF(options->dstTransferFunctionParameters == nullptr, + "dstTransferFunctionParameters is nullptr when doing color conversion"); + } + return {}; + } + + // Whether the format of dst texture of CopyTextureForBrowser() is srgb or non-srgb. + bool IsSrgbDstFormat(wgpu::TextureFormat format) { + switch (format) { + case wgpu::TextureFormat::RGBA8UnormSrgb: + case wgpu::TextureFormat::BGRA8UnormSrgb: + return true; + default: + return false; + } + } + + MaybeError DoCopyTextureForBrowser(DeviceBase* device, + const ImageCopyTexture* source, + const ImageCopyTexture* destination, + const Extent3D* copySize, + const CopyTextureForBrowserOptions* options) { + // TODO(crbug.com/dawn/856): In D3D12 and Vulkan, compatible texture format can directly + // copy to each other. This can be a potential fast path. + + // Noop copy + if (copySize->width == 0 || copySize->height == 0 || copySize->depthOrArrayLayers == 0) { + return {}; + } + + bool isSrgbDstFormat = IsSrgbDstFormat(destination->texture->GetFormat().format); + RenderPipelineBase* pipeline; + DAWN_TRY_ASSIGN(pipeline, GetOrCreateCopyTextureForBrowserPipeline( + device, destination->texture->GetFormat().format)); + + // Prepare bind group layout. + Ref<BindGroupLayoutBase> layout; + DAWN_TRY_ASSIGN(layout, pipeline->GetBindGroupLayout(0)); + + Extent3D srcTextureSize = source->texture->GetSize(); + + // Prepare binding 0 resource: uniform buffer. + Uniform uniformData = { + copySize->width / static_cast<float>(srcTextureSize.width), + copySize->height / static_cast<float>(srcTextureSize.height), // scale + source->origin.x / static_cast<float>(srcTextureSize.width), + source->origin.y / static_cast<float>(srcTextureSize.height) // offset + }; + + // Handle flipY. FlipY here means we flip the source texture firstly and then + // do copy. This helps on the case which source texture is flipped and the copy + // need to unpack the flip. + if (options->flipY) { + uniformData.scaleY *= -1.0; + uniformData.offsetY += copySize->height / static_cast<float>(srcTextureSize.height); + } + + uint32_t stepsMask = 0u; + + // Steps to do color space conversion + // From https://skia.org/docs/user/color/ + // - unpremultiply if the source color is premultiplied; Alpha is not involved in color + // management, and we need to divide it out if it’s multiplied in. + // - linearize the source color using the source color space’s transfer function + // - convert those unpremultiplied, linear source colors to XYZ D50 gamut by multiplying by + // a 3x3 matrix. + // - convert those XYZ D50 colors to the destination gamut by multiplying by a 3x3 matrix. + // - encode that color using the inverse of the destination color space’s transfer function. + // - premultiply by alpha if the destination is premultiplied. + // The reason to choose XYZ D50 as intermediate color space: + // From http://www.brucelindbloom.com/index.html?WorkingSpaceInfo.html + // "Since the Lab TIFF specification, the ICC profile specification and + // Adobe Photoshop all use a D50" + constexpr uint32_t kUnpremultiplyStep = 0x01; + constexpr uint32_t kDecodeToLinearStep = 0x02; + constexpr uint32_t kConvertToDstGamutStep = 0x04; + constexpr uint32_t kEncodeToGammaStep = 0x08; + constexpr uint32_t kPremultiplyStep = 0x10; + constexpr uint32_t kDecodeForSrgbDstFormat = 0x20; + + if (options->srcAlphaMode == wgpu::AlphaMode::Premultiplied) { + if (options->needsColorSpaceConversion || + options->srcAlphaMode != options->dstAlphaMode) { + stepsMask |= kUnpremultiplyStep; + } + } + + if (options->needsColorSpaceConversion) { + stepsMask |= kDecodeToLinearStep; + const float* decodingParams = options->srcTransferFunctionParameters; + + uniformData.gammaDecodingParams = { + decodingParams[0], decodingParams[1], decodingParams[2], decodingParams[3], + decodingParams[4], decodingParams[5], decodingParams[6]}; + + stepsMask |= kConvertToDstGamutStep; + const float* matrix = options->conversionMatrix; + uniformData.conversionMatrix = {{ + matrix[0], + matrix[1], + matrix[2], + 0.0, + matrix[3], + matrix[4], + matrix[5], + 0.0, + matrix[6], + matrix[7], + matrix[8], + 0.0, + }}; + + stepsMask |= kEncodeToGammaStep; + const float* encodingParams = options->dstTransferFunctionParameters; + + uniformData.gammaEncodingParams = { + encodingParams[0], encodingParams[1], encodingParams[2], encodingParams[3], + encodingParams[4], encodingParams[5], encodingParams[6]}; + } + + if (options->dstAlphaMode == wgpu::AlphaMode::Premultiplied) { + if (options->needsColorSpaceConversion || + options->srcAlphaMode != options->dstAlphaMode) { + stepsMask |= kPremultiplyStep; + } + } + + // Copy to *-srgb texture should keep the bytes exactly the same as copy + // to non-srgb texture. Add an extra decode-to-linear step so that after the + // sampler of *-srgb format texture applying encoding, the bytes keeps the same + // as non-srgb format texture. + // NOTE: CopyTextureForBrowser() doesn't need to accept *-srgb format texture as + // source input. But above operation also valid for *-srgb format texture input and + // non-srgb format dst texture. + // TODO(crbug.com/dawn/1195): Reinterpret to non-srgb texture view on *-srgb texture + // and use it as render attachment when possible. + // TODO(crbug.com/dawn/1195): Opt the condition for this extra step. It is possible to + // bypass this extra step in some cases. + if (isSrgbDstFormat) { + stepsMask |= kDecodeForSrgbDstFormat; + // Get gamma-linear conversion params from https://en.wikipedia.org/wiki/SRGB with some + // mathematics. Order: {G, A, B, C, D, E, F, } + uniformData.gammaDecodingForDstSrgbParams = { + 2.4, 1.0 / 1.055, 0.055 / 1.055, 1.0 / 12.92, 4.045e-02, 0.0, 0.0}; + } + + uniformData.stepsMask = stepsMask; + + Ref<BufferBase> uniformBuffer; + DAWN_TRY_ASSIGN( + uniformBuffer, + utils::CreateBufferFromData( + device, wgpu::BufferUsage::CopyDst | wgpu::BufferUsage::Uniform, {uniformData})); + + // Prepare binding 1 resource: sampler + // Use default configuration, filterMode set to Nearest for min and mag. + SamplerDescriptor samplerDesc = {}; + Ref<SamplerBase> sampler; + DAWN_TRY_ASSIGN(sampler, device->CreateSampler(&samplerDesc)); + + // Prepare binding 2 resource: sampled texture + TextureViewDescriptor srcTextureViewDesc = {}; + srcTextureViewDesc.baseMipLevel = source->mipLevel; + srcTextureViewDesc.mipLevelCount = 1; + srcTextureViewDesc.arrayLayerCount = 1; + Ref<TextureViewBase> srcTextureView; + DAWN_TRY_ASSIGN(srcTextureView, + device->CreateTextureView(source->texture, &srcTextureViewDesc)); + + // Create bind group after all binding entries are set. + Ref<BindGroupBase> bindGroup; + DAWN_TRY_ASSIGN(bindGroup, utils::MakeBindGroup( + device, layout, + {{0, uniformBuffer}, {1, sampler}, {2, srcTextureView}})); + + // Create command encoder. + Ref<CommandEncoder> encoder; + DAWN_TRY_ASSIGN(encoder, device->CreateCommandEncoder()); + + // Prepare dst texture view as color Attachment. + TextureViewDescriptor dstTextureViewDesc; + dstTextureViewDesc.baseMipLevel = destination->mipLevel; + dstTextureViewDesc.mipLevelCount = 1; + dstTextureViewDesc.baseArrayLayer = destination->origin.z; + dstTextureViewDesc.arrayLayerCount = 1; + Ref<TextureViewBase> dstView; + + DAWN_TRY_ASSIGN(dstView, + device->CreateTextureView(destination->texture, &dstTextureViewDesc)); + // Prepare render pass color attachment descriptor. + RenderPassColorAttachment colorAttachmentDesc; + + colorAttachmentDesc.view = dstView.Get(); + colorAttachmentDesc.loadOp = wgpu::LoadOp::Load; + colorAttachmentDesc.storeOp = wgpu::StoreOp::Store; + colorAttachmentDesc.clearValue = {0.0, 0.0, 0.0, 1.0}; + + // Create render pass. + RenderPassDescriptor renderPassDesc; + renderPassDesc.colorAttachmentCount = 1; + renderPassDesc.colorAttachments = &colorAttachmentDesc; + Ref<RenderPassEncoder> passEncoder = encoder->BeginRenderPass(&renderPassDesc); + + // Start pipeline and encode commands to complete + // the copy from src texture to dst texture with transformation. + passEncoder->APISetPipeline(pipeline); + passEncoder->APISetBindGroup(0, bindGroup.Get()); + passEncoder->APISetViewport(destination->origin.x, destination->origin.y, copySize->width, + copySize->height, 0.0, 1.0); + passEncoder->APIDraw(3); + passEncoder->APIEnd(); + + // Finsh encoding. + Ref<CommandBufferBase> commandBuffer; + DAWN_TRY_ASSIGN(commandBuffer, encoder->Finish()); + CommandBufferBase* submitCommandBuffer = commandBuffer.Get(); + + // Submit command buffer. + device->GetQueue()->APISubmit(1, &submitCommandBuffer); + return {}; + } + +} // namespace dawn::native
diff --git a/src/dawn/native/CopyTextureForBrowserHelper.h b/src/dawn/native/CopyTextureForBrowserHelper.h new file mode 100644 index 0000000..de82f5f --- /dev/null +++ b/src/dawn/native/CopyTextureForBrowserHelper.h
@@ -0,0 +1,41 @@ +// Copyright 2020 The Dawn Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#ifndef DAWNNATIVE_COPYTEXTUREFORBROWSERHELPER_H_ +#define DAWNNATIVE_COPYTEXTUREFORBROWSERHELPER_H_ + +#include "dawn/native/Error.h" +#include "dawn/native/ObjectBase.h" + +namespace dawn::native { + class DeviceBase; + struct Extent3D; + struct ImageCopyTexture; + struct CopyTextureForBrowserOptions; + + MaybeError ValidateCopyTextureForBrowser(DeviceBase* device, + const ImageCopyTexture* source, + const ImageCopyTexture* destination, + const Extent3D* copySize, + const CopyTextureForBrowserOptions* options); + + MaybeError DoCopyTextureForBrowser(DeviceBase* device, + const ImageCopyTexture* source, + const ImageCopyTexture* destination, + const Extent3D* copySize, + const CopyTextureForBrowserOptions* options); + +} // namespace dawn::native + +#endif // DAWNNATIVE_COPYTEXTUREFORBROWSERHELPER_H_
diff --git a/src/dawn/native/CreatePipelineAsyncTask.cpp b/src/dawn/native/CreatePipelineAsyncTask.cpp new file mode 100644 index 0000000..92a3bdf --- /dev/null +++ b/src/dawn/native/CreatePipelineAsyncTask.cpp
@@ -0,0 +1,206 @@ +// Copyright 2020 The Dawn Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include "dawn/native/CreatePipelineAsyncTask.h" + +#include "dawn/native/AsyncTask.h" +#include "dawn/native/ComputePipeline.h" +#include "dawn/native/Device.h" +#include "dawn/native/RenderPipeline.h" +#include "dawn/native/utils/WGPUHelpers.h" +#include "dawn/platform/DawnPlatform.h" +#include "dawn/platform/tracing/TraceEvent.h" + +namespace dawn::native { + + CreatePipelineAsyncCallbackTaskBase::CreatePipelineAsyncCallbackTaskBase( + std::string errorMessage, + void* userdata) + : mErrorMessage(errorMessage), mUserData(userdata) { + } + + CreateComputePipelineAsyncCallbackTask::CreateComputePipelineAsyncCallbackTask( + Ref<ComputePipelineBase> pipeline, + std::string errorMessage, + WGPUCreateComputePipelineAsyncCallback callback, + void* userdata) + : CreatePipelineAsyncCallbackTaskBase(errorMessage, userdata), + mPipeline(std::move(pipeline)), + mCreateComputePipelineAsyncCallback(callback) { + } + + void CreateComputePipelineAsyncCallbackTask::Finish() { + ASSERT(mCreateComputePipelineAsyncCallback != nullptr); + + if (mPipeline.Get() != nullptr) { + mCreateComputePipelineAsyncCallback(WGPUCreatePipelineAsyncStatus_Success, + ToAPI(mPipeline.Detach()), "", mUserData); + } else { + mCreateComputePipelineAsyncCallback(WGPUCreatePipelineAsyncStatus_Error, nullptr, + mErrorMessage.c_str(), mUserData); + } + } + + void CreateComputePipelineAsyncCallbackTask::HandleShutDown() { + ASSERT(mCreateComputePipelineAsyncCallback != nullptr); + + mCreateComputePipelineAsyncCallback(WGPUCreatePipelineAsyncStatus_DeviceDestroyed, nullptr, + "Device destroyed before callback", mUserData); + } + + void CreateComputePipelineAsyncCallbackTask::HandleDeviceLoss() { + ASSERT(mCreateComputePipelineAsyncCallback != nullptr); + + mCreateComputePipelineAsyncCallback(WGPUCreatePipelineAsyncStatus_DeviceLost, nullptr, + "Device lost before callback", mUserData); + } + + CreateRenderPipelineAsyncCallbackTask::CreateRenderPipelineAsyncCallbackTask( + Ref<RenderPipelineBase> pipeline, + std::string errorMessage, + WGPUCreateRenderPipelineAsyncCallback callback, + void* userdata) + : CreatePipelineAsyncCallbackTaskBase(errorMessage, userdata), + mPipeline(std::move(pipeline)), + mCreateRenderPipelineAsyncCallback(callback) { + } + + void CreateRenderPipelineAsyncCallbackTask::Finish() { + ASSERT(mCreateRenderPipelineAsyncCallback != nullptr); + + if (mPipeline.Get() != nullptr) { + mCreateRenderPipelineAsyncCallback(WGPUCreatePipelineAsyncStatus_Success, + ToAPI(mPipeline.Detach()), "", mUserData); + } else { + mCreateRenderPipelineAsyncCallback(WGPUCreatePipelineAsyncStatus_Error, nullptr, + mErrorMessage.c_str(), mUserData); + } + } + + void CreateRenderPipelineAsyncCallbackTask::HandleShutDown() { + ASSERT(mCreateRenderPipelineAsyncCallback != nullptr); + + mCreateRenderPipelineAsyncCallback(WGPUCreatePipelineAsyncStatus_DeviceDestroyed, nullptr, + "Device destroyed before callback", mUserData); + } + + void CreateRenderPipelineAsyncCallbackTask::HandleDeviceLoss() { + ASSERT(mCreateRenderPipelineAsyncCallback != nullptr); + + mCreateRenderPipelineAsyncCallback(WGPUCreatePipelineAsyncStatus_DeviceLost, nullptr, + "Device lost before callback", mUserData); + } + + CreateComputePipelineAsyncTask::CreateComputePipelineAsyncTask( + Ref<ComputePipelineBase> nonInitializedComputePipeline, + WGPUCreateComputePipelineAsyncCallback callback, + void* userdata) + : mComputePipeline(std::move(nonInitializedComputePipeline)), + mCallback(callback), + mUserdata(userdata) { + ASSERT(mComputePipeline != nullptr); + } + + void CreateComputePipelineAsyncTask::Run() { + const char* eventLabel = utils::GetLabelForTrace(mComputePipeline->GetLabel().c_str()); + + DeviceBase* device = mComputePipeline->GetDevice(); + TRACE_EVENT_FLOW_END1(device->GetPlatform(), General, + "CreateComputePipelineAsyncTask::RunAsync", this, "label", + eventLabel); + TRACE_EVENT1(device->GetPlatform(), General, "CreateComputePipelineAsyncTask::Run", "label", + eventLabel); + + MaybeError maybeError = mComputePipeline->Initialize(); + std::string errorMessage; + if (maybeError.IsError()) { + mComputePipeline = nullptr; + errorMessage = maybeError.AcquireError()->GetMessage(); + } + + device->AddComputePipelineAsyncCallbackTask(mComputePipeline, errorMessage, mCallback, + mUserdata); + } + + void CreateComputePipelineAsyncTask::RunAsync( + std::unique_ptr<CreateComputePipelineAsyncTask> task) { + DeviceBase* device = task->mComputePipeline->GetDevice(); + + const char* eventLabel = + utils::GetLabelForTrace(task->mComputePipeline->GetLabel().c_str()); + + // Using "taskPtr = std::move(task)" causes compilation error while it should be supported + // since C++14: + // https://docs.microsoft.com/en-us/cpp/cpp/lambda-expressions-in-cpp?view=msvc-160 + auto asyncTask = [taskPtr = task.release()] { + std::unique_ptr<CreateComputePipelineAsyncTask> innnerTaskPtr(taskPtr); + innnerTaskPtr->Run(); + }; + + TRACE_EVENT_FLOW_BEGIN1(device->GetPlatform(), General, + "CreateComputePipelineAsyncTask::RunAsync", task.get(), "label", + eventLabel); + device->GetAsyncTaskManager()->PostTask(std::move(asyncTask)); + } + + CreateRenderPipelineAsyncTask::CreateRenderPipelineAsyncTask( + Ref<RenderPipelineBase> nonInitializedRenderPipeline, + WGPUCreateRenderPipelineAsyncCallback callback, + void* userdata) + : mRenderPipeline(std::move(nonInitializedRenderPipeline)), + mCallback(callback), + mUserdata(userdata) { + ASSERT(mRenderPipeline != nullptr); + } + + void CreateRenderPipelineAsyncTask::Run() { + const char* eventLabel = utils::GetLabelForTrace(mRenderPipeline->GetLabel().c_str()); + + DeviceBase* device = mRenderPipeline->GetDevice(); + TRACE_EVENT_FLOW_END1(device->GetPlatform(), General, + "CreateRenderPipelineAsyncTask::RunAsync", this, "label", eventLabel); + TRACE_EVENT1(device->GetPlatform(), General, "CreateRenderPipelineAsyncTask::Run", "label", + eventLabel); + + MaybeError maybeError = mRenderPipeline->Initialize(); + std::string errorMessage; + if (maybeError.IsError()) { + mRenderPipeline = nullptr; + errorMessage = maybeError.AcquireError()->GetMessage(); + } + + device->AddRenderPipelineAsyncCallbackTask(mRenderPipeline, errorMessage, mCallback, + mUserdata); + } + + void CreateRenderPipelineAsyncTask::RunAsync( + std::unique_ptr<CreateRenderPipelineAsyncTask> task) { + DeviceBase* device = task->mRenderPipeline->GetDevice(); + + const char* eventLabel = utils::GetLabelForTrace(task->mRenderPipeline->GetLabel().c_str()); + + // Using "taskPtr = std::move(task)" causes compilation error while it should be supported + // since C++14: + // https://docs.microsoft.com/en-us/cpp/cpp/lambda-expressions-in-cpp?view=msvc-160 + auto asyncTask = [taskPtr = task.release()] { + std::unique_ptr<CreateRenderPipelineAsyncTask> innerTaskPtr(taskPtr); + innerTaskPtr->Run(); + }; + + TRACE_EVENT_FLOW_BEGIN1(device->GetPlatform(), General, + "CreateRenderPipelineAsyncTask::RunAsync", task.get(), "label", + eventLabel); + device->GetAsyncTaskManager()->PostTask(std::move(asyncTask)); + } +} // namespace dawn::native
diff --git a/src/dawn/native/CreatePipelineAsyncTask.h b/src/dawn/native/CreatePipelineAsyncTask.h new file mode 100644 index 0000000..4b936cf --- /dev/null +++ b/src/dawn/native/CreatePipelineAsyncTask.h
@@ -0,0 +1,108 @@ +// Copyright 2020 The Dawn Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#ifndef DAWNNATIVE_CREATEPIPELINEASYNCTASK_H_ +#define DAWNNATIVE_CREATEPIPELINEASYNCTASK_H_ + +#include "dawn/common/RefCounted.h" +#include "dawn/native/CallbackTaskManager.h" +#include "dawn/native/Error.h" +#include "dawn/webgpu.h" + +namespace dawn::native { + + class ComputePipelineBase; + class DeviceBase; + class PipelineLayoutBase; + class RenderPipelineBase; + class ShaderModuleBase; + struct FlatComputePipelineDescriptor; + + struct CreatePipelineAsyncCallbackTaskBase : CallbackTask { + CreatePipelineAsyncCallbackTaskBase(std::string errorMessage, void* userData); + + protected: + std::string mErrorMessage; + void* mUserData; + }; + + struct CreateComputePipelineAsyncCallbackTask : CreatePipelineAsyncCallbackTaskBase { + CreateComputePipelineAsyncCallbackTask(Ref<ComputePipelineBase> pipeline, + std::string errorMessage, + WGPUCreateComputePipelineAsyncCallback callback, + void* userdata); + + void Finish() override; + void HandleShutDown() final; + void HandleDeviceLoss() final; + + protected: + Ref<ComputePipelineBase> mPipeline; + WGPUCreateComputePipelineAsyncCallback mCreateComputePipelineAsyncCallback; + }; + + struct CreateRenderPipelineAsyncCallbackTask : CreatePipelineAsyncCallbackTaskBase { + CreateRenderPipelineAsyncCallbackTask(Ref<RenderPipelineBase> pipeline, + std::string errorMessage, + WGPUCreateRenderPipelineAsyncCallback callback, + void* userdata); + + void Finish() override; + void HandleShutDown() final; + void HandleDeviceLoss() final; + + protected: + Ref<RenderPipelineBase> mPipeline; + WGPUCreateRenderPipelineAsyncCallback mCreateRenderPipelineAsyncCallback; + }; + + // CreateComputePipelineAsyncTask defines all the inputs and outputs of + // CreateComputePipelineAsync() tasks, which are the same among all the backends. + class CreateComputePipelineAsyncTask { + public: + CreateComputePipelineAsyncTask(Ref<ComputePipelineBase> nonInitializedComputePipeline, + WGPUCreateComputePipelineAsyncCallback callback, + void* userdata); + + void Run(); + + static void RunAsync(std::unique_ptr<CreateComputePipelineAsyncTask> task); + + private: + Ref<ComputePipelineBase> mComputePipeline; + WGPUCreateComputePipelineAsyncCallback mCallback; + void* mUserdata; + }; + + // CreateRenderPipelineAsyncTask defines all the inputs and outputs of + // CreateRenderPipelineAsync() tasks, which are the same among all the backends. + class CreateRenderPipelineAsyncTask { + public: + CreateRenderPipelineAsyncTask(Ref<RenderPipelineBase> nonInitializedRenderPipeline, + WGPUCreateRenderPipelineAsyncCallback callback, + void* userdata); + + void Run(); + + static void RunAsync(std::unique_ptr<CreateRenderPipelineAsyncTask> task); + + private: + Ref<RenderPipelineBase> mRenderPipeline; + WGPUCreateRenderPipelineAsyncCallback mCallback; + void* mUserdata; + }; + +} // namespace dawn::native + +#endif // DAWNNATIVE_CREATEPIPELINEASYNCTASK_H_
diff --git a/src/dawn/native/DawnNative.cpp b/src/dawn/native/DawnNative.cpp new file mode 100644 index 0000000..ca46df8 --- /dev/null +++ b/src/dawn/native/DawnNative.cpp
@@ -0,0 +1,312 @@ +// Copyright 2018 The Dawn Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include "dawn/native/DawnNative.h" + +#include "dawn/common/Log.h" +#include "dawn/native/BindGroupLayout.h" +#include "dawn/native/Buffer.h" +#include "dawn/native/Device.h" +#include "dawn/native/Instance.h" +#include "dawn/native/Texture.h" +#include "dawn/platform/DawnPlatform.h" + +// Contains the entry-points into dawn_native + +namespace dawn::native { + + namespace { + struct ComboDeprecatedDawnDeviceDescriptor : DeviceDescriptor { + ComboDeprecatedDawnDeviceDescriptor(const DawnDeviceDescriptor* deviceDescriptor) { + dawn::WarningLog() << "DawnDeviceDescriptor is deprecated. Please use " + "WGPUDeviceDescriptor instead."; + + DeviceDescriptor* desc = this; + + if (deviceDescriptor != nullptr) { + desc->nextInChain = &mTogglesDesc; + mTogglesDesc.forceEnabledToggles = deviceDescriptor->forceEnabledToggles.data(); + mTogglesDesc.forceEnabledTogglesCount = + deviceDescriptor->forceEnabledToggles.size(); + mTogglesDesc.forceDisabledToggles = + deviceDescriptor->forceDisabledToggles.data(); + mTogglesDesc.forceDisabledTogglesCount = + deviceDescriptor->forceDisabledToggles.size(); + + desc->requiredLimits = + reinterpret_cast<const RequiredLimits*>(deviceDescriptor->requiredLimits); + + FeaturesInfo featuresInfo; + for (const char* featureStr : deviceDescriptor->requiredFeatures) { + mRequiredFeatures.push_back(featuresInfo.FeatureNameToAPIEnum(featureStr)); + } + desc->requiredFeatures = mRequiredFeatures.data(); + desc->requiredFeaturesCount = mRequiredFeatures.size(); + } + } + + DawnTogglesDeviceDescriptor mTogglesDesc = {}; + std::vector<wgpu::FeatureName> mRequiredFeatures = {}; + }; + } // namespace + + const DawnProcTable& GetProcsAutogen(); + + const DawnProcTable& GetProcs() { + return GetProcsAutogen(); + } + + std::vector<const char*> GetTogglesUsed(WGPUDevice device) { + return FromAPI(device)->GetTogglesUsed(); + } + + // Adapter + + Adapter::Adapter() = default; + + Adapter::Adapter(AdapterBase* impl) : mImpl(impl) { + if (mImpl != nullptr) { + mImpl->Reference(); + } + } + + Adapter::~Adapter() { + if (mImpl != nullptr) { + mImpl->Release(); + } + mImpl = nullptr; + } + + Adapter::Adapter(const Adapter& other) : Adapter(other.mImpl) { + } + + Adapter& Adapter::operator=(const Adapter& other) { + if (this != &other) { + if (mImpl) { + mImpl->Release(); + } + mImpl = other.mImpl; + if (mImpl) { + mImpl->Reference(); + } + } + return *this; + } + + void Adapter::GetProperties(wgpu::AdapterProperties* properties) const { + GetProperties(reinterpret_cast<WGPUAdapterProperties*>(properties)); + } + + void Adapter::GetProperties(WGPUAdapterProperties* properties) const { + mImpl->APIGetProperties(FromAPI(properties)); + } + + WGPUAdapter Adapter::Get() const { + return ToAPI(mImpl); + } + + std::vector<const char*> Adapter::GetSupportedFeatures() const { + FeaturesSet supportedFeaturesSet = mImpl->GetSupportedFeatures(); + return supportedFeaturesSet.GetEnabledFeatureNames(); + } + + WGPUDeviceProperties Adapter::GetAdapterProperties() const { + return mImpl->GetAdapterProperties(); + } + + bool Adapter::GetLimits(WGPUSupportedLimits* limits) const { + return mImpl->GetLimits(FromAPI(limits)); + } + + void Adapter::SetUseTieredLimits(bool useTieredLimits) { + mImpl->SetUseTieredLimits(useTieredLimits); + } + + bool Adapter::SupportsExternalImages() const { + return mImpl->SupportsExternalImages(); + } + + Adapter::operator bool() const { + return mImpl != nullptr; + } + + WGPUDevice Adapter::CreateDevice(const DawnDeviceDescriptor* deviceDescriptor) { + ComboDeprecatedDawnDeviceDescriptor desc(deviceDescriptor); + return ToAPI(mImpl->APICreateDevice(&desc)); + } + + WGPUDevice Adapter::CreateDevice(const wgpu::DeviceDescriptor* deviceDescriptor) { + return CreateDevice(reinterpret_cast<const WGPUDeviceDescriptor*>(deviceDescriptor)); + } + + WGPUDevice Adapter::CreateDevice(const WGPUDeviceDescriptor* deviceDescriptor) { + return ToAPI(mImpl->APICreateDevice(FromAPI(deviceDescriptor))); + } + + void Adapter::RequestDevice(const DawnDeviceDescriptor* descriptor, + WGPURequestDeviceCallback callback, + void* userdata) { + ComboDeprecatedDawnDeviceDescriptor desc(descriptor); + mImpl->APIRequestDevice(&desc, callback, userdata); + } + + void Adapter::RequestDevice(const wgpu::DeviceDescriptor* descriptor, + WGPURequestDeviceCallback callback, + void* userdata) { + mImpl->APIRequestDevice(reinterpret_cast<const DeviceDescriptor*>(descriptor), callback, + userdata); + } + + void Adapter::RequestDevice(const WGPUDeviceDescriptor* descriptor, + WGPURequestDeviceCallback callback, + void* userdata) { + mImpl->APIRequestDevice(reinterpret_cast<const DeviceDescriptor*>(descriptor), callback, + userdata); + } + + void Adapter::ResetInternalDeviceForTesting() { + mImpl->ResetInternalDeviceForTesting(); + } + + // AdapterDiscoverOptionsBase + + AdapterDiscoveryOptionsBase::AdapterDiscoveryOptionsBase(WGPUBackendType type) + : backendType(type) { + } + + // Instance + + Instance::Instance(const WGPUInstanceDescriptor* desc) + : mImpl(InstanceBase::Create(reinterpret_cast<const InstanceDescriptor*>(desc))) { + } + + Instance::~Instance() { + if (mImpl != nullptr) { + mImpl->Release(); + mImpl = nullptr; + } + } + + void Instance::DiscoverDefaultAdapters() { + mImpl->DiscoverDefaultAdapters(); + } + + bool Instance::DiscoverAdapters(const AdapterDiscoveryOptionsBase* options) { + return mImpl->DiscoverAdapters(options); + } + + std::vector<Adapter> Instance::GetAdapters() const { + // Adapters are owned by mImpl so it is safe to return non RAII pointers to them + std::vector<Adapter> adapters; + for (const Ref<AdapterBase>& adapter : mImpl->GetAdapters()) { + adapters.push_back({adapter.Get()}); + } + return adapters; + } + + const ToggleInfo* Instance::GetToggleInfo(const char* toggleName) { + return mImpl->GetToggleInfo(toggleName); + } + + const FeatureInfo* Instance::GetFeatureInfo(WGPUFeatureName feature) { + return mImpl->GetFeatureInfo(static_cast<wgpu::FeatureName>(feature)); + } + + void Instance::EnableBackendValidation(bool enableBackendValidation) { + if (enableBackendValidation) { + mImpl->SetBackendValidationLevel(BackendValidationLevel::Full); + } + } + + void Instance::SetBackendValidationLevel(BackendValidationLevel level) { + mImpl->SetBackendValidationLevel(level); + } + + void Instance::EnableBeginCaptureOnStartup(bool beginCaptureOnStartup) { + mImpl->EnableBeginCaptureOnStartup(beginCaptureOnStartup); + } + + void Instance::SetPlatform(dawn::platform::Platform* platform) { + mImpl->SetPlatform(platform); + } + + WGPUInstance Instance::Get() const { + return ToAPI(mImpl); + } + + size_t GetLazyClearCountForTesting(WGPUDevice device) { + return FromAPI(device)->GetLazyClearCountForTesting(); + } + + size_t GetDeprecationWarningCountForTesting(WGPUDevice device) { + return FromAPI(device)->GetDeprecationWarningCountForTesting(); + } + + bool IsTextureSubresourceInitialized(WGPUTexture texture, + uint32_t baseMipLevel, + uint32_t levelCount, + uint32_t baseArrayLayer, + uint32_t layerCount, + WGPUTextureAspect cAspect) { + TextureBase* textureBase = FromAPI(texture); + + Aspect aspect = + ConvertAspect(textureBase->GetFormat(), static_cast<wgpu::TextureAspect>(cAspect)); + SubresourceRange range(aspect, {baseArrayLayer, layerCount}, {baseMipLevel, levelCount}); + return textureBase->IsSubresourceContentInitialized(range); + } + + std::vector<const char*> GetProcMapNamesForTestingInternal(); + + std::vector<const char*> GetProcMapNamesForTesting() { + return GetProcMapNamesForTestingInternal(); + } + + DAWN_NATIVE_EXPORT bool DeviceTick(WGPUDevice device) { + return FromAPI(device)->APITick(); + } + + // ExternalImageDescriptor + + ExternalImageDescriptor::ExternalImageDescriptor(ExternalImageType type) : mType(type) { + } + + ExternalImageType ExternalImageDescriptor::GetType() const { + return mType; + } + + // ExternalImageExportInfo + + ExternalImageExportInfo::ExternalImageExportInfo(ExternalImageType type) : mType(type) { + } + + ExternalImageType ExternalImageExportInfo::GetType() const { + return mType; + } + + const char* GetObjectLabelForTesting(void* objectHandle) { + ApiObjectBase* object = reinterpret_cast<ApiObjectBase*>(objectHandle); + return object->GetLabel().c_str(); + } + + uint64_t GetAllocatedSizeForTesting(WGPUBuffer buffer) { + return FromAPI(buffer)->GetAllocatedSize(); + } + + bool BindGroupLayoutBindingsEqualForTesting(WGPUBindGroupLayout a, WGPUBindGroupLayout b) { + bool excludePipelineCompatibiltyToken = true; + return FromAPI(a)->IsLayoutEqual(FromAPI(b), excludePipelineCompatibiltyToken); + } + +} // namespace dawn::native
diff --git a/src/dawn/native/Device.cpp b/src/dawn/native/Device.cpp new file mode 100644 index 0000000..f79ead5 --- /dev/null +++ b/src/dawn/native/Device.cpp
@@ -0,0 +1,1806 @@ +// Copyright 2017 The Dawn Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include "dawn/native/Device.h" + +#include "dawn/common/Log.h" +#include "dawn/native/Adapter.h" +#include "dawn/native/AsyncTask.h" +#include "dawn/native/AttachmentState.h" +#include "dawn/native/BindGroup.h" +#include "dawn/native/BindGroupLayout.h" +#include "dawn/native/Buffer.h" +#include "dawn/native/ChainUtils_autogen.h" +#include "dawn/native/CommandBuffer.h" +#include "dawn/native/CommandEncoder.h" +#include "dawn/native/CompilationMessages.h" +#include "dawn/native/CreatePipelineAsyncTask.h" +#include "dawn/native/DynamicUploader.h" +#include "dawn/native/ErrorData.h" +#include "dawn/native/ErrorInjector.h" +#include "dawn/native/ErrorScope.h" +#include "dawn/native/ExternalTexture.h" +#include "dawn/native/Instance.h" +#include "dawn/native/InternalPipelineStore.h" +#include "dawn/native/ObjectType_autogen.h" +#include "dawn/native/PersistentCache.h" +#include "dawn/native/QuerySet.h" +#include "dawn/native/Queue.h" +#include "dawn/native/RenderBundleEncoder.h" +#include "dawn/native/RenderPipeline.h" +#include "dawn/native/Sampler.h" +#include "dawn/native/Surface.h" +#include "dawn/native/SwapChain.h" +#include "dawn/native/Texture.h" +#include "dawn/native/ValidationUtils_autogen.h" +#include "dawn/native/utils/WGPUHelpers.h" +#include "dawn/platform/DawnPlatform.h" +#include "dawn/platform/tracing/TraceEvent.h" + +#include <array> +#include <mutex> +#include <unordered_set> + +namespace dawn::native { + + // DeviceBase sub-structures + + // The caches are unordered_sets of pointers with special hash and compare functions + // to compare the value of the objects, instead of the pointers. + template <typename Object> + using ContentLessObjectCache = + std::unordered_set<Object*, typename Object::HashFunc, typename Object::EqualityFunc>; + + struct DeviceBase::Caches { + ~Caches() { + ASSERT(attachmentStates.empty()); + ASSERT(bindGroupLayouts.empty()); + ASSERT(computePipelines.empty()); + ASSERT(pipelineLayouts.empty()); + ASSERT(renderPipelines.empty()); + ASSERT(samplers.empty()); + ASSERT(shaderModules.empty()); + } + + ContentLessObjectCache<AttachmentStateBlueprint> attachmentStates; + ContentLessObjectCache<BindGroupLayoutBase> bindGroupLayouts; + ContentLessObjectCache<ComputePipelineBase> computePipelines; + ContentLessObjectCache<PipelineLayoutBase> pipelineLayouts; + ContentLessObjectCache<RenderPipelineBase> renderPipelines; + ContentLessObjectCache<SamplerBase> samplers; + ContentLessObjectCache<ShaderModuleBase> shaderModules; + }; + + struct DeviceBase::DeprecationWarnings { + std::unordered_set<std::string> emitted; + size_t count = 0; + }; + + namespace { + struct LoggingCallbackTask : CallbackTask { + public: + LoggingCallbackTask() = delete; + LoggingCallbackTask(wgpu::LoggingCallback loggingCallback, + WGPULoggingType loggingType, + const char* message, + void* userdata) + : mCallback(loggingCallback), + mLoggingType(loggingType), + mMessage(message), + mUserdata(userdata) { + // Since the Finish() will be called in uncertain future in which time the message + // may already disposed, we must keep a local copy in the CallbackTask. + } + + void Finish() override { + mCallback(mLoggingType, mMessage.c_str(), mUserdata); + } + + void HandleShutDown() override { + // Do the logging anyway + mCallback(mLoggingType, mMessage.c_str(), mUserdata); + } + + void HandleDeviceLoss() override { + mCallback(mLoggingType, mMessage.c_str(), mUserdata); + } + + private: + // As all deferred callback tasks will be triggered before modifying the registered + // callback or shutting down, we are ensured that callback function and userdata pointer + // stored in tasks is valid when triggered. + wgpu::LoggingCallback mCallback; + WGPULoggingType mLoggingType; + std::string mMessage; + void* mUserdata; + }; + + ResultOrError<Ref<PipelineLayoutBase>> + ValidateLayoutAndGetComputePipelineDescriptorWithDefaults( + DeviceBase* device, + const ComputePipelineDescriptor& descriptor, + ComputePipelineDescriptor* outDescriptor) { + Ref<PipelineLayoutBase> layoutRef; + *outDescriptor = descriptor; + + if (outDescriptor->layout == nullptr) { + DAWN_TRY_ASSIGN(layoutRef, PipelineLayoutBase::CreateDefault( + device, {{ + SingleShaderStage::Compute, + outDescriptor->compute.module, + outDescriptor->compute.entryPoint, + outDescriptor->compute.constantCount, + outDescriptor->compute.constants, + }})); + outDescriptor->layout = layoutRef.Get(); + } + + return layoutRef; + } + + ResultOrError<Ref<PipelineLayoutBase>> + ValidateLayoutAndGetRenderPipelineDescriptorWithDefaults( + DeviceBase* device, + const RenderPipelineDescriptor& descriptor, + RenderPipelineDescriptor* outDescriptor) { + Ref<PipelineLayoutBase> layoutRef; + *outDescriptor = descriptor; + + if (descriptor.layout == nullptr) { + // Ref will keep the pipeline layout alive until the end of the function where + // the pipeline will take another reference. + DAWN_TRY_ASSIGN(layoutRef, + PipelineLayoutBase::CreateDefault( + device, GetRenderStagesAndSetDummyShader(device, &descriptor))); + outDescriptor->layout = layoutRef.Get(); + } + + return layoutRef; + } + + } // anonymous namespace + + // DeviceBase + + DeviceBase::DeviceBase(AdapterBase* adapter, const DeviceDescriptor* descriptor) + : mInstance(adapter->GetInstance()), mAdapter(adapter), mNextPipelineCompatibilityToken(1) { + ASSERT(descriptor != nullptr); + + const DawnTogglesDeviceDescriptor* togglesDesc = nullptr; + FindInChain(descriptor->nextInChain, &togglesDesc); + if (togglesDesc != nullptr) { + ApplyToggleOverrides(togglesDesc); + } + ApplyFeatures(descriptor); + + const DawnCacheDeviceDescriptor* cacheDesc = nullptr; + FindInChain(descriptor->nextInChain, &cacheDesc); + if (cacheDesc != nullptr) { + mCacheIsolationKey = cacheDesc->isolationKey; + } + + if (descriptor->requiredLimits != nullptr) { + mLimits.v1 = ReifyDefaultLimits(descriptor->requiredLimits->limits); + } else { + GetDefaultLimits(&mLimits.v1); + } + + mFormatTable = BuildFormatTable(this); + SetDefaultToggles(); + } + + DeviceBase::DeviceBase() : mState(State::Alive) { + mCaches = std::make_unique<DeviceBase::Caches>(); + } + + DeviceBase::~DeviceBase() { + // We need to explicitly release the Queue before we complete the destructor so that the + // Queue does not get destroyed after the Device. + mQueue = nullptr; + } + + MaybeError DeviceBase::Initialize(QueueBase* defaultQueue) { + mQueue = AcquireRef(defaultQueue); + +#if defined(DAWN_ENABLE_ASSERTS) + mUncapturedErrorCallback = [](WGPUErrorType, char const*, void*) { + static bool calledOnce = false; + if (!calledOnce) { + calledOnce = true; + dawn::WarningLog() << "No Dawn device uncaptured error callback was set. This is " + "probably not intended. If you really want to ignore errors " + "and suppress this message, set the callback to null."; + } + }; + + mDeviceLostCallback = [](WGPUDeviceLostReason, char const*, void*) { + static bool calledOnce = false; + if (!calledOnce) { + calledOnce = true; + dawn::WarningLog() << "No Dawn device lost callback was set. This is probably not " + "intended. If you really want to ignore device lost " + "and suppress this message, set the callback to null."; + } + }; +#endif // DAWN_ENABLE_ASSERTS + + mCaches = std::make_unique<DeviceBase::Caches>(); + mErrorScopeStack = std::make_unique<ErrorScopeStack>(); + mDynamicUploader = std::make_unique<DynamicUploader>(this); + mCallbackTaskManager = std::make_unique<CallbackTaskManager>(); + mDeprecationWarnings = std::make_unique<DeprecationWarnings>(); + mInternalPipelineStore = std::make_unique<InternalPipelineStore>(this); + mPersistentCache = std::make_unique<PersistentCache>(this); + + ASSERT(GetPlatform() != nullptr); + mWorkerTaskPool = GetPlatform()->CreateWorkerTaskPool(); + mAsyncTaskManager = std::make_unique<AsyncTaskManager>(mWorkerTaskPool.get()); + + // Starting from now the backend can start doing reentrant calls so the device is marked as + // alive. + mState = State::Alive; + + DAWN_TRY_ASSIGN(mEmptyBindGroupLayout, CreateEmptyBindGroupLayout()); + + // If dummy fragment shader module is needed, initialize it + if (IsToggleEnabled(Toggle::UseDummyFragmentInVertexOnlyPipeline)) { + // The empty fragment shader, used as a work around for vertex-only render pipeline + constexpr char kEmptyFragmentShader[] = R"( + @stage(fragment) fn fs_empty_main() {} + )"; + ShaderModuleDescriptor descriptor; + ShaderModuleWGSLDescriptor wgslDesc; + wgslDesc.source = kEmptyFragmentShader; + descriptor.nextInChain = &wgslDesc; + + DAWN_TRY_ASSIGN(mInternalPipelineStore->dummyFragmentShader, + CreateShaderModule(&descriptor)); + } + + return {}; + } + + void DeviceBase::DestroyObjects() { + // List of object types in reverse "dependency" order so we can iterate and delete the + // objects safely starting at leaf objects. We define dependent here such that if B has + // a ref to A, then B depends on A. We therefore try to destroy B before destroying A. Note + // that this only considers the immediate frontend dependencies, while backend objects could + // add complications and extra dependencies. + // + // Note that AttachmentState is not an ApiObject so it cannot be eagerly destroyed. However, + // since AttachmentStates are cached by the device, objects that hold references to + // AttachmentStates should make sure to un-ref them in their Destroy operation so that we + // can destroy the frontend cache. + + // clang-format off + static constexpr std::array<ObjectType, 19> kObjectTypeDependencyOrder = { + ObjectType::ComputePassEncoder, + ObjectType::RenderPassEncoder, + ObjectType::RenderBundleEncoder, + ObjectType::RenderBundle, + ObjectType::CommandEncoder, + ObjectType::CommandBuffer, + ObjectType::RenderPipeline, + ObjectType::ComputePipeline, + ObjectType::PipelineLayout, + ObjectType::SwapChain, + ObjectType::BindGroup, + ObjectType::BindGroupLayout, + ObjectType::ShaderModule, + ObjectType::ExternalTexture, + ObjectType::TextureView, + ObjectType::Texture, + ObjectType::QuerySet, + ObjectType::Sampler, + ObjectType::Buffer, + }; + // clang-format on + + // We first move all objects out from the tracking list into a separate list so that we can + // avoid locking the same mutex twice. We can then iterate across the separate list to call + // the actual destroy function. + LinkedList<ApiObjectBase> objects; + for (ObjectType type : kObjectTypeDependencyOrder) { + ApiObjectList& objList = mObjectLists[type]; + const std::lock_guard<std::mutex> lock(objList.mutex); + objList.objects.MoveInto(&objects); + } + for (LinkNode<ApiObjectBase>* node : objects) { + node->value()->Destroy(); + } + } + + void DeviceBase::Destroy() { + // Skip if we are already destroyed. + if (mState == State::Destroyed) { + return; + } + + // Skip handling device facilities if they haven't even been created (or failed doing so) + if (mState != State::BeingCreated) { + // The device is being destroyed so it will be lost, call the application callback. + if (mDeviceLostCallback != nullptr) { + mDeviceLostCallback(WGPUDeviceLostReason_Destroyed, "Device was destroyed.", + mDeviceLostUserdata); + mDeviceLostCallback = nullptr; + } + + // Call all the callbacks immediately as the device is about to shut down. + // TODO(crbug.com/dawn/826): Cancel the tasks that are in flight if possible. + mAsyncTaskManager->WaitAllPendingTasks(); + auto callbackTasks = mCallbackTaskManager->AcquireCallbackTasks(); + for (std::unique_ptr<CallbackTask>& callbackTask : callbackTasks) { + callbackTask->HandleShutDown(); + } + } + + // Disconnect the device, depending on which state we are currently in. + switch (mState) { + case State::BeingCreated: + // The GPU timeline was never started so we don't have to wait. + break; + + case State::Alive: + // Alive is the only state which can have GPU work happening. Wait for all of it to + // complete before proceeding with destruction. + // Ignore errors so that we can continue with destruction + IgnoreErrors(WaitForIdleForDestruction()); + AssumeCommandsComplete(); + break; + + case State::BeingDisconnected: + // Getting disconnected is a transient state happening in a single API call so there + // is always an external reference keeping the Device alive, which means the + // destructor cannot run while BeingDisconnected. + UNREACHABLE(); + break; + + case State::Disconnected: + break; + + case State::Destroyed: + // If we are already destroyed we should've skipped this work entirely. + UNREACHABLE(); + break; + } + ASSERT(mCompletedSerial == mLastSubmittedSerial); + ASSERT(mFutureSerial <= mCompletedSerial); + + if (mState != State::BeingCreated) { + // The GPU timeline is finished. + // Finish destroying all objects owned by the device and tick the queue-related tasks + // since they should be complete. This must be done before DestroyImpl() it may + // relinquish resources that will be freed by backends in the DestroyImpl() call. + DestroyObjects(); + mQueue->Tick(GetCompletedCommandSerial()); + // Call TickImpl once last time to clean up resources + // Ignore errors so that we can continue with destruction + IgnoreErrors(TickImpl()); + } + + // At this point GPU operations are always finished, so we are in the disconnected state. + // Note that currently this state change is required because some of the backend + // implementations of DestroyImpl checks that we are disconnected before doing work. + mState = State::Disconnected; + + mDynamicUploader = nullptr; + mCallbackTaskManager = nullptr; + mAsyncTaskManager = nullptr; + mPersistentCache = nullptr; + mEmptyBindGroupLayout = nullptr; + mInternalPipelineStore = nullptr; + mExternalTextureDummyView = nullptr; + + AssumeCommandsComplete(); + + // Now that the GPU timeline is empty, destroy the backend device. + DestroyImpl(); + + mCaches = nullptr; + mState = State::Destroyed; + } + + void DeviceBase::APIDestroy() { + Destroy(); + } + + void DeviceBase::HandleError(InternalErrorType type, const char* message) { + if (type == InternalErrorType::DeviceLost) { + mState = State::Disconnected; + + // If the ErrorInjector is enabled, then the device loss might be fake and the device + // still be executing commands. Force a wait for idle in this case, with State being + // Disconnected so we can detect this case in WaitForIdleForDestruction. + if (ErrorInjectorEnabled()) { + IgnoreErrors(WaitForIdleForDestruction()); + } + + // A real device lost happened. Set the state to disconnected as the device cannot be + // used. Also tags all commands as completed since the device stopped running. + AssumeCommandsComplete(); + } else if (type == InternalErrorType::Internal) { + // If we receive an internal error, assume the backend can't recover and proceed with + // device destruction. We first wait for all previous commands to be completed so that + // backend objects can be freed immediately, before handling the loss. + + // Move away from the Alive state so that the application cannot use this device + // anymore. + // TODO(crbug.com/dawn/831): Do we need atomics for this to become visible to other + // threads in a multithreaded scenario? + mState = State::BeingDisconnected; + + // Ignore errors so that we can continue with destruction + // Assume all commands are complete after WaitForIdleForDestruction (because they were) + IgnoreErrors(WaitForIdleForDestruction()); + IgnoreErrors(TickImpl()); + AssumeCommandsComplete(); + ASSERT(mFutureSerial <= mCompletedSerial); + mState = State::Disconnected; + + // Now everything is as if the device was lost. + type = InternalErrorType::DeviceLost; + } + + if (type == InternalErrorType::DeviceLost) { + // The device was lost, call the application callback. + if (mDeviceLostCallback != nullptr) { + mDeviceLostCallback(WGPUDeviceLostReason_Undefined, message, mDeviceLostUserdata); + mDeviceLostCallback = nullptr; + } + + mQueue->HandleDeviceLoss(); + + // TODO(crbug.com/dawn/826): Cancel the tasks that are in flight if possible. + mAsyncTaskManager->WaitAllPendingTasks(); + auto callbackTasks = mCallbackTaskManager->AcquireCallbackTasks(); + for (std::unique_ptr<CallbackTask>& callbackTask : callbackTasks) { + callbackTask->HandleDeviceLoss(); + } + + // Still forward device loss errors to the error scopes so they all reject. + mErrorScopeStack->HandleError(ToWGPUErrorType(type), message); + } else { + // Pass the error to the error scope stack and call the uncaptured error callback + // if it isn't handled. DeviceLost is not handled here because it should be + // handled by the lost callback. + bool captured = mErrorScopeStack->HandleError(ToWGPUErrorType(type), message); + if (!captured && mUncapturedErrorCallback != nullptr) { + mUncapturedErrorCallback(static_cast<WGPUErrorType>(ToWGPUErrorType(type)), message, + mUncapturedErrorUserdata); + } + } + } + + void DeviceBase::ConsumeError(std::unique_ptr<ErrorData> error) { + ASSERT(error != nullptr); + HandleError(error->GetType(), error->GetFormattedMessage().c_str()); + } + + void DeviceBase::APISetLoggingCallback(wgpu::LoggingCallback callback, void* userdata) { + // The registered callback function and userdata pointer are stored and used by deferred + // callback tasks, and after setting a different callback (especially in the case of + // resetting) the resources pointed by such pointer may be freed. Flush all deferred + // callback tasks to guarantee we are never going to use the previous callback after + // this call. + if (IsLost()) { + return; + } + FlushCallbackTaskQueue(); + mLoggingCallback = callback; + mLoggingUserdata = userdata; + } + + void DeviceBase::APISetUncapturedErrorCallback(wgpu::ErrorCallback callback, void* userdata) { + // The registered callback function and userdata pointer are stored and used by deferred + // callback tasks, and after setting a different callback (especially in the case of + // resetting) the resources pointed by such pointer may be freed. Flush all deferred + // callback tasks to guarantee we are never going to use the previous callback after + // this call. + if (IsLost()) { + return; + } + FlushCallbackTaskQueue(); + mUncapturedErrorCallback = callback; + mUncapturedErrorUserdata = userdata; + } + + void DeviceBase::APISetDeviceLostCallback(wgpu::DeviceLostCallback callback, void* userdata) { + // The registered callback function and userdata pointer are stored and used by deferred + // callback tasks, and after setting a different callback (especially in the case of + // resetting) the resources pointed by such pointer may be freed. Flush all deferred + // callback tasks to guarantee we are never going to use the previous callback after + // this call. + if (IsLost()) { + return; + } + FlushCallbackTaskQueue(); + mDeviceLostCallback = callback; + mDeviceLostUserdata = userdata; + } + + void DeviceBase::APIPushErrorScope(wgpu::ErrorFilter filter) { + if (ConsumedError(ValidateErrorFilter(filter))) { + return; + } + mErrorScopeStack->Push(filter); + } + + bool DeviceBase::APIPopErrorScope(wgpu::ErrorCallback callback, void* userdata) { + // TODO(crbug.com/dawn/1324) Remove return and make function void when users are updated. + bool returnValue = true; + if (callback == nullptr) { + static wgpu::ErrorCallback defaultCallback = [](WGPUErrorType, char const*, void*) {}; + callback = defaultCallback; + } + // TODO(crbug.com/dawn/1122): Call callbacks only on wgpuInstanceProcessEvents + if (IsLost()) { + callback(WGPUErrorType_DeviceLost, "GPU device disconnected", userdata); + return returnValue; + } + if (mErrorScopeStack->Empty()) { + callback(WGPUErrorType_Unknown, "No error scopes to pop", userdata); + return returnValue; + } + ErrorScope scope = mErrorScopeStack->Pop(); + callback(static_cast<WGPUErrorType>(scope.GetErrorType()), scope.GetErrorMessage(), + userdata); + return returnValue; + } + + PersistentCache* DeviceBase::GetPersistentCache() { + ASSERT(mPersistentCache.get() != nullptr); + return mPersistentCache.get(); + } + + MaybeError DeviceBase::ValidateObject(const ApiObjectBase* object) const { + ASSERT(object != nullptr); + DAWN_INVALID_IF(object->GetDevice() != this, + "%s is associated with %s, and cannot be used with %s.", object, + object->GetDevice(), this); + + // TODO(dawn:563): Preserve labels for error objects. + DAWN_INVALID_IF(object->IsError(), "%s is invalid.", object); + + return {}; + } + + MaybeError DeviceBase::ValidateIsAlive() const { + DAWN_INVALID_IF(mState != State::Alive, "%s is lost.", this); + return {}; + } + + void DeviceBase::APILoseForTesting() { + if (mState != State::Alive) { + return; + } + + HandleError(InternalErrorType::Internal, "Device lost for testing"); + } + + DeviceBase::State DeviceBase::GetState() const { + return mState; + } + + bool DeviceBase::IsLost() const { + ASSERT(mState != State::BeingCreated); + return mState != State::Alive; + } + + void DeviceBase::TrackObject(ApiObjectBase* object) { + ApiObjectList& objectList = mObjectLists[object->GetType()]; + std::lock_guard<std::mutex> lock(objectList.mutex); + object->InsertBefore(objectList.objects.head()); + } + + std::mutex* DeviceBase::GetObjectListMutex(ObjectType type) { + return &mObjectLists[type].mutex; + } + + AdapterBase* DeviceBase::GetAdapter() const { + return mAdapter; + } + + dawn::platform::Platform* DeviceBase::GetPlatform() const { + return GetAdapter()->GetInstance()->GetPlatform(); + } + + ExecutionSerial DeviceBase::GetCompletedCommandSerial() const { + return mCompletedSerial; + } + + ExecutionSerial DeviceBase::GetLastSubmittedCommandSerial() const { + return mLastSubmittedSerial; + } + + ExecutionSerial DeviceBase::GetFutureSerial() const { + return mFutureSerial; + } + + InternalPipelineStore* DeviceBase::GetInternalPipelineStore() { + return mInternalPipelineStore.get(); + } + + void DeviceBase::IncrementLastSubmittedCommandSerial() { + mLastSubmittedSerial++; + } + + void DeviceBase::AssumeCommandsComplete() { + ExecutionSerial maxSerial = + ExecutionSerial(std::max(mLastSubmittedSerial + ExecutionSerial(1), mFutureSerial)); + mLastSubmittedSerial = maxSerial; + mCompletedSerial = maxSerial; + } + + bool DeviceBase::IsDeviceIdle() { + if (mAsyncTaskManager->HasPendingTasks()) { + return false; + } + + ExecutionSerial maxSerial = std::max(mLastSubmittedSerial, mFutureSerial); + if (mCompletedSerial == maxSerial) { + return true; + } + return false; + } + + ExecutionSerial DeviceBase::GetPendingCommandSerial() const { + return mLastSubmittedSerial + ExecutionSerial(1); + } + + void DeviceBase::AddFutureSerial(ExecutionSerial serial) { + if (serial > mFutureSerial) { + mFutureSerial = serial; + } + } + + MaybeError DeviceBase::CheckPassedSerials() { + ExecutionSerial completedSerial; + DAWN_TRY_ASSIGN(completedSerial, CheckAndUpdateCompletedSerials()); + + ASSERT(completedSerial <= mLastSubmittedSerial); + // completedSerial should not be less than mCompletedSerial unless it is 0. + // It can be 0 when there's no fences to check. + ASSERT(completedSerial >= mCompletedSerial || completedSerial == ExecutionSerial(0)); + + if (completedSerial > mCompletedSerial) { + mCompletedSerial = completedSerial; + } + + return {}; + } + + ResultOrError<const Format*> DeviceBase::GetInternalFormat(wgpu::TextureFormat format) const { + FormatIndex index = ComputeFormatIndex(format); + DAWN_INVALID_IF(index >= mFormatTable.size(), "Unknown texture format %s.", format); + + const Format* internalFormat = &mFormatTable[index]; + DAWN_INVALID_IF(!internalFormat->isSupported, "Unsupported texture format %s.", format); + + return internalFormat; + } + + const Format& DeviceBase::GetValidInternalFormat(wgpu::TextureFormat format) const { + FormatIndex index = ComputeFormatIndex(format); + ASSERT(index < mFormatTable.size()); + ASSERT(mFormatTable[index].isSupported); + return mFormatTable[index]; + } + + const Format& DeviceBase::GetValidInternalFormat(FormatIndex index) const { + ASSERT(index < mFormatTable.size()); + ASSERT(mFormatTable[index].isSupported); + return mFormatTable[index]; + } + + ResultOrError<Ref<BindGroupLayoutBase>> DeviceBase::GetOrCreateBindGroupLayout( + const BindGroupLayoutDescriptor* descriptor, + PipelineCompatibilityToken pipelineCompatibilityToken) { + BindGroupLayoutBase blueprint(this, descriptor, pipelineCompatibilityToken, + ApiObjectBase::kUntrackedByDevice); + + const size_t blueprintHash = blueprint.ComputeContentHash(); + blueprint.SetContentHash(blueprintHash); + + Ref<BindGroupLayoutBase> result; + auto iter = mCaches->bindGroupLayouts.find(&blueprint); + if (iter != mCaches->bindGroupLayouts.end()) { + result = *iter; + } else { + DAWN_TRY_ASSIGN(result, + CreateBindGroupLayoutImpl(descriptor, pipelineCompatibilityToken)); + result->SetIsCachedReference(); + result->SetContentHash(blueprintHash); + mCaches->bindGroupLayouts.insert(result.Get()); + } + + return std::move(result); + } + + void DeviceBase::UncacheBindGroupLayout(BindGroupLayoutBase* obj) { + ASSERT(obj->IsCachedReference()); + size_t removedCount = mCaches->bindGroupLayouts.erase(obj); + ASSERT(removedCount == 1); + } + + // Private function used at initialization + ResultOrError<Ref<BindGroupLayoutBase>> DeviceBase::CreateEmptyBindGroupLayout() { + BindGroupLayoutDescriptor desc = {}; + desc.entryCount = 0; + desc.entries = nullptr; + + return GetOrCreateBindGroupLayout(&desc); + } + + BindGroupLayoutBase* DeviceBase::GetEmptyBindGroupLayout() { + ASSERT(mEmptyBindGroupLayout != nullptr); + return mEmptyBindGroupLayout.Get(); + } + + Ref<ComputePipelineBase> DeviceBase::GetCachedComputePipeline( + ComputePipelineBase* uninitializedComputePipeline) { + Ref<ComputePipelineBase> cachedPipeline; + auto iter = mCaches->computePipelines.find(uninitializedComputePipeline); + if (iter != mCaches->computePipelines.end()) { + cachedPipeline = *iter; + } + + return cachedPipeline; + } + + Ref<RenderPipelineBase> DeviceBase::GetCachedRenderPipeline( + RenderPipelineBase* uninitializedRenderPipeline) { + Ref<RenderPipelineBase> cachedPipeline; + auto iter = mCaches->renderPipelines.find(uninitializedRenderPipeline); + if (iter != mCaches->renderPipelines.end()) { + cachedPipeline = *iter; + } + return cachedPipeline; + } + + Ref<ComputePipelineBase> DeviceBase::AddOrGetCachedComputePipeline( + Ref<ComputePipelineBase> computePipeline) { + auto [cachedPipeline, inserted] = mCaches->computePipelines.insert(computePipeline.Get()); + if (inserted) { + computePipeline->SetIsCachedReference(); + return computePipeline; + } else { + return *cachedPipeline; + } + } + + Ref<RenderPipelineBase> DeviceBase::AddOrGetCachedRenderPipeline( + Ref<RenderPipelineBase> renderPipeline) { + auto [cachedPipeline, inserted] = mCaches->renderPipelines.insert(renderPipeline.Get()); + if (inserted) { + renderPipeline->SetIsCachedReference(); + return renderPipeline; + } else { + return *cachedPipeline; + } + } + + void DeviceBase::UncacheComputePipeline(ComputePipelineBase* obj) { + ASSERT(obj->IsCachedReference()); + size_t removedCount = mCaches->computePipelines.erase(obj); + ASSERT(removedCount == 1); + } + + ResultOrError<Ref<TextureViewBase>> + DeviceBase::GetOrCreateDummyTextureViewForExternalTexture() { + if (!mExternalTextureDummyView.Get()) { + Ref<TextureBase> externalTextureDummy; + TextureDescriptor textureDesc; + textureDesc.dimension = wgpu::TextureDimension::e2D; + textureDesc.format = wgpu::TextureFormat::RGBA8Unorm; + textureDesc.label = "Dawn_External_Texture_Dummy_Texture"; + textureDesc.size = {1, 1, 1}; + textureDesc.usage = wgpu::TextureUsage::TextureBinding; + + DAWN_TRY_ASSIGN(externalTextureDummy, CreateTexture(&textureDesc)); + + TextureViewDescriptor textureViewDesc; + textureViewDesc.arrayLayerCount = 1; + textureViewDesc.aspect = wgpu::TextureAspect::All; + textureViewDesc.baseArrayLayer = 0; + textureViewDesc.dimension = wgpu::TextureViewDimension::e2D; + textureViewDesc.format = wgpu::TextureFormat::RGBA8Unorm; + textureViewDesc.label = "Dawn_External_Texture_Dummy_Texture_View"; + textureViewDesc.mipLevelCount = 1; + + DAWN_TRY_ASSIGN(mExternalTextureDummyView, + CreateTextureView(externalTextureDummy.Get(), &textureViewDesc)); + } + + return mExternalTextureDummyView; + } + + ResultOrError<Ref<PipelineLayoutBase>> DeviceBase::GetOrCreatePipelineLayout( + const PipelineLayoutDescriptor* descriptor) { + PipelineLayoutBase blueprint(this, descriptor, ApiObjectBase::kUntrackedByDevice); + + const size_t blueprintHash = blueprint.ComputeContentHash(); + blueprint.SetContentHash(blueprintHash); + + Ref<PipelineLayoutBase> result; + auto iter = mCaches->pipelineLayouts.find(&blueprint); + if (iter != mCaches->pipelineLayouts.end()) { + result = *iter; + } else { + DAWN_TRY_ASSIGN(result, CreatePipelineLayoutImpl(descriptor)); + result->SetIsCachedReference(); + result->SetContentHash(blueprintHash); + mCaches->pipelineLayouts.insert(result.Get()); + } + + return std::move(result); + } + + void DeviceBase::UncachePipelineLayout(PipelineLayoutBase* obj) { + ASSERT(obj->IsCachedReference()); + size_t removedCount = mCaches->pipelineLayouts.erase(obj); + ASSERT(removedCount == 1); + } + + void DeviceBase::UncacheRenderPipeline(RenderPipelineBase* obj) { + ASSERT(obj->IsCachedReference()); + size_t removedCount = mCaches->renderPipelines.erase(obj); + ASSERT(removedCount == 1); + } + + ResultOrError<Ref<SamplerBase>> DeviceBase::GetOrCreateSampler( + const SamplerDescriptor* descriptor) { + SamplerBase blueprint(this, descriptor, ApiObjectBase::kUntrackedByDevice); + + const size_t blueprintHash = blueprint.ComputeContentHash(); + blueprint.SetContentHash(blueprintHash); + + Ref<SamplerBase> result; + auto iter = mCaches->samplers.find(&blueprint); + if (iter != mCaches->samplers.end()) { + result = *iter; + } else { + DAWN_TRY_ASSIGN(result, CreateSamplerImpl(descriptor)); + result->SetIsCachedReference(); + result->SetContentHash(blueprintHash); + mCaches->samplers.insert(result.Get()); + } + + return std::move(result); + } + + void DeviceBase::UncacheSampler(SamplerBase* obj) { + ASSERT(obj->IsCachedReference()); + size_t removedCount = mCaches->samplers.erase(obj); + ASSERT(removedCount == 1); + } + + ResultOrError<Ref<ShaderModuleBase>> DeviceBase::GetOrCreateShaderModule( + const ShaderModuleDescriptor* descriptor, + ShaderModuleParseResult* parseResult, + OwnedCompilationMessages* compilationMessages) { + ASSERT(parseResult != nullptr); + + ShaderModuleBase blueprint(this, descriptor, ApiObjectBase::kUntrackedByDevice); + + const size_t blueprintHash = blueprint.ComputeContentHash(); + blueprint.SetContentHash(blueprintHash); + + Ref<ShaderModuleBase> result; + auto iter = mCaches->shaderModules.find(&blueprint); + if (iter != mCaches->shaderModules.end()) { + result = *iter; + } else { + if (!parseResult->HasParsedShader()) { + // We skip the parse on creation if validation isn't enabled which let's us quickly + // lookup in the cache without validating and parsing. We need the parsed module + // now, so call validate. Most of |ValidateShaderModuleDescriptor| is parsing, but + // we can consider splitting it if additional validation is added. + ASSERT(!IsValidationEnabled()); + DAWN_TRY(ValidateShaderModuleDescriptor(this, descriptor, parseResult, + compilationMessages)); + } + DAWN_TRY_ASSIGN(result, CreateShaderModuleImpl(descriptor, parseResult)); + result->SetIsCachedReference(); + result->SetContentHash(blueprintHash); + mCaches->shaderModules.insert(result.Get()); + } + + return std::move(result); + } + + void DeviceBase::UncacheShaderModule(ShaderModuleBase* obj) { + ASSERT(obj->IsCachedReference()); + size_t removedCount = mCaches->shaderModules.erase(obj); + ASSERT(removedCount == 1); + } + + Ref<AttachmentState> DeviceBase::GetOrCreateAttachmentState( + AttachmentStateBlueprint* blueprint) { + auto iter = mCaches->attachmentStates.find(blueprint); + if (iter != mCaches->attachmentStates.end()) { + return static_cast<AttachmentState*>(*iter); + } + + Ref<AttachmentState> attachmentState = AcquireRef(new AttachmentState(this, *blueprint)); + attachmentState->SetIsCachedReference(); + attachmentState->SetContentHash(attachmentState->ComputeContentHash()); + mCaches->attachmentStates.insert(attachmentState.Get()); + return attachmentState; + } + + Ref<AttachmentState> DeviceBase::GetOrCreateAttachmentState( + const RenderBundleEncoderDescriptor* descriptor) { + AttachmentStateBlueprint blueprint(descriptor); + return GetOrCreateAttachmentState(&blueprint); + } + + Ref<AttachmentState> DeviceBase::GetOrCreateAttachmentState( + const RenderPipelineDescriptor* descriptor) { + AttachmentStateBlueprint blueprint(descriptor); + return GetOrCreateAttachmentState(&blueprint); + } + + Ref<AttachmentState> DeviceBase::GetOrCreateAttachmentState( + const RenderPassDescriptor* descriptor) { + AttachmentStateBlueprint blueprint(descriptor); + return GetOrCreateAttachmentState(&blueprint); + } + + void DeviceBase::UncacheAttachmentState(AttachmentState* obj) { + ASSERT(obj->IsCachedReference()); + size_t removedCount = mCaches->attachmentStates.erase(obj); + ASSERT(removedCount == 1); + } + + // Object creation API methods + + BindGroupBase* DeviceBase::APICreateBindGroup(const BindGroupDescriptor* descriptor) { + Ref<BindGroupBase> result; + if (ConsumedError(CreateBindGroup(descriptor), &result, "calling %s.CreateBindGroup(%s).", + this, descriptor)) { + return BindGroupBase::MakeError(this); + } + return result.Detach(); + } + BindGroupLayoutBase* DeviceBase::APICreateBindGroupLayout( + const BindGroupLayoutDescriptor* descriptor) { + Ref<BindGroupLayoutBase> result; + if (ConsumedError(CreateBindGroupLayout(descriptor), &result, + "calling %s.CreateBindGroupLayout(%s).", this, descriptor)) { + return BindGroupLayoutBase::MakeError(this); + } + return result.Detach(); + } + BufferBase* DeviceBase::APICreateBuffer(const BufferDescriptor* descriptor) { + Ref<BufferBase> result = nullptr; + if (ConsumedError(CreateBuffer(descriptor), &result, "calling %s.CreateBuffer(%s).", this, + descriptor)) { + ASSERT(result == nullptr); + return BufferBase::MakeError(this, descriptor); + } + return result.Detach(); + } + CommandEncoder* DeviceBase::APICreateCommandEncoder( + const CommandEncoderDescriptor* descriptor) { + Ref<CommandEncoder> result; + if (ConsumedError(CreateCommandEncoder(descriptor), &result, + "calling %s.CreateCommandEncoder(%s).", this, descriptor)) { + return CommandEncoder::MakeError(this); + } + return result.Detach(); + } + ComputePipelineBase* DeviceBase::APICreateComputePipeline( + const ComputePipelineDescriptor* descriptor) { + TRACE_EVENT1(GetPlatform(), General, "DeviceBase::APICreateComputePipeline", "label", + utils::GetLabelForTrace(descriptor->label)); + + Ref<ComputePipelineBase> result; + if (ConsumedError(CreateComputePipeline(descriptor), &result, + "calling %s.CreateComputePipeline(%s).", this, descriptor)) { + return ComputePipelineBase::MakeError(this); + } + return result.Detach(); + } + void DeviceBase::APICreateComputePipelineAsync(const ComputePipelineDescriptor* descriptor, + WGPUCreateComputePipelineAsyncCallback callback, + void* userdata) { + TRACE_EVENT1(GetPlatform(), General, "DeviceBase::APICreateComputePipelineAsync", "label", + utils::GetLabelForTrace(descriptor->label)); + + MaybeError maybeResult = CreateComputePipelineAsync(descriptor, callback, userdata); + + // Call the callback directly when a validation error has been found in the front-end + // validations. If there is no error, then CreateComputePipelineAsync will call the + // callback. + if (maybeResult.IsError()) { + std::unique_ptr<ErrorData> error = maybeResult.AcquireError(); + // TODO(crbug.com/dawn/1122): Call callbacks only on wgpuInstanceProcessEvents + callback(WGPUCreatePipelineAsyncStatus_Error, nullptr, error->GetMessage().c_str(), + userdata); + } + } + PipelineLayoutBase* DeviceBase::APICreatePipelineLayout( + const PipelineLayoutDescriptor* descriptor) { + Ref<PipelineLayoutBase> result; + if (ConsumedError(CreatePipelineLayout(descriptor), &result, + "calling %s.CreatePipelineLayout(%s).", this, descriptor)) { + return PipelineLayoutBase::MakeError(this); + } + return result.Detach(); + } + QuerySetBase* DeviceBase::APICreateQuerySet(const QuerySetDescriptor* descriptor) { + Ref<QuerySetBase> result; + if (ConsumedError(CreateQuerySet(descriptor), &result, "calling %s.CreateQuerySet(%s).", + this, descriptor)) { + return QuerySetBase::MakeError(this); + } + return result.Detach(); + } + SamplerBase* DeviceBase::APICreateSampler(const SamplerDescriptor* descriptor) { + Ref<SamplerBase> result; + if (ConsumedError(CreateSampler(descriptor), &result, "calling %s.CreateSampler(%s).", this, + descriptor)) { + return SamplerBase::MakeError(this); + } + return result.Detach(); + } + void DeviceBase::APICreateRenderPipelineAsync(const RenderPipelineDescriptor* descriptor, + WGPUCreateRenderPipelineAsyncCallback callback, + void* userdata) { + TRACE_EVENT1(GetPlatform(), General, "DeviceBase::APICreateRenderPipelineAsync", "label", + utils::GetLabelForTrace(descriptor->label)); + // TODO(dawn:563): Add validation error context. + MaybeError maybeResult = CreateRenderPipelineAsync(descriptor, callback, userdata); + + // Call the callback directly when a validation error has been found in the front-end + // validations. If there is no error, then CreateRenderPipelineAsync will call the + // callback. + if (maybeResult.IsError()) { + std::unique_ptr<ErrorData> error = maybeResult.AcquireError(); + // TODO(crbug.com/dawn/1122): Call callbacks only on wgpuInstanceProcessEvents + callback(WGPUCreatePipelineAsyncStatus_Error, nullptr, error->GetMessage().c_str(), + userdata); + } + } + RenderBundleEncoder* DeviceBase::APICreateRenderBundleEncoder( + const RenderBundleEncoderDescriptor* descriptor) { + Ref<RenderBundleEncoder> result; + if (ConsumedError(CreateRenderBundleEncoder(descriptor), &result, + "calling %s.CreateRenderBundleEncoder(%s).", this, descriptor)) { + return RenderBundleEncoder::MakeError(this); + } + return result.Detach(); + } + RenderPipelineBase* DeviceBase::APICreateRenderPipeline( + const RenderPipelineDescriptor* descriptor) { + TRACE_EVENT1(GetPlatform(), General, "DeviceBase::APICreateRenderPipeline", "label", + utils::GetLabelForTrace(descriptor->label)); + + Ref<RenderPipelineBase> result; + if (ConsumedError(CreateRenderPipeline(descriptor), &result, + "calling %s.CreateRenderPipeline(%s).", this, descriptor)) { + return RenderPipelineBase::MakeError(this); + } + return result.Detach(); + } + ShaderModuleBase* DeviceBase::APICreateShaderModule(const ShaderModuleDescriptor* descriptor) { + TRACE_EVENT1(GetPlatform(), General, "DeviceBase::APICreateShaderModule", "label", + utils::GetLabelForTrace(descriptor->label)); + + Ref<ShaderModuleBase> result; + std::unique_ptr<OwnedCompilationMessages> compilationMessages( + std::make_unique<OwnedCompilationMessages>()); + if (ConsumedError(CreateShaderModule(descriptor, compilationMessages.get()), &result, + "calling %s.CreateShaderModule(%s).", this, descriptor)) { + DAWN_ASSERT(result == nullptr); + result = ShaderModuleBase::MakeError(this); + } + // Move compilation messages into ShaderModuleBase and emit tint errors and warnings + // after all other operations are finished successfully. + result->InjectCompilationMessages(std::move(compilationMessages)); + + return result.Detach(); + } + SwapChainBase* DeviceBase::APICreateSwapChain(Surface* surface, + const SwapChainDescriptor* descriptor) { + Ref<SwapChainBase> result; + if (ConsumedError(CreateSwapChain(surface, descriptor), &result, + "calling %s.CreateSwapChain(%s).", this, descriptor)) { + return SwapChainBase::MakeError(this); + } + return result.Detach(); + } + TextureBase* DeviceBase::APICreateTexture(const TextureDescriptor* descriptor) { + Ref<TextureBase> result; + if (ConsumedError(CreateTexture(descriptor), &result, "calling %s.CreateTexture(%s).", this, + descriptor)) { + return TextureBase::MakeError(this); + } + return result.Detach(); + } + + // For Dawn Wire + + BufferBase* DeviceBase::APICreateErrorBuffer() { + BufferDescriptor desc = {}; + return BufferBase::MakeError(this, &desc); + } + + // Other Device API methods + + // Returns true if future ticking is needed. + bool DeviceBase::APITick() { + if (IsLost() || ConsumedError(Tick())) { + return false; + } + return !IsDeviceIdle(); + } + + MaybeError DeviceBase::Tick() { + DAWN_TRY(ValidateIsAlive()); + + // to avoid overly ticking, we only want to tick when: + // 1. the last submitted serial has moved beyond the completed serial + // 2. or the completed serial has not reached the future serial set by the trackers + if (mLastSubmittedSerial > mCompletedSerial || mCompletedSerial < mFutureSerial) { + DAWN_TRY(CheckPassedSerials()); + DAWN_TRY(TickImpl()); + + // There is no GPU work in flight, we need to move the serials forward so that + // so that CPU operations waiting on GPU completion can know they don't have to wait. + // AssumeCommandsComplete will assign the max serial we must tick to in order to + // fire the awaiting callbacks. + if (mCompletedSerial == mLastSubmittedSerial) { + AssumeCommandsComplete(); + } + + // TODO(crbug.com/dawn/833): decouple TickImpl from updating the serial so that we can + // tick the dynamic uploader before the backend resource allocators. This would allow + // reclaiming resources one tick earlier. + mDynamicUploader->Deallocate(mCompletedSerial); + mQueue->Tick(mCompletedSerial); + } + + // We have to check callback tasks in every Tick because it is not related to any global + // serials. + FlushCallbackTaskQueue(); + + return {}; + } + + QueueBase* DeviceBase::APIGetQueue() { + // Backends gave the primary queue during initialization. + ASSERT(mQueue != nullptr); + + // Returns a new reference to the queue. + mQueue->Reference(); + return mQueue.Get(); + } + + ExternalTextureBase* DeviceBase::APICreateExternalTexture( + const ExternalTextureDescriptor* descriptor) { + Ref<ExternalTextureBase> result = nullptr; + if (ConsumedError(CreateExternalTextureImpl(descriptor), &result, + "calling %s.CreateExternalTexture(%s).", this, descriptor)) { + return ExternalTextureBase::MakeError(this); + } + + return result.Detach(); + } + + void DeviceBase::ApplyFeatures(const DeviceDescriptor* deviceDescriptor) { + ASSERT(deviceDescriptor); + ASSERT(GetAdapter()->SupportsAllRequiredFeatures( + {deviceDescriptor->requiredFeatures, deviceDescriptor->requiredFeaturesCount})); + + for (uint32_t i = 0; i < deviceDescriptor->requiredFeaturesCount; ++i) { + mEnabledFeatures.EnableFeature(deviceDescriptor->requiredFeatures[i]); + } + } + + bool DeviceBase::IsFeatureEnabled(Feature feature) const { + return mEnabledFeatures.IsEnabled(feature); + } + + bool DeviceBase::IsValidationEnabled() const { + return !IsToggleEnabled(Toggle::SkipValidation); + } + + bool DeviceBase::IsRobustnessEnabled() const { + return !IsToggleEnabled(Toggle::DisableRobustness); + } + + size_t DeviceBase::GetLazyClearCountForTesting() { + return mLazyClearCountForTesting; + } + + void DeviceBase::IncrementLazyClearCountForTesting() { + ++mLazyClearCountForTesting; + } + + size_t DeviceBase::GetDeprecationWarningCountForTesting() { + return mDeprecationWarnings->count; + } + + void DeviceBase::EmitDeprecationWarning(const char* warning) { + mDeprecationWarnings->count++; + if (mDeprecationWarnings->emitted.insert(warning).second) { + dawn::WarningLog() << warning; + } + } + + void DeviceBase::EmitLog(const char* message) { + this->EmitLog(WGPULoggingType_Info, message); + } + + void DeviceBase::EmitLog(WGPULoggingType loggingType, const char* message) { + if (mLoggingCallback != nullptr) { + // Use the thread-safe CallbackTaskManager routine + std::unique_ptr<LoggingCallbackTask> callbackTask = + std::make_unique<LoggingCallbackTask>(mLoggingCallback, loggingType, message, + mLoggingUserdata); + mCallbackTaskManager->AddCallbackTask(std::move(callbackTask)); + } + } + + bool DeviceBase::APIGetLimits(SupportedLimits* limits) const { + ASSERT(limits != nullptr); + if (limits->nextInChain != nullptr) { + return false; + } + limits->limits = mLimits.v1; + return true; + } + + bool DeviceBase::APIHasFeature(wgpu::FeatureName feature) const { + return mEnabledFeatures.IsEnabled(feature); + } + + size_t DeviceBase::APIEnumerateFeatures(wgpu::FeatureName* features) const { + return mEnabledFeatures.EnumerateFeatures(features); + } + + void DeviceBase::APIInjectError(wgpu::ErrorType type, const char* message) { + if (ConsumedError(ValidateErrorType(type))) { + return; + } + + // This method should only be used to make error scope reject. For DeviceLost there is the + // LoseForTesting function that can be used instead. + if (type != wgpu::ErrorType::Validation && type != wgpu::ErrorType::OutOfMemory) { + HandleError(InternalErrorType::Validation, + "Invalid injected error, must be Validation or OutOfMemory"); + return; + } + + HandleError(FromWGPUErrorType(type), message); + } + + QueueBase* DeviceBase::GetQueue() const { + return mQueue.Get(); + } + + // Implementation details of object creation + + ResultOrError<Ref<BindGroupBase>> DeviceBase::CreateBindGroup( + const BindGroupDescriptor* descriptor) { + DAWN_TRY(ValidateIsAlive()); + if (IsValidationEnabled()) { + DAWN_TRY_CONTEXT(ValidateBindGroupDescriptor(this, descriptor), + "validating %s against %s", descriptor, descriptor->layout); + } + return CreateBindGroupImpl(descriptor); + } + + ResultOrError<Ref<BindGroupLayoutBase>> DeviceBase::CreateBindGroupLayout( + const BindGroupLayoutDescriptor* descriptor, + bool allowInternalBinding) { + DAWN_TRY(ValidateIsAlive()); + if (IsValidationEnabled()) { + DAWN_TRY_CONTEXT( + ValidateBindGroupLayoutDescriptor(this, descriptor, allowInternalBinding), + "validating %s", descriptor); + } + return GetOrCreateBindGroupLayout(descriptor); + } + + ResultOrError<Ref<BufferBase>> DeviceBase::CreateBuffer(const BufferDescriptor* descriptor) { + DAWN_TRY(ValidateIsAlive()); + if (IsValidationEnabled()) { + DAWN_TRY_CONTEXT(ValidateBufferDescriptor(this, descriptor), "validating %s", + descriptor); + } + + Ref<BufferBase> buffer; + DAWN_TRY_ASSIGN(buffer, CreateBufferImpl(descriptor)); + + if (descriptor->mappedAtCreation) { + DAWN_TRY(buffer->MapAtCreation()); + } + + return std::move(buffer); + } + + ResultOrError<Ref<ComputePipelineBase>> DeviceBase::CreateComputePipeline( + const ComputePipelineDescriptor* descriptor) { + DAWN_TRY(ValidateIsAlive()); + if (IsValidationEnabled()) { + DAWN_TRY(ValidateComputePipelineDescriptor(this, descriptor)); + } + + // Ref will keep the pipeline layout alive until the end of the function where + // the pipeline will take another reference. + Ref<PipelineLayoutBase> layoutRef; + ComputePipelineDescriptor appliedDescriptor; + DAWN_TRY_ASSIGN(layoutRef, ValidateLayoutAndGetComputePipelineDescriptorWithDefaults( + this, *descriptor, &appliedDescriptor)); + + Ref<ComputePipelineBase> uninitializedComputePipeline = + CreateUninitializedComputePipelineImpl(&appliedDescriptor); + Ref<ComputePipelineBase> cachedComputePipeline = + GetCachedComputePipeline(uninitializedComputePipeline.Get()); + if (cachedComputePipeline.Get() != nullptr) { + return cachedComputePipeline; + } + + DAWN_TRY(uninitializedComputePipeline->Initialize()); + return AddOrGetCachedComputePipeline(std::move(uninitializedComputePipeline)); + } + + ResultOrError<Ref<CommandEncoder>> DeviceBase::CreateCommandEncoder( + const CommandEncoderDescriptor* descriptor) { + const CommandEncoderDescriptor defaultDescriptor = {}; + if (descriptor == nullptr) { + descriptor = &defaultDescriptor; + } + + DAWN_TRY(ValidateIsAlive()); + if (IsValidationEnabled()) { + DAWN_TRY(ValidateCommandEncoderDescriptor(this, descriptor)); + } + return CommandEncoder::Create(this, descriptor); + } + + MaybeError DeviceBase::CreateComputePipelineAsync( + const ComputePipelineDescriptor* descriptor, + WGPUCreateComputePipelineAsyncCallback callback, + void* userdata) { + DAWN_TRY(ValidateIsAlive()); + if (IsValidationEnabled()) { + DAWN_TRY(ValidateComputePipelineDescriptor(this, descriptor)); + } + + Ref<PipelineLayoutBase> layoutRef; + ComputePipelineDescriptor appliedDescriptor; + DAWN_TRY_ASSIGN(layoutRef, ValidateLayoutAndGetComputePipelineDescriptorWithDefaults( + this, *descriptor, &appliedDescriptor)); + + Ref<ComputePipelineBase> uninitializedComputePipeline = + CreateUninitializedComputePipelineImpl(&appliedDescriptor); + + // Call the callback directly when we can get a cached compute pipeline object. + Ref<ComputePipelineBase> cachedComputePipeline = + GetCachedComputePipeline(uninitializedComputePipeline.Get()); + if (cachedComputePipeline.Get() != nullptr) { + // TODO(crbug.com/dawn/1122): Call callbacks only on wgpuInstanceProcessEvents + callback(WGPUCreatePipelineAsyncStatus_Success, ToAPI(cachedComputePipeline.Detach()), + "", userdata); + } else { + // Otherwise we will create the pipeline object in InitializeComputePipelineAsyncImpl(), + // where the pipeline object may be initialized asynchronously and the result will be + // saved to mCreatePipelineAsyncTracker. + InitializeComputePipelineAsyncImpl(std::move(uninitializedComputePipeline), callback, + userdata); + } + + return {}; + } + + // This function is overwritten with the async version on the backends that supports + // initializing compute pipelines asynchronously. + void DeviceBase::InitializeComputePipelineAsyncImpl( + Ref<ComputePipelineBase> computePipeline, + WGPUCreateComputePipelineAsyncCallback callback, + void* userdata) { + Ref<ComputePipelineBase> result; + std::string errorMessage; + + MaybeError maybeError = computePipeline->Initialize(); + if (maybeError.IsError()) { + std::unique_ptr<ErrorData> error = maybeError.AcquireError(); + errorMessage = error->GetMessage(); + } else { + result = AddOrGetCachedComputePipeline(std::move(computePipeline)); + } + + std::unique_ptr<CreateComputePipelineAsyncCallbackTask> callbackTask = + std::make_unique<CreateComputePipelineAsyncCallbackTask>( + std::move(result), errorMessage, callback, userdata); + mCallbackTaskManager->AddCallbackTask(std::move(callbackTask)); + } + + // This function is overwritten with the async version on the backends + // that supports initializing render pipeline asynchronously + void DeviceBase::InitializeRenderPipelineAsyncImpl( + Ref<RenderPipelineBase> renderPipeline, + WGPUCreateRenderPipelineAsyncCallback callback, + void* userdata) { + Ref<RenderPipelineBase> result; + std::string errorMessage; + + MaybeError maybeError = renderPipeline->Initialize(); + if (maybeError.IsError()) { + std::unique_ptr<ErrorData> error = maybeError.AcquireError(); + errorMessage = error->GetMessage(); + } else { + result = AddOrGetCachedRenderPipeline(std::move(renderPipeline)); + } + + std::unique_ptr<CreateRenderPipelineAsyncCallbackTask> callbackTask = + std::make_unique<CreateRenderPipelineAsyncCallbackTask>(std::move(result), errorMessage, + callback, userdata); + mCallbackTaskManager->AddCallbackTask(std::move(callbackTask)); + } + + ResultOrError<Ref<PipelineLayoutBase>> DeviceBase::CreatePipelineLayout( + const PipelineLayoutDescriptor* descriptor) { + DAWN_TRY(ValidateIsAlive()); + if (IsValidationEnabled()) { + DAWN_TRY(ValidatePipelineLayoutDescriptor(this, descriptor)); + } + return GetOrCreatePipelineLayout(descriptor); + } + + ResultOrError<Ref<ExternalTextureBase>> DeviceBase::CreateExternalTextureImpl( + const ExternalTextureDescriptor* descriptor) { + if (IsValidationEnabled()) { + DAWN_TRY_CONTEXT(ValidateExternalTextureDescriptor(this, descriptor), "validating %s", + descriptor); + } + + return ExternalTextureBase::Create(this, descriptor); + } + + ResultOrError<Ref<QuerySetBase>> DeviceBase::CreateQuerySet( + const QuerySetDescriptor* descriptor) { + DAWN_TRY(ValidateIsAlive()); + if (IsValidationEnabled()) { + DAWN_TRY_CONTEXT(ValidateQuerySetDescriptor(this, descriptor), "validating %s", + descriptor); + } + return CreateQuerySetImpl(descriptor); + } + + ResultOrError<Ref<RenderBundleEncoder>> DeviceBase::CreateRenderBundleEncoder( + const RenderBundleEncoderDescriptor* descriptor) { + DAWN_TRY(ValidateIsAlive()); + if (IsValidationEnabled()) { + DAWN_TRY(ValidateRenderBundleEncoderDescriptor(this, descriptor)); + } + return RenderBundleEncoder::Create(this, descriptor); + } + + ResultOrError<Ref<RenderPipelineBase>> DeviceBase::CreateRenderPipeline( + const RenderPipelineDescriptor* descriptor) { + DAWN_TRY(ValidateIsAlive()); + if (IsValidationEnabled()) { + DAWN_TRY(ValidateRenderPipelineDescriptor(this, descriptor)); + } + + // Ref will keep the pipeline layout alive until the end of the function where + // the pipeline will take another reference. + Ref<PipelineLayoutBase> layoutRef; + RenderPipelineDescriptor appliedDescriptor; + DAWN_TRY_ASSIGN(layoutRef, ValidateLayoutAndGetRenderPipelineDescriptorWithDefaults( + this, *descriptor, &appliedDescriptor)); + + Ref<RenderPipelineBase> uninitializedRenderPipeline = + CreateUninitializedRenderPipelineImpl(&appliedDescriptor); + + Ref<RenderPipelineBase> cachedRenderPipeline = + GetCachedRenderPipeline(uninitializedRenderPipeline.Get()); + if (cachedRenderPipeline != nullptr) { + return cachedRenderPipeline; + } + + DAWN_TRY(uninitializedRenderPipeline->Initialize()); + return AddOrGetCachedRenderPipeline(std::move(uninitializedRenderPipeline)); + } + + MaybeError DeviceBase::CreateRenderPipelineAsync(const RenderPipelineDescriptor* descriptor, + WGPUCreateRenderPipelineAsyncCallback callback, + void* userdata) { + DAWN_TRY(ValidateIsAlive()); + if (IsValidationEnabled()) { + DAWN_TRY(ValidateRenderPipelineDescriptor(this, descriptor)); + } + + // Ref will keep the pipeline layout alive until the end of the function where + // the pipeline will take another reference. + Ref<PipelineLayoutBase> layoutRef; + RenderPipelineDescriptor appliedDescriptor; + DAWN_TRY_ASSIGN(layoutRef, ValidateLayoutAndGetRenderPipelineDescriptorWithDefaults( + this, *descriptor, &appliedDescriptor)); + + Ref<RenderPipelineBase> uninitializedRenderPipeline = + CreateUninitializedRenderPipelineImpl(&appliedDescriptor); + + // Call the callback directly when we can get a cached render pipeline object. + Ref<RenderPipelineBase> cachedRenderPipeline = + GetCachedRenderPipeline(uninitializedRenderPipeline.Get()); + if (cachedRenderPipeline != nullptr) { + // TODO(crbug.com/dawn/1122): Call callbacks only on wgpuInstanceProcessEvents + callback(WGPUCreatePipelineAsyncStatus_Success, ToAPI(cachedRenderPipeline.Detach()), + "", userdata); + } else { + // Otherwise we will create the pipeline object in InitializeRenderPipelineAsyncImpl(), + // where the pipeline object may be initialized asynchronously and the result will be + // saved to mCreatePipelineAsyncTracker. + InitializeRenderPipelineAsyncImpl(std::move(uninitializedRenderPipeline), callback, + userdata); + } + + return {}; + } + + ResultOrError<Ref<SamplerBase>> DeviceBase::CreateSampler(const SamplerDescriptor* descriptor) { + const SamplerDescriptor defaultDescriptor = {}; + DAWN_TRY(ValidateIsAlive()); + descriptor = descriptor != nullptr ? descriptor : &defaultDescriptor; + if (IsValidationEnabled()) { + DAWN_TRY_CONTEXT(ValidateSamplerDescriptor(this, descriptor), "validating %s", + descriptor); + } + return GetOrCreateSampler(descriptor); + } + + ResultOrError<Ref<ShaderModuleBase>> DeviceBase::CreateShaderModule( + const ShaderModuleDescriptor* descriptor, + OwnedCompilationMessages* compilationMessages) { + DAWN_TRY(ValidateIsAlive()); + + // CreateShaderModule can be called from inside dawn_native. If that's the case handle the + // error directly in Dawn and no compilationMessages held in the shader module. It is ok as + // long as dawn_native don't use the compilationMessages of these internal shader modules. + ShaderModuleParseResult parseResult; + + if (IsValidationEnabled()) { + DAWN_TRY_CONTEXT( + ValidateShaderModuleDescriptor(this, descriptor, &parseResult, compilationMessages), + "validating %s", descriptor); + } + + return GetOrCreateShaderModule(descriptor, &parseResult, compilationMessages); + } + + ResultOrError<Ref<SwapChainBase>> DeviceBase::CreateSwapChain( + Surface* surface, + const SwapChainDescriptor* descriptor) { + DAWN_TRY(ValidateIsAlive()); + if (IsValidationEnabled()) { + DAWN_TRY_CONTEXT(ValidateSwapChainDescriptor(this, surface, descriptor), + "validating %s", descriptor); + } + + // TODO(dawn:269): Remove this code path once implementation-based swapchains are removed. + if (surface == nullptr) { + return CreateSwapChainImpl(descriptor); + } else { + ASSERT(descriptor->implementation == 0); + + NewSwapChainBase* previousSwapChain = surface->GetAttachedSwapChain(); + ResultOrError<Ref<NewSwapChainBase>> maybeNewSwapChain = + CreateSwapChainImpl(surface, previousSwapChain, descriptor); + + if (previousSwapChain != nullptr) { + previousSwapChain->DetachFromSurface(); + } + + Ref<NewSwapChainBase> newSwapChain; + DAWN_TRY_ASSIGN(newSwapChain, std::move(maybeNewSwapChain)); + + newSwapChain->SetIsAttached(); + surface->SetAttachedSwapChain(newSwapChain.Get()); + return newSwapChain; + } + } + + ResultOrError<Ref<TextureBase>> DeviceBase::CreateTexture(const TextureDescriptor* descriptor) { + DAWN_TRY(ValidateIsAlive()); + if (IsValidationEnabled()) { + DAWN_TRY_CONTEXT(ValidateTextureDescriptor(this, descriptor), "validating %s.", + descriptor); + } + return CreateTextureImpl(descriptor); + } + + ResultOrError<Ref<TextureViewBase>> DeviceBase::CreateTextureView( + TextureBase* texture, + const TextureViewDescriptor* descriptor) { + DAWN_TRY(ValidateIsAlive()); + DAWN_TRY(ValidateObject(texture)); + TextureViewDescriptor desc = GetTextureViewDescriptorWithDefaults(texture, descriptor); + if (IsValidationEnabled()) { + DAWN_TRY_CONTEXT(ValidateTextureViewDescriptor(this, texture, &desc), + "validating %s against %s.", &desc, texture); + } + return CreateTextureViewImpl(texture, &desc); + } + + // Other implementation details + + DynamicUploader* DeviceBase::GetDynamicUploader() const { + return mDynamicUploader.get(); + } + + // The Toggle device facility + + std::vector<const char*> DeviceBase::GetTogglesUsed() const { + return mEnabledToggles.GetContainedToggleNames(); + } + + bool DeviceBase::IsToggleEnabled(Toggle toggle) const { + return mEnabledToggles.Has(toggle); + } + + void DeviceBase::SetToggle(Toggle toggle, bool isEnabled) { + if (!mOverridenToggles.Has(toggle)) { + mEnabledToggles.Set(toggle, isEnabled); + } + } + + void DeviceBase::ForceSetToggle(Toggle toggle, bool isEnabled) { + if (mOverridenToggles.Has(toggle) && mEnabledToggles.Has(toggle) != isEnabled) { + dawn::WarningLog() << "Forcing toggle \"" << ToggleEnumToName(toggle) << "\" to " + << isEnabled << " when it was overriden to be " << !isEnabled; + } + mEnabledToggles.Set(toggle, isEnabled); + } + + void DeviceBase::SetDefaultToggles() { + SetToggle(Toggle::LazyClearResourceOnFirstUse, true); + SetToggle(Toggle::DisallowUnsafeAPIs, true); + } + + void DeviceBase::ApplyToggleOverrides(const DawnTogglesDeviceDescriptor* togglesDescriptor) { + ASSERT(togglesDescriptor != nullptr); + + for (uint32_t i = 0; i < togglesDescriptor->forceEnabledTogglesCount; ++i) { + Toggle toggle = GetAdapter()->GetInstance()->ToggleNameToEnum( + togglesDescriptor->forceEnabledToggles[i]); + if (toggle != Toggle::InvalidEnum) { + mEnabledToggles.Set(toggle, true); + mOverridenToggles.Set(toggle, true); + } + } + for (uint32_t i = 0; i < togglesDescriptor->forceDisabledTogglesCount; ++i) { + Toggle toggle = GetAdapter()->GetInstance()->ToggleNameToEnum( + togglesDescriptor->forceDisabledToggles[i]); + if (toggle != Toggle::InvalidEnum) { + mEnabledToggles.Set(toggle, false); + mOverridenToggles.Set(toggle, true); + } + } + } + + void DeviceBase::FlushCallbackTaskQueue() { + if (!mCallbackTaskManager->IsEmpty()) { + // If a user calls Queue::Submit inside the callback, then the device will be ticked, + // which in turns ticks the tracker, causing reentrance and dead lock here. To prevent + // such reentrant call, we remove all the callback tasks from mCallbackTaskManager, + // update mCallbackTaskManager, then call all the callbacks. + auto callbackTasks = mCallbackTaskManager->AcquireCallbackTasks(); + for (std::unique_ptr<CallbackTask>& callbackTask : callbackTasks) { + callbackTask->Finish(); + } + } + } + + const CombinedLimits& DeviceBase::GetLimits() const { + return mLimits; + } + + AsyncTaskManager* DeviceBase::GetAsyncTaskManager() const { + return mAsyncTaskManager.get(); + } + + CallbackTaskManager* DeviceBase::GetCallbackTaskManager() const { + return mCallbackTaskManager.get(); + } + + dawn::platform::WorkerTaskPool* DeviceBase::GetWorkerTaskPool() const { + return mWorkerTaskPool.get(); + } + + void DeviceBase::AddComputePipelineAsyncCallbackTask( + Ref<ComputePipelineBase> pipeline, + std::string errorMessage, + WGPUCreateComputePipelineAsyncCallback callback, + void* userdata) { + // CreateComputePipelineAsyncWaitableCallbackTask is declared as an internal class as it + // needs to call the private member function DeviceBase::AddOrGetCachedComputePipeline(). + struct CreateComputePipelineAsyncWaitableCallbackTask final + : CreateComputePipelineAsyncCallbackTask { + using CreateComputePipelineAsyncCallbackTask::CreateComputePipelineAsyncCallbackTask; + void Finish() final { + // TODO(dawn:529): call AddOrGetCachedComputePipeline() asynchronously in + // CreateComputePipelineAsyncTaskImpl::Run() when the front-end pipeline cache is + // thread-safe. + if (mPipeline.Get() != nullptr) { + mPipeline = mPipeline->GetDevice()->AddOrGetCachedComputePipeline(mPipeline); + } + + CreateComputePipelineAsyncCallbackTask::Finish(); + } + }; + + mCallbackTaskManager->AddCallbackTask( + std::make_unique<CreateComputePipelineAsyncWaitableCallbackTask>( + std::move(pipeline), errorMessage, callback, userdata)); + } + + void DeviceBase::AddRenderPipelineAsyncCallbackTask( + Ref<RenderPipelineBase> pipeline, + std::string errorMessage, + WGPUCreateRenderPipelineAsyncCallback callback, + void* userdata) { + // CreateRenderPipelineAsyncWaitableCallbackTask is declared as an internal class as it + // needs to call the private member function DeviceBase::AddOrGetCachedRenderPipeline(). + struct CreateRenderPipelineAsyncWaitableCallbackTask final + : CreateRenderPipelineAsyncCallbackTask { + using CreateRenderPipelineAsyncCallbackTask::CreateRenderPipelineAsyncCallbackTask; + + void Finish() final { + // TODO(dawn:529): call AddOrGetCachedRenderPipeline() asynchronously in + // CreateRenderPipelineAsyncTaskImpl::Run() when the front-end pipeline cache is + // thread-safe. + if (mPipeline.Get() != nullptr) { + mPipeline = mPipeline->GetDevice()->AddOrGetCachedRenderPipeline(mPipeline); + } + + CreateRenderPipelineAsyncCallbackTask::Finish(); + } + }; + + mCallbackTaskManager->AddCallbackTask( + std::make_unique<CreateRenderPipelineAsyncWaitableCallbackTask>( + std::move(pipeline), errorMessage, callback, userdata)); + } + + PipelineCompatibilityToken DeviceBase::GetNextPipelineCompatibilityToken() { + return PipelineCompatibilityToken(mNextPipelineCompatibilityToken++); + } + + const std::string& DeviceBase::GetCacheIsolationKey() const { + return mCacheIsolationKey; + } + + const std::string& DeviceBase::GetLabel() const { + return mLabel; + } + + void DeviceBase::APISetLabel(const char* label) { + mLabel = label; + SetLabelImpl(); + } + + void DeviceBase::SetLabelImpl() { + } + + bool DeviceBase::ShouldDuplicateNumWorkgroupsForDispatchIndirect( + ComputePipelineBase* computePipeline) const { + return false; + } + +} // namespace dawn::native
diff --git a/src/dawn/native/Device.h b/src/dawn/native/Device.h new file mode 100644 index 0000000..db13e09 --- /dev/null +++ b/src/dawn/native/Device.h
@@ -0,0 +1,555 @@ +// Copyright 2017 The Dawn Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#ifndef DAWNNATIVE_DEVICE_H_ +#define DAWNNATIVE_DEVICE_H_ + +#include "dawn/native/Commands.h" +#include "dawn/native/ComputePipeline.h" +#include "dawn/native/Error.h" +#include "dawn/native/Features.h" +#include "dawn/native/Format.h" +#include "dawn/native/Forward.h" +#include "dawn/native/Limits.h" +#include "dawn/native/ObjectBase.h" +#include "dawn/native/ObjectType_autogen.h" +#include "dawn/native/StagingBuffer.h" +#include "dawn/native/Toggles.h" + +#include "dawn/native/DawnNative.h" +#include "dawn/native/dawn_platform.h" + +#include <mutex> +#include <utility> + +namespace dawn::platform { + class WorkerTaskPool; +} // namespace dawn::platform + +namespace dawn::native { + class AdapterBase; + class AsyncTaskManager; + class AttachmentState; + class AttachmentStateBlueprint; + class BindGroupLayoutBase; + class CallbackTaskManager; + class DynamicUploader; + class ErrorScopeStack; + class ExternalTextureBase; + class OwnedCompilationMessages; + class PersistentCache; + class StagingBufferBase; + struct CallbackTask; + struct InternalPipelineStore; + struct ShaderModuleParseResult; + + class DeviceBase : public RefCounted { + public: + DeviceBase(AdapterBase* adapter, const DeviceDescriptor* descriptor); + virtual ~DeviceBase(); + + void HandleError(InternalErrorType type, const char* message); + + bool ConsumedError(MaybeError maybeError) { + if (DAWN_UNLIKELY(maybeError.IsError())) { + ConsumeError(maybeError.AcquireError()); + return true; + } + return false; + } + + template <typename T> + bool ConsumedError(ResultOrError<T> resultOrError, T* result) { + if (DAWN_UNLIKELY(resultOrError.IsError())) { + ConsumeError(resultOrError.AcquireError()); + return true; + } + *result = resultOrError.AcquireSuccess(); + return false; + } + + template <typename... Args> + bool ConsumedError(MaybeError maybeError, const char* formatStr, const Args&... args) { + if (DAWN_UNLIKELY(maybeError.IsError())) { + std::unique_ptr<ErrorData> error = maybeError.AcquireError(); + if (error->GetType() == InternalErrorType::Validation) { + std::string out; + absl::UntypedFormatSpec format(formatStr); + if (absl::FormatUntyped(&out, format, {absl::FormatArg(args)...})) { + error->AppendContext(std::move(out)); + } else { + error->AppendContext( + absl::StrFormat("[Failed to format error: \"%s\"]", formatStr)); + } + } + ConsumeError(std::move(error)); + return true; + } + return false; + } + + template <typename T, typename... Args> + bool ConsumedError(ResultOrError<T> resultOrError, + T* result, + const char* formatStr, + const Args&... args) { + if (DAWN_UNLIKELY(resultOrError.IsError())) { + std::unique_ptr<ErrorData> error = resultOrError.AcquireError(); + if (error->GetType() == InternalErrorType::Validation) { + std::string out; + absl::UntypedFormatSpec format(formatStr); + if (absl::FormatUntyped(&out, format, {absl::FormatArg(args)...})) { + error->AppendContext(std::move(out)); + } else { + error->AppendContext( + absl::StrFormat("[Failed to format error: \"%s\"]", formatStr)); + } + } + ConsumeError(std::move(error)); + return true; + } + *result = resultOrError.AcquireSuccess(); + return false; + } + + MaybeError ValidateObject(const ApiObjectBase* object) const; + + AdapterBase* GetAdapter() const; + dawn::platform::Platform* GetPlatform() const; + + // Returns the Format corresponding to the wgpu::TextureFormat or an error if the format + // isn't a valid wgpu::TextureFormat or isn't supported by this device. + // The pointer returned has the same lifetime as the device. + ResultOrError<const Format*> GetInternalFormat(wgpu::TextureFormat format) const; + + // Returns the Format corresponding to the wgpu::TextureFormat and assumes the format is + // valid and supported. + // The reference returned has the same lifetime as the device. + const Format& GetValidInternalFormat(wgpu::TextureFormat format) const; + const Format& GetValidInternalFormat(FormatIndex formatIndex) const; + + virtual ResultOrError<Ref<CommandBufferBase>> CreateCommandBuffer( + CommandEncoder* encoder, + const CommandBufferDescriptor* descriptor) = 0; + + ExecutionSerial GetCompletedCommandSerial() const; + ExecutionSerial GetLastSubmittedCommandSerial() const; + ExecutionSerial GetFutureSerial() const; + ExecutionSerial GetPendingCommandSerial() const; + + // Many Dawn objects are completely immutable once created which means that if two + // creations are given the same arguments, they can return the same object. Reusing + // objects will help make comparisons between objects by a single pointer comparison. + // + // Technically no object is immutable as they have a reference count, and an + // application with reference-counting issues could "see" that objects are reused. + // This is solved by automatic-reference counting, and also the fact that when using + // the client-server wire every creation will get a different proxy object, with a + // different reference count. + // + // When trying to create an object, we give both the descriptor and an example of what + // the created object will be, the "blueprint". The blueprint is just a FooBase object + // instead of a backend Foo object. If the blueprint doesn't match an object in the + // cache, then the descriptor is used to make a new object. + ResultOrError<Ref<BindGroupLayoutBase>> GetOrCreateBindGroupLayout( + const BindGroupLayoutDescriptor* descriptor, + PipelineCompatibilityToken pipelineCompatibilityToken = PipelineCompatibilityToken(0)); + void UncacheBindGroupLayout(BindGroupLayoutBase* obj); + + BindGroupLayoutBase* GetEmptyBindGroupLayout(); + + void UncacheComputePipeline(ComputePipelineBase* obj); + + ResultOrError<Ref<TextureViewBase>> GetOrCreateDummyTextureViewForExternalTexture(); + + ResultOrError<Ref<PipelineLayoutBase>> GetOrCreatePipelineLayout( + const PipelineLayoutDescriptor* descriptor); + void UncachePipelineLayout(PipelineLayoutBase* obj); + + void UncacheRenderPipeline(RenderPipelineBase* obj); + + ResultOrError<Ref<SamplerBase>> GetOrCreateSampler(const SamplerDescriptor* descriptor); + void UncacheSampler(SamplerBase* obj); + + ResultOrError<Ref<ShaderModuleBase>> GetOrCreateShaderModule( + const ShaderModuleDescriptor* descriptor, + ShaderModuleParseResult* parseResult, + OwnedCompilationMessages* compilationMessages); + void UncacheShaderModule(ShaderModuleBase* obj); + + Ref<AttachmentState> GetOrCreateAttachmentState(AttachmentStateBlueprint* blueprint); + Ref<AttachmentState> GetOrCreateAttachmentState( + const RenderBundleEncoderDescriptor* descriptor); + Ref<AttachmentState> GetOrCreateAttachmentState(const RenderPipelineDescriptor* descriptor); + Ref<AttachmentState> GetOrCreateAttachmentState(const RenderPassDescriptor* descriptor); + void UncacheAttachmentState(AttachmentState* obj); + + // Object creation methods that be used in a reentrant manner. + ResultOrError<Ref<BindGroupBase>> CreateBindGroup(const BindGroupDescriptor* descriptor); + ResultOrError<Ref<BindGroupLayoutBase>> CreateBindGroupLayout( + const BindGroupLayoutDescriptor* descriptor, + bool allowInternalBinding = false); + ResultOrError<Ref<BufferBase>> CreateBuffer(const BufferDescriptor* descriptor); + ResultOrError<Ref<CommandEncoder>> CreateCommandEncoder( + const CommandEncoderDescriptor* descriptor = nullptr); + ResultOrError<Ref<ComputePipelineBase>> CreateComputePipeline( + const ComputePipelineDescriptor* descriptor); + MaybeError CreateComputePipelineAsync(const ComputePipelineDescriptor* descriptor, + WGPUCreateComputePipelineAsyncCallback callback, + void* userdata); + + ResultOrError<Ref<PipelineLayoutBase>> CreatePipelineLayout( + const PipelineLayoutDescriptor* descriptor); + ResultOrError<Ref<QuerySetBase>> CreateQuerySet(const QuerySetDescriptor* descriptor); + ResultOrError<Ref<RenderBundleEncoder>> CreateRenderBundleEncoder( + const RenderBundleEncoderDescriptor* descriptor); + ResultOrError<Ref<RenderPipelineBase>> CreateRenderPipeline( + const RenderPipelineDescriptor* descriptor); + MaybeError CreateRenderPipelineAsync(const RenderPipelineDescriptor* descriptor, + WGPUCreateRenderPipelineAsyncCallback callback, + void* userdata); + ResultOrError<Ref<SamplerBase>> CreateSampler( + const SamplerDescriptor* descriptor = nullptr); + ResultOrError<Ref<ShaderModuleBase>> CreateShaderModule( + const ShaderModuleDescriptor* descriptor, + OwnedCompilationMessages* compilationMessages = nullptr); + ResultOrError<Ref<SwapChainBase>> CreateSwapChain(Surface* surface, + const SwapChainDescriptor* descriptor); + ResultOrError<Ref<TextureBase>> CreateTexture(const TextureDescriptor* descriptor); + ResultOrError<Ref<TextureViewBase>> CreateTextureView( + TextureBase* texture, + const TextureViewDescriptor* descriptor); + + // Implementation of API object creation methods. DO NOT use them in a reentrant manner. + BindGroupBase* APICreateBindGroup(const BindGroupDescriptor* descriptor); + BindGroupLayoutBase* APICreateBindGroupLayout(const BindGroupLayoutDescriptor* descriptor); + BufferBase* APICreateBuffer(const BufferDescriptor* descriptor); + CommandEncoder* APICreateCommandEncoder(const CommandEncoderDescriptor* descriptor); + ComputePipelineBase* APICreateComputePipeline(const ComputePipelineDescriptor* descriptor); + PipelineLayoutBase* APICreatePipelineLayout(const PipelineLayoutDescriptor* descriptor); + QuerySetBase* APICreateQuerySet(const QuerySetDescriptor* descriptor); + void APICreateComputePipelineAsync(const ComputePipelineDescriptor* descriptor, + WGPUCreateComputePipelineAsyncCallback callback, + void* userdata); + void APICreateRenderPipelineAsync(const RenderPipelineDescriptor* descriptor, + WGPUCreateRenderPipelineAsyncCallback callback, + void* userdata); + RenderBundleEncoder* APICreateRenderBundleEncoder( + const RenderBundleEncoderDescriptor* descriptor); + RenderPipelineBase* APICreateRenderPipeline(const RenderPipelineDescriptor* descriptor); + ExternalTextureBase* APICreateExternalTexture(const ExternalTextureDescriptor* descriptor); + SamplerBase* APICreateSampler(const SamplerDescriptor* descriptor); + ShaderModuleBase* APICreateShaderModule(const ShaderModuleDescriptor* descriptor); + SwapChainBase* APICreateSwapChain(Surface* surface, const SwapChainDescriptor* descriptor); + TextureBase* APICreateTexture(const TextureDescriptor* descriptor); + + InternalPipelineStore* GetInternalPipelineStore(); + + // For Dawn Wire + BufferBase* APICreateErrorBuffer(); + + QueueBase* APIGetQueue(); + + bool APIGetLimits(SupportedLimits* limits) const; + bool APIHasFeature(wgpu::FeatureName feature) const; + size_t APIEnumerateFeatures(wgpu::FeatureName* features) const; + void APIInjectError(wgpu::ErrorType type, const char* message); + bool APITick(); + + void APISetDeviceLostCallback(wgpu::DeviceLostCallback callback, void* userdata); + void APISetUncapturedErrorCallback(wgpu::ErrorCallback callback, void* userdata); + void APISetLoggingCallback(wgpu::LoggingCallback callback, void* userdata); + void APIPushErrorScope(wgpu::ErrorFilter filter); + bool APIPopErrorScope(wgpu::ErrorCallback callback, void* userdata); + + MaybeError ValidateIsAlive() const; + + PersistentCache* GetPersistentCache(); + + virtual ResultOrError<std::unique_ptr<StagingBufferBase>> CreateStagingBuffer( + size_t size) = 0; + virtual MaybeError CopyFromStagingToBuffer(StagingBufferBase* source, + uint64_t sourceOffset, + BufferBase* destination, + uint64_t destinationOffset, + uint64_t size) = 0; + virtual MaybeError CopyFromStagingToTexture(const StagingBufferBase* source, + const TextureDataLayout& src, + TextureCopy* dst, + const Extent3D& copySizePixels) = 0; + + DynamicUploader* GetDynamicUploader() const; + + // The device state which is a combination of creation state and loss state. + // + // - BeingCreated: the device didn't finish creation yet and the frontend cannot be used + // (both for the application calling WebGPU, or re-entrant calls). No work exists on + // the GPU timeline. + // - Alive: the device is usable and might have work happening on the GPU timeline. + // - BeingDisconnected: the device is no longer usable because we are waiting for all + // work on the GPU timeline to finish. (this is to make validation prevent the + // application from adding more work during the transition from Available to + // Disconnected) + // - Disconnected: there is no longer work happening on the GPU timeline and the CPU data + // structures can be safely destroyed without additional synchronization. + // - Destroyed: the device is disconnected and resources have been reclaimed. + enum class State { + BeingCreated, + Alive, + BeingDisconnected, + Disconnected, + Destroyed, + }; + State GetState() const; + bool IsLost() const; + void TrackObject(ApiObjectBase* object); + std::mutex* GetObjectListMutex(ObjectType type); + + std::vector<const char*> GetTogglesUsed() const; + bool IsFeatureEnabled(Feature feature) const; + bool IsToggleEnabled(Toggle toggle) const; + bool IsValidationEnabled() const; + bool IsRobustnessEnabled() const; + size_t GetLazyClearCountForTesting(); + void IncrementLazyClearCountForTesting(); + size_t GetDeprecationWarningCountForTesting(); + void EmitDeprecationWarning(const char* warning); + void EmitLog(const char* message); + void EmitLog(WGPULoggingType loggingType, const char* message); + void APILoseForTesting(); + QueueBase* GetQueue() const; + + // AddFutureSerial is used to update the mFutureSerial with the max serial needed to be + // ticked in order to clean up all pending callback work or to execute asynchronous resource + // writes. It should be given the serial that a callback is tracked with, so that once that + // serial is completed, it can be resolved and cleaned up. This is so that when there is no + // gpu work (the last submitted serial has not moved beyond the completed serial), Tick can + // still check if we have pending work to take care of, rather than hanging and never + // reaching the serial the work will be executed on. + void AddFutureSerial(ExecutionSerial serial); + // Check for passed fences and set the new completed serial + MaybeError CheckPassedSerials(); + + MaybeError Tick(); + + // TODO(crbug.com/dawn/839): Organize the below backend-specific parameters into the struct + // BackendMetadata that we can query from the device. + virtual uint32_t GetOptimalBytesPerRowAlignment() const = 0; + virtual uint64_t GetOptimalBufferToTextureCopyOffsetAlignment() const = 0; + + virtual float GetTimestampPeriodInNS() const = 0; + + virtual bool ShouldDuplicateNumWorkgroupsForDispatchIndirect( + ComputePipelineBase* computePipeline) const; + + const CombinedLimits& GetLimits() const; + + AsyncTaskManager* GetAsyncTaskManager() const; + CallbackTaskManager* GetCallbackTaskManager() const; + dawn::platform::WorkerTaskPool* GetWorkerTaskPool() const; + + void AddComputePipelineAsyncCallbackTask(Ref<ComputePipelineBase> pipeline, + std::string errorMessage, + WGPUCreateComputePipelineAsyncCallback callback, + void* userdata); + void AddRenderPipelineAsyncCallbackTask(Ref<RenderPipelineBase> pipeline, + std::string errorMessage, + WGPUCreateRenderPipelineAsyncCallback callback, + void* userdata); + + PipelineCompatibilityToken GetNextPipelineCompatibilityToken(); + + const std::string& GetCacheIsolationKey() const; + const std::string& GetLabel() const; + void APISetLabel(const char* label); + void APIDestroy(); + + protected: + // Constructor used only for mocking and testing. + DeviceBase(); + + void SetToggle(Toggle toggle, bool isEnabled); + void ForceSetToggle(Toggle toggle, bool isEnabled); + + MaybeError Initialize(QueueBase* defaultQueue); + void DestroyObjects(); + void Destroy(); + + // Incrememt mLastSubmittedSerial when we submit the next serial + void IncrementLastSubmittedCommandSerial(); + + private: + virtual ResultOrError<Ref<BindGroupBase>> CreateBindGroupImpl( + const BindGroupDescriptor* descriptor) = 0; + virtual ResultOrError<Ref<BindGroupLayoutBase>> CreateBindGroupLayoutImpl( + const BindGroupLayoutDescriptor* descriptor, + PipelineCompatibilityToken pipelineCompatibilityToken) = 0; + virtual ResultOrError<Ref<BufferBase>> CreateBufferImpl( + const BufferDescriptor* descriptor) = 0; + virtual ResultOrError<Ref<ExternalTextureBase>> CreateExternalTextureImpl( + const ExternalTextureDescriptor* descriptor); + virtual ResultOrError<Ref<PipelineLayoutBase>> CreatePipelineLayoutImpl( + const PipelineLayoutDescriptor* descriptor) = 0; + virtual ResultOrError<Ref<QuerySetBase>> CreateQuerySetImpl( + const QuerySetDescriptor* descriptor) = 0; + virtual ResultOrError<Ref<SamplerBase>> CreateSamplerImpl( + const SamplerDescriptor* descriptor) = 0; + virtual ResultOrError<Ref<ShaderModuleBase>> CreateShaderModuleImpl( + const ShaderModuleDescriptor* descriptor, + ShaderModuleParseResult* parseResult) = 0; + virtual ResultOrError<Ref<SwapChainBase>> CreateSwapChainImpl( + const SwapChainDescriptor* descriptor) = 0; + // Note that previousSwapChain may be nullptr, or come from a different backend. + virtual ResultOrError<Ref<NewSwapChainBase>> CreateSwapChainImpl( + Surface* surface, + NewSwapChainBase* previousSwapChain, + const SwapChainDescriptor* descriptor) = 0; + virtual ResultOrError<Ref<TextureBase>> CreateTextureImpl( + const TextureDescriptor* descriptor) = 0; + virtual ResultOrError<Ref<TextureViewBase>> CreateTextureViewImpl( + TextureBase* texture, + const TextureViewDescriptor* descriptor) = 0; + virtual Ref<ComputePipelineBase> CreateUninitializedComputePipelineImpl( + const ComputePipelineDescriptor* descriptor) = 0; + virtual Ref<RenderPipelineBase> CreateUninitializedRenderPipelineImpl( + const RenderPipelineDescriptor* descriptor) = 0; + virtual void SetLabelImpl(); + + virtual MaybeError TickImpl() = 0; + void FlushCallbackTaskQueue(); + + ResultOrError<Ref<BindGroupLayoutBase>> CreateEmptyBindGroupLayout(); + + Ref<ComputePipelineBase> GetCachedComputePipeline( + ComputePipelineBase* uninitializedComputePipeline); + Ref<RenderPipelineBase> GetCachedRenderPipeline( + RenderPipelineBase* uninitializedRenderPipeline); + Ref<ComputePipelineBase> AddOrGetCachedComputePipeline( + Ref<ComputePipelineBase> computePipeline); + Ref<RenderPipelineBase> AddOrGetCachedRenderPipeline( + Ref<RenderPipelineBase> renderPipeline); + virtual void InitializeComputePipelineAsyncImpl( + Ref<ComputePipelineBase> computePipeline, + WGPUCreateComputePipelineAsyncCallback callback, + void* userdata); + virtual void InitializeRenderPipelineAsyncImpl( + Ref<RenderPipelineBase> renderPipeline, + WGPUCreateRenderPipelineAsyncCallback callback, + void* userdata); + + void ApplyToggleOverrides(const DawnTogglesDeviceDescriptor* togglesDescriptor); + void ApplyFeatures(const DeviceDescriptor* deviceDescriptor); + + void SetDefaultToggles(); + + void ConsumeError(std::unique_ptr<ErrorData> error); + + // Each backend should implement to check their passed fences if there are any and return a + // completed serial. Return 0 should indicate no fences to check. + virtual ResultOrError<ExecutionSerial> CheckAndUpdateCompletedSerials() = 0; + // During shut down of device, some operations might have been started since the last submit + // and waiting on a serial that doesn't have a corresponding fence enqueued. Fake serials to + // make all commands look completed. + void AssumeCommandsComplete(); + bool IsDeviceIdle(); + + // mCompletedSerial tracks the last completed command serial that the fence has returned. + // mLastSubmittedSerial tracks the last submitted command serial. + // During device removal, the serials could be artificially incremented + // to make it appear as if commands have been compeleted. They can also be artificially + // incremented when no work is being done in the GPU so CPU operations don't have to wait on + // stale serials. + // mFutureSerial tracks the largest serial we need to tick to for asynchronous commands or + // callbacks to fire + ExecutionSerial mCompletedSerial = ExecutionSerial(0); + ExecutionSerial mLastSubmittedSerial = ExecutionSerial(0); + ExecutionSerial mFutureSerial = ExecutionSerial(0); + + // DestroyImpl is used to clean up and release resources used by device, does not wait for + // GPU or check errors. + virtual void DestroyImpl() = 0; + + // WaitForIdleForDestruction waits for GPU to finish, checks errors and gets ready for + // destruction. This is only used when properly destructing the device. For a real + // device loss, this function doesn't need to be called since the driver already closed all + // resources. + virtual MaybeError WaitForIdleForDestruction() = 0; + + wgpu::ErrorCallback mUncapturedErrorCallback = nullptr; + void* mUncapturedErrorUserdata = nullptr; + + wgpu::LoggingCallback mLoggingCallback = nullptr; + void* mLoggingUserdata = nullptr; + + wgpu::DeviceLostCallback mDeviceLostCallback = nullptr; + void* mDeviceLostUserdata = nullptr; + + std::unique_ptr<ErrorScopeStack> mErrorScopeStack; + + // The Device keeps a ref to the Instance so that any live Device keeps the Instance alive. + // The Instance shouldn't need to ref child objects so this shouldn't introduce ref cycles. + // The Device keeps a simple pointer to the Adapter because the Adapter is owned by the + // Instance. + Ref<InstanceBase> mInstance; + AdapterBase* mAdapter = nullptr; + + // The object caches aren't exposed in the header as they would require a lot of + // additional includes. + struct Caches; + std::unique_ptr<Caches> mCaches; + + Ref<BindGroupLayoutBase> mEmptyBindGroupLayout; + + Ref<TextureViewBase> mExternalTextureDummyView; + + std::unique_ptr<DynamicUploader> mDynamicUploader; + std::unique_ptr<AsyncTaskManager> mAsyncTaskManager; + Ref<QueueBase> mQueue; + + struct DeprecationWarnings; + std::unique_ptr<DeprecationWarnings> mDeprecationWarnings; + + State mState = State::BeingCreated; + + // Encompasses the mutex and the actual list that contains all live objects "owned" by the + // device. + struct ApiObjectList { + std::mutex mutex; + LinkedList<ApiObjectBase> objects; + }; + PerObjectType<ApiObjectList> mObjectLists; + + FormatTable mFormatTable; + + TogglesSet mEnabledToggles; + TogglesSet mOverridenToggles; + size_t mLazyClearCountForTesting = 0; + std::atomic_uint64_t mNextPipelineCompatibilityToken; + + CombinedLimits mLimits; + FeaturesSet mEnabledFeatures; + + std::unique_ptr<InternalPipelineStore> mInternalPipelineStore; + + std::unique_ptr<PersistentCache> mPersistentCache; + + std::unique_ptr<CallbackTaskManager> mCallbackTaskManager; + std::unique_ptr<dawn::platform::WorkerTaskPool> mWorkerTaskPool; + std::string mLabel; + std::string mCacheIsolationKey = ""; + }; + +} // namespace dawn::native + +#endif // DAWNNATIVE_DEVICE_H_
diff --git a/src/dawn/native/DynamicUploader.cpp b/src/dawn/native/DynamicUploader.cpp new file mode 100644 index 0000000..262c07d --- /dev/null +++ b/src/dawn/native/DynamicUploader.cpp
@@ -0,0 +1,129 @@ +// Copyright 2018 The Dawn Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include "dawn/native/DynamicUploader.h" +#include "dawn/common/Math.h" +#include "dawn/native/Device.h" + +namespace dawn::native { + + DynamicUploader::DynamicUploader(DeviceBase* device) : mDevice(device) { + mRingBuffers.emplace_back( + std::unique_ptr<RingBuffer>(new RingBuffer{nullptr, {kRingBufferSize}})); + } + + void DynamicUploader::ReleaseStagingBuffer(std::unique_ptr<StagingBufferBase> stagingBuffer) { + mReleasedStagingBuffers.Enqueue(std::move(stagingBuffer), + mDevice->GetPendingCommandSerial()); + } + + ResultOrError<UploadHandle> DynamicUploader::AllocateInternal(uint64_t allocationSize, + ExecutionSerial serial) { + // Disable further sub-allocation should the request be too large. + if (allocationSize > kRingBufferSize) { + std::unique_ptr<StagingBufferBase> stagingBuffer; + DAWN_TRY_ASSIGN(stagingBuffer, mDevice->CreateStagingBuffer(allocationSize)); + + UploadHandle uploadHandle; + uploadHandle.mappedBuffer = static_cast<uint8_t*>(stagingBuffer->GetMappedPointer()); + uploadHandle.stagingBuffer = stagingBuffer.get(); + + ReleaseStagingBuffer(std::move(stagingBuffer)); + return uploadHandle; + } + + // Note: Validation ensures size is already aligned. + // First-fit: find next smallest buffer large enough to satisfy the allocation request. + RingBuffer* targetRingBuffer = mRingBuffers.back().get(); + for (auto& ringBuffer : mRingBuffers) { + const RingBufferAllocator& ringBufferAllocator = ringBuffer->mAllocator; + // Prevent overflow. + ASSERT(ringBufferAllocator.GetSize() >= ringBufferAllocator.GetUsedSize()); + const uint64_t remainingSize = + ringBufferAllocator.GetSize() - ringBufferAllocator.GetUsedSize(); + if (allocationSize <= remainingSize) { + targetRingBuffer = ringBuffer.get(); + break; + } + } + + uint64_t startOffset = RingBufferAllocator::kInvalidOffset; + if (targetRingBuffer != nullptr) { + startOffset = targetRingBuffer->mAllocator.Allocate(allocationSize, serial); + } + + // Upon failure, append a newly created ring buffer to fulfill the + // request. + if (startOffset == RingBufferAllocator::kInvalidOffset) { + mRingBuffers.emplace_back( + std::unique_ptr<RingBuffer>(new RingBuffer{nullptr, {kRingBufferSize}})); + + targetRingBuffer = mRingBuffers.back().get(); + startOffset = targetRingBuffer->mAllocator.Allocate(allocationSize, serial); + } + + ASSERT(startOffset != RingBufferAllocator::kInvalidOffset); + + // Allocate the staging buffer backing the ringbuffer. + // Note: the first ringbuffer will be lazily created. + if (targetRingBuffer->mStagingBuffer == nullptr) { + std::unique_ptr<StagingBufferBase> stagingBuffer; + DAWN_TRY_ASSIGN(stagingBuffer, + mDevice->CreateStagingBuffer(targetRingBuffer->mAllocator.GetSize())); + targetRingBuffer->mStagingBuffer = std::move(stagingBuffer); + } + + ASSERT(targetRingBuffer->mStagingBuffer != nullptr); + + UploadHandle uploadHandle; + uploadHandle.stagingBuffer = targetRingBuffer->mStagingBuffer.get(); + uploadHandle.mappedBuffer = + static_cast<uint8_t*>(uploadHandle.stagingBuffer->GetMappedPointer()) + startOffset; + uploadHandle.startOffset = startOffset; + + return uploadHandle; + } + + void DynamicUploader::Deallocate(ExecutionSerial lastCompletedSerial) { + // Reclaim memory within the ring buffers by ticking (or removing requests no longer + // in-flight). + for (size_t i = 0; i < mRingBuffers.size(); ++i) { + mRingBuffers[i]->mAllocator.Deallocate(lastCompletedSerial); + + // Never erase the last buffer as to prevent re-creating smaller buffers + // again. The last buffer is the largest. + if (mRingBuffers[i]->mAllocator.Empty() && i < mRingBuffers.size() - 1) { + mRingBuffers.erase(mRingBuffers.begin() + i); + } + } + mReleasedStagingBuffers.ClearUpTo(lastCompletedSerial); + } + + // TODO(dawn:512): Optimize this function so that it doesn't allocate additional memory + // when it's not necessary. + ResultOrError<UploadHandle> DynamicUploader::Allocate(uint64_t allocationSize, + ExecutionSerial serial, + uint64_t offsetAlignment) { + ASSERT(offsetAlignment > 0); + UploadHandle uploadHandle; + DAWN_TRY_ASSIGN(uploadHandle, + AllocateInternal(allocationSize + offsetAlignment - 1, serial)); + uint64_t additionalOffset = + Align(uploadHandle.startOffset, offsetAlignment) - uploadHandle.startOffset; + uploadHandle.mappedBuffer = + static_cast<uint8_t*>(uploadHandle.mappedBuffer) + additionalOffset; + uploadHandle.startOffset += additionalOffset; + return uploadHandle; + } +} // namespace dawn::native
diff --git a/src/dawn/native/DynamicUploader.h b/src/dawn/native/DynamicUploader.h new file mode 100644 index 0000000..fa3f80a --- /dev/null +++ b/src/dawn/native/DynamicUploader.h
@@ -0,0 +1,66 @@ +// Copyright 2018 The Dawn Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#ifndef DAWNNATIVE_DYNAMICUPLOADER_H_ +#define DAWNNATIVE_DYNAMICUPLOADER_H_ + +#include "dawn/native/Forward.h" +#include "dawn/native/IntegerTypes.h" +#include "dawn/native/RingBufferAllocator.h" +#include "dawn/native/StagingBuffer.h" + +// DynamicUploader is the front-end implementation used to manage multiple ring buffers for upload +// usage. +namespace dawn::native { + + struct UploadHandle { + uint8_t* mappedBuffer = nullptr; + uint64_t startOffset = 0; + StagingBufferBase* stagingBuffer = nullptr; + }; + + class DynamicUploader { + public: + DynamicUploader(DeviceBase* device); + ~DynamicUploader() = default; + + // We add functions to Release StagingBuffers to the DynamicUploader as there's + // currently no place to track the allocated staging buffers such that they're freed after + // pending commands are finished. This should be changed when better resource allocation is + // implemented. + void ReleaseStagingBuffer(std::unique_ptr<StagingBufferBase> stagingBuffer); + + ResultOrError<UploadHandle> Allocate(uint64_t allocationSize, + ExecutionSerial serial, + uint64_t offsetAlignment); + void Deallocate(ExecutionSerial lastCompletedSerial); + + private: + static constexpr uint64_t kRingBufferSize = 4 * 1024 * 1024; + + struct RingBuffer { + std::unique_ptr<StagingBufferBase> mStagingBuffer; + RingBufferAllocator mAllocator; + }; + + ResultOrError<UploadHandle> AllocateInternal(uint64_t allocationSize, + ExecutionSerial serial); + + std::vector<std::unique_ptr<RingBuffer>> mRingBuffers; + SerialQueue<ExecutionSerial, std::unique_ptr<StagingBufferBase>> mReleasedStagingBuffers; + DeviceBase* mDevice; + }; +} // namespace dawn::native + +#endif // DAWNNATIVE_DYNAMICUPLOADER_H_
diff --git a/src/dawn/native/EncodingContext.cpp b/src/dawn/native/EncodingContext.cpp new file mode 100644 index 0000000..b9ba529 --- /dev/null +++ b/src/dawn/native/EncodingContext.cpp
@@ -0,0 +1,217 @@ +// Copyright 2019 The Dawn Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include "dawn/native/EncodingContext.h" + +#include "dawn/common/Assert.h" +#include "dawn/native/CommandEncoder.h" +#include "dawn/native/Commands.h" +#include "dawn/native/Device.h" +#include "dawn/native/ErrorData.h" +#include "dawn/native/IndirectDrawValidationEncoder.h" +#include "dawn/native/RenderBundleEncoder.h" + +namespace dawn::native { + + EncodingContext::EncodingContext(DeviceBase* device, const ApiObjectBase* initialEncoder) + : mDevice(device), mTopLevelEncoder(initialEncoder), mCurrentEncoder(initialEncoder) { + } + + EncodingContext::~EncodingContext() { + Destroy(); + } + + void EncodingContext::Destroy() { + if (mDestroyed) { + return; + } + if (!mWereCommandsAcquired) { + FreeCommands(GetIterator()); + } + // If we weren't already finished, then we want to handle an error here so that any calls + // to Finish after Destroy will return a meaningful error. + if (!IsFinished()) { + HandleError(DAWN_FORMAT_VALIDATION_ERROR("Destroyed encoder cannot be finished.")); + } + mDestroyed = true; + mCurrentEncoder = nullptr; + } + + CommandIterator EncodingContext::AcquireCommands() { + MoveToIterator(); + ASSERT(!mWereCommandsAcquired); + mWereCommandsAcquired = true; + return std::move(mIterator); + } + + CommandIterator* EncodingContext::GetIterator() { + MoveToIterator(); + ASSERT(!mWereCommandsAcquired); + return &mIterator; + } + + void EncodingContext::MoveToIterator() { + CommitCommands(std::move(mPendingCommands)); + if (!mWasMovedToIterator) { + mIterator.AcquireCommandBlocks(std::move(mAllocators)); + mWasMovedToIterator = true; + } + } + + void EncodingContext::HandleError(std::unique_ptr<ErrorData> error) { + // Append in reverse so that the most recently set debug group is printed first, like a + // call stack. + for (auto iter = mDebugGroupLabels.rbegin(); iter != mDebugGroupLabels.rend(); ++iter) { + error->AppendDebugGroup(*iter); + } + + if (!IsFinished()) { + // Encoding should only generate validation errors. + ASSERT(error->GetType() == InternalErrorType::Validation); + // If the encoding context is not finished, errors are deferred until + // Finish() is called. + if (mError == nullptr) { + mError = std::move(error); + } + } else { + mDevice->HandleError(error->GetType(), error->GetFormattedMessage().c_str()); + } + } + + void EncodingContext::WillBeginRenderPass() { + ASSERT(mCurrentEncoder == mTopLevelEncoder); + if (mDevice->IsValidationEnabled()) { + // When validation is enabled, we are going to want to capture all commands encoded + // between and including BeginRenderPassCmd and EndRenderPassCmd, and defer their + // sequencing util after we have a chance to insert any necessary validation + // commands. To support this we commit any current commands now, so that the + // impending BeginRenderPassCmd starts in a fresh CommandAllocator. + CommitCommands(std::move(mPendingCommands)); + } + } + + void EncodingContext::EnterPass(const ApiObjectBase* passEncoder) { + // Assert we're at the top level. + ASSERT(mCurrentEncoder == mTopLevelEncoder); + ASSERT(passEncoder != nullptr); + + mCurrentEncoder = passEncoder; + } + + MaybeError EncodingContext::ExitRenderPass(const ApiObjectBase* passEncoder, + RenderPassResourceUsageTracker usageTracker, + CommandEncoder* commandEncoder, + IndirectDrawMetadata indirectDrawMetadata) { + ASSERT(mCurrentEncoder != mTopLevelEncoder); + ASSERT(mCurrentEncoder == passEncoder); + + mCurrentEncoder = mTopLevelEncoder; + + if (mDevice->IsValidationEnabled()) { + // With validation enabled, commands were committed just before BeginRenderPassCmd was + // encoded by our RenderPassEncoder (see WillBeginRenderPass above). This means + // mPendingCommands contains only the commands from BeginRenderPassCmd to + // EndRenderPassCmd, inclusive. Now we swap out this allocator with a fresh one to give + // the validation encoder a chance to insert its commands first. + CommandAllocator renderCommands = std::move(mPendingCommands); + DAWN_TRY(EncodeIndirectDrawValidationCommands(mDevice, commandEncoder, &usageTracker, + &indirectDrawMetadata)); + CommitCommands(std::move(mPendingCommands)); + CommitCommands(std::move(renderCommands)); + } + + mRenderPassUsages.push_back(usageTracker.AcquireResourceUsage()); + return {}; + } + + void EncodingContext::ExitComputePass(const ApiObjectBase* passEncoder, + ComputePassResourceUsage usages) { + ASSERT(mCurrentEncoder != mTopLevelEncoder); + ASSERT(mCurrentEncoder == passEncoder); + + mCurrentEncoder = mTopLevelEncoder; + mComputePassUsages.push_back(std::move(usages)); + } + + void EncodingContext::EnsurePassExited(const ApiObjectBase* passEncoder) { + if (mCurrentEncoder != mTopLevelEncoder && mCurrentEncoder == passEncoder) { + // The current pass encoder is being deleted. Implicitly end the pass with an error. + mCurrentEncoder = mTopLevelEncoder; + HandleError(DAWN_FORMAT_VALIDATION_ERROR( + "Command buffer recording ended before %s was ended.", passEncoder)); + } + } + + const RenderPassUsages& EncodingContext::GetRenderPassUsages() const { + ASSERT(!mWereRenderPassUsagesAcquired); + return mRenderPassUsages; + } + + RenderPassUsages EncodingContext::AcquireRenderPassUsages() { + ASSERT(!mWereRenderPassUsagesAcquired); + mWereRenderPassUsagesAcquired = true; + return std::move(mRenderPassUsages); + } + + const ComputePassUsages& EncodingContext::GetComputePassUsages() const { + ASSERT(!mWereComputePassUsagesAcquired); + return mComputePassUsages; + } + + ComputePassUsages EncodingContext::AcquireComputePassUsages() { + ASSERT(!mWereComputePassUsagesAcquired); + mWereComputePassUsagesAcquired = true; + return std::move(mComputePassUsages); + } + + void EncodingContext::PushDebugGroupLabel(const char* groupLabel) { + mDebugGroupLabels.emplace_back(groupLabel); + } + + void EncodingContext::PopDebugGroupLabel() { + mDebugGroupLabels.pop_back(); + } + + MaybeError EncodingContext::Finish() { + DAWN_INVALID_IF(IsFinished(), "Command encoding already finished."); + + const ApiObjectBase* currentEncoder = mCurrentEncoder; + const ApiObjectBase* topLevelEncoder = mTopLevelEncoder; + + // Even if finish validation fails, it is now invalid to call any encoding commands, + // so we clear the encoders. Note: mTopLevelEncoder == nullptr is used as a flag for + // if Finish() has been called. + mCurrentEncoder = nullptr; + mTopLevelEncoder = nullptr; + CommitCommands(std::move(mPendingCommands)); + + if (mError != nullptr) { + return std::move(mError); + } + DAWN_INVALID_IF(currentEncoder != topLevelEncoder, + "Command buffer recording ended before %s was ended.", currentEncoder); + return {}; + } + + void EncodingContext::CommitCommands(CommandAllocator allocator) { + if (!allocator.IsEmpty()) { + mAllocators.push_back(std::move(allocator)); + } + } + + bool EncodingContext::IsFinished() const { + return mTopLevelEncoder == nullptr; + } + +} // namespace dawn::native
diff --git a/src/dawn/native/EncodingContext.h b/src/dawn/native/EncodingContext.h new file mode 100644 index 0000000..659a9a7 --- /dev/null +++ b/src/dawn/native/EncodingContext.h
@@ -0,0 +1,182 @@ +// Copyright 2019 The Dawn Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#ifndef DAWNNATIVE_ENCODINGCONTEXT_H_ +#define DAWNNATIVE_ENCODINGCONTEXT_H_ + +#include "dawn/native/CommandAllocator.h" +#include "dawn/native/Error.h" +#include "dawn/native/ErrorData.h" +#include "dawn/native/IndirectDrawMetadata.h" +#include "dawn/native/PassResourceUsageTracker.h" +#include "dawn/native/dawn_platform.h" + +#include <string> + +namespace dawn::native { + + class CommandEncoder; + class DeviceBase; + class ApiObjectBase; + + // Base class for allocating/iterating commands. + // It performs error tracking as well as encoding state for render/compute passes. + class EncodingContext { + public: + EncodingContext(DeviceBase* device, const ApiObjectBase* initialEncoder); + ~EncodingContext(); + + // Marks the encoding context as destroyed so that any future encodes will fail, and all + // encoded commands are released. + void Destroy(); + + CommandIterator AcquireCommands(); + CommandIterator* GetIterator(); + + // Functions to handle encoder errors + void HandleError(std::unique_ptr<ErrorData> error); + + inline bool ConsumedError(MaybeError maybeError) { + if (DAWN_UNLIKELY(maybeError.IsError())) { + HandleError(maybeError.AcquireError()); + return true; + } + return false; + } + + template <typename... Args> + inline bool ConsumedError(MaybeError maybeError, + const char* formatStr, + const Args&... args) { + if (DAWN_UNLIKELY(maybeError.IsError())) { + std::unique_ptr<ErrorData> error = maybeError.AcquireError(); + if (error->GetType() == InternalErrorType::Validation) { + std::string out; + absl::UntypedFormatSpec format(formatStr); + if (absl::FormatUntyped(&out, format, {absl::FormatArg(args)...})) { + error->AppendContext(std::move(out)); + } else { + error->AppendContext(absl::StrFormat( + "[Failed to format error message: \"%s\"].", formatStr)); + } + } + HandleError(std::move(error)); + return true; + } + return false; + } + + inline bool CheckCurrentEncoder(const ApiObjectBase* encoder) { + if (DAWN_UNLIKELY(encoder != mCurrentEncoder)) { + if (mDestroyed) { + HandleError( + DAWN_FORMAT_VALIDATION_ERROR("Recording in a destroyed %s.", encoder)); + } else if (mCurrentEncoder != mTopLevelEncoder) { + // The top level encoder was used when a pass encoder was current. + HandleError(DAWN_FORMAT_VALIDATION_ERROR( + "Command cannot be recorded while %s is active.", mCurrentEncoder)); + } else { + HandleError(DAWN_FORMAT_VALIDATION_ERROR( + "Recording in an error or already ended %s.", encoder)); + } + return false; + } + return true; + } + + template <typename EncodeFunction> + inline bool TryEncode(const ApiObjectBase* encoder, EncodeFunction&& encodeFunction) { + if (!CheckCurrentEncoder(encoder)) { + return false; + } + ASSERT(!mWasMovedToIterator); + return !ConsumedError(encodeFunction(&mPendingCommands)); + } + + template <typename EncodeFunction, typename... Args> + inline bool TryEncode(const ApiObjectBase* encoder, + EncodeFunction&& encodeFunction, + const char* formatStr, + const Args&... args) { + if (!CheckCurrentEncoder(encoder)) { + return false; + } + ASSERT(!mWasMovedToIterator); + return !ConsumedError(encodeFunction(&mPendingCommands), formatStr, args...); + } + + // Must be called prior to encoding a BeginRenderPassCmd. Note that it's OK to call this + // and then not actually call EnterPass+ExitRenderPass, for example if some other pass setup + // failed validation before the BeginRenderPassCmd could be encoded. + void WillBeginRenderPass(); + + // Functions to set current encoder state + void EnterPass(const ApiObjectBase* passEncoder); + MaybeError ExitRenderPass(const ApiObjectBase* passEncoder, + RenderPassResourceUsageTracker usageTracker, + CommandEncoder* commandEncoder, + IndirectDrawMetadata indirectDrawMetadata); + void ExitComputePass(const ApiObjectBase* passEncoder, ComputePassResourceUsage usages); + MaybeError Finish(); + + // Called when a pass encoder is deleted. Provides an opportunity to clean up if it's the + // mCurrentEncoder. + void EnsurePassExited(const ApiObjectBase* passEncoder); + + const RenderPassUsages& GetRenderPassUsages() const; + const ComputePassUsages& GetComputePassUsages() const; + RenderPassUsages AcquireRenderPassUsages(); + ComputePassUsages AcquireComputePassUsages(); + + void PushDebugGroupLabel(const char* groupLabel); + void PopDebugGroupLabel(); + + private: + void CommitCommands(CommandAllocator allocator); + + bool IsFinished() const; + void MoveToIterator(); + + DeviceBase* mDevice; + + // There can only be two levels of encoders. Top-level and render/compute pass. + // The top level encoder is the encoder the EncodingContext is created with. + // It doubles as flag to check if encoding has been Finished. + const ApiObjectBase* mTopLevelEncoder; + // The current encoder must be the same as the encoder provided to TryEncode, + // otherwise an error is produced. It may be nullptr if the EncodingContext is an error. + // The current encoder changes with Enter/ExitPass which should be called by + // CommandEncoder::Begin/EndPass. + const ApiObjectBase* mCurrentEncoder; + + RenderPassUsages mRenderPassUsages; + bool mWereRenderPassUsagesAcquired = false; + ComputePassUsages mComputePassUsages; + bool mWereComputePassUsagesAcquired = false; + + CommandAllocator mPendingCommands; + + std::vector<CommandAllocator> mAllocators; + CommandIterator mIterator; + bool mWasMovedToIterator = false; + bool mWereCommandsAcquired = false; + bool mDestroyed = false; + + std::unique_ptr<ErrorData> mError; + std::vector<std::string> mDebugGroupLabels; + }; + +} // namespace dawn::native + +#endif // DAWNNATIVE_ENCODINGCONTEXT_H_
diff --git a/src/dawn/native/EnumClassBitmasks.h b/src/dawn/native/EnumClassBitmasks.h new file mode 100644 index 0000000..671db23 --- /dev/null +++ b/src/dawn/native/EnumClassBitmasks.h
@@ -0,0 +1,39 @@ +// Copyright 2020 The Dawn Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#ifndef DAWNNATIVE_ENUMCLASSBITMASK_H_ +#define DAWNNATIVE_ENUMCLASSBITMASK_H_ + +#include "dawn/EnumClassBitmasks.h" + +namespace dawn::native { + + // EnumClassBitmmasks is a helper in the dawn:: namespace. + // Re-export it in the dawn_native namespace. + DAWN_IMPORT_BITMASK_OPERATORS + + // Specify this for usage with EnumMaskIterator + template <typename T> + struct EnumBitmaskSize { + static constexpr unsigned value = 0; + }; + + template <typename T> + constexpr bool HasOneBit(T value) { + return HasZeroOrOneBits(value) && value != T(0); + } + +} // namespace dawn::native + +#endif // DAWNNATIVE_ENUMCLASSBITMASK_H_
diff --git a/src/dawn/native/EnumMaskIterator.h b/src/dawn/native/EnumMaskIterator.h new file mode 100644 index 0000000..6653ef4 --- /dev/null +++ b/src/dawn/native/EnumMaskIterator.h
@@ -0,0 +1,82 @@ +// Copyright 2020 The Dawn Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#ifndef DAWNNATIVE_ENUMMASKITERATOR_H_ +#define DAWNNATIVE_ENUMMASKITERATOR_H_ + +#include "dawn/common/BitSetIterator.h" +#include "dawn/native/EnumClassBitmasks.h" + +namespace dawn::native { + + template <typename T> + class EnumMaskIterator final { + static constexpr size_t N = EnumBitmaskSize<T>::value; + static_assert(N > 0); + + using U = std::underlying_type_t<T>; + + public: + EnumMaskIterator(const T& mask) : mBitSetIterator(std::bitset<N>(static_cast<U>(mask))) { + // If you hit this ASSERT it means that you forgot to update EnumBitmaskSize<T>::value; + ASSERT(U(mask) == 0 || Log2(uint64_t(U(mask))) < N); + } + + class Iterator final { + public: + Iterator(const typename BitSetIterator<N, U>::Iterator& iter) : mIter(iter) { + } + + Iterator& operator++() { + ++mIter; + return *this; + } + + bool operator==(const Iterator& other) const { + return mIter == other.mIter; + } + + bool operator!=(const Iterator& other) const { + return mIter != other.mIter; + } + + T operator*() const { + U value = *mIter; + return static_cast<T>(U(1) << value); + } + + private: + typename BitSetIterator<N, U>::Iterator mIter; + }; + + Iterator begin() const { + return Iterator(mBitSetIterator.begin()); + } + + Iterator end() const { + return Iterator(mBitSetIterator.end()); + } + + private: + BitSetIterator<N, U> mBitSetIterator; + }; + + template <typename T> + EnumMaskIterator<T> IterateEnumMask(const T& mask) { + return EnumMaskIterator<T>(mask); + } + +} // namespace dawn::native + +#endif // DAWNNATIVE_ENUMMASKITERATOR_H_
diff --git a/src/dawn/native/Error.cpp b/src/dawn/native/Error.cpp new file mode 100644 index 0000000..d524a32 --- /dev/null +++ b/src/dawn/native/Error.cpp
@@ -0,0 +1,64 @@ +// Copyright 2018 The Dawn Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include "dawn/native/Error.h" + +#include "dawn/native/ErrorData.h" +#include "dawn/native/dawn_platform.h" + +namespace dawn::native { + + void IgnoreErrors(MaybeError maybeError) { + if (maybeError.IsError()) { + std::unique_ptr<ErrorData> errorData = maybeError.AcquireError(); + // During shutdown and destruction, device lost errors can be ignored. + // We can also ignore other unexpected internal errors on shut down and treat it as + // device lost so that we can continue with destruction. + ASSERT(errorData->GetType() == InternalErrorType::DeviceLost || + errorData->GetType() == InternalErrorType::Internal); + } + } + + wgpu::ErrorType ToWGPUErrorType(InternalErrorType type) { + switch (type) { + case InternalErrorType::Validation: + return wgpu::ErrorType::Validation; + case InternalErrorType::OutOfMemory: + return wgpu::ErrorType::OutOfMemory; + + // There is no equivalent of Internal errors in the WebGPU API. Internal errors cause + // the device at the API level to be lost, so treat it like a DeviceLost error. + case InternalErrorType::Internal: + case InternalErrorType::DeviceLost: + return wgpu::ErrorType::DeviceLost; + + default: + return wgpu::ErrorType::Unknown; + } + } + + InternalErrorType FromWGPUErrorType(wgpu::ErrorType type) { + switch (type) { + case wgpu::ErrorType::Validation: + return InternalErrorType::Validation; + case wgpu::ErrorType::OutOfMemory: + return InternalErrorType::OutOfMemory; + case wgpu::ErrorType::DeviceLost: + return InternalErrorType::DeviceLost; + default: + return InternalErrorType::Internal; + } + } + +} // namespace dawn::native
diff --git a/src/dawn/native/Error.h b/src/dawn/native/Error.h new file mode 100644 index 0000000..64c481f --- /dev/null +++ b/src/dawn/native/Error.h
@@ -0,0 +1,194 @@ +// Copyright 2018 The Dawn Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#ifndef DAWNNATIVE_ERROR_H_ +#define DAWNNATIVE_ERROR_H_ + +#include "absl/strings/str_format.h" +#include "dawn/common/Result.h" +#include "dawn/native/ErrorData.h" +#include "dawn/native/webgpu_absl_format.h" + +#include <string> + +namespace dawn::native { + + enum class InternalErrorType : uint32_t { Validation, DeviceLost, Internal, OutOfMemory }; + + // MaybeError and ResultOrError are meant to be used as return value for function that are not + // expected to, but might fail. The handling of error is potentially much slower than successes. + using MaybeError = Result<void, ErrorData>; + + template <typename T> + using ResultOrError = Result<T, ErrorData>; + + // Returning a success is done like so: + // return {}; // for Error + // return SomethingOfTypeT; // for ResultOrError<T> + // + // Returning an error is done via: + // return DAWN_MAKE_ERROR(errorType, "My error message"); + // + // but shorthand version for specific error types are preferred: + // return DAWN_VALIDATION_ERROR("My error message"); + // + // There are different types of errors that should be used for different purpose: + // + // - Validation: these are errors that show the user did something bad, which causes the + // whole call to be a no-op. It's most commonly found in the frontend but there can be some + // backend specific validation in non-conformant backends too. + // + // - Out of memory: creation of a Buffer or Texture failed because there isn't enough memory. + // This is similar to validation errors in that the call becomes a no-op and returns an + // error object, but is reported separated from validation to the user. + // + // - Device loss: the backend driver reported that the GPU has been lost, which means all + // previous commands magically disappeared and the only thing left to do is clean up. + // Note: Device loss should be used rarely and in most case you want to use Internal + // instead. + // + // - Internal: something happened that the backend didn't expect, and it doesn't know + // how to recover from that situation. This causes the device to be lost, but is separate + // from device loss, because the GPU execution is still happening so we need to clean up + // more gracefully. + // + // - Unimplemented: same as Internal except it puts "unimplemented" in the error message for + // more clarity. + +#define DAWN_MAKE_ERROR(TYPE, MESSAGE) \ + ::dawn::native::ErrorData::Create(TYPE, MESSAGE, __FILE__, __func__, __LINE__) + +#define DAWN_VALIDATION_ERROR(MESSAGE) DAWN_MAKE_ERROR(InternalErrorType::Validation, MESSAGE) + +// TODO(dawn:563): Rename to DAWN_VALIDATION_ERROR once all message format strings have been +// converted to constexpr. +#define DAWN_FORMAT_VALIDATION_ERROR(...) \ + DAWN_MAKE_ERROR(InternalErrorType::Validation, absl::StrFormat(__VA_ARGS__)) + +#define DAWN_INVALID_IF(EXPR, ...) \ + if (DAWN_UNLIKELY(EXPR)) { \ + return DAWN_MAKE_ERROR(InternalErrorType::Validation, absl::StrFormat(__VA_ARGS__)); \ + } \ + for (;;) \ + break + +// DAWN_DEVICE_LOST_ERROR means that there was a real unrecoverable native device lost error. +// We can't even do a graceful shutdown because the Device is gone. +#define DAWN_DEVICE_LOST_ERROR(MESSAGE) DAWN_MAKE_ERROR(InternalErrorType::DeviceLost, MESSAGE) + +// DAWN_INTERNAL_ERROR means Dawn hit an unexpected error in the backend and should try to +// gracefully shut down. +#define DAWN_INTERNAL_ERROR(MESSAGE) DAWN_MAKE_ERROR(InternalErrorType::Internal, MESSAGE) + +#define DAWN_FORMAT_INTERNAL_ERROR(...) \ + DAWN_MAKE_ERROR(InternalErrorType::Internal, absl::StrFormat(__VA_ARGS__)) + +#define DAWN_UNIMPLEMENTED_ERROR(MESSAGE) \ + DAWN_MAKE_ERROR(InternalErrorType::Internal, std::string("Unimplemented: ") + MESSAGE) + +// DAWN_OUT_OF_MEMORY_ERROR means we ran out of memory. It may be used as a signal internally in +// Dawn to free up unused resources. Or, it may bubble up to the application to signal an allocation +// was too large or they should free some existing resources. +#define DAWN_OUT_OF_MEMORY_ERROR(MESSAGE) DAWN_MAKE_ERROR(InternalErrorType::OutOfMemory, MESSAGE) + +#define DAWN_CONCAT1(x, y) x##y +#define DAWN_CONCAT2(x, y) DAWN_CONCAT1(x, y) +#define DAWN_LOCAL_VAR DAWN_CONCAT2(_localVar, __LINE__) + + // When Errors aren't handled explicitly, calls to functions returning errors should be + // wrapped in an DAWN_TRY. It will return the error if any, otherwise keep executing + // the current function. +#define DAWN_TRY(EXPR) DAWN_TRY_WITH_CLEANUP(EXPR, {}) + +#define DAWN_TRY_CONTEXT(EXPR, ...) \ + DAWN_TRY_WITH_CLEANUP(EXPR, { error->AppendContext(absl::StrFormat(__VA_ARGS__)); }) + +#define DAWN_TRY_WITH_CLEANUP(EXPR, BODY) \ + { \ + auto DAWN_LOCAL_VAR = EXPR; \ + if (DAWN_UNLIKELY(DAWN_LOCAL_VAR.IsError())) { \ + std::unique_ptr<::dawn::native::ErrorData> error = DAWN_LOCAL_VAR.AcquireError(); \ + {BODY} /* comment to force the formatter to insert a newline */ \ + error->AppendBacktrace(__FILE__, __func__, __LINE__); \ + return {std::move(error)}; \ + } \ + } \ + for (;;) \ + break + + // DAWN_TRY_ASSIGN is the same as DAWN_TRY for ResultOrError and assigns the success value, if + // any, to VAR. +#define DAWN_TRY_ASSIGN(VAR, EXPR) DAWN_TRY_ASSIGN_WITH_CLEANUP(VAR, EXPR, {}) +#define DAWN_TRY_ASSIGN_CONTEXT(VAR, EXPR, ...) \ + DAWN_TRY_ASSIGN_WITH_CLEANUP(VAR, EXPR, { error->AppendContext(absl::StrFormat(__VA_ARGS__)); }) + + // Argument helpers are used to determine which macro implementations should be called when + // overloading with different number of variables. +#define DAWN_ERROR_UNIMPLEMENTED_MACRO_(...) UNREACHABLE() +#define DAWN_ERROR_GET_5TH_ARG_HELPER_(_1, _2, _3, _4, NAME, ...) NAME +#define DAWN_ERROR_GET_5TH_ARG_(args) DAWN_ERROR_GET_5TH_ARG_HELPER_ args + + // DAWN_TRY_ASSIGN_WITH_CLEANUP is overloaded with 2 version so that users can override the + // return value of the macro when necessary. This is particularly useful if the function + // calling the macro may want to return void instead of the error, i.e. in a test where we may + // just want to assert and fail if the assign cannot go through. In both the cleanup and return + // clauses, users can use the `error` variable to access the pointer to the acquired error. + // + // Example usages: + // 3 Argument Case: + // Result res; + // DAWN_TRY_ASSIGN_WITH_CLEANUP( + // res, GetResultOrErrorFunction(), { AddAdditionalErrorInformation(error.get()); } + // ); + // + // 4 Argument Case: + // bool FunctionThatReturnsBool() { + // DAWN_TRY_ASSIGN_WITH_CLEANUP( + // res, GetResultOrErrorFunction(), + // { AddAdditionalErrorInformation(error.get()); }, + // false + // ); + // } +#define DAWN_TRY_ASSIGN_WITH_CLEANUP(...) \ + DAWN_ERROR_GET_5TH_ARG_((__VA_ARGS__, DAWN_TRY_ASSIGN_WITH_CLEANUP_IMPL_4_, \ + DAWN_TRY_ASSIGN_WITH_CLEANUP_IMPL_3_, \ + DAWN_ERROR_UNIMPLEMENTED_MACRO_)) \ + (__VA_ARGS__) + +#define DAWN_TRY_ASSIGN_WITH_CLEANUP_IMPL_3_(VAR, EXPR, BODY) \ + DAWN_TRY_ASSIGN_WITH_CLEANUP_IMPL_4_(VAR, EXPR, BODY, std::move(error)) + +#define DAWN_TRY_ASSIGN_WITH_CLEANUP_IMPL_4_(VAR, EXPR, BODY, RET) \ + { \ + auto DAWN_LOCAL_VAR = EXPR; \ + if (DAWN_UNLIKELY(DAWN_LOCAL_VAR.IsError())) { \ + std::unique_ptr<ErrorData> error = DAWN_LOCAL_VAR.AcquireError(); \ + {BODY} /* comment to force the formatter to insert a newline */ \ + error->AppendBacktrace(__FILE__, __func__, __LINE__); \ + return (RET); \ + } \ + VAR = DAWN_LOCAL_VAR.AcquireSuccess(); \ + } \ + for (;;) \ + break + + // Assert that errors are device loss so that we can continue with destruction + void IgnoreErrors(MaybeError maybeError); + + wgpu::ErrorType ToWGPUErrorType(InternalErrorType type); + InternalErrorType FromWGPUErrorType(wgpu::ErrorType type); + +} // namespace dawn::native + +#endif // DAWNNATIVE_ERROR_H_
diff --git a/src/dawn/native/ErrorData.cpp b/src/dawn/native/ErrorData.cpp new file mode 100644 index 0000000..863d20f --- /dev/null +++ b/src/dawn/native/ErrorData.cpp
@@ -0,0 +1,103 @@ +// Copyright 2018 The Dawn Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include "dawn/native/ErrorData.h" + +#include "dawn/native/Error.h" +#include "dawn/native/ObjectBase.h" +#include "dawn/native/dawn_platform.h" + +namespace dawn::native { + + std::unique_ptr<ErrorData> ErrorData::Create(InternalErrorType type, + std::string message, + const char* file, + const char* function, + int line) { + std::unique_ptr<ErrorData> error = std::make_unique<ErrorData>(type, message); + error->AppendBacktrace(file, function, line); + return error; + } + + ErrorData::ErrorData(InternalErrorType type, std::string message) + : mType(type), mMessage(std::move(message)) { + } + + void ErrorData::AppendBacktrace(const char* file, const char* function, int line) { + BacktraceRecord record; + record.file = file; + record.function = function; + record.line = line; + + mBacktrace.push_back(std::move(record)); + } + + void ErrorData::AppendContext(std::string context) { + mContexts.push_back(std::move(context)); + } + + void ErrorData::AppendDebugGroup(std::string label) { + mDebugGroups.push_back(std::move(label)); + } + + InternalErrorType ErrorData::GetType() const { + return mType; + } + + const std::string& ErrorData::GetMessage() const { + return mMessage; + } + + const std::vector<ErrorData::BacktraceRecord>& ErrorData::GetBacktrace() const { + return mBacktrace; + } + + const std::vector<std::string>& ErrorData::GetContexts() const { + return mContexts; + } + + const std::vector<std::string>& ErrorData::GetDebugGroups() const { + return mDebugGroups; + } + + std::string ErrorData::GetFormattedMessage() const { + std::ostringstream ss; + ss << mMessage << "\n"; + + if (!mContexts.empty()) { + for (auto context : mContexts) { + ss << " - While " << context << "\n"; + } + } + + // For non-validation errors, or erros that lack a context include the + // stack trace for debugging purposes. + if (mContexts.empty() || mType != InternalErrorType::Validation) { + for (const auto& callsite : mBacktrace) { + ss << " at " << callsite.function << " (" << callsite.file << ":" + << callsite.line << ")\n"; + } + } + + if (!mDebugGroups.empty()) { + ss << "\nDebug group stack:\n"; + for (auto label : mDebugGroups) { + ss << " > \"" << label << "\"\n"; + } + } + + return ss.str(); + } + +} // namespace dawn::native
diff --git a/src/dawn/native/ErrorData.h b/src/dawn/native/ErrorData.h new file mode 100644 index 0000000..901c54f --- /dev/null +++ b/src/dawn/native/ErrorData.h
@@ -0,0 +1,70 @@ +// Copyright 2018 The Dawn Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#ifndef DAWNNATIVE_ERRORDATA_H_ +#define DAWNNATIVE_ERRORDATA_H_ + +#include "dawn/common/Compiler.h" + +#include <cstdint> +#include <memory> +#include <string> +#include <vector> + +namespace wgpu { + enum class ErrorType : uint32_t; +} + +namespace dawn { + using ErrorType = wgpu::ErrorType; +} + +namespace dawn::native { + enum class InternalErrorType : uint32_t; + + class [[nodiscard]] ErrorData { + public: + [[nodiscard]] static std::unique_ptr<ErrorData> Create( + InternalErrorType type, std::string message, const char* file, const char* function, + int line); + ErrorData(InternalErrorType type, std::string message); + + struct BacktraceRecord { + const char* file; + const char* function; + int line; + }; + void AppendBacktrace(const char* file, const char* function, int line); + void AppendContext(std::string context); + void AppendDebugGroup(std::string label); + + InternalErrorType GetType() const; + const std::string& GetMessage() const; + const std::vector<BacktraceRecord>& GetBacktrace() const; + const std::vector<std::string>& GetContexts() const; + const std::vector<std::string>& GetDebugGroups() const; + + std::string GetFormattedMessage() const; + + private: + InternalErrorType mType; + std::string mMessage; + std::vector<BacktraceRecord> mBacktrace; + std::vector<std::string> mContexts; + std::vector<std::string> mDebugGroups; + }; + +} // namespace dawn::native + +#endif // DAWNNATIVE_ERRORDATA_H_
diff --git a/src/dawn/native/ErrorInjector.cpp b/src/dawn/native/ErrorInjector.cpp new file mode 100644 index 0000000..af87498 --- /dev/null +++ b/src/dawn/native/ErrorInjector.cpp
@@ -0,0 +1,70 @@ +// Copyright 2019 The Dawn Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include "dawn/native/ErrorInjector.h" + +#include "dawn/common/Assert.h" +#include "dawn/native/DawnNative.h" + +namespace dawn::native { + + namespace { + + bool sIsEnabled = false; + uint64_t sNextIndex = 0; + uint64_t sInjectedFailureIndex = 0; + bool sHasPendingInjectedError = false; + + } // anonymous namespace + + void EnableErrorInjector() { + sIsEnabled = true; + } + + void DisableErrorInjector() { + sIsEnabled = false; + } + + void ClearErrorInjector() { + sNextIndex = 0; + sHasPendingInjectedError = false; + } + + bool ErrorInjectorEnabled() { + return sIsEnabled; + } + + uint64_t AcquireErrorInjectorCallCount() { + uint64_t count = sNextIndex; + ClearErrorInjector(); + return count; + } + + bool ShouldInjectError() { + uint64_t index = sNextIndex++; + if (sHasPendingInjectedError && index == sInjectedFailureIndex) { + sHasPendingInjectedError = false; + return true; + } + return false; + } + + void InjectErrorAt(uint64_t index) { + // Only one error can be injected at a time. + ASSERT(!sHasPendingInjectedError); + sInjectedFailureIndex = index; + sHasPendingInjectedError = true; + } + +} // namespace dawn::native
diff --git a/src/dawn/native/ErrorInjector.h b/src/dawn/native/ErrorInjector.h new file mode 100644 index 0000000..ab41886 --- /dev/null +++ b/src/dawn/native/ErrorInjector.h
@@ -0,0 +1,68 @@ +// Copyright 2019 The Dawn Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#ifndef DAWNNATIVE_ERRORINJECTOR_H_ +#define DAWNNATIVE_ERRORINJECTOR_H_ + +#include <stdint.h> +#include <type_traits> + +namespace dawn::native { + + template <typename ErrorType> + struct InjectedErrorResult { + ErrorType error; + bool injected; + }; + + bool ErrorInjectorEnabled(); + + bool ShouldInjectError(); + + template <typename ErrorType> + InjectedErrorResult<ErrorType> MaybeInjectError(ErrorType errorType) { + return InjectedErrorResult<ErrorType>{errorType, ShouldInjectError()}; + } + + template <typename ErrorType, typename... ErrorTypes> + InjectedErrorResult<ErrorType> MaybeInjectError(ErrorType errorType, ErrorTypes... errorTypes) { + if (ShouldInjectError()) { + return InjectedErrorResult<ErrorType>{errorType, true}; + } + return MaybeInjectError(errorTypes...); + } + +} // namespace dawn::native + +#if defined(DAWN_ENABLE_ERROR_INJECTION) + +# define INJECT_ERROR_OR_RUN(stmt, ...) \ + [&]() { \ + if (DAWN_UNLIKELY(::dawn::native::ErrorInjectorEnabled())) { \ + /* Only used for testing and fuzzing, so it's okay if this is deoptimized */ \ + auto injectedError = ::dawn::native::MaybeInjectError(__VA_ARGS__); \ + if (injectedError.injected) { \ + return injectedError.error; \ + } \ + } \ + return (stmt); \ + }() + +#else + +# define INJECT_ERROR_OR_RUN(stmt, ...) stmt + +#endif + +#endif // DAWNNATIVE_ERRORINJECTOR_H_
diff --git a/src/dawn/native/ErrorScope.cpp b/src/dawn/native/ErrorScope.cpp new file mode 100644 index 0000000..06b7a95 --- /dev/null +++ b/src/dawn/native/ErrorScope.cpp
@@ -0,0 +1,92 @@ +// Copyright 2019 The Dawn Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include "dawn/native/ErrorScope.h" + +#include "dawn/common/Assert.h" + +namespace dawn::native { + + namespace { + + wgpu::ErrorType ErrorFilterToErrorType(wgpu::ErrorFilter filter) { + switch (filter) { + case wgpu::ErrorFilter::Validation: + return wgpu::ErrorType::Validation; + case wgpu::ErrorFilter::OutOfMemory: + return wgpu::ErrorType::OutOfMemory; + } + UNREACHABLE(); + } + + } // namespace + + ErrorScope::ErrorScope(wgpu::ErrorFilter errorFilter) + : mMatchedErrorType(ErrorFilterToErrorType(errorFilter)) { + } + + wgpu::ErrorType ErrorScope::GetErrorType() const { + return mCapturedError; + } + + const char* ErrorScope::GetErrorMessage() const { + return mErrorMessage.c_str(); + } + + void ErrorScopeStack::Push(wgpu::ErrorFilter filter) { + mScopes.push_back(ErrorScope(filter)); + } + + ErrorScope ErrorScopeStack::Pop() { + ASSERT(!mScopes.empty()); + ErrorScope scope = std::move(mScopes.back()); + mScopes.pop_back(); + return scope; + } + + bool ErrorScopeStack::Empty() const { + return mScopes.empty(); + } + + bool ErrorScopeStack::HandleError(wgpu::ErrorType type, const char* message) { + for (auto it = mScopes.rbegin(); it != mScopes.rend(); ++it) { + if (it->mMatchedErrorType != type) { + // Error filter does not match. Move on to the next scope. + continue; + } + + // Filter matches. + // Record the error if the scope doesn't have one yet. + if (it->mCapturedError == wgpu::ErrorType::NoError) { + it->mCapturedError = type; + it->mErrorMessage = message; + } + + if (type == wgpu::ErrorType::DeviceLost) { + if (it->mCapturedError != wgpu::ErrorType::DeviceLost) { + // DeviceLost overrides any other error that is not a DeviceLost. + it->mCapturedError = type; + it->mErrorMessage = message; + } + } else { + // Errors that are not device lost are captured and stop propogating. + return true; + } + } + + // The error was not captured. + return false; + } + +} // namespace dawn::native
diff --git a/src/dawn/native/ErrorScope.h b/src/dawn/native/ErrorScope.h new file mode 100644 index 0000000..766a81e --- /dev/null +++ b/src/dawn/native/ErrorScope.h
@@ -0,0 +1,57 @@ +// Copyright 2019 The Dawn Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#ifndef DAWNNATIVE_ERRORSCOPE_H_ +#define DAWNNATIVE_ERRORSCOPE_H_ + +#include "dawn/native/dawn_platform.h" + +#include <string> +#include <vector> + +namespace dawn::native { + + class ErrorScope { + public: + wgpu::ErrorType GetErrorType() const; + const char* GetErrorMessage() const; + + private: + friend class ErrorScopeStack; + explicit ErrorScope(wgpu::ErrorFilter errorFilter); + + wgpu::ErrorType mMatchedErrorType; + wgpu::ErrorType mCapturedError = wgpu::ErrorType::NoError; + std::string mErrorMessage = ""; + }; + + class ErrorScopeStack { + public: + void Push(wgpu::ErrorFilter errorFilter); + ErrorScope Pop(); + + bool Empty() const; + + // Pass an error to the scopes in the stack. Returns true if one of the scopes + // captured the error. Returns false if the error should be forwarded to the + // uncaptured error callback. + bool HandleError(wgpu::ErrorType type, const char* message); + + private: + std::vector<ErrorScope> mScopes; + }; + +} // namespace dawn::native + +#endif // DAWNNATIVE_ERRORSCOPE_H_
diff --git a/src/dawn/native/ExternalTexture.cpp b/src/dawn/native/ExternalTexture.cpp new file mode 100644 index 0000000..1570825 --- /dev/null +++ b/src/dawn/native/ExternalTexture.cpp
@@ -0,0 +1,212 @@ +// Copyright 2021 The Dawn Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include "dawn/native/ExternalTexture.h" + +#include "dawn/native/Buffer.h" +#include "dawn/native/Device.h" +#include "dawn/native/ObjectType_autogen.h" +#include "dawn/native/Queue.h" +#include "dawn/native/Texture.h" + +#include "dawn/native/dawn_platform.h" + +namespace dawn::native { + + MaybeError ValidateExternalTexturePlane(const TextureViewBase* textureView) { + DAWN_INVALID_IF( + (textureView->GetTexture()->GetUsage() & wgpu::TextureUsage::TextureBinding) == 0, + "The external texture plane (%s) usage (%s) doesn't include the required usage (%s)", + textureView, textureView->GetTexture()->GetUsage(), wgpu::TextureUsage::TextureBinding); + + DAWN_INVALID_IF(textureView->GetDimension() != wgpu::TextureViewDimension::e2D, + "The external texture plane (%s) dimension (%s) is not 2D.", textureView, + textureView->GetDimension()); + + DAWN_INVALID_IF(textureView->GetLevelCount() > 1, + "The external texture plane (%s) mip level count (%u) is not 1.", + textureView, textureView->GetLevelCount()); + + DAWN_INVALID_IF(textureView->GetTexture()->GetSampleCount() != 1, + "The external texture plane (%s) sample count (%u) is not one.", + textureView, textureView->GetTexture()->GetSampleCount()); + + return {}; + } + + MaybeError ValidateExternalTextureDescriptor(const DeviceBase* device, + const ExternalTextureDescriptor* descriptor) { + ASSERT(descriptor); + ASSERT(descriptor->plane0); + + DAWN_TRY(device->ValidateObject(descriptor->plane0)); + + wgpu::TextureFormat plane0Format = descriptor->plane0->GetFormat().format; + + if (descriptor->plane1) { + DAWN_INVALID_IF( + device->IsToggleEnabled(Toggle::DisallowUnsafeAPIs), + "Bi-planar external textures are disabled until the implementation is completed."); + + DAWN_INVALID_IF(descriptor->colorSpace != wgpu::PredefinedColorSpace::Srgb, + "The specified color space (%s) is not %s.", descriptor->colorSpace, + wgpu::PredefinedColorSpace::Srgb); + + DAWN_TRY(device->ValidateObject(descriptor->plane1)); + wgpu::TextureFormat plane1Format = descriptor->plane1->GetFormat().format; + + DAWN_INVALID_IF(plane0Format != wgpu::TextureFormat::R8Unorm, + "The bi-planar external texture plane (%s) format (%s) is not %s.", + descriptor->plane0, plane0Format, wgpu::TextureFormat::R8Unorm); + DAWN_INVALID_IF(plane1Format != wgpu::TextureFormat::RG8Unorm, + "The bi-planar external texture plane (%s) format (%s) is not %s.", + descriptor->plane1, plane1Format, wgpu::TextureFormat::RG8Unorm); + + DAWN_TRY(ValidateExternalTexturePlane(descriptor->plane0)); + DAWN_TRY(ValidateExternalTexturePlane(descriptor->plane1)); + } else { + switch (plane0Format) { + case wgpu::TextureFormat::RGBA8Unorm: + case wgpu::TextureFormat::BGRA8Unorm: + case wgpu::TextureFormat::RGBA16Float: + DAWN_TRY(ValidateExternalTexturePlane(descriptor->plane0)); + break; + default: + return DAWN_FORMAT_VALIDATION_ERROR( + "The external texture plane (%s) format (%s) is not a supported format " + "(%s, %s, %s).", + descriptor->plane0, plane0Format, wgpu::TextureFormat::RGBA8Unorm, + wgpu::TextureFormat::BGRA8Unorm, wgpu::TextureFormat::RGBA16Float); + } + } + + return {}; + } + + // static + ResultOrError<Ref<ExternalTextureBase>> ExternalTextureBase::Create( + DeviceBase* device, + const ExternalTextureDescriptor* descriptor) { + Ref<ExternalTextureBase> externalTexture = + AcquireRef(new ExternalTextureBase(device, descriptor)); + DAWN_TRY(externalTexture->Initialize(device, descriptor)); + return std::move(externalTexture); + } + + ExternalTextureBase::ExternalTextureBase(DeviceBase* device, + const ExternalTextureDescriptor* descriptor) + : ApiObjectBase(device, descriptor->label), mState(ExternalTextureState::Alive) { + TrackInDevice(); + } + + ExternalTextureBase::ExternalTextureBase(DeviceBase* device) + : ApiObjectBase(device, kLabelNotImplemented), mState(ExternalTextureState::Alive) { + TrackInDevice(); + } + + ExternalTextureBase::ExternalTextureBase(DeviceBase* device, ObjectBase::ErrorTag tag) + : ApiObjectBase(device, tag) { + } + + ExternalTextureBase::~ExternalTextureBase() = default; + + MaybeError ExternalTextureBase::Initialize(DeviceBase* device, + const ExternalTextureDescriptor* descriptor) { + // Store any passed in TextureViews associated with individual planes. + mTextureViews[0] = descriptor->plane0; + + if (descriptor->plane1) { + mTextureViews[1] = descriptor->plane1; + } else { + DAWN_TRY_ASSIGN(mTextureViews[1], + device->GetOrCreateDummyTextureViewForExternalTexture()); + } + + // We must create a buffer to store parameters needed by a shader that operates on this + // external texture. + BufferDescriptor bufferDesc; + bufferDesc.size = sizeof(ExternalTextureParams); + bufferDesc.usage = wgpu::BufferUsage::Uniform | wgpu::BufferUsage::CopyDst; + bufferDesc.label = "Dawn_External_Texture_Params_Buffer"; + + DAWN_TRY_ASSIGN(mParamsBuffer, device->CreateBuffer(&bufferDesc)); + + // Dawn & Tint's YUV to RGB conversion implementation was inspired by the conversions found + // in libYUV. If this implementation needs expanded to support more colorspaces, this file + // is an excellent reference: chromium/src/third_party/libyuv/source/row_common.cc. + // + // The conversion from YUV to RGB looks like this: + // r = Y * 1.164 + V * vr + // g = Y * 1.164 - U * ug - V * vg + // b = Y * 1.164 + U * ub + // + // By changing the values of vr, vg, ub, and ug we can change the destination color space. + ExternalTextureParams params; + params.numPlanes = descriptor->plane1 == nullptr ? 1 : 2; + + switch (descriptor->colorSpace) { + case wgpu::PredefinedColorSpace::Srgb: + // Numbers derived from ITU-R recommendation for limited range BT.709 + params.vr = 1.793; + params.vg = 0.392; + params.ub = 0.813; + params.ug = 2.017; + break; + case wgpu::PredefinedColorSpace::Undefined: + break; + } + + DAWN_TRY(device->GetQueue()->WriteBuffer(mParamsBuffer.Get(), 0, ¶ms, + sizeof(ExternalTextureParams))); + + return {}; + } + + const std::array<Ref<TextureViewBase>, kMaxPlanesPerFormat>& + ExternalTextureBase::GetTextureViews() const { + return mTextureViews; + } + + MaybeError ExternalTextureBase::ValidateCanUseInSubmitNow() const { + ASSERT(!IsError()); + DAWN_INVALID_IF(mState == ExternalTextureState::Destroyed, + "Destroyed external texture %s is used in a submit.", this); + return {}; + } + + void ExternalTextureBase::APIDestroy() { + if (GetDevice()->ConsumedError(GetDevice()->ValidateObject(this))) { + return; + } + Destroy(); + } + + void ExternalTextureBase::DestroyImpl() { + mState = ExternalTextureState::Destroyed; + } + + // static + ExternalTextureBase* ExternalTextureBase::MakeError(DeviceBase* device) { + return new ExternalTextureBase(device, ObjectBase::kError); + } + + BufferBase* ExternalTextureBase::GetParamsBuffer() const { + return mParamsBuffer.Get(); + } + + ObjectType ExternalTextureBase::GetType() const { + return ObjectType::ExternalTexture; + } + +} // namespace dawn::native
diff --git a/src/dawn/native/ExternalTexture.h b/src/dawn/native/ExternalTexture.h new file mode 100644 index 0000000..e32b631 --- /dev/null +++ b/src/dawn/native/ExternalTexture.h
@@ -0,0 +1,77 @@ +// Copyright 2021 The Dawn Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#ifndef DAWNNATIVE_EXTERNALTEXTURE_H_ +#define DAWNNATIVE_EXTERNALTEXTURE_H_ + +#include "dawn/native/Error.h" +#include "dawn/native/Forward.h" +#include "dawn/native/ObjectBase.h" +#include "dawn/native/Subresource.h" + +#include <array> + +namespace dawn::native { + + class TextureViewBase; + + struct ExternalTextureParams { + uint32_t numPlanes; + float vr; + float vg; + float ub; + float ug; + }; + + MaybeError ValidateExternalTextureDescriptor(const DeviceBase* device, + const ExternalTextureDescriptor* descriptor); + + class ExternalTextureBase : public ApiObjectBase { + public: + static ResultOrError<Ref<ExternalTextureBase>> Create( + DeviceBase* device, + const ExternalTextureDescriptor* descriptor); + + BufferBase* GetParamsBuffer() const; + const std::array<Ref<TextureViewBase>, kMaxPlanesPerFormat>& GetTextureViews() const; + ObjectType GetType() const override; + + MaybeError ValidateCanUseInSubmitNow() const; + static ExternalTextureBase* MakeError(DeviceBase* device); + + void APIDestroy(); + + protected: + // Constructor used only for mocking and testing. + ExternalTextureBase(DeviceBase* device); + void DestroyImpl() override; + + ~ExternalTextureBase() override; + + private: + ExternalTextureBase(DeviceBase* device, const ExternalTextureDescriptor* descriptor); + + enum class ExternalTextureState { Alive, Destroyed }; + ExternalTextureBase(DeviceBase* device, ObjectBase::ErrorTag tag); + MaybeError Initialize(DeviceBase* device, const ExternalTextureDescriptor* descriptor); + + Ref<TextureBase> mDummyTexture; + Ref<BufferBase> mParamsBuffer; + std::array<Ref<TextureViewBase>, kMaxPlanesPerFormat> mTextureViews; + + ExternalTextureState mState; + }; +} // namespace dawn::native + +#endif // DAWNNATIVE_EXTERNALTEXTURE_H_
diff --git a/src/dawn/native/Features.cpp b/src/dawn/native/Features.cpp new file mode 100644 index 0000000..56a532c --- /dev/null +++ b/src/dawn/native/Features.cpp
@@ -0,0 +1,277 @@ +// Copyright 2019 The Dawn Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include <array> + +#include "dawn/common/Assert.h" +#include "dawn/common/BitSetIterator.h" +#include "dawn/native/Features.h" + +namespace dawn::native { + namespace { + + struct FeatureEnumAndInfo { + Feature feature; + FeatureInfo info; + bool WGPUDeviceProperties::*memberInWGPUDeviceProperties; + }; + + using FeatureEnumAndInfoList = + std::array<FeatureEnumAndInfo, static_cast<size_t>(Feature::EnumCount)>; + + static constexpr FeatureEnumAndInfoList kFeatureNameAndInfoList = { + {{Feature::TextureCompressionBC, + {"texture-compression-bc", "Support Block Compressed (BC) texture formats", + "https://bugs.chromium.org/p/dawn/issues/detail?id=42"}, + &WGPUDeviceProperties::textureCompressionBC}, + {Feature::TextureCompressionETC2, + {"texture-compression-etc2", + "Support Ericsson Texture Compressed (ETC2/EAC) texture " + "formats", + "https://bugs.chromium.org/p/dawn/issues/detail?id=955"}, + &WGPUDeviceProperties::textureCompressionETC2}, + {Feature::TextureCompressionASTC, + {"texture-compression-astc", + "Support Adaptable Scalable Texture Compressed (ASTC) " + "texture formats", + "https://bugs.chromium.org/p/dawn/issues/detail?id=955"}, + &WGPUDeviceProperties::textureCompressionASTC}, + {Feature::ShaderFloat16, + {"shader-float16", + "Support 16bit float arithmetic and declarations in uniform and storage buffers", + "https://bugs.chromium.org/p/dawn/issues/detail?id=426"}, + &WGPUDeviceProperties::shaderFloat16}, + {Feature::PipelineStatisticsQuery, + {"pipeline-statistics-query", "Support Pipeline Statistics Query", + "https://bugs.chromium.org/p/dawn/issues/detail?id=434"}, + &WGPUDeviceProperties::pipelineStatisticsQuery}, + {Feature::TimestampQuery, + {"timestamp-query", "Support Timestamp Query", + "https://bugs.chromium.org/p/dawn/issues/detail?id=434"}, + &WGPUDeviceProperties::timestampQuery}, + {Feature::DepthClamping, + {"depth-clamping", "Clamp depth to [0, 1] in NDC space instead of clipping", + "https://bugs.chromium.org/p/dawn/issues/detail?id=716"}, + &WGPUDeviceProperties::depthClamping}, + {Feature::Depth24UnormStencil8, + {"depth24unorm-stencil8", "Support depth24unorm-stencil8 texture format", + "https://bugs.chromium.org/p/dawn/issues/detail?id=690"}, + &WGPUDeviceProperties::depth24UnormStencil8}, + {Feature::Depth32FloatStencil8, + {"depth32float-stencil8", "Support depth32float-stencil8 texture format", + "https://bugs.chromium.org/p/dawn/issues/detail?id=690"}, + &WGPUDeviceProperties::depth32FloatStencil8}, + {Feature::DawnInternalUsages, + {"dawn-internal-usages", + "Add internal usages to resources to affect how the texture is allocated, but not " + "frontend validation. Other internal commands may access this usage.", + "https://dawn.googlesource.com/dawn/+/refs/heads/main/docs/dawn/features/" + "dawn_internal_usages.md"}, + &WGPUDeviceProperties::dawnInternalUsages}, + {Feature::MultiPlanarFormats, + {"multiplanar-formats", + "Import and use multi-planar texture formats with per plane views", + "https://bugs.chromium.org/p/dawn/issues/detail?id=551"}, + &WGPUDeviceProperties::multiPlanarFormats}, + {Feature::DawnNative, + {"dawn-native", "WebGPU is running on top of dawn_native.", + "https://dawn.googlesource.com/dawn/+/refs/heads/main/docs/dawn/features/" + "dawn_native.md"}, + &WGPUDeviceProperties::dawnNative}}}; + + Feature FromAPIFeature(wgpu::FeatureName feature) { + switch (feature) { + case wgpu::FeatureName::Undefined: + return Feature::InvalidEnum; + + case wgpu::FeatureName::TimestampQuery: + return Feature::TimestampQuery; + case wgpu::FeatureName::PipelineStatisticsQuery: + return Feature::PipelineStatisticsQuery; + case wgpu::FeatureName::TextureCompressionBC: + return Feature::TextureCompressionBC; + case wgpu::FeatureName::TextureCompressionETC2: + return Feature::TextureCompressionETC2; + case wgpu::FeatureName::TextureCompressionASTC: + return Feature::TextureCompressionASTC; + case wgpu::FeatureName::DepthClamping: + return Feature::DepthClamping; + case wgpu::FeatureName::Depth24UnormStencil8: + return Feature::Depth24UnormStencil8; + case wgpu::FeatureName::Depth32FloatStencil8: + return Feature::Depth32FloatStencil8; + case wgpu::FeatureName::DawnShaderFloat16: + return Feature::ShaderFloat16; + case wgpu::FeatureName::DawnInternalUsages: + return Feature::DawnInternalUsages; + case wgpu::FeatureName::DawnMultiPlanarFormats: + return Feature::MultiPlanarFormats; + case wgpu::FeatureName::DawnNative: + return Feature::DawnNative; + + case wgpu::FeatureName::IndirectFirstInstance: + return Feature::InvalidEnum; + } + return Feature::InvalidEnum; + } + + wgpu::FeatureName ToAPIFeature(Feature feature) { + switch (feature) { + case Feature::TextureCompressionBC: + return wgpu::FeatureName::TextureCompressionBC; + case Feature::TextureCompressionETC2: + return wgpu::FeatureName::TextureCompressionETC2; + case Feature::TextureCompressionASTC: + return wgpu::FeatureName::TextureCompressionASTC; + case Feature::PipelineStatisticsQuery: + return wgpu::FeatureName::PipelineStatisticsQuery; + case Feature::TimestampQuery: + return wgpu::FeatureName::TimestampQuery; + case Feature::DepthClamping: + return wgpu::FeatureName::DepthClamping; + case Feature::Depth24UnormStencil8: + return wgpu::FeatureName::Depth24UnormStencil8; + case Feature::Depth32FloatStencil8: + return wgpu::FeatureName::Depth32FloatStencil8; + case Feature::ShaderFloat16: + return wgpu::FeatureName::DawnShaderFloat16; + case Feature::DawnInternalUsages: + return wgpu::FeatureName::DawnInternalUsages; + case Feature::MultiPlanarFormats: + return wgpu::FeatureName::DawnMultiPlanarFormats; + case Feature::DawnNative: + return wgpu::FeatureName::DawnNative; + + case Feature::EnumCount: + UNREACHABLE(); + } + } + + } // anonymous namespace + + void FeaturesSet::EnableFeature(Feature feature) { + ASSERT(feature != Feature::InvalidEnum); + const size_t featureIndex = static_cast<size_t>(feature); + featuresBitSet.set(featureIndex); + } + + void FeaturesSet::EnableFeature(wgpu::FeatureName feature) { + EnableFeature(FromAPIFeature(feature)); + } + + bool FeaturesSet::IsEnabled(Feature feature) const { + ASSERT(feature != Feature::InvalidEnum); + const size_t featureIndex = static_cast<size_t>(feature); + return featuresBitSet[featureIndex]; + } + + bool FeaturesSet::IsEnabled(wgpu::FeatureName feature) const { + Feature f = FromAPIFeature(feature); + return f != Feature::InvalidEnum && IsEnabled(f); + } + + size_t FeaturesSet::EnumerateFeatures(wgpu::FeatureName* features) const { + for (uint32_t i : IterateBitSet(featuresBitSet)) { + wgpu::FeatureName feature = ToAPIFeature(static_cast<Feature>(i)); + if (features != nullptr) { + *features = feature; + features += 1; + } + } + return featuresBitSet.count(); + } + + std::vector<const char*> FeaturesSet::GetEnabledFeatureNames() const { + std::vector<const char*> enabledFeatureNames(featuresBitSet.count()); + + uint32_t index = 0; + for (uint32_t i : IterateBitSet(featuresBitSet)) { + Feature feature = static_cast<Feature>(i); + ASSERT(feature != Feature::InvalidEnum); + + const FeatureEnumAndInfo& featureNameAndInfo = kFeatureNameAndInfoList[i]; + ASSERT(featureNameAndInfo.feature == feature); + + enabledFeatureNames[index] = featureNameAndInfo.info.name; + ++index; + } + return enabledFeatureNames; + } + + void FeaturesSet::InitializeDeviceProperties(WGPUDeviceProperties* properties) const { + ASSERT(properties != nullptr); + + for (uint32_t i : IterateBitSet(featuresBitSet)) { + properties->*(kFeatureNameAndInfoList[i].memberInWGPUDeviceProperties) = true; + } + } + + wgpu::FeatureName FeatureEnumToAPIFeature(Feature feature) { + ASSERT(feature != Feature::InvalidEnum); + return ToAPIFeature(feature); + } + + FeaturesInfo::FeaturesInfo() { + for (size_t index = 0; index < kFeatureNameAndInfoList.size(); ++index) { + const FeatureEnumAndInfo& featureNameAndInfo = kFeatureNameAndInfoList[index]; + ASSERT(index == static_cast<size_t>(featureNameAndInfo.feature)); + mFeatureNameToEnumMap[featureNameAndInfo.info.name] = featureNameAndInfo.feature; + } + } + + const FeatureInfo* FeaturesInfo::GetFeatureInfo(wgpu::FeatureName feature) const { + Feature f = FromAPIFeature(feature); + if (f == Feature::InvalidEnum) { + return nullptr; + } + return &kFeatureNameAndInfoList[static_cast<size_t>(f)].info; + } + + Feature FeaturesInfo::FeatureNameToEnum(const char* featureName) const { + ASSERT(featureName); + + const auto& iter = mFeatureNameToEnumMap.find(featureName); + if (iter != mFeatureNameToEnumMap.cend()) { + return kFeatureNameAndInfoList[static_cast<size_t>(iter->second)].feature; + } + + // TODO(dawn:550): Remove this fallback logic when Chromium is updated. + constexpr std::array<std::pair<const char*, const char*>, 6> + kReplacementsForDeprecatedNames = {{ + {"texture_compression_bc", "texture-compression-bc"}, + {"depth_clamping", "depth-clamping"}, + {"pipeline_statistics_query", "pipeline-statistics-query"}, + {"shader_float16", "shader-float16"}, + {"timestamp_query", "timestamp-query"}, + {"multiplanar_formats", "multiplanar-formats"}, + }}; + for (const auto& [name, replacement] : kReplacementsForDeprecatedNames) { + if (strcmp(featureName, name) == 0) { + return FeatureNameToEnum(replacement); + } + } + + return Feature::InvalidEnum; + } + + wgpu::FeatureName FeaturesInfo::FeatureNameToAPIEnum(const char* featureName) const { + Feature f = FeatureNameToEnum(featureName); + if (f != Feature::InvalidEnum) { + return ToAPIFeature(f); + } + // Pass something invalid. + return static_cast<wgpu::FeatureName>(-1); + } + +} // namespace dawn::native
diff --git a/src/dawn/native/Features.h b/src/dawn/native/Features.h new file mode 100644 index 0000000..de75e99 --- /dev/null +++ b/src/dawn/native/Features.h
@@ -0,0 +1,83 @@ +// Copyright 2019 The Dawn Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#ifndef DAWNNATIVE_FEATURES_H_ +#define DAWNNATIVE_FEATURES_H_ + +#include <bitset> +#include <unordered_map> +#include <vector> + +#include "dawn/common/ityp_bitset.h" +#include "dawn/native/DawnNative.h" +#include "dawn/webgpu_cpp.h" + +namespace dawn::native { + + enum class Feature { + TextureCompressionBC, + TextureCompressionETC2, + TextureCompressionASTC, + ShaderFloat16, + PipelineStatisticsQuery, + TimestampQuery, + DepthClamping, + Depth24UnormStencil8, + Depth32FloatStencil8, + + // Dawn-specific + DawnInternalUsages, + MultiPlanarFormats, + DawnNative, + + EnumCount, + InvalidEnum = EnumCount, + FeatureMin = TextureCompressionBC, + }; + + // A wrapper of the bitset to store if an feature is enabled or not. This wrapper provides the + // convenience to convert the enums of enum class Feature to the indices of a bitset. + struct FeaturesSet { + std::bitset<static_cast<size_t>(Feature::EnumCount)> featuresBitSet; + + void EnableFeature(Feature feature); + void EnableFeature(wgpu::FeatureName feature); + bool IsEnabled(Feature feature) const; + bool IsEnabled(wgpu::FeatureName feature) const; + // Returns |count|, the number of features. Writes out all |count| values if |features| is + // non-null. + size_t EnumerateFeatures(wgpu::FeatureName* features) const; + std::vector<const char*> GetEnabledFeatureNames() const; + void InitializeDeviceProperties(WGPUDeviceProperties* properties) const; + }; + + wgpu::FeatureName FeatureEnumToAPIFeature(Feature feature); + + class FeaturesInfo { + public: + FeaturesInfo(); + + // Used to query the details of an feature. Return nullptr if featureName is not a valid + // name of an feature supported in Dawn + const FeatureInfo* GetFeatureInfo(wgpu::FeatureName feature) const; + Feature FeatureNameToEnum(const char* featureName) const; + wgpu::FeatureName FeatureNameToAPIEnum(const char* featureName) const; + + private: + std::unordered_map<std::string, Feature> mFeatureNameToEnumMap; + }; + +} // namespace dawn::native + +#endif // DAWNNATIVE_FEATURES_H_
diff --git a/src/dawn/native/Format.cpp b/src/dawn/native/Format.cpp new file mode 100644 index 0000000..c201729 --- /dev/null +++ b/src/dawn/native/Format.cpp
@@ -0,0 +1,492 @@ +// Copyright 2019 The Dawn Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include "dawn/native/Format.h" + +#include "dawn/native/Device.h" +#include "dawn/native/EnumMaskIterator.h" +#include "dawn/native/Features.h" +#include "dawn/native/Texture.h" + +#include <bitset> + +namespace dawn::native { + + // Format + + // TODO(dawn:527): Remove when unused. + SampleTypeBit ToSampleTypeBit(wgpu::TextureComponentType type) { + switch (type) { + case wgpu::TextureComponentType::Float: + return SampleTypeBit::Float; + case wgpu::TextureComponentType::Sint: + return SampleTypeBit::Sint; + case wgpu::TextureComponentType::Uint: + return SampleTypeBit::Uint; + case wgpu::TextureComponentType::DepthComparison: + return SampleTypeBit::Depth; + } + UNREACHABLE(); + } + + SampleTypeBit SampleTypeToSampleTypeBit(wgpu::TextureSampleType sampleType) { + switch (sampleType) { + case wgpu::TextureSampleType::Float: + case wgpu::TextureSampleType::UnfilterableFloat: + case wgpu::TextureSampleType::Sint: + case wgpu::TextureSampleType::Uint: + case wgpu::TextureSampleType::Depth: + case wgpu::TextureSampleType::Undefined: + // When the compiler complains that you need to add a case statement here, please + // also add a corresponding static assert below! + break; + } + + static_assert(static_cast<uint32_t>(wgpu::TextureSampleType::Undefined) == 0); + if (sampleType == wgpu::TextureSampleType::Undefined) { + return SampleTypeBit::None; + } + + // Check that SampleTypeBit bits are in the same position / order as the respective + // wgpu::TextureSampleType value. + static_assert(SampleTypeBit::Float == + static_cast<SampleTypeBit>( + 1 << (static_cast<uint32_t>(wgpu::TextureSampleType::Float) - 1))); + static_assert( + SampleTypeBit::UnfilterableFloat == + static_cast<SampleTypeBit>( + 1 << (static_cast<uint32_t>(wgpu::TextureSampleType::UnfilterableFloat) - 1))); + static_assert(SampleTypeBit::Uint == + static_cast<SampleTypeBit>( + 1 << (static_cast<uint32_t>(wgpu::TextureSampleType::Uint) - 1))); + static_assert(SampleTypeBit::Sint == + static_cast<SampleTypeBit>( + 1 << (static_cast<uint32_t>(wgpu::TextureSampleType::Sint) - 1))); + static_assert(SampleTypeBit::Depth == + static_cast<SampleTypeBit>( + 1 << (static_cast<uint32_t>(wgpu::TextureSampleType::Depth) - 1))); + return static_cast<SampleTypeBit>(1 << (static_cast<uint32_t>(sampleType) - 1)); + } + + bool Format::IsColor() const { + return aspects == Aspect::Color; + } + + bool Format::HasDepth() const { + return (aspects & Aspect::Depth) != 0; + } + + bool Format::HasStencil() const { + return (aspects & Aspect::Stencil) != 0; + } + + bool Format::HasDepthOrStencil() const { + return (aspects & (Aspect::Depth | Aspect::Stencil)) != 0; + } + + bool Format::IsMultiPlanar() const { + return (aspects & (Aspect::Plane0 | Aspect::Plane1)) != 0; + } + + bool Format::CopyCompatibleWith(const Format& format) const { + // TODO(crbug.com/dawn/1332): Add a Format compatibility matrix. + return baseFormat == format.baseFormat; + } + + bool Format::ViewCompatibleWith(const Format& format) const { + // TODO(crbug.com/dawn/1332): Add a Format compatibility matrix. + return baseFormat == format.baseFormat; + } + + const AspectInfo& Format::GetAspectInfo(wgpu::TextureAspect aspect) const { + return GetAspectInfo(SelectFormatAspects(*this, aspect)); + } + + const AspectInfo& Format::GetAspectInfo(Aspect aspect) const { + ASSERT(HasOneBit(aspect)); + ASSERT(aspects & aspect); + const size_t aspectIndex = GetAspectIndex(aspect); + ASSERT(aspectIndex < GetAspectCount(aspects)); + return aspectInfo[aspectIndex]; + } + + FormatIndex Format::GetIndex() const { + return ComputeFormatIndex(format); + } + + // FormatSet implementation + + bool FormatSet::operator[](const Format& format) const { + return Base::operator[](format.GetIndex()); + } + + typename std::bitset<kKnownFormatCount>::reference FormatSet::operator[](const Format& format) { + return Base::operator[](format.GetIndex()); + } + + // Implementation details of the format table of the DeviceBase + + // For the enum for formats are packed but this might change when we have a broader feature + // mechanism for webgpu.h. Formats start at 1 because 0 is the undefined format. + FormatIndex ComputeFormatIndex(wgpu::TextureFormat format) { + // This takes advantage of overflows to make the index of TextureFormat::Undefined outside + // of the range of the FormatTable. + static_assert(static_cast<uint32_t>(wgpu::TextureFormat::Undefined) - 1 > + kKnownFormatCount); + return static_cast<FormatIndex>(static_cast<uint32_t>(format) - 1); + } + + FormatTable BuildFormatTable(const DeviceBase* device) { + FormatTable table; + FormatSet formatsSet; + + static constexpr SampleTypeBit kAnyFloat = + SampleTypeBit::Float | SampleTypeBit::UnfilterableFloat; + + auto AddFormat = [&table, &formatsSet](Format format) { + FormatIndex index = ComputeFormatIndex(format.format); + ASSERT(index < table.size()); + + // This checks that each format is set at most once, the first part of checking that all + // formats are set exactly once. + ASSERT(!formatsSet[index]); + + // Vulkan describes bytesPerRow in units of texels. If there's any format for which this + // ASSERT isn't true, then additional validation on bytesPerRow must be added. + const bool hasMultipleAspects = !HasOneBit(format.aspects); + ASSERT(hasMultipleAspects || + (kTextureBytesPerRowAlignment % format.aspectInfo[0].block.byteSize) == 0); + + table[index] = format; + formatsSet.set(index); + }; + + auto AddColorFormat = + [&AddFormat](wgpu::TextureFormat format, bool renderable, bool supportsStorageUsage, + bool supportsMultisample, bool supportsResolveTarget, uint32_t byteSize, + SampleTypeBit sampleTypes, uint8_t componentCount, + wgpu::TextureFormat baseFormat = wgpu::TextureFormat::Undefined) { + Format internalFormat; + internalFormat.format = format; + internalFormat.isRenderable = renderable; + internalFormat.isCompressed = false; + internalFormat.isSupported = true; + internalFormat.supportsStorageUsage = supportsStorageUsage; + + if (supportsMultisample) { + ASSERT(renderable); + } + internalFormat.supportsMultisample = supportsMultisample; + internalFormat.supportsResolveTarget = supportsResolveTarget; + internalFormat.aspects = Aspect::Color; + internalFormat.componentCount = componentCount; + + // Default baseFormat of each color formats should be themselves. + if (baseFormat == wgpu::TextureFormat::Undefined) { + internalFormat.baseFormat = format; + } else { + internalFormat.baseFormat = baseFormat; + } + + AspectInfo* firstAspect = internalFormat.aspectInfo.data(); + firstAspect->block.byteSize = byteSize; + firstAspect->block.width = 1; + firstAspect->block.height = 1; + if (HasOneBit(sampleTypes)) { + switch (sampleTypes) { + case SampleTypeBit::Float: + case SampleTypeBit::UnfilterableFloat: + firstAspect->baseType = wgpu::TextureComponentType::Float; + break; + case SampleTypeBit::Sint: + firstAspect->baseType = wgpu::TextureComponentType::Sint; + break; + case SampleTypeBit::Uint: + firstAspect->baseType = wgpu::TextureComponentType::Uint; + break; + default: + UNREACHABLE(); + } + } else { + ASSERT((sampleTypes & SampleTypeBit::Float) != 0); + firstAspect->baseType = wgpu::TextureComponentType::Float; + } + firstAspect->supportedSampleTypes = sampleTypes; + firstAspect->format = format; + AddFormat(internalFormat); + }; + + auto AddDepthFormat = [&AddFormat](wgpu::TextureFormat format, uint32_t byteSize, + bool isSupported) { + Format internalFormat; + internalFormat.format = format; + internalFormat.baseFormat = format; + internalFormat.isRenderable = true; + internalFormat.isCompressed = false; + internalFormat.isSupported = isSupported; + internalFormat.supportsStorageUsage = false; + internalFormat.supportsMultisample = true; + internalFormat.supportsResolveTarget = false; + internalFormat.aspects = Aspect::Depth; + internalFormat.componentCount = 1; + + AspectInfo* firstAspect = internalFormat.aspectInfo.data(); + firstAspect->block.byteSize = byteSize; + firstAspect->block.width = 1; + firstAspect->block.height = 1; + firstAspect->baseType = wgpu::TextureComponentType::Float; + firstAspect->supportedSampleTypes = SampleTypeBit::Depth; + firstAspect->format = format; + AddFormat(internalFormat); + }; + + auto AddStencilFormat = [&AddFormat](wgpu::TextureFormat format, bool isSupported) { + Format internalFormat; + internalFormat.format = format; + internalFormat.baseFormat = format; + internalFormat.isRenderable = true; + internalFormat.isCompressed = false; + internalFormat.isSupported = isSupported; + internalFormat.supportsStorageUsage = false; + internalFormat.supportsMultisample = true; + internalFormat.supportsResolveTarget = false; + internalFormat.aspects = Aspect::Stencil; + internalFormat.componentCount = 1; + + // Duplicate the data for the stencil aspect in both the first and second aspect info. + // - aspectInfo[0] is used by AddMultiAspectFormat to copy the info for the whole + // stencil8 aspect of depth-stencil8 formats. + // - aspectInfo[1] is the actual info used in the rest of Dawn since + // GetAspectIndex(Aspect::Stencil) is 1. + ASSERT(GetAspectIndex(Aspect::Stencil) == 1); + + internalFormat.aspectInfo[0].block.byteSize = 1; + internalFormat.aspectInfo[0].block.width = 1; + internalFormat.aspectInfo[0].block.height = 1; + internalFormat.aspectInfo[0].baseType = wgpu::TextureComponentType::Uint; + internalFormat.aspectInfo[0].supportedSampleTypes = SampleTypeBit::Uint; + internalFormat.aspectInfo[0].format = format; + + internalFormat.aspectInfo[1] = internalFormat.aspectInfo[0]; + + AddFormat(internalFormat); + }; + + auto AddCompressedFormat = + [&AddFormat](wgpu::TextureFormat format, uint32_t byteSize, uint32_t width, + uint32_t height, bool isSupported, uint8_t componentCount, + wgpu::TextureFormat baseFormat = wgpu::TextureFormat::Undefined) { + Format internalFormat; + internalFormat.format = format; + internalFormat.isRenderable = false; + internalFormat.isCompressed = true; + internalFormat.isSupported = isSupported; + internalFormat.supportsStorageUsage = false; + internalFormat.supportsMultisample = false; + internalFormat.supportsResolveTarget = false; + internalFormat.aspects = Aspect::Color; + internalFormat.componentCount = componentCount; + + // Default baseFormat of each compressed formats should be themselves. + if (baseFormat == wgpu::TextureFormat::Undefined) { + internalFormat.baseFormat = format; + } else { + internalFormat.baseFormat = baseFormat; + } + + AspectInfo* firstAspect = internalFormat.aspectInfo.data(); + firstAspect->block.byteSize = byteSize; + firstAspect->block.width = width; + firstAspect->block.height = height; + firstAspect->baseType = wgpu::TextureComponentType::Float; + firstAspect->supportedSampleTypes = kAnyFloat; + firstAspect->format = format; + AddFormat(internalFormat); + }; + + auto AddMultiAspectFormat = + [&AddFormat, &table](wgpu::TextureFormat format, Aspect aspects, + wgpu::TextureFormat firstFormat, wgpu::TextureFormat secondFormat, + bool isRenderable, bool isSupported, bool supportsMultisample, + uint8_t componentCount) { + Format internalFormat; + internalFormat.format = format; + internalFormat.baseFormat = format; + internalFormat.isRenderable = isRenderable; + internalFormat.isCompressed = false; + internalFormat.isSupported = isSupported; + internalFormat.supportsStorageUsage = false; + internalFormat.supportsMultisample = supportsMultisample; + internalFormat.supportsResolveTarget = false; + internalFormat.aspects = aspects; + internalFormat.componentCount = componentCount; + + // Multi aspect formats just copy information about single-aspect formats. This + // means that the single-plane formats must have been added before multi-aspect + // ones. (it is ASSERTed below). + const FormatIndex firstFormatIndex = ComputeFormatIndex(firstFormat); + const FormatIndex secondFormatIndex = ComputeFormatIndex(secondFormat); + + ASSERT(table[firstFormatIndex].aspectInfo[0].format != + wgpu::TextureFormat::Undefined); + ASSERT(table[secondFormatIndex].aspectInfo[0].format != + wgpu::TextureFormat::Undefined); + + internalFormat.aspectInfo[0] = table[firstFormatIndex].aspectInfo[0]; + internalFormat.aspectInfo[1] = table[secondFormatIndex].aspectInfo[0]; + + AddFormat(internalFormat); + }; + + // clang-format off + // 1 byte color formats + AddColorFormat(wgpu::TextureFormat::R8Unorm, true, false, true, true, 1, kAnyFloat, 1); + AddColorFormat(wgpu::TextureFormat::R8Snorm, false, false, false, false, 1, kAnyFloat, 1); + AddColorFormat(wgpu::TextureFormat::R8Uint, true, false, true, false, 1, SampleTypeBit::Uint, 1); + AddColorFormat(wgpu::TextureFormat::R8Sint, true, false, true, false, 1, SampleTypeBit::Sint, 1); + + // 2 bytes color formats + AddColorFormat(wgpu::TextureFormat::R16Uint, true, false, true, false, 2, SampleTypeBit::Uint, 1); + AddColorFormat(wgpu::TextureFormat::R16Sint, true, false, true, false, 2, SampleTypeBit::Sint, 1); + AddColorFormat(wgpu::TextureFormat::R16Float, true, false, true, true, 2, kAnyFloat, 1); + AddColorFormat(wgpu::TextureFormat::RG8Unorm, true, false, true, true, 2, kAnyFloat, 2); + AddColorFormat(wgpu::TextureFormat::RG8Snorm, false, false, false, false, 2, kAnyFloat, 2); + AddColorFormat(wgpu::TextureFormat::RG8Uint, true, false, true, false, 2, SampleTypeBit::Uint, 2); + AddColorFormat(wgpu::TextureFormat::RG8Sint, true, false, true, false, 2, SampleTypeBit::Sint, 2); + + // 4 bytes color formats + AddColorFormat(wgpu::TextureFormat::R32Uint, true, true, false, false, 4, SampleTypeBit::Uint, 1); + AddColorFormat(wgpu::TextureFormat::R32Sint, true, true, false, false, 4, SampleTypeBit::Sint, 1); + AddColorFormat(wgpu::TextureFormat::R32Float, true, true, true, false, 4, SampleTypeBit::UnfilterableFloat, 1); + AddColorFormat(wgpu::TextureFormat::RG16Uint, true, false, true, false, 4, SampleTypeBit::Uint, 2); + AddColorFormat(wgpu::TextureFormat::RG16Sint, true, false, true, false, 4, SampleTypeBit::Sint, 2); + AddColorFormat(wgpu::TextureFormat::RG16Float, true, false, true, true, 4, kAnyFloat, 2); + AddColorFormat(wgpu::TextureFormat::RGBA8Unorm, true, true, true, true, 4, kAnyFloat, 4); + AddColorFormat(wgpu::TextureFormat::RGBA8UnormSrgb, true, false, true, true, 4, kAnyFloat, 4, wgpu::TextureFormat::RGBA8Unorm); + AddColorFormat(wgpu::TextureFormat::RGBA8Snorm, false, true, false, false, 4, kAnyFloat, 4); + AddColorFormat(wgpu::TextureFormat::RGBA8Uint, true, true, true, false, 4, SampleTypeBit::Uint, 4); + AddColorFormat(wgpu::TextureFormat::RGBA8Sint, true, true, true, false, 4, SampleTypeBit::Sint, 4); + AddColorFormat(wgpu::TextureFormat::BGRA8Unorm, true, false, true, true, 4, kAnyFloat, 4); + AddColorFormat(wgpu::TextureFormat::BGRA8UnormSrgb, true, false, true, true, 4, kAnyFloat, 4, wgpu::TextureFormat::BGRA8Unorm); + AddColorFormat(wgpu::TextureFormat::RGB10A2Unorm, true, false, true, true, 4, kAnyFloat, 4); + + AddColorFormat(wgpu::TextureFormat::RG11B10Ufloat, false, false, false, false, 4, kAnyFloat, 3); + AddColorFormat(wgpu::TextureFormat::RGB9E5Ufloat, false, false, false, false, 4, kAnyFloat, 3); + + // 8 bytes color formats + AddColorFormat(wgpu::TextureFormat::RG32Uint, true, true, false, false, 8, SampleTypeBit::Uint, 2); + AddColorFormat(wgpu::TextureFormat::RG32Sint, true, true, false, false, 8, SampleTypeBit::Sint, 2); + AddColorFormat(wgpu::TextureFormat::RG32Float, true, true, false, false, 8, SampleTypeBit::UnfilterableFloat, 2); + AddColorFormat(wgpu::TextureFormat::RGBA16Uint, true, true, true, false, 8, SampleTypeBit::Uint, 4); + AddColorFormat(wgpu::TextureFormat::RGBA16Sint, true, true, true, false, 8, SampleTypeBit::Sint, 4); + AddColorFormat(wgpu::TextureFormat::RGBA16Float, true, true, true, true, 8, kAnyFloat, 4); + + // 16 bytes color formats + AddColorFormat(wgpu::TextureFormat::RGBA32Uint, true, true, false, false, 16, SampleTypeBit::Uint, 4); + AddColorFormat(wgpu::TextureFormat::RGBA32Sint, true, true, false, false, 16, SampleTypeBit::Sint, 4); + AddColorFormat(wgpu::TextureFormat::RGBA32Float, true, true, false, false, 16, SampleTypeBit::UnfilterableFloat, 4); + + // Depth-stencil formats + AddStencilFormat(wgpu::TextureFormat::Stencil8, true); + AddDepthFormat(wgpu::TextureFormat::Depth16Unorm, 2, true); + // TODO(crbug.com/dawn/843): This is 4 because we read this to perform zero initialization, + // and textures are always use depth32float. We should improve this to be more robust. Perhaps, + // using 0 here to mean "unsized" and adding a backend-specific query for the block size. + AddDepthFormat(wgpu::TextureFormat::Depth24Plus, 4, true); + AddMultiAspectFormat(wgpu::TextureFormat::Depth24PlusStencil8, + Aspect::Depth | Aspect::Stencil, wgpu::TextureFormat::Depth24Plus, wgpu::TextureFormat::Stencil8, true, true, true, 2); + bool isD24S8Supported = device->IsFeatureEnabled(Feature::Depth24UnormStencil8); + AddMultiAspectFormat(wgpu::TextureFormat::Depth24UnormStencil8, + Aspect::Depth | Aspect::Stencil, wgpu::TextureFormat::Depth24Plus, wgpu::TextureFormat::Stencil8, true, isD24S8Supported, true, 2); + AddDepthFormat(wgpu::TextureFormat::Depth32Float, 4, true); + bool isD32S8Supported = device->IsFeatureEnabled(Feature::Depth32FloatStencil8); + AddMultiAspectFormat(wgpu::TextureFormat::Depth32FloatStencil8, + Aspect::Depth | Aspect::Stencil, wgpu::TextureFormat::Depth32Float, wgpu::TextureFormat::Stencil8, true, isD32S8Supported, true, 2); + + // BC compressed formats + bool isBCFormatSupported = device->IsFeatureEnabled(Feature::TextureCompressionBC); + AddCompressedFormat(wgpu::TextureFormat::BC1RGBAUnorm, 8, 4, 4, isBCFormatSupported, 4); + AddCompressedFormat(wgpu::TextureFormat::BC1RGBAUnormSrgb, 8, 4, 4, isBCFormatSupported, 4, wgpu::TextureFormat::BC1RGBAUnorm); + AddCompressedFormat(wgpu::TextureFormat::BC4RSnorm, 8, 4, 4, isBCFormatSupported, 1); + AddCompressedFormat(wgpu::TextureFormat::BC4RUnorm, 8, 4, 4, isBCFormatSupported, 1); + AddCompressedFormat(wgpu::TextureFormat::BC2RGBAUnorm, 16, 4, 4, isBCFormatSupported, 4); + AddCompressedFormat(wgpu::TextureFormat::BC2RGBAUnormSrgb, 16, 4, 4, isBCFormatSupported, 4, wgpu::TextureFormat::BC2RGBAUnorm); + AddCompressedFormat(wgpu::TextureFormat::BC3RGBAUnorm, 16, 4, 4, isBCFormatSupported, 4); + AddCompressedFormat(wgpu::TextureFormat::BC3RGBAUnormSrgb, 16, 4, 4, isBCFormatSupported, 4, wgpu::TextureFormat::BC3RGBAUnorm); + AddCompressedFormat(wgpu::TextureFormat::BC5RGSnorm, 16, 4, 4, isBCFormatSupported, 2); + AddCompressedFormat(wgpu::TextureFormat::BC5RGUnorm, 16, 4, 4, isBCFormatSupported, 2); + AddCompressedFormat(wgpu::TextureFormat::BC6HRGBFloat, 16, 4, 4, isBCFormatSupported, 3); + AddCompressedFormat(wgpu::TextureFormat::BC6HRGBUfloat, 16, 4, 4, isBCFormatSupported, 3); + AddCompressedFormat(wgpu::TextureFormat::BC7RGBAUnorm, 16, 4, 4, isBCFormatSupported, 4); + AddCompressedFormat(wgpu::TextureFormat::BC7RGBAUnormSrgb, 16, 4, 4, isBCFormatSupported, 4, wgpu::TextureFormat::BC7RGBAUnorm); + + // ETC2/EAC compressed formats + bool isETC2FormatSupported = device->IsFeatureEnabled(Feature::TextureCompressionETC2); + AddCompressedFormat(wgpu::TextureFormat::ETC2RGB8Unorm, 8, 4, 4, isETC2FormatSupported, 3); + AddCompressedFormat(wgpu::TextureFormat::ETC2RGB8UnormSrgb, 8, 4, 4, isETC2FormatSupported, 3, wgpu::TextureFormat::ETC2RGB8Unorm); + AddCompressedFormat(wgpu::TextureFormat::ETC2RGB8A1Unorm, 8, 4, 4, isETC2FormatSupported, 4); + AddCompressedFormat(wgpu::TextureFormat::ETC2RGB8A1UnormSrgb, 8, 4, 4, isETC2FormatSupported, 4, wgpu::TextureFormat::ETC2RGB8A1Unorm); + AddCompressedFormat(wgpu::TextureFormat::ETC2RGBA8Unorm, 16, 4, 4, isETC2FormatSupported, 4); + AddCompressedFormat(wgpu::TextureFormat::ETC2RGBA8UnormSrgb, 16, 4, 4, isETC2FormatSupported, 4, wgpu::TextureFormat::ETC2RGBA8Unorm); + AddCompressedFormat(wgpu::TextureFormat::EACR11Unorm, 8, 4, 4, isETC2FormatSupported, 1); + AddCompressedFormat(wgpu::TextureFormat::EACR11Snorm, 8, 4, 4, isETC2FormatSupported, 1); + AddCompressedFormat(wgpu::TextureFormat::EACRG11Unorm, 16, 4, 4, isETC2FormatSupported, 2); + AddCompressedFormat(wgpu::TextureFormat::EACRG11Snorm, 16, 4, 4, isETC2FormatSupported, 2); + + // ASTC compressed formats + bool isASTCFormatSupported = device->IsFeatureEnabled(Feature::TextureCompressionASTC); + AddCompressedFormat(wgpu::TextureFormat::ASTC4x4Unorm, 16, 4, 4, isASTCFormatSupported, 4); + AddCompressedFormat(wgpu::TextureFormat::ASTC4x4UnormSrgb, 16, 4, 4, isASTCFormatSupported, 4, wgpu::TextureFormat::ASTC4x4Unorm); + AddCompressedFormat(wgpu::TextureFormat::ASTC5x4Unorm, 16, 5, 4, isASTCFormatSupported, 4); + AddCompressedFormat(wgpu::TextureFormat::ASTC5x4UnormSrgb, 16, 5, 4, isASTCFormatSupported, 4, wgpu::TextureFormat::ASTC5x4Unorm); + AddCompressedFormat(wgpu::TextureFormat::ASTC5x5Unorm, 16, 5, 5, isASTCFormatSupported, 4); + AddCompressedFormat(wgpu::TextureFormat::ASTC5x5UnormSrgb, 16, 5, 5, isASTCFormatSupported, 4, wgpu::TextureFormat::ASTC5x5Unorm); + AddCompressedFormat(wgpu::TextureFormat::ASTC6x5Unorm, 16, 6, 5, isASTCFormatSupported, 4); + AddCompressedFormat(wgpu::TextureFormat::ASTC6x5UnormSrgb, 16, 6, 5, isASTCFormatSupported, 4, wgpu::TextureFormat::ASTC6x5Unorm); + AddCompressedFormat(wgpu::TextureFormat::ASTC6x6Unorm, 16, 6, 6, isASTCFormatSupported, 4); + AddCompressedFormat(wgpu::TextureFormat::ASTC6x6UnormSrgb, 16, 6, 6, isASTCFormatSupported, 4, wgpu::TextureFormat::ASTC6x6Unorm); + AddCompressedFormat(wgpu::TextureFormat::ASTC8x5Unorm, 16, 8, 5, isASTCFormatSupported, 4); + AddCompressedFormat(wgpu::TextureFormat::ASTC8x5UnormSrgb, 16, 8, 5, isASTCFormatSupported, 4, wgpu::TextureFormat::ASTC8x5Unorm); + AddCompressedFormat(wgpu::TextureFormat::ASTC8x6Unorm, 16, 8, 6, isASTCFormatSupported, 4); + AddCompressedFormat(wgpu::TextureFormat::ASTC8x6UnormSrgb, 16, 8, 6, isASTCFormatSupported, 4, wgpu::TextureFormat::ASTC8x6Unorm); + AddCompressedFormat(wgpu::TextureFormat::ASTC8x8Unorm, 16, 8, 8, isASTCFormatSupported, 4); + AddCompressedFormat(wgpu::TextureFormat::ASTC8x8UnormSrgb, 16, 8, 8, isASTCFormatSupported, 4, wgpu::TextureFormat::ASTC8x8Unorm); + AddCompressedFormat(wgpu::TextureFormat::ASTC10x5Unorm, 16, 10, 5, isASTCFormatSupported, 4); + AddCompressedFormat(wgpu::TextureFormat::ASTC10x5UnormSrgb, 16, 10, 5, isASTCFormatSupported, 4, wgpu::TextureFormat::ASTC10x5Unorm); + AddCompressedFormat(wgpu::TextureFormat::ASTC10x6Unorm, 16, 10, 6, isASTCFormatSupported, 4); + AddCompressedFormat(wgpu::TextureFormat::ASTC10x6UnormSrgb, 16, 10, 6, isASTCFormatSupported, 4, wgpu::TextureFormat::ASTC10x6Unorm); + AddCompressedFormat(wgpu::TextureFormat::ASTC10x8Unorm, 16, 10, 8, isASTCFormatSupported, 4); + AddCompressedFormat(wgpu::TextureFormat::ASTC10x8UnormSrgb, 16, 10, 8, isASTCFormatSupported, 4, wgpu::TextureFormat::ASTC10x8Unorm); + AddCompressedFormat(wgpu::TextureFormat::ASTC10x10Unorm, 16, 10, 10, isASTCFormatSupported, 4); + AddCompressedFormat(wgpu::TextureFormat::ASTC10x10UnormSrgb, 16, 10, 10, isASTCFormatSupported, 4, wgpu::TextureFormat::ASTC10x10Unorm); + AddCompressedFormat(wgpu::TextureFormat::ASTC12x10Unorm, 16, 12, 10, isASTCFormatSupported, 4); + AddCompressedFormat(wgpu::TextureFormat::ASTC12x10UnormSrgb, 16, 12, 10, isASTCFormatSupported, 4, wgpu::TextureFormat::ASTC12x10Unorm); + AddCompressedFormat(wgpu::TextureFormat::ASTC12x12Unorm, 16, 12, 12, isASTCFormatSupported, 4); + AddCompressedFormat(wgpu::TextureFormat::ASTC12x12UnormSrgb, 16, 12, 12, isASTCFormatSupported, 4, wgpu::TextureFormat::ASTC12x12Unorm); + + // multi-planar formats + const bool isMultiPlanarFormatSupported = device->IsFeatureEnabled(Feature::MultiPlanarFormats); + AddMultiAspectFormat(wgpu::TextureFormat::R8BG8Biplanar420Unorm, Aspect::Plane0 | Aspect::Plane1, + wgpu::TextureFormat::R8Unorm, wgpu::TextureFormat::RG8Unorm, false, isMultiPlanarFormatSupported, false, 3); + + // clang-format on + + // This checks that each format is set at least once, the second part of checking that all + // formats are checked exactly once. + ASSERT(formatsSet.all()); + + return table; + } + +} // namespace dawn::native
diff --git a/src/dawn/native/Format.h b/src/dawn/native/Format.h new file mode 100644 index 0000000..457c6cb --- /dev/null +++ b/src/dawn/native/Format.h
@@ -0,0 +1,173 @@ +// Copyright 2019 The Dawn Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#ifndef DAWNNATIVE_FORMAT_H_ +#define DAWNNATIVE_FORMAT_H_ + +#include "dawn/native/dawn_platform.h" + +#include "dawn/common/TypedInteger.h" +#include "dawn/common/ityp_array.h" +#include "dawn/common/ityp_bitset.h" +#include "dawn/native/EnumClassBitmasks.h" +#include "dawn/native/Error.h" +#include "dawn/native/Subresource.h" + +#include <array> + +// About multi-planar formats. +// +// Dawn supports additional multi-planar formats when the multiplanar-formats extension is enabled. +// When enabled, Dawn treats planar data as sub-resources (ie. 1 sub-resource == 1 view == 1 plane). +// A multi-planar format name encodes the channel mapping and order of planes. For example, +// R8BG8Biplanar420Unorm is YUV 4:2:0 where Plane 0 = R8, and Plane 1 = BG8. +// +// Requirements: +// * Plane aspects cannot be combined with color, depth, or stencil aspects. +// * Only compatible multi-planar formats of planes can be used with multi-planar texture +// formats. +// * Can't access multiple planes without creating per plane views (no color conversion). +// * Multi-planar format cannot be written or read without a per plane view. +// +// TODO(dawn:551): Consider moving this comment. + +namespace dawn::native { + + enum class Aspect : uint8_t; + class DeviceBase; + + // This mirrors wgpu::TextureSampleType as a bitmask instead. + enum class SampleTypeBit : uint8_t { + None = 0x0, + Float = 0x1, + UnfilterableFloat = 0x2, + Depth = 0x4, + Sint = 0x8, + Uint = 0x10, + }; + + // Converts an wgpu::TextureComponentType to its bitmask representation. + SampleTypeBit ToSampleTypeBit(wgpu::TextureComponentType type); + // Converts an wgpu::TextureSampleType to its bitmask representation. + SampleTypeBit SampleTypeToSampleTypeBit(wgpu::TextureSampleType sampleType); + + struct TexelBlockInfo { + uint32_t byteSize; + uint32_t width; + uint32_t height; + }; + + struct AspectInfo { + TexelBlockInfo block; + // TODO(crbug.com/dawn/367): Replace TextureComponentType with TextureSampleType, or make it + // an internal Dawn enum. + wgpu::TextureComponentType baseType; + SampleTypeBit supportedSampleTypes; + wgpu::TextureFormat format = wgpu::TextureFormat::Undefined; + }; + + // The number of formats Dawn knows about. Asserts in BuildFormatTable ensure that this is the + // exact number of known format. + static constexpr uint32_t kKnownFormatCount = 96; + + using FormatIndex = TypedInteger<struct FormatIndexT, uint32_t>; + + struct Format; + using FormatTable = ityp::array<FormatIndex, Format, kKnownFormatCount>; + + // A wgpu::TextureFormat along with all the information about it necessary for validation. + struct Format { + wgpu::TextureFormat format; + + // TODO(crbug.com/dawn/1332): These members could be stored in a Format capability matrix. + bool isRenderable; + bool isCompressed; + // A format can be known but not supported because it is part of a disabled extension. + bool isSupported; + bool supportsStorageUsage; + bool supportsMultisample; + bool supportsResolveTarget; + Aspect aspects; + // Only used for renderable color formats, number of color channels. + uint8_t componentCount; + + bool IsColor() const; + bool HasDepth() const; + bool HasStencil() const; + bool HasDepthOrStencil() const; + + // IsMultiPlanar() returns true if the format allows selecting a plane index. This is only + // allowed by multi-planar formats (ex. NV12). + bool IsMultiPlanar() const; + + const AspectInfo& GetAspectInfo(wgpu::TextureAspect aspect) const; + const AspectInfo& GetAspectInfo(Aspect aspect) const; + + // The index of the format in the list of all known formats: a unique number for each format + // in [0, kKnownFormatCount) + FormatIndex GetIndex() const; + + // baseFormat represents the memory layout of the format. + // If two formats has the same baseFormat, they could copy to and be viewed as the other + // format. Currently two formats have the same baseFormat if they differ only in sRGB-ness. + wgpu::TextureFormat baseFormat; + + // Returns true if the formats are copy compatible. + // Currently means they differ only in sRGB-ness. + bool CopyCompatibleWith(const Format& format) const; + + // Returns true if the formats are texture view format compatible. + // Currently means they differ only in sRGB-ness. + bool ViewCompatibleWith(const Format& format) const; + + private: + // Used to store the aspectInfo for one or more planes. For single plane "color" formats, + // only the first aspect info or aspectInfo[0] is valid. For depth-stencil, the first aspect + // info is depth and the second aspect info is stencil. For multi-planar formats, + // aspectInfo[i] is the ith plane. + std::array<AspectInfo, kMaxPlanesPerFormat> aspectInfo; + + friend FormatTable BuildFormatTable(const DeviceBase* device); + }; + + class FormatSet : public ityp::bitset<FormatIndex, kKnownFormatCount> { + using Base = ityp::bitset<FormatIndex, kKnownFormatCount>; + + public: + using Base::Base; + using Base::operator[]; + + bool operator[](const Format& format) const; + typename Base::reference operator[](const Format& format); + }; + + // Implementation details of the format table in the device. + + // Returns the index of a format in the FormatTable. + FormatIndex ComputeFormatIndex(wgpu::TextureFormat format); + // Builds the format table with the extensions enabled on the device. + FormatTable BuildFormatTable(const DeviceBase* device); + +} // namespace dawn::native + +namespace dawn { + + template <> + struct IsDawnBitmask<dawn::native::SampleTypeBit> { + static constexpr bool enable = true; + }; + +} // namespace dawn + +#endif // DAWNNATIVE_FORMAT_H_
diff --git a/src/dawn/native/Forward.h b/src/dawn/native/Forward.h new file mode 100644 index 0000000..36b092c --- /dev/null +++ b/src/dawn/native/Forward.h
@@ -0,0 +1,71 @@ +// Copyright 2017 The Dawn Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#ifndef DAWNNATIVE_FORWARD_H_ +#define DAWNNATIVE_FORWARD_H_ + +#include <cstdint> + +template <typename T> +class Ref; + +namespace dawn::native { + + enum class ObjectType : uint32_t; + + class AdapterBase; + class BindGroupBase; + class BindGroupLayoutBase; + class BufferBase; + class ComputePipelineBase; + class CommandBufferBase; + class CommandEncoder; + class ComputePassEncoder; + class ExternalTextureBase; + class InstanceBase; + class PipelineBase; + class PipelineLayoutBase; + class QuerySetBase; + class QueueBase; + class RenderBundleBase; + class RenderBundleEncoder; + class RenderPassEncoder; + class RenderPipelineBase; + class ResourceHeapBase; + class SamplerBase; + class Surface; + class ShaderModuleBase; + class StagingBufferBase; + class SwapChainBase; + class NewSwapChainBase; + class TextureBase; + class TextureViewBase; + + class DeviceBase; + + template <typename T> + class PerStage; + + struct Format; + + // Aliases for frontend-only types. + using CommandEncoderBase = CommandEncoder; + using ComputePassEncoderBase = ComputePassEncoder; + using RenderBundleEncoderBase = RenderBundleEncoder; + using RenderPassEncoderBase = RenderPassEncoder; + using SurfaceBase = Surface; + +} // namespace dawn::native + +#endif // DAWNNATIVE_FORWARD_H_
diff --git a/src/dawn/native/IndirectDrawMetadata.cpp b/src/dawn/native/IndirectDrawMetadata.cpp new file mode 100644 index 0000000..ebe0e7f --- /dev/null +++ b/src/dawn/native/IndirectDrawMetadata.cpp
@@ -0,0 +1,193 @@ +// Copyright 2021 The Dawn Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include "dawn/native/IndirectDrawMetadata.h" + +#include "dawn/common/Constants.h" +#include "dawn/common/RefCounted.h" +#include "dawn/native/IndirectDrawValidationEncoder.h" +#include "dawn/native/Limits.h" +#include "dawn/native/RenderBundle.h" + +#include <algorithm> +#include <utility> + +namespace dawn::native { + + uint32_t ComputeMaxIndirectValidationBatchOffsetRange(const CombinedLimits& limits) { + return limits.v1.maxStorageBufferBindingSize - limits.v1.minStorageBufferOffsetAlignment - + kDrawIndexedIndirectSize; + } + + IndirectDrawMetadata::IndexedIndirectBufferValidationInfo::IndexedIndirectBufferValidationInfo( + BufferBase* indirectBuffer) + : mIndirectBuffer(indirectBuffer) { + } + + void IndirectDrawMetadata::IndexedIndirectBufferValidationInfo::AddIndexedIndirectDraw( + uint32_t maxDrawCallsPerIndirectValidationBatch, + uint32_t maxBatchOffsetRange, + IndexedIndirectDraw draw) { + const uint64_t newOffset = draw.clientBufferOffset; + auto it = mBatches.begin(); + while (it != mBatches.end()) { + IndexedIndirectValidationBatch& batch = *it; + if (batch.draws.size() >= maxDrawCallsPerIndirectValidationBatch) { + // This batch is full. If its minOffset is to the right of the new offset, we can + // just insert a new batch here. + if (newOffset < batch.minOffset) { + break; + } + + // Otherwise keep looking. + ++it; + continue; + } + + if (newOffset >= batch.minOffset && newOffset <= batch.maxOffset) { + batch.draws.push_back(std::move(draw)); + return; + } + + if (newOffset < batch.minOffset && batch.maxOffset - newOffset <= maxBatchOffsetRange) { + // We can extend this batch to the left in order to fit the new offset. + batch.minOffset = newOffset; + batch.draws.push_back(std::move(draw)); + return; + } + + if (newOffset > batch.maxOffset && newOffset - batch.minOffset <= maxBatchOffsetRange) { + // We can extend this batch to the right in order to fit the new offset. + batch.maxOffset = newOffset; + batch.draws.push_back(std::move(draw)); + return; + } + + if (newOffset < batch.minOffset) { + // We want to insert a new batch just before this one. + break; + } + + ++it; + } + + IndexedIndirectValidationBatch newBatch; + newBatch.minOffset = newOffset; + newBatch.maxOffset = newOffset; + newBatch.draws.push_back(std::move(draw)); + + mBatches.insert(it, std::move(newBatch)); + } + + void IndirectDrawMetadata::IndexedIndirectBufferValidationInfo::AddBatch( + uint32_t maxDrawCallsPerIndirectValidationBatch, + uint32_t maxBatchOffsetRange, + const IndexedIndirectValidationBatch& newBatch) { + auto it = mBatches.begin(); + while (it != mBatches.end()) { + IndexedIndirectValidationBatch& batch = *it; + uint64_t min = std::min(newBatch.minOffset, batch.minOffset); + uint64_t max = std::max(newBatch.maxOffset, batch.maxOffset); + if (max - min <= maxBatchOffsetRange && batch.draws.size() + newBatch.draws.size() <= + maxDrawCallsPerIndirectValidationBatch) { + // This batch fits within the limits of an existing batch. Merge it. + batch.minOffset = min; + batch.maxOffset = max; + batch.draws.insert(batch.draws.end(), newBatch.draws.begin(), newBatch.draws.end()); + return; + } + + if (newBatch.minOffset < batch.minOffset) { + break; + } + + ++it; + } + mBatches.push_back(newBatch); + } + + const std::vector<IndirectDrawMetadata::IndexedIndirectValidationBatch>& + IndirectDrawMetadata::IndexedIndirectBufferValidationInfo::GetBatches() const { + return mBatches; + } + + IndirectDrawMetadata::IndirectDrawMetadata(const CombinedLimits& limits) + : mMaxDrawCallsPerBatch(ComputeMaxDrawCallsPerIndirectValidationBatch(limits)), + mMaxBatchOffsetRange(ComputeMaxIndirectValidationBatchOffsetRange(limits)) { + } + + IndirectDrawMetadata::~IndirectDrawMetadata() = default; + + IndirectDrawMetadata::IndirectDrawMetadata(IndirectDrawMetadata&&) = default; + + IndirectDrawMetadata& IndirectDrawMetadata::operator=(IndirectDrawMetadata&&) = default; + + IndirectDrawMetadata::IndexedIndirectBufferValidationInfoMap* + IndirectDrawMetadata::GetIndexedIndirectBufferValidationInfo() { + return &mIndexedIndirectBufferValidationInfo; + } + + void IndirectDrawMetadata::AddBundle(RenderBundleBase* bundle) { + auto [_, inserted] = mAddedBundles.insert(bundle); + if (!inserted) { + return; + } + + for (const auto& [config, validationInfo] : + bundle->GetIndirectDrawMetadata().mIndexedIndirectBufferValidationInfo) { + auto it = mIndexedIndirectBufferValidationInfo.lower_bound(config); + if (it != mIndexedIndirectBufferValidationInfo.end() && it->first == config) { + // We already have batches for the same config. Merge the new ones in. + for (const IndexedIndirectValidationBatch& batch : validationInfo.GetBatches()) { + it->second.AddBatch(mMaxDrawCallsPerBatch, mMaxBatchOffsetRange, batch); + } + } else { + mIndexedIndirectBufferValidationInfo.emplace_hint(it, config, validationInfo); + } + } + } + + void IndirectDrawMetadata::AddIndexedIndirectDraw(wgpu::IndexFormat indexFormat, + uint64_t indexBufferSize, + BufferBase* indirectBuffer, + uint64_t indirectOffset, + DrawIndexedIndirectCmd* cmd) { + uint64_t numIndexBufferElements; + switch (indexFormat) { + case wgpu::IndexFormat::Uint16: + numIndexBufferElements = indexBufferSize / 2; + break; + case wgpu::IndexFormat::Uint32: + numIndexBufferElements = indexBufferSize / 4; + break; + case wgpu::IndexFormat::Undefined: + UNREACHABLE(); + } + + const IndexedIndirectConfig config(indirectBuffer, numIndexBufferElements); + auto it = mIndexedIndirectBufferValidationInfo.find(config); + if (it == mIndexedIndirectBufferValidationInfo.end()) { + auto result = mIndexedIndirectBufferValidationInfo.emplace( + config, IndexedIndirectBufferValidationInfo(indirectBuffer)); + it = result.first; + } + + IndexedIndirectDraw draw; + draw.clientBufferOffset = indirectOffset; + draw.cmd = cmd; + it->second.AddIndexedIndirectDraw(mMaxDrawCallsPerBatch, mMaxBatchOffsetRange, + std::move(draw)); + } + +} // namespace dawn::native
diff --git a/src/dawn/native/IndirectDrawMetadata.h b/src/dawn/native/IndirectDrawMetadata.h new file mode 100644 index 0000000..602be86 --- /dev/null +++ b/src/dawn/native/IndirectDrawMetadata.h
@@ -0,0 +1,126 @@ +// Copyright 2021 The Dawn Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#ifndef DAWNNATIVE_INDIRECTDRAWMETADATA_H_ +#define DAWNNATIVE_INDIRECTDRAWMETADATA_H_ + +#include "dawn/common/NonCopyable.h" +#include "dawn/common/RefCounted.h" +#include "dawn/native/Buffer.h" +#include "dawn/native/CommandBufferStateTracker.h" +#include "dawn/native/Commands.h" + +#include <cstdint> +#include <map> +#include <set> +#include <utility> +#include <vector> + +namespace dawn::native { + + class RenderBundleBase; + struct CombinedLimits; + + // In the unlikely scenario that indirect offsets used over a single buffer span more than + // this length of the buffer, we split the validation work into multiple batches. + uint32_t ComputeMaxIndirectValidationBatchOffsetRange(const CombinedLimits& limits); + + // Metadata corresponding to the validation requirements of a single render pass. This metadata + // is accumulated while its corresponding render pass is encoded, and is later used to encode + // validation commands to be inserted into the command buffer just before the render pass's own + // commands. + class IndirectDrawMetadata : public NonCopyable { + public: + struct IndexedIndirectDraw { + uint64_t clientBufferOffset; + // This is a pointer to the command that should be populated with the validated + // indirect scratch buffer. It is only valid up until the encoded command buffer + // is submitted. + DrawIndexedIndirectCmd* cmd; + }; + + struct IndexedIndirectValidationBatch { + uint64_t minOffset; + uint64_t maxOffset; + std::vector<IndexedIndirectDraw> draws; + }; + + // Tracks information about every draw call in this render pass which uses the same indirect + // buffer and the same-sized index buffer. Calls are grouped by indirect offset ranges so + // that validation work can be chunked efficiently if necessary. + class IndexedIndirectBufferValidationInfo { + public: + explicit IndexedIndirectBufferValidationInfo(BufferBase* indirectBuffer); + + // Logs a new drawIndexedIndirect call for the render pass. `cmd` is updated with an + // assigned (and deferred) buffer ref and relative offset before returning. + void AddIndexedIndirectDraw(uint32_t maxDrawCallsPerIndirectValidationBatch, + uint32_t maxBatchOffsetRange, + IndexedIndirectDraw draw); + + // Adds draw calls from an already-computed batch, e.g. from a previously encoded + // RenderBundle. The added batch is merged into an existing batch if possible, otherwise + // it's added to mBatch. + void AddBatch(uint32_t maxDrawCallsPerIndirectValidationBatch, + uint32_t maxBatchOffsetRange, + const IndexedIndirectValidationBatch& batch); + + const std::vector<IndexedIndirectValidationBatch>& GetBatches() const; + + private: + Ref<BufferBase> mIndirectBuffer; + + // A list of information about validation batches that will need to be executed for the + // corresponding indirect buffer prior to a single render pass. These are kept sorted by + // minOffset and may overlap iff the number of offsets in one batch would otherwise + // exceed some large upper bound (roughly ~33M draw calls). + // + // Since the most common expected cases will overwhelmingly require only a single + // validation pass per render pass, this is optimized for efficient updates to a single + // batch rather than for efficient manipulation of a large number of batches. + std::vector<IndexedIndirectValidationBatch> mBatches; + }; + + // Combination of an indirect buffer reference, and the number of addressable index buffer + // elements at the time of a draw call. + using IndexedIndirectConfig = std::pair<BufferBase*, uint64_t>; + using IndexedIndirectBufferValidationInfoMap = + std::map<IndexedIndirectConfig, IndexedIndirectBufferValidationInfo>; + + explicit IndirectDrawMetadata(const CombinedLimits& limits); + ~IndirectDrawMetadata(); + + IndirectDrawMetadata(IndirectDrawMetadata&&); + IndirectDrawMetadata& operator=(IndirectDrawMetadata&&); + + IndexedIndirectBufferValidationInfoMap* GetIndexedIndirectBufferValidationInfo(); + + void AddBundle(RenderBundleBase* bundle); + void AddIndexedIndirectDraw(wgpu::IndexFormat indexFormat, + uint64_t indexBufferSize, + BufferBase* indirectBuffer, + uint64_t indirectOffset, + DrawIndexedIndirectCmd* cmd); + + private: + IndexedIndirectBufferValidationInfoMap mIndexedIndirectBufferValidationInfo; + std::set<RenderBundleBase*> mAddedBundles; + + uint32_t mMaxDrawCallsPerBatch; + uint32_t mMaxBatchOffsetRange; + }; + +} // namespace dawn::native + +#endif // DAWNNATIVE_INDIRECTDRAWMETADATA_H_
diff --git a/src/dawn/native/IndirectDrawValidationEncoder.cpp b/src/dawn/native/IndirectDrawValidationEncoder.cpp new file mode 100644 index 0000000..6567b3e --- /dev/null +++ b/src/dawn/native/IndirectDrawValidationEncoder.cpp
@@ -0,0 +1,382 @@ +// Copyright 2021 The Dawn Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include "dawn/native/IndirectDrawValidationEncoder.h" + +#include "dawn/common/Constants.h" +#include "dawn/common/Math.h" +#include "dawn/native/BindGroup.h" +#include "dawn/native/BindGroupLayout.h" +#include "dawn/native/CommandEncoder.h" +#include "dawn/native/ComputePassEncoder.h" +#include "dawn/native/ComputePipeline.h" +#include "dawn/native/Device.h" +#include "dawn/native/InternalPipelineStore.h" +#include "dawn/native/Queue.h" +#include "dawn/native/utils/WGPUHelpers.h" + +#include <cstdlib> +#include <limits> + +namespace dawn::native { + + namespace { + // NOTE: This must match the workgroup_size attribute on the compute entry point below. + constexpr uint64_t kWorkgroupSize = 64; + + // Equivalent to the BatchInfo struct defined in the shader below. + struct BatchInfo { + uint64_t numIndexBufferElements; + uint32_t numDraws; + uint32_t padding; + }; + + // TODO(https://crbug.com/dawn/1108): Propagate validation feedback from this shader in + // various failure modes. + static const char sRenderValidationShaderSource[] = R"( + let kNumIndirectParamsPerDrawCall = 5u; + + let kIndexCountEntry = 0u; + let kInstanceCountEntry = 1u; + let kFirstIndexEntry = 2u; + let kBaseVertexEntry = 3u; + let kFirstInstanceEntry = 4u; + + struct BatchInfo { + numIndexBufferElementsLow: u32; + numIndexBufferElementsHigh: u32; + numDraws: u32; + padding: u32; + indirectOffsets: array<u32>; + }; + + struct IndirectParams { + data: array<u32>; + }; + + @group(0) @binding(0) var<storage, read> batch: BatchInfo; + @group(0) @binding(1) var<storage, read_write> clientParams: IndirectParams; + @group(0) @binding(2) var<storage, write> validatedParams: IndirectParams; + + fn fail(drawIndex: u32) { + let index = drawIndex * kNumIndirectParamsPerDrawCall; + validatedParams.data[index + kIndexCountEntry] = 0u; + validatedParams.data[index + kInstanceCountEntry] = 0u; + validatedParams.data[index + kFirstIndexEntry] = 0u; + validatedParams.data[index + kBaseVertexEntry] = 0u; + validatedParams.data[index + kFirstInstanceEntry] = 0u; + } + + fn pass(drawIndex: u32) { + let vIndex = drawIndex * kNumIndirectParamsPerDrawCall; + let cIndex = batch.indirectOffsets[drawIndex]; + validatedParams.data[vIndex + kIndexCountEntry] = + clientParams.data[cIndex + kIndexCountEntry]; + validatedParams.data[vIndex + kInstanceCountEntry] = + clientParams.data[cIndex + kInstanceCountEntry]; + validatedParams.data[vIndex + kFirstIndexEntry] = + clientParams.data[cIndex + kFirstIndexEntry]; + validatedParams.data[vIndex + kBaseVertexEntry] = + clientParams.data[cIndex + kBaseVertexEntry]; + validatedParams.data[vIndex + kFirstInstanceEntry] = + clientParams.data[cIndex + kFirstInstanceEntry]; + } + + @stage(compute) @workgroup_size(64, 1, 1) + fn main(@builtin(global_invocation_id) id : vec3<u32>) { + if (id.x >= batch.numDraws) { + return; + } + + let clientIndex = batch.indirectOffsets[id.x]; + let firstInstance = clientParams.data[clientIndex + kFirstInstanceEntry]; + if (firstInstance != 0u) { + fail(id.x); + return; + } + + if (batch.numIndexBufferElementsHigh >= 2u) { + // firstIndex and indexCount are both u32. The maximum possible sum of these + // values is 0x1fffffffe, which is less than 0x200000000. Nothing to validate. + pass(id.x); + return; + } + + let firstIndex = clientParams.data[clientIndex + kFirstIndexEntry]; + if (batch.numIndexBufferElementsHigh == 0u && + batch.numIndexBufferElementsLow < firstIndex) { + fail(id.x); + return; + } + + // Note that this subtraction may underflow, but only when + // numIndexBufferElementsHigh is 1u. The result is still correct in that case. + let maxIndexCount = batch.numIndexBufferElementsLow - firstIndex; + let indexCount = clientParams.data[clientIndex + kIndexCountEntry]; + if (indexCount > maxIndexCount) { + fail(id.x); + return; + } + pass(id.x); + } + )"; + + ResultOrError<ComputePipelineBase*> GetOrCreateRenderValidationPipeline( + DeviceBase* device) { + InternalPipelineStore* store = device->GetInternalPipelineStore(); + + if (store->renderValidationPipeline == nullptr) { + // Create compute shader module if not cached before. + if (store->renderValidationShader == nullptr) { + DAWN_TRY_ASSIGN( + store->renderValidationShader, + utils::CreateShaderModule(device, sRenderValidationShaderSource)); + } + + Ref<BindGroupLayoutBase> bindGroupLayout; + DAWN_TRY_ASSIGN( + bindGroupLayout, + utils::MakeBindGroupLayout( + device, + { + {0, wgpu::ShaderStage::Compute, + wgpu::BufferBindingType::ReadOnlyStorage}, + {1, wgpu::ShaderStage::Compute, kInternalStorageBufferBinding}, + {2, wgpu::ShaderStage::Compute, wgpu::BufferBindingType::Storage}, + }, + /* allowInternalBinding */ true)); + + Ref<PipelineLayoutBase> pipelineLayout; + DAWN_TRY_ASSIGN(pipelineLayout, + utils::MakeBasicPipelineLayout(device, bindGroupLayout)); + + ComputePipelineDescriptor computePipelineDescriptor = {}; + computePipelineDescriptor.layout = pipelineLayout.Get(); + computePipelineDescriptor.compute.module = store->renderValidationShader.Get(); + computePipelineDescriptor.compute.entryPoint = "main"; + + DAWN_TRY_ASSIGN(store->renderValidationPipeline, + device->CreateComputePipeline(&computePipelineDescriptor)); + } + + return store->renderValidationPipeline.Get(); + } + + size_t GetBatchDataSize(uint32_t numDraws) { + return sizeof(BatchInfo) + numDraws * sizeof(uint32_t); + } + + } // namespace + + uint32_t ComputeMaxDrawCallsPerIndirectValidationBatch(const CombinedLimits& limits) { + const uint64_t batchDrawCallLimitByDispatchSize = + static_cast<uint64_t>(limits.v1.maxComputeWorkgroupsPerDimension) * kWorkgroupSize; + const uint64_t batchDrawCallLimitByStorageBindingSize = + (limits.v1.maxStorageBufferBindingSize - sizeof(BatchInfo)) / sizeof(uint32_t); + return static_cast<uint32_t>( + std::min({batchDrawCallLimitByDispatchSize, batchDrawCallLimitByStorageBindingSize, + uint64_t(std::numeric_limits<uint32_t>::max())})); + } + + MaybeError EncodeIndirectDrawValidationCommands(DeviceBase* device, + CommandEncoder* commandEncoder, + RenderPassResourceUsageTracker* usageTracker, + IndirectDrawMetadata* indirectDrawMetadata) { + struct Batch { + const IndirectDrawMetadata::IndexedIndirectValidationBatch* metadata; + uint64_t numIndexBufferElements; + uint64_t dataBufferOffset; + uint64_t dataSize; + uint64_t clientIndirectOffset; + uint64_t clientIndirectSize; + uint64_t validatedParamsOffset; + uint64_t validatedParamsSize; + BatchInfo* batchInfo; + }; + + struct Pass { + BufferBase* clientIndirectBuffer; + uint64_t validatedParamsSize = 0; + uint64_t batchDataSize = 0; + std::unique_ptr<void, void (*)(void*)> batchData{nullptr, std::free}; + std::vector<Batch> batches; + }; + + // First stage is grouping all batches into passes. We try to pack as many batches into a + // single pass as possible. Batches can be grouped together as long as they're validating + // data from the same indirect buffer, but they may still be split into multiple passes if + // the number of draw calls in a pass would exceed some (very high) upper bound. + size_t validatedParamsSize = 0; + std::vector<Pass> passes; + IndirectDrawMetadata::IndexedIndirectBufferValidationInfoMap& bufferInfoMap = + *indirectDrawMetadata->GetIndexedIndirectBufferValidationInfo(); + if (bufferInfoMap.empty()) { + return {}; + } + + const uint32_t maxStorageBufferBindingSize = + device->GetLimits().v1.maxStorageBufferBindingSize; + const uint32_t minStorageBufferOffsetAlignment = + device->GetLimits().v1.minStorageBufferOffsetAlignment; + + for (auto& [config, validationInfo] : bufferInfoMap) { + BufferBase* clientIndirectBuffer = config.first; + for (const IndirectDrawMetadata::IndexedIndirectValidationBatch& batch : + validationInfo.GetBatches()) { + const uint64_t minOffsetFromAlignedBoundary = + batch.minOffset % minStorageBufferOffsetAlignment; + const uint64_t minOffsetAlignedDown = + batch.minOffset - minOffsetFromAlignedBoundary; + + Batch newBatch; + newBatch.metadata = &batch; + newBatch.numIndexBufferElements = config.second; + newBatch.dataSize = GetBatchDataSize(batch.draws.size()); + newBatch.clientIndirectOffset = minOffsetAlignedDown; + newBatch.clientIndirectSize = + batch.maxOffset + kDrawIndexedIndirectSize - minOffsetAlignedDown; + + newBatch.validatedParamsSize = batch.draws.size() * kDrawIndexedIndirectSize; + newBatch.validatedParamsOffset = + Align(validatedParamsSize, minStorageBufferOffsetAlignment); + validatedParamsSize = newBatch.validatedParamsOffset + newBatch.validatedParamsSize; + if (validatedParamsSize > maxStorageBufferBindingSize) { + return DAWN_INTERNAL_ERROR("Too many drawIndexedIndirect calls to validate"); + } + + Pass* currentPass = passes.empty() ? nullptr : &passes.back(); + if (currentPass && currentPass->clientIndirectBuffer == clientIndirectBuffer) { + uint64_t nextBatchDataOffset = + Align(currentPass->batchDataSize, minStorageBufferOffsetAlignment); + uint64_t newPassBatchDataSize = nextBatchDataOffset + newBatch.dataSize; + if (newPassBatchDataSize <= maxStorageBufferBindingSize) { + // We can fit this batch in the current pass. + newBatch.dataBufferOffset = nextBatchDataOffset; + currentPass->batchDataSize = newPassBatchDataSize; + currentPass->batches.push_back(newBatch); + continue; + } + } + + // We need to start a new pass for this batch. + newBatch.dataBufferOffset = 0; + + Pass newPass; + newPass.clientIndirectBuffer = clientIndirectBuffer; + newPass.batchDataSize = newBatch.dataSize; + newPass.batches.push_back(newBatch); + passes.push_back(std::move(newPass)); + } + } + + auto* const store = device->GetInternalPipelineStore(); + ScratchBuffer& validatedParamsBuffer = store->scratchIndirectStorage; + ScratchBuffer& batchDataBuffer = store->scratchStorage; + + uint64_t requiredBatchDataBufferSize = 0; + for (const Pass& pass : passes) { + requiredBatchDataBufferSize = std::max(requiredBatchDataBufferSize, pass.batchDataSize); + } + DAWN_TRY(batchDataBuffer.EnsureCapacity(requiredBatchDataBufferSize)); + usageTracker->BufferUsedAs(batchDataBuffer.GetBuffer(), wgpu::BufferUsage::Storage); + + DAWN_TRY(validatedParamsBuffer.EnsureCapacity(validatedParamsSize)); + usageTracker->BufferUsedAs(validatedParamsBuffer.GetBuffer(), wgpu::BufferUsage::Indirect); + + // Now we allocate and populate host-side batch data to be copied to the GPU. + for (Pass& pass : passes) { + // We use std::malloc here because it guarantees maximal scalar alignment. + pass.batchData = {std::malloc(pass.batchDataSize), std::free}; + memset(pass.batchData.get(), 0, pass.batchDataSize); + uint8_t* batchData = static_cast<uint8_t*>(pass.batchData.get()); + for (Batch& batch : pass.batches) { + batch.batchInfo = new (&batchData[batch.dataBufferOffset]) BatchInfo(); + batch.batchInfo->numIndexBufferElements = batch.numIndexBufferElements; + batch.batchInfo->numDraws = static_cast<uint32_t>(batch.metadata->draws.size()); + + uint32_t* indirectOffsets = reinterpret_cast<uint32_t*>(batch.batchInfo + 1); + uint64_t validatedParamsOffset = batch.validatedParamsOffset; + for (auto& draw : batch.metadata->draws) { + // The shader uses this to index an array of u32, hence the division by 4 bytes. + *indirectOffsets++ = static_cast<uint32_t>( + (draw.clientBufferOffset - batch.clientIndirectOffset) / 4); + + draw.cmd->indirectBuffer = validatedParamsBuffer.GetBuffer(); + draw.cmd->indirectOffset = validatedParamsOffset; + + validatedParamsOffset += kDrawIndexedIndirectSize; + } + } + } + + ComputePipelineBase* pipeline; + DAWN_TRY_ASSIGN(pipeline, GetOrCreateRenderValidationPipeline(device)); + + Ref<BindGroupLayoutBase> layout; + DAWN_TRY_ASSIGN(layout, pipeline->GetBindGroupLayout(0)); + + BindGroupEntry bindings[3]; + BindGroupEntry& bufferDataBinding = bindings[0]; + bufferDataBinding.binding = 0; + bufferDataBinding.buffer = batchDataBuffer.GetBuffer(); + + BindGroupEntry& clientIndirectBinding = bindings[1]; + clientIndirectBinding.binding = 1; + + BindGroupEntry& validatedParamsBinding = bindings[2]; + validatedParamsBinding.binding = 2; + validatedParamsBinding.buffer = validatedParamsBuffer.GetBuffer(); + + BindGroupDescriptor bindGroupDescriptor = {}; + bindGroupDescriptor.layout = layout.Get(); + bindGroupDescriptor.entryCount = 3; + bindGroupDescriptor.entries = bindings; + + // Finally, we can now encode our validation passes. Each pass first does a single + // WriteBuffer to get batch data over to the GPU, followed by a single compute pass. The + // compute pass encodes a separate SetBindGroup and Dispatch command for each batch. + for (const Pass& pass : passes) { + commandEncoder->APIWriteBuffer(batchDataBuffer.GetBuffer(), 0, + static_cast<const uint8_t*>(pass.batchData.get()), + pass.batchDataSize); + + Ref<ComputePassEncoder> passEncoder = commandEncoder->BeginComputePass(); + passEncoder->APISetPipeline(pipeline); + + clientIndirectBinding.buffer = pass.clientIndirectBuffer; + + for (const Batch& batch : pass.batches) { + bufferDataBinding.offset = batch.dataBufferOffset; + bufferDataBinding.size = batch.dataSize; + clientIndirectBinding.offset = batch.clientIndirectOffset; + clientIndirectBinding.size = batch.clientIndirectSize; + validatedParamsBinding.offset = batch.validatedParamsOffset; + validatedParamsBinding.size = batch.validatedParamsSize; + + Ref<BindGroupBase> bindGroup; + DAWN_TRY_ASSIGN(bindGroup, device->CreateBindGroup(&bindGroupDescriptor)); + + const uint32_t numDrawsRoundedUp = + (batch.batchInfo->numDraws + kWorkgroupSize - 1) / kWorkgroupSize; + passEncoder->APISetBindGroup(0, bindGroup.Get()); + passEncoder->APIDispatch(numDrawsRoundedUp); + } + + passEncoder->APIEnd(); + } + + return {}; + } + +} // namespace dawn::native
diff --git a/src/dawn/native/IndirectDrawValidationEncoder.h b/src/dawn/native/IndirectDrawValidationEncoder.h new file mode 100644 index 0000000..6714137 --- /dev/null +++ b/src/dawn/native/IndirectDrawValidationEncoder.h
@@ -0,0 +1,40 @@ +// Copyright 2021 The Dawn Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#ifndef DAWNNATIVE_INDIRECTDRAWVALIDATIONENCODER_H_ +#define DAWNNATIVE_INDIRECTDRAWVALIDATIONENCODER_H_ + +#include "dawn/native/Error.h" +#include "dawn/native/IndirectDrawMetadata.h" + +namespace dawn::native { + + class CommandEncoder; + struct CombinedLimits; + class DeviceBase; + class RenderPassResourceUsageTracker; + + // The maximum number of draws call we can fit into a single validation batch. This is + // essentially limited by the number of indirect parameter blocks that can fit into the maximum + // allowed storage binding size (with the base limits, it is about 6.7M). + uint32_t ComputeMaxDrawCallsPerIndirectValidationBatch(const CombinedLimits& limits); + + MaybeError EncodeIndirectDrawValidationCommands(DeviceBase* device, + CommandEncoder* commandEncoder, + RenderPassResourceUsageTracker* usageTracker, + IndirectDrawMetadata* indirectDrawMetadata); + +} // namespace dawn::native + +#endif // DAWNNATIVE_INDIRECTDRAWVALIDATIONENCODER_H_
diff --git a/src/dawn/native/Instance.cpp b/src/dawn/native/Instance.cpp new file mode 100644 index 0000000..48bf740 --- /dev/null +++ b/src/dawn/native/Instance.cpp
@@ -0,0 +1,435 @@ +// Copyright 2018 The Dawn Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include "dawn/native/Instance.h" + +#include "dawn/common/Assert.h" +#include "dawn/common/GPUInfo.h" +#include "dawn/common/Log.h" +#include "dawn/common/SystemUtils.h" +#include "dawn/native/ChainUtils_autogen.h" +#include "dawn/native/ErrorData.h" +#include "dawn/native/Surface.h" +#include "dawn/native/ValidationUtils_autogen.h" +#include "dawn/platform/DawnPlatform.h" + +// For SwiftShader fallback +#if defined(DAWN_ENABLE_BACKEND_VULKAN) +# include "dawn/native/VulkanBackend.h" +#endif // defined(DAWN_ENABLE_BACKEND_VULKAN) + +#if defined(DAWN_USE_X11) +# include "dawn/native/XlibXcbFunctions.h" +#endif // defined(DAWN_USE_X11) + +#include <optional> + +namespace dawn::native { + + // Forward definitions of each backend's "Connect" function that creates new BackendConnection. + // Conditionally compiled declarations are used to avoid using static constructors instead. +#if defined(DAWN_ENABLE_BACKEND_D3D12) + namespace d3d12 { + BackendConnection* Connect(InstanceBase* instance); + } +#endif // defined(DAWN_ENABLE_BACKEND_D3D12) +#if defined(DAWN_ENABLE_BACKEND_METAL) + namespace metal { + BackendConnection* Connect(InstanceBase* instance); + } +#endif // defined(DAWN_ENABLE_BACKEND_METAL) +#if defined(DAWN_ENABLE_BACKEND_NULL) + namespace null { + BackendConnection* Connect(InstanceBase* instance); + } +#endif // defined(DAWN_ENABLE_BACKEND_NULL) +#if defined(DAWN_ENABLE_BACKEND_OPENGL) + namespace opengl { + BackendConnection* Connect(InstanceBase* instance, wgpu::BackendType backendType); + } +#endif // defined(DAWN_ENABLE_BACKEND_OPENGL) +#if defined(DAWN_ENABLE_BACKEND_VULKAN) + namespace vulkan { + BackendConnection* Connect(InstanceBase* instance); + } +#endif // defined(DAWN_ENABLE_BACKEND_VULKAN) + + namespace { + + BackendsBitset GetEnabledBackends() { + BackendsBitset enabledBackends; +#if defined(DAWN_ENABLE_BACKEND_NULL) + enabledBackends.set(wgpu::BackendType::Null); +#endif // defined(DAWN_ENABLE_BACKEND_NULL) +#if defined(DAWN_ENABLE_BACKEND_D3D12) + enabledBackends.set(wgpu::BackendType::D3D12); +#endif // defined(DAWN_ENABLE_BACKEND_D3D12) +#if defined(DAWN_ENABLE_BACKEND_METAL) + enabledBackends.set(wgpu::BackendType::Metal); +#endif // defined(DAWN_ENABLE_BACKEND_METAL) +#if defined(DAWN_ENABLE_BACKEND_VULKAN) + enabledBackends.set(wgpu::BackendType::Vulkan); +#endif // defined(DAWN_ENABLE_BACKEND_VULKAN) +#if defined(DAWN_ENABLE_BACKEND_DESKTOP_GL) + enabledBackends.set(wgpu::BackendType::OpenGL); +#endif // defined(DAWN_ENABLE_BACKEND_DESKTOP_GL) +#if defined(DAWN_ENABLE_BACKEND_OPENGLES) + enabledBackends.set(wgpu::BackendType::OpenGLES); +#endif // defined(DAWN_ENABLE_BACKEND_OPENGLES) + + return enabledBackends; + } + + } // anonymous namespace + + // InstanceBase + + // static + InstanceBase* InstanceBase::Create(const InstanceDescriptor* descriptor) { + Ref<InstanceBase> instance = AcquireRef(new InstanceBase); + static constexpr InstanceDescriptor kDefaultDesc = {}; + if (descriptor == nullptr) { + descriptor = &kDefaultDesc; + } + if (instance->ConsumedError(instance->Initialize(descriptor))) { + return nullptr; + } + return instance.Detach(); + } + + // TODO(crbug.com/dawn/832): make the platform an initialization parameter of the instance. + MaybeError InstanceBase::Initialize(const InstanceDescriptor* descriptor) { + DAWN_TRY(ValidateSingleSType(descriptor->nextInChain, wgpu::SType::DawnInstanceDescriptor)); + const DawnInstanceDescriptor* dawnDesc = nullptr; + FindInChain(descriptor->nextInChain, &dawnDesc); + if (dawnDesc != nullptr) { + for (uint32_t i = 0; i < dawnDesc->additionalRuntimeSearchPathsCount; ++i) { + mRuntimeSearchPaths.push_back(dawnDesc->additionalRuntimeSearchPaths[i]); + } + } + // Default paths to search are next to the shared library, next to the executable, and + // no path (just libvulkan.so). + if (auto p = GetModuleDirectory()) { + mRuntimeSearchPaths.push_back(std::move(*p)); + } + if (auto p = GetExecutableDirectory()) { + mRuntimeSearchPaths.push_back(std::move(*p)); + } + mRuntimeSearchPaths.push_back(""); + return {}; + } + + void InstanceBase::APIRequestAdapter(const RequestAdapterOptions* options, + WGPURequestAdapterCallback callback, + void* userdata) { + static constexpr RequestAdapterOptions kDefaultOptions = {}; + if (options == nullptr) { + options = &kDefaultOptions; + } + auto result = RequestAdapterInternal(options); + if (result.IsError()) { + auto err = result.AcquireError(); + std::string msg = err->GetFormattedMessage(); + // TODO(crbug.com/dawn/1122): Call callbacks only on wgpuInstanceProcessEvents + callback(WGPURequestAdapterStatus_Error, nullptr, msg.c_str(), userdata); + } else { + Ref<AdapterBase> adapter = result.AcquireSuccess(); + // TODO(crbug.com/dawn/1122): Call callbacks only on wgpuInstanceProcessEvents + callback(WGPURequestAdapterStatus_Success, ToAPI(adapter.Detach()), nullptr, userdata); + } + } + + ResultOrError<Ref<AdapterBase>> InstanceBase::RequestAdapterInternal( + const RequestAdapterOptions* options) { + ASSERT(options != nullptr); + if (options->forceFallbackAdapter) { +#if defined(DAWN_ENABLE_BACKEND_VULKAN) + if (GetEnabledBackends()[wgpu::BackendType::Vulkan]) { + dawn_native::vulkan::AdapterDiscoveryOptions vulkanOptions; + vulkanOptions.forceSwiftShader = true; + DAWN_TRY(DiscoverAdaptersInternal(&vulkanOptions)); + } +#else + return Ref<AdapterBase>(nullptr); +#endif // defined(DAWN_ENABLE_BACKEND_VULKAN) + } else { + DiscoverDefaultAdapters(); + } + + wgpu::AdapterType preferredType; + switch (options->powerPreference) { + case wgpu::PowerPreference::LowPower: + preferredType = wgpu::AdapterType::IntegratedGPU; + break; + case wgpu::PowerPreference::Undefined: + case wgpu::PowerPreference::HighPerformance: + preferredType = wgpu::AdapterType::DiscreteGPU; + break; + } + + std::optional<size_t> discreteGPUAdapterIndex; + std::optional<size_t> integratedGPUAdapterIndex; + std::optional<size_t> cpuAdapterIndex; + std::optional<size_t> unknownAdapterIndex; + + for (size_t i = 0; i < mAdapters.size(); ++i) { + AdapterProperties properties; + mAdapters[i]->APIGetProperties(&properties); + + if (options->forceFallbackAdapter) { + if (!gpu_info::IsSwiftshader(properties.vendorID, properties.deviceID)) { + continue; + } + return mAdapters[i]; + } + if (properties.adapterType == preferredType) { + return mAdapters[i]; + } + switch (properties.adapterType) { + case wgpu::AdapterType::DiscreteGPU: + discreteGPUAdapterIndex = i; + break; + case wgpu::AdapterType::IntegratedGPU: + integratedGPUAdapterIndex = i; + break; + case wgpu::AdapterType::CPU: + cpuAdapterIndex = i; + break; + case wgpu::AdapterType::Unknown: + unknownAdapterIndex = i; + break; + } + } + + // For now, we always prefer the discrete GPU + if (discreteGPUAdapterIndex) { + return mAdapters[*discreteGPUAdapterIndex]; + } + if (integratedGPUAdapterIndex) { + return mAdapters[*integratedGPUAdapterIndex]; + } + if (cpuAdapterIndex) { + return mAdapters[*cpuAdapterIndex]; + } + if (unknownAdapterIndex) { + return mAdapters[*unknownAdapterIndex]; + } + + return Ref<AdapterBase>(nullptr); + } + + void InstanceBase::DiscoverDefaultAdapters() { + for (wgpu::BackendType b : IterateBitSet(GetEnabledBackends())) { + EnsureBackendConnection(b); + } + + if (mDiscoveredDefaultAdapters) { + return; + } + + // Query and merge all default adapters for all backends + for (std::unique_ptr<BackendConnection>& backend : mBackends) { + std::vector<Ref<AdapterBase>> backendAdapters = backend->DiscoverDefaultAdapters(); + + for (Ref<AdapterBase>& adapter : backendAdapters) { + ASSERT(adapter->GetBackendType() == backend->GetType()); + ASSERT(adapter->GetInstance() == this); + mAdapters.push_back(std::move(adapter)); + } + } + + mDiscoveredDefaultAdapters = true; + } + + // This is just a wrapper around the real logic that uses Error.h error handling. + bool InstanceBase::DiscoverAdapters(const AdapterDiscoveryOptionsBase* options) { + return !ConsumedError(DiscoverAdaptersInternal(options)); + } + + const ToggleInfo* InstanceBase::GetToggleInfo(const char* toggleName) { + return mTogglesInfo.GetToggleInfo(toggleName); + } + + Toggle InstanceBase::ToggleNameToEnum(const char* toggleName) { + return mTogglesInfo.ToggleNameToEnum(toggleName); + } + + const FeatureInfo* InstanceBase::GetFeatureInfo(wgpu::FeatureName feature) { + return mFeaturesInfo.GetFeatureInfo(feature); + } + + const std::vector<Ref<AdapterBase>>& InstanceBase::GetAdapters() const { + return mAdapters; + } + + void InstanceBase::EnsureBackendConnection(wgpu::BackendType backendType) { + if (mBackendsConnected[backendType]) { + return; + } + + auto Register = [this](BackendConnection* connection, wgpu::BackendType expectedType) { + if (connection != nullptr) { + ASSERT(connection->GetType() == expectedType); + ASSERT(connection->GetInstance() == this); + mBackends.push_back(std::unique_ptr<BackendConnection>(connection)); + } + }; + + switch (backendType) { +#if defined(DAWN_ENABLE_BACKEND_NULL) + case wgpu::BackendType::Null: + Register(null::Connect(this), wgpu::BackendType::Null); + break; +#endif // defined(DAWN_ENABLE_BACKEND_NULL) + +#if defined(DAWN_ENABLE_BACKEND_D3D12) + case wgpu::BackendType::D3D12: + Register(d3d12::Connect(this), wgpu::BackendType::D3D12); + break; +#endif // defined(DAWN_ENABLE_BACKEND_D3D12) + +#if defined(DAWN_ENABLE_BACKEND_METAL) + case wgpu::BackendType::Metal: + Register(metal::Connect(this), wgpu::BackendType::Metal); + break; +#endif // defined(DAWN_ENABLE_BACKEND_METAL) + +#if defined(DAWN_ENABLE_BACKEND_VULKAN) + case wgpu::BackendType::Vulkan: + Register(vulkan::Connect(this), wgpu::BackendType::Vulkan); + break; +#endif // defined(DAWN_ENABLE_BACKEND_VULKAN) + +#if defined(DAWN_ENABLE_BACKEND_DESKTOP_GL) + case wgpu::BackendType::OpenGL: + Register(opengl::Connect(this, wgpu::BackendType::OpenGL), + wgpu::BackendType::OpenGL); + break; +#endif // defined(DAWN_ENABLE_BACKEND_DESKTOP_GL) + +#if defined(DAWN_ENABLE_BACKEND_OPENGLES) + case wgpu::BackendType::OpenGLES: + Register(opengl::Connect(this, wgpu::BackendType::OpenGLES), + wgpu::BackendType::OpenGLES); + break; +#endif // defined(DAWN_ENABLE_BACKEND_OPENGLES) + + default: + UNREACHABLE(); + } + + mBackendsConnected.set(backendType); + } + + MaybeError InstanceBase::DiscoverAdaptersInternal(const AdapterDiscoveryOptionsBase* options) { + wgpu::BackendType backendType = static_cast<wgpu::BackendType>(options->backendType); + DAWN_TRY(ValidateBackendType(backendType)); + + if (!GetEnabledBackends()[backendType]) { + return DAWN_FORMAT_VALIDATION_ERROR("%s not supported.", backendType); + } + + EnsureBackendConnection(backendType); + + bool foundBackend = false; + for (std::unique_ptr<BackendConnection>& backend : mBackends) { + if (backend->GetType() != backendType) { + continue; + } + foundBackend = true; + + std::vector<Ref<AdapterBase>> newAdapters; + DAWN_TRY_ASSIGN(newAdapters, backend->DiscoverAdapters(options)); + + for (Ref<AdapterBase>& adapter : newAdapters) { + ASSERT(adapter->GetBackendType() == backend->GetType()); + ASSERT(adapter->GetInstance() == this); + mAdapters.push_back(std::move(adapter)); + } + } + + DAWN_INVALID_IF(!foundBackend, "%s not available.", backendType); + return {}; + } + + bool InstanceBase::ConsumedError(MaybeError maybeError) { + if (maybeError.IsError()) { + std::unique_ptr<ErrorData> error = maybeError.AcquireError(); + + ASSERT(error != nullptr); + dawn::ErrorLog() << error->GetFormattedMessage(); + return true; + } + return false; + } + + bool InstanceBase::IsBackendValidationEnabled() const { + return mBackendValidationLevel != BackendValidationLevel::Disabled; + } + + void InstanceBase::SetBackendValidationLevel(BackendValidationLevel level) { + mBackendValidationLevel = level; + } + + BackendValidationLevel InstanceBase::GetBackendValidationLevel() const { + return mBackendValidationLevel; + } + + void InstanceBase::EnableBeginCaptureOnStartup(bool beginCaptureOnStartup) { + mBeginCaptureOnStartup = beginCaptureOnStartup; + } + + bool InstanceBase::IsBeginCaptureOnStartupEnabled() const { + return mBeginCaptureOnStartup; + } + + void InstanceBase::SetPlatform(dawn::platform::Platform* platform) { + mPlatform = platform; + } + + dawn::platform::Platform* InstanceBase::GetPlatform() { + if (mPlatform != nullptr) { + return mPlatform; + } + + if (mDefaultPlatform == nullptr) { + mDefaultPlatform = std::make_unique<dawn::platform::Platform>(); + } + return mDefaultPlatform.get(); + } + + const std::vector<std::string>& InstanceBase::GetRuntimeSearchPaths() const { + return mRuntimeSearchPaths; + } + + const XlibXcbFunctions* InstanceBase::GetOrCreateXlibXcbFunctions() { +#if defined(DAWN_USE_X11) + if (mXlibXcbFunctions == nullptr) { + mXlibXcbFunctions = std::make_unique<XlibXcbFunctions>(); + } + return mXlibXcbFunctions.get(); +#else + UNREACHABLE(); +#endif // defined(DAWN_USE_X11) + } + + Surface* InstanceBase::APICreateSurface(const SurfaceDescriptor* descriptor) { + if (ConsumedError(ValidateSurfaceDescriptor(this, descriptor))) { + return nullptr; + } + + return new Surface(this, descriptor); + } + +} // namespace dawn::native
diff --git a/src/dawn/native/Instance.h b/src/dawn/native/Instance.h new file mode 100644 index 0000000..5898689 --- /dev/null +++ b/src/dawn/native/Instance.h
@@ -0,0 +1,129 @@ +// Copyright 2018 The Dawn Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#ifndef DAWNNATIVE_INSTANCE_H_ +#define DAWNNATIVE_INSTANCE_H_ + +#include "dawn/common/RefCounted.h" +#include "dawn/common/ityp_bitset.h" +#include "dawn/native/Adapter.h" +#include "dawn/native/BackendConnection.h" +#include "dawn/native/Features.h" +#include "dawn/native/Toggles.h" +#include "dawn/native/dawn_platform.h" + +#include <array> +#include <memory> +#include <unordered_map> +#include <vector> + +namespace dawn::platform { + class Platform; +} // namespace dawn::platform + +namespace dawn::native { + + class Surface; + class XlibXcbFunctions; + + using BackendsBitset = ityp::bitset<wgpu::BackendType, kEnumCount<wgpu::BackendType>>; + + // This is called InstanceBase for consistency across the frontend, even if the backends don't + // specialize this class. + class InstanceBase final : public RefCounted { + public: + static InstanceBase* Create(const InstanceDescriptor* descriptor = nullptr); + + void APIRequestAdapter(const RequestAdapterOptions* options, + WGPURequestAdapterCallback callback, + void* userdata); + + void DiscoverDefaultAdapters(); + bool DiscoverAdapters(const AdapterDiscoveryOptionsBase* options); + + const std::vector<Ref<AdapterBase>>& GetAdapters() const; + + // Used to handle error that happen up to device creation. + bool ConsumedError(MaybeError maybeError); + + // Used to query the details of a toggle. Return nullptr if toggleName is not a valid name + // of a toggle supported in Dawn. + const ToggleInfo* GetToggleInfo(const char* toggleName); + Toggle ToggleNameToEnum(const char* toggleName); + + // Used to query the details of an feature. Return nullptr if featureName is not a valid + // name of an feature supported in Dawn. + const FeatureInfo* GetFeatureInfo(wgpu::FeatureName feature); + + bool IsBackendValidationEnabled() const; + void SetBackendValidationLevel(BackendValidationLevel level); + BackendValidationLevel GetBackendValidationLevel() const; + + void EnableBeginCaptureOnStartup(bool beginCaptureOnStartup); + bool IsBeginCaptureOnStartupEnabled() const; + + void SetPlatform(dawn::platform::Platform* platform); + dawn::platform::Platform* GetPlatform(); + + const std::vector<std::string>& GetRuntimeSearchPaths() const; + + // Get backend-independent libraries that need to be loaded dynamically. + const XlibXcbFunctions* GetOrCreateXlibXcbFunctions(); + + // Dawn API + Surface* APICreateSurface(const SurfaceDescriptor* descriptor); + + private: + InstanceBase() = default; + ~InstanceBase() = default; + + InstanceBase(const InstanceBase& other) = delete; + InstanceBase& operator=(const InstanceBase& other) = delete; + + MaybeError Initialize(const InstanceDescriptor* descriptor); + + // Lazily creates connections to all backends that have been compiled. + void EnsureBackendConnection(wgpu::BackendType backendType); + + MaybeError DiscoverAdaptersInternal(const AdapterDiscoveryOptionsBase* options); + + ResultOrError<Ref<AdapterBase>> RequestAdapterInternal( + const RequestAdapterOptions* options); + + std::vector<std::string> mRuntimeSearchPaths; + + BackendsBitset mBackendsConnected; + + bool mDiscoveredDefaultAdapters = false; + + bool mBeginCaptureOnStartup = false; + BackendValidationLevel mBackendValidationLevel = BackendValidationLevel::Disabled; + + dawn::platform::Platform* mPlatform = nullptr; + std::unique_ptr<dawn::platform::Platform> mDefaultPlatform; + + std::vector<std::unique_ptr<BackendConnection>> mBackends; + std::vector<Ref<AdapterBase>> mAdapters; + + FeaturesInfo mFeaturesInfo; + TogglesInfo mTogglesInfo; + +#if defined(DAWN_USE_X11) + std::unique_ptr<XlibXcbFunctions> mXlibXcbFunctions; +#endif // defined(DAWN_USE_X11) + }; + +} // namespace dawn::native + +#endif // DAWNNATIVE_INSTANCE_H_
diff --git a/src/dawn/native/IntegerTypes.h b/src/dawn/native/IntegerTypes.h new file mode 100644 index 0000000..fd4c2f1 --- /dev/null +++ b/src/dawn/native/IntegerTypes.h
@@ -0,0 +1,76 @@ +// Copyright 2020 The Dawn Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#ifndef DAWNNATIVE_INTEGERTYPES_H_ +#define DAWNNATIVE_INTEGERTYPES_H_ + +#include "dawn/common/Constants.h" +#include "dawn/common/TypedInteger.h" + +#include <cstdint> + +namespace dawn::native { + // Binding numbers in the shader and BindGroup/BindGroupLayoutDescriptors + using BindingNumber = TypedInteger<struct BindingNumberT, uint32_t>; + constexpr BindingNumber kMaxBindingNumberTyped = BindingNumber(kMaxBindingNumber); + + // Binding numbers get mapped to a packed range of indices + using BindingIndex = TypedInteger<struct BindingIndexT, uint32_t>; + + using BindGroupIndex = TypedInteger<struct BindGroupIndexT, uint32_t>; + + constexpr BindGroupIndex kMaxBindGroupsTyped = BindGroupIndex(kMaxBindGroups); + + using ColorAttachmentIndex = TypedInteger<struct ColorAttachmentIndexT, uint8_t>; + + constexpr ColorAttachmentIndex kMaxColorAttachmentsTyped = + ColorAttachmentIndex(kMaxColorAttachments); + + using VertexBufferSlot = TypedInteger<struct VertexBufferSlotT, uint8_t>; + using VertexAttributeLocation = TypedInteger<struct VertexAttributeLocationT, uint8_t>; + + constexpr VertexBufferSlot kMaxVertexBuffersTyped = VertexBufferSlot(kMaxVertexBuffers); + constexpr VertexAttributeLocation kMaxVertexAttributesTyped = + VertexAttributeLocation(kMaxVertexAttributes); + + // Serials are 64bit integers that are incremented by one each time to produce unique values. + // Some serials (like queue serials) are compared numerically to know which one is before + // another, while some serials are only checked for equality. We call serials only checked + // for equality IDs. + + // Buffer mapping requests are stored outside of the buffer while they are being processed and + // cannot be invalidated. Instead they are associated with an ID, and when a map request is + // finished, the mapping callback is fired only if its ID matches the ID if the last request + // that was sent. + using MapRequestID = TypedInteger<struct MapRequestIDT, uint64_t>; + + // The type for the WebGPU API fence serial values. + using FenceAPISerial = TypedInteger<struct FenceAPISerialT, uint64_t>; + + // A serial used to watch the progression of GPU execution on a queue, each time operations + // that need to be followed individually are scheduled for execution on a queue, the serial + // is incremented by one. This way to know if something is done executing, we just need to + // compare its serial with the currently completed serial. + using ExecutionSerial = TypedInteger<struct QueueSerialT, uint64_t>; + constexpr ExecutionSerial kMaxExecutionSerial = ExecutionSerial(~uint64_t(0)); + + // An identifier that indicates which Pipeline a BindGroupLayout is compatible with. Pipelines + // created with a default layout will produce BindGroupLayouts with a non-zero compatibility + // token, which prevents them (and any BindGroups created with them) from being used with any + // other pipelines. + using PipelineCompatibilityToken = TypedInteger<struct PipelineCompatibilityTokenT, uint64_t>; + +} // namespace dawn::native + +#endif // DAWNNATIVE_INTEGERTYPES_H_
diff --git a/src/dawn/native/InternalPipelineStore.cpp b/src/dawn/native/InternalPipelineStore.cpp new file mode 100644 index 0000000..a2532aa --- /dev/null +++ b/src/dawn/native/InternalPipelineStore.cpp
@@ -0,0 +1,38 @@ +// Copyright 2021 The Dawn Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include "dawn/native/InternalPipelineStore.h" + +#include "dawn/native/ComputePipeline.h" +#include "dawn/native/Device.h" +#include "dawn/native/RenderPipeline.h" +#include "dawn/native/ShaderModule.h" + +#include <unordered_map> + +namespace dawn::native { + + class RenderPipelineBase; + class ShaderModuleBase; + + InternalPipelineStore::InternalPipelineStore(DeviceBase* device) + : scratchStorage(device, wgpu::BufferUsage::CopyDst | wgpu::BufferUsage::Storage), + scratchIndirectStorage(device, + wgpu::BufferUsage::CopyDst | wgpu::BufferUsage::Indirect | + wgpu::BufferUsage::Storage) { + } + + InternalPipelineStore::~InternalPipelineStore() = default; + +} // namespace dawn::native
diff --git a/src/dawn/native/InternalPipelineStore.h b/src/dawn/native/InternalPipelineStore.h new file mode 100644 index 0000000..64e7728 --- /dev/null +++ b/src/dawn/native/InternalPipelineStore.h
@@ -0,0 +1,60 @@ +// Copyright 2020 The Dawn Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#ifndef DAWNNATIVE_INTERNALPIPELINESTORE_H_ +#define DAWNNATIVE_INTERNALPIPELINESTORE_H_ + +#include "dawn/native/ObjectBase.h" +#include "dawn/native/ScratchBuffer.h" +#include "dawn/native/dawn_platform.h" + +#include <unordered_map> + +namespace dawn::native { + + class DeviceBase; + class RenderPipelineBase; + class ShaderModuleBase; + + // Every DeviceBase owns an InternalPipelineStore. This is a general-purpose cache for + // long-lived objects scoped to a device and used to support arbitrary pipeline operations. + struct InternalPipelineStore { + explicit InternalPipelineStore(DeviceBase* device); + ~InternalPipelineStore(); + + std::unordered_map<wgpu::TextureFormat, Ref<RenderPipelineBase>> + copyTextureForBrowserPipelines; + + Ref<ShaderModuleBase> copyTextureForBrowser; + + Ref<ComputePipelineBase> timestampComputePipeline; + Ref<ShaderModuleBase> timestampCS; + + Ref<ShaderModuleBase> dummyFragmentShader; + + // A scratch buffer suitable for use as a copy destination and storage binding. + ScratchBuffer scratchStorage; + + // A scratch buffer suitable for use as a copy destination, storage binding, and indirect + // buffer for indirect dispatch or draw calls. + ScratchBuffer scratchIndirectStorage; + + Ref<ComputePipelineBase> renderValidationPipeline; + Ref<ShaderModuleBase> renderValidationShader; + Ref<ComputePipelineBase> dispatchIndirectValidationPipeline; + }; + +} // namespace dawn::native + +#endif // DAWNNATIVE_INTERNALPIPELINESTORE_H_
diff --git a/src/dawn/native/Limits.cpp b/src/dawn/native/Limits.cpp new file mode 100644 index 0000000..a7b8ec9 --- /dev/null +++ b/src/dawn/native/Limits.cpp
@@ -0,0 +1,213 @@ +// Copyright 2021 The Dawn Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include "dawn/native/Limits.h" + +#include "dawn/common/Assert.h" + +#include <array> + +// clang-format off +// TODO(crbug.com/dawn/685): +// For now, only expose these tiers until metrics can determine better ones. +#define LIMITS_WORKGROUP_STORAGE_SIZE(X) \ + X(Higher, maxComputeWorkgroupStorageSize, 16352, 32768, 49152, 65536) + +#define LIMITS_STORAGE_BUFFER_BINDING_SIZE(X) \ + X(Higher, maxStorageBufferBindingSize, 134217728, 1073741824, 2147483647, 4294967295) + +// TODO(crbug.com/dawn/685): +// These limits don't have tiers yet. Define two tiers with the same values since the macros +// in this file expect more than one tier. +#define LIMITS_OTHER(X) \ + X(Higher, maxTextureDimension1D, 8192, 8192) \ + X(Higher, maxTextureDimension2D, 8192, 8192) \ + X(Higher, maxTextureDimension3D, 2048, 2048) \ + X(Higher, maxTextureArrayLayers, 256, 256) \ + X(Higher, maxBindGroups, 4, 4) \ + X(Higher, maxDynamicUniformBuffersPerPipelineLayout, 8, 8) \ + X(Higher, maxDynamicStorageBuffersPerPipelineLayout, 4, 4) \ + X(Higher, maxSampledTexturesPerShaderStage, 16, 16) \ + X(Higher, maxSamplersPerShaderStage, 16, 16) \ + X(Higher, maxStorageBuffersPerShaderStage, 8, 8) \ + X(Higher, maxStorageTexturesPerShaderStage, 4, 4) \ + X(Higher, maxUniformBuffersPerShaderStage, 12, 12) \ + X(Higher, maxUniformBufferBindingSize, 65536, 65536) \ + X( Lower, minUniformBufferOffsetAlignment, 256, 256) \ + X( Lower, minStorageBufferOffsetAlignment, 256, 256) \ + X(Higher, maxVertexBuffers, 8, 8) \ + X(Higher, maxVertexAttributes, 16, 16) \ + X(Higher, maxVertexBufferArrayStride, 2048, 2048) \ + X(Higher, maxInterStageShaderComponents, 60, 60) \ + X(Higher, maxComputeInvocationsPerWorkgroup, 256, 256) \ + X(Higher, maxComputeWorkgroupSizeX, 256, 256) \ + X(Higher, maxComputeWorkgroupSizeY, 256, 256) \ + X(Higher, maxComputeWorkgroupSizeZ, 64, 64) \ + X(Higher, maxComputeWorkgroupsPerDimension, 65535, 65535) +// clang-format on + +#define LIMITS_EACH_GROUP(X) \ + X(LIMITS_WORKGROUP_STORAGE_SIZE) \ + X(LIMITS_STORAGE_BUFFER_BINDING_SIZE) \ + X(LIMITS_OTHER) + +#define LIMITS(X) \ + LIMITS_WORKGROUP_STORAGE_SIZE(X) \ + LIMITS_STORAGE_BUFFER_BINDING_SIZE(X) \ + LIMITS_OTHER(X) + +namespace dawn::native { + namespace { + template <uint32_t A, uint32_t B> + constexpr void StaticAssertSame() { + static_assert(A == B, "Mismatching tier count in limit group."); + } + + template <uint32_t I, uint32_t... Is> + constexpr uint32_t ReduceSameValue(std::integer_sequence<uint32_t, I, Is...>) { + int unused[] = {0, (StaticAssertSame<I, Is>(), 0)...}; + DAWN_UNUSED(unused); + return I; + } + + enum class LimitBetterDirection { + Lower, + Higher, + }; + + template <LimitBetterDirection Better> + struct CheckLimit; + + template <> + struct CheckLimit<LimitBetterDirection::Lower> { + template <typename T> + static bool IsBetter(T lhs, T rhs) { + return lhs < rhs; + } + + template <typename T> + static MaybeError Validate(T supported, T required) { + DAWN_INVALID_IF(IsBetter(required, supported), + "Required limit (%u) is lower than the supported limit (%u).", + required, supported); + return {}; + } + }; + + template <> + struct CheckLimit<LimitBetterDirection::Higher> { + template <typename T> + static bool IsBetter(T lhs, T rhs) { + return lhs > rhs; + } + + template <typename T> + static MaybeError Validate(T supported, T required) { + DAWN_INVALID_IF(IsBetter(required, supported), + "Required limit (%u) is greater than the supported limit (%u).", + required, supported); + return {}; + } + }; + + template <typename T> + bool IsLimitUndefined(T value) { + static_assert(sizeof(T) != sizeof(T), "IsLimitUndefined not implemented for this type"); + return false; + } + + template <> + bool IsLimitUndefined<uint32_t>(uint32_t value) { + return value == wgpu::kLimitU32Undefined; + } + + template <> + bool IsLimitUndefined<uint64_t>(uint64_t value) { + return value == wgpu::kLimitU64Undefined; + } + + } // namespace + + void GetDefaultLimits(Limits* limits) { + ASSERT(limits != nullptr); +#define X(Better, limitName, base, ...) limits->limitName = base; + LIMITS(X) +#undef X + } + + Limits ReifyDefaultLimits(const Limits& limits) { + Limits out; +#define X(Better, limitName, base, ...) \ + if (IsLimitUndefined(limits.limitName) || \ + CheckLimit<LimitBetterDirection::Better>::IsBetter( \ + static_cast<decltype(limits.limitName)>(base), limits.limitName)) { \ + /* If the limit is undefined or the default is better, use the default */ \ + out.limitName = base; \ + } else { \ + out.limitName = limits.limitName; \ + } + LIMITS(X) +#undef X + return out; + } + + MaybeError ValidateLimits(const Limits& supportedLimits, const Limits& requiredLimits) { +#define X(Better, limitName, ...) \ + if (!IsLimitUndefined(requiredLimits.limitName)) { \ + DAWN_TRY_CONTEXT(CheckLimit<LimitBetterDirection::Better>::Validate( \ + supportedLimits.limitName, requiredLimits.limitName), \ + "validating " #limitName); \ + } + LIMITS(X) +#undef X + return {}; + } + + Limits ApplyLimitTiers(Limits limits) { +#define X_TIER_COUNT(Better, limitName, ...) , std::integer_sequence<uint64_t, __VA_ARGS__>{}.size() +#define GET_TIER_COUNT(LIMIT_GROUP) \ + ReduceSameValue(std::integer_sequence<uint32_t LIMIT_GROUP(X_TIER_COUNT)>{}) + +#define X_EACH_GROUP(LIMIT_GROUP) \ + { \ + constexpr uint32_t kTierCount = GET_TIER_COUNT(LIMIT_GROUP); \ + for (uint32_t i = kTierCount; i != 0; --i) { \ + LIMIT_GROUP(X_CHECK_BETTER_AND_CLAMP) \ + /* Limits fit in tier and have been clamped. Break. */ \ + break; \ + } \ + } + +#define X_CHECK_BETTER_AND_CLAMP(Better, limitName, ...) \ + { \ + constexpr std::array<decltype(Limits::limitName), kTierCount> tiers{__VA_ARGS__}; \ + decltype(Limits::limitName) tierValue = tiers[i - 1]; \ + if (CheckLimit<LimitBetterDirection::Better>::IsBetter(tierValue, limits.limitName)) { \ + /* The tier is better. Go to the next tier. */ \ + continue; \ + } else if (tierValue != limits.limitName) { \ + /* Better than the tier. Degrade |limits| to the tier. */ \ + limits.limitName = tiers[i - 1]; \ + } \ + } + + LIMITS_EACH_GROUP(X_EACH_GROUP) +#undef X_CHECK_BETTER +#undef X_EACH_GROUP +#undef GET_TIER_COUNT +#undef X_TIER_COUNT + return limits; + } + +} // namespace dawn::native
diff --git a/src/dawn/native/Limits.h b/src/dawn/native/Limits.h new file mode 100644 index 0000000..f41eaa8 --- /dev/null +++ b/src/dawn/native/Limits.h
@@ -0,0 +1,43 @@ +// Copyright 2021 The Dawn Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#ifndef DAWNNATIVE_LIMITS_H_ +#define DAWNNATIVE_LIMITS_H_ + +#include "dawn/native/Error.h" +#include "dawn/native/dawn_platform.h" + +namespace dawn::native { + + struct CombinedLimits { + Limits v1; + }; + + // Populate |limits| with the default limits. + void GetDefaultLimits(Limits* limits); + + // Returns a copy of |limits| where all undefined values are replaced + // with their defaults. Also clamps to the defaults if the provided limits + // are worse. + Limits ReifyDefaultLimits(const Limits& limits); + + // Validate that |requiredLimits| are no better than |supportedLimits|. + MaybeError ValidateLimits(const Limits& supportedLimits, const Limits& requiredLimits); + + // Returns a copy of |limits| where limit tiers are applied. + Limits ApplyLimitTiers(Limits limits); + +} // namespace dawn::native + +#endif // DAWNNATIVE_LIMITS_H_
diff --git a/src/dawn/native/ObjectBase.cpp b/src/dawn/native/ObjectBase.cpp new file mode 100644 index 0000000..3cafdb7 --- /dev/null +++ b/src/dawn/native/ObjectBase.cpp
@@ -0,0 +1,90 @@ +// Copyright 2018 The Dawn Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include "dawn/native/ObjectBase.h" +#include "dawn/native/Device.h" + +#include <mutex> + +namespace dawn::native { + + static constexpr uint64_t kErrorPayload = 0; + static constexpr uint64_t kNotErrorPayload = 1; + + ObjectBase::ObjectBase(DeviceBase* device) : RefCounted(kNotErrorPayload), mDevice(device) { + } + + ObjectBase::ObjectBase(DeviceBase* device, ErrorTag) + : RefCounted(kErrorPayload), mDevice(device) { + } + + DeviceBase* ObjectBase::GetDevice() const { + return mDevice; + } + + bool ObjectBase::IsError() const { + return GetRefCountPayload() == kErrorPayload; + } + + ApiObjectBase::ApiObjectBase(DeviceBase* device, const char* label) : ObjectBase(device) { + if (label) { + mLabel = label; + } + } + + ApiObjectBase::ApiObjectBase(DeviceBase* device, ErrorTag tag) : ObjectBase(device, tag) { + } + + ApiObjectBase::ApiObjectBase(DeviceBase* device, LabelNotImplementedTag tag) + : ObjectBase(device) { + } + + ApiObjectBase::~ApiObjectBase() { + ASSERT(!IsAlive()); + } + + void ApiObjectBase::APISetLabel(const char* label) { + mLabel = label; + SetLabelImpl(); + } + + const std::string& ApiObjectBase::GetLabel() const { + return mLabel; + } + + void ApiObjectBase::SetLabelImpl() { + } + + bool ApiObjectBase::IsAlive() const { + return IsInList(); + } + + void ApiObjectBase::DeleteThis() { + Destroy(); + RefCounted::DeleteThis(); + } + + void ApiObjectBase::TrackInDevice() { + ASSERT(GetDevice() != nullptr); + GetDevice()->TrackObject(this); + } + + void ApiObjectBase::Destroy() { + const std::lock_guard<std::mutex> lock(*GetDevice()->GetObjectListMutex(GetType())); + if (RemoveFromList()) { + DestroyImpl(); + } + } + +} // namespace dawn::native
diff --git a/src/dawn/native/ObjectBase.h b/src/dawn/native/ObjectBase.h new file mode 100644 index 0000000..8f110a1 --- /dev/null +++ b/src/dawn/native/ObjectBase.h
@@ -0,0 +1,97 @@ +// Copyright 2018 The Dawn Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#ifndef DAWNNATIVE_OBJECTBASE_H_ +#define DAWNNATIVE_OBJECTBASE_H_ + +#include "dawn/common/LinkedList.h" +#include "dawn/common/RefCounted.h" +#include "dawn/native/Forward.h" + +#include <string> + +namespace dawn::native { + + class DeviceBase; + + class ObjectBase : public RefCounted { + public: + struct ErrorTag {}; + static constexpr ErrorTag kError = {}; + + explicit ObjectBase(DeviceBase* device); + ObjectBase(DeviceBase* device, ErrorTag tag); + + DeviceBase* GetDevice() const; + bool IsError() const; + + private: + // Pointer to owning device. + DeviceBase* mDevice; + }; + + class ApiObjectBase : public ObjectBase, public LinkNode<ApiObjectBase> { + public: + struct LabelNotImplementedTag {}; + static constexpr LabelNotImplementedTag kLabelNotImplemented = {}; + struct UntrackedByDeviceTag {}; + static constexpr UntrackedByDeviceTag kUntrackedByDevice = {}; + + ApiObjectBase(DeviceBase* device, LabelNotImplementedTag tag); + ApiObjectBase(DeviceBase* device, const char* label); + ApiObjectBase(DeviceBase* device, ErrorTag tag); + ~ApiObjectBase() override; + + virtual ObjectType GetType() const = 0; + const std::string& GetLabel() const; + + // The ApiObjectBase is considered alive if it is tracked in a respective linked list owned + // by the owning device. + bool IsAlive() const; + + // This needs to be public because it can be called from the device owning the object. + void Destroy(); + + // Dawn API + void APISetLabel(const char* label); + + protected: + // Overriding of the RefCounted's DeleteThis function ensures that instances of objects + // always call their derived class implementation of Destroy prior to the derived + // class being destroyed. This guarantees that when ApiObjects' reference counts drop to 0, + // then the underlying backend's Destroy calls are executed. We cannot naively put the call + // to Destroy in the destructor of this class because it calls DestroyImpl + // which is a virtual function often implemented in the Derived class which would already + // have been destroyed by the time ApiObject's destructor is called by C++'s destruction + // order. Note that some classes like BindGroup may override the DeleteThis function again, + // and they should ensure that their overriding versions call this underlying version + // somewhere. + void DeleteThis() override; + void TrackInDevice(); + + // Sub-classes may override this function multiple times. Whenever overriding this function, + // however, users should be sure to call their parent's version in the new override to make + // sure that all destroy functionality is kept. This function is guaranteed to only be + // called once through the exposed Destroy function. + virtual void DestroyImpl() = 0; + + private: + virtual void SetLabelImpl(); + + std::string mLabel; + }; + +} // namespace dawn::native + +#endif // DAWNNATIVE_OBJECTBASE_H_
diff --git a/src/dawn/native/ObjectContentHasher.cpp b/src/dawn/native/ObjectContentHasher.cpp new file mode 100644 index 0000000..58c892e --- /dev/null +++ b/src/dawn/native/ObjectContentHasher.cpp
@@ -0,0 +1,22 @@ +// Copyright 2020 The Dawn Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include "dawn/native/ObjectContentHasher.h" + +namespace dawn::native { + + size_t ObjectContentHasher::GetContentHash() const { + return mContentHash; + } +} // namespace dawn::native
diff --git a/src/dawn/native/ObjectContentHasher.h b/src/dawn/native/ObjectContentHasher.h new file mode 100644 index 0000000..c1ca32a --- /dev/null +++ b/src/dawn/native/ObjectContentHasher.h
@@ -0,0 +1,82 @@ +// Copyright 2020 The Dawn Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#ifndef DAWNNATIVE_OBJECT_CONTENT_HASHER_H_ +#define DAWNNATIVE_OBJECT_CONTENT_HASHER_H_ + +#include "dawn/common/HashUtils.h" + +#include <string> +#include <vector> + +namespace dawn::native { + + // ObjectContentHasher records a hash that can be used as a key to lookup a cached object in a + // cache. + class ObjectContentHasher { + public: + // Record calls the appropriate record function based on the type. + template <typename T, typename... Args> + void Record(const T& value, const Args&... args) { + RecordImpl<T, Args...>::Call(this, value, args...); + } + + size_t GetContentHash() const; + + private: + template <typename T, typename... Args> + struct RecordImpl { + static constexpr void Call(ObjectContentHasher* recorder, + const T& value, + const Args&... args) { + HashCombine(&recorder->mContentHash, value, args...); + } + }; + + template <typename T> + struct RecordImpl<T*> { + static constexpr void Call(ObjectContentHasher* recorder, T* obj) { + // Calling Record(objPtr) is not allowed. This check exists to only prevent such + // mistakes. + static_assert(obj == nullptr); + } + }; + + template <typename T> + struct RecordImpl<std::vector<T>> { + static constexpr void Call(ObjectContentHasher* recorder, const std::vector<T>& vec) { + recorder->RecordIterable<std::vector<T>>(vec); + } + }; + + template <typename IteratorT> + constexpr void RecordIterable(const IteratorT& iterable) { + for (auto it = iterable.begin(); it != iterable.end(); ++it) { + Record(*it); + } + } + + size_t mContentHash = 0; + }; + + template <> + struct ObjectContentHasher::RecordImpl<std::string> { + static constexpr void Call(ObjectContentHasher* recorder, const std::string& str) { + recorder->RecordIterable<std::string>(str); + } + }; + +} // namespace dawn::native + +#endif // DAWNNATIVE_OBJECT_CONTENT_HASHER_H_
diff --git a/src/dawn/native/PassResourceUsage.h b/src/dawn/native/PassResourceUsage.h new file mode 100644 index 0000000..c6fe535 --- /dev/null +++ b/src/dawn/native/PassResourceUsage.h
@@ -0,0 +1,100 @@ +// Copyright 2018 The Dawn Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#ifndef DAWNNATIVE_PASSRESOURCEUSAGE_H +#define DAWNNATIVE_PASSRESOURCEUSAGE_H + +#include "dawn/native/SubresourceStorage.h" +#include "dawn/native/dawn_platform.h" + +#include <set> +#include <vector> + +namespace dawn::native { + + // This file declares various "ResourceUsage" structures. They are produced by the frontend + // while recording commands to be used for later validation and also some operations in the + // backends. The are produced by the "Encoder" objects that finalize them on "EndPass" or + // "Finish". Internally the "Encoder" may use the "StateTracker" to create them. + + class BufferBase; + class QuerySetBase; + class TextureBase; + + // The texture usage inside passes must be tracked per-subresource. + using TextureSubresourceUsage = SubresourceStorage<wgpu::TextureUsage>; + + // Which resources are used by a synchronization scope and how they are used. The command + // buffer validation pre-computes this information so that backends with explicit barriers + // don't have to re-compute it. + struct SyncScopeResourceUsage { + std::vector<BufferBase*> buffers; + std::vector<wgpu::BufferUsage> bufferUsages; + + std::vector<TextureBase*> textures; + std::vector<TextureSubresourceUsage> textureUsages; + + std::vector<ExternalTextureBase*> externalTextures; + }; + + // Contains all the resource usage data for a compute pass. + // + // Essentially a list of SyncScopeResourceUsage, one per Dispatch as required by the WebGPU + // specification. ComputePassResourceUsage also stores nline the set of all buffers and + // textures used, because some unused BindGroups may not be used at all in synchronization + // scope but their resources still need to be validated on Queue::Submit. + struct ComputePassResourceUsage { + // Somehow without this defaulted constructor, MSVC or its STDlib have an issue where they + // use the copy constructor (that's deleted) when doing operations on a + // vector<ComputePassResourceUsage> + ComputePassResourceUsage(ComputePassResourceUsage&&) = default; + ComputePassResourceUsage() = default; + + std::vector<SyncScopeResourceUsage> dispatchUsages; + + // All the resources referenced by this compute pass for validation in Queue::Submit. + std::set<BufferBase*> referencedBuffers; + std::set<TextureBase*> referencedTextures; + std::set<ExternalTextureBase*> referencedExternalTextures; + }; + + // Contains all the resource usage data for a render pass. + // + // In the WebGPU specification render passes are synchronization scopes but we also need to + // track additional data. It is stored for render passes used by a CommandBuffer, but also in + // RenderBundle so they can be merged into the render passes' usage on ExecuteBundles(). + struct RenderPassResourceUsage : public SyncScopeResourceUsage { + // Storage to track the occlusion queries used during the pass. + std::vector<QuerySetBase*> querySets; + std::vector<std::vector<bool>> queryAvailabilities; + }; + + using RenderPassUsages = std::vector<RenderPassResourceUsage>; + using ComputePassUsages = std::vector<ComputePassResourceUsage>; + + // Contains a hierarchy of "ResourceUsage" that mirrors the hierarchy of the CommandBuffer and + // is used for validation and to produce barriers and lazy clears in the backends. + struct CommandBufferResourceUsage { + RenderPassUsages renderPasses; + ComputePassUsages computePasses; + + // Resources used in commands that aren't in a pass. + std::set<BufferBase*> topLevelBuffers; + std::set<TextureBase*> topLevelTextures; + std::set<QuerySetBase*> usedQuerySets; + }; + +} // namespace dawn::native + +#endif // DAWNNATIVE_PASSRESOURCEUSAGE_H
diff --git a/src/dawn/native/PassResourceUsageTracker.cpp b/src/dawn/native/PassResourceUsageTracker.cpp new file mode 100644 index 0000000..b4814cf --- /dev/null +++ b/src/dawn/native/PassResourceUsageTracker.cpp
@@ -0,0 +1,243 @@ +// Copyright 2019 The Dawn Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include "dawn/native/PassResourceUsageTracker.h" + +#include "dawn/native/BindGroup.h" +#include "dawn/native/Buffer.h" +#include "dawn/native/EnumMaskIterator.h" +#include "dawn/native/ExternalTexture.h" +#include "dawn/native/Format.h" +#include "dawn/native/QuerySet.h" +#include "dawn/native/Texture.h" + +#include <utility> + +namespace dawn::native { + + void SyncScopeUsageTracker::BufferUsedAs(BufferBase* buffer, wgpu::BufferUsage usage) { + // std::map's operator[] will create the key and return 0 if the key didn't exist + // before. + mBufferUsages[buffer] |= usage; + } + + void SyncScopeUsageTracker::TextureViewUsedAs(TextureViewBase* view, wgpu::TextureUsage usage) { + TextureBase* texture = view->GetTexture(); + const SubresourceRange& range = view->GetSubresourceRange(); + + // Get or create a new TextureSubresourceUsage for that texture (initially filled with + // wgpu::TextureUsage::None) + auto it = mTextureUsages.emplace( + std::piecewise_construct, std::forward_as_tuple(texture), + std::forward_as_tuple(texture->GetFormat().aspects, texture->GetArrayLayers(), + texture->GetNumMipLevels(), wgpu::TextureUsage::None)); + TextureSubresourceUsage& textureUsage = it.first->second; + + textureUsage.Update(range, + [usage](const SubresourceRange&, wgpu::TextureUsage* storedUsage) { + // TODO(crbug.com/dawn/1001): Consider optimizing to have fewer + // branches. + if ((*storedUsage & wgpu::TextureUsage::RenderAttachment) != 0 && + (usage & wgpu::TextureUsage::RenderAttachment) != 0) { + // Using the same subresource as an attachment for two different + // render attachments is a write-write hazard. Add this internal + // usage so we will fail the check that a subresource with + // writable usage is the single usage. + *storedUsage |= kAgainAsRenderAttachment; + } + *storedUsage |= usage; + }); + } + + void SyncScopeUsageTracker::AddRenderBundleTextureUsage( + TextureBase* texture, + const TextureSubresourceUsage& textureUsage) { + // Get or create a new TextureSubresourceUsage for that texture (initially filled with + // wgpu::TextureUsage::None) + auto it = mTextureUsages.emplace( + std::piecewise_construct, std::forward_as_tuple(texture), + std::forward_as_tuple(texture->GetFormat().aspects, texture->GetArrayLayers(), + texture->GetNumMipLevels(), wgpu::TextureUsage::None)); + TextureSubresourceUsage* passTextureUsage = &it.first->second; + + passTextureUsage->Merge( + textureUsage, [](const SubresourceRange&, wgpu::TextureUsage* storedUsage, + const wgpu::TextureUsage& addedUsage) { + ASSERT((addedUsage & wgpu::TextureUsage::RenderAttachment) == 0); + *storedUsage |= addedUsage; + }); + } + + void SyncScopeUsageTracker::AddBindGroup(BindGroupBase* group) { + for (BindingIndex bindingIndex{0}; bindingIndex < group->GetLayout()->GetBindingCount(); + ++bindingIndex) { + const BindingInfo& bindingInfo = group->GetLayout()->GetBindingInfo(bindingIndex); + + switch (bindingInfo.bindingType) { + case BindingInfoType::Buffer: { + BufferBase* buffer = group->GetBindingAsBufferBinding(bindingIndex).buffer; + switch (bindingInfo.buffer.type) { + case wgpu::BufferBindingType::Uniform: + BufferUsedAs(buffer, wgpu::BufferUsage::Uniform); + break; + case wgpu::BufferBindingType::Storage: + BufferUsedAs(buffer, wgpu::BufferUsage::Storage); + break; + case kInternalStorageBufferBinding: + BufferUsedAs(buffer, kInternalStorageBuffer); + break; + case wgpu::BufferBindingType::ReadOnlyStorage: + BufferUsedAs(buffer, kReadOnlyStorageBuffer); + break; + case wgpu::BufferBindingType::Undefined: + UNREACHABLE(); + } + break; + } + + case BindingInfoType::Texture: { + TextureViewBase* view = group->GetBindingAsTextureView(bindingIndex); + TextureViewUsedAs(view, wgpu::TextureUsage::TextureBinding); + break; + } + + case BindingInfoType::StorageTexture: { + TextureViewBase* view = group->GetBindingAsTextureView(bindingIndex); + switch (bindingInfo.storageTexture.access) { + case wgpu::StorageTextureAccess::WriteOnly: + TextureViewUsedAs(view, wgpu::TextureUsage::StorageBinding); + break; + case wgpu::StorageTextureAccess::Undefined: + UNREACHABLE(); + } + break; + } + + case BindingInfoType::ExternalTexture: + UNREACHABLE(); + break; + + case BindingInfoType::Sampler: + break; + } + } + + for (const Ref<ExternalTextureBase>& externalTexture : group->GetBoundExternalTextures()) { + mExternalTextureUsages.insert(externalTexture.Get()); + } + } + + SyncScopeResourceUsage SyncScopeUsageTracker::AcquireSyncScopeUsage() { + SyncScopeResourceUsage result; + result.buffers.reserve(mBufferUsages.size()); + result.bufferUsages.reserve(mBufferUsages.size()); + result.textures.reserve(mTextureUsages.size()); + result.textureUsages.reserve(mTextureUsages.size()); + + for (auto& [buffer, usage] : mBufferUsages) { + result.buffers.push_back(buffer); + result.bufferUsages.push_back(usage); + } + + for (auto& [texture, usage] : mTextureUsages) { + result.textures.push_back(texture); + result.textureUsages.push_back(std::move(usage)); + } + + for (auto& it : mExternalTextureUsages) { + result.externalTextures.push_back(it); + } + + mBufferUsages.clear(); + mTextureUsages.clear(); + mExternalTextureUsages.clear(); + + return result; + } + + void ComputePassResourceUsageTracker::AddDispatch(SyncScopeResourceUsage scope) { + mUsage.dispatchUsages.push_back(std::move(scope)); + } + + void ComputePassResourceUsageTracker::AddReferencedBuffer(BufferBase* buffer) { + mUsage.referencedBuffers.insert(buffer); + } + + void ComputePassResourceUsageTracker::AddResourcesReferencedByBindGroup(BindGroupBase* group) { + for (BindingIndex index{0}; index < group->GetLayout()->GetBindingCount(); ++index) { + const BindingInfo& bindingInfo = group->GetLayout()->GetBindingInfo(index); + + switch (bindingInfo.bindingType) { + case BindingInfoType::Buffer: { + mUsage.referencedBuffers.insert(group->GetBindingAsBufferBinding(index).buffer); + break; + } + + case BindingInfoType::Texture: { + mUsage.referencedTextures.insert( + group->GetBindingAsTextureView(index)->GetTexture()); + break; + } + + case BindingInfoType::ExternalTexture: + UNREACHABLE(); + case BindingInfoType::StorageTexture: + case BindingInfoType::Sampler: + break; + } + } + + for (const Ref<ExternalTextureBase>& externalTexture : group->GetBoundExternalTextures()) { + mUsage.referencedExternalTextures.insert(externalTexture.Get()); + } + } + + ComputePassResourceUsage ComputePassResourceUsageTracker::AcquireResourceUsage() { + return std::move(mUsage); + } + + RenderPassResourceUsage RenderPassResourceUsageTracker::AcquireResourceUsage() { + RenderPassResourceUsage result; + *static_cast<SyncScopeResourceUsage*>(&result) = AcquireSyncScopeUsage(); + + result.querySets.reserve(mQueryAvailabilities.size()); + result.queryAvailabilities.reserve(mQueryAvailabilities.size()); + + for (auto& it : mQueryAvailabilities) { + result.querySets.push_back(it.first); + result.queryAvailabilities.push_back(std::move(it.second)); + } + + mQueryAvailabilities.clear(); + + return result; + } + + void RenderPassResourceUsageTracker::TrackQueryAvailability(QuerySetBase* querySet, + uint32_t queryIndex) { + // The query availability only needs to be tracked again on render passes for checking + // query overwrite on render pass and resetting query sets on the Vulkan backend. + DAWN_ASSERT(querySet != nullptr); + + // Gets the iterator for that querySet or create a new vector of bool set to false + // if the querySet wasn't registered. + auto it = mQueryAvailabilities.emplace(querySet, querySet->GetQueryCount()).first; + it->second[queryIndex] = true; + } + + const QueryAvailabilityMap& RenderPassResourceUsageTracker::GetQueryAvailabilityMap() const { + return mQueryAvailabilities; + } + +} // namespace dawn::native
diff --git a/src/dawn/native/PassResourceUsageTracker.h b/src/dawn/native/PassResourceUsageTracker.h new file mode 100644 index 0000000..ad0ef92 --- /dev/null +++ b/src/dawn/native/PassResourceUsageTracker.h
@@ -0,0 +1,86 @@ +// Copyright 2019 The Dawn Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#ifndef DAWNNATIVE_PASSRESOURCEUSAGETRACKER_H_ +#define DAWNNATIVE_PASSRESOURCEUSAGETRACKER_H_ + +#include "dawn/native/PassResourceUsage.h" + +#include "dawn/native/dawn_platform.h" + +#include <map> + +namespace dawn::native { + + class BindGroupBase; + class BufferBase; + class ExternalTextureBase; + class QuerySetBase; + class TextureBase; + + using QueryAvailabilityMap = std::map<QuerySetBase*, std::vector<bool>>; + + // Helper class to build SyncScopeResourceUsages + class SyncScopeUsageTracker { + public: + void BufferUsedAs(BufferBase* buffer, wgpu::BufferUsage usage); + void TextureViewUsedAs(TextureViewBase* texture, wgpu::TextureUsage usage); + void AddRenderBundleTextureUsage(TextureBase* texture, + const TextureSubresourceUsage& textureUsage); + + // Walks the bind groups and tracks all its resources. + void AddBindGroup(BindGroupBase* group); + + // Returns the per-pass usage for use by backends for APIs with explicit barriers. + SyncScopeResourceUsage AcquireSyncScopeUsage(); + + private: + std::map<BufferBase*, wgpu::BufferUsage> mBufferUsages; + std::map<TextureBase*, TextureSubresourceUsage> mTextureUsages; + std::set<ExternalTextureBase*> mExternalTextureUsages; + }; + + // Helper class to build ComputePassResourceUsages + class ComputePassResourceUsageTracker { + public: + void AddDispatch(SyncScopeResourceUsage scope); + void AddReferencedBuffer(BufferBase* buffer); + void AddResourcesReferencedByBindGroup(BindGroupBase* group); + + ComputePassResourceUsage AcquireResourceUsage(); + + private: + ComputePassResourceUsage mUsage; + }; + + // Helper class to build RenderPassResourceUsages + class RenderPassResourceUsageTracker : public SyncScopeUsageTracker { + public: + void TrackQueryAvailability(QuerySetBase* querySet, uint32_t queryIndex); + const QueryAvailabilityMap& GetQueryAvailabilityMap() const; + + RenderPassResourceUsage AcquireResourceUsage(); + + private: + // Hide AcquireSyncScopeUsage since users of this class should use AcquireResourceUsage + // instead. + using SyncScopeUsageTracker::AcquireSyncScopeUsage; + + // Tracks queries used in the render pass to validate that they aren't written twice. + QueryAvailabilityMap mQueryAvailabilities; + }; + +} // namespace dawn::native + +#endif // DAWNNATIVE_PASSRESOURCEUSAGETRACKER_H_
diff --git a/src/dawn/native/PerStage.cpp b/src/dawn/native/PerStage.cpp new file mode 100644 index 0000000..f3d5dc5 --- /dev/null +++ b/src/dawn/native/PerStage.cpp
@@ -0,0 +1,29 @@ +// Copyright 2017 The Dawn Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include "dawn/native/PerStage.h" + +namespace dawn::native { + + BitSetIterator<kNumStages, SingleShaderStage> IterateStages(wgpu::ShaderStage stages) { + std::bitset<kNumStages> bits(static_cast<uint32_t>(stages)); + return BitSetIterator<kNumStages, SingleShaderStage>(bits); + } + + wgpu::ShaderStage StageBit(SingleShaderStage stage) { + ASSERT(static_cast<uint32_t>(stage) < kNumStages); + return static_cast<wgpu::ShaderStage>(1 << static_cast<uint32_t>(stage)); + } + +} // namespace dawn::native
diff --git a/src/dawn/native/PerStage.h b/src/dawn/native/PerStage.h new file mode 100644 index 0000000..83039b2 --- /dev/null +++ b/src/dawn/native/PerStage.h
@@ -0,0 +1,82 @@ +// Copyright 2017 The Dawn Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#ifndef DAWNNATIVE_PERSTAGE_H_ +#define DAWNNATIVE_PERSTAGE_H_ + +#include "dawn/common/Assert.h" +#include "dawn/common/BitSetIterator.h" +#include "dawn/common/Constants.h" +#include "dawn/native/Error.h" + +#include "dawn/native/dawn_platform.h" + +#include <array> + +namespace dawn::native { + + enum class SingleShaderStage { Vertex, Fragment, Compute }; + + static_assert(static_cast<uint32_t>(SingleShaderStage::Vertex) < kNumStages); + static_assert(static_cast<uint32_t>(SingleShaderStage::Fragment) < kNumStages); + static_assert(static_cast<uint32_t>(SingleShaderStage::Compute) < kNumStages); + + static_assert(static_cast<uint32_t>(wgpu::ShaderStage::Vertex) == + (1 << static_cast<uint32_t>(SingleShaderStage::Vertex))); + static_assert(static_cast<uint32_t>(wgpu::ShaderStage::Fragment) == + (1 << static_cast<uint32_t>(SingleShaderStage::Fragment))); + static_assert(static_cast<uint32_t>(wgpu::ShaderStage::Compute) == + (1 << static_cast<uint32_t>(SingleShaderStage::Compute))); + + BitSetIterator<kNumStages, SingleShaderStage> IterateStages(wgpu::ShaderStage stages); + wgpu::ShaderStage StageBit(SingleShaderStage stage); + + static constexpr wgpu::ShaderStage kAllStages = + static_cast<wgpu::ShaderStage>((1 << kNumStages) - 1); + + template <typename T> + class PerStage { + public: + PerStage() = default; + PerStage(const T& initialValue) { + mData.fill(initialValue); + } + + T& operator[](SingleShaderStage stage) { + DAWN_ASSERT(static_cast<uint32_t>(stage) < kNumStages); + return mData[static_cast<uint32_t>(stage)]; + } + const T& operator[](SingleShaderStage stage) const { + DAWN_ASSERT(static_cast<uint32_t>(stage) < kNumStages); + return mData[static_cast<uint32_t>(stage)]; + } + + T& operator[](wgpu::ShaderStage stageBit) { + uint32_t bit = static_cast<uint32_t>(stageBit); + DAWN_ASSERT(bit != 0 && IsPowerOfTwo(bit) && bit <= (1 << kNumStages)); + return mData[Log2(bit)]; + } + const T& operator[](wgpu::ShaderStage stageBit) const { + uint32_t bit = static_cast<uint32_t>(stageBit); + DAWN_ASSERT(bit != 0 && IsPowerOfTwo(bit) && bit <= (1 << kNumStages)); + return mData[Log2(bit)]; + } + + private: + std::array<T, kNumStages> mData; + }; + +} // namespace dawn::native + +#endif // DAWNNATIVE_PERSTAGE_H_
diff --git a/src/dawn/native/PersistentCache.cpp b/src/dawn/native/PersistentCache.cpp new file mode 100644 index 0000000..ce3ab49 --- /dev/null +++ b/src/dawn/native/PersistentCache.cpp
@@ -0,0 +1,64 @@ +// Copyright 2020 The Dawn Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include "dawn/native/PersistentCache.h" + +#include "dawn/common/Assert.h" +#include "dawn/native/Device.h" +#include "dawn/platform/DawnPlatform.h" + +namespace dawn::native { + + PersistentCache::PersistentCache(DeviceBase* device) + : mDevice(device), mCache(GetPlatformCache()) { + } + + ScopedCachedBlob PersistentCache::LoadData(const PersistentCacheKey& key) { + ScopedCachedBlob blob = {}; + if (mCache == nullptr) { + return blob; + } + std::lock_guard<std::mutex> lock(mMutex); + blob.bufferSize = mCache->LoadData(ToAPI(mDevice), key.data(), key.size(), nullptr, 0); + if (blob.bufferSize > 0) { + blob.buffer.reset(new uint8_t[blob.bufferSize]); + const size_t bufferSize = mCache->LoadData(ToAPI(mDevice), key.data(), key.size(), + blob.buffer.get(), blob.bufferSize); + ASSERT(bufferSize == blob.bufferSize); + return blob; + } + return blob; + } + + void PersistentCache::StoreData(const PersistentCacheKey& key, const void* value, size_t size) { + if (mCache == nullptr) { + return; + } + ASSERT(value != nullptr); + ASSERT(size > 0); + std::lock_guard<std::mutex> lock(mMutex); + mCache->StoreData(ToAPI(mDevice), key.data(), key.size(), value, size); + } + + dawn::platform::CachingInterface* PersistentCache::GetPlatformCache() { + // TODO(dawn:549): Create a fingerprint of concatenated version strings (ex. Tint commit + // hash, Dawn commit hash). This will be used by the client so it may know when to discard + // previously cached Dawn objects should this fingerprint change. + dawn::platform::Platform* platform = mDevice->GetPlatform(); + if (platform != nullptr) { + return platform->GetCachingInterface(/*fingerprint*/ nullptr, /*fingerprintSize*/ 0); + } + return nullptr; + } +} // namespace dawn::native
diff --git a/src/dawn/native/PersistentCache.h b/src/dawn/native/PersistentCache.h new file mode 100644 index 0000000..7854d59 --- /dev/null +++ b/src/dawn/native/PersistentCache.h
@@ -0,0 +1,92 @@ +// Copyright 2020 The Dawn Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#ifndef DAWNNATIVE_PERSISTENTCACHE_H_ +#define DAWNNATIVE_PERSISTENTCACHE_H_ + +#include "dawn/native/Error.h" + +#include <mutex> +#include <vector> + +namespace dawn::platform { + class CachingInterface; +} + +namespace dawn::native { + + using PersistentCacheKey = std::vector<uint8_t>; + + struct ScopedCachedBlob { + std::unique_ptr<uint8_t[]> buffer; + size_t bufferSize = 0; + }; + + class DeviceBase; + + enum class PersistentKeyType { Shader }; + + // This class should always be thread-safe as it is used in Create*PipelineAsync() where it is + // called asynchronously. + // The thread-safety of any access to mCache (the function LoadData() and StoreData()) is + // protected by mMutex. + class PersistentCache { + public: + PersistentCache(DeviceBase* device); + + // Combines load/store operations into a single call. + // If the load was successful, a non-empty blob is returned to the caller. + // Else, the creation callback |createFn| gets invoked with a callback + // |doCache| to store the newly created blob back in the cache. + // + // Example usage: + // + // ScopedCachedBlob cachedBlob = {}; + // DAWN_TRY_ASSIGN(cachedBlob, GetOrCreate(key, [&](auto doCache)) { + // // Create a new blob to be stored + // doCache(newBlobPtr, newBlobSize); // store + // })); + // + template <typename CreateFn> + ResultOrError<ScopedCachedBlob> GetOrCreate(const PersistentCacheKey& key, + CreateFn&& createFn) { + // Attempt to load an existing blob from the cache. + ScopedCachedBlob blob = LoadData(key); + if (blob.bufferSize > 0) { + return std::move(blob); + } + + // Allow the caller to create a new blob to be stored for the given key. + DAWN_TRY(createFn([this, key](const void* value, size_t size) { + this->StoreData(key, value, size); + })); + + return std::move(blob); + } + + private: + // PersistentCache impl + ScopedCachedBlob LoadData(const PersistentCacheKey& key); + void StoreData(const PersistentCacheKey& key, const void* value, size_t size); + + dawn::platform::CachingInterface* GetPlatformCache(); + + DeviceBase* mDevice = nullptr; + + std::mutex mMutex; + dawn::platform::CachingInterface* mCache = nullptr; + }; +} // namespace dawn::native + +#endif // DAWNNATIVE_PERSISTENTCACHE_H_
diff --git a/src/dawn/native/Pipeline.cpp b/src/dawn/native/Pipeline.cpp new file mode 100644 index 0000000..344d948 --- /dev/null +++ b/src/dawn/native/Pipeline.cpp
@@ -0,0 +1,259 @@ +// Copyright 2017 The Dawn Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include "dawn/native/Pipeline.h" + +#include "dawn/native/BindGroupLayout.h" +#include "dawn/native/Device.h" +#include "dawn/native/ObjectBase.h" +#include "dawn/native/ObjectContentHasher.h" +#include "dawn/native/PipelineLayout.h" +#include "dawn/native/ShaderModule.h" + +namespace dawn::native { + MaybeError ValidateProgrammableStage(DeviceBase* device, + const ShaderModuleBase* module, + const std::string& entryPoint, + uint32_t constantCount, + const ConstantEntry* constants, + const PipelineLayoutBase* layout, + SingleShaderStage stage) { + DAWN_TRY(device->ValidateObject(module)); + + DAWN_INVALID_IF(!module->HasEntryPoint(entryPoint), + "Entry point \"%s\" doesn't exist in the shader module %s.", entryPoint, + module); + + const EntryPointMetadata& metadata = module->GetEntryPoint(entryPoint); + + if (!metadata.infringedLimitErrors.empty()) { + std::ostringstream out; + out << "Entry point \"" << entryPoint << "\" infringes limits:\n"; + for (const std::string& limit : metadata.infringedLimitErrors) { + out << " - " << limit << "\n"; + } + return DAWN_VALIDATION_ERROR(out.str()); + } + + DAWN_INVALID_IF(metadata.stage != stage, + "The stage (%s) of the entry point \"%s\" isn't the expected one (%s).", + metadata.stage, entryPoint, stage); + + if (layout != nullptr) { + DAWN_TRY(ValidateCompatibilityWithPipelineLayout(device, metadata, layout)); + } + + if (constantCount > 0u && device->IsToggleEnabled(Toggle::DisallowUnsafeAPIs)) { + return DAWN_VALIDATION_ERROR( + "Pipeline overridable constants are disallowed because they are partially " + "implemented."); + } + + // Validate if overridable constants exist in shader module + // pipelineBase is not yet constructed at this moment so iterate constants from descriptor + size_t numUninitializedConstants = metadata.uninitializedOverridableConstants.size(); + // Keep an initialized constants sets to handle duplicate initialization cases + std::unordered_set<std::string> stageInitializedConstantIdentifiers; + for (uint32_t i = 0; i < constantCount; i++) { + DAWN_INVALID_IF(metadata.overridableConstants.count(constants[i].key) == 0, + "Pipeline overridable constant \"%s\" not found in %s.", + constants[i].key, module); + + if (stageInitializedConstantIdentifiers.count(constants[i].key) == 0) { + if (metadata.uninitializedOverridableConstants.count(constants[i].key) > 0) { + numUninitializedConstants--; + } + stageInitializedConstantIdentifiers.insert(constants[i].key); + } else { + // There are duplicate initializations + return DAWN_FORMAT_VALIDATION_ERROR( + "Pipeline overridable constants \"%s\" is set more than once in %s", + constants[i].key, module); + } + } + + // Validate if any overridable constant is left uninitialized + if (DAWN_UNLIKELY(numUninitializedConstants > 0)) { + std::string uninitializedConstantsArray; + bool isFirst = true; + for (std::string identifier : metadata.uninitializedOverridableConstants) { + if (stageInitializedConstantIdentifiers.count(identifier) > 0) { + continue; + } + + if (isFirst) { + isFirst = false; + } else { + uninitializedConstantsArray.append(", "); + } + uninitializedConstantsArray.append(identifier); + } + + return DAWN_FORMAT_VALIDATION_ERROR( + "There are uninitialized pipeline overridable constants in shader module %s, their " + "identifiers:[%s]", + module, uninitializedConstantsArray); + } + + return {}; + } + + // PipelineBase + + PipelineBase::PipelineBase(DeviceBase* device, + PipelineLayoutBase* layout, + const char* label, + std::vector<StageAndDescriptor> stages) + : ApiObjectBase(device, label), mLayout(layout) { + ASSERT(!stages.empty()); + + for (const StageAndDescriptor& stage : stages) { + // Extract argument for this stage. + SingleShaderStage shaderStage = stage.shaderStage; + ShaderModuleBase* module = stage.module; + const char* entryPointName = stage.entryPoint.c_str(); + + const EntryPointMetadata& metadata = module->GetEntryPoint(entryPointName); + ASSERT(metadata.stage == shaderStage); + + // Record them internally. + bool isFirstStage = mStageMask == wgpu::ShaderStage::None; + mStageMask |= StageBit(shaderStage); + mStages[shaderStage] = {module, entryPointName, &metadata, {}}; + auto& constants = mStages[shaderStage].constants; + for (uint32_t i = 0; i < stage.constantCount; i++) { + constants.emplace(stage.constants[i].key, stage.constants[i].value); + } + + // Compute the max() of all minBufferSizes across all stages. + RequiredBufferSizes stageMinBufferSizes = + ComputeRequiredBufferSizesForLayout(metadata, layout); + + if (isFirstStage) { + mMinBufferSizes = std::move(stageMinBufferSizes); + } else { + for (BindGroupIndex group(0); group < mMinBufferSizes.size(); ++group) { + ASSERT(stageMinBufferSizes[group].size() == mMinBufferSizes[group].size()); + + for (size_t i = 0; i < stageMinBufferSizes[group].size(); ++i) { + mMinBufferSizes[group][i] = + std::max(mMinBufferSizes[group][i], stageMinBufferSizes[group][i]); + } + } + } + } + } + + PipelineBase::PipelineBase(DeviceBase* device) : ApiObjectBase(device, kLabelNotImplemented) { + } + + PipelineBase::PipelineBase(DeviceBase* device, ObjectBase::ErrorTag tag) + : ApiObjectBase(device, tag) { + } + + PipelineBase::~PipelineBase() = default; + + PipelineLayoutBase* PipelineBase::GetLayout() { + ASSERT(!IsError()); + return mLayout.Get(); + } + + const PipelineLayoutBase* PipelineBase::GetLayout() const { + ASSERT(!IsError()); + return mLayout.Get(); + } + + const RequiredBufferSizes& PipelineBase::GetMinBufferSizes() const { + ASSERT(!IsError()); + return mMinBufferSizes; + } + + const ProgrammableStage& PipelineBase::GetStage(SingleShaderStage stage) const { + ASSERT(!IsError()); + return mStages[stage]; + } + + const PerStage<ProgrammableStage>& PipelineBase::GetAllStages() const { + return mStages; + } + + wgpu::ShaderStage PipelineBase::GetStageMask() const { + return mStageMask; + } + + MaybeError PipelineBase::ValidateGetBindGroupLayout(uint32_t groupIndex) { + DAWN_TRY(GetDevice()->ValidateIsAlive()); + DAWN_TRY(GetDevice()->ValidateObject(this)); + DAWN_TRY(GetDevice()->ValidateObject(mLayout.Get())); + DAWN_INVALID_IF( + groupIndex >= kMaxBindGroups, + "Bind group layout index (%u) exceeds the maximum number of bind groups (%u).", + groupIndex, kMaxBindGroups); + return {}; + } + + ResultOrError<Ref<BindGroupLayoutBase>> PipelineBase::GetBindGroupLayout( + uint32_t groupIndexIn) { + DAWN_TRY(ValidateGetBindGroupLayout(groupIndexIn)); + + BindGroupIndex groupIndex(groupIndexIn); + if (!mLayout->GetBindGroupLayoutsMask()[groupIndex]) { + return Ref<BindGroupLayoutBase>(GetDevice()->GetEmptyBindGroupLayout()); + } else { + return Ref<BindGroupLayoutBase>(mLayout->GetBindGroupLayout(groupIndex)); + } + } + + BindGroupLayoutBase* PipelineBase::APIGetBindGroupLayout(uint32_t groupIndexIn) { + Ref<BindGroupLayoutBase> result; + if (GetDevice()->ConsumedError(GetBindGroupLayout(groupIndexIn), &result, + "Validating GetBindGroupLayout (%u) on %s", groupIndexIn, + this)) { + return BindGroupLayoutBase::MakeError(GetDevice()); + } + return result.Detach(); + } + + size_t PipelineBase::ComputeContentHash() { + ObjectContentHasher recorder; + recorder.Record(mLayout->GetContentHash()); + + recorder.Record(mStageMask); + for (SingleShaderStage stage : IterateStages(mStageMask)) { + recorder.Record(mStages[stage].module->GetContentHash()); + recorder.Record(mStages[stage].entryPoint); + } + + return recorder.GetContentHash(); + } + + // static + bool PipelineBase::EqualForCache(const PipelineBase* a, const PipelineBase* b) { + // The layout is deduplicated so it can be compared by pointer. + if (a->mLayout.Get() != b->mLayout.Get() || a->mStageMask != b->mStageMask) { + return false; + } + + for (SingleShaderStage stage : IterateStages(a->mStageMask)) { + // The module is deduplicated so it can be compared by pointer. + if (a->mStages[stage].module.Get() != b->mStages[stage].module.Get() || + a->mStages[stage].entryPoint != b->mStages[stage].entryPoint) { + return false; + } + } + + return true; + } + +} // namespace dawn::native
diff --git a/src/dawn/native/Pipeline.h b/src/dawn/native/Pipeline.h new file mode 100644 index 0000000..ab078c3 --- /dev/null +++ b/src/dawn/native/Pipeline.h
@@ -0,0 +1,98 @@ +// Copyright 2017 The Dawn Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#ifndef DAWNNATIVE_PIPELINE_H_ +#define DAWNNATIVE_PIPELINE_H_ + +#include "dawn/native/CachedObject.h" +#include "dawn/native/Forward.h" +#include "dawn/native/ObjectBase.h" +#include "dawn/native/PerStage.h" +#include "dawn/native/PipelineLayout.h" +#include "dawn/native/ShaderModule.h" + +#include "dawn/native/dawn_platform.h" + +#include <array> +#include <bitset> + +namespace dawn::native { + + MaybeError ValidateProgrammableStage(DeviceBase* device, + const ShaderModuleBase* module, + const std::string& entryPoint, + uint32_t constantCount, + const ConstantEntry* constants, + const PipelineLayoutBase* layout, + SingleShaderStage stage); + + // Use map to make sure constant keys are sorted for creating shader cache keys + using PipelineConstantEntries = std::map<std::string, double>; + + struct ProgrammableStage { + Ref<ShaderModuleBase> module; + std::string entryPoint; + + // The metadata lives as long as module, that's ref-ed in the same structure. + const EntryPointMetadata* metadata = nullptr; + + PipelineConstantEntries constants; + }; + + class PipelineBase : public ApiObjectBase, public CachedObject { + public: + ~PipelineBase() override; + + PipelineLayoutBase* GetLayout(); + const PipelineLayoutBase* GetLayout() const; + const RequiredBufferSizes& GetMinBufferSizes() const; + const ProgrammableStage& GetStage(SingleShaderStage stage) const; + const PerStage<ProgrammableStage>& GetAllStages() const; + wgpu::ShaderStage GetStageMask() const; + + ResultOrError<Ref<BindGroupLayoutBase>> GetBindGroupLayout(uint32_t groupIndex); + + // Helper functions for std::unordered_map-based pipeline caches. + size_t ComputeContentHash() override; + static bool EqualForCache(const PipelineBase* a, const PipelineBase* b); + + // Implementation of the API entrypoint. Do not use in a reentrant manner. + BindGroupLayoutBase* APIGetBindGroupLayout(uint32_t groupIndex); + + // Initialize() should only be called once by the frontend. + virtual MaybeError Initialize() = 0; + + protected: + PipelineBase(DeviceBase* device, + PipelineLayoutBase* layout, + const char* label, + std::vector<StageAndDescriptor> stages); + PipelineBase(DeviceBase* device, ObjectBase::ErrorTag tag); + + // Constructor used only for mocking and testing. + PipelineBase(DeviceBase* device); + + private: + MaybeError ValidateGetBindGroupLayout(uint32_t group); + + wgpu::ShaderStage mStageMask = wgpu::ShaderStage::None; + PerStage<ProgrammableStage> mStages; + + Ref<PipelineLayoutBase> mLayout; + RequiredBufferSizes mMinBufferSizes; + }; + +} // namespace dawn::native + +#endif // DAWNNATIVE_PIPELINE_H_
diff --git a/src/dawn/native/PipelineLayout.cpp b/src/dawn/native/PipelineLayout.cpp new file mode 100644 index 0000000..56ab100 --- /dev/null +++ b/src/dawn/native/PipelineLayout.cpp
@@ -0,0 +1,409 @@ +// Copyright 2017 The Dawn Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include "dawn/native/PipelineLayout.h" + +#include "dawn/common/Assert.h" +#include "dawn/common/BitSetIterator.h" +#include "dawn/common/ityp_stack_vec.h" +#include "dawn/native/BindGroupLayout.h" +#include "dawn/native/Device.h" +#include "dawn/native/ObjectContentHasher.h" +#include "dawn/native/ObjectType_autogen.h" +#include "dawn/native/ShaderModule.h" + +namespace dawn::native { + + MaybeError ValidatePipelineLayoutDescriptor( + DeviceBase* device, + const PipelineLayoutDescriptor* descriptor, + PipelineCompatibilityToken pipelineCompatibilityToken) { + if (descriptor->nextInChain != nullptr) { + return DAWN_VALIDATION_ERROR("nextInChain must be nullptr"); + } + + if (descriptor->bindGroupLayoutCount > kMaxBindGroups) { + return DAWN_VALIDATION_ERROR("too many bind group layouts"); + } + + BindingCounts bindingCounts = {}; + for (uint32_t i = 0; i < descriptor->bindGroupLayoutCount; ++i) { + DAWN_TRY(device->ValidateObject(descriptor->bindGroupLayouts[i])); + if (descriptor->bindGroupLayouts[i]->GetPipelineCompatibilityToken() != + pipelineCompatibilityToken) { + return DAWN_VALIDATION_ERROR( + "cannot create a pipeline layout using a bind group layout that was created as " + "part of a pipeline's default layout"); + } + AccumulateBindingCounts(&bindingCounts, + descriptor->bindGroupLayouts[i]->GetBindingCountInfo()); + } + + DAWN_TRY(ValidateBindingCounts(bindingCounts)); + return {}; + } + + // PipelineLayoutBase + + PipelineLayoutBase::PipelineLayoutBase(DeviceBase* device, + const PipelineLayoutDescriptor* descriptor, + ApiObjectBase::UntrackedByDeviceTag tag) + : ApiObjectBase(device, descriptor->label) { + ASSERT(descriptor->bindGroupLayoutCount <= kMaxBindGroups); + for (BindGroupIndex group(0); group < BindGroupIndex(descriptor->bindGroupLayoutCount); + ++group) { + mBindGroupLayouts[group] = descriptor->bindGroupLayouts[static_cast<uint32_t>(group)]; + mMask.set(group); + } + } + + PipelineLayoutBase::PipelineLayoutBase(DeviceBase* device, + const PipelineLayoutDescriptor* descriptor) + : PipelineLayoutBase(device, descriptor, kUntrackedByDevice) { + TrackInDevice(); + } + + PipelineLayoutBase::PipelineLayoutBase(DeviceBase* device) + : ApiObjectBase(device, kLabelNotImplemented) { + TrackInDevice(); + } + + PipelineLayoutBase::PipelineLayoutBase(DeviceBase* device, ObjectBase::ErrorTag tag) + : ApiObjectBase(device, tag) { + } + + PipelineLayoutBase::~PipelineLayoutBase() = default; + + void PipelineLayoutBase::DestroyImpl() { + if (IsCachedReference()) { + // Do not uncache the actual cached object if we are a blueprint. + GetDevice()->UncachePipelineLayout(this); + } + } + + // static + PipelineLayoutBase* PipelineLayoutBase::MakeError(DeviceBase* device) { + return new PipelineLayoutBase(device, ObjectBase::kError); + } + + // static + ResultOrError<Ref<PipelineLayoutBase>> PipelineLayoutBase::CreateDefault( + DeviceBase* device, + std::vector<StageAndDescriptor> stages) { + using EntryMap = std::map<BindingNumber, BindGroupLayoutEntry>; + + // Merges two entries at the same location, if they are allowed to be merged. + auto MergeEntries = [](BindGroupLayoutEntry* modifiedEntry, + const BindGroupLayoutEntry& mergedEntry) -> MaybeError { + // Visibility is excluded because we take the OR across stages. + bool compatible = + modifiedEntry->binding == mergedEntry.binding && + modifiedEntry->buffer.type == mergedEntry.buffer.type && + modifiedEntry->sampler.type == mergedEntry.sampler.type && + // Compatibility between these sample types is checked below. + (modifiedEntry->texture.sampleType != wgpu::TextureSampleType::Undefined) == + (mergedEntry.texture.sampleType != wgpu::TextureSampleType::Undefined) && + modifiedEntry->storageTexture.access == mergedEntry.storageTexture.access; + + // Minimum buffer binding size excluded because we take the maximum seen across stages. + if (modifiedEntry->buffer.type != wgpu::BufferBindingType::Undefined) { + compatible = compatible && modifiedEntry->buffer.hasDynamicOffset == + mergedEntry.buffer.hasDynamicOffset; + } + + if (modifiedEntry->texture.sampleType != wgpu::TextureSampleType::Undefined) { + // Sample types are compatible if they are exactly equal, + // or if the |modifiedEntry| is Float and the |mergedEntry| is UnfilterableFloat. + // Note that the |mergedEntry| never has type Float. Texture bindings all start + // as UnfilterableFloat and are promoted to Float if they are statically used with + // a sampler. + ASSERT(mergedEntry.texture.sampleType != wgpu::TextureSampleType::Float); + bool compatibleSampleTypes = + modifiedEntry->texture.sampleType == mergedEntry.texture.sampleType || + (modifiedEntry->texture.sampleType == wgpu::TextureSampleType::Float && + mergedEntry.texture.sampleType == wgpu::TextureSampleType::UnfilterableFloat); + compatible = + compatible && compatibleSampleTypes && + modifiedEntry->texture.viewDimension == mergedEntry.texture.viewDimension && + modifiedEntry->texture.multisampled == mergedEntry.texture.multisampled; + } + + if (modifiedEntry->storageTexture.access != wgpu::StorageTextureAccess::Undefined) { + compatible = + compatible && + modifiedEntry->storageTexture.format == mergedEntry.storageTexture.format && + modifiedEntry->storageTexture.viewDimension == + mergedEntry.storageTexture.viewDimension; + } + + // Check if any properties are incompatible with existing entry + // If compatible, we will merge some properties + if (!compatible) { + return DAWN_VALIDATION_ERROR( + "Duplicate binding in default pipeline layout initialization " + "not compatible with previous declaration"); + } + + // Use the max |minBufferBindingSize| we find. + modifiedEntry->buffer.minBindingSize = + std::max(modifiedEntry->buffer.minBindingSize, mergedEntry.buffer.minBindingSize); + + // Use the OR of all the stages at which we find this binding. + modifiedEntry->visibility |= mergedEntry.visibility; + + return {}; + }; + + // Does the trivial conversions from a ShaderBindingInfo to a BindGroupLayoutEntry + auto ConvertMetadataToEntry = + [](const ShaderBindingInfo& shaderBinding, + const ExternalTextureBindingLayout* externalTextureBindingEntry) + -> BindGroupLayoutEntry { + BindGroupLayoutEntry entry = {}; + switch (shaderBinding.bindingType) { + case BindingInfoType::Buffer: + entry.buffer.type = shaderBinding.buffer.type; + entry.buffer.hasDynamicOffset = shaderBinding.buffer.hasDynamicOffset; + entry.buffer.minBindingSize = shaderBinding.buffer.minBindingSize; + break; + case BindingInfoType::Sampler: + if (shaderBinding.sampler.isComparison) { + entry.sampler.type = wgpu::SamplerBindingType::Comparison; + } else { + entry.sampler.type = wgpu::SamplerBindingType::Filtering; + } + break; + case BindingInfoType::Texture: + switch (shaderBinding.texture.compatibleSampleTypes) { + case SampleTypeBit::Depth: + entry.texture.sampleType = wgpu::TextureSampleType::Depth; + break; + case SampleTypeBit::Sint: + entry.texture.sampleType = wgpu::TextureSampleType::Sint; + break; + case SampleTypeBit::Uint: + entry.texture.sampleType = wgpu::TextureSampleType::Uint; + break; + case SampleTypeBit::Float: + case SampleTypeBit::UnfilterableFloat: + case SampleTypeBit::None: + UNREACHABLE(); + break; + default: + if (shaderBinding.texture.compatibleSampleTypes == + (SampleTypeBit::Float | SampleTypeBit::UnfilterableFloat)) { + // Default to UnfilterableFloat. It will be promoted to Float if it + // is used with a sampler. + entry.texture.sampleType = + wgpu::TextureSampleType::UnfilterableFloat; + } else { + UNREACHABLE(); + } + } + entry.texture.viewDimension = shaderBinding.texture.viewDimension; + entry.texture.multisampled = shaderBinding.texture.multisampled; + break; + case BindingInfoType::StorageTexture: + entry.storageTexture.access = shaderBinding.storageTexture.access; + entry.storageTexture.format = shaderBinding.storageTexture.format; + entry.storageTexture.viewDimension = shaderBinding.storageTexture.viewDimension; + break; + case BindingInfoType::ExternalTexture: + entry.nextInChain = externalTextureBindingEntry; + break; + } + return entry; + }; + + PipelineCompatibilityToken pipelineCompatibilityToken = + device->GetNextPipelineCompatibilityToken(); + + // Creates the BGL from the entries for a stage, checking it is valid. + auto CreateBGL = [](DeviceBase* device, const EntryMap& entries, + PipelineCompatibilityToken pipelineCompatibilityToken) + -> ResultOrError<Ref<BindGroupLayoutBase>> { + std::vector<BindGroupLayoutEntry> entryVec; + entryVec.reserve(entries.size()); + for (auto& [_, entry] : entries) { + entryVec.push_back(entry); + } + + BindGroupLayoutDescriptor desc = {}; + desc.entries = entryVec.data(); + desc.entryCount = entryVec.size(); + + if (device->IsValidationEnabled()) { + DAWN_TRY_CONTEXT(ValidateBindGroupLayoutDescriptor(device, &desc), "validating %s", + &desc); + } + return device->GetOrCreateBindGroupLayout(&desc, pipelineCompatibilityToken); + }; + + ASSERT(!stages.empty()); + + // Data which BindGroupLayoutDescriptor will point to for creation + ityp::array<BindGroupIndex, std::map<BindingNumber, BindGroupLayoutEntry>, kMaxBindGroups> + entryData = {}; + + // External texture binding layouts are chained structs that are set as a pointer within + // the bind group layout entry. We declare an entry here so that it can be used when needed + // in each BindGroupLayoutEntry and so it can stay alive until the call to + // GetOrCreateBindGroupLayout. Because ExternalTextureBindingLayout is an empty struct, + // there's no issue with using the same struct multiple times. + ExternalTextureBindingLayout externalTextureBindingLayout; + + // Loops over all the reflected BindGroupLayoutEntries from shaders. + for (const StageAndDescriptor& stage : stages) { + const EntryPointMetadata& metadata = stage.module->GetEntryPoint(stage.entryPoint); + + for (BindGroupIndex group(0); group < metadata.bindings.size(); ++group) { + for (const auto& [bindingNumber, shaderBinding] : metadata.bindings[group]) { + // Create the BindGroupLayoutEntry + BindGroupLayoutEntry entry = + ConvertMetadataToEntry(shaderBinding, &externalTextureBindingLayout); + entry.binding = static_cast<uint32_t>(bindingNumber); + entry.visibility = StageBit(stage.shaderStage); + + // Add it to our map of all entries, if there is an existing entry, then we + // need to merge, if we can. + const auto& [existingEntry, inserted] = + entryData[group].insert({bindingNumber, entry}); + if (!inserted) { + DAWN_TRY(MergeEntries(&existingEntry->second, entry)); + } + } + } + + // Promote any Unfilterable textures used with a sampler to Filtering. + for (const EntryPointMetadata::SamplerTexturePair& pair : + metadata.samplerTexturePairs) { + BindGroupLayoutEntry* entry = &entryData[pair.texture.group][pair.texture.binding]; + if (entry->texture.sampleType == wgpu::TextureSampleType::UnfilterableFloat) { + entry->texture.sampleType = wgpu::TextureSampleType::Float; + } + } + } + + // Create the bind group layouts. We need to keep track of the last non-empty BGL because + // Dawn doesn't yet know that an empty BGL and a null BGL are the same thing. + // TODO(cwallez@chromium.org): remove this when Dawn knows that empty and null BGL are the + // same. + BindGroupIndex pipelineBGLCount = BindGroupIndex(0); + ityp::array<BindGroupIndex, Ref<BindGroupLayoutBase>, kMaxBindGroups> bindGroupLayouts = {}; + for (BindGroupIndex group(0); group < kMaxBindGroupsTyped; ++group) { + DAWN_TRY_ASSIGN(bindGroupLayouts[group], + CreateBGL(device, entryData[group], pipelineCompatibilityToken)); + if (entryData[group].size() != 0) { + pipelineBGLCount = group + BindGroupIndex(1); + } + } + + // Create the deduced pipeline layout, validating if it is valid. + ityp::array<BindGroupIndex, BindGroupLayoutBase*, kMaxBindGroups> bgls = {}; + for (BindGroupIndex group(0); group < pipelineBGLCount; ++group) { + bgls[group] = bindGroupLayouts[group].Get(); + } + + PipelineLayoutDescriptor desc = {}; + desc.bindGroupLayouts = bgls.data(); + desc.bindGroupLayoutCount = static_cast<uint32_t>(pipelineBGLCount); + + DAWN_TRY(ValidatePipelineLayoutDescriptor(device, &desc, pipelineCompatibilityToken)); + + Ref<PipelineLayoutBase> result; + DAWN_TRY_ASSIGN(result, device->GetOrCreatePipelineLayout(&desc)); + ASSERT(!result->IsError()); + + // Sanity check in debug that the pipeline layout is compatible with the current + // pipeline. + for (const StageAndDescriptor& stage : stages) { + const EntryPointMetadata& metadata = stage.module->GetEntryPoint(stage.entryPoint); + ASSERT(ValidateCompatibilityWithPipelineLayout(device, metadata, result.Get()) + .IsSuccess()); + } + + return std::move(result); + } + + ObjectType PipelineLayoutBase::GetType() const { + return ObjectType::PipelineLayout; + } + + const BindGroupLayoutBase* PipelineLayoutBase::GetBindGroupLayout(BindGroupIndex group) const { + ASSERT(!IsError()); + ASSERT(group < kMaxBindGroupsTyped); + ASSERT(mMask[group]); + const BindGroupLayoutBase* bgl = mBindGroupLayouts[group].Get(); + ASSERT(bgl != nullptr); + return bgl; + } + + BindGroupLayoutBase* PipelineLayoutBase::GetBindGroupLayout(BindGroupIndex group) { + ASSERT(!IsError()); + ASSERT(group < kMaxBindGroupsTyped); + ASSERT(mMask[group]); + BindGroupLayoutBase* bgl = mBindGroupLayouts[group].Get(); + ASSERT(bgl != nullptr); + return bgl; + } + + const BindGroupLayoutMask& PipelineLayoutBase::GetBindGroupLayoutsMask() const { + ASSERT(!IsError()); + return mMask; + } + + BindGroupLayoutMask PipelineLayoutBase::InheritedGroupsMask( + const PipelineLayoutBase* other) const { + ASSERT(!IsError()); + return {(1 << static_cast<uint32_t>(GroupsInheritUpTo(other))) - 1u}; + } + + BindGroupIndex PipelineLayoutBase::GroupsInheritUpTo(const PipelineLayoutBase* other) const { + ASSERT(!IsError()); + + for (BindGroupIndex i(0); i < kMaxBindGroupsTyped; ++i) { + if (!mMask[i] || mBindGroupLayouts[i].Get() != other->mBindGroupLayouts[i].Get()) { + return i; + } + } + return kMaxBindGroupsTyped; + } + + size_t PipelineLayoutBase::ComputeContentHash() { + ObjectContentHasher recorder; + recorder.Record(mMask); + + for (BindGroupIndex group : IterateBitSet(mMask)) { + recorder.Record(GetBindGroupLayout(group)->GetContentHash()); + } + + return recorder.GetContentHash(); + } + + bool PipelineLayoutBase::EqualityFunc::operator()(const PipelineLayoutBase* a, + const PipelineLayoutBase* b) const { + if (a->mMask != b->mMask) { + return false; + } + + for (BindGroupIndex group : IterateBitSet(a->mMask)) { + if (a->GetBindGroupLayout(group) != b->GetBindGroupLayout(group)) { + return false; + } + } + + return true; + } + +} // namespace dawn::native
diff --git a/src/dawn/native/PipelineLayout.h b/src/dawn/native/PipelineLayout.h new file mode 100644 index 0000000..4850536 --- /dev/null +++ b/src/dawn/native/PipelineLayout.h
@@ -0,0 +1,97 @@ +// Copyright 2017 The Dawn Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#ifndef DAWNNATIVE_PIPELINELAYOUT_H_ +#define DAWNNATIVE_PIPELINELAYOUT_H_ + +#include "dawn/common/Constants.h" +#include "dawn/common/ityp_array.h" +#include "dawn/common/ityp_bitset.h" +#include "dawn/native/BindingInfo.h" +#include "dawn/native/CachedObject.h" +#include "dawn/native/Error.h" +#include "dawn/native/Forward.h" +#include "dawn/native/ObjectBase.h" + +#include "dawn/native/dawn_platform.h" + +#include <array> +#include <bitset> + +namespace dawn::native { + + MaybeError ValidatePipelineLayoutDescriptor( + DeviceBase*, + const PipelineLayoutDescriptor* descriptor, + PipelineCompatibilityToken pipelineCompatibilityToken = PipelineCompatibilityToken(0)); + + using BindGroupLayoutArray = + ityp::array<BindGroupIndex, Ref<BindGroupLayoutBase>, kMaxBindGroups>; + using BindGroupLayoutMask = ityp::bitset<BindGroupIndex, kMaxBindGroups>; + + struct StageAndDescriptor { + SingleShaderStage shaderStage; + ShaderModuleBase* module; + std::string entryPoint; + uint32_t constantCount = 0u; + ConstantEntry const* constants = nullptr; + }; + + class PipelineLayoutBase : public ApiObjectBase, public CachedObject { + public: + PipelineLayoutBase(DeviceBase* device, + const PipelineLayoutDescriptor* descriptor, + ApiObjectBase::UntrackedByDeviceTag tag); + PipelineLayoutBase(DeviceBase* device, const PipelineLayoutDescriptor* descriptor); + ~PipelineLayoutBase() override; + + static PipelineLayoutBase* MakeError(DeviceBase* device); + static ResultOrError<Ref<PipelineLayoutBase>> CreateDefault( + DeviceBase* device, + std::vector<StageAndDescriptor> stages); + + ObjectType GetType() const override; + + const BindGroupLayoutBase* GetBindGroupLayout(BindGroupIndex group) const; + BindGroupLayoutBase* GetBindGroupLayout(BindGroupIndex group); + const BindGroupLayoutMask& GetBindGroupLayoutsMask() const; + + // Utility functions to compute inherited bind groups. + // Returns the inherited bind groups as a mask. + BindGroupLayoutMask InheritedGroupsMask(const PipelineLayoutBase* other) const; + + // Returns the index of the first incompatible bind group in the range + // [0, kMaxBindGroups] + BindGroupIndex GroupsInheritUpTo(const PipelineLayoutBase* other) const; + + // Functions necessary for the unordered_set<PipelineLayoutBase*>-based cache. + size_t ComputeContentHash() override; + + struct EqualityFunc { + bool operator()(const PipelineLayoutBase* a, const PipelineLayoutBase* b) const; + }; + + protected: + // Constructor used only for mocking and testing. + PipelineLayoutBase(DeviceBase* device); + PipelineLayoutBase(DeviceBase* device, ObjectBase::ErrorTag tag); + void DestroyImpl() override; + + BindGroupLayoutArray mBindGroupLayouts; + BindGroupLayoutMask mMask; + }; + +} // namespace dawn::native + +#endif // DAWNNATIVE_PIPELINELAYOUT_H_
diff --git a/src/dawn/native/PooledResourceMemoryAllocator.cpp b/src/dawn/native/PooledResourceMemoryAllocator.cpp new file mode 100644 index 0000000..0a01a99 --- /dev/null +++ b/src/dawn/native/PooledResourceMemoryAllocator.cpp
@@ -0,0 +1,60 @@ +// Copyright 2020 The Dawn Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include "dawn/native/PooledResourceMemoryAllocator.h" +#include "dawn/native/Device.h" + +namespace dawn::native { + + PooledResourceMemoryAllocator::PooledResourceMemoryAllocator( + ResourceHeapAllocator* heapAllocator) + : mHeapAllocator(heapAllocator) { + } + + void PooledResourceMemoryAllocator::DestroyPool() { + for (auto& resourceHeap : mPool) { + ASSERT(resourceHeap != nullptr); + mHeapAllocator->DeallocateResourceHeap(std::move(resourceHeap)); + } + + mPool.clear(); + } + + ResultOrError<std::unique_ptr<ResourceHeapBase>> + PooledResourceMemoryAllocator::AllocateResourceHeap(uint64_t size) { + // Pooled memory is LIFO because memory can be evicted by LRU. However, this means + // pooling is disabled in-frame when the memory is still pending. For high in-frame + // memory users, FIFO might be preferable when memory consumption is a higher priority. + std::unique_ptr<ResourceHeapBase> memory; + if (!mPool.empty()) { + memory = std::move(mPool.front()); + mPool.pop_front(); + } + + if (memory == nullptr) { + DAWN_TRY_ASSIGN(memory, mHeapAllocator->AllocateResourceHeap(size)); + } + + return std::move(memory); + } + + void PooledResourceMemoryAllocator::DeallocateResourceHeap( + std::unique_ptr<ResourceHeapBase> allocation) { + mPool.push_front(std::move(allocation)); + } + + uint64_t PooledResourceMemoryAllocator::GetPoolSizeForTesting() const { + return mPool.size(); + } +} // namespace dawn::native
diff --git a/src/dawn/native/PooledResourceMemoryAllocator.h b/src/dawn/native/PooledResourceMemoryAllocator.h new file mode 100644 index 0000000..898bafe --- /dev/null +++ b/src/dawn/native/PooledResourceMemoryAllocator.h
@@ -0,0 +1,53 @@ +// Copyright 2020 The Dawn Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#ifndef DAWNNATIVE_POOLEDRESOURCEMEMORYALLOCATOR_H_ +#define DAWNNATIVE_POOLEDRESOURCEMEMORYALLOCATOR_H_ + +#include "dawn/common/SerialQueue.h" +#include "dawn/native/ResourceHeapAllocator.h" + +#include <deque> + +namespace dawn::native { + + class DeviceBase; + + // |PooledResourceMemoryAllocator| allocates a fixed-size resource memory from a resource memory + // pool. Internally, it manages a list of heaps using LIFO (newest heaps are recycled first). + // The heap is in one of two states: AVAILABLE or not. Upon de-allocate, the heap is returned + // the pool and made AVAILABLE. + class PooledResourceMemoryAllocator : public ResourceHeapAllocator { + public: + PooledResourceMemoryAllocator(ResourceHeapAllocator* heapAllocator); + ~PooledResourceMemoryAllocator() override = default; + + ResultOrError<std::unique_ptr<ResourceHeapBase>> AllocateResourceHeap( + uint64_t size) override; + void DeallocateResourceHeap(std::unique_ptr<ResourceHeapBase> allocation) override; + + void DestroyPool(); + + // For testing purposes. + uint64_t GetPoolSizeForTesting() const; + + private: + ResourceHeapAllocator* mHeapAllocator = nullptr; + + std::deque<std::unique_ptr<ResourceHeapBase>> mPool; + }; + +} // namespace dawn::native + +#endif // DAWNNATIVE_POOLEDRESOURCEMEMORYALLOCATOR_H_
diff --git a/src/dawn/native/ProgrammableEncoder.cpp b/src/dawn/native/ProgrammableEncoder.cpp new file mode 100644 index 0000000..8bdc08b --- /dev/null +++ b/src/dawn/native/ProgrammableEncoder.cpp
@@ -0,0 +1,203 @@ +// Copyright 2018 The Dawn Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include "dawn/native/ProgrammableEncoder.h" + +#include "dawn/common/BitSetIterator.h" +#include "dawn/common/ityp_array.h" +#include "dawn/native/BindGroup.h" +#include "dawn/native/Buffer.h" +#include "dawn/native/CommandBuffer.h" +#include "dawn/native/Commands.h" +#include "dawn/native/Device.h" +#include "dawn/native/ObjectType_autogen.h" +#include "dawn/native/ValidationUtils_autogen.h" + +#include <cstring> + +namespace dawn::native { + + ProgrammableEncoder::ProgrammableEncoder(DeviceBase* device, + const char* label, + EncodingContext* encodingContext) + : ApiObjectBase(device, label), + mEncodingContext(encodingContext), + mValidationEnabled(device->IsValidationEnabled()) { + } + + ProgrammableEncoder::ProgrammableEncoder(DeviceBase* device, + EncodingContext* encodingContext, + ErrorTag errorTag) + : ApiObjectBase(device, errorTag), + mEncodingContext(encodingContext), + mValidationEnabled(device->IsValidationEnabled()) { + } + + bool ProgrammableEncoder::IsValidationEnabled() const { + return mValidationEnabled; + } + + MaybeError ProgrammableEncoder::ValidateProgrammableEncoderEnd() const { + DAWN_INVALID_IF(mDebugGroupStackSize != 0, + "PushDebugGroup called %u time(s) without a corresponding PopDebugGroup.", + mDebugGroupStackSize); + return {}; + } + + void ProgrammableEncoder::APIInsertDebugMarker(const char* groupLabel) { + mEncodingContext->TryEncode( + this, + [&](CommandAllocator* allocator) -> MaybeError { + InsertDebugMarkerCmd* cmd = + allocator->Allocate<InsertDebugMarkerCmd>(Command::InsertDebugMarker); + cmd->length = strlen(groupLabel); + + char* label = allocator->AllocateData<char>(cmd->length + 1); + memcpy(label, groupLabel, cmd->length + 1); + + return {}; + }, + "encoding %s.InsertDebugMarker(\"%s\").", this, groupLabel); + } + + void ProgrammableEncoder::APIPopDebugGroup() { + mEncodingContext->TryEncode( + this, + [&](CommandAllocator* allocator) -> MaybeError { + if (IsValidationEnabled()) { + DAWN_INVALID_IF( + mDebugGroupStackSize == 0, + "PopDebugGroup called when no debug groups are currently pushed."); + } + allocator->Allocate<PopDebugGroupCmd>(Command::PopDebugGroup); + mDebugGroupStackSize--; + mEncodingContext->PopDebugGroupLabel(); + + return {}; + }, + "encoding %s.PopDebugGroup().", this); + } + + void ProgrammableEncoder::APIPushDebugGroup(const char* groupLabel) { + mEncodingContext->TryEncode( + this, + [&](CommandAllocator* allocator) -> MaybeError { + PushDebugGroupCmd* cmd = + allocator->Allocate<PushDebugGroupCmd>(Command::PushDebugGroup); + cmd->length = strlen(groupLabel); + + char* label = allocator->AllocateData<char>(cmd->length + 1); + memcpy(label, groupLabel, cmd->length + 1); + + mDebugGroupStackSize++; + mEncodingContext->PushDebugGroupLabel(groupLabel); + + return {}; + }, + "encoding %s.PushDebugGroup(\"%s\").", this, groupLabel); + } + + MaybeError ProgrammableEncoder::ValidateSetBindGroup(BindGroupIndex index, + BindGroupBase* group, + uint32_t dynamicOffsetCountIn, + const uint32_t* dynamicOffsetsIn) const { + DAWN_TRY(GetDevice()->ValidateObject(group)); + + DAWN_INVALID_IF(index >= kMaxBindGroupsTyped, + "Bind group index (%u) exceeds the maximum (%u).", + static_cast<uint32_t>(index), kMaxBindGroups); + + ityp::span<BindingIndex, const uint32_t> dynamicOffsets(dynamicOffsetsIn, + BindingIndex(dynamicOffsetCountIn)); + + // Dynamic offsets count must match the number required by the layout perfectly. + const BindGroupLayoutBase* layout = group->GetLayout(); + DAWN_INVALID_IF( + layout->GetDynamicBufferCount() != dynamicOffsets.size(), + "The number of dynamic offsets (%u) does not match the number of dynamic buffers (%u) " + "in %s.", + static_cast<uint32_t>(dynamicOffsets.size()), + static_cast<uint32_t>(layout->GetDynamicBufferCount()), layout); + + for (BindingIndex i{0}; i < dynamicOffsets.size(); ++i) { + const BindingInfo& bindingInfo = layout->GetBindingInfo(i); + + // BGL creation sorts bindings such that the dynamic buffer bindings are first. + // ASSERT that this true. + ASSERT(bindingInfo.bindingType == BindingInfoType::Buffer); + ASSERT(bindingInfo.buffer.hasDynamicOffset); + + uint64_t requiredAlignment; + switch (bindingInfo.buffer.type) { + case wgpu::BufferBindingType::Uniform: + requiredAlignment = GetDevice()->GetLimits().v1.minUniformBufferOffsetAlignment; + break; + case wgpu::BufferBindingType::Storage: + case wgpu::BufferBindingType::ReadOnlyStorage: + case kInternalStorageBufferBinding: + requiredAlignment = GetDevice()->GetLimits().v1.minStorageBufferOffsetAlignment; + break; + case wgpu::BufferBindingType::Undefined: + UNREACHABLE(); + } + + DAWN_INVALID_IF(!IsAligned(dynamicOffsets[i], requiredAlignment), + "Dynamic Offset[%u] (%u) is not %u byte aligned.", + static_cast<uint32_t>(i), dynamicOffsets[i], requiredAlignment); + + BufferBinding bufferBinding = group->GetBindingAsBufferBinding(i); + + // During BindGroup creation, validation ensures binding offset + binding size + // <= buffer size. + ASSERT(bufferBinding.buffer->GetSize() >= bufferBinding.size); + ASSERT(bufferBinding.buffer->GetSize() - bufferBinding.size >= bufferBinding.offset); + + if ((dynamicOffsets[i] > + bufferBinding.buffer->GetSize() - bufferBinding.offset - bufferBinding.size)) { + DAWN_INVALID_IF( + (bufferBinding.buffer->GetSize() - bufferBinding.offset) == bufferBinding.size, + "Dynamic Offset[%u] (%u) is out of bounds of %s with a size of %u and a bound " + "range of (offset: %u, size: %u). The binding goes to the end of the buffer " + "even with a dynamic offset of 0. Did you forget to specify " + "the binding's size?", + static_cast<uint32_t>(i), dynamicOffsets[i], bufferBinding.buffer, + bufferBinding.buffer->GetSize(), bufferBinding.offset, bufferBinding.size); + + return DAWN_FORMAT_VALIDATION_ERROR( + "Dynamic Offset[%u] (%u) is out of bounds of " + "%s with a size of %u and a bound range of (offset: %u, size: %u).", + static_cast<uint32_t>(i), dynamicOffsets[i], bufferBinding.buffer, + bufferBinding.buffer->GetSize(), bufferBinding.offset, bufferBinding.size); + } + } + + return {}; + } + + void ProgrammableEncoder::RecordSetBindGroup(CommandAllocator* allocator, + BindGroupIndex index, + BindGroupBase* group, + uint32_t dynamicOffsetCount, + const uint32_t* dynamicOffsets) const { + SetBindGroupCmd* cmd = allocator->Allocate<SetBindGroupCmd>(Command::SetBindGroup); + cmd->index = index; + cmd->group = group; + cmd->dynamicOffsetCount = dynamicOffsetCount; + if (dynamicOffsetCount > 0) { + uint32_t* offsets = allocator->AllocateData<uint32_t>(cmd->dynamicOffsetCount); + memcpy(offsets, dynamicOffsets, dynamicOffsetCount * sizeof(uint32_t)); + } + } + +} // namespace dawn::native
diff --git a/src/dawn/native/ProgrammableEncoder.h b/src/dawn/native/ProgrammableEncoder.h new file mode 100644 index 0000000..6bba7a2 --- /dev/null +++ b/src/dawn/native/ProgrammableEncoder.h
@@ -0,0 +1,72 @@ +// Copyright 2018 The Dawn Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#ifndef DAWNNATIVE_PROGRAMMABLEENCODER_H_ +#define DAWNNATIVE_PROGRAMMABLEENCODER_H_ + +#include "dawn/native/CommandEncoder.h" +#include "dawn/native/Error.h" +#include "dawn/native/Forward.h" +#include "dawn/native/IntegerTypes.h" +#include "dawn/native/ObjectBase.h" + +#include "dawn/native/dawn_platform.h" + +namespace dawn::native { + + class DeviceBase; + + // Base class for shared functionality between programmable encoders. + class ProgrammableEncoder : public ApiObjectBase { + public: + ProgrammableEncoder(DeviceBase* device, + const char* label, + EncodingContext* encodingContext); + + void APIInsertDebugMarker(const char* groupLabel); + void APIPopDebugGroup(); + void APIPushDebugGroup(const char* groupLabel); + + protected: + bool IsValidationEnabled() const; + MaybeError ValidateProgrammableEncoderEnd() const; + + // Compute and render passes do different things on SetBindGroup. These are helper functions + // for the logic they have in common. + MaybeError ValidateSetBindGroup(BindGroupIndex index, + BindGroupBase* group, + uint32_t dynamicOffsetCountIn, + const uint32_t* dynamicOffsetsIn) const; + void RecordSetBindGroup(CommandAllocator* allocator, + BindGroupIndex index, + BindGroupBase* group, + uint32_t dynamicOffsetCount, + const uint32_t* dynamicOffsets) const; + + // Construct an "error" programmable pass encoder. + ProgrammableEncoder(DeviceBase* device, + EncodingContext* encodingContext, + ErrorTag errorTag); + + EncodingContext* mEncodingContext = nullptr; + + uint64_t mDebugGroupStackSize = 0; + + private: + const bool mValidationEnabled; + }; + +} // namespace dawn::native + +#endif // DAWNNATIVE_PROGRAMMABLEENCODER_H_
diff --git a/src/dawn/native/QueryHelper.cpp b/src/dawn/native/QueryHelper.cpp new file mode 100644 index 0000000..c6d7541 --- /dev/null +++ b/src/dawn/native/QueryHelper.cpp
@@ -0,0 +1,217 @@ +// Copyright 2020 The Dawn Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include "dawn/native/QueryHelper.h" + +#include "dawn/native/BindGroup.h" +#include "dawn/native/BindGroupLayout.h" +#include "dawn/native/Buffer.h" +#include "dawn/native/CommandEncoder.h" +#include "dawn/native/ComputePassEncoder.h" +#include "dawn/native/ComputePipeline.h" +#include "dawn/native/Device.h" +#include "dawn/native/InternalPipelineStore.h" +#include "dawn/native/utils/WGPUHelpers.h" + +#include <cmath> + +namespace dawn::native { + + namespace { + + // Assert the offsets in dawn::native::TimestampParams are same with the ones in the shader + static_assert(offsetof(dawn::native::TimestampParams, first) == 0); + static_assert(offsetof(dawn::native::TimestampParams, count) == 4); + static_assert(offsetof(dawn::native::TimestampParams, offset) == 8); + static_assert(offsetof(dawn::native::TimestampParams, multiplier) == 12); + static_assert(offsetof(dawn::native::TimestampParams, rightShift) == 16); + + static const char sConvertTimestampsToNanoseconds[] = R"( + struct Timestamp { + low : u32; + high : u32; + }; + + struct TimestampArr { + t : array<Timestamp>; + }; + + struct AvailabilityArr { + v : array<u32>; + }; + + struct TimestampParams { + first : u32; + count : u32; + offset : u32; + multiplier : u32; + right_shift : u32; + }; + + @group(0) @binding(0) var<storage, read_write> timestamps : TimestampArr; + @group(0) @binding(1) var<storage, read> availability : AvailabilityArr; + @group(0) @binding(2) var<uniform> params : TimestampParams; + + let sizeofTimestamp : u32 = 8u; + + @stage(compute) @workgroup_size(8, 1, 1) + fn main(@builtin(global_invocation_id) GlobalInvocationID : vec3<u32>) { + if (GlobalInvocationID.x >= params.count) { return; } + + var index = GlobalInvocationID.x + params.offset / sizeofTimestamp; + + // Return 0 for the unavailable value. + if (availability.v[GlobalInvocationID.x + params.first] == 0u) { + timestamps.t[index].low = 0u; + timestamps.t[index].high = 0u; + return; + } + + var timestamp = timestamps.t[index]; + + // TODO(dawn:1250): Consider using the umulExtended and uaddCarry intrinsics once + // available. + var chunks : array<u32, 5>; + chunks[0] = timestamp.low & 0xFFFFu; + chunks[1] = timestamp.low >> 16u; + chunks[2] = timestamp.high & 0xFFFFu; + chunks[3] = timestamp.high >> 16u; + chunks[4] = 0u; + + // Multiply all the chunks with the integer period. + for (var i = 0u; i < 4u; i = i + 1u) { + chunks[i] = chunks[i] * params.multiplier; + } + + // Propagate the carry + var carry = 0u; + for (var i = 0u; i < 4u; i = i + 1u) { + var chunk_with_carry = chunks[i] + carry; + carry = chunk_with_carry >> 16u; + chunks[i] = chunk_with_carry & 0xFFFFu; + } + chunks[4] = carry; + + // Apply the right shift. + for (var i = 0u; i < 4u; i = i + 1u) { + var low = chunks[i] >> params.right_shift; + var high = (chunks[i + 1u] << (16u - params.right_shift)) & 0xFFFFu; + chunks[i] = low | high; + } + + timestamps.t[index].low = chunks[0] | (chunks[1] << 16u); + timestamps.t[index].high = chunks[2] | (chunks[3] << 16u); + } + )"; + + ResultOrError<ComputePipelineBase*> GetOrCreateTimestampComputePipeline( + DeviceBase* device) { + InternalPipelineStore* store = device->GetInternalPipelineStore(); + + if (store->timestampComputePipeline == nullptr) { + // Create compute shader module if not cached before. + if (store->timestampCS == nullptr) { + DAWN_TRY_ASSIGN( + store->timestampCS, + utils::CreateShaderModule(device, sConvertTimestampsToNanoseconds)); + } + + // Create binding group layout + Ref<BindGroupLayoutBase> bgl; + DAWN_TRY_ASSIGN( + bgl, utils::MakeBindGroupLayout( + device, + { + {0, wgpu::ShaderStage::Compute, kInternalStorageBufferBinding}, + {1, wgpu::ShaderStage::Compute, + wgpu::BufferBindingType::ReadOnlyStorage}, + {2, wgpu::ShaderStage::Compute, wgpu::BufferBindingType::Uniform}, + }, + /* allowInternalBinding */ true)); + + // Create pipeline layout + Ref<PipelineLayoutBase> layout; + DAWN_TRY_ASSIGN(layout, utils::MakeBasicPipelineLayout(device, bgl)); + + // Create ComputePipeline. + ComputePipelineDescriptor computePipelineDesc = {}; + // Generate the layout based on shader module. + computePipelineDesc.layout = layout.Get(); + computePipelineDesc.compute.module = store->timestampCS.Get(); + computePipelineDesc.compute.entryPoint = "main"; + + DAWN_TRY_ASSIGN(store->timestampComputePipeline, + device->CreateComputePipeline(&computePipelineDesc)); + } + + return store->timestampComputePipeline.Get(); + } + + } // anonymous namespace + + TimestampParams::TimestampParams(uint32_t first, uint32_t count, uint32_t offset, float period) + : first(first), count(count), offset(offset) { + // The overall conversion happening, if p is the period, m the multiplier, s the shift, is:: + // + // m = round(p * 2^s) + // + // Then in the shader we compute: + // + // m / 2^s = round(p * 2^s) / 2*s ~= p + // + // The goal is to find the best shift to keep the precision of computations. The + // conversion shader uses chunks of 16 bits to compute the multiplication with the perios, + // so we need to keep the multiplier under 2^16. At the same time, the larger the + // multiplier, the better the precision, so we maximize the value of the right shift while + // keeping the multiplier under 2 ^ 16 + uint32_t upperLog2 = ceil(log2(period)); + + // Clamp the shift to 16 because we're doing computations in 16bit chunks. The + // multiplication by the period will overflow the chunks, but timestamps are mostly + // informational so that's ok. + rightShift = 16u - std::min(upperLog2, 16u); + multiplier = uint32_t(period * (1 << rightShift)); + } + + MaybeError EncodeConvertTimestampsToNanoseconds(CommandEncoder* encoder, + BufferBase* timestamps, + BufferBase* availability, + BufferBase* params) { + DeviceBase* device = encoder->GetDevice(); + + ComputePipelineBase* pipeline; + DAWN_TRY_ASSIGN(pipeline, GetOrCreateTimestampComputePipeline(device)); + + // Prepare bind group layout. + Ref<BindGroupLayoutBase> layout; + DAWN_TRY_ASSIGN(layout, pipeline->GetBindGroupLayout(0)); + + // Create bind group after all binding entries are set. + Ref<BindGroupBase> bindGroup; + DAWN_TRY_ASSIGN(bindGroup, + utils::MakeBindGroup(device, layout, + {{0, timestamps}, {1, availability}, {2, params}})); + + // Create compute encoder and issue dispatch. + Ref<ComputePassEncoder> pass = encoder->BeginComputePass(); + pass->APISetPipeline(pipeline); + pass->APISetBindGroup(0, bindGroup.Get()); + pass->APIDispatch( + static_cast<uint32_t>((timestamps->GetSize() / sizeof(uint64_t) + 7) / 8)); + pass->APIEnd(); + + return {}; + } + +} // namespace dawn::native
diff --git a/src/dawn/native/QueryHelper.h b/src/dawn/native/QueryHelper.h new file mode 100644 index 0000000..111b195 --- /dev/null +++ b/src/dawn/native/QueryHelper.h
@@ -0,0 +1,43 @@ +// Copyright 2020 The Dawn Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#ifndef DAWNNATIVE_QUERYHELPER_H_ +#define DAWNNATIVE_QUERYHELPER_H_ + +#include "dawn/native/Error.h" +#include "dawn/native/ObjectBase.h" + +namespace dawn::native { + + class BufferBase; + class CommandEncoder; + + struct TimestampParams { + TimestampParams(uint32_t first, uint32_t count, uint32_t offset, float period); + + uint32_t first; + uint32_t count; + uint32_t offset; + uint32_t multiplier; + uint32_t rightShift; + }; + + MaybeError EncodeConvertTimestampsToNanoseconds(CommandEncoder* encoder, + BufferBase* timestamps, + BufferBase* availability, + BufferBase* params); + +} // namespace dawn::native + +#endif // DAWNNATIVE_QUERYHELPER_H_
diff --git a/src/dawn/native/QuerySet.cpp b/src/dawn/native/QuerySet.cpp new file mode 100644 index 0000000..3f20dab --- /dev/null +++ b/src/dawn/native/QuerySet.cpp
@@ -0,0 +1,180 @@ +// Copyright 2020 The Dawn Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include "dawn/native/QuerySet.h" + +#include "dawn/native/Device.h" +#include "dawn/native/Features.h" +#include "dawn/native/ObjectType_autogen.h" +#include "dawn/native/ValidationUtils_autogen.h" + +#include <set> + +namespace dawn::native { + + namespace { + + class ErrorQuerySet final : public QuerySetBase { + public: + ErrorQuerySet(DeviceBase* device) : QuerySetBase(device, ObjectBase::kError) { + } + + private: + void DestroyImpl() override { + UNREACHABLE(); + } + }; + + } // anonymous namespace + + MaybeError ValidateQuerySetDescriptor(DeviceBase* device, + const QuerySetDescriptor* descriptor) { + DAWN_INVALID_IF(descriptor->nextInChain != nullptr, "nextInChain must be nullptr"); + + DAWN_TRY(ValidateQueryType(descriptor->type)); + + DAWN_INVALID_IF(descriptor->count > kMaxQueryCount, + "Query count (%u) exceeds the maximum query count (%u).", descriptor->count, + kMaxQueryCount); + + switch (descriptor->type) { + case wgpu::QueryType::Occlusion: + DAWN_INVALID_IF(descriptor->pipelineStatisticsCount != 0, + "Pipeline statistics specified for a query of type %s.", + descriptor->type); + break; + + case wgpu::QueryType::PipelineStatistics: { + // TODO(crbug.com/1177506): Pipeline statistics query is not fully implemented. + // Disallow it as unsafe until the implementaion is completed. + DAWN_INVALID_IF(device->IsToggleEnabled(Toggle::DisallowUnsafeAPIs), + "Pipeline statistics queries are disallowed because they are not " + "fully implemented"); + + DAWN_INVALID_IF( + !device->IsFeatureEnabled(Feature::PipelineStatisticsQuery), + "Pipeline statistics query set created without the feature being enabled."); + + DAWN_INVALID_IF(descriptor->pipelineStatisticsCount == 0, + "Pipeline statistics query set created with 0 statistics."); + + std::set<wgpu::PipelineStatisticName> pipelineStatisticsSet; + for (uint32_t i = 0; i < descriptor->pipelineStatisticsCount; i++) { + DAWN_TRY(ValidatePipelineStatisticName(descriptor->pipelineStatistics[i])); + + auto [_, inserted] = + pipelineStatisticsSet.insert((descriptor->pipelineStatistics[i])); + DAWN_INVALID_IF(!inserted, "Statistic %s is specified more than once.", + descriptor->pipelineStatistics[i]); + } + } break; + + case wgpu::QueryType::Timestamp: + DAWN_INVALID_IF(device->IsToggleEnabled(Toggle::DisallowUnsafeAPIs), + "Timestamp queries are disallowed because they may expose precise " + "timing information."); + + DAWN_INVALID_IF(!device->IsFeatureEnabled(Feature::TimestampQuery), + "Timestamp query set created without the feature being enabled."); + + DAWN_INVALID_IF(descriptor->pipelineStatisticsCount != 0, + "Pipeline statistics specified for a query of type %s.", + descriptor->type); + break; + + default: + break; + } + + return {}; + } + + QuerySetBase::QuerySetBase(DeviceBase* device, const QuerySetDescriptor* descriptor) + : ApiObjectBase(device, descriptor->label), + mQueryType(descriptor->type), + mQueryCount(descriptor->count), + mState(QuerySetState::Available) { + for (uint32_t i = 0; i < descriptor->pipelineStatisticsCount; i++) { + mPipelineStatistics.push_back(descriptor->pipelineStatistics[i]); + } + + mQueryAvailability.resize(descriptor->count); + TrackInDevice(); + } + + QuerySetBase::QuerySetBase(DeviceBase* device) : ApiObjectBase(device, kLabelNotImplemented) { + TrackInDevice(); + } + + QuerySetBase::QuerySetBase(DeviceBase* device, ObjectBase::ErrorTag tag) + : ApiObjectBase(device, tag) { + } + + QuerySetBase::~QuerySetBase() { + // Uninitialized or already destroyed + ASSERT(mState == QuerySetState::Unavailable || mState == QuerySetState::Destroyed); + } + + void QuerySetBase::DestroyImpl() { + mState = QuerySetState::Destroyed; + } + + // static + QuerySetBase* QuerySetBase::MakeError(DeviceBase* device) { + return new ErrorQuerySet(device); + } + + ObjectType QuerySetBase::GetType() const { + return ObjectType::QuerySet; + } + + wgpu::QueryType QuerySetBase::GetQueryType() const { + return mQueryType; + } + + uint32_t QuerySetBase::GetQueryCount() const { + return mQueryCount; + } + + const std::vector<wgpu::PipelineStatisticName>& QuerySetBase::GetPipelineStatistics() const { + return mPipelineStatistics; + } + + const std::vector<bool>& QuerySetBase::GetQueryAvailability() const { + return mQueryAvailability; + } + + void QuerySetBase::SetQueryAvailability(uint32_t index, bool available) { + mQueryAvailability[index] = available; + } + + MaybeError QuerySetBase::ValidateCanUseInSubmitNow() const { + ASSERT(!IsError()); + DAWN_INVALID_IF(mState == QuerySetState::Destroyed, "%s used while destroyed.", this); + return {}; + } + + void QuerySetBase::APIDestroy() { + if (GetDevice()->ConsumedError(ValidateDestroy())) { + return; + } + Destroy(); + } + + MaybeError QuerySetBase::ValidateDestroy() const { + DAWN_TRY(GetDevice()->ValidateObject(this)); + return {}; + } + +} // namespace dawn::native
diff --git a/src/dawn/native/QuerySet.h b/src/dawn/native/QuerySet.h new file mode 100644 index 0000000..39a69df --- /dev/null +++ b/src/dawn/native/QuerySet.h
@@ -0,0 +1,72 @@ +// Copyright 2020 The Dawn Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#ifndef DAWNNATIVE_QUERYSET_H_ +#define DAWNNATIVE_QUERYSET_H_ + +#include "dawn/native/Error.h" +#include "dawn/native/Forward.h" +#include "dawn/native/ObjectBase.h" + +#include "dawn/native/dawn_platform.h" + +namespace dawn::native { + + MaybeError ValidateQuerySetDescriptor(DeviceBase* device, const QuerySetDescriptor* descriptor); + + class QuerySetBase : public ApiObjectBase { + public: + QuerySetBase(DeviceBase* device, const QuerySetDescriptor* descriptor); + + static QuerySetBase* MakeError(DeviceBase* device); + + ObjectType GetType() const override; + + wgpu::QueryType GetQueryType() const; + uint32_t GetQueryCount() const; + const std::vector<wgpu::PipelineStatisticName>& GetPipelineStatistics() const; + + const std::vector<bool>& GetQueryAvailability() const; + void SetQueryAvailability(uint32_t index, bool available); + + MaybeError ValidateCanUseInSubmitNow() const; + + void APIDestroy(); + + protected: + QuerySetBase(DeviceBase* device, ObjectBase::ErrorTag tag); + + // Constructor used only for mocking and testing. + QuerySetBase(DeviceBase* device); + void DestroyImpl() override; + + ~QuerySetBase() override; + + private: + MaybeError ValidateDestroy() const; + + wgpu::QueryType mQueryType; + uint32_t mQueryCount; + std::vector<wgpu::PipelineStatisticName> mPipelineStatistics; + + enum class QuerySetState { Unavailable, Available, Destroyed }; + QuerySetState mState = QuerySetState::Unavailable; + + // Indicates the available queries on the query set for resolving + std::vector<bool> mQueryAvailability; + }; + +} // namespace dawn::native + +#endif // DAWNNATIVE_QUERYSET_H_
diff --git a/src/dawn/native/Queue.cpp b/src/dawn/native/Queue.cpp new file mode 100644 index 0000000..6c061d3 --- /dev/null +++ b/src/dawn/native/Queue.cpp
@@ -0,0 +1,512 @@ +// Copyright 2017 The Dawn Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include "dawn/native/Queue.h" + +#include "dawn/common/Constants.h" +#include "dawn/native/Buffer.h" +#include "dawn/native/CommandBuffer.h" +#include "dawn/native/CommandEncoder.h" +#include "dawn/native/CommandValidation.h" +#include "dawn/native/Commands.h" +#include "dawn/native/CopyTextureForBrowserHelper.h" +#include "dawn/native/Device.h" +#include "dawn/native/DynamicUploader.h" +#include "dawn/native/ExternalTexture.h" +#include "dawn/native/ObjectType_autogen.h" +#include "dawn/native/QuerySet.h" +#include "dawn/native/RenderPassEncoder.h" +#include "dawn/native/RenderPipeline.h" +#include "dawn/native/Texture.h" +#include "dawn/platform/DawnPlatform.h" +#include "dawn/platform/tracing/TraceEvent.h" + +#include <cstring> + +namespace dawn::native { + + namespace { + + void CopyTextureData(uint8_t* dstPointer, + const uint8_t* srcPointer, + uint32_t depth, + uint32_t rowsPerImage, + uint64_t imageAdditionalStride, + uint32_t actualBytesPerRow, + uint32_t dstBytesPerRow, + uint32_t srcBytesPerRow) { + bool copyWholeLayer = + actualBytesPerRow == dstBytesPerRow && dstBytesPerRow == srcBytesPerRow; + bool copyWholeData = copyWholeLayer && imageAdditionalStride == 0; + + if (!copyWholeLayer) { // copy row by row + for (uint32_t d = 0; d < depth; ++d) { + for (uint32_t h = 0; h < rowsPerImage; ++h) { + memcpy(dstPointer, srcPointer, actualBytesPerRow); + dstPointer += dstBytesPerRow; + srcPointer += srcBytesPerRow; + } + srcPointer += imageAdditionalStride; + } + } else { + uint64_t layerSize = uint64_t(rowsPerImage) * actualBytesPerRow; + if (!copyWholeData) { // copy layer by layer + for (uint32_t d = 0; d < depth; ++d) { + memcpy(dstPointer, srcPointer, layerSize); + dstPointer += layerSize; + srcPointer += layerSize + imageAdditionalStride; + } + } else { // do a single copy + memcpy(dstPointer, srcPointer, layerSize * depth); + } + } + } + + ResultOrError<UploadHandle> UploadTextureDataAligningBytesPerRowAndOffset( + DeviceBase* device, + const void* data, + uint32_t alignedBytesPerRow, + uint32_t optimallyAlignedBytesPerRow, + uint32_t alignedRowsPerImage, + const TextureDataLayout& dataLayout, + bool hasDepthOrStencil, + const TexelBlockInfo& blockInfo, + const Extent3D& writeSizePixel) { + uint64_t newDataSizeBytes; + DAWN_TRY_ASSIGN( + newDataSizeBytes, + ComputeRequiredBytesInCopy(blockInfo, writeSizePixel, optimallyAlignedBytesPerRow, + alignedRowsPerImage)); + + uint64_t optimalOffsetAlignment = + device->GetOptimalBufferToTextureCopyOffsetAlignment(); + ASSERT(IsPowerOfTwo(optimalOffsetAlignment)); + ASSERT(IsPowerOfTwo(blockInfo.byteSize)); + // We need the offset to be aligned to both optimalOffsetAlignment and blockByteSize, + // since both of them are powers of two, we only need to align to the max value. + uint64_t offsetAlignment = + std::max(optimalOffsetAlignment, uint64_t(blockInfo.byteSize)); + + // For depth-stencil texture, buffer offset must be a multiple of 4, which is required + // by WebGPU and Vulkan SPEC. + if (hasDepthOrStencil) { + constexpr uint64_t kOffsetAlignmentForDepthStencil = 4; + offsetAlignment = std::max(offsetAlignment, kOffsetAlignmentForDepthStencil); + } + + UploadHandle uploadHandle; + DAWN_TRY_ASSIGN(uploadHandle, device->GetDynamicUploader()->Allocate( + newDataSizeBytes, device->GetPendingCommandSerial(), + offsetAlignment)); + ASSERT(uploadHandle.mappedBuffer != nullptr); + + uint8_t* dstPointer = static_cast<uint8_t*>(uploadHandle.mappedBuffer); + const uint8_t* srcPointer = static_cast<const uint8_t*>(data); + srcPointer += dataLayout.offset; + + uint32_t dataRowsPerImage = dataLayout.rowsPerImage; + if (dataRowsPerImage == 0) { + dataRowsPerImage = writeSizePixel.height / blockInfo.height; + } + + ASSERT(dataRowsPerImage >= alignedRowsPerImage); + uint64_t imageAdditionalStride = + dataLayout.bytesPerRow * (dataRowsPerImage - alignedRowsPerImage); + + CopyTextureData(dstPointer, srcPointer, writeSizePixel.depthOrArrayLayers, + alignedRowsPerImage, imageAdditionalStride, alignedBytesPerRow, + optimallyAlignedBytesPerRow, dataLayout.bytesPerRow); + + return uploadHandle; + } + + struct SubmittedWorkDone : QueueBase::TaskInFlight { + SubmittedWorkDone(WGPUQueueWorkDoneCallback callback, void* userdata) + : mCallback(callback), mUserdata(userdata) { + } + void Finish() override { + ASSERT(mCallback != nullptr); + mCallback(WGPUQueueWorkDoneStatus_Success, mUserdata); + mCallback = nullptr; + } + void HandleDeviceLoss() override { + ASSERT(mCallback != nullptr); + mCallback(WGPUQueueWorkDoneStatus_DeviceLost, mUserdata); + mCallback = nullptr; + } + ~SubmittedWorkDone() override = default; + + private: + WGPUQueueWorkDoneCallback mCallback = nullptr; + void* mUserdata; + }; + + class ErrorQueue : public QueueBase { + public: + ErrorQueue(DeviceBase* device) : QueueBase(device, ObjectBase::kError) { + } + + private: + MaybeError SubmitImpl(uint32_t commandCount, + CommandBufferBase* const* commands) override { + UNREACHABLE(); + } + }; + } // namespace + + // QueueBase + + QueueBase::TaskInFlight::~TaskInFlight() { + } + + QueueBase::QueueBase(DeviceBase* device) : ApiObjectBase(device, kLabelNotImplemented) { + } + + QueueBase::QueueBase(DeviceBase* device, ObjectBase::ErrorTag tag) + : ApiObjectBase(device, tag) { + } + + QueueBase::~QueueBase() { + ASSERT(mTasksInFlight.Empty()); + } + + void QueueBase::DestroyImpl() { + } + + // static + QueueBase* QueueBase::MakeError(DeviceBase* device) { + return new ErrorQueue(device); + } + + ObjectType QueueBase::GetType() const { + return ObjectType::Queue; + } + + void QueueBase::APISubmit(uint32_t commandCount, CommandBufferBase* const* commands) { + SubmitInternal(commandCount, commands); + + for (uint32_t i = 0; i < commandCount; ++i) { + commands[i]->Destroy(); + } + } + + void QueueBase::APIOnSubmittedWorkDone(uint64_t signalValue, + WGPUQueueWorkDoneCallback callback, + void* userdata) { + // The error status depends on the type of error so we let the validation function choose it + WGPUQueueWorkDoneStatus status; + if (GetDevice()->ConsumedError(ValidateOnSubmittedWorkDone(signalValue, &status))) { + callback(status, userdata); + return; + } + + std::unique_ptr<SubmittedWorkDone> task = + std::make_unique<SubmittedWorkDone>(callback, userdata); + + // Technically we only need to wait for previously submitted work but OnSubmittedWorkDone is + // also used to make sure ALL queue work is finished in tests, so we also wait for pending + // commands (this is non-observable outside of tests so it's ok to do deviate a bit from the + // spec). + TrackTask(std::move(task), GetDevice()->GetPendingCommandSerial()); + } + + void QueueBase::TrackTask(std::unique_ptr<TaskInFlight> task, ExecutionSerial serial) { + mTasksInFlight.Enqueue(std::move(task), serial); + GetDevice()->AddFutureSerial(serial); + } + + void QueueBase::Tick(ExecutionSerial finishedSerial) { + // If a user calls Queue::Submit inside a task, for example in a Buffer::MapAsync callback, + // then the device will be ticked, which in turns ticks the queue, causing reentrance here. + // To prevent the reentrant call from invalidating mTasksInFlight while in use by the first + // call, we remove the tasks to finish from the queue, update mTasksInFlight, then run the + // callbacks. + std::vector<std::unique_ptr<TaskInFlight>> tasks; + for (auto& task : mTasksInFlight.IterateUpTo(finishedSerial)) { + tasks.push_back(std::move(task)); + } + mTasksInFlight.ClearUpTo(finishedSerial); + + for (auto& task : tasks) { + task->Finish(); + } + } + + void QueueBase::HandleDeviceLoss() { + for (auto& task : mTasksInFlight.IterateAll()) { + task->HandleDeviceLoss(); + } + mTasksInFlight.Clear(); + } + + void QueueBase::APIWriteBuffer(BufferBase* buffer, + uint64_t bufferOffset, + const void* data, + size_t size) { + GetDevice()->ConsumedError(WriteBuffer(buffer, bufferOffset, data, size)); + } + + MaybeError QueueBase::WriteBuffer(BufferBase* buffer, + uint64_t bufferOffset, + const void* data, + size_t size) { + DAWN_TRY(GetDevice()->ValidateIsAlive()); + DAWN_TRY(GetDevice()->ValidateObject(this)); + DAWN_TRY(ValidateWriteBuffer(GetDevice(), buffer, bufferOffset, size)); + DAWN_TRY(buffer->ValidateCanUseOnQueueNow()); + return WriteBufferImpl(buffer, bufferOffset, data, size); + } + + MaybeError QueueBase::WriteBufferImpl(BufferBase* buffer, + uint64_t bufferOffset, + const void* data, + size_t size) { + if (size == 0) { + return {}; + } + + DeviceBase* device = GetDevice(); + + UploadHandle uploadHandle; + DAWN_TRY_ASSIGN(uploadHandle, device->GetDynamicUploader()->Allocate( + size, device->GetPendingCommandSerial(), + kCopyBufferToBufferOffsetAlignment)); + ASSERT(uploadHandle.mappedBuffer != nullptr); + + memcpy(uploadHandle.mappedBuffer, data, size); + + device->AddFutureSerial(device->GetPendingCommandSerial()); + + return device->CopyFromStagingToBuffer(uploadHandle.stagingBuffer, uploadHandle.startOffset, + buffer, bufferOffset, size); + } + + void QueueBase::APIWriteTexture(const ImageCopyTexture* destination, + const void* data, + size_t dataSize, + const TextureDataLayout* dataLayout, + const Extent3D* writeSize) { + GetDevice()->ConsumedError( + WriteTextureInternal(destination, data, dataSize, *dataLayout, writeSize)); + } + + MaybeError QueueBase::WriteTextureInternal(const ImageCopyTexture* destination, + const void* data, + size_t dataSize, + const TextureDataLayout& dataLayout, + const Extent3D* writeSize) { + DAWN_TRY(ValidateWriteTexture(destination, dataSize, dataLayout, writeSize)); + + if (writeSize->width == 0 || writeSize->height == 0 || writeSize->depthOrArrayLayers == 0) { + return {}; + } + + const TexelBlockInfo& blockInfo = + destination->texture->GetFormat().GetAspectInfo(destination->aspect).block; + TextureDataLayout layout = dataLayout; + ApplyDefaultTextureDataLayoutOptions(&layout, blockInfo, *writeSize); + return WriteTextureImpl(*destination, data, layout, *writeSize); + } + + MaybeError QueueBase::WriteTextureImpl(const ImageCopyTexture& destination, + const void* data, + const TextureDataLayout& dataLayout, + const Extent3D& writeSizePixel) { + const Format& format = destination.texture->GetFormat(); + const TexelBlockInfo& blockInfo = format.GetAspectInfo(destination.aspect).block; + + // We are only copying the part of the data that will appear in the texture. + // Note that validating texture copy range ensures that writeSizePixel->width and + // writeSizePixel->height are multiples of blockWidth and blockHeight respectively. + ASSERT(writeSizePixel.width % blockInfo.width == 0); + ASSERT(writeSizePixel.height % blockInfo.height == 0); + uint32_t alignedBytesPerRow = writeSizePixel.width / blockInfo.width * blockInfo.byteSize; + uint32_t alignedRowsPerImage = writeSizePixel.height / blockInfo.height; + + uint32_t optimalBytesPerRowAlignment = GetDevice()->GetOptimalBytesPerRowAlignment(); + uint32_t optimallyAlignedBytesPerRow = + Align(alignedBytesPerRow, optimalBytesPerRowAlignment); + + UploadHandle uploadHandle; + DAWN_TRY_ASSIGN(uploadHandle, + UploadTextureDataAligningBytesPerRowAndOffset( + GetDevice(), data, alignedBytesPerRow, optimallyAlignedBytesPerRow, + alignedRowsPerImage, dataLayout, format.HasDepthOrStencil(), blockInfo, + writeSizePixel)); + + TextureDataLayout passDataLayout = dataLayout; + passDataLayout.offset = uploadHandle.startOffset; + passDataLayout.bytesPerRow = optimallyAlignedBytesPerRow; + passDataLayout.rowsPerImage = alignedRowsPerImage; + + TextureCopy textureCopy; + textureCopy.texture = destination.texture; + textureCopy.mipLevel = destination.mipLevel; + textureCopy.origin = destination.origin; + textureCopy.aspect = ConvertAspect(format, destination.aspect); + + DeviceBase* device = GetDevice(); + + device->AddFutureSerial(device->GetPendingCommandSerial()); + + return device->CopyFromStagingToTexture(uploadHandle.stagingBuffer, passDataLayout, + &textureCopy, writeSizePixel); + } + + void QueueBase::APICopyTextureForBrowser(const ImageCopyTexture* source, + const ImageCopyTexture* destination, + const Extent3D* copySize, + const CopyTextureForBrowserOptions* options) { + GetDevice()->ConsumedError( + CopyTextureForBrowserInternal(source, destination, copySize, options)); + } + + MaybeError QueueBase::CopyTextureForBrowserInternal( + const ImageCopyTexture* source, + const ImageCopyTexture* destination, + const Extent3D* copySize, + const CopyTextureForBrowserOptions* options) { + if (GetDevice()->IsValidationEnabled()) { + DAWN_TRY_CONTEXT( + ValidateCopyTextureForBrowser(GetDevice(), source, destination, copySize, options), + "validating CopyTextureForBrowser from %s to %s", source->texture, + destination->texture); + } + + return DoCopyTextureForBrowser(GetDevice(), source, destination, copySize, options); + } + + MaybeError QueueBase::ValidateSubmit(uint32_t commandCount, + CommandBufferBase* const* commands) const { + TRACE_EVENT0(GetDevice()->GetPlatform(), Validation, "Queue::ValidateSubmit"); + DAWN_TRY(GetDevice()->ValidateObject(this)); + + for (uint32_t i = 0; i < commandCount; ++i) { + DAWN_TRY(GetDevice()->ValidateObject(commands[i])); + DAWN_TRY(commands[i]->ValidateCanUseInSubmitNow()); + + const CommandBufferResourceUsage& usages = commands[i]->GetResourceUsages(); + + for (const SyncScopeResourceUsage& scope : usages.renderPasses) { + for (const BufferBase* buffer : scope.buffers) { + DAWN_TRY(buffer->ValidateCanUseOnQueueNow()); + } + + for (const TextureBase* texture : scope.textures) { + DAWN_TRY(texture->ValidateCanUseInSubmitNow()); + } + + for (const ExternalTextureBase* externalTexture : scope.externalTextures) { + DAWN_TRY(externalTexture->ValidateCanUseInSubmitNow()); + } + } + + for (const ComputePassResourceUsage& pass : usages.computePasses) { + for (const BufferBase* buffer : pass.referencedBuffers) { + DAWN_TRY(buffer->ValidateCanUseOnQueueNow()); + } + for (const TextureBase* texture : pass.referencedTextures) { + DAWN_TRY(texture->ValidateCanUseInSubmitNow()); + } + for (const ExternalTextureBase* externalTexture : pass.referencedExternalTextures) { + DAWN_TRY(externalTexture->ValidateCanUseInSubmitNow()); + } + } + + for (const BufferBase* buffer : usages.topLevelBuffers) { + DAWN_TRY(buffer->ValidateCanUseOnQueueNow()); + } + for (const TextureBase* texture : usages.topLevelTextures) { + DAWN_TRY(texture->ValidateCanUseInSubmitNow()); + } + for (const QuerySetBase* querySet : usages.usedQuerySets) { + DAWN_TRY(querySet->ValidateCanUseInSubmitNow()); + } + } + + return {}; + } + + MaybeError QueueBase::ValidateOnSubmittedWorkDone(uint64_t signalValue, + WGPUQueueWorkDoneStatus* status) const { + *status = WGPUQueueWorkDoneStatus_DeviceLost; + DAWN_TRY(GetDevice()->ValidateIsAlive()); + + *status = WGPUQueueWorkDoneStatus_Error; + DAWN_TRY(GetDevice()->ValidateObject(this)); + + DAWN_INVALID_IF(signalValue != 0, "SignalValue (%u) is not 0.", signalValue); + + return {}; + } + + MaybeError QueueBase::ValidateWriteTexture(const ImageCopyTexture* destination, + size_t dataSize, + const TextureDataLayout& dataLayout, + const Extent3D* writeSize) const { + DAWN_TRY(GetDevice()->ValidateIsAlive()); + DAWN_TRY(GetDevice()->ValidateObject(this)); + DAWN_TRY(GetDevice()->ValidateObject(destination->texture)); + + DAWN_TRY(ValidateImageCopyTexture(GetDevice(), *destination, *writeSize)); + + DAWN_INVALID_IF(dataLayout.offset > dataSize, + "Data offset (%u) is greater than the data size (%u).", dataLayout.offset, + dataSize); + + DAWN_INVALID_IF(!(destination->texture->GetUsage() & wgpu::TextureUsage::CopyDst), + "Usage (%s) of %s does not include %s.", destination->texture->GetUsage(), + destination->texture, wgpu::TextureUsage::CopyDst); + + DAWN_INVALID_IF(destination->texture->GetSampleCount() > 1, + "Sample count (%u) of %s is not 1", destination->texture->GetSampleCount(), + destination->texture); + + DAWN_TRY(ValidateLinearToDepthStencilCopyRestrictions(*destination)); + // We validate texture copy range before validating linear texture data, + // because in the latter we divide copyExtent.width by blockWidth and + // copyExtent.height by blockHeight while the divisibility conditions are + // checked in validating texture copy range. + DAWN_TRY(ValidateTextureCopyRange(GetDevice(), *destination, *writeSize)); + + const TexelBlockInfo& blockInfo = + destination->texture->GetFormat().GetAspectInfo(destination->aspect).block; + + DAWN_TRY(ValidateLinearTextureData(dataLayout, dataSize, blockInfo, *writeSize)); + + DAWN_TRY(destination->texture->ValidateCanUseInSubmitNow()); + + return {}; + } + + void QueueBase::SubmitInternal(uint32_t commandCount, CommandBufferBase* const* commands) { + DeviceBase* device = GetDevice(); + if (device->ConsumedError(device->ValidateIsAlive())) { + // If device is lost, don't let any commands be submitted + return; + } + + TRACE_EVENT0(device->GetPlatform(), General, "Queue::Submit"); + if (device->IsValidationEnabled() && + device->ConsumedError(ValidateSubmit(commandCount, commands))) { + return; + } + ASSERT(!IsError()); + + if (device->ConsumedError(SubmitImpl(commandCount, commands))) { + return; + } + } + +} // namespace dawn::native
diff --git a/src/dawn/native/Queue.h b/src/dawn/native/Queue.h new file mode 100644 index 0000000..ee1074d --- /dev/null +++ b/src/dawn/native/Queue.h
@@ -0,0 +1,111 @@ +// Copyright 2017 The Dawn Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#ifndef DAWNNATIVE_QUEUE_H_ +#define DAWNNATIVE_QUEUE_H_ + +#include "dawn/common/SerialQueue.h" +#include "dawn/native/Error.h" +#include "dawn/native/Forward.h" +#include "dawn/native/IntegerTypes.h" +#include "dawn/native/ObjectBase.h" + +#include "dawn/native/dawn_platform.h" + +namespace dawn::native { + + class QueueBase : public ApiObjectBase { + public: + struct TaskInFlight { + virtual ~TaskInFlight(); + virtual void Finish() = 0; + virtual void HandleDeviceLoss() = 0; + }; + + ~QueueBase() override; + + static QueueBase* MakeError(DeviceBase* device); + + ObjectType GetType() const override; + + // Dawn API + void APISubmit(uint32_t commandCount, CommandBufferBase* const* commands); + void APIOnSubmittedWorkDone(uint64_t signalValue, + WGPUQueueWorkDoneCallback callback, + void* userdata); + void APIWriteBuffer(BufferBase* buffer, + uint64_t bufferOffset, + const void* data, + size_t size); + void APIWriteTexture(const ImageCopyTexture* destination, + const void* data, + size_t dataSize, + const TextureDataLayout* dataLayout, + const Extent3D* writeSize); + void APICopyTextureForBrowser(const ImageCopyTexture* source, + const ImageCopyTexture* destination, + const Extent3D* copySize, + const CopyTextureForBrowserOptions* options); + + MaybeError WriteBuffer(BufferBase* buffer, + uint64_t bufferOffset, + const void* data, + size_t size); + void TrackTask(std::unique_ptr<TaskInFlight> task, ExecutionSerial serial); + void Tick(ExecutionSerial finishedSerial); + void HandleDeviceLoss(); + + protected: + QueueBase(DeviceBase* device); + QueueBase(DeviceBase* device, ObjectBase::ErrorTag tag); + void DestroyImpl() override; + + private: + MaybeError WriteTextureInternal(const ImageCopyTexture* destination, + const void* data, + size_t dataSize, + const TextureDataLayout& dataLayout, + const Extent3D* writeSize); + MaybeError CopyTextureForBrowserInternal(const ImageCopyTexture* source, + const ImageCopyTexture* destination, + const Extent3D* copySize, + const CopyTextureForBrowserOptions* options); + + virtual MaybeError SubmitImpl(uint32_t commandCount, + CommandBufferBase* const* commands) = 0; + virtual MaybeError WriteBufferImpl(BufferBase* buffer, + uint64_t bufferOffset, + const void* data, + size_t size); + virtual MaybeError WriteTextureImpl(const ImageCopyTexture& destination, + const void* data, + const TextureDataLayout& dataLayout, + const Extent3D& writeSize); + + MaybeError ValidateSubmit(uint32_t commandCount, CommandBufferBase* const* commands) const; + MaybeError ValidateOnSubmittedWorkDone(uint64_t signalValue, + WGPUQueueWorkDoneStatus* status) const; + MaybeError ValidateWriteTexture(const ImageCopyTexture* destination, + size_t dataSize, + const TextureDataLayout& dataLayout, + const Extent3D* writeSize) const; + + void SubmitInternal(uint32_t commandCount, CommandBufferBase* const* commands); + + SerialQueue<ExecutionSerial, std::unique_ptr<TaskInFlight>> mTasksInFlight; + }; + +} // namespace dawn::native + +#endif // DAWNNATIVE_QUEUE_H_
diff --git a/src/dawn/native/RenderBundle.cpp b/src/dawn/native/RenderBundle.cpp new file mode 100644 index 0000000..da101882 --- /dev/null +++ b/src/dawn/native/RenderBundle.cpp
@@ -0,0 +1,91 @@ +// Copyright 2019 The Dawn Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include "dawn/native/RenderBundle.h" + +#include "dawn/common/BitSetIterator.h" +#include "dawn/native/Commands.h" +#include "dawn/native/Device.h" +#include "dawn/native/ObjectType_autogen.h" +#include "dawn/native/RenderBundleEncoder.h" + +namespace dawn::native { + + RenderBundleBase::RenderBundleBase(RenderBundleEncoder* encoder, + const RenderBundleDescriptor* descriptor, + Ref<AttachmentState> attachmentState, + bool depthReadOnly, + bool stencilReadOnly, + RenderPassResourceUsage resourceUsage, + IndirectDrawMetadata indirectDrawMetadata) + : ApiObjectBase(encoder->GetDevice(), kLabelNotImplemented), + mCommands(encoder->AcquireCommands()), + mIndirectDrawMetadata(std::move(indirectDrawMetadata)), + mAttachmentState(std::move(attachmentState)), + mDepthReadOnly(depthReadOnly), + mStencilReadOnly(stencilReadOnly), + mResourceUsage(std::move(resourceUsage)) { + TrackInDevice(); + } + + void RenderBundleBase::DestroyImpl() { + FreeCommands(&mCommands); + + // Remove reference to the attachment state so that we don't have lingering references to + // it preventing it from being uncached in the device. + mAttachmentState = nullptr; + } + + // static + RenderBundleBase* RenderBundleBase::MakeError(DeviceBase* device) { + return new RenderBundleBase(device, ObjectBase::kError); + } + + RenderBundleBase::RenderBundleBase(DeviceBase* device, ErrorTag errorTag) + : ApiObjectBase(device, errorTag), mIndirectDrawMetadata(device->GetLimits()) { + } + + ObjectType RenderBundleBase::GetType() const { + return ObjectType::RenderBundle; + } + + CommandIterator* RenderBundleBase::GetCommands() { + return &mCommands; + } + + const AttachmentState* RenderBundleBase::GetAttachmentState() const { + ASSERT(!IsError()); + return mAttachmentState.Get(); + } + + bool RenderBundleBase::IsDepthReadOnly() const { + ASSERT(!IsError()); + return mDepthReadOnly; + } + + bool RenderBundleBase::IsStencilReadOnly() const { + ASSERT(!IsError()); + return mStencilReadOnly; + } + + const RenderPassResourceUsage& RenderBundleBase::GetResourceUsage() const { + ASSERT(!IsError()); + return mResourceUsage; + } + + const IndirectDrawMetadata& RenderBundleBase::GetIndirectDrawMetadata() { + return mIndirectDrawMetadata; + } + +} // namespace dawn::native
diff --git a/src/dawn/native/RenderBundle.h b/src/dawn/native/RenderBundle.h new file mode 100644 index 0000000..9112b77 --- /dev/null +++ b/src/dawn/native/RenderBundle.h
@@ -0,0 +1,73 @@ +// Copyright 2019 The Dawn Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#ifndef DAWNNATIVE_RENDERBUNDLE_H_ +#define DAWNNATIVE_RENDERBUNDLE_H_ + +#include "dawn/common/Constants.h" +#include "dawn/native/AttachmentState.h" +#include "dawn/native/CommandAllocator.h" +#include "dawn/native/Error.h" +#include "dawn/native/Forward.h" +#include "dawn/native/IndirectDrawMetadata.h" +#include "dawn/native/ObjectBase.h" +#include "dawn/native/PassResourceUsage.h" + +#include "dawn/native/dawn_platform.h" + +#include <bitset> + +namespace dawn::native { + + struct RenderBundleDescriptor; + class RenderBundleEncoder; + + class RenderBundleBase final : public ApiObjectBase { + public: + RenderBundleBase(RenderBundleEncoder* encoder, + const RenderBundleDescriptor* descriptor, + Ref<AttachmentState> attachmentState, + bool depthReadOnly, + bool stencilReadOnly, + RenderPassResourceUsage resourceUsage, + IndirectDrawMetadata indirectDrawMetadata); + + static RenderBundleBase* MakeError(DeviceBase* device); + + ObjectType GetType() const override; + + CommandIterator* GetCommands(); + + const AttachmentState* GetAttachmentState() const; + bool IsDepthReadOnly() const; + bool IsStencilReadOnly() const; + const RenderPassResourceUsage& GetResourceUsage() const; + const IndirectDrawMetadata& GetIndirectDrawMetadata(); + + private: + RenderBundleBase(DeviceBase* device, ErrorTag errorTag); + + void DestroyImpl() override; + + CommandIterator mCommands; + IndirectDrawMetadata mIndirectDrawMetadata; + Ref<AttachmentState> mAttachmentState; + bool mDepthReadOnly; + bool mStencilReadOnly; + RenderPassResourceUsage mResourceUsage; + }; + +} // namespace dawn::native + +#endif // DAWNNATIVE_RENDERBUNDLE_H_
diff --git a/src/dawn/native/RenderBundleEncoder.cpp b/src/dawn/native/RenderBundleEncoder.cpp new file mode 100644 index 0000000..6d7a2db --- /dev/null +++ b/src/dawn/native/RenderBundleEncoder.cpp
@@ -0,0 +1,172 @@ +// Copyright 2019 The Dawn Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include "dawn/native/RenderBundleEncoder.h" + +#include "dawn/native/CommandValidation.h" +#include "dawn/native/Commands.h" +#include "dawn/native/Device.h" +#include "dawn/native/Format.h" +#include "dawn/native/ObjectType_autogen.h" +#include "dawn/native/RenderPipeline.h" +#include "dawn/native/ValidationUtils_autogen.h" +#include "dawn/platform/DawnPlatform.h" +#include "dawn/platform/tracing/TraceEvent.h" + +namespace dawn::native { + + MaybeError ValidateColorAttachmentFormat(const DeviceBase* device, + wgpu::TextureFormat textureFormat) { + DAWN_TRY(ValidateTextureFormat(textureFormat)); + const Format* format = nullptr; + DAWN_TRY_ASSIGN(format, device->GetInternalFormat(textureFormat)); + DAWN_INVALID_IF(!format->IsColor() || !format->isRenderable, + "Texture format %s is not color renderable.", textureFormat); + return {}; + } + + MaybeError ValidateDepthStencilAttachmentFormat(const DeviceBase* device, + wgpu::TextureFormat textureFormat, + bool depthReadOnly, + bool stencilReadOnly) { + DAWN_TRY(ValidateTextureFormat(textureFormat)); + const Format* format = nullptr; + DAWN_TRY_ASSIGN(format, device->GetInternalFormat(textureFormat)); + DAWN_INVALID_IF(!format->HasDepthOrStencil() || !format->isRenderable, + "Texture format %s is not depth/stencil renderable.", textureFormat); + + DAWN_INVALID_IF( + format->HasDepth() && format->HasStencil() && depthReadOnly != stencilReadOnly, + "depthReadOnly (%u) and stencilReadOnly (%u) must be the same when format %s has " + "both depth and stencil aspects.", + depthReadOnly, stencilReadOnly, textureFormat); + + return {}; + } + + MaybeError ValidateRenderBundleEncoderDescriptor( + const DeviceBase* device, + const RenderBundleEncoderDescriptor* descriptor) { + DAWN_INVALID_IF(!IsValidSampleCount(descriptor->sampleCount), + "Sample count (%u) is not supported.", descriptor->sampleCount); + + DAWN_INVALID_IF( + descriptor->colorFormatsCount > kMaxColorAttachments, + "Color formats count (%u) exceeds maximum number of color attachements (%u).", + descriptor->colorFormatsCount, kMaxColorAttachments); + + bool allColorFormatsUndefined = true; + for (uint32_t i = 0; i < descriptor->colorFormatsCount; ++i) { + wgpu::TextureFormat format = descriptor->colorFormats[i]; + if (format != wgpu::TextureFormat::Undefined) { + DAWN_TRY_CONTEXT(ValidateColorAttachmentFormat(device, format), + "validating colorFormats[%u]", i); + allColorFormatsUndefined = false; + } + } + + if (descriptor->depthStencilFormat != wgpu::TextureFormat::Undefined) { + DAWN_TRY_CONTEXT(ValidateDepthStencilAttachmentFormat( + device, descriptor->depthStencilFormat, descriptor->depthReadOnly, + descriptor->stencilReadOnly), + "validating depthStencilFormat"); + } else { + DAWN_INVALID_IF( + allColorFormatsUndefined, + "No color or depthStencil attachments specified. At least one is required."); + } + + return {}; + } + + RenderBundleEncoder::RenderBundleEncoder(DeviceBase* device, + const RenderBundleEncoderDescriptor* descriptor) + : RenderEncoderBase(device, + descriptor->label, + &mBundleEncodingContext, + device->GetOrCreateAttachmentState(descriptor), + descriptor->depthReadOnly, + descriptor->stencilReadOnly), + mBundleEncodingContext(device, this) { + TrackInDevice(); + } + + RenderBundleEncoder::RenderBundleEncoder(DeviceBase* device, ErrorTag errorTag) + : RenderEncoderBase(device, &mBundleEncodingContext, errorTag), + mBundleEncodingContext(device, this) { + } + + void RenderBundleEncoder::DestroyImpl() { + RenderEncoderBase::DestroyImpl(); + mBundleEncodingContext.Destroy(); + } + + // static + Ref<RenderBundleEncoder> RenderBundleEncoder::Create( + DeviceBase* device, + const RenderBundleEncoderDescriptor* descriptor) { + return AcquireRef(new RenderBundleEncoder(device, descriptor)); + } + + // static + RenderBundleEncoder* RenderBundleEncoder::MakeError(DeviceBase* device) { + return new RenderBundleEncoder(device, ObjectBase::kError); + } + + ObjectType RenderBundleEncoder::GetType() const { + return ObjectType::RenderBundleEncoder; + } + + CommandIterator RenderBundleEncoder::AcquireCommands() { + return mBundleEncodingContext.AcquireCommands(); + } + + RenderBundleBase* RenderBundleEncoder::APIFinish(const RenderBundleDescriptor* descriptor) { + RenderBundleBase* result = nullptr; + + if (GetDevice()->ConsumedError(FinishImpl(descriptor), &result, "calling %s.Finish(%s).", + this, descriptor)) { + return RenderBundleBase::MakeError(GetDevice()); + } + + return result; + } + + ResultOrError<RenderBundleBase*> RenderBundleEncoder::FinishImpl( + const RenderBundleDescriptor* descriptor) { + // Even if mBundleEncodingContext.Finish() validation fails, calling it will mutate the + // internal state of the encoding context. Subsequent calls to encode commands will generate + // errors. + DAWN_TRY(mBundleEncodingContext.Finish()); + + RenderPassResourceUsage usages = mUsageTracker.AcquireResourceUsage(); + if (IsValidationEnabled()) { + DAWN_TRY(GetDevice()->ValidateObject(this)); + DAWN_TRY(ValidateProgrammableEncoderEnd()); + DAWN_TRY(ValidateFinish(usages)); + } + + return new RenderBundleBase(this, descriptor, AcquireAttachmentState(), IsDepthReadOnly(), + IsStencilReadOnly(), std::move(usages), + std::move(mIndirectDrawMetadata)); + } + + MaybeError RenderBundleEncoder::ValidateFinish(const RenderPassResourceUsage& usages) const { + TRACE_EVENT0(GetDevice()->GetPlatform(), Validation, "RenderBundleEncoder::ValidateFinish"); + DAWN_TRY(GetDevice()->ValidateObject(this)); + DAWN_TRY(ValidateSyncScopeResourceUsage(usages)); + return {}; + } + +} // namespace dawn::native
diff --git a/src/dawn/native/RenderBundleEncoder.h b/src/dawn/native/RenderBundleEncoder.h new file mode 100644 index 0000000..46c1470 --- /dev/null +++ b/src/dawn/native/RenderBundleEncoder.h
@@ -0,0 +1,56 @@ +// Copyright 2019 The Dawn Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#ifndef DAWNNATIVE_RENDERBUNDLEENCODER_H_ +#define DAWNNATIVE_RENDERBUNDLEENCODER_H_ + +#include "dawn/native/EncodingContext.h" +#include "dawn/native/Error.h" +#include "dawn/native/Forward.h" +#include "dawn/native/RenderBundle.h" +#include "dawn/native/RenderEncoderBase.h" + +namespace dawn::native { + + MaybeError ValidateRenderBundleEncoderDescriptor( + const DeviceBase* device, + const RenderBundleEncoderDescriptor* descriptor); + + class RenderBundleEncoder final : public RenderEncoderBase { + public: + static Ref<RenderBundleEncoder> Create(DeviceBase* device, + const RenderBundleEncoderDescriptor* descriptor); + static RenderBundleEncoder* MakeError(DeviceBase* device); + + ObjectType GetType() const override; + + RenderBundleBase* APIFinish(const RenderBundleDescriptor* descriptor); + + CommandIterator AcquireCommands(); + + private: + RenderBundleEncoder(DeviceBase* device, const RenderBundleEncoderDescriptor* descriptor); + RenderBundleEncoder(DeviceBase* device, ErrorTag errorTag); + + void DestroyImpl() override; + + ResultOrError<RenderBundleBase*> FinishImpl(const RenderBundleDescriptor* descriptor); + MaybeError ValidateFinish(const RenderPassResourceUsage& usages) const; + + EncodingContext mBundleEncodingContext; + }; + +} // namespace dawn::native + +#endif // DAWNNATIVE_RENDERBUNDLEENCODER_H_
diff --git a/src/dawn/native/RenderEncoderBase.cpp b/src/dawn/native/RenderEncoderBase.cpp new file mode 100644 index 0000000..f186c16 --- /dev/null +++ b/src/dawn/native/RenderEncoderBase.cpp
@@ -0,0 +1,414 @@ +// Copyright 2019 The Dawn Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include "dawn/native/RenderEncoderBase.h" + +#include "dawn/common/Constants.h" +#include "dawn/common/Log.h" +#include "dawn/native/Buffer.h" +#include "dawn/native/CommandEncoder.h" +#include "dawn/native/CommandValidation.h" +#include "dawn/native/Commands.h" +#include "dawn/native/Device.h" +#include "dawn/native/RenderPipeline.h" +#include "dawn/native/ValidationUtils_autogen.h" + +#include <math.h> +#include <cstring> + +namespace dawn::native { + + RenderEncoderBase::RenderEncoderBase(DeviceBase* device, + const char* label, + EncodingContext* encodingContext, + Ref<AttachmentState> attachmentState, + bool depthReadOnly, + bool stencilReadOnly) + : ProgrammableEncoder(device, label, encodingContext), + mIndirectDrawMetadata(device->GetLimits()), + mAttachmentState(std::move(attachmentState)), + mDisableBaseVertex(device->IsToggleEnabled(Toggle::DisableBaseVertex)), + mDisableBaseInstance(device->IsToggleEnabled(Toggle::DisableBaseInstance)) { + mDepthReadOnly = depthReadOnly; + mStencilReadOnly = stencilReadOnly; + } + + RenderEncoderBase::RenderEncoderBase(DeviceBase* device, + EncodingContext* encodingContext, + ErrorTag errorTag) + : ProgrammableEncoder(device, encodingContext, errorTag), + mIndirectDrawMetadata(device->GetLimits()), + mDisableBaseVertex(device->IsToggleEnabled(Toggle::DisableBaseVertex)), + mDisableBaseInstance(device->IsToggleEnabled(Toggle::DisableBaseInstance)) { + } + + void RenderEncoderBase::DestroyImpl() { + // Remove reference to the attachment state so that we don't have lingering references to + // it preventing it from being uncached in the device. + mAttachmentState = nullptr; + } + + const AttachmentState* RenderEncoderBase::GetAttachmentState() const { + ASSERT(!IsError()); + ASSERT(mAttachmentState != nullptr); + return mAttachmentState.Get(); + } + + bool RenderEncoderBase::IsDepthReadOnly() const { + ASSERT(!IsError()); + return mDepthReadOnly; + } + + bool RenderEncoderBase::IsStencilReadOnly() const { + ASSERT(!IsError()); + return mStencilReadOnly; + } + + Ref<AttachmentState> RenderEncoderBase::AcquireAttachmentState() { + return std::move(mAttachmentState); + } + + void RenderEncoderBase::APIDraw(uint32_t vertexCount, + uint32_t instanceCount, + uint32_t firstVertex, + uint32_t firstInstance) { + mEncodingContext->TryEncode( + this, + [&](CommandAllocator* allocator) -> MaybeError { + if (IsValidationEnabled()) { + DAWN_TRY(mCommandBufferState.ValidateCanDraw()); + + DAWN_INVALID_IF(mDisableBaseInstance && firstInstance != 0, + "First instance (%u) must be zero.", firstInstance); + + DAWN_TRY(mCommandBufferState.ValidateBufferInRangeForVertexBuffer(vertexCount, + firstVertex)); + DAWN_TRY(mCommandBufferState.ValidateBufferInRangeForInstanceBuffer( + instanceCount, firstInstance)); + } + + DrawCmd* draw = allocator->Allocate<DrawCmd>(Command::Draw); + draw->vertexCount = vertexCount; + draw->instanceCount = instanceCount; + draw->firstVertex = firstVertex; + draw->firstInstance = firstInstance; + + return {}; + }, + "encoding %s.Draw(%u, %u, %u, %u).", this, vertexCount, instanceCount, firstVertex, + firstInstance); + } + + void RenderEncoderBase::APIDrawIndexed(uint32_t indexCount, + uint32_t instanceCount, + uint32_t firstIndex, + int32_t baseVertex, + uint32_t firstInstance) { + mEncodingContext->TryEncode( + this, + [&](CommandAllocator* allocator) -> MaybeError { + if (IsValidationEnabled()) { + DAWN_TRY(mCommandBufferState.ValidateCanDrawIndexed()); + + DAWN_INVALID_IF(mDisableBaseInstance && firstInstance != 0, + "First instance (%u) must be zero.", firstInstance); + + DAWN_INVALID_IF(mDisableBaseVertex && baseVertex != 0, + "Base vertex (%u) must be zero.", baseVertex); + + DAWN_TRY( + mCommandBufferState.ValidateIndexBufferInRange(indexCount, firstIndex)); + + // Although we don't know actual vertex access range in CPU, we still call the + // ValidateBufferInRangeForVertexBuffer in order to deal with those vertex step + // mode vertex buffer with an array stride of zero. + DAWN_TRY(mCommandBufferState.ValidateBufferInRangeForVertexBuffer(0, 0)); + DAWN_TRY(mCommandBufferState.ValidateBufferInRangeForInstanceBuffer( + instanceCount, firstInstance)); + } + + DrawIndexedCmd* draw = allocator->Allocate<DrawIndexedCmd>(Command::DrawIndexed); + draw->indexCount = indexCount; + draw->instanceCount = instanceCount; + draw->firstIndex = firstIndex; + draw->baseVertex = baseVertex; + draw->firstInstance = firstInstance; + + return {}; + }, + "encoding %s.DrawIndexed(%u, %u, %u, %i, %u).", this, indexCount, instanceCount, + firstIndex, baseVertex, firstInstance); + } + + void RenderEncoderBase::APIDrawIndirect(BufferBase* indirectBuffer, uint64_t indirectOffset) { + mEncodingContext->TryEncode( + this, + [&](CommandAllocator* allocator) -> MaybeError { + if (IsValidationEnabled()) { + DAWN_TRY(GetDevice()->ValidateObject(indirectBuffer)); + DAWN_TRY(ValidateCanUseAs(indirectBuffer, wgpu::BufferUsage::Indirect)); + DAWN_TRY(mCommandBufferState.ValidateCanDraw()); + + DAWN_INVALID_IF(indirectOffset % 4 != 0, + "Indirect offset (%u) is not a multiple of 4.", indirectOffset); + + DAWN_INVALID_IF( + indirectOffset >= indirectBuffer->GetSize() || + kDrawIndirectSize > indirectBuffer->GetSize() - indirectOffset, + "Indirect offset (%u) is out of bounds of indirect buffer %s size (%u).", + indirectOffset, indirectBuffer, indirectBuffer->GetSize()); + } + + DrawIndirectCmd* cmd = allocator->Allocate<DrawIndirectCmd>(Command::DrawIndirect); + cmd->indirectBuffer = indirectBuffer; + cmd->indirectOffset = indirectOffset; + + mUsageTracker.BufferUsedAs(indirectBuffer, wgpu::BufferUsage::Indirect); + + return {}; + }, + "encoding %s.DrawIndirect(%s, %u).", this, indirectBuffer, indirectOffset); + } + + void RenderEncoderBase::APIDrawIndexedIndirect(BufferBase* indirectBuffer, + uint64_t indirectOffset) { + mEncodingContext->TryEncode( + this, + [&](CommandAllocator* allocator) -> MaybeError { + if (IsValidationEnabled()) { + DAWN_TRY(GetDevice()->ValidateObject(indirectBuffer)); + DAWN_TRY(ValidateCanUseAs(indirectBuffer, wgpu::BufferUsage::Indirect)); + DAWN_TRY(mCommandBufferState.ValidateCanDrawIndexed()); + + DAWN_INVALID_IF(indirectOffset % 4 != 0, + "Indirect offset (%u) is not a multiple of 4.", indirectOffset); + + DAWN_INVALID_IF( + (indirectOffset >= indirectBuffer->GetSize() || + kDrawIndexedIndirectSize > indirectBuffer->GetSize() - indirectOffset), + "Indirect offset (%u) is out of bounds of indirect buffer %s size (%u).", + indirectOffset, indirectBuffer, indirectBuffer->GetSize()); + } + + DrawIndexedIndirectCmd* cmd = + allocator->Allocate<DrawIndexedIndirectCmd>(Command::DrawIndexedIndirect); + if (IsValidationEnabled()) { + // Later, EncodeIndirectDrawValidationCommands will allocate a scratch storage + // buffer which will store the validated indirect data. The buffer and offset + // will be updated to point to it. + // |EncodeIndirectDrawValidationCommands| is called at the end of encoding the + // render pass, while the |cmd| pointer is still valid. + cmd->indirectBuffer = nullptr; + + mIndirectDrawMetadata.AddIndexedIndirectDraw( + mCommandBufferState.GetIndexFormat(), + mCommandBufferState.GetIndexBufferSize(), indirectBuffer, indirectOffset, + cmd); + } else { + cmd->indirectBuffer = indirectBuffer; + cmd->indirectOffset = indirectOffset; + } + + // TODO(crbug.com/dawn/1166): Adding the indirectBuffer is needed for correct usage + // validation, but it will unecessarily transition to indirectBuffer usage in the + // backend. + mUsageTracker.BufferUsedAs(indirectBuffer, wgpu::BufferUsage::Indirect); + + return {}; + }, + "encoding %s.DrawIndexedIndirect(%s, %u).", this, indirectBuffer, indirectOffset); + } + + void RenderEncoderBase::APISetPipeline(RenderPipelineBase* pipeline) { + mEncodingContext->TryEncode( + this, + [&](CommandAllocator* allocator) -> MaybeError { + if (IsValidationEnabled()) { + DAWN_TRY(GetDevice()->ValidateObject(pipeline)); + + DAWN_INVALID_IF(pipeline->GetAttachmentState() != mAttachmentState.Get(), + "Attachment state of %s is not compatible with %s.\n" + "%s expects an attachment state of %s.\n" + "%s has an attachment state of %s.", + pipeline, this, this, mAttachmentState.Get(), pipeline, + pipeline->GetAttachmentState()); + + DAWN_INVALID_IF(pipeline->WritesDepth() && mDepthReadOnly, + "%s writes depth while %s's depthReadOnly is true", pipeline, + this); + + DAWN_INVALID_IF(pipeline->WritesStencil() && mStencilReadOnly, + "%s writes stencil while %s's stencilReadOnly is true", + pipeline, this); + } + + mCommandBufferState.SetRenderPipeline(pipeline); + + SetRenderPipelineCmd* cmd = + allocator->Allocate<SetRenderPipelineCmd>(Command::SetRenderPipeline); + cmd->pipeline = pipeline; + + return {}; + }, + "encoding %s.SetPipeline(%s).", this, pipeline); + } + + void RenderEncoderBase::APISetIndexBuffer(BufferBase* buffer, + wgpu::IndexFormat format, + uint64_t offset, + uint64_t size) { + mEncodingContext->TryEncode( + this, + [&](CommandAllocator* allocator) -> MaybeError { + if (IsValidationEnabled()) { + DAWN_TRY(GetDevice()->ValidateObject(buffer)); + DAWN_TRY(ValidateCanUseAs(buffer, wgpu::BufferUsage::Index)); + + DAWN_TRY(ValidateIndexFormat(format)); + + DAWN_INVALID_IF(format == wgpu::IndexFormat::Undefined, + "Index format must be specified"); + + DAWN_INVALID_IF(offset % uint64_t(IndexFormatSize(format)) != 0, + "Index buffer offset (%u) is not a multiple of the size (%u) " + "of %s.", + offset, IndexFormatSize(format), format); + + uint64_t bufferSize = buffer->GetSize(); + DAWN_INVALID_IF(offset > bufferSize, + "Index buffer offset (%u) is larger than the size (%u) of %s.", + offset, bufferSize, buffer); + + uint64_t remainingSize = bufferSize - offset; + + if (size == wgpu::kWholeSize) { + size = remainingSize; + } else { + DAWN_INVALID_IF(size > remainingSize, + "Index buffer range (offset: %u, size: %u) doesn't fit in " + "the size (%u) of " + "%s.", + offset, size, bufferSize, buffer); + } + } else { + if (size == wgpu::kWholeSize) { + DAWN_ASSERT(buffer->GetSize() >= offset); + size = buffer->GetSize() - offset; + } + } + + mCommandBufferState.SetIndexBuffer(format, size); + + SetIndexBufferCmd* cmd = + allocator->Allocate<SetIndexBufferCmd>(Command::SetIndexBuffer); + cmd->buffer = buffer; + cmd->format = format; + cmd->offset = offset; + cmd->size = size; + + mUsageTracker.BufferUsedAs(buffer, wgpu::BufferUsage::Index); + + return {}; + }, + "encoding %s.SetIndexBuffer(%s, %s, %u, %u).", this, buffer, format, offset, size); + } + + void RenderEncoderBase::APISetVertexBuffer(uint32_t slot, + BufferBase* buffer, + uint64_t offset, + uint64_t size) { + mEncodingContext->TryEncode( + this, + [&](CommandAllocator* allocator) -> MaybeError { + if (IsValidationEnabled()) { + DAWN_TRY(GetDevice()->ValidateObject(buffer)); + DAWN_TRY(ValidateCanUseAs(buffer, wgpu::BufferUsage::Vertex)); + + DAWN_INVALID_IF(slot >= kMaxVertexBuffers, + "Vertex buffer slot (%u) is larger the maximum (%u)", slot, + kMaxVertexBuffers - 1); + + DAWN_INVALID_IF(offset % 4 != 0, + "Vertex buffer offset (%u) is not a multiple of 4", offset); + + uint64_t bufferSize = buffer->GetSize(); + DAWN_INVALID_IF(offset > bufferSize, + "Vertex buffer offset (%u) is larger than the size (%u) of %s.", + offset, bufferSize, buffer); + + uint64_t remainingSize = bufferSize - offset; + + if (size == wgpu::kWholeSize) { + size = remainingSize; + } else { + DAWN_INVALID_IF(size > remainingSize, + "Vertex buffer range (offset: %u, size: %u) doesn't fit in " + "the size (%u) " + "of %s.", + offset, size, bufferSize, buffer); + } + } else { + if (size == wgpu::kWholeSize) { + DAWN_ASSERT(buffer->GetSize() >= offset); + size = buffer->GetSize() - offset; + } + } + + mCommandBufferState.SetVertexBuffer(VertexBufferSlot(uint8_t(slot)), size); + + SetVertexBufferCmd* cmd = + allocator->Allocate<SetVertexBufferCmd>(Command::SetVertexBuffer); + cmd->slot = VertexBufferSlot(static_cast<uint8_t>(slot)); + cmd->buffer = buffer; + cmd->offset = offset; + cmd->size = size; + + mUsageTracker.BufferUsedAs(buffer, wgpu::BufferUsage::Vertex); + + return {}; + }, + "encoding %s.SetVertexBuffer(%u, %s, %u, %u).", this, slot, buffer, offset, size); + } + + void RenderEncoderBase::APISetBindGroup(uint32_t groupIndexIn, + BindGroupBase* group, + uint32_t dynamicOffsetCount, + const uint32_t* dynamicOffsets) { + mEncodingContext->TryEncode( + this, + [&](CommandAllocator* allocator) -> MaybeError { + BindGroupIndex groupIndex(groupIndexIn); + + if (IsValidationEnabled()) { + DAWN_TRY(ValidateSetBindGroup(groupIndex, group, dynamicOffsetCount, + dynamicOffsets)); + } + + RecordSetBindGroup(allocator, groupIndex, group, dynamicOffsetCount, + dynamicOffsets); + mCommandBufferState.SetBindGroup(groupIndex, group, dynamicOffsetCount, + dynamicOffsets); + mUsageTracker.AddBindGroup(group); + + return {}; + }, + // TODO(dawn:1190): For unknown reasons formatting this message fails if `group` is used + // as a string value in the message. This despite the exact same code working as + // intended in ComputePassEncoder::APISetBindGroup. Replacing with a static [BindGroup] + // until the reason for the failure can be determined. + "encoding %s.SetBindGroup(%u, [BindGroup], %u, ...).", this, groupIndexIn, + dynamicOffsetCount); + } + +} // namespace dawn::native
diff --git a/src/dawn/native/RenderEncoderBase.h b/src/dawn/native/RenderEncoderBase.h new file mode 100644 index 0000000..80128f3 --- /dev/null +++ b/src/dawn/native/RenderEncoderBase.h
@@ -0,0 +1,87 @@ +// Copyright 2019 The Dawn Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#ifndef DAWNNATIVE_RENDERENCODERBASE_H_ +#define DAWNNATIVE_RENDERENCODERBASE_H_ + +#include "dawn/native/AttachmentState.h" +#include "dawn/native/CommandBufferStateTracker.h" +#include "dawn/native/Error.h" +#include "dawn/native/IndirectDrawMetadata.h" +#include "dawn/native/PassResourceUsageTracker.h" +#include "dawn/native/ProgrammableEncoder.h" + +namespace dawn::native { + + class RenderEncoderBase : public ProgrammableEncoder { + public: + RenderEncoderBase(DeviceBase* device, + const char* label, + EncodingContext* encodingContext, + Ref<AttachmentState> attachmentState, + bool depthReadOnly, + bool stencilReadOnly); + + void APIDraw(uint32_t vertexCount, + uint32_t instanceCount = 1, + uint32_t firstVertex = 0, + uint32_t firstInstance = 0); + void APIDrawIndexed(uint32_t vertexCount, + uint32_t instanceCount, + uint32_t firstIndex, + int32_t baseVertex, + uint32_t firstInstance); + + void APIDrawIndirect(BufferBase* indirectBuffer, uint64_t indirectOffset); + void APIDrawIndexedIndirect(BufferBase* indirectBuffer, uint64_t indirectOffset); + + void APISetPipeline(RenderPipelineBase* pipeline); + + void APISetVertexBuffer(uint32_t slot, BufferBase* buffer, uint64_t offset, uint64_t size); + void APISetIndexBuffer(BufferBase* buffer, + wgpu::IndexFormat format, + uint64_t offset, + uint64_t size); + + void APISetBindGroup(uint32_t groupIndex, + BindGroupBase* group, + uint32_t dynamicOffsetCount = 0, + const uint32_t* dynamicOffsets = nullptr); + + const AttachmentState* GetAttachmentState() const; + bool IsDepthReadOnly() const; + bool IsStencilReadOnly() const; + Ref<AttachmentState> AcquireAttachmentState(); + + protected: + // Construct an "error" render encoder base. + RenderEncoderBase(DeviceBase* device, EncodingContext* encodingContext, ErrorTag errorTag); + + void DestroyImpl() override; + + CommandBufferStateTracker mCommandBufferState; + RenderPassResourceUsageTracker mUsageTracker; + IndirectDrawMetadata mIndirectDrawMetadata; + + private: + Ref<AttachmentState> mAttachmentState; + const bool mDisableBaseVertex; + const bool mDisableBaseInstance; + bool mDepthReadOnly = false; + bool mStencilReadOnly = false; + }; + +} // namespace dawn::native + +#endif // DAWNNATIVE_RENDERENCODERBASE_H_
diff --git a/src/dawn/native/RenderPassEncoder.cpp b/src/dawn/native/RenderPassEncoder.cpp new file mode 100644 index 0000000..b7f7563 --- /dev/null +++ b/src/dawn/native/RenderPassEncoder.cpp
@@ -0,0 +1,425 @@ +// Copyright 2018 The Dawn Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include "dawn/native/RenderPassEncoder.h" + +#include "dawn/common/Constants.h" +#include "dawn/native/Buffer.h" +#include "dawn/native/CommandEncoder.h" +#include "dawn/native/CommandValidation.h" +#include "dawn/native/Commands.h" +#include "dawn/native/Device.h" +#include "dawn/native/ObjectType_autogen.h" +#include "dawn/native/QuerySet.h" +#include "dawn/native/RenderBundle.h" +#include "dawn/native/RenderPipeline.h" + +#include <math.h> +#include <cstring> + +namespace dawn::native { + namespace { + + // Check the query at queryIndex is unavailable, otherwise it cannot be written. + MaybeError ValidateQueryIndexOverwrite(QuerySetBase* querySet, + uint32_t queryIndex, + const QueryAvailabilityMap& queryAvailabilityMap) { + auto it = queryAvailabilityMap.find(querySet); + DAWN_INVALID_IF(it != queryAvailabilityMap.end() && it->second[queryIndex], + "Query index %u of %s is written to twice in a render pass.", + queryIndex, querySet); + + return {}; + } + + } // namespace + + // The usage tracker is passed in here, because it is prepopulated with usages from the + // BeginRenderPassCmd. If we had RenderPassEncoder responsible for recording the + // command, then this wouldn't be necessary. + RenderPassEncoder::RenderPassEncoder(DeviceBase* device, + const RenderPassDescriptor* descriptor, + CommandEncoder* commandEncoder, + EncodingContext* encodingContext, + RenderPassResourceUsageTracker usageTracker, + Ref<AttachmentState> attachmentState, + std::vector<TimestampWrite> timestampWritesAtEnd, + uint32_t renderTargetWidth, + uint32_t renderTargetHeight, + bool depthReadOnly, + bool stencilReadOnly) + : RenderEncoderBase(device, + descriptor->label, + encodingContext, + std::move(attachmentState), + depthReadOnly, + stencilReadOnly), + mCommandEncoder(commandEncoder), + mRenderTargetWidth(renderTargetWidth), + mRenderTargetHeight(renderTargetHeight), + mOcclusionQuerySet(descriptor->occlusionQuerySet), + mTimestampWritesAtEnd(std::move(timestampWritesAtEnd)) { + mUsageTracker = std::move(usageTracker); + TrackInDevice(); + } + + // static + Ref<RenderPassEncoder> RenderPassEncoder::Create( + DeviceBase* device, + const RenderPassDescriptor* descriptor, + CommandEncoder* commandEncoder, + EncodingContext* encodingContext, + RenderPassResourceUsageTracker usageTracker, + Ref<AttachmentState> attachmentState, + std::vector<TimestampWrite> timestampWritesAtEnd, + uint32_t renderTargetWidth, + uint32_t renderTargetHeight, + bool depthReadOnly, + bool stencilReadOnly) { + return AcquireRef(new RenderPassEncoder( + device, descriptor, commandEncoder, encodingContext, std::move(usageTracker), + std::move(attachmentState), std::move(timestampWritesAtEnd), renderTargetWidth, + renderTargetHeight, depthReadOnly, stencilReadOnly)); + } + + RenderPassEncoder::RenderPassEncoder(DeviceBase* device, + CommandEncoder* commandEncoder, + EncodingContext* encodingContext, + ErrorTag errorTag) + : RenderEncoderBase(device, encodingContext, errorTag), mCommandEncoder(commandEncoder) { + } + + // static + Ref<RenderPassEncoder> RenderPassEncoder::MakeError(DeviceBase* device, + CommandEncoder* commandEncoder, + EncodingContext* encodingContext) { + return AcquireRef( + new RenderPassEncoder(device, commandEncoder, encodingContext, ObjectBase::kError)); + } + + void RenderPassEncoder::DestroyImpl() { + RenderEncoderBase::DestroyImpl(); + // Ensure that the pass has exited. This is done for passes only since validation requires + // they exit before destruction while bundles do not. + mEncodingContext->EnsurePassExited(this); + } + + ObjectType RenderPassEncoder::GetType() const { + return ObjectType::RenderPassEncoder; + } + + void RenderPassEncoder::TrackQueryAvailability(QuerySetBase* querySet, uint32_t queryIndex) { + DAWN_ASSERT(querySet != nullptr); + + // Track the query availability with true on render pass for rewrite validation and query + // reset on render pass on Vulkan + mUsageTracker.TrackQueryAvailability(querySet, queryIndex); + + // Track it again on command encoder for zero-initializing when resolving unused queries. + mCommandEncoder->TrackQueryAvailability(querySet, queryIndex); + } + + void RenderPassEncoder::APIEnd() { + if (mEncodingContext->TryEncode( + this, + [&](CommandAllocator* allocator) -> MaybeError { + if (IsValidationEnabled()) { + DAWN_TRY(ValidateProgrammableEncoderEnd()); + + DAWN_INVALID_IF( + mOcclusionQueryActive, + "Render pass %s ended with incomplete occlusion query index %u of %s.", + this, mCurrentOcclusionQueryIndex, mOcclusionQuerySet.Get()); + } + + EndRenderPassCmd* cmd = + allocator->Allocate<EndRenderPassCmd>(Command::EndRenderPass); + // The query availability has already been updated at the beginning of render + // pass, and no need to do update here. + cmd->timestampWrites = std::move(mTimestampWritesAtEnd); + + DAWN_TRY(mEncodingContext->ExitRenderPass(this, std::move(mUsageTracker), + mCommandEncoder.Get(), + std::move(mIndirectDrawMetadata))); + return {}; + }, + "encoding %s.End().", this)) { + } + } + + void RenderPassEncoder::APIEndPass() { + GetDevice()->EmitDeprecationWarning("endPass() has been deprecated. Use end() instead."); + APIEnd(); + } + + void RenderPassEncoder::APISetStencilReference(uint32_t reference) { + mEncodingContext->TryEncode( + this, + [&](CommandAllocator* allocator) -> MaybeError { + SetStencilReferenceCmd* cmd = + allocator->Allocate<SetStencilReferenceCmd>(Command::SetStencilReference); + cmd->reference = reference; + + return {}; + }, + "encoding %s.SetStencilReference(%u).", this, reference); + } + + void RenderPassEncoder::APISetBlendConstant(const Color* color) { + mEncodingContext->TryEncode( + this, + [&](CommandAllocator* allocator) -> MaybeError { + SetBlendConstantCmd* cmd = + allocator->Allocate<SetBlendConstantCmd>(Command::SetBlendConstant); + cmd->color = *color; + + return {}; + }, + "encoding %s.SetBlendConstant(%s).", this, color); + } + + void RenderPassEncoder::APISetViewport(float x, + float y, + float width, + float height, + float minDepth, + float maxDepth) { + mEncodingContext->TryEncode( + this, + [&](CommandAllocator* allocator) -> MaybeError { + if (IsValidationEnabled()) { + DAWN_INVALID_IF( + (isnan(x) || isnan(y) || isnan(width) || isnan(height) || isnan(minDepth) || + isnan(maxDepth)), + "A parameter of the viewport (x: %f, y: %f, width: %f, height: %f, " + "minDepth: %f, maxDepth: %f) is NaN.", + x, y, width, height, minDepth, maxDepth); + + DAWN_INVALID_IF( + x < 0 || y < 0 || width < 0 || height < 0, + "Viewport bounds (x: %f, y: %f, width: %f, height: %f) contains a negative " + "value.", + x, y, width, height); + + DAWN_INVALID_IF( + x + width > mRenderTargetWidth || y + height > mRenderTargetHeight, + "Viewport bounds (x: %f, y: %f, width: %f, height: %f) are not contained " + "in " + "the render target dimensions (%u x %u).", + x, y, width, height, mRenderTargetWidth, mRenderTargetHeight); + + // Check for depths being in [0, 1] and min <= max in 3 checks instead of 5. + DAWN_INVALID_IF(minDepth < 0 || minDepth > maxDepth || maxDepth > 1, + "Viewport minDepth (%f) and maxDepth (%f) are not in [0, 1] or " + "minDepth was " + "greater than maxDepth.", + minDepth, maxDepth); + } + + SetViewportCmd* cmd = allocator->Allocate<SetViewportCmd>(Command::SetViewport); + cmd->x = x; + cmd->y = y; + cmd->width = width; + cmd->height = height; + cmd->minDepth = minDepth; + cmd->maxDepth = maxDepth; + + return {}; + }, + "encoding %s.SetViewport(%f, %f, %f, %f, %f, %f).", this, x, y, width, height, minDepth, + maxDepth); + } + + void RenderPassEncoder::APISetScissorRect(uint32_t x, + uint32_t y, + uint32_t width, + uint32_t height) { + mEncodingContext->TryEncode( + this, + [&](CommandAllocator* allocator) -> MaybeError { + if (IsValidationEnabled()) { + DAWN_INVALID_IF( + width > mRenderTargetWidth || height > mRenderTargetHeight || + x > mRenderTargetWidth - width || y > mRenderTargetHeight - height, + "Scissor rect (x: %u, y: %u, width: %u, height: %u) is not contained in " + "the render target dimensions (%u x %u).", + x, y, width, height, mRenderTargetWidth, mRenderTargetHeight); + } + + SetScissorRectCmd* cmd = + allocator->Allocate<SetScissorRectCmd>(Command::SetScissorRect); + cmd->x = x; + cmd->y = y; + cmd->width = width; + cmd->height = height; + + return {}; + }, + "encoding %s.SetScissorRect(%u, %u, %u, %u).", this, x, y, width, height); + } + + void RenderPassEncoder::APIExecuteBundles(uint32_t count, + RenderBundleBase* const* renderBundles) { + mEncodingContext->TryEncode( + this, + [&](CommandAllocator* allocator) -> MaybeError { + if (IsValidationEnabled()) { + const AttachmentState* attachmentState = GetAttachmentState(); + bool depthReadOnlyInPass = IsDepthReadOnly(); + bool stencilReadOnlyInPass = IsStencilReadOnly(); + for (uint32_t i = 0; i < count; ++i) { + DAWN_TRY(GetDevice()->ValidateObject(renderBundles[i])); + + DAWN_INVALID_IF(attachmentState != renderBundles[i]->GetAttachmentState(), + "Attachment state of renderBundles[%i] (%s) is not " + "compatible with %s.\n" + "%s expects an attachment state of %s.\n" + "renderBundles[%i] (%s) has an attachment state of %s.", + i, renderBundles[i], this, this, attachmentState, i, + renderBundles[i], renderBundles[i]->GetAttachmentState()); + + bool depthReadOnlyInBundle = renderBundles[i]->IsDepthReadOnly(); + DAWN_INVALID_IF( + depthReadOnlyInPass && !depthReadOnlyInBundle, + "DepthReadOnly (%u) of renderBundle[%i] (%s) is not compatible " + "with DepthReadOnly (%u) of %s.", + depthReadOnlyInBundle, i, renderBundles[i], depthReadOnlyInPass, this); + + bool stencilReadOnlyInBundle = renderBundles[i]->IsStencilReadOnly(); + DAWN_INVALID_IF(stencilReadOnlyInPass && !stencilReadOnlyInBundle, + "StencilReadOnly (%u) of renderBundle[%i] (%s) is not " + "compatible with StencilReadOnly (%u) of %s.", + stencilReadOnlyInBundle, i, renderBundles[i], + stencilReadOnlyInPass, this); + } + } + + mCommandBufferState = CommandBufferStateTracker{}; + + ExecuteBundlesCmd* cmd = + allocator->Allocate<ExecuteBundlesCmd>(Command::ExecuteBundles); + cmd->count = count; + + Ref<RenderBundleBase>* bundles = + allocator->AllocateData<Ref<RenderBundleBase>>(count); + for (uint32_t i = 0; i < count; ++i) { + bundles[i] = renderBundles[i]; + + const RenderPassResourceUsage& usages = bundles[i]->GetResourceUsage(); + for (uint32_t i = 0; i < usages.buffers.size(); ++i) { + mUsageTracker.BufferUsedAs(usages.buffers[i], usages.bufferUsages[i]); + } + + for (uint32_t i = 0; i < usages.textures.size(); ++i) { + mUsageTracker.AddRenderBundleTextureUsage(usages.textures[i], + usages.textureUsages[i]); + } + + if (IsValidationEnabled()) { + mIndirectDrawMetadata.AddBundle(renderBundles[i]); + } + } + + return {}; + }, + "encoding %s.ExecuteBundles(%u, ...).", this, count); + } + + void RenderPassEncoder::APIBeginOcclusionQuery(uint32_t queryIndex) { + mEncodingContext->TryEncode( + this, + [&](CommandAllocator* allocator) -> MaybeError { + if (IsValidationEnabled()) { + DAWN_INVALID_IF(mOcclusionQuerySet.Get() == nullptr, + "The occlusionQuerySet in RenderPassDescriptor is not set."); + + // The type of querySet has been validated by ValidateRenderPassDescriptor + + DAWN_INVALID_IF(queryIndex >= mOcclusionQuerySet->GetQueryCount(), + "Query index (%u) exceeds the number of queries (%u) in %s.", + queryIndex, mOcclusionQuerySet->GetQueryCount(), + mOcclusionQuerySet.Get()); + + DAWN_INVALID_IF(mOcclusionQueryActive, + "An occlusion query (%u) in %s is already active.", + mCurrentOcclusionQueryIndex, mOcclusionQuerySet.Get()); + + DAWN_TRY_CONTEXT( + ValidateQueryIndexOverwrite(mOcclusionQuerySet.Get(), queryIndex, + mUsageTracker.GetQueryAvailabilityMap()), + "validating the occlusion query index (%u) in %s", queryIndex, + mOcclusionQuerySet.Get()); + } + + // Record the current query index for endOcclusionQuery. + mCurrentOcclusionQueryIndex = queryIndex; + mOcclusionQueryActive = true; + + BeginOcclusionQueryCmd* cmd = + allocator->Allocate<BeginOcclusionQueryCmd>(Command::BeginOcclusionQuery); + cmd->querySet = mOcclusionQuerySet.Get(); + cmd->queryIndex = queryIndex; + + return {}; + }, + "encoding %s.BeginOcclusionQuery(%u).", this, queryIndex); + } + + void RenderPassEncoder::APIEndOcclusionQuery() { + mEncodingContext->TryEncode( + this, + [&](CommandAllocator* allocator) -> MaybeError { + if (IsValidationEnabled()) { + DAWN_INVALID_IF(!mOcclusionQueryActive, "No occlusion queries are active."); + } + + TrackQueryAvailability(mOcclusionQuerySet.Get(), mCurrentOcclusionQueryIndex); + + mOcclusionQueryActive = false; + + EndOcclusionQueryCmd* cmd = + allocator->Allocate<EndOcclusionQueryCmd>(Command::EndOcclusionQuery); + cmd->querySet = mOcclusionQuerySet.Get(); + cmd->queryIndex = mCurrentOcclusionQueryIndex; + + return {}; + }, + "encoding %s.EndOcclusionQuery().", this); + } + + void RenderPassEncoder::APIWriteTimestamp(QuerySetBase* querySet, uint32_t queryIndex) { + mEncodingContext->TryEncode( + this, + [&](CommandAllocator* allocator) -> MaybeError { + if (IsValidationEnabled()) { + DAWN_TRY(ValidateTimestampQuery(GetDevice(), querySet, queryIndex)); + DAWN_TRY_CONTEXT( + ValidateQueryIndexOverwrite(querySet, queryIndex, + mUsageTracker.GetQueryAvailabilityMap()), + "validating the timestamp query index (%u) of %s", queryIndex, querySet); + } + + TrackQueryAvailability(querySet, queryIndex); + + WriteTimestampCmd* cmd = + allocator->Allocate<WriteTimestampCmd>(Command::WriteTimestamp); + cmd->querySet = querySet; + cmd->queryIndex = queryIndex; + + return {}; + }, + "encoding %s.WriteTimestamp(%s, %u).", this, querySet, queryIndex); + } + +} // namespace dawn::native
diff --git a/src/dawn/native/RenderPassEncoder.h b/src/dawn/native/RenderPassEncoder.h new file mode 100644 index 0000000..970af73 --- /dev/null +++ b/src/dawn/native/RenderPassEncoder.h
@@ -0,0 +1,103 @@ +// Copyright 2018 The Dawn Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#ifndef DAWNNATIVE_RENDERPASSENCODER_H_ +#define DAWNNATIVE_RENDERPASSENCODER_H_ + +#include "dawn/native/Error.h" +#include "dawn/native/Forward.h" +#include "dawn/native/RenderEncoderBase.h" + +namespace dawn::native { + + class RenderBundleBase; + + class RenderPassEncoder final : public RenderEncoderBase { + public: + static Ref<RenderPassEncoder> Create(DeviceBase* device, + const RenderPassDescriptor* descriptor, + CommandEncoder* commandEncoder, + EncodingContext* encodingContext, + RenderPassResourceUsageTracker usageTracker, + Ref<AttachmentState> attachmentState, + std::vector<TimestampWrite> timestampWritesAtEnd, + uint32_t renderTargetWidth, + uint32_t renderTargetHeight, + bool depthReadOnly, + bool stencilReadOnly); + static Ref<RenderPassEncoder> MakeError(DeviceBase* device, + CommandEncoder* commandEncoder, + EncodingContext* encodingContext); + + ObjectType GetType() const override; + + void APIEnd(); + void APIEndPass(); // TODO(dawn:1286): Remove after deprecation period. + + void APISetStencilReference(uint32_t reference); + void APISetBlendConstant(const Color* color); + void APISetViewport(float x, + float y, + float width, + float height, + float minDepth, + float maxDepth); + void APISetScissorRect(uint32_t x, uint32_t y, uint32_t width, uint32_t height); + void APIExecuteBundles(uint32_t count, RenderBundleBase* const* renderBundles); + + void APIBeginOcclusionQuery(uint32_t queryIndex); + void APIEndOcclusionQuery(); + + void APIWriteTimestamp(QuerySetBase* querySet, uint32_t queryIndex); + + protected: + RenderPassEncoder(DeviceBase* device, + const RenderPassDescriptor* descriptor, + CommandEncoder* commandEncoder, + EncodingContext* encodingContext, + RenderPassResourceUsageTracker usageTracker, + Ref<AttachmentState> attachmentState, + std::vector<TimestampWrite> timestampWritesAtEnd, + uint32_t renderTargetWidth, + uint32_t renderTargetHeight, + bool depthReadOnly, + bool stencilReadOnly); + RenderPassEncoder(DeviceBase* device, + CommandEncoder* commandEncoder, + EncodingContext* encodingContext, + ErrorTag errorTag); + + private: + void DestroyImpl() override; + + void TrackQueryAvailability(QuerySetBase* querySet, uint32_t queryIndex); + + // For render and compute passes, the encoding context is borrowed from the command encoder. + // Keep a reference to the encoder to make sure the context isn't freed. + Ref<CommandEncoder> mCommandEncoder; + + uint32_t mRenderTargetWidth; + uint32_t mRenderTargetHeight; + + // The resources for occlusion query + Ref<QuerySetBase> mOcclusionQuerySet; + uint32_t mCurrentOcclusionQueryIndex = 0; + bool mOcclusionQueryActive = false; + + std::vector<TimestampWrite> mTimestampWritesAtEnd; + }; + +} // namespace dawn::native + +#endif // DAWNNATIVE_RENDERPASSENCODER_H_
diff --git a/src/dawn/native/RenderPipeline.cpp b/src/dawn/native/RenderPipeline.cpp new file mode 100644 index 0000000..8af3554 --- /dev/null +++ b/src/dawn/native/RenderPipeline.cpp
@@ -0,0 +1,1014 @@ +// Copyright 2017 The Dawn Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include "dawn/native/RenderPipeline.h" + +#include "dawn/common/BitSetIterator.h" +#include "dawn/native/ChainUtils_autogen.h" +#include "dawn/native/Commands.h" +#include "dawn/native/Device.h" +#include "dawn/native/InternalPipelineStore.h" +#include "dawn/native/ObjectContentHasher.h" +#include "dawn/native/ObjectType_autogen.h" +#include "dawn/native/ValidationUtils_autogen.h" +#include "dawn/native/VertexFormat.h" + +#include <cmath> +#include <sstream> + +namespace dawn::native { + + // Helper functions + namespace { + MaybeError ValidateVertexAttribute( + DeviceBase* device, + const VertexAttribute* attribute, + const EntryPointMetadata& metadata, + uint64_t vertexBufferStride, + ityp::bitset<VertexAttributeLocation, kMaxVertexAttributes>* attributesSetMask) { + DAWN_TRY(ValidateVertexFormat(attribute->format)); + const VertexFormatInfo& formatInfo = GetVertexFormatInfo(attribute->format); + + DAWN_INVALID_IF( + attribute->shaderLocation >= kMaxVertexAttributes, + "Attribute shader location (%u) exceeds the maximum number of vertex attributes " + "(%u).", + attribute->shaderLocation, kMaxVertexAttributes); + + VertexAttributeLocation location(static_cast<uint8_t>(attribute->shaderLocation)); + + // No underflow is possible because the max vertex format size is smaller than + // kMaxVertexBufferArrayStride. + ASSERT(kMaxVertexBufferArrayStride >= formatInfo.byteSize); + DAWN_INVALID_IF( + attribute->offset > kMaxVertexBufferArrayStride - formatInfo.byteSize, + "Attribute offset (%u) with format %s (size: %u) doesn't fit in the maximum vertex " + "buffer stride (%u).", + attribute->offset, attribute->format, formatInfo.byteSize, + kMaxVertexBufferArrayStride); + + // No overflow is possible because the offset is already validated to be less + // than kMaxVertexBufferArrayStride. + ASSERT(attribute->offset < kMaxVertexBufferArrayStride); + DAWN_INVALID_IF( + vertexBufferStride > 0 && + attribute->offset + formatInfo.byteSize > vertexBufferStride, + "Attribute offset (%u) with format %s (size: %u) doesn't fit in the vertex buffer " + "stride (%u).", + attribute->offset, attribute->format, formatInfo.byteSize, vertexBufferStride); + + DAWN_INVALID_IF(attribute->offset % std::min(4u, formatInfo.byteSize) != 0, + "Attribute offset (%u) in not a multiple of %u.", attribute->offset, + std::min(4u, formatInfo.byteSize)); + + DAWN_INVALID_IF(metadata.usedVertexInputs[location] && + formatInfo.baseType != metadata.vertexInputBaseTypes[location], + "Attribute base type (%s) does not match the " + "shader's base type (%s) in location (%u).", + formatInfo.baseType, metadata.vertexInputBaseTypes[location], + attribute->shaderLocation); + + DAWN_INVALID_IF((*attributesSetMask)[location], + "Attribute shader location (%u) is used more than once.", + attribute->shaderLocation); + + attributesSetMask->set(location); + return {}; + } + + MaybeError ValidateVertexBufferLayout( + DeviceBase* device, + const VertexBufferLayout* buffer, + const EntryPointMetadata& metadata, + ityp::bitset<VertexAttributeLocation, kMaxVertexAttributes>* attributesSetMask) { + DAWN_TRY(ValidateVertexStepMode(buffer->stepMode)); + DAWN_INVALID_IF( + buffer->arrayStride > kMaxVertexBufferArrayStride, + "Vertex buffer arrayStride (%u) is larger than the maximum array stride (%u).", + buffer->arrayStride, kMaxVertexBufferArrayStride); + + DAWN_INVALID_IF(buffer->arrayStride % 4 != 0, + "Vertex buffer arrayStride (%u) is not a multiple of 4.", + buffer->arrayStride); + + for (uint32_t i = 0; i < buffer->attributeCount; ++i) { + DAWN_TRY_CONTEXT(ValidateVertexAttribute(device, &buffer->attributes[i], metadata, + buffer->arrayStride, attributesSetMask), + "validating attributes[%u].", i); + } + + return {}; + } + + MaybeError ValidateVertexState(DeviceBase* device, + const VertexState* descriptor, + const PipelineLayoutBase* layout) { + DAWN_INVALID_IF(descriptor->nextInChain != nullptr, "nextInChain must be nullptr."); + + DAWN_INVALID_IF( + descriptor->bufferCount > kMaxVertexBuffers, + "Vertex buffer count (%u) exceeds the maximum number of vertex buffers (%u).", + descriptor->bufferCount, kMaxVertexBuffers); + + DAWN_TRY_CONTEXT( + ValidateProgrammableStage(device, descriptor->module, descriptor->entryPoint, + descriptor->constantCount, descriptor->constants, layout, + SingleShaderStage::Vertex), + "validating vertex stage (module: %s, entryPoint: %s).", descriptor->module, + descriptor->entryPoint); + const EntryPointMetadata& vertexMetadata = + descriptor->module->GetEntryPoint(descriptor->entryPoint); + + ityp::bitset<VertexAttributeLocation, kMaxVertexAttributes> attributesSetMask; + uint32_t totalAttributesNum = 0; + for (uint32_t i = 0; i < descriptor->bufferCount; ++i) { + DAWN_TRY_CONTEXT(ValidateVertexBufferLayout(device, &descriptor->buffers[i], + vertexMetadata, &attributesSetMask), + "validating buffers[%u].", i); + totalAttributesNum += descriptor->buffers[i].attributeCount; + } + + // Every vertex attribute has a member called shaderLocation, and there are some + // requirements for shaderLocation: 1) >=0, 2) values are different across different + // attributes, 3) can't exceed kMaxVertexAttributes. So it can ensure that total + // attribute number never exceed kMaxVertexAttributes. + ASSERT(totalAttributesNum <= kMaxVertexAttributes); + + // TODO(dawn:563): Specify which inputs were not used in error message. + DAWN_INVALID_IF(!IsSubset(vertexMetadata.usedVertexInputs, attributesSetMask), + "Pipeline vertex stage uses vertex buffers not in the vertex state"); + + return {}; + } + + MaybeError ValidatePrimitiveState(const DeviceBase* device, + const PrimitiveState* descriptor) { + DAWN_TRY(ValidateSingleSType(descriptor->nextInChain, + wgpu::SType::PrimitiveDepthClampingState)); + const PrimitiveDepthClampingState* clampInfo = nullptr; + FindInChain(descriptor->nextInChain, &clampInfo); + if (clampInfo && !device->IsFeatureEnabled(Feature::DepthClamping)) { + return DAWN_VALIDATION_ERROR("The depth clamping feature is not supported"); + } + DAWN_TRY(ValidatePrimitiveTopology(descriptor->topology)); + DAWN_TRY(ValidateIndexFormat(descriptor->stripIndexFormat)); + DAWN_TRY(ValidateFrontFace(descriptor->frontFace)); + DAWN_TRY(ValidateCullMode(descriptor->cullMode)); + + // Pipeline descriptors must have stripIndexFormat == undefined if they are using + // non-strip topologies. + if (!IsStripPrimitiveTopology(descriptor->topology)) { + DAWN_INVALID_IF( + descriptor->stripIndexFormat != wgpu::IndexFormat::Undefined, + "StripIndexFormat (%s) is not undefined when using a non-strip primitive " + "topology (%s).", + descriptor->stripIndexFormat, descriptor->topology); + } + + return {}; + } + + MaybeError ValidateDepthStencilState(const DeviceBase* device, + const DepthStencilState* descriptor) { + if (descriptor->nextInChain != nullptr) { + return DAWN_VALIDATION_ERROR("nextInChain must be nullptr"); + } + + DAWN_TRY(ValidateCompareFunction(descriptor->depthCompare)); + DAWN_TRY(ValidateCompareFunction(descriptor->stencilFront.compare)); + DAWN_TRY(ValidateStencilOperation(descriptor->stencilFront.failOp)); + DAWN_TRY(ValidateStencilOperation(descriptor->stencilFront.depthFailOp)); + DAWN_TRY(ValidateStencilOperation(descriptor->stencilFront.passOp)); + DAWN_TRY(ValidateCompareFunction(descriptor->stencilBack.compare)); + DAWN_TRY(ValidateStencilOperation(descriptor->stencilBack.failOp)); + DAWN_TRY(ValidateStencilOperation(descriptor->stencilBack.depthFailOp)); + DAWN_TRY(ValidateStencilOperation(descriptor->stencilBack.passOp)); + + const Format* format; + DAWN_TRY_ASSIGN(format, device->GetInternalFormat(descriptor->format)); + DAWN_INVALID_IF(!format->HasDepthOrStencil() || !format->isRenderable, + "Depth stencil format (%s) is not depth-stencil renderable.", + descriptor->format); + + DAWN_INVALID_IF(std::isnan(descriptor->depthBiasSlopeScale) || + std::isnan(descriptor->depthBiasClamp), + "Either depthBiasSlopeScale (%f) or depthBiasClamp (%f) is NaN.", + descriptor->depthBiasSlopeScale, descriptor->depthBiasClamp); + + DAWN_INVALID_IF( + !format->HasDepth() && (descriptor->depthCompare != wgpu::CompareFunction::Always || + descriptor->depthWriteEnabled), + "Depth stencil format (%s) doesn't have depth aspect while depthCompare (%s) is " + "not %s or depthWriteEnabled (%u) is true.", + descriptor->format, descriptor->depthCompare, wgpu::CompareFunction::Always, + descriptor->depthWriteEnabled); + + DAWN_INVALID_IF(!format->HasStencil() && StencilTestEnabled(descriptor), + "Depth stencil format (%s) doesn't have stencil aspect while stencil " + "test or stencil write is enabled.", + descriptor->format); + + return {}; + } + + MaybeError ValidateMultisampleState(const MultisampleState* descriptor) { + DAWN_INVALID_IF(descriptor->nextInChain != nullptr, "nextInChain must be nullptr."); + + DAWN_INVALID_IF(!IsValidSampleCount(descriptor->count), + "Multisample count (%u) is not supported.", descriptor->count); + + DAWN_INVALID_IF(descriptor->alphaToCoverageEnabled && descriptor->count <= 1, + "Multisample count (%u) must be > 1 when alphaToCoverage is enabled.", + descriptor->count); + + return {}; + } + + MaybeError ValidateBlendComponent(BlendComponent blendComponent) { + if (blendComponent.operation == wgpu::BlendOperation::Min || + blendComponent.operation == wgpu::BlendOperation::Max) { + DAWN_INVALID_IF(blendComponent.srcFactor != wgpu::BlendFactor::One || + blendComponent.dstFactor != wgpu::BlendFactor::One, + "Blend factor is not %s when blend operation is %s.", + wgpu::BlendFactor::One, blendComponent.operation); + } + + return {}; + } + + MaybeError ValidateBlendState(DeviceBase* device, const BlendState* descriptor) { + DAWN_TRY(ValidateBlendOperation(descriptor->alpha.operation)); + DAWN_TRY(ValidateBlendFactor(descriptor->alpha.srcFactor)); + DAWN_TRY(ValidateBlendFactor(descriptor->alpha.dstFactor)); + DAWN_TRY(ValidateBlendOperation(descriptor->color.operation)); + DAWN_TRY(ValidateBlendFactor(descriptor->color.srcFactor)); + DAWN_TRY(ValidateBlendFactor(descriptor->color.dstFactor)); + DAWN_TRY(ValidateBlendComponent(descriptor->alpha)); + DAWN_TRY(ValidateBlendComponent(descriptor->color)); + + return {}; + } + + bool BlendFactorContainsSrcAlpha(const wgpu::BlendFactor& blendFactor) { + return blendFactor == wgpu::BlendFactor::SrcAlpha || + blendFactor == wgpu::BlendFactor::OneMinusSrcAlpha || + blendFactor == wgpu::BlendFactor::SrcAlphaSaturated; + } + + MaybeError ValidateColorTargetState( + DeviceBase* device, + const ColorTargetState* descriptor, + bool fragmentWritten, + const EntryPointMetadata::FragmentOutputVariableInfo& fragmentOutputVariable) { + DAWN_INVALID_IF(descriptor->nextInChain != nullptr, "nextInChain must be nullptr."); + + if (descriptor->blend) { + DAWN_TRY_CONTEXT(ValidateBlendState(device, descriptor->blend), + "validating blend state."); + } + + DAWN_TRY(ValidateColorWriteMask(descriptor->writeMask)); + + const Format* format; + DAWN_TRY_ASSIGN(format, device->GetInternalFormat(descriptor->format)); + DAWN_INVALID_IF(!format->IsColor() || !format->isRenderable, + "Color format (%s) is not color renderable.", descriptor->format); + + DAWN_INVALID_IF( + descriptor->blend && !(format->GetAspectInfo(Aspect::Color).supportedSampleTypes & + SampleTypeBit::Float), + "Blending is enabled but color format (%s) is not blendable.", descriptor->format); + + if (fragmentWritten) { + DAWN_INVALID_IF(fragmentOutputVariable.baseType != + format->GetAspectInfo(Aspect::Color).baseType, + "Color format (%s) base type (%s) doesn't match the fragment " + "module output type (%s).", + descriptor->format, format->GetAspectInfo(Aspect::Color).baseType, + fragmentOutputVariable.baseType); + + DAWN_INVALID_IF( + fragmentOutputVariable.componentCount < format->componentCount, + "The fragment stage has fewer output components (%u) than the color format " + "(%s) component count (%u).", + fragmentOutputVariable.componentCount, descriptor->format, + format->componentCount); + + if (descriptor->blend) { + if (fragmentOutputVariable.componentCount < 4u) { + // No alpha channel output + // Make sure there's no alpha involved in the blending operation + DAWN_INVALID_IF( + BlendFactorContainsSrcAlpha(descriptor->blend->color.srcFactor) || + BlendFactorContainsSrcAlpha(descriptor->blend->color.dstFactor), + "Color blending srcfactor (%s) or dstFactor (%s) is reading alpha " + "but it is missing from fragment output.", + descriptor->blend->color.srcFactor, descriptor->blend->color.dstFactor); + } + } + } else { + DAWN_INVALID_IF( + descriptor->writeMask != wgpu::ColorWriteMask::None, + "Color target has no corresponding fragment stage output but writeMask (%s) is " + "not zero.", + descriptor->writeMask); + } + + return {}; + } + + MaybeError ValidateFragmentState(DeviceBase* device, + const FragmentState* descriptor, + const PipelineLayoutBase* layout) { + DAWN_INVALID_IF(descriptor->nextInChain != nullptr, "nextInChain must be nullptr."); + + DAWN_TRY_CONTEXT( + ValidateProgrammableStage(device, descriptor->module, descriptor->entryPoint, + descriptor->constantCount, descriptor->constants, layout, + SingleShaderStage::Fragment), + "validating fragment stage (module: %s, entryPoint: %s).", descriptor->module, + descriptor->entryPoint); + + DAWN_INVALID_IF(descriptor->targetCount > kMaxColorAttachments, + "Number of targets (%u) exceeds the maximum (%u).", + descriptor->targetCount, kMaxColorAttachments); + + const EntryPointMetadata& fragmentMetadata = + descriptor->module->GetEntryPoint(descriptor->entryPoint); + for (ColorAttachmentIndex i(uint8_t(0)); + i < ColorAttachmentIndex(static_cast<uint8_t>(descriptor->targetCount)); ++i) { + const ColorTargetState* target = &descriptor->targets[static_cast<uint8_t>(i)]; + if (target->format != wgpu::TextureFormat::Undefined) { + DAWN_TRY_CONTEXT(ValidateColorTargetState( + device, target, fragmentMetadata.fragmentOutputsWritten[i], + fragmentMetadata.fragmentOutputVariables[i]), + "validating targets[%u].", static_cast<uint8_t>(i)); + } else { + DAWN_INVALID_IF( + target->blend, + "Color target[%u] blend state is set when the format is undefined.", + static_cast<uint8_t>(i)); + DAWN_INVALID_IF( + target->writeMask != wgpu::ColorWriteMask::None, + "Color target[%u] write mask is set to (%s) when the format is undefined.", + static_cast<uint8_t>(i), target->writeMask); + } + } + + return {}; + } + + MaybeError ValidateInterStageMatching(DeviceBase* device, + const VertexState& vertexState, + const FragmentState& fragmentState) { + const EntryPointMetadata& vertexMetadata = + vertexState.module->GetEntryPoint(vertexState.entryPoint); + const EntryPointMetadata& fragmentMetadata = + fragmentState.module->GetEntryPoint(fragmentState.entryPoint); + + // TODO(dawn:563): Can this message give more details? + DAWN_INVALID_IF( + vertexMetadata.usedInterStageVariables != fragmentMetadata.usedInterStageVariables, + "One or more fragment inputs and vertex outputs are not one-to-one matching"); + + // TODO(dawn:802): Validate interpolation types and interpolition sampling types + for (size_t i : IterateBitSet(vertexMetadata.usedInterStageVariables)) { + const auto& vertexOutputInfo = vertexMetadata.interStageVariables[i]; + const auto& fragmentInputInfo = fragmentMetadata.interStageVariables[i]; + DAWN_INVALID_IF( + vertexOutputInfo.baseType != fragmentInputInfo.baseType, + "The base type (%s) of the vertex output at location %u is different from the " + "base type (%s) of the fragment input at location %u.", + vertexOutputInfo.baseType, i, fragmentInputInfo.baseType, i); + + DAWN_INVALID_IF( + vertexOutputInfo.componentCount != fragmentInputInfo.componentCount, + "The component count (%u) of the vertex output at location %u is different " + "from the component count (%u) of the fragment input at location %u.", + vertexOutputInfo.componentCount, i, fragmentInputInfo.componentCount, i); + + DAWN_INVALID_IF( + vertexOutputInfo.interpolationType != fragmentInputInfo.interpolationType, + "The interpolation type (%s) of the vertex output at location %u is different " + "from the interpolation type (%s) of the fragment input at location %u.", + vertexOutputInfo.interpolationType, i, fragmentInputInfo.interpolationType, i); + + DAWN_INVALID_IF( + vertexOutputInfo.interpolationSampling != + fragmentInputInfo.interpolationSampling, + "The interpolation sampling (%s) of the vertex output at location %u is " + "different from the interpolation sampling (%s) of the fragment input at " + "location %u.", + vertexOutputInfo.interpolationSampling, i, + fragmentInputInfo.interpolationSampling, i); + } + + return {}; + } + } // anonymous namespace + + // Helper functions + size_t IndexFormatSize(wgpu::IndexFormat format) { + switch (format) { + case wgpu::IndexFormat::Uint16: + return sizeof(uint16_t); + case wgpu::IndexFormat::Uint32: + return sizeof(uint32_t); + case wgpu::IndexFormat::Undefined: + break; + } + UNREACHABLE(); + } + + bool IsStripPrimitiveTopology(wgpu::PrimitiveTopology primitiveTopology) { + return primitiveTopology == wgpu::PrimitiveTopology::LineStrip || + primitiveTopology == wgpu::PrimitiveTopology::TriangleStrip; + } + + MaybeError ValidateRenderPipelineDescriptor(DeviceBase* device, + const RenderPipelineDescriptor* descriptor) { + DAWN_INVALID_IF(descriptor->nextInChain != nullptr, "nextInChain must be nullptr."); + + if (descriptor->layout != nullptr) { + DAWN_TRY(device->ValidateObject(descriptor->layout)); + } + + DAWN_TRY_CONTEXT(ValidateVertexState(device, &descriptor->vertex, descriptor->layout), + "validating vertex state."); + + DAWN_TRY_CONTEXT(ValidatePrimitiveState(device, &descriptor->primitive), + "validating primitive state."); + + if (descriptor->depthStencil) { + DAWN_TRY_CONTEXT(ValidateDepthStencilState(device, descriptor->depthStencil), + "validating depthStencil state."); + } + + DAWN_TRY_CONTEXT(ValidateMultisampleState(&descriptor->multisample), + "validating multisample state."); + + if (descriptor->fragment != nullptr) { + DAWN_TRY_CONTEXT( + ValidateFragmentState(device, descriptor->fragment, descriptor->layout), + "validating fragment state."); + + DAWN_INVALID_IF(descriptor->fragment->targetCount == 0 && !descriptor->depthStencil, + "Must have at least one color or depthStencil target."); + + DAWN_TRY( + ValidateInterStageMatching(device, descriptor->vertex, *(descriptor->fragment))); + } + + return {}; + } + + std::vector<StageAndDescriptor> GetRenderStagesAndSetDummyShader( + DeviceBase* device, + const RenderPipelineDescriptor* descriptor) { + std::vector<StageAndDescriptor> stages; + stages.push_back({SingleShaderStage::Vertex, descriptor->vertex.module, + descriptor->vertex.entryPoint, descriptor->vertex.constantCount, + descriptor->vertex.constants}); + if (descriptor->fragment != nullptr) { + stages.push_back({SingleShaderStage::Fragment, descriptor->fragment->module, + descriptor->fragment->entryPoint, descriptor->fragment->constantCount, + descriptor->fragment->constants}); + } else if (device->IsToggleEnabled(Toggle::UseDummyFragmentInVertexOnlyPipeline)) { + InternalPipelineStore* store = device->GetInternalPipelineStore(); + // The dummy fragment shader module should already be initialized + DAWN_ASSERT(store->dummyFragmentShader != nullptr); + ShaderModuleBase* dummyFragmentShader = store->dummyFragmentShader.Get(); + stages.push_back( + {SingleShaderStage::Fragment, dummyFragmentShader, "fs_empty_main", 0, nullptr}); + } + return stages; + } + + bool StencilTestEnabled(const DepthStencilState* depthStencil) { + return depthStencil->stencilBack.compare != wgpu::CompareFunction::Always || + depthStencil->stencilBack.failOp != wgpu::StencilOperation::Keep || + depthStencil->stencilBack.depthFailOp != wgpu::StencilOperation::Keep || + depthStencil->stencilBack.passOp != wgpu::StencilOperation::Keep || + depthStencil->stencilFront.compare != wgpu::CompareFunction::Always || + depthStencil->stencilFront.failOp != wgpu::StencilOperation::Keep || + depthStencil->stencilFront.depthFailOp != wgpu::StencilOperation::Keep || + depthStencil->stencilFront.passOp != wgpu::StencilOperation::Keep; + } + + // RenderPipelineBase + + RenderPipelineBase::RenderPipelineBase(DeviceBase* device, + const RenderPipelineDescriptor* descriptor) + : PipelineBase(device, + descriptor->layout, + descriptor->label, + GetRenderStagesAndSetDummyShader(device, descriptor)), + mAttachmentState(device->GetOrCreateAttachmentState(descriptor)) { + mVertexBufferCount = descriptor->vertex.bufferCount; + const VertexBufferLayout* buffers = descriptor->vertex.buffers; + for (uint8_t slot = 0; slot < mVertexBufferCount; ++slot) { + if (buffers[slot].attributeCount == 0) { + continue; + } + + VertexBufferSlot typedSlot(slot); + + mVertexBufferSlotsUsed.set(typedSlot); + mVertexBufferInfos[typedSlot].arrayStride = buffers[slot].arrayStride; + mVertexBufferInfos[typedSlot].stepMode = buffers[slot].stepMode; + mVertexBufferInfos[typedSlot].usedBytesInStride = 0; + mVertexBufferInfos[typedSlot].lastStride = 0; + switch (buffers[slot].stepMode) { + case wgpu::VertexStepMode::Vertex: + mVertexBufferSlotsUsedAsVertexBuffer.set(typedSlot); + break; + case wgpu::VertexStepMode::Instance: + mVertexBufferSlotsUsedAsInstanceBuffer.set(typedSlot); + break; + default: + DAWN_UNREACHABLE(); + } + + for (uint32_t i = 0; i < buffers[slot].attributeCount; ++i) { + VertexAttributeLocation location = VertexAttributeLocation( + static_cast<uint8_t>(buffers[slot].attributes[i].shaderLocation)); + mAttributeLocationsUsed.set(location); + mAttributeInfos[location].shaderLocation = location; + mAttributeInfos[location].vertexBufferSlot = typedSlot; + mAttributeInfos[location].offset = buffers[slot].attributes[i].offset; + mAttributeInfos[location].format = buffers[slot].attributes[i].format; + // Compute the access boundary of this attribute by adding attribute format size to + // attribute offset. Although offset is in uint64_t, such sum must be no larger than + // maxVertexBufferArrayStride (2048), which is promised by the GPUVertexBufferLayout + // validation of creating render pipeline. Therefore, calculating in uint16_t will + // cause no overflow. + uint32_t formatByteSize = + GetVertexFormatInfo(buffers[slot].attributes[i].format).byteSize; + DAWN_ASSERT(buffers[slot].attributes[i].offset <= 2048); + uint16_t accessBoundary = + uint16_t(buffers[slot].attributes[i].offset) + uint16_t(formatByteSize); + mVertexBufferInfos[typedSlot].usedBytesInStride = + std::max(mVertexBufferInfos[typedSlot].usedBytesInStride, accessBoundary); + mVertexBufferInfos[typedSlot].lastStride = + std::max(mVertexBufferInfos[typedSlot].lastStride, + mAttributeInfos[location].offset + formatByteSize); + } + } + + mPrimitive = descriptor->primitive; + const PrimitiveDepthClampingState* clampInfo = nullptr; + FindInChain(mPrimitive.nextInChain, &clampInfo); + if (clampInfo) { + mClampDepth = clampInfo->clampDepth; + } + mMultisample = descriptor->multisample; + + if (mAttachmentState->HasDepthStencilAttachment()) { + mDepthStencil = *descriptor->depthStencil; + mWritesDepth = mDepthStencil.depthWriteEnabled; + if (mDepthStencil.stencilWriteMask) { + if ((mPrimitive.cullMode != wgpu::CullMode::Front && + (mDepthStencil.stencilFront.failOp != wgpu::StencilOperation::Keep || + mDepthStencil.stencilFront.depthFailOp != wgpu::StencilOperation::Keep || + mDepthStencil.stencilFront.passOp != wgpu::StencilOperation::Keep)) || + (mPrimitive.cullMode != wgpu::CullMode::Back && + (mDepthStencil.stencilBack.failOp != wgpu::StencilOperation::Keep || + mDepthStencil.stencilBack.depthFailOp != wgpu::StencilOperation::Keep || + mDepthStencil.stencilBack.passOp != wgpu::StencilOperation::Keep))) { + mWritesStencil = true; + } + } + } else { + // These default values below are useful for backends to fill information. + // The values indicate that depth and stencil test are disabled when backends + // set their own depth stencil states/descriptors according to the values in + // mDepthStencil. + mDepthStencil.format = wgpu::TextureFormat::Undefined; + mDepthStencil.depthWriteEnabled = false; + mDepthStencil.depthCompare = wgpu::CompareFunction::Always; + mDepthStencil.stencilBack.compare = wgpu::CompareFunction::Always; + mDepthStencil.stencilBack.failOp = wgpu::StencilOperation::Keep; + mDepthStencil.stencilBack.depthFailOp = wgpu::StencilOperation::Keep; + mDepthStencil.stencilBack.passOp = wgpu::StencilOperation::Keep; + mDepthStencil.stencilFront.compare = wgpu::CompareFunction::Always; + mDepthStencil.stencilFront.failOp = wgpu::StencilOperation::Keep; + mDepthStencil.stencilFront.depthFailOp = wgpu::StencilOperation::Keep; + mDepthStencil.stencilFront.passOp = wgpu::StencilOperation::Keep; + mDepthStencil.stencilReadMask = 0xff; + mDepthStencil.stencilWriteMask = 0xff; + mDepthStencil.depthBias = 0; + mDepthStencil.depthBiasSlopeScale = 0.0f; + mDepthStencil.depthBiasClamp = 0.0f; + } + + for (ColorAttachmentIndex i : IterateBitSet(mAttachmentState->GetColorAttachmentsMask())) { + // Vertex-only render pipeline have no color attachment. For a render pipeline with + // color attachments, there must be a valid FragmentState. + ASSERT(descriptor->fragment != nullptr); + const ColorTargetState* target = + &descriptor->fragment->targets[static_cast<uint8_t>(i)]; + mTargets[i] = *target; + + if (target->blend != nullptr) { + mTargetBlend[i] = *target->blend; + mTargets[i].blend = &mTargetBlend[i]; + } + } + + SetContentHash(ComputeContentHash()); + TrackInDevice(); + } + + RenderPipelineBase::RenderPipelineBase(DeviceBase* device) : PipelineBase(device) { + TrackInDevice(); + } + + RenderPipelineBase::RenderPipelineBase(DeviceBase* device, ObjectBase::ErrorTag tag) + : PipelineBase(device, tag) { + } + + RenderPipelineBase::~RenderPipelineBase() = default; + + void RenderPipelineBase::DestroyImpl() { + if (IsCachedReference()) { + // Do not uncache the actual cached object if we are a blueprint. + GetDevice()->UncacheRenderPipeline(this); + } + + // Remove reference to the attachment state so that we don't have lingering references to + // it preventing it from being uncached in the device. + mAttachmentState = nullptr; + } + + // static + RenderPipelineBase* RenderPipelineBase::MakeError(DeviceBase* device) { + class ErrorRenderPipeline final : public RenderPipelineBase { + public: + ErrorRenderPipeline(DeviceBase* device) + : RenderPipelineBase(device, ObjectBase::kError) { + } + + MaybeError Initialize() override { + UNREACHABLE(); + return {}; + } + }; + + return new ErrorRenderPipeline(device); + } + + ObjectType RenderPipelineBase::GetType() const { + return ObjectType::RenderPipeline; + } + + const ityp::bitset<VertexAttributeLocation, kMaxVertexAttributes>& + RenderPipelineBase::GetAttributeLocationsUsed() const { + ASSERT(!IsError()); + return mAttributeLocationsUsed; + } + + const VertexAttributeInfo& RenderPipelineBase::GetAttribute( + VertexAttributeLocation location) const { + ASSERT(!IsError()); + ASSERT(mAttributeLocationsUsed[location]); + return mAttributeInfos[location]; + } + + const ityp::bitset<VertexBufferSlot, kMaxVertexBuffers>& + RenderPipelineBase::GetVertexBufferSlotsUsed() const { + ASSERT(!IsError()); + return mVertexBufferSlotsUsed; + } + + const ityp::bitset<VertexBufferSlot, kMaxVertexBuffers>& + RenderPipelineBase::GetVertexBufferSlotsUsedAsVertexBuffer() const { + ASSERT(!IsError()); + return mVertexBufferSlotsUsedAsVertexBuffer; + } + + const ityp::bitset<VertexBufferSlot, kMaxVertexBuffers>& + RenderPipelineBase::GetVertexBufferSlotsUsedAsInstanceBuffer() const { + ASSERT(!IsError()); + return mVertexBufferSlotsUsedAsInstanceBuffer; + } + + const VertexBufferInfo& RenderPipelineBase::GetVertexBuffer(VertexBufferSlot slot) const { + ASSERT(!IsError()); + ASSERT(mVertexBufferSlotsUsed[slot]); + return mVertexBufferInfos[slot]; + } + + uint32_t RenderPipelineBase::GetVertexBufferCount() const { + ASSERT(!IsError()); + return mVertexBufferCount; + } + + const ColorTargetState* RenderPipelineBase::GetColorTargetState( + ColorAttachmentIndex attachmentSlot) const { + ASSERT(!IsError()); + ASSERT(attachmentSlot < mTargets.size()); + return &mTargets[attachmentSlot]; + } + + const DepthStencilState* RenderPipelineBase::GetDepthStencilState() const { + ASSERT(!IsError()); + return &mDepthStencil; + } + + wgpu::PrimitiveTopology RenderPipelineBase::GetPrimitiveTopology() const { + ASSERT(!IsError()); + return mPrimitive.topology; + } + + wgpu::IndexFormat RenderPipelineBase::GetStripIndexFormat() const { + ASSERT(!IsError()); + return mPrimitive.stripIndexFormat; + } + + wgpu::CullMode RenderPipelineBase::GetCullMode() const { + ASSERT(!IsError()); + return mPrimitive.cullMode; + } + + wgpu::FrontFace RenderPipelineBase::GetFrontFace() const { + ASSERT(!IsError()); + return mPrimitive.frontFace; + } + + bool RenderPipelineBase::IsDepthBiasEnabled() const { + ASSERT(!IsError()); + return mDepthStencil.depthBias != 0 || mDepthStencil.depthBiasSlopeScale != 0; + } + + int32_t RenderPipelineBase::GetDepthBias() const { + ASSERT(!IsError()); + return mDepthStencil.depthBias; + } + + float RenderPipelineBase::GetDepthBiasSlopeScale() const { + ASSERT(!IsError()); + return mDepthStencil.depthBiasSlopeScale; + } + + float RenderPipelineBase::GetDepthBiasClamp() const { + ASSERT(!IsError()); + return mDepthStencil.depthBiasClamp; + } + + bool RenderPipelineBase::ShouldClampDepth() const { + ASSERT(!IsError()); + return mClampDepth; + } + + ityp::bitset<ColorAttachmentIndex, kMaxColorAttachments> + RenderPipelineBase::GetColorAttachmentsMask() const { + ASSERT(!IsError()); + return mAttachmentState->GetColorAttachmentsMask(); + } + + bool RenderPipelineBase::HasDepthStencilAttachment() const { + ASSERT(!IsError()); + return mAttachmentState->HasDepthStencilAttachment(); + } + + wgpu::TextureFormat RenderPipelineBase::GetColorAttachmentFormat( + ColorAttachmentIndex attachment) const { + ASSERT(!IsError()); + return mTargets[attachment].format; + } + + wgpu::TextureFormat RenderPipelineBase::GetDepthStencilFormat() const { + ASSERT(!IsError()); + ASSERT(mAttachmentState->HasDepthStencilAttachment()); + return mDepthStencil.format; + } + + uint32_t RenderPipelineBase::GetSampleCount() const { + ASSERT(!IsError()); + return mAttachmentState->GetSampleCount(); + } + + uint32_t RenderPipelineBase::GetSampleMask() const { + ASSERT(!IsError()); + return mMultisample.mask; + } + + bool RenderPipelineBase::IsAlphaToCoverageEnabled() const { + ASSERT(!IsError()); + return mMultisample.alphaToCoverageEnabled; + } + + const AttachmentState* RenderPipelineBase::GetAttachmentState() const { + ASSERT(!IsError()); + + return mAttachmentState.Get(); + } + + bool RenderPipelineBase::WritesDepth() const { + ASSERT(!IsError()); + + return mWritesDepth; + } + + bool RenderPipelineBase::WritesStencil() const { + ASSERT(!IsError()); + + return mWritesStencil; + } + + size_t RenderPipelineBase::ComputeContentHash() { + ObjectContentHasher recorder; + + // Record modules and layout + recorder.Record(PipelineBase::ComputeContentHash()); + + // Hierarchically record the attachment state. + // It contains the attachments set, texture formats, and sample count. + recorder.Record(mAttachmentState->GetContentHash()); + + // Record attachments + for (ColorAttachmentIndex i : IterateBitSet(mAttachmentState->GetColorAttachmentsMask())) { + const ColorTargetState& desc = *GetColorTargetState(i); + recorder.Record(desc.writeMask); + if (desc.blend != nullptr) { + recorder.Record(desc.blend->color.operation, desc.blend->color.srcFactor, + desc.blend->color.dstFactor); + recorder.Record(desc.blend->alpha.operation, desc.blend->alpha.srcFactor, + desc.blend->alpha.dstFactor); + } + } + + if (mAttachmentState->HasDepthStencilAttachment()) { + const DepthStencilState& desc = mDepthStencil; + recorder.Record(desc.depthWriteEnabled, desc.depthCompare); + recorder.Record(desc.stencilReadMask, desc.stencilWriteMask); + recorder.Record(desc.stencilFront.compare, desc.stencilFront.failOp, + desc.stencilFront.depthFailOp, desc.stencilFront.passOp); + recorder.Record(desc.stencilBack.compare, desc.stencilBack.failOp, + desc.stencilBack.depthFailOp, desc.stencilBack.passOp); + recorder.Record(desc.depthBias, desc.depthBiasSlopeScale, desc.depthBiasClamp); + } + + // Record vertex state + recorder.Record(mAttributeLocationsUsed); + for (VertexAttributeLocation location : IterateBitSet(mAttributeLocationsUsed)) { + const VertexAttributeInfo& desc = GetAttribute(location); + recorder.Record(desc.shaderLocation, desc.vertexBufferSlot, desc.offset, desc.format); + } + + recorder.Record(mVertexBufferSlotsUsed); + for (VertexBufferSlot slot : IterateBitSet(mVertexBufferSlotsUsed)) { + const VertexBufferInfo& desc = GetVertexBuffer(slot); + recorder.Record(desc.arrayStride, desc.stepMode); + } + + // Record primitive state + recorder.Record(mPrimitive.topology, mPrimitive.stripIndexFormat, mPrimitive.frontFace, + mPrimitive.cullMode, mClampDepth); + + // Record multisample state + // Sample count hashed as part of the attachment state + recorder.Record(mMultisample.mask, mMultisample.alphaToCoverageEnabled); + + return recorder.GetContentHash(); + } + + bool RenderPipelineBase::EqualityFunc::operator()(const RenderPipelineBase* a, + const RenderPipelineBase* b) const { + // Check the layout and shader stages. + if (!PipelineBase::EqualForCache(a, b)) { + return false; + } + + // Check the attachment state. + // It contains the attachments set, texture formats, and sample count. + if (a->mAttachmentState.Get() != b->mAttachmentState.Get()) { + return false; + } + + if (a->mAttachmentState.Get() != nullptr) { + for (ColorAttachmentIndex i : + IterateBitSet(a->mAttachmentState->GetColorAttachmentsMask())) { + const ColorTargetState& descA = *a->GetColorTargetState(i); + const ColorTargetState& descB = *b->GetColorTargetState(i); + if (descA.writeMask != descB.writeMask) { + return false; + } + if ((descA.blend == nullptr) != (descB.blend == nullptr)) { + return false; + } + if (descA.blend != nullptr) { + if (descA.blend->color.operation != descB.blend->color.operation || + descA.blend->color.srcFactor != descB.blend->color.srcFactor || + descA.blend->color.dstFactor != descB.blend->color.dstFactor) { + return false; + } + if (descA.blend->alpha.operation != descB.blend->alpha.operation || + descA.blend->alpha.srcFactor != descB.blend->alpha.srcFactor || + descA.blend->alpha.dstFactor != descB.blend->alpha.dstFactor) { + return false; + } + } + } + + // Check depth/stencil state + if (a->mAttachmentState->HasDepthStencilAttachment()) { + const DepthStencilState& stateA = a->mDepthStencil; + const DepthStencilState& stateB = b->mDepthStencil; + + ASSERT(!std::isnan(stateA.depthBiasSlopeScale)); + ASSERT(!std::isnan(stateB.depthBiasSlopeScale)); + ASSERT(!std::isnan(stateA.depthBiasClamp)); + ASSERT(!std::isnan(stateB.depthBiasClamp)); + + if (stateA.depthWriteEnabled != stateB.depthWriteEnabled || + stateA.depthCompare != stateB.depthCompare || + stateA.depthBias != stateB.depthBias || + stateA.depthBiasSlopeScale != stateB.depthBiasSlopeScale || + stateA.depthBiasClamp != stateB.depthBiasClamp) { + return false; + } + if (stateA.stencilFront.compare != stateB.stencilFront.compare || + stateA.stencilFront.failOp != stateB.stencilFront.failOp || + stateA.stencilFront.depthFailOp != stateB.stencilFront.depthFailOp || + stateA.stencilFront.passOp != stateB.stencilFront.passOp) { + return false; + } + if (stateA.stencilBack.compare != stateB.stencilBack.compare || + stateA.stencilBack.failOp != stateB.stencilBack.failOp || + stateA.stencilBack.depthFailOp != stateB.stencilBack.depthFailOp || + stateA.stencilBack.passOp != stateB.stencilBack.passOp) { + return false; + } + if (stateA.stencilReadMask != stateB.stencilReadMask || + stateA.stencilWriteMask != stateB.stencilWriteMask) { + return false; + } + } + } + + // Check vertex state + if (a->mAttributeLocationsUsed != b->mAttributeLocationsUsed) { + return false; + } + + for (VertexAttributeLocation loc : IterateBitSet(a->mAttributeLocationsUsed)) { + const VertexAttributeInfo& descA = a->GetAttribute(loc); + const VertexAttributeInfo& descB = b->GetAttribute(loc); + if (descA.shaderLocation != descB.shaderLocation || + descA.vertexBufferSlot != descB.vertexBufferSlot || descA.offset != descB.offset || + descA.format != descB.format) { + return false; + } + } + + if (a->mVertexBufferSlotsUsed != b->mVertexBufferSlotsUsed) { + return false; + } + + for (VertexBufferSlot slot : IterateBitSet(a->mVertexBufferSlotsUsed)) { + const VertexBufferInfo& descA = a->GetVertexBuffer(slot); + const VertexBufferInfo& descB = b->GetVertexBuffer(slot); + if (descA.arrayStride != descB.arrayStride || descA.stepMode != descB.stepMode) { + return false; + } + } + + // Check primitive state + { + const PrimitiveState& stateA = a->mPrimitive; + const PrimitiveState& stateB = b->mPrimitive; + if (stateA.topology != stateB.topology || + stateA.stripIndexFormat != stateB.stripIndexFormat || + stateA.frontFace != stateB.frontFace || stateA.cullMode != stateB.cullMode || + a->mClampDepth != b->mClampDepth) { + return false; + } + } + + // Check multisample state + { + const MultisampleState& stateA = a->mMultisample; + const MultisampleState& stateB = b->mMultisample; + // Sample count already checked as part of the attachment state. + if (stateA.mask != stateB.mask || + stateA.alphaToCoverageEnabled != stateB.alphaToCoverageEnabled) { + return false; + } + } + + return true; + } + +} // namespace dawn::native
diff --git a/src/dawn/native/RenderPipeline.h b/src/dawn/native/RenderPipeline.h new file mode 100644 index 0000000..429f2a9 --- /dev/null +++ b/src/dawn/native/RenderPipeline.h
@@ -0,0 +1,147 @@ +// Copyright 2017 The Dawn Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#ifndef DAWNNATIVE_RENDERPIPELINE_H_ +#define DAWNNATIVE_RENDERPIPELINE_H_ + +#include "dawn/common/TypedInteger.h" +#include "dawn/native/AttachmentState.h" +#include "dawn/native/Forward.h" +#include "dawn/native/IntegerTypes.h" +#include "dawn/native/Pipeline.h" + +#include "dawn/native/dawn_platform.h" + +#include <array> +#include <bitset> + +namespace dawn::native { + + class DeviceBase; + + MaybeError ValidateRenderPipelineDescriptor(DeviceBase* device, + const RenderPipelineDescriptor* descriptor); + + std::vector<StageAndDescriptor> GetRenderStagesAndSetDummyShader( + DeviceBase* device, + const RenderPipelineDescriptor* descriptor); + + size_t IndexFormatSize(wgpu::IndexFormat format); + + bool IsStripPrimitiveTopology(wgpu::PrimitiveTopology primitiveTopology); + + bool StencilTestEnabled(const DepthStencilState* depthStencil); + + struct VertexAttributeInfo { + wgpu::VertexFormat format; + uint64_t offset; + VertexAttributeLocation shaderLocation; + VertexBufferSlot vertexBufferSlot; + }; + + struct VertexBufferInfo { + uint64_t arrayStride; + wgpu::VertexStepMode stepMode; + uint16_t usedBytesInStride; + // As indicated in the spec, the lastStride is max(attribute.offset + + // sizeof(attribute.format)) for each attribute in the buffer[slot] + uint64_t lastStride; + }; + + class RenderPipelineBase : public PipelineBase { + public: + RenderPipelineBase(DeviceBase* device, const RenderPipelineDescriptor* descriptor); + ~RenderPipelineBase() override; + + static RenderPipelineBase* MakeError(DeviceBase* device); + + ObjectType GetType() const override; + + const ityp::bitset<VertexAttributeLocation, kMaxVertexAttributes>& + GetAttributeLocationsUsed() const; + const VertexAttributeInfo& GetAttribute(VertexAttributeLocation location) const; + const ityp::bitset<VertexBufferSlot, kMaxVertexBuffers>& GetVertexBufferSlotsUsed() const; + const ityp::bitset<VertexBufferSlot, kMaxVertexBuffers>& + GetVertexBufferSlotsUsedAsVertexBuffer() const; + const ityp::bitset<VertexBufferSlot, kMaxVertexBuffers>& + GetVertexBufferSlotsUsedAsInstanceBuffer() const; + const VertexBufferInfo& GetVertexBuffer(VertexBufferSlot slot) const; + uint32_t GetVertexBufferCount() const; + + const ColorTargetState* GetColorTargetState(ColorAttachmentIndex attachmentSlot) const; + const DepthStencilState* GetDepthStencilState() const; + wgpu::PrimitiveTopology GetPrimitiveTopology() const; + wgpu::IndexFormat GetStripIndexFormat() const; + wgpu::CullMode GetCullMode() const; + wgpu::FrontFace GetFrontFace() const; + bool IsDepthBiasEnabled() const; + int32_t GetDepthBias() const; + float GetDepthBiasSlopeScale() const; + float GetDepthBiasClamp() const; + bool ShouldClampDepth() const; + + ityp::bitset<ColorAttachmentIndex, kMaxColorAttachments> GetColorAttachmentsMask() const; + bool HasDepthStencilAttachment() const; + wgpu::TextureFormat GetColorAttachmentFormat(ColorAttachmentIndex attachment) const; + wgpu::TextureFormat GetDepthStencilFormat() const; + uint32_t GetSampleCount() const; + uint32_t GetSampleMask() const; + bool IsAlphaToCoverageEnabled() const; + bool WritesDepth() const; + bool WritesStencil() const; + + const AttachmentState* GetAttachmentState() const; + + // Functions necessary for the unordered_set<RenderPipelineBase*>-based cache. + size_t ComputeContentHash() override; + + struct EqualityFunc { + bool operator()(const RenderPipelineBase* a, const RenderPipelineBase* b) const; + }; + + protected: + // Constructor used only for mocking and testing. + RenderPipelineBase(DeviceBase* device); + void DestroyImpl() override; + + private: + RenderPipelineBase(DeviceBase* device, ObjectBase::ErrorTag tag); + + // Vertex state + uint32_t mVertexBufferCount; + ityp::bitset<VertexAttributeLocation, kMaxVertexAttributes> mAttributeLocationsUsed; + ityp::array<VertexAttributeLocation, VertexAttributeInfo, kMaxVertexAttributes> + mAttributeInfos; + ityp::bitset<VertexBufferSlot, kMaxVertexBuffers> mVertexBufferSlotsUsed; + ityp::bitset<VertexBufferSlot, kMaxVertexBuffers> mVertexBufferSlotsUsedAsVertexBuffer; + ityp::bitset<VertexBufferSlot, kMaxVertexBuffers> mVertexBufferSlotsUsedAsInstanceBuffer; + ityp::array<VertexBufferSlot, VertexBufferInfo, kMaxVertexBuffers> mVertexBufferInfos; + + // Attachments + Ref<AttachmentState> mAttachmentState; + ityp::array<ColorAttachmentIndex, ColorTargetState, kMaxColorAttachments> mTargets; + ityp::array<ColorAttachmentIndex, BlendState, kMaxColorAttachments> mTargetBlend; + + // Other state + PrimitiveState mPrimitive; + DepthStencilState mDepthStencil; + MultisampleState mMultisample; + bool mClampDepth = false; + bool mWritesDepth = false; + bool mWritesStencil = false; + }; + +} // namespace dawn::native + +#endif // DAWNNATIVE_RENDERPIPELINE_H_
diff --git a/src/dawn/native/ResourceHeap.h b/src/dawn/native/ResourceHeap.h new file mode 100644 index 0000000..cb45c88 --- /dev/null +++ b/src/dawn/native/ResourceHeap.h
@@ -0,0 +1,31 @@ +// Copyright 2019 The Dawn Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#ifndef DAWNNATIVE_RESOURCEHEAP_H_ +#define DAWNNATIVE_RESOURCEHEAP_H_ + +#include "dawn/native/Error.h" + +namespace dawn::native { + + // Wrapper for a resource backed by a heap. + class ResourceHeapBase { + public: + ResourceHeapBase() = default; + virtual ~ResourceHeapBase() = default; + }; + +} // namespace dawn::native + +#endif // DAWNNATIVE_RESOURCEHEAP_H_
diff --git a/src/dawn/native/ResourceHeapAllocator.h b/src/dawn/native/ResourceHeapAllocator.h new file mode 100644 index 0000000..3c86154 --- /dev/null +++ b/src/dawn/native/ResourceHeapAllocator.h
@@ -0,0 +1,37 @@ +// Copyright 2019 The Dawn Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#ifndef DAWNNATIVE_RESOURCEHEAPALLOCATOR_H_ +#define DAWNNATIVE_RESOURCEHEAPALLOCATOR_H_ + +#include "dawn/native/Error.h" +#include "dawn/native/ResourceHeap.h" + +#include <memory> + +namespace dawn::native { + + // Interface for backend allocators that create memory heaps resoruces can be suballocated in. + class ResourceHeapAllocator { + public: + virtual ~ResourceHeapAllocator() = default; + + virtual ResultOrError<std::unique_ptr<ResourceHeapBase>> AllocateResourceHeap( + uint64_t size) = 0; + virtual void DeallocateResourceHeap(std::unique_ptr<ResourceHeapBase> allocation) = 0; + }; + +} // namespace dawn::native + +#endif // DAWNNATIVE_RESOURCEHEAPALLOCATOR_H_
diff --git a/src/dawn/native/ResourceMemoryAllocation.cpp b/src/dawn/native/ResourceMemoryAllocation.cpp new file mode 100644 index 0000000..8848c18 --- /dev/null +++ b/src/dawn/native/ResourceMemoryAllocation.cpp
@@ -0,0 +1,53 @@ +// Copyright 2019 The Dawn Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include "dawn/native/ResourceMemoryAllocation.h" +#include "dawn/common/Assert.h" + +namespace dawn::native { + + ResourceMemoryAllocation::ResourceMemoryAllocation() + : mOffset(0), mResourceHeap(nullptr), mMappedPointer(nullptr) { + } + + ResourceMemoryAllocation::ResourceMemoryAllocation(const AllocationInfo& info, + uint64_t offset, + ResourceHeapBase* resourceHeap, + uint8_t* mappedPointer) + : mInfo(info), mOffset(offset), mResourceHeap(resourceHeap), mMappedPointer(mappedPointer) { + } + + ResourceHeapBase* ResourceMemoryAllocation::GetResourceHeap() const { + ASSERT(mInfo.mMethod != AllocationMethod::kInvalid); + return mResourceHeap; + } + + uint64_t ResourceMemoryAllocation::GetOffset() const { + ASSERT(mInfo.mMethod != AllocationMethod::kInvalid); + return mOffset; + } + + AllocationInfo ResourceMemoryAllocation::GetInfo() const { + return mInfo; + } + + uint8_t* ResourceMemoryAllocation::GetMappedPointer() const { + return mMappedPointer; + } + + void ResourceMemoryAllocation::Invalidate() { + mResourceHeap = nullptr; + mInfo = {}; + } +} // namespace dawn::native
diff --git a/src/dawn/native/ResourceMemoryAllocation.h b/src/dawn/native/ResourceMemoryAllocation.h new file mode 100644 index 0000000..307d90a --- /dev/null +++ b/src/dawn/native/ResourceMemoryAllocation.h
@@ -0,0 +1,80 @@ +// Copyright 2018 The Dawn Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#ifndef DAWNNATIVE_RESOURCEMEMORYALLOCATION_H_ +#define DAWNNATIVE_RESOURCEMEMORYALLOCATION_H_ + +#include <cstdint> + +namespace dawn::native { + + class ResourceHeapBase; + + // Allocation method determines how memory was sub-divided. + // Used by the device to get the allocator that was responsible for the allocation. + enum class AllocationMethod { + + // Memory not sub-divided. + kDirect, + + // Memory sub-divided using one or more blocks of various sizes. + kSubAllocated, + + // Memory was allocated outside of Dawn. + kExternal, + + // Memory not allocated or freed. + kInvalid + }; + + // Metadata that describes how the allocation was allocated. + struct AllocationInfo { + // AllocationInfo contains a separate offset to not confuse block vs memory offsets. + // The block offset is within the entire allocator memory range and only required by the + // buddy sub-allocator to get the corresponding memory. Unlike the block offset, the + // allocation offset is always local to the memory. + uint64_t mBlockOffset = 0; + + AllocationMethod mMethod = AllocationMethod::kInvalid; + }; + + // Handle into a resource heap pool. + class ResourceMemoryAllocation { + public: + ResourceMemoryAllocation(); + ResourceMemoryAllocation(const AllocationInfo& info, + uint64_t offset, + ResourceHeapBase* resourceHeap, + uint8_t* mappedPointer = nullptr); + virtual ~ResourceMemoryAllocation() = default; + + ResourceMemoryAllocation(const ResourceMemoryAllocation&) = default; + ResourceMemoryAllocation& operator=(const ResourceMemoryAllocation&) = default; + + ResourceHeapBase* GetResourceHeap() const; + uint64_t GetOffset() const; + uint8_t* GetMappedPointer() const; + AllocationInfo GetInfo() const; + + virtual void Invalidate(); + + private: + AllocationInfo mInfo; + uint64_t mOffset; + ResourceHeapBase* mResourceHeap; + uint8_t* mMappedPointer; + }; +} // namespace dawn::native + +#endif // DAWNNATIVE_RESOURCEMEMORYALLOCATION_H_
diff --git a/src/dawn/native/RingBufferAllocator.cpp b/src/dawn/native/RingBufferAllocator.cpp new file mode 100644 index 0000000..e1dc7ae --- /dev/null +++ b/src/dawn/native/RingBufferAllocator.cpp
@@ -0,0 +1,121 @@ +// Copyright 2018 The Dawn Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include "dawn/native/RingBufferAllocator.h" + +// Note: Current RingBufferAllocator implementation uses two indices (start and end) to implement a +// circular queue. However, this approach defines a full queue when one element is still unused. +// +// For example, [E,E,E,E] would be equivelent to [U,U,U,U]. +// ^ ^ +// S=E=1 S=E=1 +// +// The latter case is eliminated by counting used bytes >= capacity. This definition prevents +// (the last) byte and requires an extra variable to count used bytes. Alternatively, we could use +// only two indices that keep increasing (unbounded) but can be still indexed using bit masks. +// However, this 1) requires the size to always be a power-of-two and 2) remove tests that check +// used bytes. +namespace dawn::native { + + RingBufferAllocator::RingBufferAllocator(uint64_t maxSize) : mMaxBlockSize(maxSize) { + } + + void RingBufferAllocator::Deallocate(ExecutionSerial lastCompletedSerial) { + // Reclaim memory from previously recorded blocks. + for (Request& request : mInflightRequests.IterateUpTo(lastCompletedSerial)) { + mUsedStartOffset = request.endOffset; + mUsedSize -= request.size; + } + + // Dequeue previously recorded requests. + mInflightRequests.ClearUpTo(lastCompletedSerial); + } + + uint64_t RingBufferAllocator::GetSize() const { + return mMaxBlockSize; + } + + uint64_t RingBufferAllocator::GetUsedSize() const { + return mUsedSize; + } + + bool RingBufferAllocator::Empty() const { + return mInflightRequests.Empty(); + } + + // Sub-allocate the ring-buffer by requesting a chunk of the specified size. + // This is a serial-based resource scheme, the life-span of resources (and the allocations) get + // tracked by GPU progress via serials. Memory can be reused by determining if the GPU has + // completed up to a given serial. Each sub-allocation request is tracked in the serial offset + // queue, which identifies an existing (or new) frames-worth of resources. Internally, the + // ring-buffer maintains offsets of 3 "memory" states: Free, Reclaimed, and Used. This is done + // in FIFO order as older frames would free resources before newer ones. + uint64_t RingBufferAllocator::Allocate(uint64_t allocationSize, ExecutionSerial serial) { + // Check if the buffer is full by comparing the used size. + // If the buffer is not split where waste occurs (e.g. cannot fit new sub-alloc in front), a + // subsequent sub-alloc could fail where the used size was previously adjusted to include + // the wasted. + if (mUsedSize >= mMaxBlockSize) { + return kInvalidOffset; + } + + // Ensure adding allocationSize does not overflow. + const uint64_t remainingSize = (mMaxBlockSize - mUsedSize); + if (allocationSize > remainingSize) { + return kInvalidOffset; + } + + uint64_t startOffset = kInvalidOffset; + + // Check if the buffer is NOT split (i.e sub-alloc on ends) + if (mUsedStartOffset <= mUsedEndOffset) { + // Order is important (try to sub-alloc at end first). + // This is due to FIFO order where sub-allocs are inserted from left-to-right (when not + // wrapped). + if (mUsedEndOffset + allocationSize <= mMaxBlockSize) { + startOffset = mUsedEndOffset; + mUsedEndOffset += allocationSize; + mUsedSize += allocationSize; + mCurrentRequestSize += allocationSize; + } else if (allocationSize <= mUsedStartOffset) { // Try to sub-alloc at front. + // Count the space at the end so that a subsequent + // sub-alloc cannot not succeed when the buffer is full. + const uint64_t requestSize = (mMaxBlockSize - mUsedEndOffset) + allocationSize; + + startOffset = 0; + mUsedEndOffset = allocationSize; + mUsedSize += requestSize; + mCurrentRequestSize += requestSize; + } + } else if (mUsedEndOffset + allocationSize <= + mUsedStartOffset) { // Otherwise, buffer is split where sub-alloc must be + // in-between. + startOffset = mUsedEndOffset; + mUsedEndOffset += allocationSize; + mUsedSize += allocationSize; + mCurrentRequestSize += allocationSize; + } + + if (startOffset != kInvalidOffset) { + Request request; + request.endOffset = mUsedEndOffset; + request.size = mCurrentRequestSize; + + mInflightRequests.Enqueue(std::move(request), serial); + mCurrentRequestSize = 0; // reset + } + + return startOffset; + } +} // namespace dawn::native
diff --git a/src/dawn/native/RingBufferAllocator.h b/src/dawn/native/RingBufferAllocator.h new file mode 100644 index 0000000..8049470 --- /dev/null +++ b/src/dawn/native/RingBufferAllocator.h
@@ -0,0 +1,63 @@ +// Copyright 2018 The Dawn Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#ifndef DAWNNATIVE_RINGBUFFERALLOCATOR_H_ +#define DAWNNATIVE_RINGBUFFERALLOCATOR_H_ + +#include "dawn/common/SerialQueue.h" +#include "dawn/native/IntegerTypes.h" + +#include <limits> +#include <memory> + +// RingBufferAllocator is the front-end implementation used to manage a ring buffer in GPU memory. +namespace dawn::native { + + class RingBufferAllocator { + public: + RingBufferAllocator() = default; + RingBufferAllocator(uint64_t maxSize); + ~RingBufferAllocator() = default; + RingBufferAllocator(const RingBufferAllocator&) = default; + RingBufferAllocator& operator=(const RingBufferAllocator&) = default; + + uint64_t Allocate(uint64_t allocationSize, ExecutionSerial serial); + void Deallocate(ExecutionSerial lastCompletedSerial); + + uint64_t GetSize() const; + bool Empty() const; + uint64_t GetUsedSize() const; + + static constexpr uint64_t kInvalidOffset = std::numeric_limits<uint64_t>::max(); + + private: + struct Request { + uint64_t endOffset; + uint64_t size; + }; + + SerialQueue<ExecutionSerial, Request> + mInflightRequests; // Queue of the recorded sub-alloc requests + // (e.g. frame of resources). + + uint64_t mUsedEndOffset = 0; // Tail of used sub-alloc requests (in bytes). + uint64_t mUsedStartOffset = 0; // Head of used sub-alloc requests (in bytes). + uint64_t mMaxBlockSize = 0; // Max size of the ring buffer (in bytes). + uint64_t mUsedSize = 0; // Size of the sub-alloc requests (in bytes) of the ring buffer. + uint64_t mCurrentRequestSize = + 0; // Size of the sub-alloc requests (in bytes) of the current serial. + }; +} // namespace dawn::native + +#endif // DAWNNATIVE_RINGBUFFERALLOCATOR_H_
diff --git a/src/dawn/native/Sampler.cpp b/src/dawn/native/Sampler.cpp new file mode 100644 index 0000000..ffd8a72 --- /dev/null +++ b/src/dawn/native/Sampler.cpp
@@ -0,0 +1,153 @@ +// Copyright 2017 The Dawn Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include "dawn/native/Sampler.h" + +#include "dawn/native/Device.h" +#include "dawn/native/ObjectContentHasher.h" +#include "dawn/native/ValidationUtils_autogen.h" + +#include <cmath> + +namespace dawn::native { + + MaybeError ValidateSamplerDescriptor(DeviceBase*, const SamplerDescriptor* descriptor) { + DAWN_INVALID_IF(descriptor->nextInChain != nullptr, "nextInChain must be nullptr"); + + DAWN_INVALID_IF(std::isnan(descriptor->lodMinClamp) || std::isnan(descriptor->lodMaxClamp), + "LOD clamp bounds [%f, %f] contain a NaN.", descriptor->lodMinClamp, + descriptor->lodMaxClamp); + + DAWN_INVALID_IF(descriptor->lodMinClamp < 0 || descriptor->lodMaxClamp < 0, + "LOD clamp bounds [%f, %f] contain contain a negative number.", + descriptor->lodMinClamp, descriptor->lodMaxClamp); + + DAWN_INVALID_IF(descriptor->lodMinClamp > descriptor->lodMaxClamp, + "LOD min clamp (%f) is larger than the max clamp (%f).", + descriptor->lodMinClamp, descriptor->lodMaxClamp); + + if (descriptor->maxAnisotropy > 1) { + DAWN_INVALID_IF(descriptor->minFilter != wgpu::FilterMode::Linear || + descriptor->magFilter != wgpu::FilterMode::Linear || + descriptor->mipmapFilter != wgpu::FilterMode::Linear, + "One of minFilter (%s), magFilter (%s) or mipmapFilter (%s) is not %s " + "while using anisotropic filter (maxAnisotropy is %f)", + descriptor->magFilter, descriptor->minFilter, descriptor->mipmapFilter, + wgpu::FilterMode::Linear, descriptor->maxAnisotropy); + } else if (descriptor->maxAnisotropy == 0u) { + return DAWN_FORMAT_VALIDATION_ERROR("Max anisotropy (%f) is less than 1.", + descriptor->maxAnisotropy); + } + + DAWN_TRY(ValidateFilterMode(descriptor->minFilter)); + DAWN_TRY(ValidateFilterMode(descriptor->magFilter)); + DAWN_TRY(ValidateFilterMode(descriptor->mipmapFilter)); + DAWN_TRY(ValidateAddressMode(descriptor->addressModeU)); + DAWN_TRY(ValidateAddressMode(descriptor->addressModeV)); + DAWN_TRY(ValidateAddressMode(descriptor->addressModeW)); + + // CompareFunction::Undefined is tagged as invalid because it can't be used, except for the + // SamplerDescriptor where it is a special value that means the sampler is not a + // comparison-sampler. + if (descriptor->compare != wgpu::CompareFunction::Undefined) { + DAWN_TRY(ValidateCompareFunction(descriptor->compare)); + } + + return {}; + } + + // SamplerBase + + SamplerBase::SamplerBase(DeviceBase* device, + const SamplerDescriptor* descriptor, + ApiObjectBase::UntrackedByDeviceTag tag) + : ApiObjectBase(device, descriptor->label), + mAddressModeU(descriptor->addressModeU), + mAddressModeV(descriptor->addressModeV), + mAddressModeW(descriptor->addressModeW), + mMagFilter(descriptor->magFilter), + mMinFilter(descriptor->minFilter), + mMipmapFilter(descriptor->mipmapFilter), + mLodMinClamp(descriptor->lodMinClamp), + mLodMaxClamp(descriptor->lodMaxClamp), + mCompareFunction(descriptor->compare), + mMaxAnisotropy(descriptor->maxAnisotropy) { + } + + SamplerBase::SamplerBase(DeviceBase* device, const SamplerDescriptor* descriptor) + : SamplerBase(device, descriptor, kUntrackedByDevice) { + TrackInDevice(); + } + + SamplerBase::SamplerBase(DeviceBase* device) : ApiObjectBase(device, kLabelNotImplemented) { + TrackInDevice(); + } + + SamplerBase::SamplerBase(DeviceBase* device, ObjectBase::ErrorTag tag) + : ApiObjectBase(device, tag) { + } + + SamplerBase::~SamplerBase() = default; + + void SamplerBase::DestroyImpl() { + if (IsCachedReference()) { + // Do not uncache the actual cached object if we are a blueprint. + GetDevice()->UncacheSampler(this); + } + } + + // static + SamplerBase* SamplerBase::MakeError(DeviceBase* device) { + return new SamplerBase(device, ObjectBase::kError); + } + + ObjectType SamplerBase::GetType() const { + return ObjectType::Sampler; + } + + bool SamplerBase::IsComparison() const { + return mCompareFunction != wgpu::CompareFunction::Undefined; + } + + bool SamplerBase::IsFiltering() const { + return mMinFilter == wgpu::FilterMode::Linear || mMagFilter == wgpu::FilterMode::Linear || + mMipmapFilter == wgpu::FilterMode::Linear; + } + + size_t SamplerBase::ComputeContentHash() { + ObjectContentHasher recorder; + recorder.Record(mAddressModeU, mAddressModeV, mAddressModeW, mMagFilter, mMinFilter, + mMipmapFilter, mLodMinClamp, mLodMaxClamp, mCompareFunction, + mMaxAnisotropy); + return recorder.GetContentHash(); + } + + bool SamplerBase::EqualityFunc::operator()(const SamplerBase* a, const SamplerBase* b) const { + if (a == b) { + return true; + } + + ASSERT(!std::isnan(a->mLodMinClamp)); + ASSERT(!std::isnan(b->mLodMinClamp)); + ASSERT(!std::isnan(a->mLodMaxClamp)); + ASSERT(!std::isnan(b->mLodMaxClamp)); + + return a->mAddressModeU == b->mAddressModeU && a->mAddressModeV == b->mAddressModeV && + a->mAddressModeW == b->mAddressModeW && a->mMagFilter == b->mMagFilter && + a->mMinFilter == b->mMinFilter && a->mMipmapFilter == b->mMipmapFilter && + a->mLodMinClamp == b->mLodMinClamp && a->mLodMaxClamp == b->mLodMaxClamp && + a->mCompareFunction == b->mCompareFunction && a->mMaxAnisotropy == b->mMaxAnisotropy; + } + +} // namespace dawn::native
diff --git a/src/dawn/native/Sampler.h b/src/dawn/native/Sampler.h new file mode 100644 index 0000000..e21b52c --- /dev/null +++ b/src/dawn/native/Sampler.h
@@ -0,0 +1,80 @@ +// Copyright 2017 The Dawn Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#ifndef DAWNNATIVE_SAMPLER_H_ +#define DAWNNATIVE_SAMPLER_H_ + +#include "dawn/native/CachedObject.h" +#include "dawn/native/Error.h" +#include "dawn/native/Forward.h" +#include "dawn/native/ObjectBase.h" + +#include "dawn/native/dawn_platform.h" + +namespace dawn::native { + + class DeviceBase; + + MaybeError ValidateSamplerDescriptor(DeviceBase* device, const SamplerDescriptor* descriptor); + + class SamplerBase : public ApiObjectBase, public CachedObject { + public: + SamplerBase(DeviceBase* device, + const SamplerDescriptor* descriptor, + ApiObjectBase::UntrackedByDeviceTag tag); + SamplerBase(DeviceBase* device, const SamplerDescriptor* descriptor); + ~SamplerBase() override; + + static SamplerBase* MakeError(DeviceBase* device); + + ObjectType GetType() const override; + + bool IsComparison() const; + bool IsFiltering() const; + + // Functions necessary for the unordered_set<SamplerBase*>-based cache. + size_t ComputeContentHash() override; + + struct EqualityFunc { + bool operator()(const SamplerBase* a, const SamplerBase* b) const; + }; + + uint16_t GetMaxAnisotropy() const { + return mMaxAnisotropy; + } + + protected: + // Constructor used only for mocking and testing. + SamplerBase(DeviceBase* device); + void DestroyImpl() override; + + private: + SamplerBase(DeviceBase* device, ObjectBase::ErrorTag tag); + + // TODO(cwallez@chromium.org): Store a crypto hash of the items instead? + wgpu::AddressMode mAddressModeU; + wgpu::AddressMode mAddressModeV; + wgpu::AddressMode mAddressModeW; + wgpu::FilterMode mMagFilter; + wgpu::FilterMode mMinFilter; + wgpu::FilterMode mMipmapFilter; + float mLodMinClamp; + float mLodMaxClamp; + wgpu::CompareFunction mCompareFunction; + uint16_t mMaxAnisotropy; + }; + +} // namespace dawn::native + +#endif // DAWNNATIVE_SAMPLER_H_
diff --git a/src/dawn/native/ScratchBuffer.cpp b/src/dawn/native/ScratchBuffer.cpp new file mode 100644 index 0000000..be53683 --- /dev/null +++ b/src/dawn/native/ScratchBuffer.cpp
@@ -0,0 +1,47 @@ +// Copyright 2021 The Dawn Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include "dawn/native/ScratchBuffer.h" + +#include "dawn/native/Device.h" + +namespace dawn::native { + + ScratchBuffer::ScratchBuffer(DeviceBase* device, wgpu::BufferUsage usage) + : mDevice(device), mUsage(usage) { + } + + ScratchBuffer::~ScratchBuffer() = default; + + void ScratchBuffer::Reset() { + mBuffer = nullptr; + } + + MaybeError ScratchBuffer::EnsureCapacity(uint64_t capacity) { + if (!mBuffer.Get() || mBuffer->GetSize() < capacity) { + BufferDescriptor descriptor; + descriptor.size = capacity; + descriptor.usage = mUsage; + DAWN_TRY_ASSIGN(mBuffer, mDevice->CreateBuffer(&descriptor)); + mBuffer->SetIsDataInitialized(); + } + return {}; + } + + BufferBase* ScratchBuffer::GetBuffer() const { + ASSERT(mBuffer.Get() != nullptr); + return mBuffer.Get(); + } + +} // namespace dawn::native
diff --git a/src/dawn/native/ScratchBuffer.h b/src/dawn/native/ScratchBuffer.h new file mode 100644 index 0000000..7845022 --- /dev/null +++ b/src/dawn/native/ScratchBuffer.h
@@ -0,0 +1,55 @@ +// Copyright 2021 The Dawn Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#ifndef DAWNNATIVE_SCRATCHBUFFER_H_ +#define DAWNNATIVE_SCRATCHBUFFER_H_ + +#include "dawn/common/RefCounted.h" +#include "dawn/native/Buffer.h" + +#include <cstdint> + +namespace dawn::native { + + class DeviceBase; + + // A ScratchBuffer is a lazily allocated and lazily grown GPU buffer for intermittent use by + // commands in the GPU queue. Note that scratch buffers are not zero-initialized, so users must + // be careful not to exposed uninitialized bytes to client shaders. + class ScratchBuffer { + public: + // Note that this object does not retain a reference to `device`, so `device` MUST outlive + // this object. + ScratchBuffer(DeviceBase* device, wgpu::BufferUsage usage); + ~ScratchBuffer(); + + // Resets this ScratchBuffer, guaranteeing that the next EnsureCapacity call allocates a + // fresh buffer. + void Reset(); + + // Ensures that this ScratchBuffer is backed by a buffer on `device` with at least + // `capacity` bytes of storage. + MaybeError EnsureCapacity(uint64_t capacity); + + BufferBase* GetBuffer() const; + + private: + DeviceBase* const mDevice; + const wgpu::BufferUsage mUsage; + Ref<BufferBase> mBuffer; + }; + +} // namespace dawn::native + +#endif // DAWNNATIVE_SCRATCHBUFFER_H_
diff --git a/src/dawn/native/ShaderModule.cpp b/src/dawn/native/ShaderModule.cpp new file mode 100644 index 0000000..779e6ae --- /dev/null +++ b/src/dawn/native/ShaderModule.cpp
@@ -0,0 +1,1329 @@ +// Copyright 2017 The Dawn Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include "dawn/native/ShaderModule.h" + +#include "absl/strings/str_format.h" +#include "dawn/common/BitSetIterator.h" +#include "dawn/common/Constants.h" +#include "dawn/common/HashUtils.h" +#include "dawn/native/BindGroupLayout.h" +#include "dawn/native/ChainUtils_autogen.h" +#include "dawn/native/CompilationMessages.h" +#include "dawn/native/Device.h" +#include "dawn/native/ObjectContentHasher.h" +#include "dawn/native/Pipeline.h" +#include "dawn/native/PipelineLayout.h" +#include "dawn/native/RenderPipeline.h" +#include "dawn/native/TintUtils.h" + +#include <tint/tint.h> + +#include <sstream> + +namespace dawn::native { + + namespace { + + tint::transform::VertexFormat ToTintVertexFormat(wgpu::VertexFormat format) { + switch (format) { + case wgpu::VertexFormat::Uint8x2: + return tint::transform::VertexFormat::kUint8x2; + case wgpu::VertexFormat::Uint8x4: + return tint::transform::VertexFormat::kUint8x4; + case wgpu::VertexFormat::Sint8x2: + return tint::transform::VertexFormat::kSint8x2; + case wgpu::VertexFormat::Sint8x4: + return tint::transform::VertexFormat::kSint8x4; + case wgpu::VertexFormat::Unorm8x2: + return tint::transform::VertexFormat::kUnorm8x2; + case wgpu::VertexFormat::Unorm8x4: + return tint::transform::VertexFormat::kUnorm8x4; + case wgpu::VertexFormat::Snorm8x2: + return tint::transform::VertexFormat::kSnorm8x2; + case wgpu::VertexFormat::Snorm8x4: + return tint::transform::VertexFormat::kSnorm8x4; + case wgpu::VertexFormat::Uint16x2: + return tint::transform::VertexFormat::kUint16x2; + case wgpu::VertexFormat::Uint16x4: + return tint::transform::VertexFormat::kUint16x4; + case wgpu::VertexFormat::Sint16x2: + return tint::transform::VertexFormat::kSint16x2; + case wgpu::VertexFormat::Sint16x4: + return tint::transform::VertexFormat::kSint16x4; + case wgpu::VertexFormat::Unorm16x2: + return tint::transform::VertexFormat::kUnorm16x2; + case wgpu::VertexFormat::Unorm16x4: + return tint::transform::VertexFormat::kUnorm16x4; + case wgpu::VertexFormat::Snorm16x2: + return tint::transform::VertexFormat::kSnorm16x2; + case wgpu::VertexFormat::Snorm16x4: + return tint::transform::VertexFormat::kSnorm16x4; + case wgpu::VertexFormat::Float16x2: + return tint::transform::VertexFormat::kFloat16x2; + case wgpu::VertexFormat::Float16x4: + return tint::transform::VertexFormat::kFloat16x4; + case wgpu::VertexFormat::Float32: + return tint::transform::VertexFormat::kFloat32; + case wgpu::VertexFormat::Float32x2: + return tint::transform::VertexFormat::kFloat32x2; + case wgpu::VertexFormat::Float32x3: + return tint::transform::VertexFormat::kFloat32x3; + case wgpu::VertexFormat::Float32x4: + return tint::transform::VertexFormat::kFloat32x4; + case wgpu::VertexFormat::Uint32: + return tint::transform::VertexFormat::kUint32; + case wgpu::VertexFormat::Uint32x2: + return tint::transform::VertexFormat::kUint32x2; + case wgpu::VertexFormat::Uint32x3: + return tint::transform::VertexFormat::kUint32x3; + case wgpu::VertexFormat::Uint32x4: + return tint::transform::VertexFormat::kUint32x4; + case wgpu::VertexFormat::Sint32: + return tint::transform::VertexFormat::kSint32; + case wgpu::VertexFormat::Sint32x2: + return tint::transform::VertexFormat::kSint32x2; + case wgpu::VertexFormat::Sint32x3: + return tint::transform::VertexFormat::kSint32x3; + case wgpu::VertexFormat::Sint32x4: + return tint::transform::VertexFormat::kSint32x4; + + case wgpu::VertexFormat::Undefined: + break; + } + UNREACHABLE(); + } + + tint::transform::VertexStepMode ToTintVertexStepMode(wgpu::VertexStepMode mode) { + switch (mode) { + case wgpu::VertexStepMode::Vertex: + return tint::transform::VertexStepMode::kVertex; + case wgpu::VertexStepMode::Instance: + return tint::transform::VertexStepMode::kInstance; + } + UNREACHABLE(); + } + + ResultOrError<SingleShaderStage> TintPipelineStageToShaderStage( + tint::ast::PipelineStage stage) { + switch (stage) { + case tint::ast::PipelineStage::kVertex: + return SingleShaderStage::Vertex; + case tint::ast::PipelineStage::kFragment: + return SingleShaderStage::Fragment; + case tint::ast::PipelineStage::kCompute: + return SingleShaderStage::Compute; + case tint::ast::PipelineStage::kNone: + break; + } + UNREACHABLE(); + } + + BindingInfoType TintResourceTypeToBindingInfoType( + tint::inspector::ResourceBinding::ResourceType type) { + switch (type) { + case tint::inspector::ResourceBinding::ResourceType::kUniformBuffer: + case tint::inspector::ResourceBinding::ResourceType::kStorageBuffer: + case tint::inspector::ResourceBinding::ResourceType::kReadOnlyStorageBuffer: + return BindingInfoType::Buffer; + case tint::inspector::ResourceBinding::ResourceType::kSampler: + case tint::inspector::ResourceBinding::ResourceType::kComparisonSampler: + return BindingInfoType::Sampler; + case tint::inspector::ResourceBinding::ResourceType::kSampledTexture: + case tint::inspector::ResourceBinding::ResourceType::kMultisampledTexture: + case tint::inspector::ResourceBinding::ResourceType::kDepthTexture: + case tint::inspector::ResourceBinding::ResourceType::kDepthMultisampledTexture: + return BindingInfoType::Texture; + case tint::inspector::ResourceBinding::ResourceType::kWriteOnlyStorageTexture: + return BindingInfoType::StorageTexture; + case tint::inspector::ResourceBinding::ResourceType::kExternalTexture: + return BindingInfoType::ExternalTexture; + + default: + UNREACHABLE(); + return BindingInfoType::Buffer; + } + } + + wgpu::TextureFormat TintImageFormatToTextureFormat( + tint::inspector::ResourceBinding::TexelFormat format) { + switch (format) { + case tint::inspector::ResourceBinding::TexelFormat::kR32Uint: + return wgpu::TextureFormat::R32Uint; + case tint::inspector::ResourceBinding::TexelFormat::kR32Sint: + return wgpu::TextureFormat::R32Sint; + case tint::inspector::ResourceBinding::TexelFormat::kR32Float: + return wgpu::TextureFormat::R32Float; + case tint::inspector::ResourceBinding::TexelFormat::kRgba8Unorm: + return wgpu::TextureFormat::RGBA8Unorm; + case tint::inspector::ResourceBinding::TexelFormat::kRgba8Snorm: + return wgpu::TextureFormat::RGBA8Snorm; + case tint::inspector::ResourceBinding::TexelFormat::kRgba8Uint: + return wgpu::TextureFormat::RGBA8Uint; + case tint::inspector::ResourceBinding::TexelFormat::kRgba8Sint: + return wgpu::TextureFormat::RGBA8Sint; + case tint::inspector::ResourceBinding::TexelFormat::kRg32Uint: + return wgpu::TextureFormat::RG32Uint; + case tint::inspector::ResourceBinding::TexelFormat::kRg32Sint: + return wgpu::TextureFormat::RG32Sint; + case tint::inspector::ResourceBinding::TexelFormat::kRg32Float: + return wgpu::TextureFormat::RG32Float; + case tint::inspector::ResourceBinding::TexelFormat::kRgba16Uint: + return wgpu::TextureFormat::RGBA16Uint; + case tint::inspector::ResourceBinding::TexelFormat::kRgba16Sint: + return wgpu::TextureFormat::RGBA16Sint; + case tint::inspector::ResourceBinding::TexelFormat::kRgba16Float: + return wgpu::TextureFormat::RGBA16Float; + case tint::inspector::ResourceBinding::TexelFormat::kRgba32Uint: + return wgpu::TextureFormat::RGBA32Uint; + case tint::inspector::ResourceBinding::TexelFormat::kRgba32Sint: + return wgpu::TextureFormat::RGBA32Sint; + case tint::inspector::ResourceBinding::TexelFormat::kRgba32Float: + return wgpu::TextureFormat::RGBA32Float; + case tint::inspector::ResourceBinding::TexelFormat::kNone: + return wgpu::TextureFormat::Undefined; + + default: + UNREACHABLE(); + return wgpu::TextureFormat::Undefined; + } + } + + wgpu::TextureViewDimension TintTextureDimensionToTextureViewDimension( + tint::inspector::ResourceBinding::TextureDimension dim) { + switch (dim) { + case tint::inspector::ResourceBinding::TextureDimension::k1d: + return wgpu::TextureViewDimension::e1D; + case tint::inspector::ResourceBinding::TextureDimension::k2d: + return wgpu::TextureViewDimension::e2D; + case tint::inspector::ResourceBinding::TextureDimension::k2dArray: + return wgpu::TextureViewDimension::e2DArray; + case tint::inspector::ResourceBinding::TextureDimension::k3d: + return wgpu::TextureViewDimension::e3D; + case tint::inspector::ResourceBinding::TextureDimension::kCube: + return wgpu::TextureViewDimension::Cube; + case tint::inspector::ResourceBinding::TextureDimension::kCubeArray: + return wgpu::TextureViewDimension::CubeArray; + case tint::inspector::ResourceBinding::TextureDimension::kNone: + return wgpu::TextureViewDimension::Undefined; + } + UNREACHABLE(); + } + + SampleTypeBit TintSampledKindToSampleTypeBit( + tint::inspector::ResourceBinding::SampledKind s) { + switch (s) { + case tint::inspector::ResourceBinding::SampledKind::kSInt: + return SampleTypeBit::Sint; + case tint::inspector::ResourceBinding::SampledKind::kUInt: + return SampleTypeBit::Uint; + case tint::inspector::ResourceBinding::SampledKind::kFloat: + return SampleTypeBit::Float | SampleTypeBit::UnfilterableFloat; + case tint::inspector::ResourceBinding::SampledKind::kUnknown: + return SampleTypeBit::None; + } + UNREACHABLE(); + } + + ResultOrError<wgpu::TextureComponentType> TintComponentTypeToTextureComponentType( + tint::inspector::ComponentType type) { + switch (type) { + case tint::inspector::ComponentType::kFloat: + return wgpu::TextureComponentType::Float; + case tint::inspector::ComponentType::kSInt: + return wgpu::TextureComponentType::Sint; + case tint::inspector::ComponentType::kUInt: + return wgpu::TextureComponentType::Uint; + case tint::inspector::ComponentType::kUnknown: + return DAWN_VALIDATION_ERROR( + "Attempted to convert 'Unknown' component type from Tint"); + } + UNREACHABLE(); + } + + ResultOrError<VertexFormatBaseType> TintComponentTypeToVertexFormatBaseType( + tint::inspector::ComponentType type) { + switch (type) { + case tint::inspector::ComponentType::kFloat: + return VertexFormatBaseType::Float; + case tint::inspector::ComponentType::kSInt: + return VertexFormatBaseType::Sint; + case tint::inspector::ComponentType::kUInt: + return VertexFormatBaseType::Uint; + case tint::inspector::ComponentType::kUnknown: + return DAWN_VALIDATION_ERROR( + "Attempted to convert 'Unknown' component type from Tint"); + } + UNREACHABLE(); + } + + ResultOrError<wgpu::BufferBindingType> TintResourceTypeToBufferBindingType( + tint::inspector::ResourceBinding::ResourceType resource_type) { + switch (resource_type) { + case tint::inspector::ResourceBinding::ResourceType::kUniformBuffer: + return wgpu::BufferBindingType::Uniform; + case tint::inspector::ResourceBinding::ResourceType::kStorageBuffer: + return wgpu::BufferBindingType::Storage; + case tint::inspector::ResourceBinding::ResourceType::kReadOnlyStorageBuffer: + return wgpu::BufferBindingType::ReadOnlyStorage; + default: + return DAWN_VALIDATION_ERROR("Attempted to convert non-buffer resource type"); + } + UNREACHABLE(); + } + + ResultOrError<wgpu::StorageTextureAccess> TintResourceTypeToStorageTextureAccess( + tint::inspector::ResourceBinding::ResourceType resource_type) { + switch (resource_type) { + case tint::inspector::ResourceBinding::ResourceType::kWriteOnlyStorageTexture: + return wgpu::StorageTextureAccess::WriteOnly; + default: + return DAWN_VALIDATION_ERROR( + "Attempted to convert non-storage texture resource type"); + } + UNREACHABLE(); + } + + ResultOrError<InterStageComponentType> TintComponentTypeToInterStageComponentType( + tint::inspector::ComponentType type) { + switch (type) { + case tint::inspector::ComponentType::kFloat: + return InterStageComponentType::Float; + case tint::inspector::ComponentType::kSInt: + return InterStageComponentType::Sint; + case tint::inspector::ComponentType::kUInt: + return InterStageComponentType::Uint; + case tint::inspector::ComponentType::kUnknown: + return DAWN_VALIDATION_ERROR( + "Attempted to convert 'Unknown' component type from Tint"); + } + UNREACHABLE(); + } + + ResultOrError<uint32_t> TintCompositionTypeToInterStageComponentCount( + tint::inspector::CompositionType type) { + switch (type) { + case tint::inspector::CompositionType::kScalar: + return 1u; + case tint::inspector::CompositionType::kVec2: + return 2u; + case tint::inspector::CompositionType::kVec3: + return 3u; + case tint::inspector::CompositionType::kVec4: + return 4u; + case tint::inspector::CompositionType::kUnknown: + return DAWN_VALIDATION_ERROR( + "Attempt to convert 'Unknown' composition type from Tint"); + } + UNREACHABLE(); + } + + ResultOrError<InterpolationType> TintInterpolationTypeToInterpolationType( + tint::inspector::InterpolationType type) { + switch (type) { + case tint::inspector::InterpolationType::kPerspective: + return InterpolationType::Perspective; + case tint::inspector::InterpolationType::kLinear: + return InterpolationType::Linear; + case tint::inspector::InterpolationType::kFlat: + return InterpolationType::Flat; + case tint::inspector::InterpolationType::kUnknown: + return DAWN_VALIDATION_ERROR( + "Attempted to convert 'Unknown' interpolation type from Tint"); + } + UNREACHABLE(); + } + + ResultOrError<InterpolationSampling> TintInterpolationSamplingToInterpolationSamplingType( + tint::inspector::InterpolationSampling type) { + switch (type) { + case tint::inspector::InterpolationSampling::kNone: + return InterpolationSampling::None; + case tint::inspector::InterpolationSampling::kCenter: + return InterpolationSampling::Center; + case tint::inspector::InterpolationSampling::kCentroid: + return InterpolationSampling::Centroid; + case tint::inspector::InterpolationSampling::kSample: + return InterpolationSampling::Sample; + case tint::inspector::InterpolationSampling::kUnknown: + return DAWN_VALIDATION_ERROR( + "Attempted to convert 'Unknown' interpolation sampling type from Tint"); + } + UNREACHABLE(); + } + + EntryPointMetadata::OverridableConstant::Type FromTintOverridableConstantType( + tint::inspector::OverridableConstant::Type type) { + switch (type) { + case tint::inspector::OverridableConstant::Type::kBool: + return EntryPointMetadata::OverridableConstant::Type::Boolean; + case tint::inspector::OverridableConstant::Type::kFloat32: + return EntryPointMetadata::OverridableConstant::Type::Float32; + case tint::inspector::OverridableConstant::Type::kInt32: + return EntryPointMetadata::OverridableConstant::Type::Int32; + case tint::inspector::OverridableConstant::Type::kUint32: + return EntryPointMetadata::OverridableConstant::Type::Uint32; + } + UNREACHABLE(); + } + + ResultOrError<tint::Program> ParseWGSL(const tint::Source::File* file, + OwnedCompilationMessages* outMessages) { + tint::Program program = tint::reader::wgsl::Parse(file); + if (outMessages != nullptr) { + outMessages->AddMessages(program.Diagnostics()); + } + if (!program.IsValid()) { + return DAWN_FORMAT_VALIDATION_ERROR( + "Tint WGSL reader failure:\nParser: %s\nShader:\n%s\n", + program.Diagnostics().str(), file->content.data); + } + + return std::move(program); + } + + ResultOrError<tint::Program> ParseSPIRV(const std::vector<uint32_t>& spirv, + OwnedCompilationMessages* outMessages) { + tint::Program program = tint::reader::spirv::Parse(spirv); + if (outMessages != nullptr) { + outMessages->AddMessages(program.Diagnostics()); + } + if (!program.IsValid()) { + return DAWN_FORMAT_VALIDATION_ERROR("Tint SPIR-V reader failure:\nParser: %s\n", + program.Diagnostics().str()); + } + + return std::move(program); + } + + std::vector<uint64_t> GetBindGroupMinBufferSizes(const BindingGroupInfoMap& shaderBindings, + const BindGroupLayoutBase* layout) { + std::vector<uint64_t> requiredBufferSizes(layout->GetUnverifiedBufferCount()); + uint32_t packedIdx = 0; + + for (BindingIndex bindingIndex{0}; bindingIndex < layout->GetBufferCount(); + ++bindingIndex) { + const BindingInfo& bindingInfo = layout->GetBindingInfo(bindingIndex); + if (bindingInfo.buffer.minBindingSize != 0) { + // Skip bindings that have minimum buffer size set in the layout + continue; + } + + ASSERT(packedIdx < requiredBufferSizes.size()); + const auto& shaderInfo = shaderBindings.find(bindingInfo.binding); + if (shaderInfo != shaderBindings.end()) { + requiredBufferSizes[packedIdx] = shaderInfo->second.buffer.minBindingSize; + } else { + // We have to include buffers if they are included in the bind group's + // packed vector. We don't actually need to check these at draw time, so + // if this is a problem in the future we can optimize it further. + requiredBufferSizes[packedIdx] = 0; + } + ++packedIdx; + } + + return requiredBufferSizes; + } + + MaybeError ValidateCompatibilityOfSingleBindingWithLayout( + const DeviceBase* device, + const BindGroupLayoutBase* layout, + SingleShaderStage entryPointStage, + BindingNumber bindingNumber, + const ShaderBindingInfo& shaderInfo) { + const BindGroupLayoutBase::BindingMap& layoutBindings = layout->GetBindingMap(); + + // An external texture binding found in the shader will later be expanded into multiple + // bindings at compile time. This expansion will have already happened in the bgl - so + // the shader and bgl will always mismatch at this point. Expansion info is contained in + // the bgl object, so we can still verify the bgl used to have an external texture in + // the slot corresponding to the shader reflection. + if (shaderInfo.bindingType == BindingInfoType::ExternalTexture) { + // If an external texture binding used to exist in the bgl, it will be found as a + // key in the ExternalTextureBindingExpansions map. + ExternalTextureBindingExpansionMap expansions = + layout->GetExternalTextureBindingExpansionMap(); + std::map<BindingNumber, dawn_native::ExternalTextureBindingExpansion>::iterator it = + expansions.find(bindingNumber); + // TODO(dawn:563): Provide info about the binding types. + DAWN_INVALID_IF(it == expansions.end(), + "Binding type in the shader (texture_external) doesn't match the " + "type in the layout."); + + return {}; + } + + const auto& bindingIt = layoutBindings.find(bindingNumber); + DAWN_INVALID_IF(bindingIt == layoutBindings.end(), "Binding doesn't exist in %s.", + layout); + + BindingIndex bindingIndex(bindingIt->second); + const BindingInfo& layoutInfo = layout->GetBindingInfo(bindingIndex); + + // TODO(dawn:563): Provide info about the binding types. + DAWN_INVALID_IF( + layoutInfo.bindingType != shaderInfo.bindingType, + "Binding type (buffer vs. texture vs. sampler vs. external) doesn't match the type " + "in the layout."); + + ExternalTextureBindingExpansionMap expansions = + layout->GetExternalTextureBindingExpansionMap(); + DAWN_INVALID_IF(expansions.find(bindingNumber) != expansions.end(), + "Binding type (buffer vs. texture vs. sampler vs. external) doesn't " + "match the type in the layout."); + + // TODO(dawn:563): Provide info about the visibility. + DAWN_INVALID_IF( + (layoutInfo.visibility & StageBit(entryPointStage)) == 0, + "Entry point's stage is not in the binding visibility in the layout (%s)", + layoutInfo.visibility); + + switch (layoutInfo.bindingType) { + case BindingInfoType::Texture: { + DAWN_INVALID_IF( + layoutInfo.texture.multisampled != shaderInfo.texture.multisampled, + "Binding multisampled flag (%u) doesn't match the layout's multisampled " + "flag (%u)", + layoutInfo.texture.multisampled, shaderInfo.texture.multisampled); + + // TODO(dawn:563): Provide info about the sample types. + DAWN_INVALID_IF((SampleTypeToSampleTypeBit(layoutInfo.texture.sampleType) & + shaderInfo.texture.compatibleSampleTypes) == 0, + "The sample type in the shader is not compatible with the " + "sample type of the layout."); + + DAWN_INVALID_IF( + layoutInfo.texture.viewDimension != shaderInfo.texture.viewDimension, + "The shader's binding dimension (%s) doesn't match the shader's binding " + "dimension (%s).", + layoutInfo.texture.viewDimension, shaderInfo.texture.viewDimension); + break; + } + + case BindingInfoType::StorageTexture: { + ASSERT(layoutInfo.storageTexture.format != wgpu::TextureFormat::Undefined); + ASSERT(shaderInfo.storageTexture.format != wgpu::TextureFormat::Undefined); + + DAWN_INVALID_IF( + layoutInfo.storageTexture.access != shaderInfo.storageTexture.access, + "The layout's binding access (%s) isn't compatible with the shader's " + "binding access (%s).", + layoutInfo.storageTexture.access, shaderInfo.storageTexture.access); + + DAWN_INVALID_IF( + layoutInfo.storageTexture.format != shaderInfo.storageTexture.format, + "The layout's binding format (%s) doesn't match the shader's binding " + "format (%s).", + layoutInfo.storageTexture.format, shaderInfo.storageTexture.format); + + DAWN_INVALID_IF(layoutInfo.storageTexture.viewDimension != + shaderInfo.storageTexture.viewDimension, + "The layout's binding dimension (%s) doesn't match the " + "shader's binding dimension (%s).", + layoutInfo.storageTexture.viewDimension, + shaderInfo.storageTexture.viewDimension); + break; + } + + case BindingInfoType::Buffer: { + // Binding mismatch between shader and bind group is invalid. For example, a + // writable binding in the shader with a readonly storage buffer in the bind + // group layout is invalid. For internal usage with internal shaders, a storage + // binding in the shader with an internal storage buffer in the bind group + // layout is also valid. + bool validBindingConversion = + (layoutInfo.buffer.type == kInternalStorageBufferBinding && + shaderInfo.buffer.type == wgpu::BufferBindingType::Storage); + + DAWN_INVALID_IF( + layoutInfo.buffer.type != shaderInfo.buffer.type && !validBindingConversion, + "The buffer type in the shader (%s) is not compatible with the type in the " + "layout (%s).", + shaderInfo.buffer.type, layoutInfo.buffer.type); + + DAWN_INVALID_IF( + layoutInfo.buffer.minBindingSize != 0 && + shaderInfo.buffer.minBindingSize > layoutInfo.buffer.minBindingSize, + "The shader uses more bytes of the buffer (%u) than the layout's " + "minBindingSize (%u).", + shaderInfo.buffer.minBindingSize, layoutInfo.buffer.minBindingSize); + break; + } + + case BindingInfoType::Sampler: + DAWN_INVALID_IF( + (layoutInfo.sampler.type == wgpu::SamplerBindingType::Comparison) != + shaderInfo.sampler.isComparison, + "The sampler type in the shader (comparison: %u) doesn't match the type in " + "the layout (comparison: %u).", + shaderInfo.sampler.isComparison, + layoutInfo.sampler.type == wgpu::SamplerBindingType::Comparison); + break; + + case BindingInfoType::ExternalTexture: { + UNREACHABLE(); + break; + } + } + + return {}; + } + MaybeError ValidateCompatibilityWithBindGroupLayout(DeviceBase* device, + BindGroupIndex group, + const EntryPointMetadata& entryPoint, + const BindGroupLayoutBase* layout) { + // Iterate over all bindings used by this group in the shader, and find the + // corresponding binding in the BindGroupLayout, if it exists. + for (const auto& [bindingId, bindingInfo] : entryPoint.bindings[group]) { + DAWN_TRY_CONTEXT(ValidateCompatibilityOfSingleBindingWithLayout( + device, layout, entryPoint.stage, bindingId, bindingInfo), + "validating that the entry-point's declaration for @group(%u) " + "@binding(%u) matches %s", + static_cast<uint32_t>(group), static_cast<uint32_t>(bindingId), + layout); + } + + return {}; + } + + ResultOrError<std::unique_ptr<EntryPointMetadata>> ReflectEntryPointUsingTint( + const DeviceBase* device, + tint::inspector::Inspector* inspector, + const tint::inspector::EntryPoint& entryPoint) { + const CombinedLimits& limits = device->GetLimits(); + constexpr uint32_t kMaxInterStageShaderLocation = kMaxInterStageShaderVariables - 1; + + std::unique_ptr<EntryPointMetadata> metadata = std::make_unique<EntryPointMetadata>(); + + // Returns the invalid argument, and if it is true additionally store the formatted + // error in metadata.infringedLimits. This is to delay the emission of these validation + // errors until the entry point is used. +#define DelayedInvalidIf(invalid, ...) \ + ([&]() { \ + if (invalid) { \ + metadata->infringedLimitErrors.push_back(absl::StrFormat(__VA_ARGS__)); \ + } \ + return invalid; \ + })() + + if (!entryPoint.overridable_constants.empty()) { + DAWN_INVALID_IF(device->IsToggleEnabled(Toggle::DisallowUnsafeAPIs), + "Pipeline overridable constants are disallowed because they " + "are partially implemented."); + + const auto& name2Id = inspector->GetConstantNameToIdMap(); + const auto& id2Scalar = inspector->GetConstantIDs(); + + for (auto& c : entryPoint.overridable_constants) { + uint32_t id = name2Id.at(c.name); + OverridableConstantScalar defaultValue; + if (c.is_initialized) { + // if it is initialized, the scalar must exist + const auto& scalar = id2Scalar.at(id); + if (scalar.IsBool()) { + defaultValue.b = scalar.AsBool(); + } else if (scalar.IsU32()) { + defaultValue.u32 = scalar.AsU32(); + } else if (scalar.IsI32()) { + defaultValue.i32 = scalar.AsI32(); + } else if (scalar.IsFloat()) { + defaultValue.f32 = scalar.AsFloat(); + } else { + UNREACHABLE(); + } + } + EntryPointMetadata::OverridableConstant constant = { + id, FromTintOverridableConstantType(c.type), c.is_initialized, + defaultValue}; + + std::string identifier = + c.is_numeric_id_specified ? std::to_string(constant.id) : c.name; + metadata->overridableConstants[identifier] = constant; + + if (!c.is_initialized) { + auto [_, inserted] = metadata->uninitializedOverridableConstants.emplace( + std::move(identifier)); + // The insertion should have taken place + ASSERT(inserted); + } else { + auto [_, inserted] = metadata->initializedOverridableConstants.emplace( + std::move(identifier)); + // The insertion should have taken place + ASSERT(inserted); + } + } + } + + DAWN_TRY_ASSIGN(metadata->stage, TintPipelineStageToShaderStage(entryPoint.stage)); + + if (metadata->stage == SingleShaderStage::Compute) { + DelayedInvalidIf( + entryPoint.workgroup_size_x > limits.v1.maxComputeWorkgroupSizeX || + entryPoint.workgroup_size_y > limits.v1.maxComputeWorkgroupSizeY || + entryPoint.workgroup_size_z > limits.v1.maxComputeWorkgroupSizeZ, + "Entry-point uses workgroup_size(%u, %u, %u) that exceeds the " + "maximum allowed (%u, %u, %u).", + entryPoint.workgroup_size_x, entryPoint.workgroup_size_y, + entryPoint.workgroup_size_z, limits.v1.maxComputeWorkgroupSizeX, + limits.v1.maxComputeWorkgroupSizeY, limits.v1.maxComputeWorkgroupSizeZ); + + // Dimensions have already been validated against their individual limits above. + // Cast to uint64_t to avoid overflow in this multiplication. + uint64_t numInvocations = static_cast<uint64_t>(entryPoint.workgroup_size_x) * + entryPoint.workgroup_size_y * entryPoint.workgroup_size_z; + DelayedInvalidIf(numInvocations > limits.v1.maxComputeInvocationsPerWorkgroup, + "The total number of workgroup invocations (%u) exceeds the " + "maximum allowed (%u).", + numInvocations, limits.v1.maxComputeInvocationsPerWorkgroup); + + const size_t workgroupStorageSize = + inspector->GetWorkgroupStorageSize(entryPoint.name); + DelayedInvalidIf(workgroupStorageSize > limits.v1.maxComputeWorkgroupStorageSize, + "The total use of workgroup storage (%u bytes) is larger than " + "the maximum allowed (%u bytes).", + workgroupStorageSize, limits.v1.maxComputeWorkgroupStorageSize); + + metadata->localWorkgroupSize.x = entryPoint.workgroup_size_x; + metadata->localWorkgroupSize.y = entryPoint.workgroup_size_y; + metadata->localWorkgroupSize.z = entryPoint.workgroup_size_z; + + metadata->usesNumWorkgroups = entryPoint.num_workgroups_used; + } + + if (metadata->stage == SingleShaderStage::Vertex) { + for (const auto& inputVar : entryPoint.input_variables) { + uint32_t unsanitizedLocation = inputVar.location_decoration; + if (DelayedInvalidIf(unsanitizedLocation >= kMaxVertexAttributes, + "Vertex input variable \"%s\" has a location (%u) that " + "exceeds the maximum (%u)", + inputVar.name, unsanitizedLocation, + kMaxVertexAttributes)) { + continue; + } + + VertexAttributeLocation location(static_cast<uint8_t>(unsanitizedLocation)); + DAWN_TRY_ASSIGN( + metadata->vertexInputBaseTypes[location], + TintComponentTypeToVertexFormatBaseType(inputVar.component_type)); + metadata->usedVertexInputs.set(location); + } + + // [[position]] must be declared in a vertex shader but is not exposed as an + // output variable by Tint so we directly add its components to the total. + uint32_t totalInterStageShaderComponents = 4; + for (const auto& outputVar : entryPoint.output_variables) { + EntryPointMetadata::InterStageVariableInfo variable; + DAWN_TRY_ASSIGN(variable.baseType, TintComponentTypeToInterStageComponentType( + outputVar.component_type)); + DAWN_TRY_ASSIGN( + variable.componentCount, + TintCompositionTypeToInterStageComponentCount(outputVar.composition_type)); + DAWN_TRY_ASSIGN( + variable.interpolationType, + TintInterpolationTypeToInterpolationType(outputVar.interpolation_type)); + DAWN_TRY_ASSIGN(variable.interpolationSampling, + TintInterpolationSamplingToInterpolationSamplingType( + outputVar.interpolation_sampling)); + totalInterStageShaderComponents += variable.componentCount; + + uint32_t location = outputVar.location_decoration; + if (DelayedInvalidIf(location > kMaxInterStageShaderLocation, + "Vertex output variable \"%s\" has a location (%u) that " + "exceeds the maximum (%u).", + outputVar.name, location, kMaxInterStageShaderLocation)) { + continue; + } + + metadata->usedInterStageVariables.set(location); + metadata->interStageVariables[location] = variable; + } + + DelayedInvalidIf( + totalInterStageShaderComponents > kMaxInterStageShaderComponents, + "Total vertex output components count (%u) exceeds the maximum (%u).", + totalInterStageShaderComponents, kMaxInterStageShaderComponents); + } + + if (metadata->stage == SingleShaderStage::Fragment) { + uint32_t totalInterStageShaderComponents = 0; + for (const auto& inputVar : entryPoint.input_variables) { + EntryPointMetadata::InterStageVariableInfo variable; + DAWN_TRY_ASSIGN(variable.baseType, TintComponentTypeToInterStageComponentType( + inputVar.component_type)); + DAWN_TRY_ASSIGN( + variable.componentCount, + TintCompositionTypeToInterStageComponentCount(inputVar.composition_type)); + DAWN_TRY_ASSIGN( + variable.interpolationType, + TintInterpolationTypeToInterpolationType(inputVar.interpolation_type)); + DAWN_TRY_ASSIGN(variable.interpolationSampling, + TintInterpolationSamplingToInterpolationSamplingType( + inputVar.interpolation_sampling)); + totalInterStageShaderComponents += variable.componentCount; + + uint32_t location = inputVar.location_decoration; + if (DelayedInvalidIf(location > kMaxInterStageShaderLocation, + "Fragment input variable \"%s\" has a location (%u) that " + "exceeds the maximum (%u).", + inputVar.name, location, kMaxInterStageShaderLocation)) { + continue; + } + + metadata->usedInterStageVariables.set(location); + metadata->interStageVariables[location] = variable; + } + + if (entryPoint.front_facing_used) { + totalInterStageShaderComponents += 1; + } + if (entryPoint.input_sample_mask_used) { + totalInterStageShaderComponents += 1; + } + if (entryPoint.sample_index_used) { + totalInterStageShaderComponents += 1; + } + if (entryPoint.input_position_used) { + totalInterStageShaderComponents += 4; + } + + DelayedInvalidIf( + totalInterStageShaderComponents > kMaxInterStageShaderComponents, + "Total fragment input components count (%u) exceeds the maximum (%u).", + totalInterStageShaderComponents, kMaxInterStageShaderComponents); + + for (const auto& outputVar : entryPoint.output_variables) { + EntryPointMetadata::FragmentOutputVariableInfo variable; + DAWN_TRY_ASSIGN(variable.baseType, TintComponentTypeToTextureComponentType( + outputVar.component_type)); + DAWN_TRY_ASSIGN( + variable.componentCount, + TintCompositionTypeToInterStageComponentCount(outputVar.composition_type)); + ASSERT(variable.componentCount <= 4); + + uint32_t unsanitizedAttachment = outputVar.location_decoration; + if (DelayedInvalidIf(unsanitizedAttachment >= kMaxColorAttachments, + "Fragment output variable \"%s\" has a location (%u) that " + "exceeds the maximum (%u).", + outputVar.name, unsanitizedAttachment, + kMaxColorAttachments)) { + continue; + } + + ColorAttachmentIndex attachment(static_cast<uint8_t>(unsanitizedAttachment)); + metadata->fragmentOutputVariables[attachment] = variable; + metadata->fragmentOutputsWritten.set(attachment); + } + } + + for (const tint::inspector::ResourceBinding& resource : + inspector->GetResourceBindings(entryPoint.name)) { + ShaderBindingInfo info; + + info.bindingType = TintResourceTypeToBindingInfoType(resource.resource_type); + + switch (info.bindingType) { + case BindingInfoType::Buffer: + info.buffer.minBindingSize = resource.size_no_padding; + DAWN_TRY_ASSIGN(info.buffer.type, TintResourceTypeToBufferBindingType( + resource.resource_type)); + break; + case BindingInfoType::Sampler: + switch (resource.resource_type) { + case tint::inspector::ResourceBinding::ResourceType::kSampler: + info.sampler.isComparison = false; + break; + case tint::inspector::ResourceBinding::ResourceType::kComparisonSampler: + info.sampler.isComparison = true; + break; + default: + UNREACHABLE(); + } + break; + case BindingInfoType::Texture: + info.texture.viewDimension = + TintTextureDimensionToTextureViewDimension(resource.dim); + if (resource.resource_type == + tint::inspector::ResourceBinding::ResourceType::kDepthTexture || + resource.resource_type == tint::inspector::ResourceBinding:: + ResourceType::kDepthMultisampledTexture) { + info.texture.compatibleSampleTypes = SampleTypeBit::Depth; + } else { + info.texture.compatibleSampleTypes = + TintSampledKindToSampleTypeBit(resource.sampled_kind); + } + info.texture.multisampled = + resource.resource_type == tint::inspector::ResourceBinding:: + ResourceType::kMultisampledTexture || + resource.resource_type == tint::inspector::ResourceBinding:: + ResourceType::kDepthMultisampledTexture; + + break; + case BindingInfoType::StorageTexture: + DAWN_TRY_ASSIGN( + info.storageTexture.access, + TintResourceTypeToStorageTextureAccess(resource.resource_type)); + info.storageTexture.format = + TintImageFormatToTextureFormat(resource.image_format); + info.storageTexture.viewDimension = + TintTextureDimensionToTextureViewDimension(resource.dim); + + break; + case BindingInfoType::ExternalTexture: + break; + default: + return DAWN_VALIDATION_ERROR("Unknown binding type in Shader"); + } + + BindingNumber bindingNumber(resource.binding); + BindGroupIndex bindGroupIndex(resource.bind_group); + + if (DelayedInvalidIf(bindGroupIndex >= kMaxBindGroupsTyped, + "The entry-point uses a binding with a group decoration (%u) " + "that exceeds the maximum (%u).", + resource.bind_group, kMaxBindGroups) || + DelayedInvalidIf(bindingNumber > kMaxBindingNumberTyped, + "Binding number (%u) exceeds the maximum binding number (%u).", + uint32_t(bindingNumber), uint32_t(kMaxBindingNumberTyped))) { + continue; + } + + const auto& [binding, inserted] = + metadata->bindings[bindGroupIndex].emplace(bindingNumber, info); + DAWN_INVALID_IF(!inserted, + "Entry-point has a duplicate binding for (group:%u, binding:%u).", + resource.binding, resource.bind_group); + } + + std::vector<tint::inspector::SamplerTexturePair> samplerTextureUses = + inspector->GetSamplerTextureUses(entryPoint.name); + metadata->samplerTexturePairs.reserve(samplerTextureUses.size()); + std::transform(samplerTextureUses.begin(), samplerTextureUses.end(), + std::back_inserter(metadata->samplerTexturePairs), + [](const tint::inspector::SamplerTexturePair& pair) { + EntryPointMetadata::SamplerTexturePair result; + result.sampler = {BindGroupIndex(pair.sampler_binding_point.group), + BindingNumber(pair.sampler_binding_point.binding)}; + result.texture = {BindGroupIndex(pair.texture_binding_point.group), + BindingNumber(pair.texture_binding_point.binding)}; + return result; + }); + +#undef DelayedInvalidIf + return std::move(metadata); + } + + ResultOrError<EntryPointMetadataTable> ReflectShaderUsingTint( + const DeviceBase* device, + const tint::Program* program) { + ASSERT(program->IsValid()); + + tint::inspector::Inspector inspector(program); + std::vector<tint::inspector::EntryPoint> entryPoints = inspector.GetEntryPoints(); + DAWN_INVALID_IF(inspector.has_error(), "Tint Reflection failure: Inspector: %s\n", + inspector.error()); + + EntryPointMetadataTable result; + + for (const tint::inspector::EntryPoint& entryPoint : entryPoints) { + std::unique_ptr<EntryPointMetadata> metadata; + DAWN_TRY_ASSIGN_CONTEXT(metadata, + ReflectEntryPointUsingTint(device, &inspector, entryPoint), + "processing entry point \"%s\".", entryPoint.name); + + ASSERT(result.count(entryPoint.name) == 0); + result[entryPoint.name] = std::move(metadata); + } + return std::move(result); + } + } // anonymous namespace + + ShaderModuleParseResult::ShaderModuleParseResult() = default; + ShaderModuleParseResult::~ShaderModuleParseResult() = default; + + ShaderModuleParseResult::ShaderModuleParseResult(ShaderModuleParseResult&& rhs) = default; + + ShaderModuleParseResult& ShaderModuleParseResult::operator=(ShaderModuleParseResult&& rhs) = + default; + + bool ShaderModuleParseResult::HasParsedShader() const { + return tintProgram != nullptr; + } + + // TintSource is a PIMPL container for a tint::Source::File, which needs to be kept alive for as + // long as tint diagnostics are inspected / printed. + class TintSource { + public: + template <typename... ARGS> + TintSource(ARGS&&... args) : file(std::forward<ARGS>(args)...) { + } + + tint::Source::File file; + }; + + MaybeError ValidateShaderModuleDescriptor(DeviceBase* device, + const ShaderModuleDescriptor* descriptor, + ShaderModuleParseResult* parseResult, + OwnedCompilationMessages* outMessages) { + ASSERT(parseResult != nullptr); + + const ChainedStruct* chainedDescriptor = descriptor->nextInChain; + DAWN_INVALID_IF(chainedDescriptor == nullptr, + "Shader module descriptor missing chained descriptor"); + + // For now only a single SPIRV or WGSL subdescriptor is allowed. + DAWN_TRY(ValidateSingleSType(chainedDescriptor, wgpu::SType::ShaderModuleSPIRVDescriptor, + wgpu::SType::ShaderModuleWGSLDescriptor)); + + ScopedTintICEHandler scopedICEHandler(device); + + const ShaderModuleSPIRVDescriptor* spirvDesc = nullptr; + FindInChain(chainedDescriptor, &spirvDesc); + const ShaderModuleWGSLDescriptor* wgslDesc = nullptr; + FindInChain(chainedDescriptor, &wgslDesc); + + // We have a temporary toggle to force the SPIRV ingestion to go through a WGSL + // intermediate step. It is done by switching the spirvDesc for a wgslDesc below. + ShaderModuleWGSLDescriptor newWgslDesc; + std::string newWgslCode; + if (spirvDesc && device->IsToggleEnabled(Toggle::ForceWGSLStep)) { + std::vector<uint32_t> spirv(spirvDesc->code, spirvDesc->code + spirvDesc->codeSize); + tint::Program program; + DAWN_TRY_ASSIGN(program, ParseSPIRV(spirv, outMessages)); + + tint::writer::wgsl::Options options; + auto result = tint::writer::wgsl::Generate(&program, options); + DAWN_INVALID_IF(!result.success, "Tint WGSL failure: Generator: %s", result.error); + + newWgslCode = std::move(result.wgsl); + newWgslDesc.source = newWgslCode.c_str(); + + spirvDesc = nullptr; + wgslDesc = &newWgslDesc; + } + + if (spirvDesc) { + DAWN_INVALID_IF(device->IsToggleEnabled(Toggle::DisallowSpirv), + "SPIR-V is disallowed."); + + std::vector<uint32_t> spirv(spirvDesc->code, spirvDesc->code + spirvDesc->codeSize); + tint::Program program; + DAWN_TRY_ASSIGN(program, ParseSPIRV(spirv, outMessages)); + parseResult->tintProgram = std::make_unique<tint::Program>(std::move(program)); + } else if (wgslDesc) { + auto tintSource = std::make_unique<TintSource>("", wgslDesc->source); + + if (device->IsToggleEnabled(Toggle::DumpShaders)) { + std::ostringstream dumpedMsg; + dumpedMsg << "// Dumped WGSL:" << std::endl << wgslDesc->source; + device->EmitLog(WGPULoggingType_Info, dumpedMsg.str().c_str()); + } + + tint::Program program; + DAWN_TRY_ASSIGN(program, ParseWGSL(&tintSource->file, outMessages)); + parseResult->tintProgram = std::make_unique<tint::Program>(std::move(program)); + parseResult->tintSource = std::move(tintSource); + } + + return {}; + } + + RequiredBufferSizes ComputeRequiredBufferSizesForLayout(const EntryPointMetadata& entryPoint, + const PipelineLayoutBase* layout) { + RequiredBufferSizes bufferSizes; + for (BindGroupIndex group : IterateBitSet(layout->GetBindGroupLayoutsMask())) { + bufferSizes[group] = GetBindGroupMinBufferSizes(entryPoint.bindings[group], + layout->GetBindGroupLayout(group)); + } + + return bufferSizes; + } + + ResultOrError<tint::Program> RunTransforms(tint::transform::Transform* transform, + const tint::Program* program, + const tint::transform::DataMap& inputs, + tint::transform::DataMap* outputs, + OwnedCompilationMessages* outMessages) { + tint::transform::Output output = transform->Run(program, inputs); + if (outMessages != nullptr) { + outMessages->AddMessages(output.program.Diagnostics()); + } + DAWN_INVALID_IF(!output.program.IsValid(), "Tint program failure: %s\n", + output.program.Diagnostics().str()); + if (outputs != nullptr) { + *outputs = std::move(output.data); + } + return std::move(output.program); + } + + void AddVertexPullingTransformConfig(const RenderPipelineBase& renderPipeline, + const std::string& entryPoint, + BindGroupIndex pullingBufferBindingSet, + tint::transform::DataMap* transformInputs) { + tint::transform::VertexPulling::Config cfg; + cfg.entry_point_name = entryPoint; + cfg.pulling_group = static_cast<uint32_t>(pullingBufferBindingSet); + + cfg.vertex_state.resize(renderPipeline.GetVertexBufferCount()); + for (VertexBufferSlot slot : IterateBitSet(renderPipeline.GetVertexBufferSlotsUsed())) { + const VertexBufferInfo& dawnInfo = renderPipeline.GetVertexBuffer(slot); + tint::transform::VertexBufferLayoutDescriptor* tintInfo = + &cfg.vertex_state[static_cast<uint8_t>(slot)]; + + tintInfo->array_stride = dawnInfo.arrayStride; + tintInfo->step_mode = ToTintVertexStepMode(dawnInfo.stepMode); + } + + for (VertexAttributeLocation location : + IterateBitSet(renderPipeline.GetAttributeLocationsUsed())) { + const VertexAttributeInfo& dawnInfo = renderPipeline.GetAttribute(location); + tint::transform::VertexAttributeDescriptor tintInfo; + tintInfo.format = ToTintVertexFormat(dawnInfo.format); + tintInfo.offset = dawnInfo.offset; + tintInfo.shader_location = static_cast<uint32_t>(static_cast<uint8_t>(location)); + + uint8_t vertexBufferSlot = static_cast<uint8_t>(dawnInfo.vertexBufferSlot); + cfg.vertex_state[vertexBufferSlot].attributes.push_back(tintInfo); + } + + transformInputs->Add<tint::transform::VertexPulling::Config>(cfg); + } + + MaybeError ValidateCompatibilityWithPipelineLayout(DeviceBase* device, + const EntryPointMetadata& entryPoint, + const PipelineLayoutBase* layout) { + for (BindGroupIndex group : IterateBitSet(layout->GetBindGroupLayoutsMask())) { + DAWN_TRY_CONTEXT(ValidateCompatibilityWithBindGroupLayout( + device, group, entryPoint, layout->GetBindGroupLayout(group)), + "validating the entry-point's compatibility for group %u with %s", + static_cast<uint32_t>(group), layout->GetBindGroupLayout(group)); + } + + for (BindGroupIndex group : IterateBitSet(~layout->GetBindGroupLayoutsMask())) { + DAWN_INVALID_IF(entryPoint.bindings[group].size() > 0, + "The entry-point uses bindings in group %u but %s doesn't have a " + "BindGroupLayout for this index", + static_cast<uint32_t>(group), layout); + } + + // Validate that filtering samplers are not used with unfilterable textures. + for (const auto& pair : entryPoint.samplerTexturePairs) { + const BindGroupLayoutBase* samplerBGL = layout->GetBindGroupLayout(pair.sampler.group); + const BindingInfo& samplerInfo = + samplerBGL->GetBindingInfo(samplerBGL->GetBindingIndex(pair.sampler.binding)); + if (samplerInfo.sampler.type != wgpu::SamplerBindingType::Filtering) { + continue; + } + const BindGroupLayoutBase* textureBGL = layout->GetBindGroupLayout(pair.texture.group); + const BindingInfo& textureInfo = + textureBGL->GetBindingInfo(textureBGL->GetBindingIndex(pair.texture.binding)); + + ASSERT(textureInfo.bindingType != BindingInfoType::Buffer && + textureInfo.bindingType != BindingInfoType::Sampler && + textureInfo.bindingType != BindingInfoType::StorageTexture); + + if (textureInfo.bindingType != BindingInfoType::Texture) { + continue; + } + + // Uint/sint can't be statically used with a sampler, so they any + // texture bindings reflected must be float or depth textures. If + // the shader uses a float/depth texture but the bind group layout + // specifies a uint/sint texture binding, + // |ValidateCompatibilityWithBindGroupLayout| will fail since the + // sampleType does not match. + ASSERT(textureInfo.texture.sampleType != wgpu::TextureSampleType::Undefined && + textureInfo.texture.sampleType != wgpu::TextureSampleType::Uint && + textureInfo.texture.sampleType != wgpu::TextureSampleType::Sint); + + DAWN_INVALID_IF( + textureInfo.texture.sampleType == wgpu::TextureSampleType::UnfilterableFloat, + "Texture binding (group:%u, binding:%u) is %s but used statically with a sampler " + "(group:%u, binding:%u) that's %s", + static_cast<uint32_t>(pair.texture.group), + static_cast<uint32_t>(pair.texture.binding), + wgpu::TextureSampleType::UnfilterableFloat, + static_cast<uint32_t>(pair.sampler.group), + static_cast<uint32_t>(pair.sampler.binding), wgpu::SamplerBindingType::Filtering); + } + + return {}; + } + + // ShaderModuleBase + + ShaderModuleBase::ShaderModuleBase(DeviceBase* device, + const ShaderModuleDescriptor* descriptor, + ApiObjectBase::UntrackedByDeviceTag tag) + : ApiObjectBase(device, descriptor->label), mType(Type::Undefined) { + ASSERT(descriptor->nextInChain != nullptr); + const ShaderModuleSPIRVDescriptor* spirvDesc = nullptr; + FindInChain(descriptor->nextInChain, &spirvDesc); + const ShaderModuleWGSLDescriptor* wgslDesc = nullptr; + FindInChain(descriptor->nextInChain, &wgslDesc); + ASSERT(spirvDesc || wgslDesc); + + if (spirvDesc) { + mType = Type::Spirv; + mOriginalSpirv.assign(spirvDesc->code, spirvDesc->code + spirvDesc->codeSize); + } else if (wgslDesc) { + mType = Type::Wgsl; + mWgsl = std::string(wgslDesc->source); + } + } + + ShaderModuleBase::ShaderModuleBase(DeviceBase* device, const ShaderModuleDescriptor* descriptor) + : ShaderModuleBase(device, descriptor, kUntrackedByDevice) { + TrackInDevice(); + } + + ShaderModuleBase::ShaderModuleBase(DeviceBase* device) + : ApiObjectBase(device, kLabelNotImplemented) { + TrackInDevice(); + } + + ShaderModuleBase::ShaderModuleBase(DeviceBase* device, ObjectBase::ErrorTag tag) + : ApiObjectBase(device, tag), mType(Type::Undefined) { + } + + ShaderModuleBase::~ShaderModuleBase() = default; + + void ShaderModuleBase::DestroyImpl() { + if (IsCachedReference()) { + // Do not uncache the actual cached object if we are a blueprint. + GetDevice()->UncacheShaderModule(this); + } + } + + // static + Ref<ShaderModuleBase> ShaderModuleBase::MakeError(DeviceBase* device) { + return AcquireRef(new ShaderModuleBase(device, ObjectBase::kError)); + } + + ObjectType ShaderModuleBase::GetType() const { + return ObjectType::ShaderModule; + } + + bool ShaderModuleBase::HasEntryPoint(const std::string& entryPoint) const { + return mEntryPoints.count(entryPoint) > 0; + } + + const EntryPointMetadata& ShaderModuleBase::GetEntryPoint(const std::string& entryPoint) const { + ASSERT(HasEntryPoint(entryPoint)); + return *mEntryPoints.at(entryPoint); + } + + size_t ShaderModuleBase::ComputeContentHash() { + ObjectContentHasher recorder; + recorder.Record(mType); + recorder.Record(mOriginalSpirv); + recorder.Record(mWgsl); + return recorder.GetContentHash(); + } + + bool ShaderModuleBase::EqualityFunc::operator()(const ShaderModuleBase* a, + const ShaderModuleBase* b) const { + return a->mType == b->mType && a->mOriginalSpirv == b->mOriginalSpirv && + a->mWgsl == b->mWgsl; + } + + const tint::Program* ShaderModuleBase::GetTintProgram() const { + ASSERT(mTintProgram); + return mTintProgram.get(); + } + + void ShaderModuleBase::APIGetCompilationInfo(wgpu::CompilationInfoCallback callback, + void* userdata) { + if (callback == nullptr) { + return; + } + + callback(WGPUCompilationInfoRequestStatus_Success, + mCompilationMessages->GetCompilationInfo(), userdata); + } + + void ShaderModuleBase::InjectCompilationMessages( + std::unique_ptr<OwnedCompilationMessages> compilationMessages) { + // TODO(dawn:944): ensure the InjectCompilationMessages is properly handled for shader + // module returned from cache. + // InjectCompilationMessages should be called only once for a shader module, after it is + // created. However currently InjectCompilationMessages may be called on a shader module + // returned from cache rather than newly created, and violate the rule. We just skip the + // injection in this case for now, but a proper solution including ensure the cache goes + // before the validation is required. + if (mCompilationMessages != nullptr) { + return; + } + // Move the compilationMessages into the shader module and emit the tint errors and warnings + mCompilationMessages = std::move(compilationMessages); + + // Emit the formatted Tint errors and warnings within the moved compilationMessages + const std::vector<std::string>& formattedTintMessages = + mCompilationMessages->GetFormattedTintMessages(); + if (formattedTintMessages.empty()) { + return; + } + std::ostringstream t; + for (auto pMessage = formattedTintMessages.begin(); pMessage != formattedTintMessages.end(); + pMessage++) { + if (pMessage != formattedTintMessages.begin()) { + t << std::endl; + } + t << *pMessage; + } + this->GetDevice()->EmitLog(WGPULoggingType_Warning, t.str().c_str()); + } + + OwnedCompilationMessages* ShaderModuleBase::GetCompilationMessages() const { + return mCompilationMessages.get(); + } + + // static + void ShaderModuleBase::AddExternalTextureTransform(const PipelineLayoutBase* layout, + tint::transform::Manager* transformManager, + tint::transform::DataMap* transformInputs) { + tint::transform::MultiplanarExternalTexture::BindingsMap newBindingsMap; + for (BindGroupIndex i : IterateBitSet(layout->GetBindGroupLayoutsMask())) { + const BindGroupLayoutBase* bgl = layout->GetBindGroupLayout(i); + + for (const auto& expansion : bgl->GetExternalTextureBindingExpansionMap()) { + newBindingsMap[{static_cast<uint32_t>(i), + static_cast<uint32_t>(expansion.second.plane0)}] = { + {static_cast<uint32_t>(i), static_cast<uint32_t>(expansion.second.plane1)}, + {static_cast<uint32_t>(i), static_cast<uint32_t>(expansion.second.params)}}; + } + } + + if (!newBindingsMap.empty()) { + transformManager->Add<tint::transform::MultiplanarExternalTexture>(); + transformInputs->Add<tint::transform::MultiplanarExternalTexture::NewBindingPoints>( + newBindingsMap); + } + } + + MaybeError ShaderModuleBase::InitializeBase(ShaderModuleParseResult* parseResult) { + mTintProgram = std::move(parseResult->tintProgram); + mTintSource = std::move(parseResult->tintSource); + + DAWN_TRY_ASSIGN(mEntryPoints, ReflectShaderUsingTint(GetDevice(), mTintProgram.get())); + return {}; + } + + size_t PipelineLayoutEntryPointPairHashFunc::operator()( + const PipelineLayoutEntryPointPair& pair) const { + size_t hash = 0; + HashCombine(&hash, pair.first, pair.second); + return hash; + } + +} // namespace dawn::native
diff --git a/src/dawn/native/ShaderModule.h b/src/dawn/native/ShaderModule.h new file mode 100644 index 0000000..ff643eb --- /dev/null +++ b/src/dawn/native/ShaderModule.h
@@ -0,0 +1,314 @@ +// Copyright 2017 The Dawn Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#ifndef DAWNNATIVE_SHADERMODULE_H_ +#define DAWNNATIVE_SHADERMODULE_H_ + +#include "dawn/common/Constants.h" +#include "dawn/common/ityp_array.h" +#include "dawn/native/BindingInfo.h" +#include "dawn/native/CachedObject.h" +#include "dawn/native/CompilationMessages.h" +#include "dawn/native/Error.h" +#include "dawn/native/Format.h" +#include "dawn/native/Forward.h" +#include "dawn/native/IntegerTypes.h" +#include "dawn/native/ObjectBase.h" +#include "dawn/native/PerStage.h" +#include "dawn/native/VertexFormat.h" +#include "dawn/native/dawn_platform.h" + +#include <bitset> +#include <map> +#include <unordered_map> +#include <unordered_set> +#include <vector> + +namespace tint { + + class Program; + + namespace transform { + class DataMap; + class Manager; + class Transform; + class VertexPulling; + } // namespace transform + +} // namespace tint + +namespace dawn::native { + + struct EntryPointMetadata; + + // Base component type of an inter-stage variable + enum class InterStageComponentType { + Sint, + Uint, + Float, + }; + + enum class InterpolationType { + Perspective, + Linear, + Flat, + }; + + enum class InterpolationSampling { + None, + Center, + Centroid, + Sample, + }; + + using PipelineLayoutEntryPointPair = std::pair<PipelineLayoutBase*, std::string>; + struct PipelineLayoutEntryPointPairHashFunc { + size_t operator()(const PipelineLayoutEntryPointPair& pair) const; + }; + + // A map from name to EntryPointMetadata. + using EntryPointMetadataTable = + std::unordered_map<std::string, std::unique_ptr<EntryPointMetadata>>; + + // Source for a tint program + class TintSource; + + struct ShaderModuleParseResult { + ShaderModuleParseResult(); + ~ShaderModuleParseResult(); + ShaderModuleParseResult(ShaderModuleParseResult&& rhs); + ShaderModuleParseResult& operator=(ShaderModuleParseResult&& rhs); + + bool HasParsedShader() const; + + std::unique_ptr<tint::Program> tintProgram; + std::unique_ptr<TintSource> tintSource; + }; + + MaybeError ValidateShaderModuleDescriptor(DeviceBase* device, + const ShaderModuleDescriptor* descriptor, + ShaderModuleParseResult* parseResult, + OwnedCompilationMessages* outMessages); + MaybeError ValidateCompatibilityWithPipelineLayout(DeviceBase* device, + const EntryPointMetadata& entryPoint, + const PipelineLayoutBase* layout); + + RequiredBufferSizes ComputeRequiredBufferSizesForLayout(const EntryPointMetadata& entryPoint, + const PipelineLayoutBase* layout); + ResultOrError<tint::Program> RunTransforms(tint::transform::Transform* transform, + const tint::Program* program, + const tint::transform::DataMap& inputs, + tint::transform::DataMap* outputs, + OwnedCompilationMessages* messages); + + /// Creates and adds the tint::transform::VertexPulling::Config to transformInputs. + void AddVertexPullingTransformConfig(const RenderPipelineBase& renderPipeline, + const std::string& entryPoint, + BindGroupIndex pullingBufferBindingSet, + tint::transform::DataMap* transformInputs); + + // Mirrors wgpu::SamplerBindingLayout but instead stores a single boolean + // for isComparison instead of a wgpu::SamplerBindingType enum. + struct ShaderSamplerBindingInfo { + bool isComparison; + }; + + // Mirrors wgpu::TextureBindingLayout but instead has a set of compatible sampleTypes + // instead of a single enum. + struct ShaderTextureBindingInfo { + SampleTypeBit compatibleSampleTypes; + wgpu::TextureViewDimension viewDimension; + bool multisampled; + }; + + // Per-binding shader metadata contains some SPIRV specific information in addition to + // most of the frontend per-binding information. + struct ShaderBindingInfo { + // The SPIRV ID of the resource. + uint32_t id; + uint32_t base_type_id; + + BindingNumber binding; + BindingInfoType bindingType; + + BufferBindingLayout buffer; + ShaderSamplerBindingInfo sampler; + ShaderTextureBindingInfo texture; + StorageTextureBindingLayout storageTexture; + }; + + using BindingGroupInfoMap = std::map<BindingNumber, ShaderBindingInfo>; + using BindingInfoArray = ityp::array<BindGroupIndex, BindingGroupInfoMap, kMaxBindGroups>; + + // The WebGPU overridable constants only support these scalar types + union OverridableConstantScalar { + // Use int32_t for boolean to initialize the full 32bit + int32_t b; + float f32; + int32_t i32; + uint32_t u32; + }; + + // Contains all the reflection data for a valid (ShaderModule, entryPoint, stage). They are + // stored in the ShaderModuleBase and destroyed only when the shader program is destroyed so + // pointers to EntryPointMetadata are safe to store as long as you also keep a Ref to the + // ShaderModuleBase. + struct EntryPointMetadata { + // It is valid for a shader to contain entry points that go over limits. To keep this + // structure with packed arrays and bitsets, we still validate against limits when + // doing reflection, but store the errors in this vector, for later use if the application + // tries to use the entry point. + std::vector<std::string> infringedLimitErrors; + + // bindings[G][B] is the reflection data for the binding defined with + // @group(G) @binding(B) in WGSL / SPIRV. + BindingInfoArray bindings; + + struct SamplerTexturePair { + BindingSlot sampler; + BindingSlot texture; + }; + std::vector<SamplerTexturePair> samplerTexturePairs; + + // The set of vertex attributes this entryPoint uses. + ityp::array<VertexAttributeLocation, VertexFormatBaseType, kMaxVertexAttributes> + vertexInputBaseTypes; + ityp::bitset<VertexAttributeLocation, kMaxVertexAttributes> usedVertexInputs; + + // An array to record the basic types (float, int and uint) of the fragment shader outputs. + struct FragmentOutputVariableInfo { + wgpu::TextureComponentType baseType; + uint8_t componentCount; + }; + ityp::array<ColorAttachmentIndex, FragmentOutputVariableInfo, kMaxColorAttachments> + fragmentOutputVariables; + ityp::bitset<ColorAttachmentIndex, kMaxColorAttachments> fragmentOutputsWritten; + + struct InterStageVariableInfo { + InterStageComponentType baseType; + uint32_t componentCount; + InterpolationType interpolationType; + InterpolationSampling interpolationSampling; + }; + // Now that we only support vertex and fragment stages, there can't be both inter-stage + // inputs and outputs in one shader stage. + std::bitset<kMaxInterStageShaderVariables> usedInterStageVariables; + std::array<InterStageVariableInfo, kMaxInterStageShaderVariables> interStageVariables; + + // The local workgroup size declared for a compute entry point (or 0s otehrwise). + Origin3D localWorkgroupSize; + + // The shader stage for this binding. + SingleShaderStage stage; + + struct OverridableConstant { + uint32_t id; + // Match tint::inspector::OverridableConstant::Type + // Bool is defined as a macro on linux X11 and cannot compile + enum class Type { Boolean, Float32, Uint32, Int32 } type; + + // If the constant doesn't not have an initializer in the shader + // Then it is required for the pipeline stage to have a constant record to initialize a + // value + bool isInitialized; + + // Store the default initialized value in shader + // This is used by metal backend as the function_constant does not have dafault values + // Initialized when isInitialized == true + OverridableConstantScalar defaultValue; + }; + + using OverridableConstantsMap = std::unordered_map<std::string, OverridableConstant>; + + // Map identifier to overridable constant + // Identifier is unique: either the variable name or the numeric ID if specified + OverridableConstantsMap overridableConstants; + + // Overridable constants that are not initialized in shaders + // They need value initialization from pipeline stage or it is a validation error + std::unordered_set<std::string> uninitializedOverridableConstants; + + // Store constants with shader initialized values as well + // This is used by metal backend to set values with default initializers that are not + // overridden + std::unordered_set<std::string> initializedOverridableConstants; + + bool usesNumWorkgroups = false; + }; + + class ShaderModuleBase : public ApiObjectBase, public CachedObject { + public: + ShaderModuleBase(DeviceBase* device, + const ShaderModuleDescriptor* descriptor, + ApiObjectBase::UntrackedByDeviceTag tag); + ShaderModuleBase(DeviceBase* device, const ShaderModuleDescriptor* descriptor); + ~ShaderModuleBase() override; + + static Ref<ShaderModuleBase> MakeError(DeviceBase* device); + + ObjectType GetType() const override; + + // Return true iff the program has an entrypoint called `entryPoint`. + bool HasEntryPoint(const std::string& entryPoint) const; + + // Return the metadata for the given `entryPoint`. HasEntryPoint with the same argument + // must be true. + const EntryPointMetadata& GetEntryPoint(const std::string& entryPoint) const; + + // Functions necessary for the unordered_set<ShaderModuleBase*>-based cache. + size_t ComputeContentHash() override; + + struct EqualityFunc { + bool operator()(const ShaderModuleBase* a, const ShaderModuleBase* b) const; + }; + + const tint::Program* GetTintProgram() const; + + void APIGetCompilationInfo(wgpu::CompilationInfoCallback callback, void* userdata); + + void InjectCompilationMessages( + std::unique_ptr<OwnedCompilationMessages> compilationMessages); + + OwnedCompilationMessages* GetCompilationMessages() const; + + protected: + // Constructor used only for mocking and testing. + ShaderModuleBase(DeviceBase* device); + void DestroyImpl() override; + + MaybeError InitializeBase(ShaderModuleParseResult* parseResult); + + static void AddExternalTextureTransform(const PipelineLayoutBase* layout, + tint::transform::Manager* transformManager, + tint::transform::DataMap* transformInputs); + + private: + ShaderModuleBase(DeviceBase* device, ObjectBase::ErrorTag tag); + + // The original data in the descriptor for caching. + enum class Type { Undefined, Spirv, Wgsl }; + Type mType; + std::vector<uint32_t> mOriginalSpirv; + std::string mWgsl; + + EntryPointMetadataTable mEntryPoints; + std::unique_ptr<tint::Program> mTintProgram; + std::unique_ptr<TintSource> mTintSource; // Keep the tint::Source::File alive + + std::unique_ptr<OwnedCompilationMessages> mCompilationMessages; + }; + +} // namespace dawn::native + +#endif // DAWNNATIVE_SHADERMODULE_H_
diff --git a/src/dawn/native/SpirvValidation.cpp b/src/dawn/native/SpirvValidation.cpp new file mode 100644 index 0000000..72eb8c1 --- /dev/null +++ b/src/dawn/native/SpirvValidation.cpp
@@ -0,0 +1,74 @@ +// Copyright 2021 The Dawn Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include "dawn/native/SpirvValidation.h" + +#include "dawn/native/Device.h" + +#include <spirv-tools/libspirv.hpp> +#include <sstream> + +namespace dawn::native { + + MaybeError ValidateSpirv(DeviceBase* device, + const std::vector<uint32_t>& spirv, + bool dumpSpirv) { + spvtools::SpirvTools spirvTools(SPV_ENV_VULKAN_1_1); + spirvTools.SetMessageConsumer([device](spv_message_level_t level, const char*, + const spv_position_t& position, + const char* message) { + WGPULoggingType wgpuLogLevel; + switch (level) { + case SPV_MSG_FATAL: + case SPV_MSG_INTERNAL_ERROR: + case SPV_MSG_ERROR: + wgpuLogLevel = WGPULoggingType_Error; + break; + case SPV_MSG_WARNING: + wgpuLogLevel = WGPULoggingType_Warning; + break; + case SPV_MSG_INFO: + wgpuLogLevel = WGPULoggingType_Info; + break; + default: + wgpuLogLevel = WGPULoggingType_Error; + break; + } + + std::ostringstream ss; + ss << "SPIRV line " << position.index << ": " << message << std::endl; + device->EmitLog(wgpuLogLevel, ss.str().c_str()); + }); + + const bool valid = spirvTools.Validate(spirv); + if (dumpSpirv || !valid) { + std::ostringstream dumpedMsg; + std::string disassembly; + if (spirvTools.Disassemble( + spirv, &disassembly, + SPV_BINARY_TO_TEXT_OPTION_FRIENDLY_NAMES | SPV_BINARY_TO_TEXT_OPTION_INDENT)) { + dumpedMsg << "/* Dumped generated SPIRV disassembly */" << std::endl << disassembly; + } else { + dumpedMsg << "/* Failed to disassemble generated SPIRV */"; + } + device->EmitLog(WGPULoggingType_Info, dumpedMsg.str().c_str()); + } + + DAWN_INVALID_IF(!valid, + "Produced invalid SPIRV. Please file a bug at https://crbug.com/tint."); + + return {}; + } + +} // namespace dawn::native
diff --git a/src/dawn/native/SpirvValidation.h b/src/dawn/native/SpirvValidation.h new file mode 100644 index 0000000..984ebcd --- /dev/null +++ b/src/dawn/native/SpirvValidation.h
@@ -0,0 +1,27 @@ +// Copyright 2021 The Dawn Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include "dawn/native/Error.h" + +#include <vector> + +namespace dawn::native { + + class DeviceBase; + + MaybeError ValidateSpirv(DeviceBase* device, + const std::vector<uint32_t>& spirv, + bool dumpSpirv); + +} // namespace dawn::native
diff --git a/src/dawn/native/StagingBuffer.cpp b/src/dawn/native/StagingBuffer.cpp new file mode 100644 index 0000000..a6c258c --- /dev/null +++ b/src/dawn/native/StagingBuffer.cpp
@@ -0,0 +1,29 @@ +// Copyright 2018 The Dawn Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include "dawn/native/StagingBuffer.h" + +namespace dawn::native { + + StagingBufferBase::StagingBufferBase(size_t size) : mBufferSize(size) { + } + + size_t StagingBufferBase::GetSize() const { + return mBufferSize; + } + + void* StagingBufferBase::GetMappedPointer() const { + return mMappedPointer; + } +} // namespace dawn::native
diff --git a/src/dawn/native/StagingBuffer.h b/src/dawn/native/StagingBuffer.h new file mode 100644 index 0000000..0ebb1c4 --- /dev/null +++ b/src/dawn/native/StagingBuffer.h
@@ -0,0 +1,41 @@ +// Copyright 2018 The Dawn Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#ifndef DAWNNATIVE_STAGINGBUFFER_H_ +#define DAWNNATIVE_STAGINGBUFFER_H_ + +#include "dawn/native/Error.h" + +namespace dawn::native { + + class StagingBufferBase { + public: + StagingBufferBase(size_t size); + virtual ~StagingBufferBase() = default; + + virtual MaybeError Initialize() = 0; + + void* GetMappedPointer() const; + size_t GetSize() const; + + protected: + void* mMappedPointer = nullptr; + + private: + const size_t mBufferSize; + }; + +} // namespace dawn::native + +#endif // DAWNNATIVE_STAGINGBUFFER_H_
diff --git a/src/dawn/native/Subresource.cpp b/src/dawn/native/Subresource.cpp new file mode 100644 index 0000000..6ebba9f --- /dev/null +++ b/src/dawn/native/Subresource.cpp
@@ -0,0 +1,136 @@ +// Copyright 2020 The Dawn Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include "dawn/native/Subresource.h" + +#include "dawn/common/Assert.h" +#include "dawn/native/Format.h" + +namespace dawn::native { + + Aspect ConvertSingleAspect(const Format& format, wgpu::TextureAspect aspect) { + Aspect aspectMask = ConvertAspect(format, aspect); + ASSERT(HasOneBit(aspectMask)); + return aspectMask; + } + + Aspect ConvertAspect(const Format& format, wgpu::TextureAspect aspect) { + Aspect aspectMask = SelectFormatAspects(format, aspect); + ASSERT(aspectMask != Aspect::None); + return aspectMask; + } + + Aspect ConvertViewAspect(const Format& format, wgpu::TextureAspect aspect) { + // Color view |format| must be treated as the same plane |aspect|. + if (format.aspects == Aspect::Color) { + switch (aspect) { + case wgpu::TextureAspect::Plane0Only: + return Aspect::Plane0; + case wgpu::TextureAspect::Plane1Only: + return Aspect::Plane1; + default: + break; + } + } + return ConvertAspect(format, aspect); + } + + Aspect SelectFormatAspects(const Format& format, wgpu::TextureAspect aspect) { + switch (aspect) { + case wgpu::TextureAspect::All: + return format.aspects; + case wgpu::TextureAspect::DepthOnly: + return format.aspects & Aspect::Depth; + case wgpu::TextureAspect::StencilOnly: + return format.aspects & Aspect::Stencil; + case wgpu::TextureAspect::Plane0Only: + return format.aspects & Aspect::Plane0; + case wgpu::TextureAspect::Plane1Only: + return format.aspects & Aspect::Plane1; + } + UNREACHABLE(); + } + + uint8_t GetAspectIndex(Aspect aspect) { + ASSERT(HasOneBit(aspect)); + switch (aspect) { + case Aspect::Color: + case Aspect::Depth: + case Aspect::Plane0: + case Aspect::CombinedDepthStencil: + return 0; + case Aspect::Plane1: + case Aspect::Stencil: + return 1; + default: + UNREACHABLE(); + } + } + + uint8_t GetAspectCount(Aspect aspects) { + // TODO(crbug.com/dawn/829): This should use popcount once Dawn has such a function. + // Note that we can't do a switch because compilers complain that Depth | Stencil is not + // a valid enum value. + if (aspects == Aspect::Color || aspects == Aspect::Depth || + aspects == Aspect::CombinedDepthStencil) { + return 1; + } else if (aspects == (Aspect::Plane0 | Aspect::Plane1)) { + return 2; + } else if (aspects == Aspect::Stencil) { + // Fake a the existence of a depth aspect so that the stencil data stays at index 1. + ASSERT(GetAspectIndex(Aspect::Stencil) == 1); + return 2; + } else { + ASSERT(aspects == (Aspect::Depth | Aspect::Stencil)); + return 2; + } + } + + SubresourceRange::SubresourceRange(Aspect aspects, + FirstAndCountRange<uint32_t> arrayLayerParam, + FirstAndCountRange<uint32_t> mipLevelParams) + : aspects(aspects), + baseArrayLayer(arrayLayerParam.first), + layerCount(arrayLayerParam.count), + baseMipLevel(mipLevelParams.first), + levelCount(mipLevelParams.count) { + } + + SubresourceRange::SubresourceRange() + : aspects(Aspect::None), baseArrayLayer(0), layerCount(0), baseMipLevel(0), levelCount(0) { + } + + // static + SubresourceRange SubresourceRange::SingleMipAndLayer(uint32_t baseMipLevel, + uint32_t baseArrayLayer, + Aspect aspects) { + return {aspects, {baseArrayLayer, 1}, {baseMipLevel, 1}}; + } + + // static + SubresourceRange SubresourceRange::MakeSingle(Aspect aspect, + uint32_t baseArrayLayer, + uint32_t baseMipLevel) { + ASSERT(HasOneBit(aspect)); + return {aspect, {baseArrayLayer, 1}, {baseMipLevel, 1}}; + } + + // static + SubresourceRange SubresourceRange::MakeFull(Aspect aspects, + uint32_t layerCount, + uint32_t levelCount) { + return {aspects, {0, layerCount}, {0, levelCount}}; + } + +} // namespace dawn::native
diff --git a/src/dawn/native/Subresource.h b/src/dawn/native/Subresource.h new file mode 100644 index 0000000..63795e5 --- /dev/null +++ b/src/dawn/native/Subresource.h
@@ -0,0 +1,112 @@ +// Copyright 2020 The Dawn Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#ifndef DAWNNATIVE_SUBRESOURCE_H_ +#define DAWNNATIVE_SUBRESOURCE_H_ + +#include "dawn/native/EnumClassBitmasks.h" +#include "dawn/native/dawn_platform.h" + +namespace dawn::native { + + // Note: Subresource indices are computed by iterating the aspects in increasing order. + // D3D12 uses these directly, so the order much match D3D12's indices. + // - Depth/Stencil textures have Depth as Plane 0, and Stencil as Plane 1. + enum class Aspect : uint8_t { + None = 0x0, + Color = 0x1, + Depth = 0x2, + Stencil = 0x4, + + // Aspects used to select individual planes in a multi-planar format. + Plane0 = 0x8, + Plane1 = 0x10, + + // An aspect for that represents the combination of both the depth and stencil aspects. It + // can be ignored outside of the Vulkan backend. + CombinedDepthStencil = 0x20, + }; + + template <> + struct EnumBitmaskSize<Aspect> { + static constexpr unsigned value = 6; + }; + + // Convert the TextureAspect to an Aspect mask for the format. ASSERTs if the aspect + // does not exist in the format. + // Also ASSERTs if "All" is selected and results in more than one aspect. + Aspect ConvertSingleAspect(const Format& format, wgpu::TextureAspect aspect); + + // Convert the TextureAspect to an Aspect mask for the format. ASSERTs if the aspect + // does not exist in the format. + Aspect ConvertAspect(const Format& format, wgpu::TextureAspect aspect); + + // Returns the Aspects of the Format that are selected by the wgpu::TextureAspect. + // Note that this can return Aspect::None if the Format doesn't have any of the + // selected aspects. + Aspect SelectFormatAspects(const Format& format, wgpu::TextureAspect aspect); + + // Convert TextureAspect to the aspect which corresponds to the view format. This + // special cases per plane view formats before calling ConvertAspect. + Aspect ConvertViewAspect(const Format& format, wgpu::TextureAspect aspect); + + // Helper struct to make it clear that what the parameters of a range mean. + template <typename T> + struct FirstAndCountRange { + T first; + T count; + }; + + struct SubresourceRange { + SubresourceRange(Aspect aspects, + FirstAndCountRange<uint32_t> arrayLayerParam, + FirstAndCountRange<uint32_t> mipLevelParams); + SubresourceRange(); + + Aspect aspects; + uint32_t baseArrayLayer; + uint32_t layerCount; + uint32_t baseMipLevel; + uint32_t levelCount; + + static SubresourceRange SingleMipAndLayer(uint32_t baseMipLevel, + uint32_t baseArrayLayer, + Aspect aspects); + static SubresourceRange MakeSingle(Aspect aspect, + uint32_t baseArrayLayer, + uint32_t baseMipLevel); + + static SubresourceRange MakeFull(Aspect aspects, uint32_t layerCount, uint32_t levelCount); + }; + + // Helper function to use aspects as linear indices in arrays. + uint8_t GetAspectIndex(Aspect aspect); + uint8_t GetAspectCount(Aspect aspects); + + // The maximum number of planes per format Dawn knows about. Asserts in BuildFormatTable that + // the per plane index does not exceed the known maximum plane count. + static constexpr uint32_t kMaxPlanesPerFormat = 3; + +} // namespace dawn::native + +namespace dawn { + + template <> + struct IsDawnBitmask<dawn::native::Aspect> { + static constexpr bool enable = true; + }; + +} // namespace dawn + +#endif // DAWNNATIVE_SUBRESOURCE_H_
diff --git a/src/dawn/native/SubresourceStorage.h b/src/dawn/native/SubresourceStorage.h new file mode 100644 index 0000000..345f994 --- /dev/null +++ b/src/dawn/native/SubresourceStorage.h
@@ -0,0 +1,555 @@ +// Copyright 2020 The Dawn Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#ifndef DAWNNATIVE_SUBRESOURCESTORAGE_H_ +#define DAWNNATIVE_SUBRESOURCESTORAGE_H_ + +#include "dawn/common/Assert.h" +#include "dawn/common/TypeTraits.h" +#include "dawn/native/EnumMaskIterator.h" +#include "dawn/native/Subresource.h" + +#include <array> +#include <limits> +#include <memory> +#include <vector> + +namespace dawn::native { + + // SubresourceStorage<T> acts like a simple map from subresource (aspect, layer, level) to a + // value of type T except that it tries to compress similar subresources so that algorithms + // can act on a whole range of subresources at once if they have the same state. + // + // For example a very common case to optimize for is the tracking of the usage of texture + // subresources inside a render pass: the vast majority of texture views will select the whole + // texture while a small minority will select a sub-range. We want to optimize the common case + // by setting and checking a single "usage" value when a full subresource is used but at the + // same time allow per-subresource data when needed. + // + // Another example is barrier tracking per-subresource in the backends: it will often happen + // that during texture upload each mip level will have a different "barrier state". However + // when the texture is fully uploaded and after it is used for sampling (with a full view) for + // the first time, the barrier state will likely be the same across all the subresources. + // That's why some form of "recompression" of subresource state must be possibe. + // + // In order to keep the implementation details private and to avoid iterator-hell, this + // container uses a more functional approach of calling a closure on the interesting ranges. + // This is for example how to look at the state of all subresources. + // + // subresources.Iterate([](const SubresourceRange& range, const T& data) { + // // Do something with the knowledge that all the subresources in `range` have value + // // `data`. + // }); + // + // SubresourceStorage internally tracks compression state per aspect and then per layer of each + // aspect. This means that a 2-aspect texture can have the following compression state: + // + // - Aspect 0 is fully compressed. + // - Aspect 1 is partially compressed: + // - Aspect 1 layer 3 is decompressed. + // - Aspect 1 layer 0-2 and 4-42 are compressed. + // + // A useful model to reason about SubresourceStorage is to represent is as a tree: + // + // - SubresourceStorage is the root. + // |-> Nodes 1 deep represent each aspect. If an aspect is compressed, its node doesn't have + // any children because the data is constant across all of the subtree. + // |-> Nodes 2 deep represent layers (for uncompressed aspects). If a layer is compressed, + // its node doesn't have any children because the data is constant across all of the + // subtree. + // |-> Nodes 3 deep represent individial mip levels (for uncompressed layers). + // + // The concept of recompression is the removal of all child nodes of a non-leaf node when the + // data is constant across them. Decompression is the addition of child nodes to a leaf node + // and copying of its data to all its children. + // + // The choice of having secondary compression for array layers is to optimize for the cases + // where transfer operations are used to update specific layers of texture with render or + // transfer operations, while the rest is untouched. It seems much less likely that there + // would be operations that touch all Nth mips of a 2D array texture without touching the + // others. + // + // There are several hot code paths that create new SubresourceStorage like the tracking of + // resource usage per-pass. We don't want to allocate a container for the decompressed data + // unless we have to because it would dramatically lower performance. Instead + // SubresourceStorage contains an inline array that contains the per-aspect compressed data + // and only allocates a per-subresource on aspect decompression. + // + // T must be a copyable type that supports equality comparison with ==. + // + // The implementation of functions in this file can have a lot of control flow and corner cases + // so each modification should come with extensive tests and ensure 100% code coverage of the + // modified functions. See instructions at + // https://chromium.googlesource.com/chromium/src/+/master/docs/testing/code_coverage.md#local-coverage-script + // to run the test with code coverage. A command line that worked in the past (with the right + // GN args for the out/coverage directory in a Chromium checkout) is: + // + /* + python tools/code_coverage/coverage.py dawn_unittests -b out/coverage -o out/report -c \ + "out/coverage/dawn_unittests --gtest_filter=SubresourceStorage\*" -f \ + third_party/dawn/src/dawn/native + */ + // + // TODO(crbug.com/dawn/836): Make the recompression optional, the calling code should know + // if recompression can happen or not in Update() and Merge() + template <typename T> + class SubresourceStorage { + public: + static_assert(std::is_copy_assignable<T>::value, "T must be copyable"); + static_assert(HasEqualityOperator<T>::value, "T requires bool operator == (T, T)"); + + // Creates the storage with the given "dimensions" and all subresources starting with the + // initial value. + SubresourceStorage(Aspect aspects, + uint32_t arrayLayerCount, + uint32_t mipLevelCount, + T initialValue = {}); + + // Returns the data for a single subresource. Note that the reference returned might be the + // same for multiple subresources. + const T& Get(Aspect aspect, uint32_t arrayLayer, uint32_t mipLevel) const; + + // Given an iterateFunc that's a function or function-like objet that can be called with + // arguments of type (const SubresourceRange& range, const T& data) and returns void, + // calls it with aggregate ranges if possible, such that each subresource is part of + // exactly one of the ranges iterateFunc is called with (and obviously data is the value + // stored for that subresource). For example: + // + // subresources.Iterate([&](const SubresourceRange& range, const T& data) { + // // Do something with range and data. + // }); + template <typename F> + void Iterate(F&& iterateFunc) const; + + // Given an updateFunc that's a function or function-like objet that can be called with + // arguments of type (const SubresourceRange& range, T* data) and returns void, + // calls it with ranges that in aggregate form `range` and pass for each of the + // sub-ranges a pointer to modify the value for that sub-range. For example: + // + // subresources.Update(view->GetRange(), [](const SubresourceRange&, T* data) { + // *data |= wgpu::TextureUsage::Stuff; + // }); + // + // /!\ WARNING: updateFunc should never use range to compute the update to data otherwise + // your code is likely to break when compression happens. Range should only be used for + // side effects like using it to compute a Vulkan pipeline barrier. + template <typename F> + void Update(const SubresourceRange& range, F&& updateFunc); + + // Given a mergeFunc that's a function or a function-like object that can be called with + // arguments of type (const SubresourceRange& range, T* data, const U& otherData) and + // returns void, calls it with ranges that in aggregate form the full resources and pass + // for each of the sub-ranges a pointer to modify the value for that sub-range and the + // corresponding value from other for that sub-range. For example: + // + // subresources.Merge(otherUsages, + // [](const SubresourceRange&, T* data, const T& otherData) { + // *data |= otherData; + // }); + // + // /!\ WARNING: mergeFunc should never use range to compute the update to data otherwise + // your code is likely to break when compression happens. Range should only be used for + // side effects like using it to compute a Vulkan pipeline barrier. + template <typename U, typename F> + void Merge(const SubresourceStorage<U>& other, F&& mergeFunc); + + // Other operations to consider: + // + // - UpdateTo(Range, T) that updates the range to a constant value. + + // Methods to query the internal state of SubresourceStorage for testing. + Aspect GetAspectsForTesting() const; + uint32_t GetArrayLayerCountForTesting() const; + uint32_t GetMipLevelCountForTesting() const; + bool IsAspectCompressedForTesting(Aspect aspect) const; + bool IsLayerCompressedForTesting(Aspect aspect, uint32_t layer) const; + + private: + template <typename U> + friend class SubresourceStorage; + + void DecompressAspect(uint32_t aspectIndex); + void RecompressAspect(uint32_t aspectIndex); + + void DecompressLayer(uint32_t aspectIndex, uint32_t layer); + void RecompressLayer(uint32_t aspectIndex, uint32_t layer); + + SubresourceRange GetFullLayerRange(Aspect aspect, uint32_t layer) const; + + // LayerCompressed should never be called when the aspect is compressed otherwise it would + // need to check that mLayerCompressed is not null before indexing it. + bool& LayerCompressed(uint32_t aspectIndex, uint32_t layerIndex); + bool LayerCompressed(uint32_t aspectIndex, uint32_t layerIndex) const; + + // Return references to the data for a compressed plane / layer or subresource. + // Each variant should be called exactly under the correct compression level. + T& DataInline(uint32_t aspectIndex); + T& Data(uint32_t aspectIndex, uint32_t layer, uint32_t level = 0); + const T& DataInline(uint32_t aspectIndex) const; + const T& Data(uint32_t aspectIndex, uint32_t layer, uint32_t level = 0) const; + + Aspect mAspects; + uint8_t mMipLevelCount; + uint16_t mArrayLayerCount; + + // Invariant: if an aspect is marked compressed, then all it's layers are marked as + // compressed. + static constexpr size_t kMaxAspects = 2; + std::array<bool, kMaxAspects> mAspectCompressed; + std::array<T, kMaxAspects> mInlineAspectData; + + // Indexed as mLayerCompressed[aspectIndex * mArrayLayerCount + layer]. + std::unique_ptr<bool[]> mLayerCompressed; + + // Indexed as mData[(aspectIndex * mArrayLayerCount + layer) * mMipLevelCount + level]. + // The data for a compressed aspect is stored in the slot for (aspect, 0, 0). Similarly + // the data for a compressed layer of aspect if in the slot for (aspect, layer, 0). + std::unique_ptr<T[]> mData; + }; + + template <typename T> + SubresourceStorage<T>::SubresourceStorage(Aspect aspects, + uint32_t arrayLayerCount, + uint32_t mipLevelCount, + T initialValue) + : mAspects(aspects), mMipLevelCount(mipLevelCount), mArrayLayerCount(arrayLayerCount) { + ASSERT(arrayLayerCount <= std::numeric_limits<decltype(mArrayLayerCount)>::max()); + ASSERT(mipLevelCount <= std::numeric_limits<decltype(mMipLevelCount)>::max()); + + uint32_t aspectCount = GetAspectCount(aspects); + ASSERT(aspectCount <= kMaxAspects); + + for (uint32_t aspectIndex = 0; aspectIndex < aspectCount; aspectIndex++) { + mAspectCompressed[aspectIndex] = true; + DataInline(aspectIndex) = initialValue; + } + } + + template <typename T> + template <typename F> + void SubresourceStorage<T>::Update(const SubresourceRange& range, F&& updateFunc) { + bool fullLayers = range.baseMipLevel == 0 && range.levelCount == mMipLevelCount; + bool fullAspects = + range.baseArrayLayer == 0 && range.layerCount == mArrayLayerCount && fullLayers; + + for (Aspect aspect : IterateEnumMask(range.aspects)) { + uint32_t aspectIndex = GetAspectIndex(aspect); + + // Call the updateFunc once for the whole aspect if possible or decompress and fallback + // to per-layer handling. + if (mAspectCompressed[aspectIndex]) { + if (fullAspects) { + SubresourceRange updateRange = + SubresourceRange::MakeFull(aspect, mArrayLayerCount, mMipLevelCount); + updateFunc(updateRange, &DataInline(aspectIndex)); + continue; + } + DecompressAspect(aspectIndex); + } + + uint32_t layerEnd = range.baseArrayLayer + range.layerCount; + for (uint32_t layer = range.baseArrayLayer; layer < layerEnd; layer++) { + // Call the updateFunc once for the whole layer if possible or decompress and + // fallback to per-level handling. + if (LayerCompressed(aspectIndex, layer)) { + if (fullLayers) { + SubresourceRange updateRange = GetFullLayerRange(aspect, layer); + updateFunc(updateRange, &Data(aspectIndex, layer)); + continue; + } + DecompressLayer(aspectIndex, layer); + } + + // Worst case: call updateFunc per level. + uint32_t levelEnd = range.baseMipLevel + range.levelCount; + for (uint32_t level = range.baseMipLevel; level < levelEnd; level++) { + SubresourceRange updateRange = + SubresourceRange::MakeSingle(aspect, layer, level); + updateFunc(updateRange, &Data(aspectIndex, layer, level)); + } + + // If the range has fullLayers then it is likely we can recompress after the calls + // to updateFunc (this branch is skipped if updateFunc was called for the whole + // layer). + if (fullLayers) { + RecompressLayer(aspectIndex, layer); + } + } + + // If the range has fullAspects then it is likely we can recompress after the calls to + // updateFunc (this branch is skipped if updateFunc was called for the whole aspect). + if (fullAspects) { + RecompressAspect(aspectIndex); + } + } + } + + template <typename T> + template <typename U, typename F> + void SubresourceStorage<T>::Merge(const SubresourceStorage<U>& other, F&& mergeFunc) { + ASSERT(mAspects == other.mAspects); + ASSERT(mArrayLayerCount == other.mArrayLayerCount); + ASSERT(mMipLevelCount == other.mMipLevelCount); + + for (Aspect aspect : IterateEnumMask(mAspects)) { + uint32_t aspectIndex = GetAspectIndex(aspect); + + // If the other storage's aspect is compressed we don't need to decompress anything + // in `this` and can just iterate through it, merging with `other`'s constant value for + // the aspect. For code simplicity this can be done with a call to Update(). + if (other.mAspectCompressed[aspectIndex]) { + const U& otherData = other.DataInline(aspectIndex); + Update(SubresourceRange::MakeFull(aspect, mArrayLayerCount, mMipLevelCount), + [&](const SubresourceRange& subrange, T* data) { + mergeFunc(subrange, data, otherData); + }); + continue; + } + + // Other doesn't have the aspect compressed so we must do at least per-layer merging. + if (mAspectCompressed[aspectIndex]) { + DecompressAspect(aspectIndex); + } + + for (uint32_t layer = 0; layer < mArrayLayerCount; layer++) { + // Similarly to above, use a fast path if other's layer is compressed. + if (other.LayerCompressed(aspectIndex, layer)) { + const U& otherData = other.Data(aspectIndex, layer); + Update(GetFullLayerRange(aspect, layer), + [&](const SubresourceRange& subrange, T* data) { + mergeFunc(subrange, data, otherData); + }); + continue; + } + + // Sad case, other is decompressed for this layer, do per-level merging. + if (LayerCompressed(aspectIndex, layer)) { + DecompressLayer(aspectIndex, layer); + } + + for (uint32_t level = 0; level < mMipLevelCount; level++) { + SubresourceRange updateRange = + SubresourceRange::MakeSingle(aspect, layer, level); + mergeFunc(updateRange, &Data(aspectIndex, layer, level), + other.Data(aspectIndex, layer, level)); + } + + RecompressLayer(aspectIndex, layer); + } + + RecompressAspect(aspectIndex); + } + } + + template <typename T> + template <typename F> + void SubresourceStorage<T>::Iterate(F&& iterateFunc) const { + for (Aspect aspect : IterateEnumMask(mAspects)) { + uint32_t aspectIndex = GetAspectIndex(aspect); + + // Fastest path, call iterateFunc on the whole aspect at once. + if (mAspectCompressed[aspectIndex]) { + SubresourceRange range = + SubresourceRange::MakeFull(aspect, mArrayLayerCount, mMipLevelCount); + iterateFunc(range, DataInline(aspectIndex)); + continue; + } + + for (uint32_t layer = 0; layer < mArrayLayerCount; layer++) { + // Fast path, call iterateFunc on the whole array layer at once. + if (LayerCompressed(aspectIndex, layer)) { + SubresourceRange range = GetFullLayerRange(aspect, layer); + iterateFunc(range, Data(aspectIndex, layer)); + continue; + } + + // Slow path, call iterateFunc for each mip level. + for (uint32_t level = 0; level < mMipLevelCount; level++) { + SubresourceRange range = SubresourceRange::MakeSingle(aspect, layer, level); + iterateFunc(range, Data(aspectIndex, layer, level)); + } + } + } + } + + template <typename T> + const T& SubresourceStorage<T>::Get(Aspect aspect, + uint32_t arrayLayer, + uint32_t mipLevel) const { + uint32_t aspectIndex = GetAspectIndex(aspect); + ASSERT(aspectIndex < GetAspectCount(mAspects)); + ASSERT(arrayLayer < mArrayLayerCount); + ASSERT(mipLevel < mMipLevelCount); + + // Fastest path, the aspect is compressed! + if (mAspectCompressed[aspectIndex]) { + return DataInline(aspectIndex); + } + + // Fast path, the array layer is compressed. + if (LayerCompressed(aspectIndex, arrayLayer)) { + return Data(aspectIndex, arrayLayer); + } + + return Data(aspectIndex, arrayLayer, mipLevel); + } + + template <typename T> + Aspect SubresourceStorage<T>::GetAspectsForTesting() const { + return mAspects; + } + + template <typename T> + uint32_t SubresourceStorage<T>::GetArrayLayerCountForTesting() const { + return mArrayLayerCount; + } + + template <typename T> + uint32_t SubresourceStorage<T>::GetMipLevelCountForTesting() const { + return mMipLevelCount; + } + + template <typename T> + bool SubresourceStorage<T>::IsAspectCompressedForTesting(Aspect aspect) const { + return mAspectCompressed[GetAspectIndex(aspect)]; + } + + template <typename T> + bool SubresourceStorage<T>::IsLayerCompressedForTesting(Aspect aspect, uint32_t layer) const { + return mAspectCompressed[GetAspectIndex(aspect)] || + mLayerCompressed[GetAspectIndex(aspect) * mArrayLayerCount + layer]; + } + + template <typename T> + void SubresourceStorage<T>::DecompressAspect(uint32_t aspectIndex) { + ASSERT(mAspectCompressed[aspectIndex]); + const T& aspectData = DataInline(aspectIndex); + mAspectCompressed[aspectIndex] = false; + + // Extra allocations are only needed when aspects are decompressed. Create them lazily. + if (mData == nullptr) { + ASSERT(mLayerCompressed == nullptr); + + uint32_t aspectCount = GetAspectCount(mAspects); + mLayerCompressed = std::make_unique<bool[]>(aspectCount * mArrayLayerCount); + mData = std::make_unique<T[]>(aspectCount * mArrayLayerCount * mMipLevelCount); + + for (uint32_t layerIndex = 0; layerIndex < aspectCount * mArrayLayerCount; + layerIndex++) { + mLayerCompressed[layerIndex] = true; + } + } + + ASSERT(LayerCompressed(aspectIndex, 0)); + for (uint32_t layer = 0; layer < mArrayLayerCount; layer++) { + Data(aspectIndex, layer) = aspectData; + ASSERT(LayerCompressed(aspectIndex, layer)); + } + } + + template <typename T> + void SubresourceStorage<T>::RecompressAspect(uint32_t aspectIndex) { + ASSERT(!mAspectCompressed[aspectIndex]); + // All layers of the aspect must be compressed for the aspect to possibly recompress. + for (uint32_t layer = 0; layer < mArrayLayerCount; layer++) { + if (!LayerCompressed(aspectIndex, layer)) { + return; + } + } + + T layer0Data = Data(aspectIndex, 0); + for (uint32_t layer = 1; layer < mArrayLayerCount; layer++) { + if (!(Data(aspectIndex, layer) == layer0Data)) { + return; + } + } + + mAspectCompressed[aspectIndex] = true; + DataInline(aspectIndex) = layer0Data; + } + + template <typename T> + void SubresourceStorage<T>::DecompressLayer(uint32_t aspectIndex, uint32_t layer) { + ASSERT(LayerCompressed(aspectIndex, layer)); + ASSERT(!mAspectCompressed[aspectIndex]); + const T& layerData = Data(aspectIndex, layer); + LayerCompressed(aspectIndex, layer) = false; + + // We assume that (aspect, layer, 0) is stored at the same place as (aspect, layer) which + // allows starting the iteration at level 1. + for (uint32_t level = 1; level < mMipLevelCount; level++) { + Data(aspectIndex, layer, level) = layerData; + } + } + + template <typename T> + void SubresourceStorage<T>::RecompressLayer(uint32_t aspectIndex, uint32_t layer) { + ASSERT(!LayerCompressed(aspectIndex, layer)); + ASSERT(!mAspectCompressed[aspectIndex]); + const T& level0Data = Data(aspectIndex, layer, 0); + + for (uint32_t level = 1; level < mMipLevelCount; level++) { + if (!(Data(aspectIndex, layer, level) == level0Data)) { + return; + } + } + + LayerCompressed(aspectIndex, layer) = true; + } + + template <typename T> + SubresourceRange SubresourceStorage<T>::GetFullLayerRange(Aspect aspect, uint32_t layer) const { + return {aspect, {layer, 1}, {0, mMipLevelCount}}; + } + + template <typename T> + bool& SubresourceStorage<T>::LayerCompressed(uint32_t aspectIndex, uint32_t layer) { + ASSERT(!mAspectCompressed[aspectIndex]); + return mLayerCompressed[aspectIndex * mArrayLayerCount + layer]; + } + + template <typename T> + bool SubresourceStorage<T>::LayerCompressed(uint32_t aspectIndex, uint32_t layer) const { + ASSERT(!mAspectCompressed[aspectIndex]); + return mLayerCompressed[aspectIndex * mArrayLayerCount + layer]; + } + + template <typename T> + T& SubresourceStorage<T>::DataInline(uint32_t aspectIndex) { + ASSERT(mAspectCompressed[aspectIndex]); + return mInlineAspectData[aspectIndex]; + } + template <typename T> + T& SubresourceStorage<T>::Data(uint32_t aspectIndex, uint32_t layer, uint32_t level) { + ASSERT(level == 0 || !LayerCompressed(aspectIndex, layer)); + ASSERT(!mAspectCompressed[aspectIndex]); + return mData[(aspectIndex * mArrayLayerCount + layer) * mMipLevelCount + level]; + } + template <typename T> + const T& SubresourceStorage<T>::DataInline(uint32_t aspectIndex) const { + ASSERT(mAspectCompressed[aspectIndex]); + return mInlineAspectData[aspectIndex]; + } + template <typename T> + const T& SubresourceStorage<T>::Data(uint32_t aspectIndex, + uint32_t layer, + uint32_t level) const { + ASSERT(level == 0 || !LayerCompressed(aspectIndex, layer)); + ASSERT(!mAspectCompressed[aspectIndex]); + return mData[(aspectIndex * mArrayLayerCount + layer) * mMipLevelCount + level]; + } + +} // namespace dawn::native + +#endif // DAWNNATIVE_SUBRESOURCESTORAGE_H_
diff --git a/src/dawn/native/Surface.cpp b/src/dawn/native/Surface.cpp new file mode 100644 index 0000000..ff6fd07 --- /dev/null +++ b/src/dawn/native/Surface.cpp
@@ -0,0 +1,270 @@ +// Copyright 2020 the Dawn Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include "dawn/native/Surface.h" + +#include "dawn/common/Platform.h" +#include "dawn/native/ChainUtils_autogen.h" +#include "dawn/native/Instance.h" +#include "dawn/native/SwapChain.h" + +#if defined(DAWN_PLATFORM_WINDOWS) +# include <windows.ui.core.h> +# include <windows.ui.xaml.controls.h> +#endif // defined(DAWN_PLATFORM_WINDOWS) + +#if defined(DAWN_USE_X11) +# include "dawn/common/xlib_with_undefs.h" +#endif // defined(DAWN_USE_X11) + +namespace dawn::native { + + absl::FormatConvertResult<absl::FormatConversionCharSet::kString> AbslFormatConvert( + Surface::Type value, + const absl::FormatConversionSpec& spec, + absl::FormatSink* s) { + switch (value) { + case Surface::Type::AndroidWindow: + s->Append("AndroidWindow"); + break; + case Surface::Type::MetalLayer: + s->Append("MetalLayer"); + break; + case Surface::Type::WindowsHWND: + s->Append("WindowsHWND"); + break; + case Surface::Type::WindowsCoreWindow: + s->Append("WindowsCoreWindow"); + break; + case Surface::Type::WindowsSwapChainPanel: + s->Append("WindowsSwapChainPanel"); + break; + case Surface::Type::XlibWindow: + s->Append("XlibWindow"); + break; + } + return {true}; + } + +#if defined(DAWN_ENABLE_BACKEND_METAL) + bool InheritsFromCAMetalLayer(void* obj); +#endif // defined(DAWN_ENABLE_BACKEND_METAL) + + MaybeError ValidateSurfaceDescriptor(const InstanceBase* instance, + const SurfaceDescriptor* descriptor) { + DAWN_INVALID_IF(descriptor->nextInChain == nullptr, + "Surface cannot be created with %s. nextInChain is not specified.", + descriptor); + + DAWN_TRY(ValidateSingleSType(descriptor->nextInChain, + wgpu::SType::SurfaceDescriptorFromAndroidNativeWindow, + wgpu::SType::SurfaceDescriptorFromMetalLayer, + wgpu::SType::SurfaceDescriptorFromWindowsHWND, + wgpu::SType::SurfaceDescriptorFromWindowsCoreWindow, + wgpu::SType::SurfaceDescriptorFromWindowsSwapChainPanel, + wgpu::SType::SurfaceDescriptorFromXlibWindow)); + +#if defined(DAWN_ENABLE_BACKEND_METAL) + const SurfaceDescriptorFromMetalLayer* metalDesc = nullptr; + FindInChain(descriptor->nextInChain, &metalDesc); + if (metalDesc) { + // Check that the layer is a CAMetalLayer (or a derived class). + DAWN_INVALID_IF(!InheritsFromCAMetalLayer(metalDesc->layer), + "Layer must be a CAMetalLayer"); + return {}; + } +#endif // defined(DAWN_ENABLE_BACKEND_METAL) + +#if defined(DAWN_PLATFORM_ANDROID) + const SurfaceDescriptorFromAndroidNativeWindow* androidDesc = nullptr; + FindInChain(descriptor->nextInChain, &androidDesc); + // Currently the best validation we can do since it's not possible to check if the pointer + // to a ANativeWindow is valid. + if (androidDesc) { + DAWN_INVALID_IF(androidDesc->window == nullptr, "Android window is not set."); + return {}; + } +#endif // defined(DAWN_PLATFORM_ANDROID) + +#if defined(DAWN_PLATFORM_WINDOWS) +# if defined(DAWN_PLATFORM_WIN32) + const SurfaceDescriptorFromWindowsHWND* hwndDesc = nullptr; + FindInChain(descriptor->nextInChain, &hwndDesc); + if (hwndDesc) { + DAWN_INVALID_IF(IsWindow(static_cast<HWND>(hwndDesc->hwnd)) == 0, "Invalid HWND"); + return {}; + } +# endif // defined(DAWN_PLATFORM_WIN32) + const SurfaceDescriptorFromWindowsCoreWindow* coreWindowDesc = nullptr; + FindInChain(descriptor->nextInChain, &coreWindowDesc); + if (coreWindowDesc) { + // Validate the coreWindow by query for ICoreWindow interface + ComPtr<ABI::Windows::UI::Core::ICoreWindow> coreWindow; + DAWN_INVALID_IF(coreWindowDesc->coreWindow == nullptr || + FAILED(static_cast<IUnknown*>(coreWindowDesc->coreWindow) + ->QueryInterface(IID_PPV_ARGS(&coreWindow))), + "Invalid CoreWindow"); + return {}; + } + const SurfaceDescriptorFromWindowsSwapChainPanel* swapChainPanelDesc = nullptr; + FindInChain(descriptor->nextInChain, &swapChainPanelDesc); + if (swapChainPanelDesc) { + // Validate the swapChainPanel by querying for ISwapChainPanel interface + ComPtr<ABI::Windows::UI::Xaml::Controls::ISwapChainPanel> swapChainPanel; + DAWN_INVALID_IF(swapChainPanelDesc->swapChainPanel == nullptr || + FAILED(static_cast<IUnknown*>(swapChainPanelDesc->swapChainPanel) + ->QueryInterface(IID_PPV_ARGS(&swapChainPanel))), + "Invalid SwapChainPanel"); + return {}; + } +#endif // defined(DAWN_PLATFORM_WINDOWS) + +#if defined(DAWN_USE_X11) + const SurfaceDescriptorFromXlibWindow* xDesc = nullptr; + FindInChain(descriptor->nextInChain, &xDesc); + if (xDesc) { + // Check the validity of the window by calling a getter function on the window that + // returns a status code. If the window is bad the call return a status of zero. We + // need to set a temporary X11 error handler while doing this because the default + // X11 error handler exits the program on any error. + XErrorHandler oldErrorHandler = + XSetErrorHandler([](Display*, XErrorEvent*) { return 0; }); + XWindowAttributes attributes; + int status = XGetWindowAttributes(reinterpret_cast<Display*>(xDesc->display), + xDesc->window, &attributes); + XSetErrorHandler(oldErrorHandler); + + DAWN_INVALID_IF(status == 0, "Invalid X Window"); + return {}; + } +#endif // defined(DAWN_USE_X11) + + return DAWN_FORMAT_VALIDATION_ERROR("Unsupported sType (%s)", + descriptor->nextInChain->sType); + } + + Surface::Surface(InstanceBase* instance, const SurfaceDescriptor* descriptor) + : mInstance(instance) { + ASSERT(descriptor->nextInChain != nullptr); + const SurfaceDescriptorFromAndroidNativeWindow* androidDesc = nullptr; + const SurfaceDescriptorFromMetalLayer* metalDesc = nullptr; + const SurfaceDescriptorFromWindowsHWND* hwndDesc = nullptr; + const SurfaceDescriptorFromWindowsCoreWindow* coreWindowDesc = nullptr; + const SurfaceDescriptorFromWindowsSwapChainPanel* swapChainPanelDesc = nullptr; + const SurfaceDescriptorFromXlibWindow* xDesc = nullptr; + FindInChain(descriptor->nextInChain, &androidDesc); + FindInChain(descriptor->nextInChain, &metalDesc); + FindInChain(descriptor->nextInChain, &hwndDesc); + FindInChain(descriptor->nextInChain, &coreWindowDesc); + FindInChain(descriptor->nextInChain, &swapChainPanelDesc); + FindInChain(descriptor->nextInChain, &xDesc); + if (metalDesc) { + mType = Type::MetalLayer; + mMetalLayer = metalDesc->layer; + } else if (androidDesc) { + mType = Type::AndroidWindow; + mAndroidNativeWindow = androidDesc->window; + } else if (hwndDesc) { + mType = Type::WindowsHWND; + mHInstance = hwndDesc->hinstance; + mHWND = hwndDesc->hwnd; + } else if (coreWindowDesc) { +#if defined(DAWN_PLATFORM_WINDOWS) + mType = Type::WindowsCoreWindow; + mCoreWindow = static_cast<IUnknown*>(coreWindowDesc->coreWindow); +#endif // defined(DAWN_PLATFORM_WINDOWS) + } else if (swapChainPanelDesc) { +#if defined(DAWN_PLATFORM_WINDOWS) + mType = Type::WindowsSwapChainPanel; + mSwapChainPanel = static_cast<IUnknown*>(swapChainPanelDesc->swapChainPanel); +#endif // defined(DAWN_PLATFORM_WINDOWS) + } else if (xDesc) { + mType = Type::XlibWindow; + mXDisplay = xDesc->display; + mXWindow = xDesc->window; + } else { + UNREACHABLE(); + } + } + + Surface::~Surface() { + if (mSwapChain != nullptr) { + mSwapChain->DetachFromSurface(); + mSwapChain = nullptr; + } + } + + NewSwapChainBase* Surface::GetAttachedSwapChain() { + return mSwapChain.Get(); + } + + void Surface::SetAttachedSwapChain(NewSwapChainBase* swapChain) { + mSwapChain = swapChain; + } + + InstanceBase* Surface::GetInstance() { + return mInstance.Get(); + } + + Surface::Type Surface::GetType() const { + return mType; + } + + void* Surface::GetAndroidNativeWindow() const { + ASSERT(mType == Type::AndroidWindow); + return mAndroidNativeWindow; + } + + void* Surface::GetMetalLayer() const { + ASSERT(mType == Type::MetalLayer); + return mMetalLayer; + } + + void* Surface::GetHInstance() const { + ASSERT(mType == Type::WindowsHWND); + return mHInstance; + } + void* Surface::GetHWND() const { + ASSERT(mType == Type::WindowsHWND); + return mHWND; + } + + IUnknown* Surface::GetCoreWindow() const { + ASSERT(mType == Type::WindowsCoreWindow); +#if defined(DAWN_PLATFORM_WINDOWS) + return mCoreWindow.Get(); +#else + return nullptr; +#endif + } + + IUnknown* Surface::GetSwapChainPanel() const { + ASSERT(mType == Type::WindowsSwapChainPanel); +#if defined(DAWN_PLATFORM_WINDOWS) + return mSwapChainPanel.Get(); +#else + return nullptr; +#endif + } + + void* Surface::GetXDisplay() const { + ASSERT(mType == Type::XlibWindow); + return mXDisplay; + } + uint32_t Surface::GetXWindow() const { + ASSERT(mType == Type::XlibWindow); + return mXWindow; + } + +} // namespace dawn::native
diff --git a/src/dawn/native/Surface.h b/src/dawn/native/Surface.h new file mode 100644 index 0000000..c5d6185 --- /dev/null +++ b/src/dawn/native/Surface.h
@@ -0,0 +1,124 @@ +// Copyright 2020 The Dawn Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#ifndef DAWNNATIVE_SURFACE_H_ +#define DAWNNATIVE_SURFACE_H_ + +#include "dawn/common/RefCounted.h" +#include "dawn/native/Error.h" +#include "dawn/native/Forward.h" + +#include "dawn/native/dawn_platform.h" + +#include "dawn/common/Platform.h" + +#if defined(DAWN_PLATFORM_WINDOWS) +# include "dawn/native/d3d12/d3d12_platform.h" +#endif // defined(DAWN_PLATFORM_WINDOWS) + +// Forward declare IUnknown +// GetCoreWindow needs to return an IUnknown pointer +// non-windows platforms don't have this type +struct IUnknown; + +namespace dawn::native { + + MaybeError ValidateSurfaceDescriptor(const InstanceBase* instance, + const SurfaceDescriptor* descriptor); + + // A surface is a sum types of all the kind of windows Dawn supports. The OS-specific types + // aren't used because they would cause compilation errors on other OSes (or require + // ObjectiveC). + // The surface is also used to store the current swapchain so that we can detach it when it is + // replaced. + class Surface final : public RefCounted { + public: + Surface(InstanceBase* instance, const SurfaceDescriptor* descriptor); + + void SetAttachedSwapChain(NewSwapChainBase* swapChain); + NewSwapChainBase* GetAttachedSwapChain(); + + // These are valid to call on all Surfaces. + enum class Type { + AndroidWindow, + MetalLayer, + WindowsHWND, + WindowsCoreWindow, + WindowsSwapChainPanel, + XlibWindow, + }; + Type GetType() const; + InstanceBase* GetInstance(); + + // Valid to call if the type is MetalLayer + void* GetMetalLayer() const; + + // Valid to call if the type is Android + void* GetAndroidNativeWindow() const; + + // Valid to call if the type is WindowsHWND + void* GetHInstance() const; + void* GetHWND() const; + + // Valid to call if the type is WindowsCoreWindow + IUnknown* GetCoreWindow() const; + + // Valid to call if the type is WindowsSwapChainPanel + IUnknown* GetSwapChainPanel() const; + + // Valid to call if the type is WindowsXlib + void* GetXDisplay() const; + uint32_t GetXWindow() const; + + private: + ~Surface() override; + + Ref<InstanceBase> mInstance; + Type mType; + + // The swapchain will set this to null when it is destroyed. + Ref<NewSwapChainBase> mSwapChain; + + // MetalLayer + void* mMetalLayer = nullptr; + + // ANativeWindow + void* mAndroidNativeWindow = nullptr; + + // WindowsHwnd + void* mHInstance = nullptr; + void* mHWND = nullptr; + +#if defined(DAWN_PLATFORM_WINDOWS) + // WindowsCoreWindow + ComPtr<IUnknown> mCoreWindow; + + // WindowsSwapChainPanel + ComPtr<IUnknown> mSwapChainPanel; +#endif // defined(DAWN_PLATFORM_WINDOWS) + + // Xlib + void* mXDisplay = nullptr; + uint32_t mXWindow = 0; + }; + + // Not defined in webgpu_absl_format.h/cpp because you can't forward-declare a nested type. + absl::FormatConvertResult<absl::FormatConversionCharSet::kString> AbslFormatConvert( + Surface::Type value, + const absl::FormatConversionSpec& spec, + absl::FormatSink* s); + +} // namespace dawn::native + +#endif // DAWNNATIVE_SURFACE_H_
diff --git a/src/dawn/native/Surface_metal.mm b/src/dawn/native/Surface_metal.mm new file mode 100644 index 0000000..ecb5d88 --- /dev/null +++ b/src/dawn/native/Surface_metal.mm
@@ -0,0 +1,30 @@ +// Copyright 2020 the Dawn Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +// Contains a helper function for Surface.cpp that needs to be written in ObjectiveC. + +#if !defined(DAWN_ENABLE_BACKEND_METAL) +# error "Surface_metal.mm requires the Metal backend to be enabled." +#endif // !defined(DAWN_ENABLE_BACKEND_METAL) + +#import <QuartzCore/CAMetalLayer.h> + +namespace dawn::native { + + bool InheritsFromCAMetalLayer(void* obj) { + id<NSObject> object = static_cast<id>(obj); + return [object isKindOfClass:[CAMetalLayer class]]; + } + +} // namespace dawn::native
diff --git a/src/dawn/native/SwapChain.cpp b/src/dawn/native/SwapChain.cpp new file mode 100644 index 0000000..1bd5fd7 --- /dev/null +++ b/src/dawn/native/SwapChain.cpp
@@ -0,0 +1,422 @@ +// Copyright 2017 The Dawn Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include "dawn/native/SwapChain.h" + +#include "dawn/common/Constants.h" +#include "dawn/native/Adapter.h" +#include "dawn/native/Device.h" +#include "dawn/native/ObjectType_autogen.h" +#include "dawn/native/Surface.h" +#include "dawn/native/Texture.h" +#include "dawn/native/ValidationUtils_autogen.h" + +namespace dawn::native { + + namespace { + + class ErrorSwapChain final : public SwapChainBase { + public: + ErrorSwapChain(DeviceBase* device) : SwapChainBase(device, ObjectBase::kError) { + } + + private: + void APIConfigure(wgpu::TextureFormat format, + wgpu::TextureUsage allowedUsage, + uint32_t width, + uint32_t height) override { + GetDevice()->ConsumedError( + DAWN_FORMAT_VALIDATION_ERROR("%s is an error swapchain.", this)); + } + + TextureViewBase* APIGetCurrentTextureView() override { + GetDevice()->ConsumedError( + DAWN_FORMAT_VALIDATION_ERROR("%s is an error swapchain.", this)); + return TextureViewBase::MakeError(GetDevice()); + } + + void APIPresent() override { + GetDevice()->ConsumedError( + DAWN_FORMAT_VALIDATION_ERROR("%s is an error swapchain.", this)); + } + }; + + } // anonymous namespace + + MaybeError ValidateSwapChainDescriptor(const DeviceBase* device, + const Surface* surface, + const SwapChainDescriptor* descriptor) { + if (descriptor->implementation != 0) { + DAWN_INVALID_IF(surface != nullptr, + "Exactly one of surface or implementation must be set"); + + DawnSwapChainImplementation* impl = + reinterpret_cast<DawnSwapChainImplementation*>(descriptor->implementation); + + DAWN_INVALID_IF(!impl->Init || !impl->Destroy || !impl->Configure || + !impl->GetNextTexture || !impl->Present, + "Implementation is incomplete"); + + } else { + DAWN_INVALID_IF(surface == nullptr, + "At least one of surface or implementation must be set"); + + DAWN_TRY(ValidatePresentMode(descriptor->presentMode)); + +// TODO(crbug.com/dawn/160): Lift this restriction once wgpu::Instance::GetPreferredSurfaceFormat is +// implemented. +// TODO(dawn:286): +#if defined(DAWN_PLATFORM_ANDROID) + constexpr wgpu::TextureFormat kRequireSwapChainFormat = wgpu::TextureFormat::RGBA8Unorm; +#else + constexpr wgpu::TextureFormat kRequireSwapChainFormat = wgpu::TextureFormat::BGRA8Unorm; +#endif // !defined(DAWN_PLATFORM_ANDROID) + DAWN_INVALID_IF(descriptor->format != kRequireSwapChainFormat, + "Format (%s) is not %s, which is (currently) the only accepted format.", + descriptor->format, kRequireSwapChainFormat); + + DAWN_INVALID_IF(descriptor->usage != wgpu::TextureUsage::RenderAttachment, + "Usage (%s) is not %s, which is (currently) the only accepted usage.", + descriptor->usage, wgpu::TextureUsage::RenderAttachment); + + DAWN_INVALID_IF(descriptor->width == 0 || descriptor->height == 0, + "Swap Chain size (width: %u, height: %u) is empty.", descriptor->width, + descriptor->height); + + DAWN_INVALID_IF( + descriptor->width > device->GetLimits().v1.maxTextureDimension2D || + descriptor->height > device->GetLimits().v1.maxTextureDimension2D, + "Swap Chain size (width: %u, height: %u) is greater than the maximum 2D texture " + "size (width: %u, height: %u).", + descriptor->width, descriptor->height, device->GetLimits().v1.maxTextureDimension2D, + device->GetLimits().v1.maxTextureDimension2D); + } + + return {}; + } + + TextureDescriptor GetSwapChainBaseTextureDescriptor(NewSwapChainBase* swapChain) { + TextureDescriptor desc; + desc.usage = swapChain->GetUsage(); + desc.dimension = wgpu::TextureDimension::e2D; + desc.size = {swapChain->GetWidth(), swapChain->GetHeight(), 1}; + desc.format = swapChain->GetFormat(); + desc.mipLevelCount = 1; + desc.sampleCount = 1; + + return desc; + } + + // SwapChainBase + + SwapChainBase::SwapChainBase(DeviceBase* device) : ApiObjectBase(device, kLabelNotImplemented) { + TrackInDevice(); + } + + SwapChainBase::SwapChainBase(DeviceBase* device, ObjectBase::ErrorTag tag) + : ApiObjectBase(device, tag) { + } + + SwapChainBase::~SwapChainBase() { + } + + void SwapChainBase::DestroyImpl() { + } + + // static + SwapChainBase* SwapChainBase::MakeError(DeviceBase* device) { + return new ErrorSwapChain(device); + } + + ObjectType SwapChainBase::GetType() const { + return ObjectType::SwapChain; + } + + // OldSwapChainBase + + OldSwapChainBase::OldSwapChainBase(DeviceBase* device, const SwapChainDescriptor* descriptor) + : SwapChainBase(device), + mImplementation( + *reinterpret_cast<DawnSwapChainImplementation*>(descriptor->implementation)) { + } + + OldSwapChainBase::~OldSwapChainBase() { + if (!IsError()) { + const auto& im = GetImplementation(); + im.Destroy(im.userData); + } + } + + void OldSwapChainBase::APIConfigure(wgpu::TextureFormat format, + wgpu::TextureUsage allowedUsage, + uint32_t width, + uint32_t height) { + if (GetDevice()->ConsumedError(ValidateConfigure(format, allowedUsage, width, height))) { + return; + } + ASSERT(!IsError()); + + allowedUsage |= wgpu::TextureUsage::Present; + + mFormat = format; + mAllowedUsage = allowedUsage; + mWidth = width; + mHeight = height; + mImplementation.Configure(mImplementation.userData, static_cast<WGPUTextureFormat>(format), + static_cast<WGPUTextureUsage>(allowedUsage), width, height); + } + + TextureViewBase* OldSwapChainBase::APIGetCurrentTextureView() { + if (GetDevice()->ConsumedError(ValidateGetCurrentTextureView())) { + return TextureViewBase::MakeError(GetDevice()); + } + ASSERT(!IsError()); + + // Return the same current texture view until Present is called. + if (mCurrentTextureView != nullptr) { + // Calling GetCurrentTextureView always returns a new reference so add it even when + // reuse the existing texture view. + mCurrentTextureView->Reference(); + return mCurrentTextureView.Get(); + } + + // Create the backing texture and the view. + TextureDescriptor descriptor; + descriptor.dimension = wgpu::TextureDimension::e2D; + descriptor.size.width = mWidth; + descriptor.size.height = mHeight; + descriptor.size.depthOrArrayLayers = 1; + descriptor.sampleCount = 1; + descriptor.format = mFormat; + descriptor.mipLevelCount = 1; + descriptor.usage = mAllowedUsage; + + // Get the texture but remove the external refcount because it is never passed outside + // of dawn_native + mCurrentTexture = AcquireRef(GetNextTextureImpl(&descriptor)); + + mCurrentTextureView = mCurrentTexture->APICreateView(); + return mCurrentTextureView.Get(); + } + + void OldSwapChainBase::APIPresent() { + if (GetDevice()->ConsumedError(ValidatePresent())) { + return; + } + ASSERT(!IsError()); + + if (GetDevice()->ConsumedError(OnBeforePresent(mCurrentTextureView.Get()))) { + return; + } + + mImplementation.Present(mImplementation.userData); + + mCurrentTexture = nullptr; + mCurrentTextureView = nullptr; + } + + const DawnSwapChainImplementation& OldSwapChainBase::GetImplementation() { + ASSERT(!IsError()); + return mImplementation; + } + + MaybeError OldSwapChainBase::ValidateConfigure(wgpu::TextureFormat format, + wgpu::TextureUsage allowedUsage, + uint32_t width, + uint32_t height) const { + DAWN_TRY(GetDevice()->ValidateIsAlive()); + DAWN_TRY(GetDevice()->ValidateObject(this)); + + DAWN_TRY(ValidateTextureUsage(allowedUsage)); + DAWN_TRY(ValidateTextureFormat(format)); + + DAWN_INVALID_IF(width == 0 || height == 0, + "Configuration size (width: %u, height: %u) for %s is empty.", width, + height, this); + + return {}; + } + + MaybeError OldSwapChainBase::ValidateGetCurrentTextureView() const { + DAWN_TRY(GetDevice()->ValidateIsAlive()); + DAWN_TRY(GetDevice()->ValidateObject(this)); + + // If width is 0, it implies swap chain has never been configured + DAWN_INVALID_IF(mWidth == 0, "%s was not configured prior to calling GetNextTexture.", + this); + + return {}; + } + + MaybeError OldSwapChainBase::ValidatePresent() const { + DAWN_TRY(GetDevice()->ValidateIsAlive()); + DAWN_TRY(GetDevice()->ValidateObject(this)); + + DAWN_INVALID_IF( + mCurrentTextureView == nullptr, + "GetCurrentTextureView was not called on %s this frame prior to calling Present.", + this); + + return {}; + } + + // Implementation of NewSwapChainBase + + NewSwapChainBase::NewSwapChainBase(DeviceBase* device, + Surface* surface, + const SwapChainDescriptor* descriptor) + : SwapChainBase(device), + mAttached(false), + mWidth(descriptor->width), + mHeight(descriptor->height), + mFormat(descriptor->format), + mUsage(descriptor->usage), + mPresentMode(descriptor->presentMode), + mSurface(surface) { + } + + NewSwapChainBase::~NewSwapChainBase() { + if (mCurrentTextureView != nullptr) { + ASSERT(mCurrentTextureView->GetTexture()->GetTextureState() == + TextureBase::TextureState::Destroyed); + } + + ASSERT(!mAttached); + } + + void NewSwapChainBase::DetachFromSurface() { + if (mAttached) { + DetachFromSurfaceImpl(); + mSurface = nullptr; + mAttached = false; + } + } + + void NewSwapChainBase::SetIsAttached() { + mAttached = true; + } + + void NewSwapChainBase::APIConfigure(wgpu::TextureFormat format, + wgpu::TextureUsage allowedUsage, + uint32_t width, + uint32_t height) { + GetDevice()->ConsumedError( + DAWN_FORMAT_VALIDATION_ERROR("Configure is invalid for surface-based swapchains.")); + } + + TextureViewBase* NewSwapChainBase::APIGetCurrentTextureView() { + Ref<TextureViewBase> result; + if (GetDevice()->ConsumedError(GetCurrentTextureView(), &result, + "calling %s.GetCurrentTextureView()", this)) { + return TextureViewBase::MakeError(GetDevice()); + } + return result.Detach(); + } + + ResultOrError<Ref<TextureViewBase>> NewSwapChainBase::GetCurrentTextureView() { + DAWN_TRY(ValidateGetCurrentTextureView()); + + if (mCurrentTextureView != nullptr) { + // Calling GetCurrentTextureView always returns a new reference. + return mCurrentTextureView; + } + + DAWN_TRY_ASSIGN(mCurrentTextureView, GetCurrentTextureViewImpl()); + + // Check that the return texture view matches exactly what was given for this descriptor. + ASSERT(mCurrentTextureView->GetTexture()->GetFormat().format == mFormat); + ASSERT(IsSubset(mUsage, mCurrentTextureView->GetTexture()->GetUsage())); + ASSERT(mCurrentTextureView->GetLevelCount() == 1); + ASSERT(mCurrentTextureView->GetLayerCount() == 1); + ASSERT(mCurrentTextureView->GetDimension() == wgpu::TextureViewDimension::e2D); + ASSERT(mCurrentTextureView->GetTexture() + ->GetMipLevelVirtualSize(mCurrentTextureView->GetBaseMipLevel()) + .width == mWidth); + ASSERT(mCurrentTextureView->GetTexture() + ->GetMipLevelVirtualSize(mCurrentTextureView->GetBaseMipLevel()) + .height == mHeight); + + return mCurrentTextureView; + } + + void NewSwapChainBase::APIPresent() { + if (GetDevice()->ConsumedError(ValidatePresent())) { + return; + } + + if (GetDevice()->ConsumedError(PresentImpl())) { + return; + } + + ASSERT(mCurrentTextureView->GetTexture()->GetTextureState() == + TextureBase::TextureState::Destroyed); + mCurrentTextureView = nullptr; + } + + uint32_t NewSwapChainBase::GetWidth() const { + return mWidth; + } + + uint32_t NewSwapChainBase::GetHeight() const { + return mHeight; + } + + wgpu::TextureFormat NewSwapChainBase::GetFormat() const { + return mFormat; + } + + wgpu::TextureUsage NewSwapChainBase::GetUsage() const { + return mUsage; + } + + wgpu::PresentMode NewSwapChainBase::GetPresentMode() const { + return mPresentMode; + } + + Surface* NewSwapChainBase::GetSurface() const { + return mSurface; + } + + bool NewSwapChainBase::IsAttached() const { + return mAttached; + } + + wgpu::BackendType NewSwapChainBase::GetBackendType() const { + return GetDevice()->GetAdapter()->GetBackendType(); + } + + MaybeError NewSwapChainBase::ValidatePresent() const { + DAWN_TRY(GetDevice()->ValidateIsAlive()); + DAWN_TRY(GetDevice()->ValidateObject(this)); + + DAWN_INVALID_IF(!mAttached, "Cannot call Present called on detached %s.", this); + + DAWN_INVALID_IF( + mCurrentTextureView == nullptr, + "GetCurrentTextureView was not called on %s this frame prior to calling Present.", + this); + + return {}; + } + + MaybeError NewSwapChainBase::ValidateGetCurrentTextureView() const { + DAWN_TRY(GetDevice()->ValidateIsAlive()); + DAWN_TRY(GetDevice()->ValidateObject(this)); + + DAWN_INVALID_IF(!mAttached, "Cannot call GetCurrentTextureView on detached %s.", this); + + return {}; + } + +} // namespace dawn::native
diff --git a/src/dawn/native/SwapChain.h b/src/dawn/native/SwapChain.h new file mode 100644 index 0000000..48b8270 --- /dev/null +++ b/src/dawn/native/SwapChain.h
@@ -0,0 +1,170 @@ +// Copyright 2017 The Dawn Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#ifndef DAWNNATIVE_SWAPCHAIN_H_ +#define DAWNNATIVE_SWAPCHAIN_H_ + +#include "dawn/native/Error.h" +#include "dawn/native/Forward.h" +#include "dawn/native/ObjectBase.h" + +#include "dawn/dawn_wsi.h" +#include "dawn/native/dawn_platform.h" + +namespace dawn::native { + + MaybeError ValidateSwapChainDescriptor(const DeviceBase* device, + const Surface* surface, + const SwapChainDescriptor* descriptor); + + TextureDescriptor GetSwapChainBaseTextureDescriptor(NewSwapChainBase* swapChain); + + class SwapChainBase : public ApiObjectBase { + public: + SwapChainBase(DeviceBase* device); + + static SwapChainBase* MakeError(DeviceBase* device); + + ObjectType GetType() const override; + + // Dawn API + virtual void APIConfigure(wgpu::TextureFormat format, + wgpu::TextureUsage allowedUsage, + uint32_t width, + uint32_t height) = 0; + virtual TextureViewBase* APIGetCurrentTextureView() = 0; + virtual void APIPresent() = 0; + + protected: + SwapChainBase(DeviceBase* device, ObjectBase::ErrorTag tag); + ~SwapChainBase() override; + void DestroyImpl() override; + }; + + // The base class for implementation-based SwapChains that are deprecated. + class OldSwapChainBase : public SwapChainBase { + public: + OldSwapChainBase(DeviceBase* device, const SwapChainDescriptor* descriptor); + + // Dawn API + void APIConfigure(wgpu::TextureFormat format, + wgpu::TextureUsage allowedUsage, + uint32_t width, + uint32_t height) override; + TextureViewBase* APIGetCurrentTextureView() override; + void APIPresent() override; + + protected: + ~OldSwapChainBase() override; + const DawnSwapChainImplementation& GetImplementation(); + virtual TextureBase* GetNextTextureImpl(const TextureDescriptor*) = 0; + virtual MaybeError OnBeforePresent(TextureViewBase* view) = 0; + + private: + MaybeError ValidateConfigure(wgpu::TextureFormat format, + wgpu::TextureUsage allowedUsage, + uint32_t width, + uint32_t height) const; + MaybeError ValidateGetCurrentTextureView() const; + MaybeError ValidatePresent() const; + + DawnSwapChainImplementation mImplementation = {}; + wgpu::TextureFormat mFormat = {}; + wgpu::TextureUsage mAllowedUsage; + uint32_t mWidth = 0; + uint32_t mHeight = 0; + Ref<TextureBase> mCurrentTexture; + Ref<TextureViewBase> mCurrentTextureView; + }; + + // The base class for surface-based SwapChains that aren't ready yet. + class NewSwapChainBase : public SwapChainBase { + public: + NewSwapChainBase(DeviceBase* device, + Surface* surface, + const SwapChainDescriptor* descriptor); + + // This is called when the swapchain is detached when one of the following happens: + // + // - The surface it is attached to is being destroyed. + // - The swapchain is being replaced by another one on the surface. + // + // Note that the surface has a Ref on the last swapchain that was used on it so the + // SwapChain destructor will only be called after one of the things above happens. + // + // The call for the detaching previous swapchain should be called inside the backend + // implementation of SwapChains. This is to allow them to acquire any resources before + // calling detach to make a seamless transition from the previous swapchain. + // + // Likewise the call for the swapchain being destroyed must be done in the backend's + // swapchain's destructor since C++ says it is UB to call virtual methods in the base class + // destructor. + void DetachFromSurface(); + + void SetIsAttached(); + + // Dawn API + void APIConfigure(wgpu::TextureFormat format, + wgpu::TextureUsage allowedUsage, + uint32_t width, + uint32_t height) override; + TextureViewBase* APIGetCurrentTextureView() override; + void APIPresent() override; + + uint32_t GetWidth() const; + uint32_t GetHeight() const; + wgpu::TextureFormat GetFormat() const; + wgpu::TextureUsage GetUsage() const; + wgpu::PresentMode GetPresentMode() const; + Surface* GetSurface() const; + bool IsAttached() const; + wgpu::BackendType GetBackendType() const; + + protected: + ~NewSwapChainBase() override; + + private: + bool mAttached; + uint32_t mWidth; + uint32_t mHeight; + wgpu::TextureFormat mFormat; + wgpu::TextureUsage mUsage; + wgpu::PresentMode mPresentMode; + + // This is a weak reference to the surface. If the surface is destroyed it will call + // DetachFromSurface and mSurface will be updated to nullptr. + Surface* mSurface = nullptr; + Ref<TextureViewBase> mCurrentTextureView; + + MaybeError ValidatePresent() const; + MaybeError ValidateGetCurrentTextureView() const; + + // GetCurrentTextureViewImpl and PresentImpl are guaranteed to be called in an interleaved + // manner, starting with GetCurrentTextureViewImpl. + + // The returned texture view must match the swapchain descriptor exactly. + ResultOrError<Ref<TextureViewBase>> GetCurrentTextureView(); + virtual ResultOrError<Ref<TextureViewBase>> GetCurrentTextureViewImpl() = 0; + // The call to present must destroy the current view's texture so further access to it are + // invalid. + virtual MaybeError PresentImpl() = 0; + + // Guaranteed to be called exactly once during the lifetime of the SwapChain. After it is + // called no other virtual method can be called. + virtual void DetachFromSurfaceImpl() = 0; + }; + +} // namespace dawn::native + +#endif // DAWNNATIVE_SWAPCHAIN_H_
diff --git a/src/dawn/native/Texture.cpp b/src/dawn/native/Texture.cpp new file mode 100644 index 0000000..f4c4fc5 --- /dev/null +++ b/src/dawn/native/Texture.cpp
@@ -0,0 +1,866 @@ +// Copyright 2017 The Dawn Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include "dawn/native/Texture.h" + +#include <algorithm> + +#include "dawn/common/Assert.h" +#include "dawn/common/Constants.h" +#include "dawn/common/Math.h" +#include "dawn/native/Adapter.h" +#include "dawn/native/ChainUtils_autogen.h" +#include "dawn/native/Device.h" +#include "dawn/native/EnumMaskIterator.h" +#include "dawn/native/ObjectType_autogen.h" +#include "dawn/native/PassResourceUsage.h" +#include "dawn/native/ValidationUtils_autogen.h" + +namespace dawn::native { + namespace { + + MaybeError ValidateTextureViewFormatCompatibility(const DeviceBase* device, + const Format& format, + wgpu::TextureFormat viewFormatEnum) { + const Format* viewFormat; + DAWN_TRY_ASSIGN(viewFormat, device->GetInternalFormat(viewFormatEnum)); + + DAWN_INVALID_IF(!format.ViewCompatibleWith(*viewFormat), + "The texture view format (%s) is not texture view format compatible " + "with the texture format (%s).", + viewFormatEnum, format.format); + return {}; + } + + MaybeError ValidateCanViewTextureAs(const DeviceBase* device, + const TextureBase* texture, + const Format& viewFormat, + wgpu::TextureAspect aspect) { + const Format& format = texture->GetFormat(); + + if (aspect != wgpu::TextureAspect::All) { + wgpu::TextureFormat aspectFormat = format.GetAspectInfo(aspect).format; + if (viewFormat.format == aspectFormat) { + return {}; + } else { + return DAWN_FORMAT_VALIDATION_ERROR( + "The view format (%s) is not compatible with %s of %s (%s).", + viewFormat.format, aspect, format.format, aspectFormat); + } + } + + if (format.format == viewFormat.format) { + return {}; + } + + const FormatSet& compatibleViewFormats = texture->GetViewFormats(); + if (compatibleViewFormats[viewFormat]) { + // Validation of this list is done on texture creation, so we don't need to + // handle the case where a format is in the list, but not compatible. + return {}; + } + + // |viewFormat| is not in the list. Check compatibility to generate an error message + // depending on whether it could be compatible, but needs to be explicitly listed, + // or it could never be compatible. + if (!format.ViewCompatibleWith(viewFormat)) { + // The view format isn't compatible with the format at all. Return an error + // that indicates this, in addition to reporting that it's missing from the + // list. + return DAWN_FORMAT_VALIDATION_ERROR( + "The texture view format (%s) is not compatible with the " + "texture format (%s)." + "The formats must be compatible, and the view format " + "must be passed in the list of view formats on texture creation.", + viewFormat.format, format.format); + } else { + // The view format is compatible, but not in the list. + return DAWN_FORMAT_VALIDATION_ERROR( + "%s was not created with the texture view format (%s) " + "in the list of compatible view formats.", + texture, viewFormat.format); + } + return {}; + } + + bool IsTextureViewDimensionCompatibleWithTextureDimension( + wgpu::TextureViewDimension textureViewDimension, + wgpu::TextureDimension textureDimension) { + switch (textureViewDimension) { + case wgpu::TextureViewDimension::e2D: + case wgpu::TextureViewDimension::e2DArray: + case wgpu::TextureViewDimension::Cube: + case wgpu::TextureViewDimension::CubeArray: + return textureDimension == wgpu::TextureDimension::e2D; + + case wgpu::TextureViewDimension::e3D: + return textureDimension == wgpu::TextureDimension::e3D; + + case wgpu::TextureViewDimension::e1D: + return textureDimension == wgpu::TextureDimension::e1D; + + case wgpu::TextureViewDimension::Undefined: + UNREACHABLE(); + } + } + + bool IsArrayLayerValidForTextureViewDimension( + wgpu::TextureViewDimension textureViewDimension, + uint32_t textureViewArrayLayer) { + switch (textureViewDimension) { + case wgpu::TextureViewDimension::e2D: + case wgpu::TextureViewDimension::e3D: + return textureViewArrayLayer == 1u; + case wgpu::TextureViewDimension::e2DArray: + return true; + case wgpu::TextureViewDimension::Cube: + return textureViewArrayLayer == 6u; + case wgpu::TextureViewDimension::CubeArray: + return textureViewArrayLayer % 6 == 0; + case wgpu::TextureViewDimension::e1D: + return textureViewArrayLayer == 1u; + + case wgpu::TextureViewDimension::Undefined: + UNREACHABLE(); + } + } + + MaybeError ValidateSampleCount(const TextureDescriptor* descriptor, + wgpu::TextureUsage usage, + const Format* format) { + DAWN_INVALID_IF(!IsValidSampleCount(descriptor->sampleCount), + "The sample count (%u) of the texture is not supported.", + descriptor->sampleCount); + + if (descriptor->sampleCount > 1) { + DAWN_INVALID_IF(descriptor->mipLevelCount > 1, + "The mip level count (%u) of a multisampled texture is not 1.", + descriptor->mipLevelCount); + + // Multisampled 1D and 3D textures are not supported in D3D12/Metal/Vulkan. + // Multisampled 2D array texture is not supported because on Metal it requires the + // version of macOS be greater than 10.14. + DAWN_INVALID_IF(descriptor->dimension != wgpu::TextureDimension::e2D, + "The dimension (%s) of a multisampled texture is not 2D.", + descriptor->dimension); + + DAWN_INVALID_IF(descriptor->size.depthOrArrayLayers > 1, + "The depthOrArrayLayers (%u) of a multisampled texture is not 1.", + descriptor->size.depthOrArrayLayers); + + DAWN_INVALID_IF(!format->supportsMultisample, + "The texture format (%s) does not support multisampling.", + format->format); + + // Compressed formats are not renderable. They cannot support multisample. + ASSERT(!format->isCompressed); + + DAWN_INVALID_IF(usage & wgpu::TextureUsage::StorageBinding, + "The sample count (%u) of a storage textures is not 1.", + descriptor->sampleCount); + } + + return {}; + } + + MaybeError ValidateTextureViewDimensionCompatibility( + const TextureBase* texture, + const TextureViewDescriptor* descriptor) { + DAWN_INVALID_IF( + !IsArrayLayerValidForTextureViewDimension(descriptor->dimension, + descriptor->arrayLayerCount), + "The dimension (%s) of the texture view is not compatible with the layer count " + "(%u) of %s.", + descriptor->dimension, descriptor->arrayLayerCount, texture); + + DAWN_INVALID_IF( + !IsTextureViewDimensionCompatibleWithTextureDimension(descriptor->dimension, + texture->GetDimension()), + "The dimension (%s) of the texture view is not compatible with the dimension (%s) " + "of %s.", + descriptor->dimension, texture->GetDimension(), texture); + + DAWN_INVALID_IF(texture->GetSampleCount() > 1 && + descriptor->dimension != wgpu::TextureViewDimension::e2D, + "The dimension (%s) of the multisampled texture view is not %s.", + descriptor->dimension, wgpu::TextureViewDimension::e2D); + + switch (descriptor->dimension) { + case wgpu::TextureViewDimension::Cube: + case wgpu::TextureViewDimension::CubeArray: + DAWN_INVALID_IF( + texture->GetSize().width != texture->GetSize().height, + "A %s texture view is not compatible with %s because the texture's width " + "(%u) and height (%u) are not equal.", + descriptor->dimension, texture, texture->GetSize().width, + texture->GetSize().height); + break; + + case wgpu::TextureViewDimension::e1D: + case wgpu::TextureViewDimension::e2D: + case wgpu::TextureViewDimension::e2DArray: + case wgpu::TextureViewDimension::e3D: + break; + + case wgpu::TextureViewDimension::Undefined: + UNREACHABLE(); + } + + return {}; + } + + MaybeError ValidateTextureSize(const DeviceBase* device, + const TextureDescriptor* descriptor, + const Format* format) { + ASSERT(descriptor->size.width != 0 && descriptor->size.height != 0 && + descriptor->size.depthOrArrayLayers != 0); + const CombinedLimits& limits = device->GetLimits(); + Extent3D maxExtent; + switch (descriptor->dimension) { + case wgpu::TextureDimension::e1D: + maxExtent = {limits.v1.maxTextureDimension1D, 1, 1}; + break; + case wgpu::TextureDimension::e2D: + maxExtent = {limits.v1.maxTextureDimension2D, limits.v1.maxTextureDimension2D, + limits.v1.maxTextureArrayLayers}; + break; + case wgpu::TextureDimension::e3D: + maxExtent = {limits.v1.maxTextureDimension3D, limits.v1.maxTextureDimension3D, + limits.v1.maxTextureDimension3D}; + break; + } + DAWN_INVALID_IF(descriptor->size.width > maxExtent.width || + descriptor->size.height > maxExtent.height || + descriptor->size.depthOrArrayLayers > maxExtent.depthOrArrayLayers, + "Texture size (%s) exceeded maximum texture size (%s).", + &descriptor->size, &maxExtent); + + switch (descriptor->dimension) { + case wgpu::TextureDimension::e1D: + DAWN_INVALID_IF( + descriptor->mipLevelCount != 1, + "Texture mip level count (%u) is more than 1 when its dimension is %s.", + descriptor->mipLevelCount, wgpu::TextureDimension::e1D); + break; + case wgpu::TextureDimension::e2D: { + uint32_t maxMippedDimension = + std::max(descriptor->size.width, descriptor->size.height); + DAWN_INVALID_IF( + Log2(maxMippedDimension) + 1 < descriptor->mipLevelCount, + "Texture mip level count (%u) exceeds the maximum (%u) for its size (%s).", + descriptor->mipLevelCount, Log2(maxMippedDimension) + 1, &descriptor->size); + break; + } + case wgpu::TextureDimension::e3D: { + uint32_t maxMippedDimension = std::max( + descriptor->size.width, + std::max(descriptor->size.height, descriptor->size.depthOrArrayLayers)); + DAWN_INVALID_IF( + Log2(maxMippedDimension) + 1 < descriptor->mipLevelCount, + "Texture mip level count (%u) exceeds the maximum (%u) for its size (%s).", + descriptor->mipLevelCount, Log2(maxMippedDimension) + 1, &descriptor->size); + break; + } + } + + if (format->isCompressed) { + const TexelBlockInfo& blockInfo = + format->GetAspectInfo(wgpu::TextureAspect::All).block; + DAWN_INVALID_IF( + descriptor->size.width % blockInfo.width != 0 || + descriptor->size.height % blockInfo.height != 0, + "The size (%s) of the texture is not a multiple of the block width (%u) and " + "height (%u) of the texture format (%s).", + &descriptor->size, blockInfo.width, blockInfo.height, format->format); + } + + return {}; + } + + MaybeError ValidateTextureUsage(const TextureDescriptor* descriptor, + wgpu::TextureUsage usage, + const Format* format) { + DAWN_TRY(dawn::native::ValidateTextureUsage(usage)); + + DAWN_INVALID_IF(usage == wgpu::TextureUsage::None, "The texture usage must not be 0."); + + constexpr wgpu::TextureUsage kValidCompressedUsages = + wgpu::TextureUsage::TextureBinding | wgpu::TextureUsage::CopySrc | + wgpu::TextureUsage::CopyDst; + DAWN_INVALID_IF( + format->isCompressed && !IsSubset(usage, kValidCompressedUsages), + "The texture usage (%s) is incompatible with the compressed texture format (%s).", + usage, format->format); + + DAWN_INVALID_IF( + !format->isRenderable && (usage & wgpu::TextureUsage::RenderAttachment), + "The texture usage (%s) includes %s, which is incompatible with the non-renderable " + "format (%s).", + usage, wgpu::TextureUsage::RenderAttachment, format->format); + + DAWN_INVALID_IF( + !format->supportsStorageUsage && (usage & wgpu::TextureUsage::StorageBinding), + "The texture usage (%s) includes %s, which is incompatible with the format (%s).", + usage, wgpu::TextureUsage::StorageBinding, format->format); + + // Only allows simple readonly texture usages. + constexpr wgpu::TextureUsage kValidMultiPlanarUsages = + wgpu::TextureUsage::TextureBinding | wgpu::TextureUsage::CopySrc; + DAWN_INVALID_IF( + format->IsMultiPlanar() && !IsSubset(usage, kValidMultiPlanarUsages), + "The texture usage (%s) is incompatible with the multi-planar format (%s).", usage, + format->format); + + return {}; + } + + } // anonymous namespace + + MaybeError ValidateTextureDescriptor(const DeviceBase* device, + const TextureDescriptor* descriptor) { + DAWN_TRY(ValidateSingleSType(descriptor->nextInChain, + wgpu::SType::DawnTextureInternalUsageDescriptor)); + + const DawnTextureInternalUsageDescriptor* internalUsageDesc = nullptr; + FindInChain(descriptor->nextInChain, &internalUsageDesc); + + DAWN_INVALID_IF( + internalUsageDesc != nullptr && !device->IsFeatureEnabled(Feature::DawnInternalUsages), + "The dawn-internal-usages feature is not enabled"); + + const Format* format; + DAWN_TRY_ASSIGN(format, device->GetInternalFormat(descriptor->format)); + + for (uint32_t i = 0; i < descriptor->viewFormatCount; ++i) { + DAWN_TRY_CONTEXT( + ValidateTextureViewFormatCompatibility(device, *format, descriptor->viewFormats[i]), + "validating viewFormats[%u]", i); + } + + wgpu::TextureUsage usage = descriptor->usage; + if (internalUsageDesc != nullptr) { + usage |= internalUsageDesc->internalUsage; + } + + DAWN_TRY(ValidateTextureUsage(descriptor, usage, format)); + DAWN_TRY(ValidateTextureDimension(descriptor->dimension)); + DAWN_TRY(ValidateSampleCount(descriptor, usage, format)); + + DAWN_INVALID_IF(descriptor->size.width == 0 || descriptor->size.height == 0 || + descriptor->size.depthOrArrayLayers == 0 || + descriptor->mipLevelCount == 0, + "The texture size (%s) or mipLevelCount (%u) is empty.", &descriptor->size, + descriptor->mipLevelCount); + + DAWN_INVALID_IF( + descriptor->dimension != wgpu::TextureDimension::e2D && format->isCompressed, + "The dimension (%s) of a texture with a compressed format (%s) is not 2D.", + descriptor->dimension, format->format); + + // Depth/stencil formats are valid for 2D textures only. Metal has this limit. And D3D12 + // doesn't support depth/stencil formats on 3D textures. + DAWN_INVALID_IF( + descriptor->dimension != wgpu::TextureDimension::e2D && + (format->aspects & (Aspect::Depth | Aspect::Stencil)) != 0, + "The dimension (%s) of a texture with a depth/stencil format (%s) is not 2D.", + descriptor->dimension, format->format); + + DAWN_TRY(ValidateTextureSize(device, descriptor, format)); + + // TODO(crbug.com/dawn/838): Implement a workaround for this issue. + // Readbacks from the non-zero mip of a stencil texture may contain garbage data. + DAWN_INVALID_IF( + device->IsToggleEnabled(Toggle::DisallowUnsafeAPIs) && format->HasStencil() && + descriptor->mipLevelCount > 1 && + device->GetAdapter()->GetBackendType() == wgpu::BackendType::Metal, + "https://crbug.com/dawn/838: Stencil textures with more than one mip level are " + "disabled on Metal."); + + DAWN_INVALID_IF( + device->IsToggleEnabled(Toggle::DisableR8RG8Mipmaps) && descriptor->mipLevelCount > 1 && + (descriptor->format == wgpu::TextureFormat::R8Unorm || + descriptor->format == wgpu::TextureFormat::RG8Unorm), + "https://crbug.com/dawn/1071: r8unorm and rg8unorm textures with more than one mip " + "level are disabled on Metal."); + + return {}; + } + + MaybeError ValidateTextureViewDescriptor(const DeviceBase* device, + const TextureBase* texture, + const TextureViewDescriptor* descriptor) { + DAWN_INVALID_IF(descriptor->nextInChain != nullptr, "nextInChain must be nullptr."); + + // Parent texture should have been already validated. + ASSERT(texture); + ASSERT(!texture->IsError()); + + DAWN_TRY(ValidateTextureViewDimension(descriptor->dimension)); + DAWN_TRY(ValidateTextureFormat(descriptor->format)); + DAWN_TRY(ValidateTextureAspect(descriptor->aspect)); + + const Format& format = texture->GetFormat(); + const Format* viewFormat; + DAWN_TRY_ASSIGN(viewFormat, device->GetInternalFormat(descriptor->format)); + + DAWN_INVALID_IF( + SelectFormatAspects(format, descriptor->aspect) == Aspect::None, + "Texture format (%s) does not have the texture view's selected aspect (%s).", + format.format, descriptor->aspect); + + DAWN_INVALID_IF(descriptor->arrayLayerCount == 0 || descriptor->mipLevelCount == 0, + "The texture view's arrayLayerCount (%u) or mipLevelCount (%u) is zero.", + descriptor->arrayLayerCount, descriptor->mipLevelCount); + + DAWN_INVALID_IF( + uint64_t(descriptor->baseArrayLayer) + uint64_t(descriptor->arrayLayerCount) > + uint64_t(texture->GetArrayLayers()), + "Texture view array layer range (baseArrayLayer: %u, arrayLayerCount: %u) exceeds the " + "texture's array layer count (%u).", + descriptor->baseArrayLayer, descriptor->arrayLayerCount, texture->GetArrayLayers()); + + DAWN_INVALID_IF( + uint64_t(descriptor->baseMipLevel) + uint64_t(descriptor->mipLevelCount) > + uint64_t(texture->GetNumMipLevels()), + "Texture view mip level range (baseMipLevel: %u, mipLevelCount: %u) exceeds the " + "texture's mip level count (%u).", + descriptor->baseMipLevel, descriptor->mipLevelCount, texture->GetNumMipLevels()); + + DAWN_TRY(ValidateCanViewTextureAs(device, texture, *viewFormat, descriptor->aspect)); + DAWN_TRY(ValidateTextureViewDimensionCompatibility(texture, descriptor)); + + return {}; + } + + TextureViewDescriptor GetTextureViewDescriptorWithDefaults( + const TextureBase* texture, + const TextureViewDescriptor* descriptor) { + ASSERT(texture); + + TextureViewDescriptor desc = {}; + if (descriptor) { + desc = *descriptor; + } + + // The default value for the view dimension depends on the texture's dimension with a + // special case for 2DArray being chosen automatically if arrayLayerCount is unspecified. + if (desc.dimension == wgpu::TextureViewDimension::Undefined) { + switch (texture->GetDimension()) { + case wgpu::TextureDimension::e1D: + desc.dimension = wgpu::TextureViewDimension::e1D; + break; + + case wgpu::TextureDimension::e2D: + desc.dimension = wgpu::TextureViewDimension::e2D; + break; + + case wgpu::TextureDimension::e3D: + desc.dimension = wgpu::TextureViewDimension::e3D; + break; + } + } + + if (desc.format == wgpu::TextureFormat::Undefined) { + const Format& format = texture->GetFormat(); + Aspect aspects = SelectFormatAspects(format, desc.aspect); + if (HasOneBit(aspects)) { + desc.format = format.GetAspectInfo(aspects).format; + } else { + desc.format = format.format; + } + } + if (desc.arrayLayerCount == wgpu::kArrayLayerCountUndefined) { + switch (desc.dimension) { + case wgpu::TextureViewDimension::e1D: + case wgpu::TextureViewDimension::e2D: + case wgpu::TextureViewDimension::e3D: + desc.arrayLayerCount = 1; + break; + case wgpu::TextureViewDimension::Cube: + desc.arrayLayerCount = 6; + break; + case wgpu::TextureViewDimension::e2DArray: + case wgpu::TextureViewDimension::CubeArray: + desc.arrayLayerCount = texture->GetArrayLayers() - desc.baseArrayLayer; + break; + default: + // We don't put UNREACHABLE() here because we validate enums only after this + // function sets default values. Otherwise, the UNREACHABLE() will be hit. + break; + } + } + + if (desc.mipLevelCount == wgpu::kMipLevelCountUndefined) { + desc.mipLevelCount = texture->GetNumMipLevels() - desc.baseMipLevel; + } + return desc; + } + + // WebGPU only supports sample counts of 1 and 4. We could expand to more based on + // platform support, but it would probably be a feature. + bool IsValidSampleCount(uint32_t sampleCount) { + switch (sampleCount) { + case 1: + case 4: + return true; + + default: + return false; + } + } + + // TextureBase + + TextureBase::TextureBase(DeviceBase* device, + const TextureDescriptor* descriptor, + TextureState state) + : ApiObjectBase(device, descriptor->label), + mDimension(descriptor->dimension), + mFormat(device->GetValidInternalFormat(descriptor->format)), + mSize(descriptor->size), + mMipLevelCount(descriptor->mipLevelCount), + mSampleCount(descriptor->sampleCount), + mUsage(descriptor->usage), + mInternalUsage(mUsage), + mState(state) { + uint32_t subresourceCount = + mMipLevelCount * GetArrayLayers() * GetAspectCount(mFormat.aspects); + mIsSubresourceContentInitializedAtIndex = std::vector<bool>(subresourceCount, false); + + for (uint32_t i = 0; i < descriptor->viewFormatCount; ++i) { + if (descriptor->viewFormats[i] == descriptor->format) { + // Skip our own format, so the backends don't allocate the texture for + // reinterpretation if it's not needed. + continue; + } + mViewFormats[device->GetValidInternalFormat(descriptor->viewFormats[i])] = true; + } + + const DawnTextureInternalUsageDescriptor* internalUsageDesc = nullptr; + FindInChain(descriptor->nextInChain, &internalUsageDesc); + if (internalUsageDesc != nullptr) { + mInternalUsage |= internalUsageDesc->internalUsage; + } + TrackInDevice(); + } + + static Format kUnusedFormat; + + TextureBase::TextureBase(DeviceBase* device, TextureState state) + : ApiObjectBase(device, kLabelNotImplemented), mFormat(kUnusedFormat), mState(state) { + TrackInDevice(); + } + + TextureBase::TextureBase(DeviceBase* device, ObjectBase::ErrorTag tag) + : ApiObjectBase(device, tag), mFormat(kUnusedFormat) { + } + + void TextureBase::DestroyImpl() { + mState = TextureState::Destroyed; + } + + // static + TextureBase* TextureBase::MakeError(DeviceBase* device) { + return new TextureBase(device, ObjectBase::kError); + } + + ObjectType TextureBase::GetType() const { + return ObjectType::Texture; + } + + wgpu::TextureDimension TextureBase::GetDimension() const { + ASSERT(!IsError()); + return mDimension; + } + + const Format& TextureBase::GetFormat() const { + ASSERT(!IsError()); + return mFormat; + } + const FormatSet& TextureBase::GetViewFormats() const { + ASSERT(!IsError()); + return mViewFormats; + } + const Extent3D& TextureBase::GetSize() const { + ASSERT(!IsError()); + return mSize; + } + uint32_t TextureBase::GetWidth() const { + ASSERT(!IsError()); + return mSize.width; + } + uint32_t TextureBase::GetHeight() const { + ASSERT(!IsError()); + return mSize.height; + } + uint32_t TextureBase::GetDepth() const { + ASSERT(!IsError()); + ASSERT(mDimension == wgpu::TextureDimension::e3D); + return mSize.depthOrArrayLayers; + } + uint32_t TextureBase::GetArrayLayers() const { + ASSERT(!IsError()); + if (mDimension == wgpu::TextureDimension::e3D) { + return 1; + } + return mSize.depthOrArrayLayers; + } + uint32_t TextureBase::GetNumMipLevels() const { + ASSERT(!IsError()); + return mMipLevelCount; + } + SubresourceRange TextureBase::GetAllSubresources() const { + ASSERT(!IsError()); + return {mFormat.aspects, {0, GetArrayLayers()}, {0, mMipLevelCount}}; + } + uint32_t TextureBase::GetSampleCount() const { + ASSERT(!IsError()); + return mSampleCount; + } + uint32_t TextureBase::GetSubresourceCount() const { + ASSERT(!IsError()); + return static_cast<uint32_t>(mIsSubresourceContentInitializedAtIndex.size()); + } + wgpu::TextureUsage TextureBase::GetUsage() const { + ASSERT(!IsError()); + return mUsage; + } + wgpu::TextureUsage TextureBase::GetInternalUsage() const { + ASSERT(!IsError()); + return mInternalUsage; + } + + TextureBase::TextureState TextureBase::GetTextureState() const { + ASSERT(!IsError()); + return mState; + } + + uint32_t TextureBase::GetSubresourceIndex(uint32_t mipLevel, + uint32_t arraySlice, + Aspect aspect) const { + ASSERT(HasOneBit(aspect)); + return mipLevel + + GetNumMipLevels() * (arraySlice + GetArrayLayers() * GetAspectIndex(aspect)); + } + + bool TextureBase::IsSubresourceContentInitialized(const SubresourceRange& range) const { + ASSERT(!IsError()); + for (Aspect aspect : IterateEnumMask(range.aspects)) { + for (uint32_t arrayLayer = range.baseArrayLayer; + arrayLayer < range.baseArrayLayer + range.layerCount; ++arrayLayer) { + for (uint32_t mipLevel = range.baseMipLevel; + mipLevel < range.baseMipLevel + range.levelCount; ++mipLevel) { + uint32_t subresourceIndex = GetSubresourceIndex(mipLevel, arrayLayer, aspect); + ASSERT(subresourceIndex < mIsSubresourceContentInitializedAtIndex.size()); + if (!mIsSubresourceContentInitializedAtIndex[subresourceIndex]) { + return false; + } + } + } + } + return true; + } + + void TextureBase::SetIsSubresourceContentInitialized(bool isInitialized, + const SubresourceRange& range) { + ASSERT(!IsError()); + for (Aspect aspect : IterateEnumMask(range.aspects)) { + for (uint32_t arrayLayer = range.baseArrayLayer; + arrayLayer < range.baseArrayLayer + range.layerCount; ++arrayLayer) { + for (uint32_t mipLevel = range.baseMipLevel; + mipLevel < range.baseMipLevel + range.levelCount; ++mipLevel) { + uint32_t subresourceIndex = GetSubresourceIndex(mipLevel, arrayLayer, aspect); + ASSERT(subresourceIndex < mIsSubresourceContentInitializedAtIndex.size()); + mIsSubresourceContentInitializedAtIndex[subresourceIndex] = isInitialized; + } + } + } + } + + MaybeError TextureBase::ValidateCanUseInSubmitNow() const { + ASSERT(!IsError()); + DAWN_INVALID_IF(mState == TextureState::Destroyed, "Destroyed texture %s used in a submit.", + this); + return {}; + } + + bool TextureBase::IsMultisampledTexture() const { + ASSERT(!IsError()); + return mSampleCount > 1; + } + + Extent3D TextureBase::GetMipLevelVirtualSize(uint32_t level) const { + Extent3D extent = {std::max(mSize.width >> level, 1u), 1u, 1u}; + if (mDimension == wgpu::TextureDimension::e1D) { + return extent; + } + + extent.height = std::max(mSize.height >> level, 1u); + if (mDimension == wgpu::TextureDimension::e2D) { + return extent; + } + + extent.depthOrArrayLayers = std::max(mSize.depthOrArrayLayers >> level, 1u); + return extent; + } + + Extent3D TextureBase::GetMipLevelPhysicalSize(uint32_t level) const { + Extent3D extent = GetMipLevelVirtualSize(level); + + // Compressed Textures will have paddings if their width or height is not a multiple of + // 4 at non-zero mipmap levels. + if (mFormat.isCompressed && level != 0) { + // If |level| is non-zero, then each dimension of |extent| is at most half of + // the max texture dimension. Computations here which add the block width/height + // to the extent cannot overflow. + const TexelBlockInfo& blockInfo = mFormat.GetAspectInfo(wgpu::TextureAspect::All).block; + extent.width = (extent.width + blockInfo.width - 1) / blockInfo.width * blockInfo.width; + extent.height = + (extent.height + blockInfo.height - 1) / blockInfo.height * blockInfo.height; + } + + return extent; + } + + Extent3D TextureBase::ClampToMipLevelVirtualSize(uint32_t level, + const Origin3D& origin, + const Extent3D& extent) const { + const Extent3D virtualSizeAtLevel = GetMipLevelVirtualSize(level); + ASSERT(origin.x <= virtualSizeAtLevel.width); + ASSERT(origin.y <= virtualSizeAtLevel.height); + uint32_t clampedCopyExtentWidth = (extent.width > virtualSizeAtLevel.width - origin.x) + ? (virtualSizeAtLevel.width - origin.x) + : extent.width; + uint32_t clampedCopyExtentHeight = (extent.height > virtualSizeAtLevel.height - origin.y) + ? (virtualSizeAtLevel.height - origin.y) + : extent.height; + return {clampedCopyExtentWidth, clampedCopyExtentHeight, extent.depthOrArrayLayers}; + } + + ResultOrError<Ref<TextureViewBase>> TextureBase::CreateView( + const TextureViewDescriptor* descriptor) { + return GetDevice()->CreateTextureView(this, descriptor); + } + + TextureViewBase* TextureBase::APICreateView(const TextureViewDescriptor* descriptor) { + DeviceBase* device = GetDevice(); + + Ref<TextureViewBase> result; + if (device->ConsumedError(CreateView(descriptor), &result, "calling %s.CreateView(%s).", + this, descriptor)) { + return TextureViewBase::MakeError(device); + } + return result.Detach(); + } + + void TextureBase::APIDestroy() { + if (GetDevice()->ConsumedError(ValidateDestroy(), "calling %s.Destroy().", this)) { + return; + } + ASSERT(!IsError()); + Destroy(); + } + + MaybeError TextureBase::ValidateDestroy() const { + DAWN_TRY(GetDevice()->ValidateObject(this)); + return {}; + } + + // TextureViewBase + + TextureViewBase::TextureViewBase(TextureBase* texture, const TextureViewDescriptor* descriptor) + : ApiObjectBase(texture->GetDevice(), descriptor->label), + mTexture(texture), + mFormat(GetDevice()->GetValidInternalFormat(descriptor->format)), + mDimension(descriptor->dimension), + mRange({ConvertViewAspect(mFormat, descriptor->aspect), + {descriptor->baseArrayLayer, descriptor->arrayLayerCount}, + {descriptor->baseMipLevel, descriptor->mipLevelCount}}) { + TrackInDevice(); + } + + TextureViewBase::TextureViewBase(TextureBase* texture) + : ApiObjectBase(texture->GetDevice(), kLabelNotImplemented), + mTexture(texture), + mFormat(kUnusedFormat) { + TrackInDevice(); + } + + TextureViewBase::TextureViewBase(DeviceBase* device, ObjectBase::ErrorTag tag) + : ApiObjectBase(device, tag), mFormat(kUnusedFormat) { + } + + void TextureViewBase::DestroyImpl() { + } + + // static + TextureViewBase* TextureViewBase::MakeError(DeviceBase* device) { + return new TextureViewBase(device, ObjectBase::kError); + } + + ObjectType TextureViewBase::GetType() const { + return ObjectType::TextureView; + } + + const TextureBase* TextureViewBase::GetTexture() const { + ASSERT(!IsError()); + return mTexture.Get(); + } + + TextureBase* TextureViewBase::GetTexture() { + ASSERT(!IsError()); + return mTexture.Get(); + } + + Aspect TextureViewBase::GetAspects() const { + ASSERT(!IsError()); + return mRange.aspects; + } + + const Format& TextureViewBase::GetFormat() const { + ASSERT(!IsError()); + return mFormat; + } + + wgpu::TextureViewDimension TextureViewBase::GetDimension() const { + ASSERT(!IsError()); + return mDimension; + } + + uint32_t TextureViewBase::GetBaseMipLevel() const { + ASSERT(!IsError()); + return mRange.baseMipLevel; + } + + uint32_t TextureViewBase::GetLevelCount() const { + ASSERT(!IsError()); + return mRange.levelCount; + } + + uint32_t TextureViewBase::GetBaseArrayLayer() const { + ASSERT(!IsError()); + return mRange.baseArrayLayer; + } + + uint32_t TextureViewBase::GetLayerCount() const { + ASSERT(!IsError()); + return mRange.layerCount; + } + + const SubresourceRange& TextureViewBase::GetSubresourceRange() const { + ASSERT(!IsError()); + return mRange; + } + +} // namespace dawn::native
diff --git a/src/dawn/native/Texture.h b/src/dawn/native/Texture.h new file mode 100644 index 0000000..4024c82 --- /dev/null +++ b/src/dawn/native/Texture.h
@@ -0,0 +1,163 @@ +// Copyright 2017 The Dawn Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#ifndef DAWNNATIVE_TEXTURE_H_ +#define DAWNNATIVE_TEXTURE_H_ + +#include "dawn/common/ityp_array.h" +#include "dawn/common/ityp_bitset.h" +#include "dawn/native/Error.h" +#include "dawn/native/Format.h" +#include "dawn/native/Forward.h" +#include "dawn/native/ObjectBase.h" +#include "dawn/native/Subresource.h" + +#include "dawn/native/dawn_platform.h" + +#include <vector> + +namespace dawn::native { + + MaybeError ValidateTextureDescriptor(const DeviceBase* device, + const TextureDescriptor* descriptor); + MaybeError ValidateTextureViewDescriptor(const DeviceBase* device, + const TextureBase* texture, + const TextureViewDescriptor* descriptor); + TextureViewDescriptor GetTextureViewDescriptorWithDefaults( + const TextureBase* texture, + const TextureViewDescriptor* descriptor); + + bool IsValidSampleCount(uint32_t sampleCount); + + static constexpr wgpu::TextureUsage kReadOnlyTextureUsages = + wgpu::TextureUsage::CopySrc | wgpu::TextureUsage::TextureBinding | + kReadOnlyRenderAttachment; + + class TextureBase : public ApiObjectBase { + public: + enum class TextureState { OwnedInternal, OwnedExternal, Destroyed }; + enum class ClearValue { Zero, NonZero }; + TextureBase(DeviceBase* device, const TextureDescriptor* descriptor, TextureState state); + + static TextureBase* MakeError(DeviceBase* device); + + ObjectType GetType() const override; + + wgpu::TextureDimension GetDimension() const; + const Format& GetFormat() const; + const FormatSet& GetViewFormats() const; + const Extent3D& GetSize() const; + uint32_t GetWidth() const; + uint32_t GetHeight() const; + uint32_t GetDepth() const; + uint32_t GetArrayLayers() const; + uint32_t GetNumMipLevels() const; + SubresourceRange GetAllSubresources() const; + uint32_t GetSampleCount() const; + uint32_t GetSubresourceCount() const; + + // |GetUsage| returns the usage with which the texture was created using the base WebGPU + // API. The dawn-internal-usages extension may add additional usages. |GetInternalUsage| + // returns the union of base usage and the usages added by the extension. + wgpu::TextureUsage GetUsage() const; + wgpu::TextureUsage GetInternalUsage() const; + + TextureState GetTextureState() const; + uint32_t GetSubresourceIndex(uint32_t mipLevel, uint32_t arraySlice, Aspect aspect) const; + bool IsSubresourceContentInitialized(const SubresourceRange& range) const; + void SetIsSubresourceContentInitialized(bool isInitialized, const SubresourceRange& range); + + MaybeError ValidateCanUseInSubmitNow() const; + + bool IsMultisampledTexture() const; + + // For a texture with non-block-compressed texture format, its physical size is always equal + // to its virtual size. For a texture with block compressed texture format, the physical + // size is the one with paddings if necessary, which is always a multiple of the block size + // and used in texture copying. The virtual size is the one without paddings, which is not + // required to be a multiple of the block size and used in texture sampling. + Extent3D GetMipLevelPhysicalSize(uint32_t level) const; + Extent3D GetMipLevelVirtualSize(uint32_t level) const; + Extent3D ClampToMipLevelVirtualSize(uint32_t level, + const Origin3D& origin, + const Extent3D& extent) const; + + ResultOrError<Ref<TextureViewBase>> CreateView( + const TextureViewDescriptor* descriptor = nullptr); + + // Dawn API + TextureViewBase* APICreateView(const TextureViewDescriptor* descriptor = nullptr); + void APIDestroy(); + + protected: + // Constructor used only for mocking and testing. + TextureBase(DeviceBase* device, TextureState state); + void DestroyImpl() override; + + private: + TextureBase(DeviceBase* device, ObjectBase::ErrorTag tag); + + MaybeError ValidateDestroy() const; + wgpu::TextureDimension mDimension; + const Format& mFormat; + FormatSet mViewFormats; + Extent3D mSize; + uint32_t mMipLevelCount; + uint32_t mSampleCount; + wgpu::TextureUsage mUsage = wgpu::TextureUsage::None; + wgpu::TextureUsage mInternalUsage = wgpu::TextureUsage::None; + TextureState mState; + + // TODO(crbug.com/dawn/845): Use a more optimized data structure to save space + std::vector<bool> mIsSubresourceContentInitializedAtIndex; + }; + + class TextureViewBase : public ApiObjectBase { + public: + TextureViewBase(TextureBase* texture, const TextureViewDescriptor* descriptor); + + static TextureViewBase* MakeError(DeviceBase* device); + + ObjectType GetType() const override; + + const TextureBase* GetTexture() const; + TextureBase* GetTexture(); + + Aspect GetAspects() const; + const Format& GetFormat() const; + wgpu::TextureViewDimension GetDimension() const; + uint32_t GetBaseMipLevel() const; + uint32_t GetLevelCount() const; + uint32_t GetBaseArrayLayer() const; + uint32_t GetLayerCount() const; + const SubresourceRange& GetSubresourceRange() const; + + protected: + // Constructor used only for mocking and testing. + TextureViewBase(TextureBase* texture); + void DestroyImpl() override; + + private: + TextureViewBase(DeviceBase* device, ObjectBase::ErrorTag tag); + + Ref<TextureBase> mTexture; + + const Format& mFormat; + wgpu::TextureViewDimension mDimension; + SubresourceRange mRange; + }; + +} // namespace dawn::native + +#endif // DAWNNATIVE_TEXTURE_H_
diff --git a/src/dawn/native/TintUtils.cpp b/src/dawn/native/TintUtils.cpp new file mode 100644 index 0000000..d84c982 --- /dev/null +++ b/src/dawn/native/TintUtils.cpp
@@ -0,0 +1,55 @@ +// Copyright 2021 The Dawn Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include "dawn/native/TintUtils.h" +#include "dawn/native/Device.h" + +#include <tint/tint.h> + +namespace dawn::native { + + namespace { + + thread_local DeviceBase* tlDevice = nullptr; + + void TintICEReporter(const tint::diag::List& diagnostics) { + if (tlDevice) { + tlDevice->HandleError(InternalErrorType::Validation, diagnostics.str().c_str()); + } + } + + bool InitializeTintErrorReporter() { + tint::SetInternalCompilerErrorReporter(&TintICEReporter); + return true; + } + + } // namespace + + ScopedTintICEHandler::ScopedTintICEHandler(DeviceBase* device) { + // Call tint::SetInternalCompilerErrorReporter() the first time + // this constructor is called. Static initialization is + // guaranteed to be thread-safe, and only occur once. + static bool init_once_tint_error_reporter = InitializeTintErrorReporter(); + (void)init_once_tint_error_reporter; + + // Shouldn't have overlapping instances of this handler. + ASSERT(tlDevice == nullptr); + tlDevice = device; + } + + ScopedTintICEHandler::~ScopedTintICEHandler() { + tlDevice = nullptr; + } + +} // namespace dawn::native
diff --git a/src/dawn/native/TintUtils.h b/src/dawn/native/TintUtils.h new file mode 100644 index 0000000..2dcb8f3 --- /dev/null +++ b/src/dawn/native/TintUtils.h
@@ -0,0 +1,37 @@ +// Copyright 2021 The Dawn Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#ifndef DAWNNATIVE_TINTUTILS_H_ +#define DAWNNATIVE_TINTUTILS_H_ + +#include "dawn/common/NonCopyable.h" + +namespace dawn::native { + + class DeviceBase; + + // Indicates that for the lifetime of this object tint internal compiler errors should be + // reported to the given device. + class ScopedTintICEHandler : public NonCopyable { + public: + ScopedTintICEHandler(DeviceBase* device); + ~ScopedTintICEHandler(); + + private: + ScopedTintICEHandler(ScopedTintICEHandler&&) = delete; + }; + +} // namespace dawn::native + +#endif // DAWNNATIVE_TEXTURE_H_
diff --git a/src/dawn/native/ToBackend.h b/src/dawn/native/ToBackend.h new file mode 100644 index 0000000..a2a69cb --- /dev/null +++ b/src/dawn/native/ToBackend.h
@@ -0,0 +1,155 @@ +// Copyright 2017 The Dawn Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#ifndef DAWNNATIVE_TOBACKEND_H_ +#define DAWNNATIVE_TOBACKEND_H_ + +#include "dawn/native/Forward.h" + +namespace dawn::native { + + // ToBackendTraits implements the mapping from base type to member type of BackendTraits + template <typename T, typename BackendTraits> + struct ToBackendTraits; + + template <typename BackendTraits> + struct ToBackendTraits<AdapterBase, BackendTraits> { + using BackendType = typename BackendTraits::AdapterType; + }; + + template <typename BackendTraits> + struct ToBackendTraits<BindGroupBase, BackendTraits> { + using BackendType = typename BackendTraits::BindGroupType; + }; + + template <typename BackendTraits> + struct ToBackendTraits<BindGroupLayoutBase, BackendTraits> { + using BackendType = typename BackendTraits::BindGroupLayoutType; + }; + + template <typename BackendTraits> + struct ToBackendTraits<BufferBase, BackendTraits> { + using BackendType = typename BackendTraits::BufferType; + }; + + template <typename BackendTraits> + struct ToBackendTraits<CommandBufferBase, BackendTraits> { + using BackendType = typename BackendTraits::CommandBufferType; + }; + + template <typename BackendTraits> + struct ToBackendTraits<ComputePipelineBase, BackendTraits> { + using BackendType = typename BackendTraits::ComputePipelineType; + }; + + template <typename BackendTraits> + struct ToBackendTraits<DeviceBase, BackendTraits> { + using BackendType = typename BackendTraits::DeviceType; + }; + + template <typename BackendTraits> + struct ToBackendTraits<PipelineLayoutBase, BackendTraits> { + using BackendType = typename BackendTraits::PipelineLayoutType; + }; + + template <typename BackendTraits> + struct ToBackendTraits<QuerySetBase, BackendTraits> { + using BackendType = typename BackendTraits::QuerySetType; + }; + + template <typename BackendTraits> + struct ToBackendTraits<QueueBase, BackendTraits> { + using BackendType = typename BackendTraits::QueueType; + }; + + template <typename BackendTraits> + struct ToBackendTraits<RenderPipelineBase, BackendTraits> { + using BackendType = typename BackendTraits::RenderPipelineType; + }; + + template <typename BackendTraits> + struct ToBackendTraits<ResourceHeapBase, BackendTraits> { + using BackendType = typename BackendTraits::ResourceHeapType; + }; + + template <typename BackendTraits> + struct ToBackendTraits<SamplerBase, BackendTraits> { + using BackendType = typename BackendTraits::SamplerType; + }; + + template <typename BackendTraits> + struct ToBackendTraits<ShaderModuleBase, BackendTraits> { + using BackendType = typename BackendTraits::ShaderModuleType; + }; + + template <typename BackendTraits> + struct ToBackendTraits<StagingBufferBase, BackendTraits> { + using BackendType = typename BackendTraits::StagingBufferType; + }; + + template <typename BackendTraits> + struct ToBackendTraits<TextureBase, BackendTraits> { + using BackendType = typename BackendTraits::TextureType; + }; + + template <typename BackendTraits> + struct ToBackendTraits<SwapChainBase, BackendTraits> { + using BackendType = typename BackendTraits::SwapChainType; + }; + + template <typename BackendTraits> + struct ToBackendTraits<TextureViewBase, BackendTraits> { + using BackendType = typename BackendTraits::TextureViewType; + }; + + // ToBackendBase implements conversion to the given BackendTraits + // To use it in a backend, use the following: + // template<typename T> + // auto ToBackend(T&& common) -> decltype(ToBackendBase<MyBackendTraits>(common)) { + // return ToBackendBase<MyBackendTraits>(common); + // } + + template <typename BackendTraits, typename T> + Ref<typename ToBackendTraits<T, BackendTraits>::BackendType>& ToBackendBase(Ref<T>& common) { + return reinterpret_cast<Ref<typename ToBackendTraits<T, BackendTraits>::BackendType>&>( + common); + } + + template <typename BackendTraits, typename T> + Ref<typename ToBackendTraits<T, BackendTraits>::BackendType>&& ToBackendBase(Ref<T>&& common) { + return reinterpret_cast<Ref<typename ToBackendTraits<T, BackendTraits>::BackendType>&&>( + common); + } + + template <typename BackendTraits, typename T> + const Ref<typename ToBackendTraits<T, BackendTraits>::BackendType>& ToBackendBase( + const Ref<T>& common) { + return reinterpret_cast< + const Ref<typename ToBackendTraits<T, BackendTraits>::BackendType>&>(common); + } + + template <typename BackendTraits, typename T> + typename ToBackendTraits<T, BackendTraits>::BackendType* ToBackendBase(T* common) { + return reinterpret_cast<typename ToBackendTraits<T, BackendTraits>::BackendType*>(common); + } + + template <typename BackendTraits, typename T> + const typename ToBackendTraits<T, BackendTraits>::BackendType* ToBackendBase(const T* common) { + return reinterpret_cast<const typename ToBackendTraits<T, BackendTraits>::BackendType*>( + common); + } + +} // namespace dawn::native + +#endif // DAWNNATIVE_TOBACKEND_H_
diff --git a/src/dawn/native/Toggles.cpp b/src/dawn/native/Toggles.cpp new file mode 100644 index 0000000..9b3a655 --- /dev/null +++ b/src/dawn/native/Toggles.cpp
@@ -0,0 +1,346 @@ +// Copyright 2019 The Dawn Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include <array> + +#include "dawn/common/Assert.h" +#include "dawn/common/BitSetIterator.h" +#include "dawn/native/Toggles.h" + +namespace dawn::native { + namespace { + + struct ToggleEnumAndInfo { + Toggle toggle; + ToggleInfo info; + }; + + using ToggleEnumAndInfoList = + std::array<ToggleEnumAndInfo, static_cast<size_t>(Toggle::EnumCount)>; + + static constexpr ToggleEnumAndInfoList kToggleNameAndInfoList = {{ + {Toggle::EmulateStoreAndMSAAResolve, + {"emulate_store_and_msaa_resolve", + "Emulate storing into multisampled color attachments and doing MSAA resolve " + "simultaneously. This workaround is enabled by default on the Metal drivers that do " + "not support MTLStoreActionStoreAndMultisampleResolve. To support StoreOp::Store on " + "those platforms, we should do MSAA resolve in another render pass after ending the " + "previous one.", + "https://crbug.com/dawn/56"}}, + {Toggle::NonzeroClearResourcesOnCreationForTesting, + {"nonzero_clear_resources_on_creation_for_testing", + "Clears texture to full 1 bits as soon as they are created, but doesn't update " + "the tracking state of the texture. This way we can test the logic of clearing " + "textures that use recycled memory.", + "https://crbug.com/dawn/145"}}, + {Toggle::AlwaysResolveIntoZeroLevelAndLayer, + {"always_resolve_into_zero_level_and_layer", + "When the resolve target is a texture view that is created on the non-zero level or " + "layer of a texture, we first resolve into a temporarily 2D texture with only one " + "mipmap level and one array layer, and copy the result of MSAA resolve into the " + "true resolve target. This workaround is enabled by default on the Metal drivers " + "that have bugs when setting non-zero resolveLevel or resolveSlice.", + "https://crbug.com/dawn/56"}}, + {Toggle::LazyClearResourceOnFirstUse, + {"lazy_clear_resource_on_first_use", + "Clears resource to zero on first usage. This initializes the resource " + "so that no dirty bits from recycled memory is present in the new resource.", + "https://crbug.com/dawn/145"}}, + {Toggle::TurnOffVsync, + {"turn_off_vsync", + "Turn off vsync when rendering. In order to do performance test or run perf tests, " + "turn off vsync so that the fps can exeed 60.", + "https://crbug.com/dawn/237"}}, + {Toggle::UseTemporaryBufferInCompressedTextureToTextureCopy, + {"use_temporary_buffer_in_texture_to_texture_copy", + "Split texture-to-texture copy into two copies: copy from source texture into a " + "temporary buffer, and copy from the temporary buffer into the destination texture " + "when copying between compressed textures that don't have block-aligned sizes. This " + "workaround is enabled by default on all Vulkan drivers to solve an issue in the " + "Vulkan SPEC about the texture-to-texture copies with compressed formats. See #1005 " + "(https://github.com/KhronosGroup/Vulkan-Docs/issues/1005) for more details.", + "https://crbug.com/dawn/42"}}, + {Toggle::UseD3D12ResourceHeapTier2, + {"use_d3d12_resource_heap_tier2", + "Enable support for resource heap tier 2. Resource heap tier 2 allows mixing of " + "texture and buffers in the same heap. This allows better heap re-use and reduces " + "fragmentation.", + "https://crbug.com/dawn/27"}}, + {Toggle::UseD3D12RenderPass, + {"use_d3d12_render_pass", + "Use the D3D12 render pass API introduced in Windows build 1809 by default. On " + "versions of Windows prior to build 1809, or when this toggle is turned off, Dawn " + "will emulate a render pass.", + "https://crbug.com/dawn/36"}}, + {Toggle::UseD3D12ResidencyManagement, + {"use_d3d12_residency_management", + "Enable residency management. This allows page-in and page-out of resource heaps in " + "GPU memory. This component improves overcommitted performance by keeping the most " + "recently used resources local to the GPU. Turning this component off can cause " + "allocation failures when application memory exceeds physical device memory.", + "https://crbug.com/dawn/193"}}, + {Toggle::DisableResourceSuballocation, + {"disable_resource_suballocation", + "Force the backends to not perform resource suballocation. This may expose " + "allocation " + "patterns which would otherwise only occur with large or specific types of " + "resources.", + "https://crbug.com/1313172"}}, + {Toggle::SkipValidation, + {"skip_validation", "Skip expensive validation of Dawn commands.", + "https://crbug.com/dawn/271"}}, + {Toggle::VulkanUseD32S8, + {"vulkan_use_d32s8", + "Vulkan mandates support of either D32_FLOAT_S8 or D24_UNORM_S8. When available the " + "backend will use D32S8 (toggle to on) but setting the toggle to off will make it " + "use the D24S8 format when possible.", + "https://crbug.com/dawn/286"}}, + {Toggle::VulkanUseS8, + {"vulkan_use_s8", + "Vulkan has a pure stencil8 format but it is not universally available. When this " + "toggle is on, the backend will use S8 for the stencil8 format, otherwise it will " + "fallback to D32S8 or D24S8.", + "https://crbug.com/dawn/666"}}, + {Toggle::MetalDisableSamplerCompare, + {"metal_disable_sampler_compare", + "Disables the use of sampler compare on Metal. This is unsupported before A9 " + "processors.", + "https://crbug.com/dawn/342"}}, + {Toggle::MetalUseSharedModeForCounterSampleBuffer, + {"metal_use_shared_mode_for_counter_sample_buffer", + "The query set on Metal need to create MTLCounterSampleBuffer which storage mode " + "must be either MTLStorageModeShared or MTLStorageModePrivate. But the private mode " + "does not work properly on Intel platforms. The workaround is use shared mode " + "instead.", + "https://crbug.com/dawn/434"}}, + {Toggle::DisableBaseVertex, + {"disable_base_vertex", + "Disables the use of non-zero base vertex which is unsupported on some platforms.", + "https://crbug.com/dawn/343"}}, + {Toggle::DisableBaseInstance, + {"disable_base_instance", + "Disables the use of non-zero base instance which is unsupported on some " + "platforms.", + "https://crbug.com/dawn/343"}}, + {Toggle::DisableIndexedDrawBuffers, + {"disable_indexed_draw_buffers", + "Disables the use of indexed draw buffer state which is unsupported on some " + "platforms.", + "https://crbug.com/dawn/582"}}, + {Toggle::DisableSnormRead, + {"disable_snorm_read", + "Disables reading from Snorm textures which is unsupported on some platforms.", + "https://crbug.com/dawn/667"}}, + {Toggle::DisableDepthStencilRead, + {"disable_depth_stencil_read", + "Disables reading from depth/stencil textures which is unsupported on some " + "platforms.", + "https://crbug.com/dawn/667"}}, + {Toggle::DisableSampleVariables, + {"disable_sample_variables", + "Disables gl_SampleMask and related functionality which is unsupported on some " + "platforms.", + "https://crbug.com/dawn/673"}}, + {Toggle::UseD3D12SmallShaderVisibleHeapForTesting, + {"use_d3d12_small_shader_visible_heap", + "Enable use of a small D3D12 shader visible heap, instead of using a large one by " + "default. This setting is used to test bindgroup encoding.", + "https://crbug.com/dawn/155"}}, + {Toggle::UseDXC, + {"use_dxc", + "Use DXC instead of FXC for compiling HLSL when both dxcompiler.dll and dxil.dll " + "is available.", + "https://crbug.com/dawn/402"}}, + {Toggle::DisableRobustness, + {"disable_robustness", "Disable robust buffer access", "https://crbug.com/dawn/480"}}, + {Toggle::MetalEnableVertexPulling, + {"metal_enable_vertex_pulling", + "Uses vertex pulling to protect out-of-bounds reads on Metal", + "https://crbug.com/dawn/480"}}, + {Toggle::DisallowUnsafeAPIs, + {"disallow_unsafe_apis", + "Produces validation errors on API entry points or parameter combinations that " + "aren't considered secure yet.", + "http://crbug.com/1138528"}}, + {Toggle::FlushBeforeClientWaitSync, + {"flush_before_client_wait_sync", + "Call glFlush before glClientWaitSync to work around bugs in the latter", + "https://crbug.com/dawn/633"}}, + {Toggle::UseTempBufferInSmallFormatTextureToTextureCopyFromGreaterToLessMipLevel, + {"use_temp_buffer_in_small_format_texture_to_texture_copy_from_greater_to_less_mip_" + "level", + "Split texture-to-texture copy into two copies: copy from source texture into a " + "temporary buffer, and copy from the temporary buffer into the destination texture " + "under specific situations. This workaround is by default enabled on some Intel " + "GPUs which have a driver bug in the execution of CopyTextureRegion() when we copy " + "with the formats whose texel block sizes are less than 4 bytes from a greater mip " + "level to a smaller mip level on D3D12 backends.", + "https://crbug.com/1161355"}}, + {Toggle::EmitHLSLDebugSymbols, + {"emit_hlsl_debug_symbols", + "Sets the D3DCOMPILE_SKIP_OPTIMIZATION and D3DCOMPILE_DEBUG compilation flags when " + "compiling HLSL code. Enables better shader debugging with external graphics " + "debugging tools.", + "https://crbug.com/dawn/776"}}, + {Toggle::DisallowSpirv, + {"disallow_spirv", + "Disallow usage of SPIR-V completely so that only WGSL is used for shader modules. " + "This is useful to prevent a Chromium renderer process from successfully sending " + "SPIR-V code to be compiled in the GPU process.", + "https://crbug.com/1214923"}}, + {Toggle::DumpShaders, + {"dump_shaders", + "Dump shaders for debugging purposes. Dumped shaders will be log via " + "EmitLog, thus printed in Chrome console or consumed by user-defined callback " + "function.", + "https://crbug.com/dawn/792"}}, + {Toggle::DEPRECATED_DumpTranslatedShaders, + {"dump_translated_shaders", "Deprecated. Use dump_shaders", + "https://crbug.com/dawn/792"}}, + {Toggle::ForceWGSLStep, + {"force_wgsl_step", + "When ingesting SPIR-V shaders, force a first conversion to WGSL. This allows " + "testing Tint's SPIRV->WGSL translation on real content to be sure that it will " + "work when the same translation runs in a WASM module in the page.", + "https://crbug.com/dawn/960"}}, + {Toggle::DisableWorkgroupInit, + {"disable_workgroup_init", + "Disables the workgroup memory zero-initialization for compute shaders.", + "https://crbug.com/tint/1003"}}, + {Toggle::DisableSymbolRenaming, + {"disable_symbol_renaming", + "Disables the WGSL symbol renaming so that names are preserved.", + "https://crbug.com/dawn/1016"}}, + {Toggle::UseUserDefinedLabelsInBackend, + {"use_user_defined_labels_in_backend", + "Enables calls to SetLabel to be forwarded to backend-specific APIs that label " + "objects.", + "https://crbug.com/dawn/840"}}, + {Toggle::DisableR8RG8Mipmaps, + {"disable_r8_rg8_mipmaps", + "Disables mipmaps for r8unorm and rg8unorm textures, which are known on some drivers " + "to not clear correctly.", + "https://crbug.com/dawn/1071"}}, + {Toggle::UseDummyFragmentInVertexOnlyPipeline, + {"use_dummy_fragment_in_vertex_only_pipeline", + "Use a dummy empty fragment shader in vertex only render pipeline. This toggle must " + "be enabled for OpenGL ES backend, and serves as a workaround by default enabled on " + "some Metal devices with Intel GPU to ensure the depth result is correct.", + "https://crbug.com/dawn/136"}}, + {Toggle::FxcOptimizations, + {"fxc_optimizations", + "Enable optimizations when compiling with FXC. Disabled by default because FXC " + "miscompiles in many cases when optimizations are enabled.", + "https://crbug.com/dawn/1203"}}, + {Toggle::RecordDetailedTimingInTraceEvents, + {"record_detailed_timing_in_trace_events", + "Record detailed timing information in trace events at certain point. Currently the " + "timing information is recorded right before calling ExecuteCommandLists on a D3D12 " + "command queue, and the information includes system time, CPU timestamp, GPU " + "timestamp, and their frequency.", + "https://crbug.com/dawn/1264"}}, + {Toggle::DisableTimestampQueryConversion, + {"disable_timestamp_query_conversion", + "Resolve timestamp queries into ticks instead of nanoseconds.", + "https://crbug.com/dawn/1305"}}, + {Toggle::VulkanUseZeroInitializeWorkgroupMemoryExtension, + {"use_vulkan_zero_initialize_workgroup_memory_extension", + "Initialize workgroup memory with OpConstantNull on Vulkan when the Vulkan extension " + "VK_KHR_zero_initialize_workgroup_memory is supported.", + "https://crbug.com/dawn/1302"}}, + + // Dummy comment to separate the }} so it is clearer what to copy-paste to add a toggle. + }}; + } // anonymous namespace + + void TogglesSet::Set(Toggle toggle, bool enabled) { + if (toggle == Toggle::DEPRECATED_DumpTranslatedShaders) { + Set(Toggle::DumpShaders, enabled); + return; + } + ASSERT(toggle != Toggle::InvalidEnum); + const size_t toggleIndex = static_cast<size_t>(toggle); + toggleBitset.set(toggleIndex, enabled); + } + + bool TogglesSet::Has(Toggle toggle) const { + if (toggle == Toggle::DEPRECATED_DumpTranslatedShaders) { + return Has(Toggle::DumpShaders); + } + ASSERT(toggle != Toggle::InvalidEnum); + const size_t toggleIndex = static_cast<size_t>(toggle); + return toggleBitset.test(toggleIndex); + } + + std::vector<const char*> TogglesSet::GetContainedToggleNames() const { + std::vector<const char*> togglesNameInUse(toggleBitset.count()); + + uint32_t index = 0; + for (uint32_t i : IterateBitSet(toggleBitset)) { + const char* toggleName = ToggleEnumToName(static_cast<Toggle>(i)); + togglesNameInUse[index] = toggleName; + ++index; + } + + return togglesNameInUse; + } + + const char* ToggleEnumToName(Toggle toggle) { + ASSERT(toggle != Toggle::InvalidEnum); + + const ToggleEnumAndInfo& toggleNameAndInfo = + kToggleNameAndInfoList[static_cast<size_t>(toggle)]; + ASSERT(toggleNameAndInfo.toggle == toggle); + return toggleNameAndInfo.info.name; + } + + const ToggleInfo* TogglesInfo::GetToggleInfo(const char* toggleName) { + ASSERT(toggleName); + + EnsureToggleNameToEnumMapInitialized(); + + const auto& iter = mToggleNameToEnumMap.find(toggleName); + if (iter != mToggleNameToEnumMap.cend()) { + return &kToggleNameAndInfoList[static_cast<size_t>(iter->second)].info; + } + return nullptr; + } + + Toggle TogglesInfo::ToggleNameToEnum(const char* toggleName) { + ASSERT(toggleName); + + EnsureToggleNameToEnumMapInitialized(); + + const auto& iter = mToggleNameToEnumMap.find(toggleName); + if (iter != mToggleNameToEnumMap.cend()) { + return kToggleNameAndInfoList[static_cast<size_t>(iter->second)].toggle; + } + return Toggle::InvalidEnum; + } + + void TogglesInfo::EnsureToggleNameToEnumMapInitialized() { + if (mToggleNameToEnumMapInitialized) { + return; + } + + for (size_t index = 0; index < kToggleNameAndInfoList.size(); ++index) { + const ToggleEnumAndInfo& toggleNameAndInfo = kToggleNameAndInfoList[index]; + ASSERT(index == static_cast<size_t>(toggleNameAndInfo.toggle)); + mToggleNameToEnumMap[toggleNameAndInfo.info.name] = toggleNameAndInfo.toggle; + } + + mToggleNameToEnumMapInitialized = true; + } + +} // namespace dawn::native
diff --git a/src/dawn/native/Toggles.h b/src/dawn/native/Toggles.h new file mode 100644 index 0000000..a45a82e --- /dev/null +++ b/src/dawn/native/Toggles.h
@@ -0,0 +1,102 @@ +// Copyright 2019 The Dawn Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#ifndef DAWNNATIVE_TOGGLES_H_ +#define DAWNNATIVE_TOGGLES_H_ + +#include <bitset> +#include <unordered_map> +#include <vector> + +#include "dawn/native/DawnNative.h" + +namespace dawn::native { + + enum class Toggle { + EmulateStoreAndMSAAResolve, + NonzeroClearResourcesOnCreationForTesting, + AlwaysResolveIntoZeroLevelAndLayer, + LazyClearResourceOnFirstUse, + TurnOffVsync, + UseTemporaryBufferInCompressedTextureToTextureCopy, + UseD3D12ResourceHeapTier2, + UseD3D12RenderPass, + UseD3D12ResidencyManagement, + DisableResourceSuballocation, + SkipValidation, + VulkanUseD32S8, + VulkanUseS8, + MetalDisableSamplerCompare, + MetalUseSharedModeForCounterSampleBuffer, + DisableBaseVertex, + DisableBaseInstance, + DisableIndexedDrawBuffers, + DisableSnormRead, + DisableDepthStencilRead, + DisableSampleVariables, + UseD3D12SmallShaderVisibleHeapForTesting, + UseDXC, + DisableRobustness, + MetalEnableVertexPulling, + DisallowUnsafeAPIs, + FlushBeforeClientWaitSync, + UseTempBufferInSmallFormatTextureToTextureCopyFromGreaterToLessMipLevel, + EmitHLSLDebugSymbols, + DisallowSpirv, + DumpShaders, + DEPRECATED_DumpTranslatedShaders, // Use DumpShaders + ForceWGSLStep, + DisableWorkgroupInit, + DisableSymbolRenaming, + UseUserDefinedLabelsInBackend, + DisableR8RG8Mipmaps, + UseDummyFragmentInVertexOnlyPipeline, + FxcOptimizations, + RecordDetailedTimingInTraceEvents, + DisableTimestampQueryConversion, + VulkanUseZeroInitializeWorkgroupMemoryExtension, + + EnumCount, + InvalidEnum = EnumCount, + }; + + // A wrapper of the bitset to store if a toggle is present or not. This wrapper provides the + // convenience to convert the enums of enum class Toggle to the indices of a bitset. + struct TogglesSet { + std::bitset<static_cast<size_t>(Toggle::EnumCount)> toggleBitset; + + void Set(Toggle toggle, bool enabled); + bool Has(Toggle toggle) const; + std::vector<const char*> GetContainedToggleNames() const; + }; + + const char* ToggleEnumToName(Toggle toggle); + + class TogglesInfo { + public: + // Used to query the details of a toggle. Return nullptr if toggleName is not a valid name + // of a toggle supported in Dawn. + const ToggleInfo* GetToggleInfo(const char* toggleName); + Toggle ToggleNameToEnum(const char* toggleName); + + private: + void EnsureToggleNameToEnumMapInitialized(); + + bool mToggleNameToEnumMapInitialized = false; + std::unordered_map<std::string, Toggle> mToggleNameToEnumMap; + }; + +} // namespace dawn::native + +#endif // DAWNNATIVE_TOGGLES_H_
diff --git a/src/dawn/native/VertexFormat.cpp b/src/dawn/native/VertexFormat.cpp new file mode 100644 index 0000000..2f2ae7f --- /dev/null +++ b/src/dawn/native/VertexFormat.cpp
@@ -0,0 +1,69 @@ +// Copyright 2021 The Dawn Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include "dawn/native/VertexFormat.h" + +#include "dawn/common/Assert.h" + +#include <array> + +namespace dawn::native { + + static constexpr std::array<VertexFormatInfo, 31> sVertexFormatTable = {{ + // + {wgpu::VertexFormat::Undefined, 0, 0, 0, VertexFormatBaseType::Float}, + + {wgpu::VertexFormat::Uint8x2, 2, 2, 1, VertexFormatBaseType::Uint}, + {wgpu::VertexFormat::Uint8x4, 4, 4, 1, VertexFormatBaseType::Uint}, + {wgpu::VertexFormat::Sint8x2, 2, 2, 1, VertexFormatBaseType::Sint}, + {wgpu::VertexFormat::Sint8x4, 4, 4, 1, VertexFormatBaseType::Sint}, + {wgpu::VertexFormat::Unorm8x2, 2, 2, 1, VertexFormatBaseType::Float}, + {wgpu::VertexFormat::Unorm8x4, 4, 4, 1, VertexFormatBaseType::Float}, + {wgpu::VertexFormat::Snorm8x2, 2, 2, 1, VertexFormatBaseType::Float}, + {wgpu::VertexFormat::Snorm8x4, 4, 4, 1, VertexFormatBaseType::Float}, + + {wgpu::VertexFormat::Uint16x2, 4, 2, 2, VertexFormatBaseType::Uint}, + {wgpu::VertexFormat::Uint16x4, 8, 4, 2, VertexFormatBaseType::Uint}, + {wgpu::VertexFormat::Sint16x2, 4, 2, 2, VertexFormatBaseType::Sint}, + {wgpu::VertexFormat::Sint16x4, 8, 4, 2, VertexFormatBaseType::Sint}, + {wgpu::VertexFormat::Unorm16x2, 4, 2, 2, VertexFormatBaseType::Float}, + {wgpu::VertexFormat::Unorm16x4, 8, 4, 2, VertexFormatBaseType::Float}, + {wgpu::VertexFormat::Snorm16x2, 4, 2, 2, VertexFormatBaseType::Float}, + {wgpu::VertexFormat::Snorm16x4, 8, 4, 2, VertexFormatBaseType::Float}, + {wgpu::VertexFormat::Float16x2, 4, 2, 2, VertexFormatBaseType::Float}, + {wgpu::VertexFormat::Float16x4, 8, 4, 2, VertexFormatBaseType::Float}, + + {wgpu::VertexFormat::Float32, 4, 1, 4, VertexFormatBaseType::Float}, + {wgpu::VertexFormat::Float32x2, 8, 2, 4, VertexFormatBaseType::Float}, + {wgpu::VertexFormat::Float32x3, 12, 3, 4, VertexFormatBaseType::Float}, + {wgpu::VertexFormat::Float32x4, 16, 4, 4, VertexFormatBaseType::Float}, + {wgpu::VertexFormat::Uint32, 4, 1, 4, VertexFormatBaseType::Uint}, + {wgpu::VertexFormat::Uint32x2, 8, 2, 4, VertexFormatBaseType::Uint}, + {wgpu::VertexFormat::Uint32x3, 12, 3, 4, VertexFormatBaseType::Uint}, + {wgpu::VertexFormat::Uint32x4, 16, 4, 4, VertexFormatBaseType::Uint}, + {wgpu::VertexFormat::Sint32, 4, 1, 4, VertexFormatBaseType::Sint}, + {wgpu::VertexFormat::Sint32x2, 8, 2, 4, VertexFormatBaseType::Sint}, + {wgpu::VertexFormat::Sint32x3, 12, 3, 4, VertexFormatBaseType::Sint}, + {wgpu::VertexFormat::Sint32x4, 16, 4, 4, VertexFormatBaseType::Sint}, + // + }}; + + const VertexFormatInfo& GetVertexFormatInfo(wgpu::VertexFormat format) { + ASSERT(format != wgpu::VertexFormat::Undefined); + ASSERT(static_cast<uint32_t>(format) < sVertexFormatTable.size()); + ASSERT(sVertexFormatTable[static_cast<uint32_t>(format)].format == format); + return sVertexFormatTable[static_cast<uint32_t>(format)]; + } + +} // namespace dawn::native
diff --git a/src/dawn/native/VertexFormat.h b/src/dawn/native/VertexFormat.h new file mode 100644 index 0000000..f88ae28 --- /dev/null +++ b/src/dawn/native/VertexFormat.h
@@ -0,0 +1,40 @@ +// Copyright 2021 The Dawn Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#ifndef DAWNNATIVE_VERTEXFORMAT_H_ +#define DAWNNATIVE_VERTEXFORMAT_H_ + +#include "dawn/native/dawn_platform.h" + +namespace dawn::native { + + enum class VertexFormatBaseType { + Float, + Uint, + Sint, + }; + + struct VertexFormatInfo { + wgpu::VertexFormat format; + uint32_t byteSize; + uint32_t componentCount; + uint32_t componentByteSize; + VertexFormatBaseType baseType; + }; + + const VertexFormatInfo& GetVertexFormatInfo(wgpu::VertexFormat format); + +} // namespace dawn::native + +#endif // DAWNNATIVE_VERTEXFORMAT_H_
diff --git a/src/dawn/native/XlibXcbFunctions.cpp b/src/dawn/native/XlibXcbFunctions.cpp new file mode 100644 index 0000000..1b0f6e8 --- /dev/null +++ b/src/dawn/native/XlibXcbFunctions.cpp
@@ -0,0 +1,31 @@ +// Copyright 2021 The Dawn Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include "dawn/native/XlibXcbFunctions.h" + +namespace dawn::native { + + XlibXcbFunctions::XlibXcbFunctions() { + if (!mLib.Open("libX11-xcb.so.1") || + !mLib.GetProc(&xGetXCBConnection, "XGetXCBConnection")) { + mLib.Close(); + } + } + XlibXcbFunctions::~XlibXcbFunctions() = default; + + bool XlibXcbFunctions::IsLoaded() const { + return xGetXCBConnection != nullptr; + } + +} // namespace dawn::native
diff --git a/src/dawn/native/XlibXcbFunctions.h b/src/dawn/native/XlibXcbFunctions.h new file mode 100644 index 0000000..52998a4 --- /dev/null +++ b/src/dawn/native/XlibXcbFunctions.h
@@ -0,0 +1,46 @@ +// Copyright 2021 The Dawn Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#ifndef DAWNNATIVE_XLIBXCBFUNCTIONS_H_ +#define DAWNNATIVE_XLIBXCBFUNCTIONS_H_ + +#include "dawn/common/DynamicLib.h" +#include "dawn/native/Error.h" + +#include "dawn/common/xlib_with_undefs.h" + +class DynamicLib; + +namespace dawn::native { + + // A helper class that dynamically loads the x11-xcb library that contains XGetXCBConnection + // (and nothing else). This has to be dynamic because this libraries isn't present on all Linux + // deployment platforms that Chromium targets. + class XlibXcbFunctions { + public: + XlibXcbFunctions(); + ~XlibXcbFunctions(); + + bool IsLoaded() const; + + // Functions from x11-xcb + decltype(&::XGetXCBConnection) xGetXCBConnection = nullptr; + + private: + DynamicLib mLib; + }; + +} // namespace dawn::native + +#endif // DAWNNATIVE_XLIBXCBFUNCTIONS_H_
diff --git a/src/dawn/native/d3d12/AdapterD3D12.cpp b/src/dawn/native/d3d12/AdapterD3D12.cpp new file mode 100644 index 0000000..d31b9af --- /dev/null +++ b/src/dawn/native/d3d12/AdapterD3D12.cpp
@@ -0,0 +1,425 @@ +// Copyright 2019 The Dawn Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include "dawn/native/d3d12/AdapterD3D12.h" + +#include "dawn/common/Constants.h" +#include "dawn/common/WindowsUtils.h" +#include "dawn/native/Instance.h" +#include "dawn/native/d3d12/BackendD3D12.h" +#include "dawn/native/d3d12/D3D12Error.h" +#include "dawn/native/d3d12/DeviceD3D12.h" +#include "dawn/native/d3d12/PlatformFunctions.h" + +#include <sstream> + +namespace dawn::native::d3d12 { + + Adapter::Adapter(Backend* backend, ComPtr<IDXGIAdapter3> hardwareAdapter) + : AdapterBase(backend->GetInstance(), wgpu::BackendType::D3D12), + mHardwareAdapter(hardwareAdapter), + mBackend(backend) { + } + + Adapter::~Adapter() { + CleanUpDebugLayerFilters(); + } + + bool Adapter::SupportsExternalImages() const { + // Via dawn::native::d3d12::ExternalImageDXGI::Create + return true; + } + + const D3D12DeviceInfo& Adapter::GetDeviceInfo() const { + return mDeviceInfo; + } + + IDXGIAdapter3* Adapter::GetHardwareAdapter() const { + return mHardwareAdapter.Get(); + } + + Backend* Adapter::GetBackend() const { + return mBackend; + } + + ComPtr<ID3D12Device> Adapter::GetDevice() const { + return mD3d12Device; + } + + const gpu_info::D3DDriverVersion& Adapter::GetDriverVersion() const { + return mDriverVersion; + } + + MaybeError Adapter::InitializeImpl() { + // D3D12 cannot check for feature support without a device. + // Create the device to populate the adapter properties then reuse it when needed for actual + // rendering. + const PlatformFunctions* functions = GetBackend()->GetFunctions(); + if (FAILED(functions->d3d12CreateDevice(GetHardwareAdapter(), D3D_FEATURE_LEVEL_11_0, + _uuidof(ID3D12Device), &mD3d12Device))) { + return DAWN_INTERNAL_ERROR("D3D12CreateDevice failed"); + } + + DAWN_TRY(InitializeDebugLayerFilters()); + + DXGI_ADAPTER_DESC1 adapterDesc; + mHardwareAdapter->GetDesc1(&adapterDesc); + + mDeviceId = adapterDesc.DeviceId; + mVendorId = adapterDesc.VendorId; + mName = WCharToUTF8(adapterDesc.Description); + + DAWN_TRY_ASSIGN(mDeviceInfo, GatherDeviceInfo(*this)); + + if (adapterDesc.Flags & DXGI_ADAPTER_FLAG_SOFTWARE) { + mAdapterType = wgpu::AdapterType::CPU; + } else { + mAdapterType = (mDeviceInfo.isUMA) ? wgpu::AdapterType::IntegratedGPU + : wgpu::AdapterType::DiscreteGPU; + } + + // Convert the adapter's D3D12 driver version to a readable string like "24.21.13.9793". + LARGE_INTEGER umdVersion; + if (mHardwareAdapter->CheckInterfaceSupport(__uuidof(IDXGIDevice), &umdVersion) != + DXGI_ERROR_UNSUPPORTED) { + uint64_t encodedVersion = umdVersion.QuadPart; + + std::ostringstream o; + o << "D3D12 driver version "; + for (size_t i = 0; i < mDriverVersion.size(); ++i) { + mDriverVersion[i] = (encodedVersion >> (48 - 16 * i)) & 0xFFFF; + o << mDriverVersion[i] << "."; + } + mDriverDescription = o.str(); + } + + return {}; + } + + bool Adapter::AreTimestampQueriesSupported() const { + D3D12_COMMAND_QUEUE_DESC queueDesc = {}; + queueDesc.Flags = D3D12_COMMAND_QUEUE_FLAG_NONE; + queueDesc.Type = D3D12_COMMAND_LIST_TYPE_DIRECT; + ComPtr<ID3D12CommandQueue> d3d12CommandQueue; + HRESULT hr = mD3d12Device->CreateCommandQueue(&queueDesc, IID_PPV_ARGS(&d3d12CommandQueue)); + if (FAILED(hr)) { + return false; + } + + // GetTimestampFrequency returns an error HRESULT when there are bugs in Windows container + // and vGPU implementations. + uint64_t timeStampFrequency; + hr = d3d12CommandQueue->GetTimestampFrequency(&timeStampFrequency); + if (FAILED(hr)) { + return false; + } + + return true; + } + + MaybeError Adapter::InitializeSupportedFeaturesImpl() { + if (AreTimestampQueriesSupported()) { + mSupportedFeatures.EnableFeature(Feature::TimestampQuery); + } + mSupportedFeatures.EnableFeature(Feature::TextureCompressionBC); + mSupportedFeatures.EnableFeature(Feature::PipelineStatisticsQuery); + mSupportedFeatures.EnableFeature(Feature::MultiPlanarFormats); + mSupportedFeatures.EnableFeature(Feature::Depth24UnormStencil8); + mSupportedFeatures.EnableFeature(Feature::Depth32FloatStencil8); + + return {}; + } + + MaybeError Adapter::InitializeSupportedLimitsImpl(CombinedLimits* limits) { + D3D12_FEATURE_DATA_D3D12_OPTIONS featureData = {}; + + DAWN_TRY(CheckHRESULT(mD3d12Device->CheckFeatureSupport(D3D12_FEATURE_D3D12_OPTIONS, + &featureData, sizeof(featureData)), + "CheckFeatureSupport D3D12_FEATURE_D3D12_OPTIONS")); + + // Check if the device is at least D3D_FEATURE_LEVEL_11_1 or D3D_FEATURE_LEVEL_11_0 + const D3D_FEATURE_LEVEL levelsToQuery[]{D3D_FEATURE_LEVEL_11_1, D3D_FEATURE_LEVEL_11_0}; + + D3D12_FEATURE_DATA_FEATURE_LEVELS featureLevels; + featureLevels.NumFeatureLevels = sizeof(levelsToQuery) / sizeof(D3D_FEATURE_LEVEL); + featureLevels.pFeatureLevelsRequested = levelsToQuery; + DAWN_TRY( + CheckHRESULT(mD3d12Device->CheckFeatureSupport(D3D12_FEATURE_FEATURE_LEVELS, + &featureLevels, sizeof(featureLevels)), + "CheckFeatureSupport D3D12_FEATURE_FEATURE_LEVELS")); + + if (featureLevels.MaxSupportedFeatureLevel == D3D_FEATURE_LEVEL_11_0 && + featureData.ResourceBindingTier < D3D12_RESOURCE_BINDING_TIER_2) { + return DAWN_VALIDATION_ERROR( + "At least Resource Binding Tier 2 is required for D3D12 Feature Level 11.0 " + "devices."); + } + + GetDefaultLimits(&limits->v1); + + // https://docs.microsoft.com/en-us/windows/win32/direct3d12/hardware-feature-levels + + // Limits that are the same across D3D feature levels + limits->v1.maxTextureDimension1D = D3D12_REQ_TEXTURE1D_U_DIMENSION; + limits->v1.maxTextureDimension2D = D3D12_REQ_TEXTURE2D_U_OR_V_DIMENSION; + limits->v1.maxTextureDimension3D = D3D12_REQ_TEXTURE3D_U_V_OR_W_DIMENSION; + limits->v1.maxTextureArrayLayers = D3D12_REQ_TEXTURE2D_ARRAY_AXIS_DIMENSION; + // Slot values can be 0-15, inclusive: + // https://docs.microsoft.com/en-ca/windows/win32/api/d3d12/ns-d3d12-d3d12_input_element_desc + limits->v1.maxVertexBuffers = 16; + limits->v1.maxVertexAttributes = D3D12_IA_VERTEX_INPUT_RESOURCE_SLOT_COUNT; + + // Note: WebGPU requires FL11.1+ + // https://docs.microsoft.com/en-us/windows/win32/direct3d12/hardware-support + // Resource Binding Tier: 1 2 3 + + // Max(CBV+UAV+SRV) 1M 1M 1M+ + // Max CBV per stage 14 14 full + // Max SRV per stage 128 full full + // Max UAV in all stages 64 64 full + // Max Samplers per stage 16 2048 2048 + + // https://docs.microsoft.com/en-us/windows-hardware/test/hlk/testref/efad06e8-51d1-40ce-ad5c-573a134b4bb6 + // "full" means the full heap can be used. This is tested + // to work for 1 million descriptors, and 1.1M for tier 3. + uint32_t maxCBVsPerStage; + uint32_t maxSRVsPerStage; + uint32_t maxUAVsAllStages; + uint32_t maxSamplersPerStage; + switch (featureData.ResourceBindingTier) { + case D3D12_RESOURCE_BINDING_TIER_1: + maxCBVsPerStage = 14; + maxSRVsPerStage = 128; + maxUAVsAllStages = 64; + maxSamplersPerStage = 16; + break; + case D3D12_RESOURCE_BINDING_TIER_2: + maxCBVsPerStage = 14; + maxSRVsPerStage = 1'000'000; + maxUAVsAllStages = 64; + maxSamplersPerStage = 2048; + break; + case D3D12_RESOURCE_BINDING_TIER_3: + default: + maxCBVsPerStage = 1'100'000; + maxSRVsPerStage = 1'100'000; + maxUAVsAllStages = 1'100'000; + maxSamplersPerStage = 2048; + break; + } + + ASSERT(maxUAVsAllStages / 4 > limits->v1.maxStorageTexturesPerShaderStage); + ASSERT(maxUAVsAllStages / 4 > limits->v1.maxStorageBuffersPerShaderStage); + uint32_t maxUAVsPerStage = maxUAVsAllStages / 2; + + limits->v1.maxUniformBuffersPerShaderStage = maxCBVsPerStage; + // Allocate half of the UAVs to storage buffers, and half to storage textures. + limits->v1.maxStorageTexturesPerShaderStage = maxUAVsPerStage / 2; + limits->v1.maxStorageBuffersPerShaderStage = maxUAVsPerStage - maxUAVsPerStage / 2; + limits->v1.maxSampledTexturesPerShaderStage = maxSRVsPerStage; + limits->v1.maxSamplersPerShaderStage = maxSamplersPerStage; + + // https://docs.microsoft.com/en-us/windows/win32/direct3d12/root-signature-limits + // In DWORDS. Descriptor tables cost 1, Root constants cost 1, Root descriptors cost 2. + static constexpr uint32_t kMaxRootSignatureSize = 64u; + // Dawn maps WebGPU's binding model by: + // - (maxBindGroups) + // CBVs/UAVs/SRVs for bind group are a root descriptor table + // - (maxBindGroups) + // Samplers for each bind group are a root descriptor table + // - (2 * maxDynamicBuffers) + // Each dynamic buffer is a root descriptor + // RESERVED: + // - 3 = max of: + // - 2 root constants for the baseVertex/baseInstance constants. + // - 3 root constants for num workgroups X, Y, Z + // - 4 root constants (kMaxDynamicStorageBuffersPerPipelineLayout) for dynamic storage + // buffer lengths. + static constexpr uint32_t kReservedSlots = 7; + + // Available slots after base limits considered. + uint32_t availableRootSignatureSlots = + kMaxRootSignatureSize - kReservedSlots - + 2 * (limits->v1.maxBindGroups + limits->v1.maxDynamicUniformBuffersPerPipelineLayout + + limits->v1.maxDynamicStorageBuffersPerPipelineLayout); + + // Because we need either: + // - 1 cbv/uav/srv table + 1 sampler table + // - 2 slots for a root descriptor + uint32_t availableDynamicBufferOrBindGroup = availableRootSignatureSlots / 2; + + // We can either have a bind group, a dyn uniform buffer or a dyn storage buffer. + // Distribute evenly. + limits->v1.maxBindGroups += availableDynamicBufferOrBindGroup / 3; + limits->v1.maxDynamicUniformBuffersPerPipelineLayout += + availableDynamicBufferOrBindGroup / 3; + limits->v1.maxDynamicStorageBuffersPerPipelineLayout += + (availableDynamicBufferOrBindGroup - 2 * (availableDynamicBufferOrBindGroup / 3)); + + ASSERT(2 * (limits->v1.maxBindGroups + + limits->v1.maxDynamicUniformBuffersPerPipelineLayout + + limits->v1.maxDynamicStorageBuffersPerPipelineLayout) <= + kMaxRootSignatureSize - kReservedSlots); + + // https://docs.microsoft.com/en-us/windows/win32/direct3dhlsl/sm5-attributes-numthreads + limits->v1.maxComputeWorkgroupSizeX = D3D12_CS_THREAD_GROUP_MAX_X; + limits->v1.maxComputeWorkgroupSizeY = D3D12_CS_THREAD_GROUP_MAX_Y; + limits->v1.maxComputeWorkgroupSizeZ = D3D12_CS_THREAD_GROUP_MAX_Z; + limits->v1.maxComputeInvocationsPerWorkgroup = D3D12_CS_THREAD_GROUP_MAX_THREADS_PER_GROUP; + + // https://docs.maxComputeWorkgroupSizeXmicrosoft.com/en-us/windows/win32/api/d3d12/ns-d3d12-d3d12_dispatch_arguments + limits->v1.maxComputeWorkgroupsPerDimension = + D3D12_CS_DISPATCH_MAX_THREAD_GROUPS_PER_DIMENSION; + + // https://docs.microsoft.com/en-us/windows/win32/direct3d11/overviews-direct3d-11-devices-downlevel-compute-shaders + // Thread Group Shared Memory is limited to 16Kb on downlevel hardware. This is less than + // the 32Kb that is available to Direct3D 11 hardware. D3D12 is also 32kb. + limits->v1.maxComputeWorkgroupStorageSize = 32768; + + // Max number of "constants" where each constant is a 16-byte float4 + limits->v1.maxUniformBufferBindingSize = D3D12_REQ_CONSTANT_BUFFER_ELEMENT_COUNT * 16; + // D3D12 has no documented limit on the size of a storage buffer binding. + limits->v1.maxStorageBufferBindingSize = 4294967295; + + // TODO(crbug.com/dawn/685): + // LIMITS NOT SET: + // - maxInterStageShaderComponents + // - maxVertexBufferArrayStride + + return {}; + } + + MaybeError Adapter::InitializeDebugLayerFilters() { + if (!GetInstance()->IsBackendValidationEnabled()) { + return {}; + } + + D3D12_MESSAGE_ID denyIds[] = { + + // + // Permanent IDs: list of warnings that are not applicable + // + + // Resource sub-allocation partially maps pre-allocated heaps. This means the + // entire physical addresses space may have no resources or have many resources + // assigned the same heap. + D3D12_MESSAGE_ID_HEAP_ADDRESS_RANGE_HAS_NO_RESOURCE, + D3D12_MESSAGE_ID_HEAP_ADDRESS_RANGE_INTERSECTS_MULTIPLE_BUFFERS, + + // The debug layer validates pipeline objects when they are created. Dawn validates + // them when them when they are set. Therefore, since the issue is caught at a later + // time, we can silence this warnings. + D3D12_MESSAGE_ID_CREATEGRAPHICSPIPELINESTATE_RENDERTARGETVIEW_NOT_SET, + + // Adding a clear color during resource creation would require heuristics or delayed + // creation. + // https://crbug.com/dawn/418 + D3D12_MESSAGE_ID_CLEARRENDERTARGETVIEW_MISMATCHINGCLEARVALUE, + D3D12_MESSAGE_ID_CLEARDEPTHSTENCILVIEW_MISMATCHINGCLEARVALUE, + + // Dawn enforces proper Unmaps at a later time. + // https://crbug.com/dawn/422 + D3D12_MESSAGE_ID_EXECUTECOMMANDLISTS_GPU_WRITTEN_READBACK_RESOURCE_MAPPED, + + // WebGPU allows empty scissors without empty viewports. + D3D12_MESSAGE_ID_DRAW_EMPTY_SCISSOR_RECTANGLE, + + // + // Temporary IDs: list of warnings that should be fixed or promoted + // + + // Remove after warning have been addressed + // https://crbug.com/dawn/421 + D3D12_MESSAGE_ID_GPU_BASED_VALIDATION_INCOMPATIBLE_RESOURCE_STATE, + + // For small placed resource alignment, we first request the small alignment, which may + // get rejected and generate a debug error. Then, we request 0 to get the allowed + // allowed alignment. + D3D12_MESSAGE_ID_CREATERESOURCE_INVALIDALIGNMENT, + + // WebGPU allows OOB vertex buffer access and relies on D3D12's robust buffer access + // behavior. + D3D12_MESSAGE_ID_COMMAND_LIST_DRAW_VERTEX_BUFFER_TOO_SMALL, + + // WebGPU allows setVertexBuffer with offset that equals to the whole vertex buffer + // size. + // Even this means that no vertex buffer view has been set in D3D12 backend. + // https://crbug.com/dawn/1255 + D3D12_MESSAGE_ID_COMMAND_LIST_DRAW_VERTEX_BUFFER_NOT_SET, + }; + + // Create a retrieval filter with a deny list to suppress messages. + // Any messages remaining will be converted to Dawn errors. + D3D12_INFO_QUEUE_FILTER filter{}; + // Filter out info/message and only create errors from warnings or worse. + D3D12_MESSAGE_SEVERITY severities[] = { + D3D12_MESSAGE_SEVERITY_INFO, + D3D12_MESSAGE_SEVERITY_MESSAGE, + }; + filter.DenyList.NumSeverities = ARRAYSIZE(severities); + filter.DenyList.pSeverityList = severities; + filter.DenyList.NumIDs = ARRAYSIZE(denyIds); + filter.DenyList.pIDList = denyIds; + + ComPtr<ID3D12InfoQueue> infoQueue; + DAWN_TRY(CheckHRESULT(mD3d12Device.As(&infoQueue), + "D3D12 QueryInterface ID3D12Device to ID3D12InfoQueue")); + + // To avoid flooding the console, a storage-filter is also used to + // prevent messages from getting logged. + DAWN_TRY(CheckHRESULT(infoQueue->PushStorageFilter(&filter), + "ID3D12InfoQueue::PushStorageFilter")); + + DAWN_TRY(CheckHRESULT(infoQueue->PushRetrievalFilter(&filter), + "ID3D12InfoQueue::PushRetrievalFilter")); + + return {}; + } + + void Adapter::CleanUpDebugLayerFilters() { + if (!GetInstance()->IsBackendValidationEnabled()) { + return; + } + + // The device may not exist if this adapter failed to initialize. + if (mD3d12Device == nullptr) { + return; + } + + // If the debug layer is not installed, return immediately to avoid crashing the process. + ComPtr<ID3D12InfoQueue> infoQueue; + if (FAILED(mD3d12Device.As(&infoQueue))) { + return; + } + + infoQueue->PopRetrievalFilter(); + infoQueue->PopStorageFilter(); + } + + ResultOrError<Ref<DeviceBase>> Adapter::CreateDeviceImpl(const DeviceDescriptor* descriptor) { + return Device::Create(this, descriptor); + } + + // Resets the backend device and creates a new one. If any D3D12 objects belonging to the + // current ID3D12Device have not been destroyed, a non-zero value will be returned upon Reset() + // and the subequent call to CreateDevice will return a handle the existing device instead of + // creating a new one. + MaybeError Adapter::ResetInternalDeviceForTestingImpl() { + ASSERT(mD3d12Device.Reset() == 0); + DAWN_TRY(Initialize()); + + return {}; + } + +} // namespace dawn::native::d3d12
diff --git a/src/dawn/native/d3d12/AdapterD3D12.h b/src/dawn/native/d3d12/AdapterD3D12.h new file mode 100644 index 0000000..3247a13 --- /dev/null +++ b/src/dawn/native/d3d12/AdapterD3D12.h
@@ -0,0 +1,66 @@ +// Copyright 2019 The Dawn Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#ifndef DAWNNATIVE_D3D12_ADAPTERD3D12_H_ +#define DAWNNATIVE_D3D12_ADAPTERD3D12_H_ + +#include "dawn/native/Adapter.h" + +#include "dawn/common/GPUInfo.h" +#include "dawn/native/d3d12/D3D12Info.h" +#include "dawn/native/d3d12/d3d12_platform.h" + +namespace dawn::native::d3d12 { + + class Backend; + + class Adapter : public AdapterBase { + public: + Adapter(Backend* backend, ComPtr<IDXGIAdapter3> hardwareAdapter); + ~Adapter() override; + + // AdapterBase Implementation + bool SupportsExternalImages() const override; + + const D3D12DeviceInfo& GetDeviceInfo() const; + IDXGIAdapter3* GetHardwareAdapter() const; + Backend* GetBackend() const; + ComPtr<ID3D12Device> GetDevice() const; + const gpu_info::D3DDriverVersion& GetDriverVersion() const; + + private: + ResultOrError<Ref<DeviceBase>> CreateDeviceImpl( + const DeviceDescriptor* descriptor) override; + MaybeError ResetInternalDeviceForTestingImpl() override; + + bool AreTimestampQueriesSupported() const; + + MaybeError InitializeImpl() override; + MaybeError InitializeSupportedFeaturesImpl() override; + MaybeError InitializeSupportedLimitsImpl(CombinedLimits* limits) override; + + MaybeError InitializeDebugLayerFilters(); + void CleanUpDebugLayerFilters(); + + ComPtr<IDXGIAdapter3> mHardwareAdapter; + ComPtr<ID3D12Device> mD3d12Device; + gpu_info::D3DDriverVersion mDriverVersion; + + Backend* mBackend; + D3D12DeviceInfo mDeviceInfo = {}; + }; + +} // namespace dawn::native::d3d12 + +#endif // DAWNNATIVE_D3D12_ADAPTERD3D12_H_
diff --git a/src/dawn/native/d3d12/BackendD3D12.cpp b/src/dawn/native/d3d12/BackendD3D12.cpp new file mode 100644 index 0000000..27a9882 --- /dev/null +++ b/src/dawn/native/d3d12/BackendD3D12.cpp
@@ -0,0 +1,209 @@ +// Copyright 2019 The Dawn Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include "dawn/native/d3d12/BackendD3D12.h" + +#include "dawn/native/D3D12Backend.h" +#include "dawn/native/Instance.h" +#include "dawn/native/d3d12/AdapterD3D12.h" +#include "dawn/native/d3d12/D3D12Error.h" +#include "dawn/native/d3d12/PlatformFunctions.h" + +namespace dawn::native::d3d12 { + + namespace { + + ResultOrError<ComPtr<IDXGIFactory4>> CreateFactory(const PlatformFunctions* functions, + BackendValidationLevel validationLevel, + bool beginCaptureOnStartup) { + ComPtr<IDXGIFactory4> factory; + + uint32_t dxgiFactoryFlags = 0; + + // Enable the debug layer (requires the Graphics Tools "optional feature"). + { + if (validationLevel != BackendValidationLevel::Disabled) { + ComPtr<ID3D12Debug3> debugController; + if (SUCCEEDED( + functions->d3d12GetDebugInterface(IID_PPV_ARGS(&debugController)))) { + ASSERT(debugController != nullptr); + debugController->EnableDebugLayer(); + if (validationLevel == BackendValidationLevel::Full) { + debugController->SetEnableGPUBasedValidation(true); + } + + // Enable additional debug layers. + dxgiFactoryFlags |= DXGI_CREATE_FACTORY_DEBUG; + } + } + + if (beginCaptureOnStartup) { + ComPtr<IDXGraphicsAnalysis> graphicsAnalysis; + if (functions->dxgiGetDebugInterface1 != nullptr && + SUCCEEDED(functions->dxgiGetDebugInterface1( + 0, IID_PPV_ARGS(&graphicsAnalysis)))) { + graphicsAnalysis->BeginCapture(); + } + } + } + + if (FAILED(functions->createDxgiFactory2(dxgiFactoryFlags, IID_PPV_ARGS(&factory)))) { + return DAWN_INTERNAL_ERROR("Failed to create a DXGI factory"); + } + + ASSERT(factory != nullptr); + return std::move(factory); + } + + ResultOrError<Ref<AdapterBase>> CreateAdapterFromIDXGIAdapter( + Backend* backend, + ComPtr<IDXGIAdapter> dxgiAdapter) { + ComPtr<IDXGIAdapter3> dxgiAdapter3; + DAWN_TRY(CheckHRESULT(dxgiAdapter.As(&dxgiAdapter3), "DXGIAdapter retrieval")); + Ref<Adapter> adapter = AcquireRef(new Adapter(backend, std::move(dxgiAdapter3))); + DAWN_TRY(adapter->Initialize()); + + return {std::move(adapter)}; + } + + } // anonymous namespace + + Backend::Backend(InstanceBase* instance) + : BackendConnection(instance, wgpu::BackendType::D3D12) { + } + + MaybeError Backend::Initialize() { + mFunctions = std::make_unique<PlatformFunctions>(); + DAWN_TRY(mFunctions->LoadFunctions()); + + const auto instance = GetInstance(); + + DAWN_TRY_ASSIGN(mFactory, + CreateFactory(mFunctions.get(), instance->GetBackendValidationLevel(), + instance->IsBeginCaptureOnStartupEnabled())); + + return {}; + } + + ComPtr<IDXGIFactory4> Backend::GetFactory() const { + return mFactory; + } + + MaybeError Backend::EnsureDxcLibrary() { + if (mDxcLibrary == nullptr) { + DAWN_TRY(CheckHRESULT( + mFunctions->dxcCreateInstance(CLSID_DxcLibrary, IID_PPV_ARGS(&mDxcLibrary)), + "DXC create library")); + ASSERT(mDxcLibrary != nullptr); + } + return {}; + } + + MaybeError Backend::EnsureDxcCompiler() { + if (mDxcCompiler == nullptr) { + DAWN_TRY(CheckHRESULT( + mFunctions->dxcCreateInstance(CLSID_DxcCompiler, IID_PPV_ARGS(&mDxcCompiler)), + "DXC create compiler")); + ASSERT(mDxcCompiler != nullptr); + } + return {}; + } + + MaybeError Backend::EnsureDxcValidator() { + if (mDxcValidator == nullptr) { + DAWN_TRY(CheckHRESULT( + mFunctions->dxcCreateInstance(CLSID_DxcValidator, IID_PPV_ARGS(&mDxcValidator)), + "DXC create validator")); + ASSERT(mDxcValidator != nullptr); + } + return {}; + } + + ComPtr<IDxcLibrary> Backend::GetDxcLibrary() const { + ASSERT(mDxcLibrary != nullptr); + return mDxcLibrary; + } + + ComPtr<IDxcCompiler> Backend::GetDxcCompiler() const { + ASSERT(mDxcCompiler != nullptr); + return mDxcCompiler; + } + + ComPtr<IDxcValidator> Backend::GetDxcValidator() const { + ASSERT(mDxcValidator != nullptr); + return mDxcValidator; + } + + const PlatformFunctions* Backend::GetFunctions() const { + return mFunctions.get(); + } + + std::vector<Ref<AdapterBase>> Backend::DiscoverDefaultAdapters() { + AdapterDiscoveryOptions options; + auto result = DiscoverAdapters(&options); + if (result.IsError()) { + GetInstance()->ConsumedError(result.AcquireError()); + return {}; + } + return result.AcquireSuccess(); + } + + ResultOrError<std::vector<Ref<AdapterBase>>> Backend::DiscoverAdapters( + const AdapterDiscoveryOptionsBase* optionsBase) { + ASSERT(optionsBase->backendType == WGPUBackendType_D3D12); + const AdapterDiscoveryOptions* options = + static_cast<const AdapterDiscoveryOptions*>(optionsBase); + + std::vector<Ref<AdapterBase>> adapters; + if (options->dxgiAdapter != nullptr) { + // |dxgiAdapter| was provided. Discover just that adapter. + Ref<AdapterBase> adapter; + DAWN_TRY_ASSIGN(adapter, CreateAdapterFromIDXGIAdapter(this, options->dxgiAdapter)); + adapters.push_back(std::move(adapter)); + return std::move(adapters); + } + + // Enumerate and discover all available adapters. + for (uint32_t adapterIndex = 0;; ++adapterIndex) { + ComPtr<IDXGIAdapter1> dxgiAdapter = nullptr; + if (mFactory->EnumAdapters1(adapterIndex, &dxgiAdapter) == DXGI_ERROR_NOT_FOUND) { + break; // No more adapters to enumerate. + } + + ASSERT(dxgiAdapter != nullptr); + ResultOrError<Ref<AdapterBase>> adapter = + CreateAdapterFromIDXGIAdapter(this, dxgiAdapter); + if (adapter.IsError()) { + GetInstance()->ConsumedError(adapter.AcquireError()); + continue; + } + + adapters.push_back(adapter.AcquireSuccess()); + } + + return adapters; + } + + BackendConnection* Connect(InstanceBase* instance) { + Backend* backend = new Backend(instance); + + if (instance->ConsumedError(backend->Initialize())) { + delete backend; + return nullptr; + } + + return backend; + } + +} // namespace dawn::native::d3d12
diff --git a/src/dawn/native/d3d12/BackendD3D12.h b/src/dawn/native/d3d12/BackendD3D12.h new file mode 100644 index 0000000..01ae6bc --- /dev/null +++ b/src/dawn/native/d3d12/BackendD3D12.h
@@ -0,0 +1,59 @@ +// Copyright 2019 The Dawn Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#ifndef DAWNNATIVE_D3D12_BACKENDD3D12_H_ +#define DAWNNATIVE_D3D12_BACKENDD3D12_H_ + +#include "dawn/native/BackendConnection.h" + +#include "dawn/native/d3d12/d3d12_platform.h" + +namespace dawn::native::d3d12 { + + class PlatformFunctions; + + class Backend : public BackendConnection { + public: + Backend(InstanceBase* instance); + + MaybeError Initialize(); + + ComPtr<IDXGIFactory4> GetFactory() const; + + MaybeError EnsureDxcLibrary(); + MaybeError EnsureDxcCompiler(); + MaybeError EnsureDxcValidator(); + ComPtr<IDxcLibrary> GetDxcLibrary() const; + ComPtr<IDxcCompiler> GetDxcCompiler() const; + ComPtr<IDxcValidator> GetDxcValidator() const; + + const PlatformFunctions* GetFunctions() const; + + std::vector<Ref<AdapterBase>> DiscoverDefaultAdapters() override; + ResultOrError<std::vector<Ref<AdapterBase>>> DiscoverAdapters( + const AdapterDiscoveryOptionsBase* optionsBase) override; + + private: + // Keep mFunctions as the first member so that in the destructor it is freed last. Otherwise + // the D3D12 DLLs are unloaded before we are done using them. + std::unique_ptr<PlatformFunctions> mFunctions; + ComPtr<IDXGIFactory4> mFactory; + ComPtr<IDxcLibrary> mDxcLibrary; + ComPtr<IDxcCompiler> mDxcCompiler; + ComPtr<IDxcValidator> mDxcValidator; + }; + +} // namespace dawn::native::d3d12 + +#endif // DAWNNATIVE_D3D12_BACKENDD3D12_H_
diff --git a/src/dawn/native/d3d12/BindGroupD3D12.cpp b/src/dawn/native/d3d12/BindGroupD3D12.cpp new file mode 100644 index 0000000..f169345 --- /dev/null +++ b/src/dawn/native/d3d12/BindGroupD3D12.cpp
@@ -0,0 +1,268 @@ +// Copyright 2017 The Dawn Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include "dawn/native/d3d12/BindGroupD3D12.h" + +#include "dawn/common/BitSetIterator.h" +#include "dawn/native/ExternalTexture.h" +#include "dawn/native/d3d12/BindGroupLayoutD3D12.h" +#include "dawn/native/d3d12/BufferD3D12.h" +#include "dawn/native/d3d12/DeviceD3D12.h" +#include "dawn/native/d3d12/SamplerHeapCacheD3D12.h" +#include "dawn/native/d3d12/ShaderVisibleDescriptorAllocatorD3D12.h" +#include "dawn/native/d3d12/TextureD3D12.h" + +namespace dawn::native::d3d12 { + + // static + ResultOrError<Ref<BindGroup>> BindGroup::Create(Device* device, + const BindGroupDescriptor* descriptor) { + return ToBackend(descriptor->layout)->AllocateBindGroup(device, descriptor); + } + + BindGroup::BindGroup(Device* device, + const BindGroupDescriptor* descriptor, + uint32_t viewSizeIncrement, + const CPUDescriptorHeapAllocation& viewAllocation) + : BindGroupBase(this, device, descriptor) { + BindGroupLayout* bgl = ToBackend(GetLayout()); + + mCPUViewAllocation = viewAllocation; + + const auto& descriptorHeapOffsets = bgl->GetDescriptorHeapOffsets(); + + ID3D12Device* d3d12Device = device->GetD3D12Device(); + + // It's not necessary to create descriptors in the descriptor heap for dynamic resources. + // This is because they are created as root descriptors which are never heap allocated. + // Since dynamic buffers are packed in the front, we can skip over these bindings by + // starting from the dynamic buffer count. + for (BindingIndex bindingIndex = bgl->GetDynamicBufferCount(); + bindingIndex < bgl->GetBindingCount(); ++bindingIndex) { + const BindingInfo& bindingInfo = bgl->GetBindingInfo(bindingIndex); + + // Increment size does not need to be stored and is only used to get a handle + // local to the allocation with OffsetFrom(). + switch (bindingInfo.bindingType) { + case BindingInfoType::Buffer: { + BufferBinding binding = GetBindingAsBufferBinding(bindingIndex); + + ID3D12Resource* resource = ToBackend(binding.buffer)->GetD3D12Resource(); + if (resource == nullptr) { + // The Buffer was destroyed. Skip creating buffer views since there is no + // resource. This bind group won't be used as it is an error to submit a + // command buffer that references destroyed resources. + continue; + } + + switch (bindingInfo.buffer.type) { + case wgpu::BufferBindingType::Uniform: { + D3D12_CONSTANT_BUFFER_VIEW_DESC desc; + desc.SizeInBytes = + Align(binding.size, D3D12_CONSTANT_BUFFER_DATA_PLACEMENT_ALIGNMENT); + desc.BufferLocation = + ToBackend(binding.buffer)->GetVA() + binding.offset; + + d3d12Device->CreateConstantBufferView( + &desc, viewAllocation.OffsetFrom( + viewSizeIncrement, descriptorHeapOffsets[bindingIndex])); + break; + } + case wgpu::BufferBindingType::Storage: + case kInternalStorageBufferBinding: { + // Since Tint outputs HLSL shaders with RWByteAddressBuffer, + // we must use D3D12_BUFFER_UAV_FLAG_RAW when making the + // UNORDERED_ACCESS_VIEW_DESC. Using D3D12_BUFFER_UAV_FLAG_RAW requires + // that we use DXGI_FORMAT_R32_TYPELESS as the format of the view. + // DXGI_FORMAT_R32_TYPELESS requires that the element size be 4 + // byte aligned. Since binding.size and binding.offset are in bytes, + // we need to divide by 4 to obtain the element size. + D3D12_UNORDERED_ACCESS_VIEW_DESC desc; + desc.Buffer.NumElements = binding.size / 4; + desc.Format = DXGI_FORMAT_R32_TYPELESS; + desc.ViewDimension = D3D12_UAV_DIMENSION_BUFFER; + desc.Buffer.FirstElement = binding.offset / 4; + desc.Buffer.StructureByteStride = 0; + desc.Buffer.CounterOffsetInBytes = 0; + desc.Buffer.Flags = D3D12_BUFFER_UAV_FLAG_RAW; + + d3d12Device->CreateUnorderedAccessView( + resource, nullptr, &desc, + viewAllocation.OffsetFrom(viewSizeIncrement, + descriptorHeapOffsets[bindingIndex])); + break; + } + case wgpu::BufferBindingType::ReadOnlyStorage: { + // Like StorageBuffer, Tint outputs HLSL shaders for readonly + // storage buffer with ByteAddressBuffer. So we must use + // D3D12_BUFFER_SRV_FLAG_RAW when making the SRV descriptor. And it has + // similar requirement for format, element size, etc. + D3D12_SHADER_RESOURCE_VIEW_DESC desc; + desc.Format = DXGI_FORMAT_R32_TYPELESS; + desc.ViewDimension = D3D12_SRV_DIMENSION_BUFFER; + desc.Shader4ComponentMapping = D3D12_DEFAULT_SHADER_4_COMPONENT_MAPPING; + desc.Buffer.FirstElement = binding.offset / 4; + desc.Buffer.NumElements = binding.size / 4; + desc.Buffer.StructureByteStride = 0; + desc.Buffer.Flags = D3D12_BUFFER_SRV_FLAG_RAW; + d3d12Device->CreateShaderResourceView( + resource, &desc, + viewAllocation.OffsetFrom(viewSizeIncrement, + descriptorHeapOffsets[bindingIndex])); + break; + } + case wgpu::BufferBindingType::Undefined: + UNREACHABLE(); + } + + break; + } + + case BindingInfoType::Texture: { + auto* view = ToBackend(GetBindingAsTextureView(bindingIndex)); + auto& srv = view->GetSRVDescriptor(); + + ID3D12Resource* resource = ToBackend(view->GetTexture())->GetD3D12Resource(); + if (resource == nullptr) { + // The Texture was destroyed. Skip creating the SRV since there is no + // resource. This bind group won't be used as it is an error to submit a + // command buffer that references destroyed resources. + continue; + } + + d3d12Device->CreateShaderResourceView( + resource, &srv, + viewAllocation.OffsetFrom(viewSizeIncrement, + descriptorHeapOffsets[bindingIndex])); + break; + } + + case BindingInfoType::StorageTexture: { + TextureView* view = ToBackend(GetBindingAsTextureView(bindingIndex)); + + ID3D12Resource* resource = ToBackend(view->GetTexture())->GetD3D12Resource(); + if (resource == nullptr) { + // The Texture was destroyed. Skip creating the SRV/UAV since there is no + // resource. This bind group won't be used as it is an error to submit a + // command buffer that references destroyed resources. + continue; + } + + switch (bindingInfo.storageTexture.access) { + case wgpu::StorageTextureAccess::WriteOnly: { + D3D12_UNORDERED_ACCESS_VIEW_DESC uav = view->GetUAVDescriptor(); + d3d12Device->CreateUnorderedAccessView( + resource, nullptr, &uav, + viewAllocation.OffsetFrom(viewSizeIncrement, + descriptorHeapOffsets[bindingIndex])); + break; + } + + case wgpu::StorageTextureAccess::Undefined: + UNREACHABLE(); + } + + break; + } + + case BindingInfoType::ExternalTexture: { + UNREACHABLE(); + } + + case BindingInfoType::Sampler: { + // No-op as samplers will be later initialized by CreateSamplers(). + break; + } + } + } + + // Loop through the dynamic storage buffers and build a flat map from the index of the + // dynamic storage buffer to its binding size. The index |dynamicStorageBufferIndex| + // means that it is the i'th buffer that is both dynamic and storage, in increasing order + // of BindingNumber. + mDynamicStorageBufferLengths.resize(bgl->GetBindingCountInfo().dynamicStorageBufferCount); + uint32_t dynamicStorageBufferIndex = 0; + for (BindingIndex bindingIndex(0); bindingIndex < bgl->GetDynamicBufferCount(); + ++bindingIndex) { + if (bgl->IsStorageBufferBinding(bindingIndex)) { + mDynamicStorageBufferLengths[dynamicStorageBufferIndex++] = + GetBindingAsBufferBinding(bindingIndex).size; + } + } + } + + BindGroup::~BindGroup() = default; + + void BindGroup::DestroyImpl() { + BindGroupBase::DestroyImpl(); + ToBackend(GetLayout())->DeallocateBindGroup(this, &mCPUViewAllocation); + ASSERT(!mCPUViewAllocation.IsValid()); + } + + bool BindGroup::PopulateViews(ShaderVisibleDescriptorAllocator* viewAllocator) { + const BindGroupLayout* bgl = ToBackend(GetLayout()); + + const uint32_t descriptorCount = bgl->GetCbvUavSrvDescriptorCount(); + if (descriptorCount == 0 || viewAllocator->IsAllocationStillValid(mGPUViewAllocation)) { + return true; + } + + // Attempt to allocate descriptors for the currently bound shader-visible heaps. + // If either failed, return early to re-allocate and switch the heaps. + Device* device = ToBackend(GetDevice()); + + D3D12_CPU_DESCRIPTOR_HANDLE baseCPUDescriptor; + if (!viewAllocator->AllocateGPUDescriptors(descriptorCount, + device->GetPendingCommandSerial(), + &baseCPUDescriptor, &mGPUViewAllocation)) { + return false; + } + + // CPU bindgroups are sparsely allocated across CPU heaps. Instead of doing + // simple copies per bindgroup, a single non-simple copy could be issued. + // TODO(dawn:155): Consider doing this optimization. + device->GetD3D12Device()->CopyDescriptorsSimple(descriptorCount, baseCPUDescriptor, + mCPUViewAllocation.GetBaseDescriptor(), + D3D12_DESCRIPTOR_HEAP_TYPE_CBV_SRV_UAV); + + return true; + } + + D3D12_GPU_DESCRIPTOR_HANDLE BindGroup::GetBaseViewDescriptor() const { + return mGPUViewAllocation.GetBaseDescriptor(); + } + + D3D12_GPU_DESCRIPTOR_HANDLE BindGroup::GetBaseSamplerDescriptor() const { + ASSERT(mSamplerAllocationEntry != nullptr); + return mSamplerAllocationEntry->GetBaseDescriptor(); + } + + bool BindGroup::PopulateSamplers(Device* device, + ShaderVisibleDescriptorAllocator* samplerAllocator) { + if (mSamplerAllocationEntry == nullptr) { + return true; + } + return mSamplerAllocationEntry->Populate(device, samplerAllocator); + } + + void BindGroup::SetSamplerAllocationEntry(Ref<SamplerHeapCacheEntry> entry) { + mSamplerAllocationEntry = std::move(entry); + } + + const BindGroup::DynamicStorageBufferLengths& BindGroup::GetDynamicStorageBufferLengths() + const { + return mDynamicStorageBufferLengths; + } + +} // namespace dawn::native::d3d12
diff --git a/src/dawn/native/d3d12/BindGroupD3D12.h b/src/dawn/native/d3d12/BindGroupD3D12.h new file mode 100644 index 0000000..7fcf782 --- /dev/null +++ b/src/dawn/native/d3d12/BindGroupD3D12.h
@@ -0,0 +1,68 @@ +// Copyright 2017 The Dawn Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#ifndef DAWNNATIVE_D3D12_BINDGROUPD3D12_H_ +#define DAWNNATIVE_D3D12_BINDGROUPD3D12_H_ + +#include "dawn/common/PlacementAllocated.h" +#include "dawn/common/ityp_span.h" +#include "dawn/common/ityp_stack_vec.h" +#include "dawn/native/BindGroup.h" +#include "dawn/native/d3d12/CPUDescriptorHeapAllocationD3D12.h" +#include "dawn/native/d3d12/GPUDescriptorHeapAllocationD3D12.h" + +namespace dawn::native::d3d12 { + + class Device; + class SamplerHeapCacheEntry; + class ShaderVisibleDescriptorAllocator; + + class BindGroup final : public BindGroupBase, public PlacementAllocated { + public: + static ResultOrError<Ref<BindGroup>> Create(Device* device, + const BindGroupDescriptor* descriptor); + + BindGroup(Device* device, + const BindGroupDescriptor* descriptor, + uint32_t viewSizeIncrement, + const CPUDescriptorHeapAllocation& viewAllocation); + + // Returns true if the BindGroup was successfully populated. + bool PopulateViews(ShaderVisibleDescriptorAllocator* viewAllocator); + bool PopulateSamplers(Device* device, ShaderVisibleDescriptorAllocator* samplerAllocator); + + D3D12_GPU_DESCRIPTOR_HANDLE GetBaseViewDescriptor() const; + D3D12_GPU_DESCRIPTOR_HANDLE GetBaseSamplerDescriptor() const; + + void SetSamplerAllocationEntry(Ref<SamplerHeapCacheEntry> entry); + + using DynamicStorageBufferLengths = + ityp::stack_vec<uint32_t, uint32_t, kMaxDynamicStorageBuffersPerPipelineLayout>; + const DynamicStorageBufferLengths& GetDynamicStorageBufferLengths() const; + + private: + ~BindGroup() override; + + void DestroyImpl() override; + + Ref<SamplerHeapCacheEntry> mSamplerAllocationEntry; + + GPUDescriptorHeapAllocation mGPUViewAllocation; + CPUDescriptorHeapAllocation mCPUViewAllocation; + + DynamicStorageBufferLengths mDynamicStorageBufferLengths; + }; +} // namespace dawn::native::d3d12 + +#endif // DAWNNATIVE_D3D12_BINDGROUPD3D12_H_
diff --git a/src/dawn/native/d3d12/BindGroupLayoutD3D12.cpp b/src/dawn/native/d3d12/BindGroupLayoutD3D12.cpp new file mode 100644 index 0000000..4e586a0 --- /dev/null +++ b/src/dawn/native/d3d12/BindGroupLayoutD3D12.cpp
@@ -0,0 +1,185 @@ +// Copyright 2017 The Dawn Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include "dawn/native/d3d12/BindGroupLayoutD3D12.h" + +#include "dawn/common/BitSetIterator.h" +#include "dawn/native/d3d12/BindGroupD3D12.h" +#include "dawn/native/d3d12/DeviceD3D12.h" +#include "dawn/native/d3d12/SamplerHeapCacheD3D12.h" +#include "dawn/native/d3d12/StagingDescriptorAllocatorD3D12.h" + +namespace dawn::native::d3d12 { + namespace { + D3D12_DESCRIPTOR_RANGE_TYPE WGPUBindingInfoToDescriptorRangeType( + const BindingInfo& bindingInfo) { + switch (bindingInfo.bindingType) { + case BindingInfoType::Buffer: + switch (bindingInfo.buffer.type) { + case wgpu::BufferBindingType::Uniform: + return D3D12_DESCRIPTOR_RANGE_TYPE_CBV; + case wgpu::BufferBindingType::Storage: + case kInternalStorageBufferBinding: + return D3D12_DESCRIPTOR_RANGE_TYPE_UAV; + case wgpu::BufferBindingType::ReadOnlyStorage: + return D3D12_DESCRIPTOR_RANGE_TYPE_SRV; + case wgpu::BufferBindingType::Undefined: + UNREACHABLE(); + } + + case BindingInfoType::Sampler: + return D3D12_DESCRIPTOR_RANGE_TYPE_SAMPLER; + + case BindingInfoType::Texture: + case BindingInfoType::ExternalTexture: + return D3D12_DESCRIPTOR_RANGE_TYPE_SRV; + + case BindingInfoType::StorageTexture: + switch (bindingInfo.storageTexture.access) { + case wgpu::StorageTextureAccess::WriteOnly: + return D3D12_DESCRIPTOR_RANGE_TYPE_UAV; + case wgpu::StorageTextureAccess::Undefined: + UNREACHABLE(); + } + } + } + } // anonymous namespace + + // static + Ref<BindGroupLayout> BindGroupLayout::Create( + Device* device, + const BindGroupLayoutDescriptor* descriptor, + PipelineCompatibilityToken pipelineCompatibilityToken) { + return AcquireRef(new BindGroupLayout(device, descriptor, pipelineCompatibilityToken)); + } + + BindGroupLayout::BindGroupLayout(Device* device, + const BindGroupLayoutDescriptor* descriptor, + PipelineCompatibilityToken pipelineCompatibilityToken) + : BindGroupLayoutBase(device, descriptor, pipelineCompatibilityToken), + mDescriptorHeapOffsets(GetBindingCount()), + mShaderRegisters(GetBindingCount()), + mCbvUavSrvDescriptorCount(0), + mSamplerDescriptorCount(0), + mBindGroupAllocator(MakeFrontendBindGroupAllocator<BindGroup>(4096)) { + for (BindingIndex bindingIndex{0}; bindingIndex < GetBindingCount(); ++bindingIndex) { + const BindingInfo& bindingInfo = GetBindingInfo(bindingIndex); + + D3D12_DESCRIPTOR_RANGE_TYPE descriptorRangeType = + WGPUBindingInfoToDescriptorRangeType(bindingInfo); + mShaderRegisters[bindingIndex] = uint32_t(bindingInfo.binding); + + // For dynamic resources, Dawn uses root descriptor in D3D12 backend. So there is no + // need to allocate the descriptor from descriptor heap or create descriptor ranges. + if (bindingIndex < GetDynamicBufferCount()) { + continue; + } + ASSERT(!bindingInfo.buffer.hasDynamicOffset); + + mDescriptorHeapOffsets[bindingIndex] = + descriptorRangeType == D3D12_DESCRIPTOR_RANGE_TYPE_SAMPLER + ? mSamplerDescriptorCount++ + : mCbvUavSrvDescriptorCount++; + + D3D12_DESCRIPTOR_RANGE range; + range.RangeType = descriptorRangeType; + range.NumDescriptors = 1; + range.BaseShaderRegister = GetShaderRegister(bindingIndex); + range.RegisterSpace = kRegisterSpacePlaceholder; + range.OffsetInDescriptorsFromTableStart = D3D12_DESCRIPTOR_RANGE_OFFSET_APPEND; + + std::vector<D3D12_DESCRIPTOR_RANGE>& descriptorRanges = + descriptorRangeType == D3D12_DESCRIPTOR_RANGE_TYPE_SAMPLER + ? mSamplerDescriptorRanges + : mCbvUavSrvDescriptorRanges; + + // Try to join this range with the previous one, if the current range is a continuation + // of the previous. This is possible because the binding infos in the base type are + // sorted. + if (descriptorRanges.size() >= 2) { + D3D12_DESCRIPTOR_RANGE& previous = descriptorRanges.back(); + if (previous.RangeType == range.RangeType && + previous.BaseShaderRegister + previous.NumDescriptors == + range.BaseShaderRegister) { + previous.NumDescriptors += range.NumDescriptors; + continue; + } + } + + descriptorRanges.push_back(range); + } + + mViewAllocator = device->GetViewStagingDescriptorAllocator(GetCbvUavSrvDescriptorCount()); + mSamplerAllocator = + device->GetSamplerStagingDescriptorAllocator(GetSamplerDescriptorCount()); + } + + ResultOrError<Ref<BindGroup>> BindGroupLayout::AllocateBindGroup( + Device* device, + const BindGroupDescriptor* descriptor) { + uint32_t viewSizeIncrement = 0; + CPUDescriptorHeapAllocation viewAllocation; + if (GetCbvUavSrvDescriptorCount() > 0) { + DAWN_TRY_ASSIGN(viewAllocation, mViewAllocator->AllocateCPUDescriptors()); + viewSizeIncrement = mViewAllocator->GetSizeIncrement(); + } + + Ref<BindGroup> bindGroup = AcquireRef<BindGroup>( + mBindGroupAllocator.Allocate(device, descriptor, viewSizeIncrement, viewAllocation)); + + if (GetSamplerDescriptorCount() > 0) { + Ref<SamplerHeapCacheEntry> samplerHeapCacheEntry; + DAWN_TRY_ASSIGN(samplerHeapCacheEntry, device->GetSamplerHeapCache()->GetOrCreate( + bindGroup.Get(), mSamplerAllocator)); + bindGroup->SetSamplerAllocationEntry(std::move(samplerHeapCacheEntry)); + } + + return bindGroup; + } + + void BindGroupLayout::DeallocateBindGroup(BindGroup* bindGroup, + CPUDescriptorHeapAllocation* viewAllocation) { + if (viewAllocation->IsValid()) { + mViewAllocator->Deallocate(viewAllocation); + } + + mBindGroupAllocator.Deallocate(bindGroup); + } + + ityp::span<BindingIndex, const uint32_t> BindGroupLayout::GetDescriptorHeapOffsets() const { + return {mDescriptorHeapOffsets.data(), mDescriptorHeapOffsets.size()}; + } + + uint32_t BindGroupLayout::GetShaderRegister(BindingIndex bindingIndex) const { + return mShaderRegisters[bindingIndex]; + } + + uint32_t BindGroupLayout::GetCbvUavSrvDescriptorCount() const { + return mCbvUavSrvDescriptorCount; + } + + uint32_t BindGroupLayout::GetSamplerDescriptorCount() const { + return mSamplerDescriptorCount; + } + + const std::vector<D3D12_DESCRIPTOR_RANGE>& BindGroupLayout::GetCbvUavSrvDescriptorRanges() + const { + return mCbvUavSrvDescriptorRanges; + } + + const std::vector<D3D12_DESCRIPTOR_RANGE>& BindGroupLayout::GetSamplerDescriptorRanges() const { + return mSamplerDescriptorRanges; + } + +} // namespace dawn::native::d3d12
diff --git a/src/dawn/native/d3d12/BindGroupLayoutD3D12.h b/src/dawn/native/d3d12/BindGroupLayoutD3D12.h new file mode 100644 index 0000000..f16b16b --- /dev/null +++ b/src/dawn/native/d3d12/BindGroupLayoutD3D12.h
@@ -0,0 +1,94 @@ +// Copyright 2017 The Dawn Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#ifndef DAWNNATIVE_D3D12_BINDGROUPLAYOUTD3D12_H_ +#define DAWNNATIVE_D3D12_BINDGROUPLAYOUTD3D12_H_ + +#include "dawn/native/BindGroupLayout.h" + +#include "dawn/common/SlabAllocator.h" +#include "dawn/common/ityp_stack_vec.h" +#include "dawn/native/d3d12/d3d12_platform.h" + +namespace dawn::native::d3d12 { + + class BindGroup; + class CPUDescriptorHeapAllocation; + class Device; + class StagingDescriptorAllocator; + + // A purposefully invalid register space. + // + // We use the bind group index as the register space, but don't know the bind group index until + // pipeline layout creation time. This value should be replaced in PipelineLayoutD3D12. + static constexpr uint32_t kRegisterSpacePlaceholder = + D3D12_DRIVER_RESERVED_REGISTER_SPACE_VALUES_START; + + class BindGroupLayout final : public BindGroupLayoutBase { + public: + static Ref<BindGroupLayout> Create(Device* device, + const BindGroupLayoutDescriptor* descriptor, + PipelineCompatibilityToken pipelineCompatibilityToken); + + ResultOrError<Ref<BindGroup>> AllocateBindGroup(Device* device, + const BindGroupDescriptor* descriptor); + void DeallocateBindGroup(BindGroup* bindGroup, CPUDescriptorHeapAllocation* viewAllocation); + + // The offset (in descriptor count) into the corresponding descriptor heap. Not valid for + // dynamic binding indexes. + ityp::span<BindingIndex, const uint32_t> GetDescriptorHeapOffsets() const; + + // The D3D shader register that the Dawn binding index is mapped to by this bind group + // layout. + uint32_t GetShaderRegister(BindingIndex bindingIndex) const; + + // Counts of descriptors in the descriptor tables. + uint32_t GetCbvUavSrvDescriptorCount() const; + uint32_t GetSamplerDescriptorCount() const; + + const std::vector<D3D12_DESCRIPTOR_RANGE>& GetCbvUavSrvDescriptorRanges() const; + const std::vector<D3D12_DESCRIPTOR_RANGE>& GetSamplerDescriptorRanges() const; + + private: + BindGroupLayout(Device* device, + const BindGroupLayoutDescriptor* descriptor, + PipelineCompatibilityToken pipelineCompatibilityToken); + ~BindGroupLayout() override = default; + + // Contains the offset into the descriptor heap for the given resource view. Samplers and + // non-samplers are stored in separate descriptor heaps, so the offsets should be unique + // within each group and tightly packed. + // + // Dynamic resources are not used here since their descriptors are placed directly in root + // parameters. + ityp::stack_vec<BindingIndex, uint32_t, kMaxOptimalBindingsPerGroup> mDescriptorHeapOffsets; + + // Contains the shader register this binding is mapped to. + ityp::stack_vec<BindingIndex, uint32_t, kMaxOptimalBindingsPerGroup> mShaderRegisters; + + uint32_t mCbvUavSrvDescriptorCount; + uint32_t mSamplerDescriptorCount; + + std::vector<D3D12_DESCRIPTOR_RANGE> mCbvUavSrvDescriptorRanges; + std::vector<D3D12_DESCRIPTOR_RANGE> mSamplerDescriptorRanges; + + SlabAllocator<BindGroup> mBindGroupAllocator; + + StagingDescriptorAllocator* mSamplerAllocator = nullptr; + StagingDescriptorAllocator* mViewAllocator = nullptr; + }; + +} // namespace dawn::native::d3d12 + +#endif // DAWNNATIVE_D3D12_BINDGROUPLAYOUTD3D12_H_
diff --git a/src/dawn/native/d3d12/BufferD3D12.cpp b/src/dawn/native/d3d12/BufferD3D12.cpp new file mode 100644 index 0000000..27d9991 --- /dev/null +++ b/src/dawn/native/d3d12/BufferD3D12.cpp
@@ -0,0 +1,493 @@ +// Copyright 2017 The Dawn Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include "dawn/native/d3d12/BufferD3D12.h" + +#include "dawn/common/Assert.h" +#include "dawn/common/Constants.h" +#include "dawn/common/Math.h" +#include "dawn/native/CommandBuffer.h" +#include "dawn/native/DynamicUploader.h" +#include "dawn/native/d3d12/CommandRecordingContext.h" +#include "dawn/native/d3d12/D3D12Error.h" +#include "dawn/native/d3d12/DeviceD3D12.h" +#include "dawn/native/d3d12/HeapD3D12.h" +#include "dawn/native/d3d12/ResidencyManagerD3D12.h" +#include "dawn/native/d3d12/UtilsD3D12.h" + +namespace dawn::native::d3d12 { + + namespace { + D3D12_RESOURCE_FLAGS D3D12ResourceFlags(wgpu::BufferUsage usage) { + D3D12_RESOURCE_FLAGS flags = D3D12_RESOURCE_FLAG_NONE; + + if (usage & (wgpu::BufferUsage::Storage | kInternalStorageBuffer)) { + flags |= D3D12_RESOURCE_FLAG_ALLOW_UNORDERED_ACCESS; + } + + return flags; + } + + D3D12_RESOURCE_STATES D3D12BufferUsage(wgpu::BufferUsage usage) { + D3D12_RESOURCE_STATES resourceState = D3D12_RESOURCE_STATE_COMMON; + + if (usage & wgpu::BufferUsage::CopySrc) { + resourceState |= D3D12_RESOURCE_STATE_COPY_SOURCE; + } + if (usage & wgpu::BufferUsage::CopyDst) { + resourceState |= D3D12_RESOURCE_STATE_COPY_DEST; + } + if (usage & (wgpu::BufferUsage::Vertex | wgpu::BufferUsage::Uniform)) { + resourceState |= D3D12_RESOURCE_STATE_VERTEX_AND_CONSTANT_BUFFER; + } + if (usage & wgpu::BufferUsage::Index) { + resourceState |= D3D12_RESOURCE_STATE_INDEX_BUFFER; + } + if (usage & (wgpu::BufferUsage::Storage | kInternalStorageBuffer)) { + resourceState |= D3D12_RESOURCE_STATE_UNORDERED_ACCESS; + } + if (usage & kReadOnlyStorageBuffer) { + resourceState |= (D3D12_RESOURCE_STATE_PIXEL_SHADER_RESOURCE | + D3D12_RESOURCE_STATE_NON_PIXEL_SHADER_RESOURCE); + } + if (usage & wgpu::BufferUsage::Indirect) { + resourceState |= D3D12_RESOURCE_STATE_INDIRECT_ARGUMENT; + } + if (usage & wgpu::BufferUsage::QueryResolve) { + resourceState |= D3D12_RESOURCE_STATE_COPY_DEST; + } + + return resourceState; + } + + D3D12_HEAP_TYPE D3D12HeapType(wgpu::BufferUsage allowedUsage) { + if (allowedUsage & wgpu::BufferUsage::MapRead) { + return D3D12_HEAP_TYPE_READBACK; + } else if (allowedUsage & wgpu::BufferUsage::MapWrite) { + return D3D12_HEAP_TYPE_UPLOAD; + } else { + return D3D12_HEAP_TYPE_DEFAULT; + } + } + + size_t D3D12BufferSizeAlignment(wgpu::BufferUsage usage) { + if ((usage & wgpu::BufferUsage::Uniform) != 0) { + // D3D buffers are always resource size aligned to 64KB. However, D3D12's validation + // forbids binding a CBV to an unaligned size. To prevent, one can always safely + // align the buffer size to the CBV data alignment as other buffer usages + // ignore it (no size check). The validation will still enforce bound checks with + // the unaligned size returned by GetSize(). + // https://docs.microsoft.com/en-us/windows/win32/direct3d12/uploading-resources#buffer-alignment + return D3D12_CONSTANT_BUFFER_DATA_PLACEMENT_ALIGNMENT; + } + return 1; + } + } // namespace + + // static + ResultOrError<Ref<Buffer>> Buffer::Create(Device* device, const BufferDescriptor* descriptor) { + Ref<Buffer> buffer = AcquireRef(new Buffer(device, descriptor)); + DAWN_TRY(buffer->Initialize(descriptor->mappedAtCreation)); + return buffer; + } + + Buffer::Buffer(Device* device, const BufferDescriptor* descriptor) + : BufferBase(device, descriptor) { + } + + MaybeError Buffer::Initialize(bool mappedAtCreation) { + // Allocate at least 4 bytes so clamped accesses are always in bounds. + uint64_t size = std::max(GetSize(), uint64_t(4u)); + size_t alignment = D3D12BufferSizeAlignment(GetUsage()); + if (size > std::numeric_limits<uint64_t>::max() - alignment) { + // Alignment would overlow. + return DAWN_OUT_OF_MEMORY_ERROR("Buffer allocation is too large"); + } + mAllocatedSize = Align(size, alignment); + + D3D12_RESOURCE_DESC resourceDescriptor; + resourceDescriptor.Dimension = D3D12_RESOURCE_DIMENSION_BUFFER; + resourceDescriptor.Alignment = 0; + resourceDescriptor.Width = mAllocatedSize; + resourceDescriptor.Height = 1; + resourceDescriptor.DepthOrArraySize = 1; + resourceDescriptor.MipLevels = 1; + resourceDescriptor.Format = DXGI_FORMAT_UNKNOWN; + resourceDescriptor.SampleDesc.Count = 1; + resourceDescriptor.SampleDesc.Quality = 0; + resourceDescriptor.Layout = D3D12_TEXTURE_LAYOUT_ROW_MAJOR; + // Add CopyDst for non-mappable buffer initialization with mappedAtCreation + // and robust resource initialization. + resourceDescriptor.Flags = D3D12ResourceFlags(GetUsage() | wgpu::BufferUsage::CopyDst); + + auto heapType = D3D12HeapType(GetUsage()); + auto bufferUsage = D3D12_RESOURCE_STATE_COMMON; + + // D3D12 requires buffers on the READBACK heap to have the D3D12_RESOURCE_STATE_COPY_DEST + // state + if (heapType == D3D12_HEAP_TYPE_READBACK) { + bufferUsage |= D3D12_RESOURCE_STATE_COPY_DEST; + mFixedResourceState = true; + mLastUsage = wgpu::BufferUsage::CopyDst; + } + + // D3D12 requires buffers on the UPLOAD heap to have the D3D12_RESOURCE_STATE_GENERIC_READ + // state + if (heapType == D3D12_HEAP_TYPE_UPLOAD) { + bufferUsage |= D3D12_RESOURCE_STATE_GENERIC_READ; + mFixedResourceState = true; + mLastUsage = wgpu::BufferUsage::CopySrc; + } + + DAWN_TRY_ASSIGN( + mResourceAllocation, + ToBackend(GetDevice())->AllocateMemory(heapType, resourceDescriptor, bufferUsage)); + + SetLabelImpl(); + + // The buffers with mappedAtCreation == true will be initialized in + // BufferBase::MapAtCreation(). + if (GetDevice()->IsToggleEnabled(Toggle::NonzeroClearResourcesOnCreationForTesting) && + !mappedAtCreation) { + CommandRecordingContext* commandRecordingContext; + DAWN_TRY_ASSIGN(commandRecordingContext, + ToBackend(GetDevice())->GetPendingCommandContext()); + + DAWN_TRY(ClearBuffer(commandRecordingContext, uint8_t(1u))); + } + + // Initialize the padding bytes to zero. + if (GetDevice()->IsToggleEnabled(Toggle::LazyClearResourceOnFirstUse) && + !mappedAtCreation) { + uint32_t paddingBytes = GetAllocatedSize() - GetSize(); + if (paddingBytes > 0) { + CommandRecordingContext* commandRecordingContext; + DAWN_TRY_ASSIGN(commandRecordingContext, + ToBackend(GetDevice())->GetPendingCommandContext()); + + uint32_t clearSize = paddingBytes; + uint64_t clearOffset = GetSize(); + DAWN_TRY(ClearBuffer(commandRecordingContext, 0, clearOffset, clearSize)); + } + } + + return {}; + } + + Buffer::~Buffer() = default; + + ID3D12Resource* Buffer::GetD3D12Resource() const { + return mResourceAllocation.GetD3D12Resource(); + } + + // When true is returned, a D3D12_RESOURCE_BARRIER has been created and must be used in a + // ResourceBarrier call. Failing to do so will cause the tracked state to become invalid and can + // cause subsequent errors. + bool Buffer::TrackUsageAndGetResourceBarrier(CommandRecordingContext* commandContext, + D3D12_RESOURCE_BARRIER* barrier, + wgpu::BufferUsage newUsage) { + // Track the underlying heap to ensure residency. + Heap* heap = ToBackend(mResourceAllocation.GetResourceHeap()); + commandContext->TrackHeapUsage(heap, GetDevice()->GetPendingCommandSerial()); + + // Return the resource barrier. + return TransitionUsageAndGetResourceBarrier(commandContext, barrier, newUsage); + } + + void Buffer::TrackUsageAndTransitionNow(CommandRecordingContext* commandContext, + wgpu::BufferUsage newUsage) { + D3D12_RESOURCE_BARRIER barrier; + + if (TrackUsageAndGetResourceBarrier(commandContext, &barrier, newUsage)) { + commandContext->GetCommandList()->ResourceBarrier(1, &barrier); + } + } + + // When true is returned, a D3D12_RESOURCE_BARRIER has been created and must be used in a + // ResourceBarrier call. Failing to do so will cause the tracked state to become invalid and can + // cause subsequent errors. + bool Buffer::TransitionUsageAndGetResourceBarrier(CommandRecordingContext* commandContext, + D3D12_RESOURCE_BARRIER* barrier, + wgpu::BufferUsage newUsage) { + // Resources in upload and readback heaps must be kept in the COPY_SOURCE/DEST state + if (mFixedResourceState) { + ASSERT(mLastUsage == newUsage); + return false; + } + + D3D12_RESOURCE_STATES lastState = D3D12BufferUsage(mLastUsage); + D3D12_RESOURCE_STATES newState = D3D12BufferUsage(newUsage); + + // If the transition is from-UAV-to-UAV, then a UAV barrier is needed. + // If one of the usages isn't UAV, then other barriers are used. + bool needsUAVBarrier = lastState == D3D12_RESOURCE_STATE_UNORDERED_ACCESS && + newState == D3D12_RESOURCE_STATE_UNORDERED_ACCESS; + + if (needsUAVBarrier) { + barrier->Type = D3D12_RESOURCE_BARRIER_TYPE_UAV; + barrier->Flags = D3D12_RESOURCE_BARRIER_FLAG_NONE; + barrier->UAV.pResource = GetD3D12Resource(); + + mLastUsage = newUsage; + return true; + } + + // We can skip transitions to already current usages. + if (IsSubset(newUsage, mLastUsage)) { + return false; + } + + mLastUsage = newUsage; + + // The COMMON state represents a state where no write operations can be pending, which makes + // it possible to transition to and from some states without synchronizaton (i.e. without an + // explicit ResourceBarrier call). A buffer can be implicitly promoted to 1) a single write + // state, or 2) multiple read states. A buffer that is accessed within a command list will + // always implicitly decay to the COMMON state after the call to ExecuteCommandLists + // completes - this is because all buffer writes are guaranteed to be completed before the + // next ExecuteCommandLists call executes. + // https://docs.microsoft.com/en-us/windows/desktop/direct3d12/using-resource-barriers-to-synchronize-resource-states-in-direct3d-12#implicit-state-transitions + + // To track implicit decays, we must record the pending serial on which a transition will + // occur. When that buffer is used again, the previously recorded serial must be compared to + // the last completed serial to determine if the buffer has implicity decayed to the common + // state. + const ExecutionSerial pendingCommandSerial = + ToBackend(GetDevice())->GetPendingCommandSerial(); + if (pendingCommandSerial > mLastUsedSerial) { + lastState = D3D12_RESOURCE_STATE_COMMON; + mLastUsedSerial = pendingCommandSerial; + } + + // All possible buffer states used by Dawn are eligible for implicit promotion from COMMON. + // These are: COPY_SOURCE, VERTEX_AND_COPY_BUFFER, INDEX_BUFFER, COPY_DEST, + // UNORDERED_ACCESS, and INDIRECT_ARGUMENT. Note that for implicit promotion, the + // destination state cannot be 1) more than one write state, or 2) both a read and write + // state. This goes unchecked here because it should not be allowed through render/compute + // pass validation. + if (lastState == D3D12_RESOURCE_STATE_COMMON) { + return false; + } + + // TODO(crbug.com/dawn/1024): The before and after states must be different. Remove this + // workaround and use D3D12 states instead of WebGPU usages to manage the tracking of + // barrier state. + if (lastState == newState) { + return false; + } + + barrier->Type = D3D12_RESOURCE_BARRIER_TYPE_TRANSITION; + barrier->Flags = D3D12_RESOURCE_BARRIER_FLAG_NONE; + barrier->Transition.pResource = GetD3D12Resource(); + barrier->Transition.StateBefore = lastState; + barrier->Transition.StateAfter = newState; + barrier->Transition.Subresource = D3D12_RESOURCE_BARRIER_ALL_SUBRESOURCES; + + return true; + } + + D3D12_GPU_VIRTUAL_ADDRESS Buffer::GetVA() const { + return mResourceAllocation.GetGPUPointer(); + } + + bool Buffer::IsCPUWritableAtCreation() const { + // We use a staging buffer for the buffers with mappedAtCreation == true and created on the + // READBACK heap because for the buffers on the READBACK heap, the data written on the CPU + // side won't be uploaded to GPU. When we enable zero-initialization, the CPU side memory + // of the buffer is all written to 0 but not the GPU side memory, so on the next mapping + // operation the zeroes get overwritten by whatever was in the GPU memory when the buffer + // was created. With a staging buffer, the data on the CPU side will first upload to the + // staging buffer, and copied from the staging buffer to the GPU memory of the current + // buffer in the unmap() call. + // TODO(enga): Handle CPU-visible memory on UMA + return (GetUsage() & wgpu::BufferUsage::MapWrite) != 0; + } + + MaybeError Buffer::MapInternal(bool isWrite, + size_t offset, + size_t size, + const char* contextInfo) { + // The mapped buffer can be accessed at any time, so it must be locked to ensure it is never + // evicted. This buffer should already have been made resident when it was created. + Heap* heap = ToBackend(mResourceAllocation.GetResourceHeap()); + DAWN_TRY(ToBackend(GetDevice())->GetResidencyManager()->LockAllocation(heap)); + + D3D12_RANGE range = {offset, offset + size}; + // mMappedData is the pointer to the start of the resource, irrespective of offset. + // MSDN says (note the weird use of "never"): + // + // When ppData is not NULL, the pointer returned is never offset by any values in + // pReadRange. + // + // https://docs.microsoft.com/en-us/windows/win32/api/d3d12/nf-d3d12-id3d12resource-map + DAWN_TRY(CheckHRESULT(GetD3D12Resource()->Map(0, &range, &mMappedData), contextInfo)); + + if (isWrite) { + mWrittenMappedRange = range; + } + + return {}; + } + + MaybeError Buffer::MapAtCreationImpl() { + // We will use a staging buffer for MapRead buffers instead so we just clear the staging + // buffer and initialize the original buffer by copying the staging buffer to the original + // buffer one the first time Unmap() is called. + ASSERT((GetUsage() & wgpu::BufferUsage::MapWrite) != 0); + + // The buffers with mappedAtCreation == true will be initialized in + // BufferBase::MapAtCreation(). + DAWN_TRY(MapInternal(true, 0, size_t(GetAllocatedSize()), "D3D12 map at creation")); + + return {}; + } + + MaybeError Buffer::MapAsyncImpl(wgpu::MapMode mode, size_t offset, size_t size) { + CommandRecordingContext* commandContext; + DAWN_TRY_ASSIGN(commandContext, ToBackend(GetDevice())->GetPendingCommandContext()); + DAWN_TRY(EnsureDataInitialized(commandContext)); + + return MapInternal(mode & wgpu::MapMode::Write, offset, size, "D3D12 map async"); + } + + void Buffer::UnmapImpl() { + GetD3D12Resource()->Unmap(0, &mWrittenMappedRange); + mMappedData = nullptr; + mWrittenMappedRange = {0, 0}; + + // When buffers are mapped, they are locked to keep them in resident memory. We must unlock + // them when they are unmapped. + Heap* heap = ToBackend(mResourceAllocation.GetResourceHeap()); + ToBackend(GetDevice())->GetResidencyManager()->UnlockAllocation(heap); + } + + void* Buffer::GetMappedPointerImpl() { + // The frontend asks that the pointer returned is from the start of the resource + // irrespective of the offset passed in MapAsyncImpl, which is what mMappedData is. + return mMappedData; + } + + void Buffer::DestroyImpl() { + if (mMappedData != nullptr) { + // If the buffer is currently mapped, unmap without flushing the writes to the GPU + // since the buffer cannot be used anymore. UnmapImpl checks mWrittenRange to know + // which parts to flush, so we set it to an empty range to prevent flushes. + mWrittenMappedRange = {0, 0}; + } + BufferBase::DestroyImpl(); + + ToBackend(GetDevice())->DeallocateMemory(mResourceAllocation); + } + + bool Buffer::CheckIsResidentForTesting() const { + Heap* heap = ToBackend(mResourceAllocation.GetResourceHeap()); + return heap->IsInList() || heap->IsResidencyLocked(); + } + + bool Buffer::CheckAllocationMethodForTesting(AllocationMethod allocationMethod) const { + return mResourceAllocation.GetInfo().mMethod == allocationMethod; + } + + MaybeError Buffer::EnsureDataInitialized(CommandRecordingContext* commandContext) { + if (!NeedsInitialization()) { + return {}; + } + + DAWN_TRY(InitializeToZero(commandContext)); + return {}; + } + + ResultOrError<bool> Buffer::EnsureDataInitializedAsDestination( + CommandRecordingContext* commandContext, + uint64_t offset, + uint64_t size) { + if (!NeedsInitialization()) { + return {false}; + } + + if (IsFullBufferRange(offset, size)) { + SetIsDataInitialized(); + return {false}; + } + + DAWN_TRY(InitializeToZero(commandContext)); + return {true}; + } + + MaybeError Buffer::EnsureDataInitializedAsDestination(CommandRecordingContext* commandContext, + const CopyTextureToBufferCmd* copy) { + if (!NeedsInitialization()) { + return {}; + } + + if (IsFullBufferOverwrittenInTextureToBufferCopy(copy)) { + SetIsDataInitialized(); + } else { + DAWN_TRY(InitializeToZero(commandContext)); + } + + return {}; + } + + void Buffer::SetLabelImpl() { + SetDebugName(ToBackend(GetDevice()), mResourceAllocation.GetD3D12Resource(), "Dawn_Buffer", + GetLabel()); + } + + MaybeError Buffer::InitializeToZero(CommandRecordingContext* commandContext) { + ASSERT(NeedsInitialization()); + + // TODO(crbug.com/dawn/484): skip initializing the buffer when it is created on a heap + // that has already been zero initialized. + DAWN_TRY(ClearBuffer(commandContext, uint8_t(0u))); + SetIsDataInitialized(); + GetDevice()->IncrementLazyClearCountForTesting(); + + return {}; + } + + MaybeError Buffer::ClearBuffer(CommandRecordingContext* commandContext, + uint8_t clearValue, + uint64_t offset, + uint64_t size) { + Device* device = ToBackend(GetDevice()); + size = size > 0 ? size : GetAllocatedSize(); + + // The state of the buffers on UPLOAD heap must always be GENERIC_READ and cannot be + // changed away, so we can only clear such buffer with buffer mapping. + if (D3D12HeapType(GetUsage()) == D3D12_HEAP_TYPE_UPLOAD) { + DAWN_TRY(MapInternal(true, static_cast<size_t>(offset), static_cast<size_t>(size), + "D3D12 map at clear buffer")); + memset(mMappedData, clearValue, size); + UnmapImpl(); + } else if (clearValue == 0u) { + DAWN_TRY(device->ClearBufferToZero(commandContext, this, offset, size)); + } else { + // TODO(crbug.com/dawn/852): use ClearUnorderedAccessView*() when the buffer usage + // includes STORAGE. + DynamicUploader* uploader = device->GetDynamicUploader(); + UploadHandle uploadHandle; + DAWN_TRY_ASSIGN(uploadHandle, + uploader->Allocate(size, device->GetPendingCommandSerial(), + kCopyBufferToBufferOffsetAlignment)); + + memset(uploadHandle.mappedBuffer, clearValue, size); + + device->CopyFromStagingToBufferImpl(commandContext, uploadHandle.stagingBuffer, + uploadHandle.startOffset, this, offset, size); + } + + return {}; + } +} // namespace dawn::native::d3d12
diff --git a/src/dawn/native/d3d12/BufferD3D12.h b/src/dawn/native/d3d12/BufferD3D12.h new file mode 100644 index 0000000..253565a --- /dev/null +++ b/src/dawn/native/d3d12/BufferD3D12.h
@@ -0,0 +1,91 @@ +// Copyright 2017 The Dawn Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#ifndef DAWNNATIVE_D3D12_BUFFERD3D12_H_ +#define DAWNNATIVE_D3D12_BUFFERD3D12_H_ + +#include "dawn/native/Buffer.h" + +#include "dawn/native/d3d12/ResourceHeapAllocationD3D12.h" +#include "dawn/native/d3d12/d3d12_platform.h" + +namespace dawn::native::d3d12 { + + class CommandRecordingContext; + class Device; + + class Buffer final : public BufferBase { + public: + static ResultOrError<Ref<Buffer>> Create(Device* device, + const BufferDescriptor* descriptor); + + ID3D12Resource* GetD3D12Resource() const; + D3D12_GPU_VIRTUAL_ADDRESS GetVA() const; + + bool TrackUsageAndGetResourceBarrier(CommandRecordingContext* commandContext, + D3D12_RESOURCE_BARRIER* barrier, + wgpu::BufferUsage newUsage); + void TrackUsageAndTransitionNow(CommandRecordingContext* commandContext, + wgpu::BufferUsage newUsage); + + bool CheckAllocationMethodForTesting(AllocationMethod allocationMethod) const; + bool CheckIsResidentForTesting() const; + + MaybeError EnsureDataInitialized(CommandRecordingContext* commandContext); + ResultOrError<bool> EnsureDataInitializedAsDestination( + CommandRecordingContext* commandContext, + uint64_t offset, + uint64_t size); + MaybeError EnsureDataInitializedAsDestination(CommandRecordingContext* commandContext, + const CopyTextureToBufferCmd* copy); + + // Dawn API + void SetLabelImpl() override; + + private: + Buffer(Device* device, const BufferDescriptor* descriptor); + ~Buffer() override; + + MaybeError Initialize(bool mappedAtCreation); + MaybeError MapAsyncImpl(wgpu::MapMode mode, size_t offset, size_t size) override; + void UnmapImpl() override; + void DestroyImpl() override; + bool IsCPUWritableAtCreation() const override; + virtual MaybeError MapAtCreationImpl() override; + void* GetMappedPointerImpl() override; + + MaybeError MapInternal(bool isWrite, size_t start, size_t end, const char* contextInfo); + + bool TransitionUsageAndGetResourceBarrier(CommandRecordingContext* commandContext, + D3D12_RESOURCE_BARRIER* barrier, + wgpu::BufferUsage newUsage); + + MaybeError InitializeToZero(CommandRecordingContext* commandContext); + MaybeError ClearBuffer(CommandRecordingContext* commandContext, + uint8_t clearValue, + uint64_t offset = 0, + uint64_t size = 0); + + ResourceHeapAllocation mResourceAllocation; + bool mFixedResourceState = false; + wgpu::BufferUsage mLastUsage = wgpu::BufferUsage::None; + ExecutionSerial mLastUsedSerial = std::numeric_limits<ExecutionSerial>::max(); + + D3D12_RANGE mWrittenMappedRange = {0, 0}; + void* mMappedData = nullptr; + }; + +} // namespace dawn::native::d3d12 + +#endif // DAWNNATIVE_D3D12_BUFFERD3D12_H_
diff --git a/src/dawn/native/d3d12/CPUDescriptorHeapAllocationD3D12.cpp b/src/dawn/native/d3d12/CPUDescriptorHeapAllocationD3D12.cpp new file mode 100644 index 0000000..617c196 --- /dev/null +++ b/src/dawn/native/d3d12/CPUDescriptorHeapAllocationD3D12.cpp
@@ -0,0 +1,53 @@ +// Copyright 2020 The Dawn Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include "dawn/native/d3d12/CPUDescriptorHeapAllocationD3D12.h" +#include "dawn/native/Error.h" + +namespace dawn::native::d3d12 { + + CPUDescriptorHeapAllocation::CPUDescriptorHeapAllocation( + D3D12_CPU_DESCRIPTOR_HANDLE baseDescriptor, + uint32_t heapIndex) + : mBaseDescriptor(baseDescriptor), mHeapIndex(heapIndex) { + } + + D3D12_CPU_DESCRIPTOR_HANDLE CPUDescriptorHeapAllocation::GetBaseDescriptor() const { + ASSERT(IsValid()); + return mBaseDescriptor; + } + + D3D12_CPU_DESCRIPTOR_HANDLE CPUDescriptorHeapAllocation::OffsetFrom( + uint32_t sizeIncrementInBytes, + uint32_t offsetInDescriptorCount) const { + ASSERT(IsValid()); + D3D12_CPU_DESCRIPTOR_HANDLE cpuHandle = mBaseDescriptor; + cpuHandle.ptr += sizeIncrementInBytes * offsetInDescriptorCount; + return cpuHandle; + } + + uint32_t CPUDescriptorHeapAllocation::GetHeapIndex() const { + ASSERT(mHeapIndex >= 0); + return mHeapIndex; + } + + bool CPUDescriptorHeapAllocation::IsValid() const { + return mBaseDescriptor.ptr != 0; + } + + void CPUDescriptorHeapAllocation::Invalidate() { + mBaseDescriptor = {0}; + } + +} // namespace dawn::native::d3d12
diff --git a/src/dawn/native/d3d12/CPUDescriptorHeapAllocationD3D12.h b/src/dawn/native/d3d12/CPUDescriptorHeapAllocationD3D12.h new file mode 100644 index 0000000..997d056 --- /dev/null +++ b/src/dawn/native/d3d12/CPUDescriptorHeapAllocationD3D12.h
@@ -0,0 +1,47 @@ +// Copyright 2020 The Dawn Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#ifndef DAWNNATIVE_D3D12_CPUDESCRIPTORHEAPALLOCATION_H_ +#define DAWNNATIVE_D3D12_CPUDESCRIPTORHEAPALLOCATION_H_ + +#include <cstdint> + +#include "dawn/native/d3d12/d3d12_platform.h" + +namespace dawn::native::d3d12 { + + // Wrapper for a handle into a CPU-only descriptor heap. + class CPUDescriptorHeapAllocation { + public: + CPUDescriptorHeapAllocation() = default; + CPUDescriptorHeapAllocation(D3D12_CPU_DESCRIPTOR_HANDLE baseDescriptor, uint32_t heapIndex); + + D3D12_CPU_DESCRIPTOR_HANDLE GetBaseDescriptor() const; + + D3D12_CPU_DESCRIPTOR_HANDLE OffsetFrom(uint32_t sizeIncrementInBytes, + uint32_t offsetInDescriptorCount) const; + uint32_t GetHeapIndex() const; + + bool IsValid() const; + + void Invalidate(); + + private: + D3D12_CPU_DESCRIPTOR_HANDLE mBaseDescriptor = {0}; + uint32_t mHeapIndex = -1; + }; + +} // namespace dawn::native::d3d12 + +#endif // DAWNNATIVE_D3D12_CPUDESCRIPTORHEAPALLOCATION_H_
diff --git a/src/dawn/native/d3d12/CommandAllocatorManager.cpp b/src/dawn/native/d3d12/CommandAllocatorManager.cpp new file mode 100644 index 0000000..88ac0b8 --- /dev/null +++ b/src/dawn/native/d3d12/CommandAllocatorManager.cpp
@@ -0,0 +1,72 @@ +// Copyright 2017 The Dawn Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include "dawn/native/d3d12/CommandAllocatorManager.h" + +#include "dawn/native/d3d12/D3D12Error.h" +#include "dawn/native/d3d12/DeviceD3D12.h" + +#include "dawn/common/Assert.h" +#include "dawn/common/BitSetIterator.h" + +namespace dawn::native::d3d12 { + + CommandAllocatorManager::CommandAllocatorManager(Device* device) + : device(device), mAllocatorCount(0) { + mFreeAllocators.set(); + } + + ResultOrError<ID3D12CommandAllocator*> CommandAllocatorManager::ReserveCommandAllocator() { + // If there are no free allocators, get the oldest serial in flight and wait on it + if (mFreeAllocators.none()) { + const ExecutionSerial firstSerial = mInFlightCommandAllocators.FirstSerial(); + DAWN_TRY(device->WaitForSerial(firstSerial)); + DAWN_TRY(Tick(firstSerial)); + } + + ASSERT(mFreeAllocators.any()); + + // Get the index of the first free allocator from the bitset + unsigned int firstFreeIndex = *(IterateBitSet(mFreeAllocators).begin()); + + if (firstFreeIndex >= mAllocatorCount) { + ASSERT(firstFreeIndex == mAllocatorCount); + mAllocatorCount++; + DAWN_TRY(CheckHRESULT(device->GetD3D12Device()->CreateCommandAllocator( + D3D12_COMMAND_LIST_TYPE_DIRECT, + IID_PPV_ARGS(&mCommandAllocators[firstFreeIndex])), + "D3D12 create command allocator")); + } + + // Mark the command allocator as used + mFreeAllocators.reset(firstFreeIndex); + + // Enqueue the command allocator. It will be scheduled for reset after the next + // ExecuteCommandLists + mInFlightCommandAllocators.Enqueue({mCommandAllocators[firstFreeIndex], firstFreeIndex}, + device->GetPendingCommandSerial()); + return mCommandAllocators[firstFreeIndex].Get(); + } + + MaybeError CommandAllocatorManager::Tick(ExecutionSerial lastCompletedSerial) { + // Reset all command allocators that are no longer in flight + for (auto it : mInFlightCommandAllocators.IterateUpTo(lastCompletedSerial)) { + DAWN_TRY(CheckHRESULT(it.commandAllocator->Reset(), "D3D12 reset command allocator")); + mFreeAllocators.set(it.index); + } + mInFlightCommandAllocators.ClearUpTo(lastCompletedSerial); + return {}; + } + +} // namespace dawn::native::d3d12
diff --git a/src/dawn/native/d3d12/CommandAllocatorManager.h b/src/dawn/native/d3d12/CommandAllocatorManager.h new file mode 100644 index 0000000..1f8cc1e --- /dev/null +++ b/src/dawn/native/d3d12/CommandAllocatorManager.h
@@ -0,0 +1,58 @@ +// Copyright 2017 The Dawn Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#ifndef DAWNNATIVE_D3D12_COMMANDALLOCATORMANAGER_H_ +#define DAWNNATIVE_D3D12_COMMANDALLOCATORMANAGER_H_ + +#include "dawn/native/d3d12/d3d12_platform.h" + +#include "dawn/common/SerialQueue.h" +#include "dawn/native/Error.h" +#include "dawn/native/IntegerTypes.h" + +#include <bitset> + +namespace dawn::native::d3d12 { + + class Device; + + class CommandAllocatorManager { + public: + CommandAllocatorManager(Device* device); + + // A CommandAllocator that is reserved must be used on the next ExecuteCommandLists + // otherwise its commands may be reset before execution has completed on the GPU + ResultOrError<ID3D12CommandAllocator*> ReserveCommandAllocator(); + MaybeError Tick(ExecutionSerial lastCompletedSerial); + + private: + Device* device; + + // This must be at least 2 because the Device and Queue use separate command allocators + static constexpr unsigned int kMaxCommandAllocators = 32; + unsigned int mAllocatorCount; + + struct IndexedCommandAllocator { + ComPtr<ID3D12CommandAllocator> commandAllocator; + unsigned int index; + }; + + ComPtr<ID3D12CommandAllocator> mCommandAllocators[kMaxCommandAllocators]; + std::bitset<kMaxCommandAllocators> mFreeAllocators; + SerialQueue<ExecutionSerial, IndexedCommandAllocator> mInFlightCommandAllocators; + }; + +} // namespace dawn::native::d3d12 + +#endif // DAWNNATIVE_D3D12_COMMANDALLOCATORMANAGER_H_
diff --git a/src/dawn/native/d3d12/CommandBufferD3D12.cpp b/src/dawn/native/d3d12/CommandBufferD3D12.cpp new file mode 100644 index 0000000..86022c7 --- /dev/null +++ b/src/dawn/native/d3d12/CommandBufferD3D12.cpp
@@ -0,0 +1,1676 @@ +// Copyright 2017 The Dawn Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include "dawn/native/d3d12/CommandBufferD3D12.h" + +#include "dawn/native/BindGroupTracker.h" +#include "dawn/native/CommandValidation.h" +#include "dawn/native/DynamicUploader.h" +#include "dawn/native/Error.h" +#include "dawn/native/RenderBundle.h" +#include "dawn/native/d3d12/BindGroupD3D12.h" +#include "dawn/native/d3d12/BindGroupLayoutD3D12.h" +#include "dawn/native/d3d12/ComputePipelineD3D12.h" +#include "dawn/native/d3d12/DeviceD3D12.h" +#include "dawn/native/d3d12/PipelineLayoutD3D12.h" +#include "dawn/native/d3d12/PlatformFunctions.h" +#include "dawn/native/d3d12/QuerySetD3D12.h" +#include "dawn/native/d3d12/RenderPassBuilderD3D12.h" +#include "dawn/native/d3d12/RenderPipelineD3D12.h" +#include "dawn/native/d3d12/ShaderVisibleDescriptorAllocatorD3D12.h" +#include "dawn/native/d3d12/StagingBufferD3D12.h" +#include "dawn/native/d3d12/StagingDescriptorAllocatorD3D12.h" +#include "dawn/native/d3d12/UtilsD3D12.h" + +namespace dawn::native::d3d12 { + + namespace { + + DXGI_FORMAT DXGIIndexFormat(wgpu::IndexFormat format) { + switch (format) { + case wgpu::IndexFormat::Undefined: + return DXGI_FORMAT_UNKNOWN; + case wgpu::IndexFormat::Uint16: + return DXGI_FORMAT_R16_UINT; + case wgpu::IndexFormat::Uint32: + return DXGI_FORMAT_R32_UINT; + } + } + + D3D12_QUERY_TYPE D3D12QueryType(wgpu::QueryType type) { + switch (type) { + case wgpu::QueryType::Occlusion: + return D3D12_QUERY_TYPE_BINARY_OCCLUSION; + case wgpu::QueryType::PipelineStatistics: + return D3D12_QUERY_TYPE_PIPELINE_STATISTICS; + case wgpu::QueryType::Timestamp: + return D3D12_QUERY_TYPE_TIMESTAMP; + } + } + + bool CanUseCopyResource(const TextureCopy& src, + const TextureCopy& dst, + const Extent3D& copySize) { + // Checked by validation + ASSERT(src.texture->GetSampleCount() == dst.texture->GetSampleCount()); + ASSERT(src.texture->GetFormat().CopyCompatibleWith(dst.texture->GetFormat())); + ASSERT(src.aspect == dst.aspect); + + const Extent3D& srcSize = src.texture->GetSize(); + const Extent3D& dstSize = dst.texture->GetSize(); + + // https://docs.microsoft.com/en-us/windows/win32/api/d3d12/nf-d3d12-id3d12graphicscommandlist-copyresource + // In order to use D3D12's copy resource, the textures must be the same dimensions, and + // the copy must be of the entire resource. + // TODO(dawn:129): Support 1D textures. + return src.aspect == src.texture->GetFormat().aspects && + src.texture->GetDimension() == dst.texture->GetDimension() && // + dst.texture->GetNumMipLevels() == 1 && // + src.texture->GetNumMipLevels() == 1 && // A copy command is of a single mip, so + // if a resource has more than one, we + // definitely cannot use CopyResource. + copySize.width == dstSize.width && // + copySize.width == srcSize.width && // + copySize.height == dstSize.height && // + copySize.height == srcSize.height && // + copySize.depthOrArrayLayers == dstSize.depthOrArrayLayers && // + copySize.depthOrArrayLayers == srcSize.depthOrArrayLayers; + } + + void RecordWriteTimestampCmd(ID3D12GraphicsCommandList* commandList, + WriteTimestampCmd* cmd) { + QuerySet* querySet = ToBackend(cmd->querySet.Get()); + ASSERT(D3D12QueryType(querySet->GetQueryType()) == D3D12_QUERY_TYPE_TIMESTAMP); + commandList->EndQuery(querySet->GetQueryHeap(), D3D12_QUERY_TYPE_TIMESTAMP, + cmd->queryIndex); + } + + void RecordResolveQuerySetCmd(ID3D12GraphicsCommandList* commandList, + Device* device, + QuerySet* querySet, + uint32_t firstQuery, + uint32_t queryCount, + Buffer* destination, + uint64_t destinationOffset) { + const std::vector<bool>& availability = querySet->GetQueryAvailability(); + + auto currentIt = availability.begin() + firstQuery; + auto lastIt = availability.begin() + firstQuery + queryCount; + + // Traverse available queries in the range of [firstQuery, firstQuery + queryCount - 1] + while (currentIt != lastIt) { + auto firstTrueIt = std::find(currentIt, lastIt, true); + // No available query found for resolving + if (firstTrueIt == lastIt) { + break; + } + auto nextFalseIt = std::find(firstTrueIt, lastIt, false); + + // The query index of firstTrueIt where the resolving starts + uint32_t resolveQueryIndex = std::distance(availability.begin(), firstTrueIt); + // The queries count between firstTrueIt and nextFalseIt need to be resolved + uint32_t resolveQueryCount = std::distance(firstTrueIt, nextFalseIt); + + // Calculate destinationOffset based on the current resolveQueryIndex and firstQuery + uint32_t resolveDestinationOffset = + destinationOffset + (resolveQueryIndex - firstQuery) * sizeof(uint64_t); + + // Resolve the queries between firstTrueIt and nextFalseIt (which is at most lastIt) + commandList->ResolveQueryData( + querySet->GetQueryHeap(), D3D12QueryType(querySet->GetQueryType()), + resolveQueryIndex, resolveQueryCount, destination->GetD3D12Resource(), + resolveDestinationOffset); + + // Set current iterator to next false + currentIt = nextFalseIt; + } + } + + void RecordFirstIndexOffset(ID3D12GraphicsCommandList* commandList, + RenderPipeline* pipeline, + uint32_t firstVertex, + uint32_t firstInstance) { + const FirstOffsetInfo& firstOffsetInfo = pipeline->GetFirstOffsetInfo(); + if (!firstOffsetInfo.usesVertexIndex && !firstOffsetInfo.usesInstanceIndex) { + return; + } + std::array<uint32_t, 2> offsets{}; + uint32_t count = 0; + if (firstOffsetInfo.usesVertexIndex) { + offsets[firstOffsetInfo.vertexIndexOffset / sizeof(uint32_t)] = firstVertex; + ++count; + } + if (firstOffsetInfo.usesInstanceIndex) { + offsets[firstOffsetInfo.instanceIndexOffset / sizeof(uint32_t)] = firstInstance; + ++count; + } + PipelineLayout* layout = ToBackend(pipeline->GetLayout()); + commandList->SetGraphicsRoot32BitConstants(layout->GetFirstIndexOffsetParameterIndex(), + count, offsets.data(), 0); + } + + bool ShouldCopyUsingTemporaryBuffer(DeviceBase* device, + const TextureCopy& srcCopy, + const TextureCopy& dstCopy) { + // Currently we only need the workaround for an Intel D3D12 driver issue. + if (device->IsToggleEnabled( + Toggle:: + UseTempBufferInSmallFormatTextureToTextureCopyFromGreaterToLessMipLevel)) { + bool copyToLesserLevel = srcCopy.mipLevel > dstCopy.mipLevel; + ASSERT( + srcCopy.texture->GetFormat().CopyCompatibleWith(dstCopy.texture->GetFormat())); + + // GetAspectInfo(aspect) requires HasOneBit(aspect) == true, plus the texel block + // sizes of depth stencil formats are always no less than 4 bytes. + bool isSmallColorFormat = + HasOneBit(srcCopy.aspect) && + srcCopy.texture->GetFormat().GetAspectInfo(srcCopy.aspect).block.byteSize < 4u; + if (copyToLesserLevel && isSmallColorFormat) { + return true; + } + } + + return false; + } + + MaybeError RecordCopyTextureWithTemporaryBuffer(CommandRecordingContext* recordingContext, + const TextureCopy& srcCopy, + const TextureCopy& dstCopy, + const Extent3D& copySize) { + ASSERT(srcCopy.texture->GetFormat().format == dstCopy.texture->GetFormat().format); + ASSERT(srcCopy.aspect == dstCopy.aspect); + dawn::native::Format format = srcCopy.texture->GetFormat(); + const TexelBlockInfo& blockInfo = format.GetAspectInfo(srcCopy.aspect).block; + ASSERT(copySize.width % blockInfo.width == 0); + uint32_t widthInBlocks = copySize.width / blockInfo.width; + ASSERT(copySize.height % blockInfo.height == 0); + uint32_t heightInBlocks = copySize.height / blockInfo.height; + + // Create tempBuffer + uint32_t bytesPerRow = + Align(blockInfo.byteSize * widthInBlocks, kTextureBytesPerRowAlignment); + uint32_t rowsPerImage = heightInBlocks; + + // The size of temporary buffer isn't needed to be a multiple of 4 because we don't + // need to set mappedAtCreation to be true. + auto tempBufferSize = + ComputeRequiredBytesInCopy(blockInfo, copySize, bytesPerRow, rowsPerImage); + + BufferDescriptor tempBufferDescriptor; + tempBufferDescriptor.usage = wgpu::BufferUsage::CopySrc | wgpu::BufferUsage::CopyDst; + tempBufferDescriptor.size = tempBufferSize.AcquireSuccess(); + Device* device = ToBackend(srcCopy.texture->GetDevice()); + Ref<BufferBase> tempBufferBase; + DAWN_TRY_ASSIGN(tempBufferBase, device->CreateBuffer(&tempBufferDescriptor)); + Ref<Buffer> tempBuffer = ToBackend(std::move(tempBufferBase)); + + BufferCopy bufferCopy; + bufferCopy.buffer = tempBuffer; + bufferCopy.offset = 0; + bufferCopy.bytesPerRow = bytesPerRow; + bufferCopy.rowsPerImage = rowsPerImage; + + // Copy from source texture into tempBuffer + tempBuffer->TrackUsageAndTransitionNow(recordingContext, wgpu::BufferUsage::CopyDst); + RecordBufferTextureCopy(BufferTextureCopyDirection::T2B, + recordingContext->GetCommandList(), bufferCopy, srcCopy, + copySize); + + // Copy from tempBuffer into destination texture + tempBuffer->TrackUsageAndTransitionNow(recordingContext, wgpu::BufferUsage::CopySrc); + RecordBufferTextureCopy(BufferTextureCopyDirection::B2T, + recordingContext->GetCommandList(), bufferCopy, dstCopy, + copySize); + + // Save tempBuffer into recordingContext + recordingContext->AddToTempBuffers(std::move(tempBuffer)); + + return {}; + } + + void RecordNumWorkgroupsForDispatch(ID3D12GraphicsCommandList* commandList, + ComputePipeline* pipeline, + DispatchCmd* dispatch) { + if (!pipeline->UsesNumWorkgroups()) { + return; + } + + PipelineLayout* layout = ToBackend(pipeline->GetLayout()); + commandList->SetComputeRoot32BitConstants(layout->GetNumWorkgroupsParameterIndex(), 3, + dispatch, 0); + } + + // Records the necessary barriers for a synchronization scope using the resource usage + // data pre-computed in the frontend. Also performs lazy initialization if required. + // Returns whether any UAV are used in the synchronization scope. + bool TransitionAndClearForSyncScope(CommandRecordingContext* commandContext, + const SyncScopeResourceUsage& usages) { + std::vector<D3D12_RESOURCE_BARRIER> barriers; + + ID3D12GraphicsCommandList* commandList = commandContext->GetCommandList(); + + wgpu::BufferUsage bufferUsages = wgpu::BufferUsage::None; + + for (size_t i = 0; i < usages.buffers.size(); ++i) { + Buffer* buffer = ToBackend(usages.buffers[i]); + + // TODO(crbug.com/dawn/852): clear storage buffers with + // ClearUnorderedAccessView*(). + buffer->GetDevice()->ConsumedError(buffer->EnsureDataInitialized(commandContext)); + + D3D12_RESOURCE_BARRIER barrier; + if (buffer->TrackUsageAndGetResourceBarrier(commandContext, &barrier, + usages.bufferUsages[i])) { + barriers.push_back(barrier); + } + bufferUsages |= usages.bufferUsages[i]; + } + + wgpu::TextureUsage textureUsages = wgpu::TextureUsage::None; + + for (size_t i = 0; i < usages.textures.size(); ++i) { + Texture* texture = ToBackend(usages.textures[i]); + + // Clear subresources that are not render attachments. Render attachments will be + // cleared in RecordBeginRenderPass by setting the loadop to clear when the texture + // subresource has not been initialized before the render pass. + usages.textureUsages[i].Iterate( + [&](const SubresourceRange& range, wgpu::TextureUsage usage) { + if (usage & ~wgpu::TextureUsage::RenderAttachment) { + texture->EnsureSubresourceContentInitialized(commandContext, range); + } + textureUsages |= usage; + }); + + ToBackend(usages.textures[i]) + ->TrackUsageAndGetResourceBarrierForPass(commandContext, &barriers, + usages.textureUsages[i]); + } + + if (barriers.size()) { + commandList->ResourceBarrier(barriers.size(), barriers.data()); + } + + return (bufferUsages & wgpu::BufferUsage::Storage || + textureUsages & wgpu::TextureUsage::StorageBinding); + } + + } // anonymous namespace + + class BindGroupStateTracker : public BindGroupTrackerBase<false, uint64_t> { + using Base = BindGroupTrackerBase; + + public: + BindGroupStateTracker(Device* device) + : BindGroupTrackerBase(), + mDevice(device), + mViewAllocator(device->GetViewShaderVisibleDescriptorAllocator()), + mSamplerAllocator(device->GetSamplerShaderVisibleDescriptorAllocator()) { + } + + void SetInComputePass(bool inCompute_) { + mInCompute = inCompute_; + } + + MaybeError Apply(CommandRecordingContext* commandContext) { + BeforeApply(); + + ID3D12GraphicsCommandList* commandList = commandContext->GetCommandList(); + UpdateRootSignatureIfNecessary(commandList); + + // Bindgroups are allocated in shader-visible descriptor heaps which are managed by a + // ringbuffer. There can be a single shader-visible descriptor heap of each type bound + // at any given time. This means that when we switch heaps, all other currently bound + // bindgroups must be re-populated. Bindgroups can fail allocation gracefully which is + // the signal to change the bounded heaps. + // Re-populating all bindgroups after the last one fails causes duplicated allocations + // to occur on overflow. + bool didCreateBindGroupViews = true; + bool didCreateBindGroupSamplers = true; + for (BindGroupIndex index : IterateBitSet(mDirtyBindGroups)) { + BindGroup* group = ToBackend(mBindGroups[index]); + didCreateBindGroupViews = group->PopulateViews(mViewAllocator); + didCreateBindGroupSamplers = group->PopulateSamplers(mDevice, mSamplerAllocator); + if (!didCreateBindGroupViews && !didCreateBindGroupSamplers) { + break; + } + } + + if (!didCreateBindGroupViews || !didCreateBindGroupSamplers) { + if (!didCreateBindGroupViews) { + DAWN_TRY(mViewAllocator->AllocateAndSwitchShaderVisibleHeap()); + } + + if (!didCreateBindGroupSamplers) { + DAWN_TRY(mSamplerAllocator->AllocateAndSwitchShaderVisibleHeap()); + } + + mDirtyBindGroupsObjectChangedOrIsDynamic |= mBindGroupLayoutsMask; + mDirtyBindGroups |= mBindGroupLayoutsMask; + + // Must be called before applying the bindgroups. + SetID3D12DescriptorHeaps(commandList); + + for (BindGroupIndex index : IterateBitSet(mBindGroupLayoutsMask)) { + BindGroup* group = ToBackend(mBindGroups[index]); + didCreateBindGroupViews = group->PopulateViews(mViewAllocator); + didCreateBindGroupSamplers = + group->PopulateSamplers(mDevice, mSamplerAllocator); + ASSERT(didCreateBindGroupViews); + ASSERT(didCreateBindGroupSamplers); + } + } + + for (BindGroupIndex index : IterateBitSet(mDirtyBindGroupsObjectChangedOrIsDynamic)) { + BindGroup* group = ToBackend(mBindGroups[index]); + ApplyBindGroup(commandList, ToBackend(mPipelineLayout), index, group, + mDynamicOffsetCounts[index], mDynamicOffsets[index].data()); + } + + AfterApply(); + + return {}; + } + + void SetID3D12DescriptorHeaps(ID3D12GraphicsCommandList* commandList) { + ASSERT(commandList != nullptr); + std::array<ID3D12DescriptorHeap*, 2> descriptorHeaps = { + mViewAllocator->GetShaderVisibleHeap(), mSamplerAllocator->GetShaderVisibleHeap()}; + ASSERT(descriptorHeaps[0] != nullptr); + ASSERT(descriptorHeaps[1] != nullptr); + commandList->SetDescriptorHeaps(descriptorHeaps.size(), descriptorHeaps.data()); + + // Descriptor table state is undefined at the beginning of a command list and after + // descriptor heaps are changed on a command list. Invalidate the root sampler tables to + // reset the root descriptor table for samplers, otherwise the shader cannot access the + // descriptor heaps. + mBoundRootSamplerTables = {}; + } + + private: + void UpdateRootSignatureIfNecessary(ID3D12GraphicsCommandList* commandList) { + if (mLastAppliedPipelineLayout != mPipelineLayout) { + if (mInCompute) { + commandList->SetComputeRootSignature( + ToBackend(mPipelineLayout)->GetRootSignature()); + } else { + commandList->SetGraphicsRootSignature( + ToBackend(mPipelineLayout)->GetRootSignature()); + } + // Invalidate the root sampler tables previously set in the root signature. + mBoundRootSamplerTables = {}; + } + } + + void ApplyBindGroup(ID3D12GraphicsCommandList* commandList, + const PipelineLayout* pipelineLayout, + BindGroupIndex index, + BindGroup* group, + uint32_t dynamicOffsetCountIn, + const uint64_t* dynamicOffsetsIn) { + ityp::span<BindingIndex, const uint64_t> dynamicOffsets( + dynamicOffsetsIn, BindingIndex(dynamicOffsetCountIn)); + ASSERT(dynamicOffsets.size() == group->GetLayout()->GetDynamicBufferCount()); + + // Usually, the application won't set the same offsets many times, + // so always try to apply dynamic offsets even if the offsets stay the same + if (dynamicOffsets.size() != BindingIndex(0)) { + // Update dynamic offsets. + // Dynamic buffer bindings are packed at the beginning of the layout. + for (BindingIndex bindingIndex{0}; bindingIndex < dynamicOffsets.size(); + ++bindingIndex) { + const BindingInfo& bindingInfo = + group->GetLayout()->GetBindingInfo(bindingIndex); + if (bindingInfo.visibility == wgpu::ShaderStage::None) { + // Skip dynamic buffers that are not visible. D3D12 does not have None + // visibility. + continue; + } + + uint32_t parameterIndex = + pipelineLayout->GetDynamicRootParameterIndex(index, bindingIndex); + BufferBinding binding = group->GetBindingAsBufferBinding(bindingIndex); + + // Calculate buffer locations that root descriptors links to. The location + // is (base buffer location + initial offset + dynamic offset) + uint64_t dynamicOffset = dynamicOffsets[bindingIndex]; + uint64_t offset = binding.offset + dynamicOffset; + D3D12_GPU_VIRTUAL_ADDRESS bufferLocation = + ToBackend(binding.buffer)->GetVA() + offset; + + ASSERT(bindingInfo.bindingType == BindingInfoType::Buffer); + switch (bindingInfo.buffer.type) { + case wgpu::BufferBindingType::Uniform: + if (mInCompute) { + commandList->SetComputeRootConstantBufferView(parameterIndex, + bufferLocation); + } else { + commandList->SetGraphicsRootConstantBufferView(parameterIndex, + bufferLocation); + } + break; + case wgpu::BufferBindingType::Storage: + case kInternalStorageBufferBinding: + if (mInCompute) { + commandList->SetComputeRootUnorderedAccessView(parameterIndex, + bufferLocation); + } else { + commandList->SetGraphicsRootUnorderedAccessView(parameterIndex, + bufferLocation); + } + break; + case wgpu::BufferBindingType::ReadOnlyStorage: + if (mInCompute) { + commandList->SetComputeRootShaderResourceView(parameterIndex, + bufferLocation); + } else { + commandList->SetGraphicsRootShaderResourceView(parameterIndex, + bufferLocation); + } + break; + case wgpu::BufferBindingType::Undefined: + UNREACHABLE(); + } + } + } + + // It's not necessary to update descriptor tables if only the dynamic offset changed. + if (!mDirtyBindGroups[index]) { + return; + } + + const uint32_t cbvUavSrvCount = + ToBackend(group->GetLayout())->GetCbvUavSrvDescriptorCount(); + const uint32_t samplerCount = + ToBackend(group->GetLayout())->GetSamplerDescriptorCount(); + + if (cbvUavSrvCount > 0) { + uint32_t parameterIndex = pipelineLayout->GetCbvUavSrvRootParameterIndex(index); + const D3D12_GPU_DESCRIPTOR_HANDLE baseDescriptor = group->GetBaseViewDescriptor(); + if (mInCompute) { + commandList->SetComputeRootDescriptorTable(parameterIndex, baseDescriptor); + } else { + commandList->SetGraphicsRootDescriptorTable(parameterIndex, baseDescriptor); + } + } + + if (samplerCount > 0) { + uint32_t parameterIndex = pipelineLayout->GetSamplerRootParameterIndex(index); + const D3D12_GPU_DESCRIPTOR_HANDLE baseDescriptor = + group->GetBaseSamplerDescriptor(); + // Check if the group requires its sampler table to be set in the pipeline. + // This because sampler heap allocations could be cached and use the same table. + if (mBoundRootSamplerTables[index].ptr != baseDescriptor.ptr) { + if (mInCompute) { + commandList->SetComputeRootDescriptorTable(parameterIndex, baseDescriptor); + } else { + commandList->SetGraphicsRootDescriptorTable(parameterIndex, baseDescriptor); + } + + mBoundRootSamplerTables[index] = baseDescriptor; + } + } + + const auto& dynamicStorageBufferLengths = group->GetDynamicStorageBufferLengths(); + if (dynamicStorageBufferLengths.size() != 0) { + uint32_t parameterIndex = + pipelineLayout->GetDynamicStorageBufferLengthsParameterIndex(); + uint32_t firstRegisterOffset = + pipelineLayout->GetDynamicStorageBufferLengthInfo()[index].firstRegisterOffset; + + if (mInCompute) { + commandList->SetComputeRoot32BitConstants( + parameterIndex, dynamicStorageBufferLengths.size(), + dynamicStorageBufferLengths.data(), firstRegisterOffset); + } else { + commandList->SetGraphicsRoot32BitConstants( + parameterIndex, dynamicStorageBufferLengths.size(), + dynamicStorageBufferLengths.data(), firstRegisterOffset); + } + } + } + + Device* mDevice; + + bool mInCompute = false; + + ityp::array<BindGroupIndex, D3D12_GPU_DESCRIPTOR_HANDLE, kMaxBindGroups> + mBoundRootSamplerTables = {}; + + ShaderVisibleDescriptorAllocator* mViewAllocator; + ShaderVisibleDescriptorAllocator* mSamplerAllocator; + }; + + namespace { + class VertexBufferTracker { + public: + void OnSetVertexBuffer(VertexBufferSlot slot, + Buffer* buffer, + uint64_t offset, + uint64_t size) { + mStartSlot = std::min(mStartSlot, slot); + mEndSlot = std::max(mEndSlot, ityp::Add(slot, VertexBufferSlot(uint8_t(1)))); + + auto* d3d12BufferView = &mD3D12BufferViews[slot]; + d3d12BufferView->BufferLocation = buffer->GetVA() + offset; + d3d12BufferView->SizeInBytes = size; + // The bufferView stride is set based on the vertex state before a draw. + } + + void Apply(ID3D12GraphicsCommandList* commandList, + const RenderPipeline* renderPipeline) { + ASSERT(renderPipeline != nullptr); + + VertexBufferSlot startSlot = mStartSlot; + VertexBufferSlot endSlot = mEndSlot; + + // If the vertex state has changed, we need to update the StrideInBytes + // for the D3D12 buffer views. We also need to extend the dirty range to + // touch all these slots because the stride may have changed. + if (mLastAppliedRenderPipeline != renderPipeline) { + mLastAppliedRenderPipeline = renderPipeline; + + for (VertexBufferSlot slot : + IterateBitSet(renderPipeline->GetVertexBufferSlotsUsed())) { + startSlot = std::min(startSlot, slot); + endSlot = std::max(endSlot, ityp::Add(slot, VertexBufferSlot(uint8_t(1)))); + mD3D12BufferViews[slot].StrideInBytes = + renderPipeline->GetVertexBuffer(slot).arrayStride; + } + } + + if (endSlot <= startSlot) { + return; + } + + // mD3D12BufferViews is kept up to date with the most recent data passed + // to SetVertexBuffer. This makes it correct to only track the start + // and end of the dirty range. When Apply is called, + // we will at worst set non-dirty vertex buffers in duplicate. + commandList->IASetVertexBuffers(static_cast<uint8_t>(startSlot), + static_cast<uint8_t>(ityp::Sub(endSlot, startSlot)), + &mD3D12BufferViews[startSlot]); + + mStartSlot = VertexBufferSlot(kMaxVertexBuffers); + mEndSlot = VertexBufferSlot(uint8_t(0)); + } + + private: + // startSlot and endSlot indicate the range of dirty vertex buffers. + // If there are multiple calls to SetVertexBuffer, the start and end + // represent the union of the dirty ranges (the union may have non-dirty + // data in the middle of the range). + const RenderPipeline* mLastAppliedRenderPipeline = nullptr; + VertexBufferSlot mStartSlot{kMaxVertexBuffers}; + VertexBufferSlot mEndSlot{uint8_t(0)}; + ityp::array<VertexBufferSlot, D3D12_VERTEX_BUFFER_VIEW, kMaxVertexBuffers> + mD3D12BufferViews = {}; + }; + + void ResolveMultisampledRenderPass(CommandRecordingContext* commandContext, + BeginRenderPassCmd* renderPass) { + ASSERT(renderPass != nullptr); + + for (ColorAttachmentIndex i : + IterateBitSet(renderPass->attachmentState->GetColorAttachmentsMask())) { + TextureViewBase* resolveTarget = + renderPass->colorAttachments[i].resolveTarget.Get(); + if (resolveTarget == nullptr) { + continue; + } + + TextureViewBase* colorView = renderPass->colorAttachments[i].view.Get(); + Texture* colorTexture = ToBackend(colorView->GetTexture()); + Texture* resolveTexture = ToBackend(resolveTarget->GetTexture()); + + // Transition the usages of the color attachment and resolve target. + colorTexture->TrackUsageAndTransitionNow(commandContext, + D3D12_RESOURCE_STATE_RESOLVE_SOURCE, + colorView->GetSubresourceRange()); + resolveTexture->TrackUsageAndTransitionNow(commandContext, + D3D12_RESOURCE_STATE_RESOLVE_DEST, + resolveTarget->GetSubresourceRange()); + + // Do MSAA resolve with ResolveSubResource(). + ID3D12Resource* colorTextureHandle = colorTexture->GetD3D12Resource(); + ID3D12Resource* resolveTextureHandle = resolveTexture->GetD3D12Resource(); + const uint32_t resolveTextureSubresourceIndex = resolveTexture->GetSubresourceIndex( + resolveTarget->GetBaseMipLevel(), resolveTarget->GetBaseArrayLayer(), + Aspect::Color); + constexpr uint32_t kColorTextureSubresourceIndex = 0; + commandContext->GetCommandList()->ResolveSubresource( + resolveTextureHandle, resolveTextureSubresourceIndex, colorTextureHandle, + kColorTextureSubresourceIndex, colorTexture->GetD3D12Format()); + } + } + + } // anonymous namespace + + // static + Ref<CommandBuffer> CommandBuffer::Create(CommandEncoder* encoder, + const CommandBufferDescriptor* descriptor) { + return AcquireRef(new CommandBuffer(encoder, descriptor)); + } + + CommandBuffer::CommandBuffer(CommandEncoder* encoder, const CommandBufferDescriptor* descriptor) + : CommandBufferBase(encoder, descriptor) { + } + + MaybeError CommandBuffer::RecordCommands(CommandRecordingContext* commandContext) { + Device* device = ToBackend(GetDevice()); + BindGroupStateTracker bindingTracker(device); + + ID3D12GraphicsCommandList* commandList = commandContext->GetCommandList(); + + // Make sure we use the correct descriptors for this command list. Could be done once per + // actual command list but here is ok because there should be few command buffers. + bindingTracker.SetID3D12DescriptorHeaps(commandList); + + size_t nextComputePassNumber = 0; + size_t nextRenderPassNumber = 0; + + Command type; + while (mCommands.NextCommandId(&type)) { + switch (type) { + case Command::BeginComputePass: { + mCommands.NextCommand<BeginComputePassCmd>(); + + bindingTracker.SetInComputePass(true); + DAWN_TRY(RecordComputePass( + commandContext, &bindingTracker, + GetResourceUsages().computePasses[nextComputePassNumber])); + + nextComputePassNumber++; + break; + } + + case Command::BeginRenderPass: { + BeginRenderPassCmd* beginRenderPassCmd = + mCommands.NextCommand<BeginRenderPassCmd>(); + + const bool passHasUAV = TransitionAndClearForSyncScope( + commandContext, GetResourceUsages().renderPasses[nextRenderPassNumber]); + bindingTracker.SetInComputePass(false); + + LazyClearRenderPassAttachments(beginRenderPassCmd); + DAWN_TRY(RecordRenderPass(commandContext, &bindingTracker, beginRenderPassCmd, + passHasUAV)); + + nextRenderPassNumber++; + break; + } + + case Command::CopyBufferToBuffer: { + CopyBufferToBufferCmd* copy = mCommands.NextCommand<CopyBufferToBufferCmd>(); + if (copy->size == 0) { + // Skip no-op copies. + break; + } + Buffer* srcBuffer = ToBackend(copy->source.Get()); + Buffer* dstBuffer = ToBackend(copy->destination.Get()); + + DAWN_TRY(srcBuffer->EnsureDataInitialized(commandContext)); + bool cleared; + DAWN_TRY_ASSIGN(cleared, + dstBuffer->EnsureDataInitializedAsDestination( + commandContext, copy->destinationOffset, copy->size)); + DAWN_UNUSED(cleared); + + srcBuffer->TrackUsageAndTransitionNow(commandContext, + wgpu::BufferUsage::CopySrc); + dstBuffer->TrackUsageAndTransitionNow(commandContext, + wgpu::BufferUsage::CopyDst); + + commandList->CopyBufferRegion( + dstBuffer->GetD3D12Resource(), copy->destinationOffset, + srcBuffer->GetD3D12Resource(), copy->sourceOffset, copy->size); + break; + } + + case Command::CopyBufferToTexture: { + CopyBufferToTextureCmd* copy = mCommands.NextCommand<CopyBufferToTextureCmd>(); + if (copy->copySize.width == 0 || copy->copySize.height == 0 || + copy->copySize.depthOrArrayLayers == 0) { + // Skip no-op copies. + continue; + } + Buffer* buffer = ToBackend(copy->source.buffer.Get()); + Texture* texture = ToBackend(copy->destination.texture.Get()); + + DAWN_TRY(buffer->EnsureDataInitialized(commandContext)); + + SubresourceRange subresources = + GetSubresourcesAffectedByCopy(copy->destination, copy->copySize); + + if (IsCompleteSubresourceCopiedTo(texture, copy->copySize, + copy->destination.mipLevel)) { + texture->SetIsSubresourceContentInitialized(true, subresources); + } else { + texture->EnsureSubresourceContentInitialized(commandContext, subresources); + } + + buffer->TrackUsageAndTransitionNow(commandContext, wgpu::BufferUsage::CopySrc); + texture->TrackUsageAndTransitionNow(commandContext, wgpu::TextureUsage::CopyDst, + subresources); + + RecordBufferTextureCopy(BufferTextureCopyDirection::B2T, commandList, + copy->source, copy->destination, copy->copySize); + + break; + } + + case Command::CopyTextureToBuffer: { + CopyTextureToBufferCmd* copy = mCommands.NextCommand<CopyTextureToBufferCmd>(); + if (copy->copySize.width == 0 || copy->copySize.height == 0 || + copy->copySize.depthOrArrayLayers == 0) { + // Skip no-op copies. + continue; + } + Texture* texture = ToBackend(copy->source.texture.Get()); + Buffer* buffer = ToBackend(copy->destination.buffer.Get()); + + DAWN_TRY(buffer->EnsureDataInitializedAsDestination(commandContext, copy)); + + SubresourceRange subresources = + GetSubresourcesAffectedByCopy(copy->source, copy->copySize); + + texture->EnsureSubresourceContentInitialized(commandContext, subresources); + + texture->TrackUsageAndTransitionNow(commandContext, wgpu::TextureUsage::CopySrc, + subresources); + buffer->TrackUsageAndTransitionNow(commandContext, wgpu::BufferUsage::CopyDst); + + RecordBufferTextureCopy(BufferTextureCopyDirection::T2B, commandList, + copy->destination, copy->source, copy->copySize); + + break; + } + + case Command::CopyTextureToTexture: { + CopyTextureToTextureCmd* copy = + mCommands.NextCommand<CopyTextureToTextureCmd>(); + if (copy->copySize.width == 0 || copy->copySize.height == 0 || + copy->copySize.depthOrArrayLayers == 0) { + // Skip no-op copies. + continue; + } + Texture* source = ToBackend(copy->source.texture.Get()); + Texture* destination = ToBackend(copy->destination.texture.Get()); + + SubresourceRange srcRange = + GetSubresourcesAffectedByCopy(copy->source, copy->copySize); + SubresourceRange dstRange = + GetSubresourcesAffectedByCopy(copy->destination, copy->copySize); + + source->EnsureSubresourceContentInitialized(commandContext, srcRange); + if (IsCompleteSubresourceCopiedTo(destination, copy->copySize, + copy->destination.mipLevel)) { + destination->SetIsSubresourceContentInitialized(true, dstRange); + } else { + destination->EnsureSubresourceContentInitialized(commandContext, dstRange); + } + + if (copy->source.texture.Get() == copy->destination.texture.Get() && + copy->source.mipLevel == copy->destination.mipLevel) { + // When there are overlapped subresources, the layout of the overlapped + // subresources should all be COMMON instead of what we set now. Currently + // it is not allowed to copy with overlapped subresources, but we still + // add the ASSERT here as a reminder for this possible misuse. + ASSERT(!IsRangeOverlapped(copy->source.origin.z, copy->destination.origin.z, + copy->copySize.depthOrArrayLayers)); + } + source->TrackUsageAndTransitionNow(commandContext, wgpu::TextureUsage::CopySrc, + srcRange); + destination->TrackUsageAndTransitionNow(commandContext, + wgpu::TextureUsage::CopyDst, dstRange); + + ASSERT(srcRange.aspects == dstRange.aspects); + if (ShouldCopyUsingTemporaryBuffer(GetDevice(), copy->source, + copy->destination)) { + DAWN_TRY(RecordCopyTextureWithTemporaryBuffer( + commandContext, copy->source, copy->destination, copy->copySize)); + break; + } + + if (CanUseCopyResource(copy->source, copy->destination, copy->copySize)) { + commandList->CopyResource(destination->GetD3D12Resource(), + source->GetD3D12Resource()); + } else if (source->GetDimension() == wgpu::TextureDimension::e3D && + destination->GetDimension() == wgpu::TextureDimension::e3D) { + for (Aspect aspect : IterateEnumMask(srcRange.aspects)) { + D3D12_TEXTURE_COPY_LOCATION srcLocation = + ComputeTextureCopyLocationForTexture(source, copy->source.mipLevel, + 0, aspect); + D3D12_TEXTURE_COPY_LOCATION dstLocation = + ComputeTextureCopyLocationForTexture( + destination, copy->destination.mipLevel, 0, aspect); + + D3D12_BOX sourceRegion = ComputeD3D12BoxFromOffsetAndSize( + copy->source.origin, copy->copySize); + + commandList->CopyTextureRegion(&dstLocation, copy->destination.origin.x, + copy->destination.origin.y, + copy->destination.origin.z, &srcLocation, + &sourceRegion); + } + } else { + const dawn::native::Extent3D copyExtentOneSlice = { + copy->copySize.width, copy->copySize.height, 1u}; + + for (Aspect aspect : IterateEnumMask(srcRange.aspects)) { + for (uint32_t z = 0; z < copy->copySize.depthOrArrayLayers; ++z) { + uint32_t sourceLayer = 0; + uint32_t sourceZ = 0; + switch (source->GetDimension()) { + case wgpu::TextureDimension::e1D: + ASSERT(copy->source.origin.z == 0); + break; + case wgpu::TextureDimension::e2D: + sourceLayer = copy->source.origin.z + z; + break; + case wgpu::TextureDimension::e3D: + sourceZ = copy->source.origin.z + z; + break; + } + + uint32_t destinationLayer = 0; + uint32_t destinationZ = 0; + switch (destination->GetDimension()) { + case wgpu::TextureDimension::e1D: + ASSERT(copy->destination.origin.z == 0); + break; + case wgpu::TextureDimension::e2D: + destinationLayer = copy->destination.origin.z + z; + break; + case wgpu::TextureDimension::e3D: + destinationZ = copy->destination.origin.z + z; + break; + } + D3D12_TEXTURE_COPY_LOCATION srcLocation = + ComputeTextureCopyLocationForTexture( + source, copy->source.mipLevel, sourceLayer, aspect); + + D3D12_TEXTURE_COPY_LOCATION dstLocation = + ComputeTextureCopyLocationForTexture(destination, + copy->destination.mipLevel, + destinationLayer, aspect); + + Origin3D sourceOriginInSubresource = copy->source.origin; + sourceOriginInSubresource.z = sourceZ; + D3D12_BOX sourceRegion = ComputeD3D12BoxFromOffsetAndSize( + sourceOriginInSubresource, copyExtentOneSlice); + + commandList->CopyTextureRegion( + &dstLocation, copy->destination.origin.x, + copy->destination.origin.y, destinationZ, &srcLocation, + &sourceRegion); + } + } + } + break; + } + + case Command::ClearBuffer: { + ClearBufferCmd* cmd = mCommands.NextCommand<ClearBufferCmd>(); + if (cmd->size == 0) { + // Skip no-op fills. + break; + } + Buffer* dstBuffer = ToBackend(cmd->buffer.Get()); + + bool clearedToZero; + DAWN_TRY_ASSIGN(clearedToZero, dstBuffer->EnsureDataInitializedAsDestination( + commandContext, cmd->offset, cmd->size)); + + if (!clearedToZero) { + DAWN_TRY(device->ClearBufferToZero(commandContext, cmd->buffer.Get(), + cmd->offset, cmd->size)); + } + + break; + } + + case Command::ResolveQuerySet: { + ResolveQuerySetCmd* cmd = mCommands.NextCommand<ResolveQuerySetCmd>(); + QuerySet* querySet = ToBackend(cmd->querySet.Get()); + uint32_t firstQuery = cmd->firstQuery; + uint32_t queryCount = cmd->queryCount; + Buffer* destination = ToBackend(cmd->destination.Get()); + uint64_t destinationOffset = cmd->destinationOffset; + + bool cleared; + DAWN_TRY_ASSIGN(cleared, destination->EnsureDataInitializedAsDestination( + commandContext, destinationOffset, + queryCount * sizeof(uint64_t))); + DAWN_UNUSED(cleared); + + // Resolving unavailable queries is undefined behaviour on D3D12, we only can + // resolve the available part of sparse queries. In order to resolve the + // unavailables as 0s, we need to clear the resolving region of the destination + // buffer to 0s. + auto startIt = querySet->GetQueryAvailability().begin() + firstQuery; + auto endIt = querySet->GetQueryAvailability().begin() + firstQuery + queryCount; + bool hasUnavailableQueries = std::find(startIt, endIt, false) != endIt; + if (hasUnavailableQueries) { + DAWN_TRY(device->ClearBufferToZero(commandContext, destination, + destinationOffset, + queryCount * sizeof(uint64_t))); + } + + destination->TrackUsageAndTransitionNow(commandContext, + wgpu::BufferUsage::QueryResolve); + + RecordResolveQuerySetCmd(commandList, device, querySet, firstQuery, queryCount, + destination, destinationOffset); + + break; + } + + case Command::WriteTimestamp: { + WriteTimestampCmd* cmd = mCommands.NextCommand<WriteTimestampCmd>(); + + RecordWriteTimestampCmd(commandList, cmd); + break; + } + + case Command::InsertDebugMarker: { + InsertDebugMarkerCmd* cmd = mCommands.NextCommand<InsertDebugMarkerCmd>(); + const char* label = mCommands.NextData<char>(cmd->length + 1); + + if (ToBackend(GetDevice())->GetFunctions()->IsPIXEventRuntimeLoaded()) { + // PIX color is 1 byte per channel in ARGB format + constexpr uint64_t kPIXBlackColor = 0xff000000; + ToBackend(GetDevice()) + ->GetFunctions() + ->pixSetMarkerOnCommandList(commandList, kPIXBlackColor, label); + } + break; + } + + case Command::PopDebugGroup: { + mCommands.NextCommand<PopDebugGroupCmd>(); + + if (ToBackend(GetDevice())->GetFunctions()->IsPIXEventRuntimeLoaded()) { + ToBackend(GetDevice()) + ->GetFunctions() + ->pixEndEventOnCommandList(commandList); + } + break; + } + + case Command::PushDebugGroup: { + PushDebugGroupCmd* cmd = mCommands.NextCommand<PushDebugGroupCmd>(); + const char* label = mCommands.NextData<char>(cmd->length + 1); + + if (ToBackend(GetDevice())->GetFunctions()->IsPIXEventRuntimeLoaded()) { + // PIX color is 1 byte per channel in ARGB format + constexpr uint64_t kPIXBlackColor = 0xff000000; + ToBackend(GetDevice()) + ->GetFunctions() + ->pixBeginEventOnCommandList(commandList, kPIXBlackColor, label); + } + break; + } + + case Command::WriteBuffer: { + WriteBufferCmd* write = mCommands.NextCommand<WriteBufferCmd>(); + const uint64_t offset = write->offset; + const uint64_t size = write->size; + if (size == 0) { + continue; + } + + Buffer* dstBuffer = ToBackend(write->buffer.Get()); + uint8_t* data = mCommands.NextData<uint8_t>(size); + Device* device = ToBackend(GetDevice()); + + UploadHandle uploadHandle; + DAWN_TRY_ASSIGN(uploadHandle, device->GetDynamicUploader()->Allocate( + size, device->GetPendingCommandSerial(), + kCopyBufferToBufferOffsetAlignment)); + ASSERT(uploadHandle.mappedBuffer != nullptr); + memcpy(uploadHandle.mappedBuffer, data, size); + + bool cleared; + DAWN_TRY_ASSIGN(cleared, dstBuffer->EnsureDataInitializedAsDestination( + commandContext, offset, size)); + DAWN_UNUSED(cleared); + dstBuffer->TrackUsageAndTransitionNow(commandContext, + wgpu::BufferUsage::CopyDst); + commandList->CopyBufferRegion( + dstBuffer->GetD3D12Resource(), offset, + ToBackend(uploadHandle.stagingBuffer)->GetResource(), + uploadHandle.startOffset, size); + break; + } + + default: + UNREACHABLE(); + } + } + + return {}; + } + + MaybeError CommandBuffer::RecordComputePass(CommandRecordingContext* commandContext, + BindGroupStateTracker* bindingTracker, + const ComputePassResourceUsage& resourceUsages) { + uint64_t currentDispatch = 0; + ID3D12GraphicsCommandList* commandList = commandContext->GetCommandList(); + + Command type; + ComputePipeline* lastPipeline = nullptr; + while (mCommands.NextCommandId(&type)) { + switch (type) { + case Command::Dispatch: { + DispatchCmd* dispatch = mCommands.NextCommand<DispatchCmd>(); + + // Skip noop dispatches, it can cause D3D12 warning from validation layers and + // leads to device lost. + if (dispatch->x == 0 || dispatch->y == 0 || dispatch->z == 0) { + break; + } + + TransitionAndClearForSyncScope(commandContext, + resourceUsages.dispatchUsages[currentDispatch]); + DAWN_TRY(bindingTracker->Apply(commandContext)); + + RecordNumWorkgroupsForDispatch(commandList, lastPipeline, dispatch); + commandList->Dispatch(dispatch->x, dispatch->y, dispatch->z); + currentDispatch++; + break; + } + + case Command::DispatchIndirect: { + DispatchIndirectCmd* dispatch = mCommands.NextCommand<DispatchIndirectCmd>(); + + TransitionAndClearForSyncScope(commandContext, + resourceUsages.dispatchUsages[currentDispatch]); + DAWN_TRY(bindingTracker->Apply(commandContext)); + + ComPtr<ID3D12CommandSignature> signature = + lastPipeline->GetDispatchIndirectCommandSignature(); + commandList->ExecuteIndirect( + signature.Get(), 1, ToBackend(dispatch->indirectBuffer)->GetD3D12Resource(), + dispatch->indirectOffset, nullptr, 0); + currentDispatch++; + break; + } + + case Command::EndComputePass: { + mCommands.NextCommand<EndComputePassCmd>(); + return {}; + } + + case Command::SetComputePipeline: { + SetComputePipelineCmd* cmd = mCommands.NextCommand<SetComputePipelineCmd>(); + ComputePipeline* pipeline = ToBackend(cmd->pipeline).Get(); + + commandList->SetPipelineState(pipeline->GetPipelineState()); + + bindingTracker->OnSetPipeline(pipeline); + lastPipeline = pipeline; + break; + } + + case Command::SetBindGroup: { + SetBindGroupCmd* cmd = mCommands.NextCommand<SetBindGroupCmd>(); + BindGroup* group = ToBackend(cmd->group.Get()); + uint32_t* dynamicOffsets = nullptr; + + if (cmd->dynamicOffsetCount > 0) { + dynamicOffsets = mCommands.NextData<uint32_t>(cmd->dynamicOffsetCount); + } + + bindingTracker->OnSetBindGroup(cmd->index, group, cmd->dynamicOffsetCount, + dynamicOffsets); + break; + } + + case Command::InsertDebugMarker: { + InsertDebugMarkerCmd* cmd = mCommands.NextCommand<InsertDebugMarkerCmd>(); + const char* label = mCommands.NextData<char>(cmd->length + 1); + + if (ToBackend(GetDevice())->GetFunctions()->IsPIXEventRuntimeLoaded()) { + // PIX color is 1 byte per channel in ARGB format + constexpr uint64_t kPIXBlackColor = 0xff000000; + ToBackend(GetDevice()) + ->GetFunctions() + ->pixSetMarkerOnCommandList(commandList, kPIXBlackColor, label); + } + break; + } + + case Command::PopDebugGroup: { + mCommands.NextCommand<PopDebugGroupCmd>(); + + if (ToBackend(GetDevice())->GetFunctions()->IsPIXEventRuntimeLoaded()) { + ToBackend(GetDevice()) + ->GetFunctions() + ->pixEndEventOnCommandList(commandList); + } + break; + } + + case Command::PushDebugGroup: { + PushDebugGroupCmd* cmd = mCommands.NextCommand<PushDebugGroupCmd>(); + const char* label = mCommands.NextData<char>(cmd->length + 1); + + if (ToBackend(GetDevice())->GetFunctions()->IsPIXEventRuntimeLoaded()) { + // PIX color is 1 byte per channel in ARGB format + constexpr uint64_t kPIXBlackColor = 0xff000000; + ToBackend(GetDevice()) + ->GetFunctions() + ->pixBeginEventOnCommandList(commandList, kPIXBlackColor, label); + } + break; + } + + case Command::WriteTimestamp: { + WriteTimestampCmd* cmd = mCommands.NextCommand<WriteTimestampCmd>(); + + RecordWriteTimestampCmd(commandList, cmd); + break; + } + + default: + UNREACHABLE(); + } + } + + return {}; + } + + MaybeError CommandBuffer::SetupRenderPass(CommandRecordingContext* commandContext, + BeginRenderPassCmd* renderPass, + RenderPassBuilder* renderPassBuilder) { + Device* device = ToBackend(GetDevice()); + + CPUDescriptorHeapAllocation nullRTVAllocation; + D3D12_CPU_DESCRIPTOR_HANDLE nullRTV; + + const auto& colorAttachmentsMaskBitSet = + renderPass->attachmentState->GetColorAttachmentsMask(); + for (ColorAttachmentIndex i(uint8_t(0)); i < ColorAttachmentIndex(kMaxColorAttachments); + i++) { + if (colorAttachmentsMaskBitSet.test(i)) { + RenderPassColorAttachmentInfo& attachmentInfo = renderPass->colorAttachments[i]; + TextureView* view = ToBackend(attachmentInfo.view.Get()); + + // Set view attachment. + CPUDescriptorHeapAllocation rtvAllocation; + DAWN_TRY_ASSIGN( + rtvAllocation, + device->GetRenderTargetViewAllocator()->AllocateTransientCPUDescriptors()); + + const D3D12_RENDER_TARGET_VIEW_DESC viewDesc = view->GetRTVDescriptor(); + const D3D12_CPU_DESCRIPTOR_HANDLE baseDescriptor = + rtvAllocation.GetBaseDescriptor(); + + device->GetD3D12Device()->CreateRenderTargetView( + ToBackend(view->GetTexture())->GetD3D12Resource(), &viewDesc, baseDescriptor); + + renderPassBuilder->SetRenderTargetView(i, baseDescriptor, false); + + // Set color load operation. + renderPassBuilder->SetRenderTargetBeginningAccess( + i, attachmentInfo.loadOp, attachmentInfo.clearColor, view->GetD3D12Format()); + + // Set color store operation. + if (attachmentInfo.resolveTarget != nullptr) { + TextureView* resolveDestinationView = + ToBackend(attachmentInfo.resolveTarget.Get()); + Texture* resolveDestinationTexture = + ToBackend(resolveDestinationView->GetTexture()); + + resolveDestinationTexture->TrackUsageAndTransitionNow( + commandContext, D3D12_RESOURCE_STATE_RESOLVE_DEST, + resolveDestinationView->GetSubresourceRange()); + + renderPassBuilder->SetRenderTargetEndingAccessResolve( + i, attachmentInfo.storeOp, view, resolveDestinationView); + } else { + renderPassBuilder->SetRenderTargetEndingAccess(i, attachmentInfo.storeOp); + } + } else { + if (!nullRTVAllocation.IsValid()) { + DAWN_TRY_ASSIGN( + nullRTVAllocation, + device->GetRenderTargetViewAllocator()->AllocateTransientCPUDescriptors()); + nullRTV = nullRTVAllocation.GetBaseDescriptor(); + D3D12_RENDER_TARGET_VIEW_DESC nullRTVDesc; + nullRTVDesc.Format = DXGI_FORMAT_R8G8B8A8_UNORM; + nullRTVDesc.ViewDimension = D3D12_RTV_DIMENSION_TEXTURE2D; + nullRTVDesc.Texture2D.MipSlice = 0; + nullRTVDesc.Texture2D.PlaneSlice = 0; + device->GetD3D12Device()->CreateRenderTargetView(nullptr, &nullRTVDesc, + nullRTV); + } + + renderPassBuilder->SetRenderTargetView(i, nullRTV, true); + } + } + + if (renderPass->attachmentState->HasDepthStencilAttachment()) { + RenderPassDepthStencilAttachmentInfo& attachmentInfo = + renderPass->depthStencilAttachment; + TextureView* view = ToBackend(renderPass->depthStencilAttachment.view.Get()); + + // Set depth attachment. + CPUDescriptorHeapAllocation dsvAllocation; + DAWN_TRY_ASSIGN( + dsvAllocation, + device->GetDepthStencilViewAllocator()->AllocateTransientCPUDescriptors()); + + const D3D12_DEPTH_STENCIL_VIEW_DESC viewDesc = view->GetDSVDescriptor( + attachmentInfo.depthReadOnly, attachmentInfo.stencilReadOnly); + const D3D12_CPU_DESCRIPTOR_HANDLE baseDescriptor = dsvAllocation.GetBaseDescriptor(); + + device->GetD3D12Device()->CreateDepthStencilView( + ToBackend(view->GetTexture())->GetD3D12Resource(), &viewDesc, baseDescriptor); + + renderPassBuilder->SetDepthStencilView(baseDescriptor); + + const bool hasDepth = view->GetTexture()->GetFormat().HasDepth(); + const bool hasStencil = view->GetTexture()->GetFormat().HasStencil(); + + // Set depth/stencil load operations. + if (hasDepth) { + renderPassBuilder->SetDepthAccess( + attachmentInfo.depthLoadOp, attachmentInfo.depthStoreOp, + attachmentInfo.clearDepth, view->GetD3D12Format()); + } else { + renderPassBuilder->SetDepthNoAccess(); + } + + if (hasStencil) { + renderPassBuilder->SetStencilAccess( + attachmentInfo.stencilLoadOp, attachmentInfo.stencilStoreOp, + attachmentInfo.clearStencil, view->GetD3D12Format()); + } else { + renderPassBuilder->SetStencilNoAccess(); + } + + } else { + renderPassBuilder->SetDepthStencilNoAccess(); + } + + return {}; + } + + void CommandBuffer::EmulateBeginRenderPass(CommandRecordingContext* commandContext, + const RenderPassBuilder* renderPassBuilder) const { + ID3D12GraphicsCommandList* commandList = commandContext->GetCommandList(); + + // Clear framebuffer attachments as needed. + { + for (const auto& attachment : + renderPassBuilder->GetRenderPassRenderTargetDescriptors()) { + // Load op - color + if (attachment.cpuDescriptor.ptr != 0 && + attachment.BeginningAccess.Type == + D3D12_RENDER_PASS_BEGINNING_ACCESS_TYPE_CLEAR) { + commandList->ClearRenderTargetView( + attachment.cpuDescriptor, attachment.BeginningAccess.Clear.ClearValue.Color, + 0, nullptr); + } + } + + if (renderPassBuilder->HasDepthOrStencil()) { + D3D12_CLEAR_FLAGS clearFlags = {}; + float depthClear = 0.0f; + uint8_t stencilClear = 0u; + + if (renderPassBuilder->GetRenderPassDepthStencilDescriptor() + ->DepthBeginningAccess.Type == + D3D12_RENDER_PASS_BEGINNING_ACCESS_TYPE_CLEAR) { + clearFlags |= D3D12_CLEAR_FLAG_DEPTH; + depthClear = renderPassBuilder->GetRenderPassDepthStencilDescriptor() + ->DepthBeginningAccess.Clear.ClearValue.DepthStencil.Depth; + } + if (renderPassBuilder->GetRenderPassDepthStencilDescriptor() + ->StencilBeginningAccess.Type == + D3D12_RENDER_PASS_BEGINNING_ACCESS_TYPE_CLEAR) { + clearFlags |= D3D12_CLEAR_FLAG_STENCIL; + stencilClear = + renderPassBuilder->GetRenderPassDepthStencilDescriptor() + ->StencilBeginningAccess.Clear.ClearValue.DepthStencil.Stencil; + } + + if (clearFlags) { + commandList->ClearDepthStencilView( + renderPassBuilder->GetRenderPassDepthStencilDescriptor()->cpuDescriptor, + clearFlags, depthClear, stencilClear, 0, nullptr); + } + } + } + + commandList->OMSetRenderTargets( + static_cast<uint8_t>(renderPassBuilder->GetHighestColorAttachmentIndexPlusOne()), + renderPassBuilder->GetRenderTargetViews(), FALSE, + renderPassBuilder->HasDepthOrStencil() + ? &renderPassBuilder->GetRenderPassDepthStencilDescriptor()->cpuDescriptor + : nullptr); + } + + MaybeError CommandBuffer::RecordRenderPass(CommandRecordingContext* commandContext, + BindGroupStateTracker* bindingTracker, + BeginRenderPassCmd* renderPass, + const bool passHasUAV) { + Device* device = ToBackend(GetDevice()); + const bool useRenderPass = device->IsToggleEnabled(Toggle::UseD3D12RenderPass); + + // renderPassBuilder must be scoped to RecordRenderPass because any underlying + // D3D12_RENDER_PASS_ENDING_ACCESS_RESOLVE_SUBRESOURCE_PARAMETERS structs must remain + // valid until after EndRenderPass() has been called. + RenderPassBuilder renderPassBuilder(passHasUAV); + + DAWN_TRY(SetupRenderPass(commandContext, renderPass, &renderPassBuilder)); + + // Use D3D12's native render pass API if it's available, otherwise emulate the + // beginning and ending access operations. + if (useRenderPass) { + commandContext->GetCommandList4()->BeginRenderPass( + static_cast<uint8_t>(renderPassBuilder.GetHighestColorAttachmentIndexPlusOne()), + renderPassBuilder.GetRenderPassRenderTargetDescriptors().data(), + renderPassBuilder.HasDepthOrStencil() + ? renderPassBuilder.GetRenderPassDepthStencilDescriptor() + : nullptr, + renderPassBuilder.GetRenderPassFlags()); + } else { + EmulateBeginRenderPass(commandContext, &renderPassBuilder); + } + + ID3D12GraphicsCommandList* commandList = commandContext->GetCommandList(); + + // Set up default dynamic state + { + uint32_t width = renderPass->width; + uint32_t height = renderPass->height; + D3D12_VIEWPORT viewport = { + 0.f, 0.f, static_cast<float>(width), static_cast<float>(height), 0.f, 1.f}; + D3D12_RECT scissorRect = {0, 0, static_cast<long>(width), static_cast<long>(height)}; + commandList->RSSetViewports(1, &viewport); + commandList->RSSetScissorRects(1, &scissorRect); + + static constexpr std::array<float, 4> defaultBlendFactor = {0, 0, 0, 0}; + commandList->OMSetBlendFactor(&defaultBlendFactor[0]); + + commandList->OMSetStencilRef(0); + } + + RenderPipeline* lastPipeline = nullptr; + VertexBufferTracker vertexBufferTracker = {}; + + auto EncodeRenderBundleCommand = [&](CommandIterator* iter, Command type) -> MaybeError { + switch (type) { + case Command::Draw: { + DrawCmd* draw = iter->NextCommand<DrawCmd>(); + + DAWN_TRY(bindingTracker->Apply(commandContext)); + vertexBufferTracker.Apply(commandList, lastPipeline); + RecordFirstIndexOffset(commandList, lastPipeline, draw->firstVertex, + draw->firstInstance); + commandList->DrawInstanced(draw->vertexCount, draw->instanceCount, + draw->firstVertex, draw->firstInstance); + break; + } + + case Command::DrawIndexed: { + DrawIndexedCmd* draw = iter->NextCommand<DrawIndexedCmd>(); + + DAWN_TRY(bindingTracker->Apply(commandContext)); + vertexBufferTracker.Apply(commandList, lastPipeline); + RecordFirstIndexOffset(commandList, lastPipeline, draw->baseVertex, + draw->firstInstance); + commandList->DrawIndexedInstanced(draw->indexCount, draw->instanceCount, + draw->firstIndex, draw->baseVertex, + draw->firstInstance); + break; + } + + case Command::DrawIndirect: { + DrawIndirectCmd* draw = iter->NextCommand<DrawIndirectCmd>(); + + DAWN_TRY(bindingTracker->Apply(commandContext)); + vertexBufferTracker.Apply(commandList, lastPipeline); + + // TODO(dawn:548): remove this once builtins are emulated for indirect draws. + // Zero the index offset values to avoid reusing values from the previous draw + RecordFirstIndexOffset(commandList, lastPipeline, 0, 0); + + Buffer* buffer = ToBackend(draw->indirectBuffer.Get()); + ComPtr<ID3D12CommandSignature> signature = + ToBackend(GetDevice())->GetDrawIndirectSignature(); + commandList->ExecuteIndirect(signature.Get(), 1, buffer->GetD3D12Resource(), + draw->indirectOffset, nullptr, 0); + break; + } + + case Command::DrawIndexedIndirect: { + DrawIndexedIndirectCmd* draw = iter->NextCommand<DrawIndexedIndirectCmd>(); + + DAWN_TRY(bindingTracker->Apply(commandContext)); + vertexBufferTracker.Apply(commandList, lastPipeline); + + // TODO(dawn:548): remove this once builtins are emulated for indirect draws. + // Zero the index offset values to avoid reusing values from the previous draw + RecordFirstIndexOffset(commandList, lastPipeline, 0, 0); + + Buffer* buffer = ToBackend(draw->indirectBuffer.Get()); + ASSERT(buffer != nullptr); + + ComPtr<ID3D12CommandSignature> signature = + ToBackend(GetDevice())->GetDrawIndexedIndirectSignature(); + commandList->ExecuteIndirect(signature.Get(), 1, buffer->GetD3D12Resource(), + draw->indirectOffset, nullptr, 0); + break; + } + + case Command::InsertDebugMarker: { + InsertDebugMarkerCmd* cmd = iter->NextCommand<InsertDebugMarkerCmd>(); + const char* label = iter->NextData<char>(cmd->length + 1); + + if (ToBackend(GetDevice())->GetFunctions()->IsPIXEventRuntimeLoaded()) { + // PIX color is 1 byte per channel in ARGB format + constexpr uint64_t kPIXBlackColor = 0xff000000; + ToBackend(GetDevice()) + ->GetFunctions() + ->pixSetMarkerOnCommandList(commandList, kPIXBlackColor, label); + } + break; + } + + case Command::PopDebugGroup: { + iter->NextCommand<PopDebugGroupCmd>(); + + if (ToBackend(GetDevice())->GetFunctions()->IsPIXEventRuntimeLoaded()) { + ToBackend(GetDevice()) + ->GetFunctions() + ->pixEndEventOnCommandList(commandList); + } + break; + } + + case Command::PushDebugGroup: { + PushDebugGroupCmd* cmd = iter->NextCommand<PushDebugGroupCmd>(); + const char* label = iter->NextData<char>(cmd->length + 1); + + if (ToBackend(GetDevice())->GetFunctions()->IsPIXEventRuntimeLoaded()) { + // PIX color is 1 byte per channel in ARGB format + constexpr uint64_t kPIXBlackColor = 0xff000000; + ToBackend(GetDevice()) + ->GetFunctions() + ->pixBeginEventOnCommandList(commandList, kPIXBlackColor, label); + } + break; + } + + case Command::SetRenderPipeline: { + SetRenderPipelineCmd* cmd = iter->NextCommand<SetRenderPipelineCmd>(); + RenderPipeline* pipeline = ToBackend(cmd->pipeline).Get(); + + commandList->SetPipelineState(pipeline->GetPipelineState()); + commandList->IASetPrimitiveTopology(pipeline->GetD3D12PrimitiveTopology()); + + bindingTracker->OnSetPipeline(pipeline); + + lastPipeline = pipeline; + break; + } + + case Command::SetBindGroup: { + SetBindGroupCmd* cmd = iter->NextCommand<SetBindGroupCmd>(); + BindGroup* group = ToBackend(cmd->group.Get()); + uint32_t* dynamicOffsets = nullptr; + + if (cmd->dynamicOffsetCount > 0) { + dynamicOffsets = iter->NextData<uint32_t>(cmd->dynamicOffsetCount); + } + + bindingTracker->OnSetBindGroup(cmd->index, group, cmd->dynamicOffsetCount, + dynamicOffsets); + break; + } + + case Command::SetIndexBuffer: { + SetIndexBufferCmd* cmd = iter->NextCommand<SetIndexBufferCmd>(); + + D3D12_INDEX_BUFFER_VIEW bufferView; + bufferView.Format = DXGIIndexFormat(cmd->format); + bufferView.BufferLocation = ToBackend(cmd->buffer)->GetVA() + cmd->offset; + bufferView.SizeInBytes = cmd->size; + + commandList->IASetIndexBuffer(&bufferView); + break; + } + + case Command::SetVertexBuffer: { + SetVertexBufferCmd* cmd = iter->NextCommand<SetVertexBufferCmd>(); + + vertexBufferTracker.OnSetVertexBuffer(cmd->slot, ToBackend(cmd->buffer.Get()), + cmd->offset, cmd->size); + break; + } + + default: + UNREACHABLE(); + break; + } + return {}; + }; + + Command type; + while (mCommands.NextCommandId(&type)) { + switch (type) { + case Command::EndRenderPass: { + mCommands.NextCommand<EndRenderPassCmd>(); + if (useRenderPass) { + commandContext->GetCommandList4()->EndRenderPass(); + } else if (renderPass->attachmentState->GetSampleCount() > 1) { + ResolveMultisampledRenderPass(commandContext, renderPass); + } + return {}; + } + + case Command::SetStencilReference: { + SetStencilReferenceCmd* cmd = mCommands.NextCommand<SetStencilReferenceCmd>(); + + commandList->OMSetStencilRef(cmd->reference); + break; + } + + case Command::SetViewport: { + SetViewportCmd* cmd = mCommands.NextCommand<SetViewportCmd>(); + D3D12_VIEWPORT viewport; + viewport.TopLeftX = cmd->x; + viewport.TopLeftY = cmd->y; + viewport.Width = cmd->width; + viewport.Height = cmd->height; + viewport.MinDepth = cmd->minDepth; + viewport.MaxDepth = cmd->maxDepth; + + commandList->RSSetViewports(1, &viewport); + break; + } + + case Command::SetScissorRect: { + SetScissorRectCmd* cmd = mCommands.NextCommand<SetScissorRectCmd>(); + D3D12_RECT rect; + rect.left = cmd->x; + rect.top = cmd->y; + rect.right = cmd->x + cmd->width; + rect.bottom = cmd->y + cmd->height; + + commandList->RSSetScissorRects(1, &rect); + break; + } + + case Command::SetBlendConstant: { + SetBlendConstantCmd* cmd = mCommands.NextCommand<SetBlendConstantCmd>(); + const std::array<float, 4> color = ConvertToFloatColor(cmd->color); + commandList->OMSetBlendFactor(color.data()); + break; + } + + case Command::ExecuteBundles: { + ExecuteBundlesCmd* cmd = mCommands.NextCommand<ExecuteBundlesCmd>(); + auto bundles = mCommands.NextData<Ref<RenderBundleBase>>(cmd->count); + + for (uint32_t i = 0; i < cmd->count; ++i) { + CommandIterator* iter = bundles[i]->GetCommands(); + iter->Reset(); + while (iter->NextCommandId(&type)) { + DAWN_TRY(EncodeRenderBundleCommand(iter, type)); + } + } + break; + } + + case Command::BeginOcclusionQuery: { + BeginOcclusionQueryCmd* cmd = mCommands.NextCommand<BeginOcclusionQueryCmd>(); + QuerySet* querySet = ToBackend(cmd->querySet.Get()); + ASSERT(D3D12QueryType(querySet->GetQueryType()) == + D3D12_QUERY_TYPE_BINARY_OCCLUSION); + commandList->BeginQuery(querySet->GetQueryHeap(), + D3D12_QUERY_TYPE_BINARY_OCCLUSION, cmd->queryIndex); + break; + } + + case Command::EndOcclusionQuery: { + EndOcclusionQueryCmd* cmd = mCommands.NextCommand<EndOcclusionQueryCmd>(); + QuerySet* querySet = ToBackend(cmd->querySet.Get()); + ASSERT(D3D12QueryType(querySet->GetQueryType()) == + D3D12_QUERY_TYPE_BINARY_OCCLUSION); + commandList->EndQuery(querySet->GetQueryHeap(), + D3D12_QUERY_TYPE_BINARY_OCCLUSION, cmd->queryIndex); + break; + } + + case Command::WriteTimestamp: { + WriteTimestampCmd* cmd = mCommands.NextCommand<WriteTimestampCmd>(); + + RecordWriteTimestampCmd(commandList, cmd); + break; + } + + default: { + DAWN_TRY(EncodeRenderBundleCommand(&mCommands, type)); + break; + } + } + } + return {}; + } +} // namespace dawn::native::d3d12
diff --git a/src/dawn/native/d3d12/CommandBufferD3D12.h b/src/dawn/native/d3d12/CommandBufferD3D12.h new file mode 100644 index 0000000..d6d4438 --- /dev/null +++ b/src/dawn/native/d3d12/CommandBufferD3D12.h
@@ -0,0 +1,57 @@ +// Copyright 2017 The Dawn Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#ifndef DAWNNATIVE_D3D12_COMMANDBUFFERD3D12_H_ +#define DAWNNATIVE_D3D12_COMMANDBUFFERD3D12_H_ + +#include "dawn/native/CommandBuffer.h" +#include "dawn/native/Error.h" + +namespace dawn::native { + struct BeginRenderPassCmd; +} // namespace dawn::native + +namespace dawn::native::d3d12 { + + class BindGroupStateTracker; + class CommandRecordingContext; + class RenderPassBuilder; + + class CommandBuffer final : public CommandBufferBase { + public: + static Ref<CommandBuffer> Create(CommandEncoder* encoder, + const CommandBufferDescriptor* descriptor); + + MaybeError RecordCommands(CommandRecordingContext* commandContext); + + private: + CommandBuffer(CommandEncoder* encoder, const CommandBufferDescriptor* descriptor); + + MaybeError RecordComputePass(CommandRecordingContext* commandContext, + BindGroupStateTracker* bindingTracker, + const ComputePassResourceUsage& resourceUsages); + MaybeError RecordRenderPass(CommandRecordingContext* commandContext, + BindGroupStateTracker* bindingTracker, + BeginRenderPassCmd* renderPass, + bool passHasUAV); + MaybeError SetupRenderPass(CommandRecordingContext* commandContext, + BeginRenderPassCmd* renderPass, + RenderPassBuilder* renderPassBuilder); + void EmulateBeginRenderPass(CommandRecordingContext* commandContext, + const RenderPassBuilder* renderPassBuilder) const; + }; + +} // namespace dawn::native::d3d12 + +#endif // DAWNNATIVE_D3D12_COMMANDBUFFERD3D12_H_
diff --git a/src/dawn/native/d3d12/CommandRecordingContext.cpp b/src/dawn/native/d3d12/CommandRecordingContext.cpp new file mode 100644 index 0000000..bb8ef81 --- /dev/null +++ b/src/dawn/native/d3d12/CommandRecordingContext.cpp
@@ -0,0 +1,175 @@ +// Copyright 2019 The Dawn Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +#include "dawn/native/d3d12/CommandRecordingContext.h" + +#include "dawn/native/d3d12/CommandAllocatorManager.h" +#include "dawn/native/d3d12/D3D12Error.h" +#include "dawn/native/d3d12/DeviceD3D12.h" +#include "dawn/native/d3d12/HeapD3D12.h" +#include "dawn/native/d3d12/ResidencyManagerD3D12.h" +#include "dawn/platform/DawnPlatform.h" +#include "dawn/platform/tracing/TraceEvent.h" + +#include <profileapi.h> +#include <sysinfoapi.h> + +namespace dawn::native::d3d12 { + + void CommandRecordingContext::AddToSharedTextureList(Texture* texture) { + ASSERT(IsOpen()); + mSharedTextures.insert(texture); + } + + MaybeError CommandRecordingContext::Open(ID3D12Device* d3d12Device, + CommandAllocatorManager* commandAllocationManager) { + ASSERT(!IsOpen()); + ID3D12CommandAllocator* commandAllocator; + DAWN_TRY_ASSIGN(commandAllocator, commandAllocationManager->ReserveCommandAllocator()); + if (mD3d12CommandList != nullptr) { + MaybeError error = CheckHRESULT(mD3d12CommandList->Reset(commandAllocator, nullptr), + "D3D12 resetting command list"); + if (error.IsError()) { + mD3d12CommandList.Reset(); + DAWN_TRY(std::move(error)); + } + } else { + ComPtr<ID3D12GraphicsCommandList> d3d12GraphicsCommandList; + DAWN_TRY(CheckHRESULT( + d3d12Device->CreateCommandList(0, D3D12_COMMAND_LIST_TYPE_DIRECT, commandAllocator, + nullptr, IID_PPV_ARGS(&d3d12GraphicsCommandList)), + "D3D12 creating direct command list")); + mD3d12CommandList = std::move(d3d12GraphicsCommandList); + // Store a cast to ID3D12GraphicsCommandList4. This is required to use the D3D12 render + // pass APIs introduced in Windows build 1809. + mD3d12CommandList.As(&mD3d12CommandList4); + } + + mIsOpen = true; + + return {}; + } + + MaybeError CommandRecordingContext::ExecuteCommandList(Device* device) { + if (IsOpen()) { + // Shared textures must be transitioned to common state after the last usage in order + // for them to be used by other APIs like D3D11. We ensure this by transitioning to the + // common state right before command list submission. TransitionUsageNow itself ensures + // no unnecessary transitions happen if the resources is already in the common state. + for (Texture* texture : mSharedTextures) { + DAWN_TRY(texture->AcquireKeyedMutex()); + texture->TrackAllUsageAndTransitionNow(this, D3D12_RESOURCE_STATE_COMMON); + } + + MaybeError error = + CheckHRESULT(mD3d12CommandList->Close(), "D3D12 closing pending command list"); + if (error.IsError()) { + Release(); + DAWN_TRY(std::move(error)); + } + DAWN_TRY(device->GetResidencyManager()->EnsureHeapsAreResident( + mHeapsPendingUsage.data(), mHeapsPendingUsage.size())); + + if (device->IsToggleEnabled(Toggle::RecordDetailedTimingInTraceEvents)) { + uint64_t gpuTimestamp; + uint64_t cpuTimestamp; + FILETIME fileTimeNonPrecise; + SYSTEMTIME systemTimeNonPrecise; + + // Both supported since Windows 2000, have a accuracy of 1ms + GetSystemTimeAsFileTime(&fileTimeNonPrecise); + GetSystemTime(&systemTimeNonPrecise); + // Query CPU and GPU timestamps at almost the same time + device->GetCommandQueue()->GetClockCalibration(&gpuTimestamp, &cpuTimestamp); + + uint64_t gpuFrequency; + uint64_t cpuFrequency; + LARGE_INTEGER cpuFrequencyLargeInteger; + device->GetCommandQueue()->GetTimestampFrequency(&gpuFrequency); + QueryPerformanceFrequency( + &cpuFrequencyLargeInteger); // Supported since Windows 2000 + cpuFrequency = cpuFrequencyLargeInteger.QuadPart; + + std::string timingInfo = absl::StrFormat( + "UTC Time: %u/%u/%u %02u:%02u:%02u.%03u, File Time: %u, CPU " + "Timestamp: %u, GPU Timestamp: %u, CPU Tick Frequency: %u, GPU Tick Frequency: " + "%u", + systemTimeNonPrecise.wYear, systemTimeNonPrecise.wMonth, + systemTimeNonPrecise.wDay, systemTimeNonPrecise.wHour, + systemTimeNonPrecise.wMinute, systemTimeNonPrecise.wSecond, + systemTimeNonPrecise.wMilliseconds, + (static_cast<uint64_t>(fileTimeNonPrecise.dwHighDateTime) << 32) + + fileTimeNonPrecise.dwLowDateTime, + cpuTimestamp, gpuTimestamp, cpuFrequency, gpuFrequency); + + TRACE_EVENT_INSTANT1( + device->GetPlatform(), General, + "d3d12::CommandRecordingContext::ExecuteCommandList Detailed Timing", "Timing", + timingInfo.c_str()); + } + + ID3D12CommandList* d3d12CommandList = GetCommandList(); + device->GetCommandQueue()->ExecuteCommandLists(1, &d3d12CommandList); + + for (Texture* texture : mSharedTextures) { + texture->ReleaseKeyedMutex(); + } + + mIsOpen = false; + mSharedTextures.clear(); + mHeapsPendingUsage.clear(); + } + return {}; + } + + void CommandRecordingContext::TrackHeapUsage(Heap* heap, ExecutionSerial serial) { + // Before tracking the heap, check the last serial it was recorded on to ensure we aren't + // tracking it more than once. + if (heap->GetLastUsage() < serial) { + heap->SetLastUsage(serial); + mHeapsPendingUsage.push_back(heap); + } + } + + ID3D12GraphicsCommandList* CommandRecordingContext::GetCommandList() const { + ASSERT(mD3d12CommandList != nullptr); + ASSERT(IsOpen()); + return mD3d12CommandList.Get(); + } + + // This function will fail on Windows versions prior to 1809. Support must be queried through + // the device before calling. + ID3D12GraphicsCommandList4* CommandRecordingContext::GetCommandList4() const { + ASSERT(IsOpen()); + ASSERT(mD3d12CommandList != nullptr); + return mD3d12CommandList4.Get(); + } + + void CommandRecordingContext::Release() { + mD3d12CommandList.Reset(); + mD3d12CommandList4.Reset(); + mIsOpen = false; + mSharedTextures.clear(); + mHeapsPendingUsage.clear(); + mTempBuffers.clear(); + } + + bool CommandRecordingContext::IsOpen() const { + return mIsOpen; + } + + void CommandRecordingContext::AddToTempBuffers(Ref<Buffer> tempBuffer) { + mTempBuffers.emplace_back(tempBuffer); + } + +} // namespace dawn::native::d3d12
diff --git a/src/dawn/native/d3d12/CommandRecordingContext.h b/src/dawn/native/d3d12/CommandRecordingContext.h new file mode 100644 index 0000000..21a60f2 --- /dev/null +++ b/src/dawn/native/d3d12/CommandRecordingContext.h
@@ -0,0 +1,58 @@ +// Copyright 2019 The Dawn Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +#ifndef DAWNNATIVE_D3D12_COMMANDRECORDINGCONTEXT_H_ +#define DAWNNATIVE_D3D12_COMMANDRECORDINGCONTEXT_H_ + +#include "dawn/native/Error.h" +#include "dawn/native/IntegerTypes.h" +#include "dawn/native/d3d12/BufferD3D12.h" +#include "dawn/native/d3d12/d3d12_platform.h" + +#include <set> + +namespace dawn::native::d3d12 { + class CommandAllocatorManager; + class Device; + class Heap; + class Texture; + + class CommandRecordingContext { + public: + void AddToSharedTextureList(Texture* texture); + MaybeError Open(ID3D12Device* d3d12Device, + CommandAllocatorManager* commandAllocationManager); + + ID3D12GraphicsCommandList* GetCommandList() const; + ID3D12GraphicsCommandList4* GetCommandList4() const; + void Release(); + bool IsOpen() const; + + MaybeError ExecuteCommandList(Device* device); + + void TrackHeapUsage(Heap* heap, ExecutionSerial serial); + + void AddToTempBuffers(Ref<Buffer> tempBuffer); + + private: + ComPtr<ID3D12GraphicsCommandList> mD3d12CommandList; + ComPtr<ID3D12GraphicsCommandList4> mD3d12CommandList4; + bool mIsOpen = false; + std::set<Texture*> mSharedTextures; + std::vector<Heap*> mHeapsPendingUsage; + + std::vector<Ref<Buffer>> mTempBuffers; + }; +} // namespace dawn::native::d3d12 + +#endif // DAWNNATIVE_D3D12_COMMANDRECORDINGCONTEXT_H_
diff --git a/src/dawn/native/d3d12/ComputePipelineD3D12.cpp b/src/dawn/native/d3d12/ComputePipelineD3D12.cpp new file mode 100644 index 0000000..6df1049 --- /dev/null +++ b/src/dawn/native/d3d12/ComputePipelineD3D12.cpp
@@ -0,0 +1,105 @@ +// Copyright 2017 The Dawn Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include "dawn/native/d3d12/ComputePipelineD3D12.h" + +#include "dawn/native/CreatePipelineAsyncTask.h" +#include "dawn/native/d3d12/D3D12Error.h" +#include "dawn/native/d3d12/DeviceD3D12.h" +#include "dawn/native/d3d12/PipelineLayoutD3D12.h" +#include "dawn/native/d3d12/PlatformFunctions.h" +#include "dawn/native/d3d12/ShaderModuleD3D12.h" +#include "dawn/native/d3d12/UtilsD3D12.h" + +namespace dawn::native::d3d12 { + + Ref<ComputePipeline> ComputePipeline::CreateUninitialized( + Device* device, + const ComputePipelineDescriptor* descriptor) { + return AcquireRef(new ComputePipeline(device, descriptor)); + } + + MaybeError ComputePipeline::Initialize() { + Device* device = ToBackend(GetDevice()); + uint32_t compileFlags = 0; + + if (!device->IsToggleEnabled(Toggle::UseDXC) && + !device->IsToggleEnabled(Toggle::FxcOptimizations)) { + compileFlags |= D3DCOMPILE_OPTIMIZATION_LEVEL0; + } + + if (device->IsToggleEnabled(Toggle::EmitHLSLDebugSymbols)) { + compileFlags |= D3DCOMPILE_DEBUG | D3DCOMPILE_SKIP_OPTIMIZATION; + } + + // SPRIV-cross does matrix multiplication expecting row major matrices + compileFlags |= D3DCOMPILE_PACK_MATRIX_ROW_MAJOR; + + const ProgrammableStage& computeStage = GetStage(SingleShaderStage::Compute); + ShaderModule* module = ToBackend(computeStage.module.Get()); + + D3D12_COMPUTE_PIPELINE_STATE_DESC d3dDesc = {}; + d3dDesc.pRootSignature = ToBackend(GetLayout())->GetRootSignature(); + + CompiledShader compiledShader; + DAWN_TRY_ASSIGN(compiledShader, module->Compile(computeStage, SingleShaderStage::Compute, + ToBackend(GetLayout()), compileFlags)); + d3dDesc.CS = compiledShader.GetD3D12ShaderBytecode(); + auto* d3d12Device = device->GetD3D12Device(); + DAWN_TRY(CheckHRESULT( + d3d12Device->CreateComputePipelineState(&d3dDesc, IID_PPV_ARGS(&mPipelineState)), + "D3D12 creating pipeline state")); + + SetLabelImpl(); + + return {}; + } + + ComputePipeline::~ComputePipeline() = default; + + void ComputePipeline::DestroyImpl() { + ComputePipelineBase::DestroyImpl(); + ToBackend(GetDevice())->ReferenceUntilUnused(mPipelineState); + } + + ID3D12PipelineState* ComputePipeline::GetPipelineState() const { + return mPipelineState.Get(); + } + + void ComputePipeline::SetLabelImpl() { + SetDebugName(ToBackend(GetDevice()), GetPipelineState(), "Dawn_ComputePipeline", + GetLabel()); + } + + void ComputePipeline::InitializeAsync(Ref<ComputePipelineBase> computePipeline, + WGPUCreateComputePipelineAsyncCallback callback, + void* userdata) { + std::unique_ptr<CreateComputePipelineAsyncTask> asyncTask = + std::make_unique<CreateComputePipelineAsyncTask>(std::move(computePipeline), callback, + userdata); + CreateComputePipelineAsyncTask::RunAsync(std::move(asyncTask)); + } + + bool ComputePipeline::UsesNumWorkgroups() const { + return GetStage(SingleShaderStage::Compute).metadata->usesNumWorkgroups; + } + + ComPtr<ID3D12CommandSignature> ComputePipeline::GetDispatchIndirectCommandSignature() { + if (UsesNumWorkgroups()) { + return ToBackend(GetLayout())->GetDispatchIndirectCommandSignatureWithNumWorkgroups(); + } + return ToBackend(GetDevice())->GetDispatchIndirectSignature(); + } + +} // namespace dawn::native::d3d12
diff --git a/src/dawn/native/d3d12/ComputePipelineD3D12.h b/src/dawn/native/d3d12/ComputePipelineD3D12.h new file mode 100644 index 0000000..03a0259 --- /dev/null +++ b/src/dawn/native/d3d12/ComputePipelineD3D12.h
@@ -0,0 +1,58 @@ +// Copyright 2017 The Dawn Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#ifndef DAWNNATIVE_D3D12_COMPUTEPIPELINED3D12_H_ +#define DAWNNATIVE_D3D12_COMPUTEPIPELINED3D12_H_ + +#include "dawn/native/ComputePipeline.h" + +#include "dawn/native/d3d12/d3d12_platform.h" + +namespace dawn::native::d3d12 { + + class Device; + + class ComputePipeline final : public ComputePipelineBase { + public: + static Ref<ComputePipeline> CreateUninitialized( + Device* device, + const ComputePipelineDescriptor* descriptor); + static void InitializeAsync(Ref<ComputePipelineBase> computePipeline, + WGPUCreateComputePipelineAsyncCallback callback, + void* userdata); + ComputePipeline() = delete; + + ID3D12PipelineState* GetPipelineState() const; + + MaybeError Initialize() override; + + // Dawn API + void SetLabelImpl() override; + + bool UsesNumWorkgroups() const; + + ComPtr<ID3D12CommandSignature> GetDispatchIndirectCommandSignature(); + + private: + ~ComputePipeline() override; + + void DestroyImpl() override; + + using ComputePipelineBase::ComputePipelineBase; + ComPtr<ID3D12PipelineState> mPipelineState; + }; + +} // namespace dawn::native::d3d12 + +#endif // DAWNNATIVE_D3D12_COMPUTEPIPELINED3D12_H_
diff --git a/src/dawn/native/d3d12/D3D11on12Util.cpp b/src/dawn/native/d3d12/D3D11on12Util.cpp new file mode 100644 index 0000000..d48d41f --- /dev/null +++ b/src/dawn/native/d3d12/D3D11on12Util.cpp
@@ -0,0 +1,187 @@ +// Copyright 2021 The Dawn Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +// D3D12Backend.cpp: contains the definition of symbols exported by D3D12Backend.h so that they +// can be compiled twice: once export (shared library), once not exported (static library) + +#include "dawn/native/d3d12/D3D11on12Util.h" + +#include "dawn/common/HashUtils.h" +#include "dawn/common/Log.h" +#include "dawn/native/d3d12/D3D12Error.h" +#include "dawn/native/d3d12/DeviceD3D12.h" + +#include <dawn/native/D3D12Backend.h> + +namespace dawn::native::d3d12 { + + void Flush11On12DeviceToAvoidLeaks(ComPtr<ID3D11On12Device> d3d11on12Device) { + if (d3d11on12Device == nullptr) { + return; + } + + ComPtr<ID3D11Device> d3d11Device; + if (FAILED(d3d11on12Device.As(&d3d11Device))) { + return; + } + + ComPtr<ID3D11DeviceContext> d3d11DeviceContext; + d3d11Device->GetImmediateContext(&d3d11DeviceContext); + + ASSERT(d3d11DeviceContext != nullptr); + + // 11on12 has a bug where D3D12 resources used only for keyed shared mutexes + // are not released until work is submitted to the device context and flushed. + // The most minimal work we can get away with is issuing a TiledResourceBarrier. + + // ID3D11DeviceContext2 is available in Win8.1 and above. This suffices for a + // D3D12 backend since both D3D12 and 11on12 first appeared in Windows 10. + ComPtr<ID3D11DeviceContext2> d3d11DeviceContext2; + if (FAILED(d3d11DeviceContext.As(&d3d11DeviceContext2))) { + return; + } + + d3d11DeviceContext2->TiledResourceBarrier(nullptr, nullptr); + d3d11DeviceContext2->Flush(); + } + + D3D11on12ResourceCacheEntry::D3D11on12ResourceCacheEntry( + ComPtr<IDXGIKeyedMutex> dxgiKeyedMutex, + ComPtr<ID3D11On12Device> d3d11On12Device) + : mDXGIKeyedMutex(std::move(dxgiKeyedMutex)), mD3D11on12Device(std::move(d3d11On12Device)) { + } + + D3D11on12ResourceCacheEntry::D3D11on12ResourceCacheEntry( + ComPtr<ID3D11On12Device> d3d11On12Device) + : mD3D11on12Device(std::move(d3d11On12Device)) { + } + + D3D11on12ResourceCacheEntry::~D3D11on12ResourceCacheEntry() { + if (mDXGIKeyedMutex == nullptr) { + return; + } + + if (mAcquireCount > 0) { + mDXGIKeyedMutex->ReleaseSync(kDXGIKeyedMutexAcquireReleaseKey); + } + + ComPtr<ID3D11Resource> d3d11Resource; + if (FAILED(mDXGIKeyedMutex.As(&d3d11Resource))) { + return; + } + + ASSERT(mD3D11on12Device != nullptr); + + ID3D11Resource* d3d11ResourceRaw = d3d11Resource.Get(); + mD3D11on12Device->ReleaseWrappedResources(&d3d11ResourceRaw, 1); + + d3d11Resource.Reset(); + mDXGIKeyedMutex.Reset(); + + Flush11On12DeviceToAvoidLeaks(std::move(mD3D11on12Device)); + } + + MaybeError D3D11on12ResourceCacheEntry::AcquireKeyedMutex() { + ASSERT(mDXGIKeyedMutex != nullptr); + ASSERT(mAcquireCount >= 0); + if (mAcquireCount == 0) { + DAWN_TRY(CheckHRESULT( + mDXGIKeyedMutex->AcquireSync(kDXGIKeyedMutexAcquireReleaseKey, INFINITE), + "D3D12 acquiring shared mutex")); + } + mAcquireCount++; + return {}; + } + + void D3D11on12ResourceCacheEntry::ReleaseKeyedMutex() { + ASSERT(mDXGIKeyedMutex != nullptr); + ASSERT(mAcquireCount > 0); + mAcquireCount--; + if (mAcquireCount == 0) { + mDXGIKeyedMutex->ReleaseSync(kDXGIKeyedMutexAcquireReleaseKey); + } + } + + size_t D3D11on12ResourceCacheEntry::HashFunc::operator()( + const Ref<D3D11on12ResourceCacheEntry> a) const { + size_t hash = 0; + HashCombine(&hash, a->mD3D11on12Device.Get()); + return hash; + } + + bool D3D11on12ResourceCacheEntry::EqualityFunc::operator()( + const Ref<D3D11on12ResourceCacheEntry> a, + const Ref<D3D11on12ResourceCacheEntry> b) const { + return a->mD3D11on12Device == b->mD3D11on12Device; + } + + D3D11on12ResourceCache::D3D11on12ResourceCache() = default; + + D3D11on12ResourceCache::~D3D11on12ResourceCache() = default; + + Ref<D3D11on12ResourceCacheEntry> D3D11on12ResourceCache::GetOrCreateD3D11on12Resource( + WGPUDevice device, + ID3D12Resource* d3d12Resource) { + Device* backendDevice = reinterpret_cast<Device*>(device); + // The Dawn and 11on12 device share the same D3D12 command queue whereas this external image + // could be accessed/produced with multiple Dawn devices. To avoid cross-queue sharing + // restrictions, the 11 wrapped resource is forbidden to be shared between Dawn devices by + // using the 11on12 device as the cache key. + ComPtr<ID3D11On12Device> d3d11on12Device = backendDevice->GetOrCreateD3D11on12Device(); + if (d3d11on12Device == nullptr) { + dawn::ErrorLog() << "Unable to create 11on12 device for external image"; + return nullptr; + } + + D3D11on12ResourceCacheEntry blueprint(d3d11on12Device); + auto iter = mCache.find(&blueprint); + if (iter != mCache.end()) { + return *iter; + } + + // We use IDXGIKeyedMutexes to synchronize access between D3D11 and D3D12. D3D11/12 fences + // are a viable alternative but are, unfortunately, not available on all versions of Windows + // 10. Since D3D12 does not directly support keyed mutexes, we need to wrap the D3D12 + // resource using 11on12 and QueryInterface the D3D11 representation for the keyed mutex. + ComPtr<ID3D11Texture2D> d3d11Texture; + D3D11_RESOURCE_FLAGS resourceFlags; + resourceFlags.BindFlags = 0; + resourceFlags.MiscFlags = D3D11_RESOURCE_MISC_SHARED_KEYEDMUTEX; + resourceFlags.CPUAccessFlags = 0; + resourceFlags.StructureByteStride = 0; + if (FAILED(d3d11on12Device->CreateWrappedResource( + d3d12Resource, &resourceFlags, D3D12_RESOURCE_STATE_COMMON, + D3D12_RESOURCE_STATE_COMMON, IID_PPV_ARGS(&d3d11Texture)))) { + return nullptr; + } + + ComPtr<IDXGIKeyedMutex> dxgiKeyedMutex; + if (FAILED(d3d11Texture.As(&dxgiKeyedMutex))) { + return nullptr; + } + + // Keep this cache from growing unbounded. + // TODO(dawn:625): Consider using a replacement policy based cache. + if (mCache.size() > kMaxD3D11on12ResourceCacheSize) { + mCache.clear(); + } + + Ref<D3D11on12ResourceCacheEntry> entry = + AcquireRef(new D3D11on12ResourceCacheEntry(dxgiKeyedMutex, std::move(d3d11on12Device))); + mCache.insert(entry); + + return entry; + } + +} // namespace dawn::native::d3d12
diff --git a/src/dawn/native/d3d12/D3D11on12Util.h b/src/dawn/native/d3d12/D3D11on12Util.h new file mode 100644 index 0000000..af7e680 --- /dev/null +++ b/src/dawn/native/d3d12/D3D11on12Util.h
@@ -0,0 +1,92 @@ +// Copyright 2021 The Dawn Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#ifndef DAWNNATIVE_D3D11ON12UTIL_H_ +#define DAWNNATIVE_D3D11ON12UTIL_H_ + +#include "dawn/common/RefCounted.h" +#include "dawn/native/Error.h" +#include "dawn/native/d3d12/d3d12_platform.h" + +#include <dawn/native/DawnNative.h> +#include <memory> +#include <unordered_set> + +struct ID3D11On12Device; +struct IDXGIKeyedMutex; + +namespace dawn::native::d3d12 { + + // Wraps 11 wrapped resources in a cache. + class D3D11on12ResourceCacheEntry : public RefCounted { + public: + D3D11on12ResourceCacheEntry(ComPtr<ID3D11On12Device> d3d11on12Device); + D3D11on12ResourceCacheEntry(ComPtr<IDXGIKeyedMutex> d3d11on12Resource, + ComPtr<ID3D11On12Device> d3d11on12Device); + ~D3D11on12ResourceCacheEntry(); + + MaybeError AcquireKeyedMutex(); + void ReleaseKeyedMutex(); + + // Functors necessary for the + // unordered_set<D3D11on12ResourceCacheEntry&>-based cache. + struct HashFunc { + size_t operator()(const Ref<D3D11on12ResourceCacheEntry> a) const; + }; + + struct EqualityFunc { + bool operator()(const Ref<D3D11on12ResourceCacheEntry> a, + const Ref<D3D11on12ResourceCacheEntry> b) const; + }; + + private: + ComPtr<IDXGIKeyedMutex> mDXGIKeyedMutex; + ComPtr<ID3D11On12Device> mD3D11on12Device; + int64_t mAcquireCount = 0; + }; + + // |D3D11on12ResourceCache| maintains a cache of 11 wrapped resources. + // Each entry represents a 11 resource that is exclusively accessed by Dawn device. + // Since each Dawn device creates and stores a 11on12 device, the 11on12 device + // is used as the key for the cache entry which ensures only the same 11 wrapped + // resource is re-used and also fully released. + // + // The cache is primarily needed to avoid repeatedly calling CreateWrappedResource + // and special release code per ProduceTexture(device). + class D3D11on12ResourceCache { + public: + D3D11on12ResourceCache(); + ~D3D11on12ResourceCache(); + + Ref<D3D11on12ResourceCacheEntry> GetOrCreateD3D11on12Resource( + WGPUDevice device, + ID3D12Resource* d3d12Resource); + + private: + // TODO(dawn:625): Figure out a large enough cache size. + static constexpr uint64_t kMaxD3D11on12ResourceCacheSize = 5; + + // 11on12 resource cache entries are refcounted to ensure if the ExternalImage outlives the + // Dawn texture (or vice-versa), we always fully release the 11 wrapped resource without + // waiting until Dawn device to shutdown. + using Cache = std::unordered_set<Ref<D3D11on12ResourceCacheEntry>, + D3D11on12ResourceCacheEntry::HashFunc, + D3D11on12ResourceCacheEntry::EqualityFunc>; + + Cache mCache; + }; + +} // namespace dawn::native::d3d12 + +#endif // DAWNNATIVE_D3D11ON12UTIL_H_
diff --git a/src/dawn/native/d3d12/D3D12Backend.cpp b/src/dawn/native/d3d12/D3D12Backend.cpp new file mode 100644 index 0000000..18d7145 --- /dev/null +++ b/src/dawn/native/d3d12/D3D12Backend.cpp
@@ -0,0 +1,179 @@ +// Copyright 2019 The Dawn Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +// D3D12Backend.cpp: contains the definition of symbols exported by D3D12Backend.h so that they +// can be compiled twice: once export (shared library), once not exported (static library) + +#include "dawn/native/D3D12Backend.h" + +#include "dawn/common/Log.h" +#include "dawn/common/Math.h" +#include "dawn/common/SwapChainUtils.h" +#include "dawn/native/d3d12/D3D11on12Util.h" +#include "dawn/native/d3d12/DeviceD3D12.h" +#include "dawn/native/d3d12/NativeSwapChainImplD3D12.h" +#include "dawn/native/d3d12/ResidencyManagerD3D12.h" +#include "dawn/native/d3d12/TextureD3D12.h" + +namespace dawn::native::d3d12 { + + ComPtr<ID3D12Device> GetD3D12Device(WGPUDevice device) { + return ToBackend(FromAPI(device))->GetD3D12Device(); + } + + DawnSwapChainImplementation CreateNativeSwapChainImpl(WGPUDevice device, HWND window) { + Device* backendDevice = ToBackend(FromAPI(device)); + + DawnSwapChainImplementation impl; + impl = CreateSwapChainImplementation(new NativeSwapChainImpl(backendDevice, window)); + impl.textureUsage = WGPUTextureUsage_Present; + + return impl; + } + + WGPUTextureFormat GetNativeSwapChainPreferredFormat( + const DawnSwapChainImplementation* swapChain) { + NativeSwapChainImpl* impl = reinterpret_cast<NativeSwapChainImpl*>(swapChain->userData); + return static_cast<WGPUTextureFormat>(impl->GetPreferredFormat()); + } + + ExternalImageDescriptorDXGISharedHandle::ExternalImageDescriptorDXGISharedHandle() + : ExternalImageDescriptor(ExternalImageType::DXGISharedHandle) { + } + + ExternalImageDXGI::ExternalImageDXGI(ComPtr<ID3D12Resource> d3d12Resource, + const WGPUTextureDescriptor* descriptor) + : mD3D12Resource(std::move(d3d12Resource)), + mUsage(descriptor->usage), + mDimension(descriptor->dimension), + mSize(descriptor->size), + mFormat(descriptor->format), + mMipLevelCount(descriptor->mipLevelCount), + mSampleCount(descriptor->sampleCount) { + ASSERT(!descriptor->nextInChain || + descriptor->nextInChain->sType == WGPUSType_DawnTextureInternalUsageDescriptor); + if (descriptor->nextInChain) { + mUsageInternal = reinterpret_cast<const WGPUDawnTextureInternalUsageDescriptor*>( + descriptor->nextInChain) + ->internalUsage; + } + mD3D11on12ResourceCache = std::make_unique<D3D11on12ResourceCache>(); + } + + ExternalImageDXGI::~ExternalImageDXGI() = default; + + WGPUTexture ExternalImageDXGI::ProduceTexture( + WGPUDevice device, + const ExternalImageAccessDescriptorDXGIKeyedMutex* descriptor) { + Device* backendDevice = ToBackend(FromAPI(device)); + + // Ensure the texture usage is allowed + if (!IsSubset(descriptor->usage, mUsage)) { + dawn::ErrorLog() << "Texture usage is not valid for external image"; + return nullptr; + } + + TextureDescriptor textureDescriptor = {}; + textureDescriptor.usage = static_cast<wgpu::TextureUsage>(descriptor->usage); + textureDescriptor.dimension = static_cast<wgpu::TextureDimension>(mDimension); + textureDescriptor.size = {mSize.width, mSize.height, mSize.depthOrArrayLayers}; + textureDescriptor.format = static_cast<wgpu::TextureFormat>(mFormat); + textureDescriptor.mipLevelCount = mMipLevelCount; + textureDescriptor.sampleCount = mSampleCount; + + DawnTextureInternalUsageDescriptor internalDesc = {}; + if (mUsageInternal) { + textureDescriptor.nextInChain = &internalDesc; + internalDesc.internalUsage = static_cast<wgpu::TextureUsage>(mUsageInternal); + internalDesc.sType = wgpu::SType::DawnTextureInternalUsageDescriptor; + } + + Ref<D3D11on12ResourceCacheEntry> d3d11on12Resource = + mD3D11on12ResourceCache->GetOrCreateD3D11on12Resource(device, mD3D12Resource.Get()); + if (d3d11on12Resource == nullptr) { + dawn::ErrorLog() << "Unable to create 11on12 resource for external image"; + return nullptr; + } + + Ref<TextureBase> texture = backendDevice->CreateD3D12ExternalTexture( + &textureDescriptor, mD3D12Resource, std::move(d3d11on12Resource), + descriptor->isSwapChainTexture, descriptor->isInitialized); + + return ToAPI(texture.Detach()); + } + + // static + std::unique_ptr<ExternalImageDXGI> ExternalImageDXGI::Create( + WGPUDevice device, + const ExternalImageDescriptorDXGISharedHandle* descriptor) { + Device* backendDevice = ToBackend(FromAPI(device)); + + Microsoft::WRL::ComPtr<ID3D12Resource> d3d12Resource; + if (FAILED(backendDevice->GetD3D12Device()->OpenSharedHandle( + descriptor->sharedHandle, IID_PPV_ARGS(&d3d12Resource)))) { + return nullptr; + } + + const TextureDescriptor* textureDescriptor = FromAPI(descriptor->cTextureDescriptor); + + if (backendDevice->ConsumedError( + ValidateTextureDescriptor(backendDevice, textureDescriptor))) { + return nullptr; + } + + if (backendDevice->ConsumedError( + ValidateTextureDescriptorCanBeWrapped(textureDescriptor), + "validating that a D3D12 external image can be wrapped with %s", + textureDescriptor)) { + return nullptr; + } + + if (backendDevice->ConsumedError( + ValidateD3D12TextureCanBeWrapped(d3d12Resource.Get(), textureDescriptor))) { + return nullptr; + } + + // Shared handle is assumed to support resource sharing capability. The resource + // shared capability tier must agree to share resources between D3D devices. + const Format* format = + backendDevice->GetInternalFormat(textureDescriptor->format).AcquireSuccess(); + if (format->IsMultiPlanar()) { + if (backendDevice->ConsumedError(ValidateD3D12VideoTextureCanBeShared( + backendDevice, D3D12TextureFormat(textureDescriptor->format)))) { + return nullptr; + } + } + + std::unique_ptr<ExternalImageDXGI> result( + new ExternalImageDXGI(std::move(d3d12Resource), descriptor->cTextureDescriptor)); + return result; + } + + uint64_t SetExternalMemoryReservation(WGPUDevice device, + uint64_t requestedReservationSize, + MemorySegment memorySegment) { + Device* backendDevice = ToBackend(FromAPI(device)); + + return backendDevice->GetResidencyManager()->SetExternalMemoryReservation( + memorySegment, requestedReservationSize); + } + + AdapterDiscoveryOptions::AdapterDiscoveryOptions() + : AdapterDiscoveryOptionsBase(WGPUBackendType_D3D12), dxgiAdapter(nullptr) { + } + + AdapterDiscoveryOptions::AdapterDiscoveryOptions(ComPtr<IDXGIAdapter> adapter) + : AdapterDiscoveryOptionsBase(WGPUBackendType_D3D12), dxgiAdapter(std::move(adapter)) { + } +} // namespace dawn::native::d3d12
diff --git a/src/dawn/native/d3d12/D3D12Error.cpp b/src/dawn/native/d3d12/D3D12Error.cpp new file mode 100644 index 0000000..23a9556 --- /dev/null +++ b/src/dawn/native/d3d12/D3D12Error.cpp
@@ -0,0 +1,51 @@ +// Copyright 2019 The Dawn Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include "dawn/native/d3d12/D3D12Error.h" + +#include <iomanip> +#include <sstream> +#include <string> + +namespace dawn::native::d3d12 { + MaybeError CheckHRESULTImpl(HRESULT result, const char* context) { + if (DAWN_LIKELY(SUCCEEDED(result))) { + return {}; + } + + std::ostringstream messageStream; + messageStream << context << " failed with "; + if (result == E_FAKE_ERROR_FOR_TESTING) { + messageStream << "E_FAKE_ERROR_FOR_TESTING"; + } else { + messageStream << "0x" << std::uppercase << std::setfill('0') << std::setw(8) << std::hex + << result; + } + + if (result == DXGI_ERROR_DEVICE_REMOVED) { + return DAWN_DEVICE_LOST_ERROR(messageStream.str()); + } else { + return DAWN_INTERNAL_ERROR(messageStream.str()); + } + } + + MaybeError CheckOutOfMemoryHRESULTImpl(HRESULT result, const char* context) { + if (result == E_OUTOFMEMORY || result == E_FAKE_OUTOFMEMORY_ERROR_FOR_TESTING) { + return DAWN_OUT_OF_MEMORY_ERROR(context); + } + + return CheckHRESULTImpl(result, context); + } + +} // namespace dawn::native::d3d12
diff --git a/src/dawn/native/d3d12/D3D12Error.h b/src/dawn/native/d3d12/D3D12Error.h new file mode 100644 index 0000000..f70690a --- /dev/null +++ b/src/dawn/native/d3d12/D3D12Error.h
@@ -0,0 +1,45 @@ +// Copyright 2019 The Dawn Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#ifndef DAWNNATIVE_D3D12_D3D12ERROR_H_ +#define DAWNNATIVE_D3D12_D3D12ERROR_H_ + +#include <d3d12.h> +#include "dawn/native/Error.h" +#include "dawn/native/ErrorInjector.h" + +namespace dawn::native::d3d12 { + + constexpr HRESULT E_FAKE_ERROR_FOR_TESTING = MAKE_HRESULT(SEVERITY_ERROR, FACILITY_ITF, 0xFF); + constexpr HRESULT E_FAKE_OUTOFMEMORY_ERROR_FOR_TESTING = + MAKE_HRESULT(SEVERITY_ERROR, FACILITY_ITF, 0xFE); + + // Returns a success only if result of HResult is success + MaybeError CheckHRESULTImpl(HRESULT result, const char* context); + + // Uses CheckRESULT but returns OOM specific error when recoverable. + MaybeError CheckOutOfMemoryHRESULTImpl(HRESULT result, const char* context); + +#define CheckHRESULT(resultIn, contextIn) \ + ::dawn::native::d3d12::CheckHRESULTImpl( \ + INJECT_ERROR_OR_RUN(resultIn, E_FAKE_ERROR_FOR_TESTING), contextIn) +#define CheckOutOfMemoryHRESULT(resultIn, contextIn) \ + ::dawn::native::d3d12::CheckOutOfMemoryHRESULTImpl( \ + INJECT_ERROR_OR_RUN(resultIn, E_FAKE_OUTOFMEMORY_ERROR_FOR_TESTING, \ + E_FAKE_ERROR_FOR_TESTING), \ + contextIn) + +} // namespace dawn::native::d3d12 + +#endif // DAWNNATIVE_D3D12_D3D12ERROR_H_
diff --git a/src/dawn/native/d3d12/D3D12Info.cpp b/src/dawn/native/d3d12/D3D12Info.cpp new file mode 100644 index 0000000..ebd629b --- /dev/null +++ b/src/dawn/native/d3d12/D3D12Info.cpp
@@ -0,0 +1,122 @@ +// Copyright 2019 The Dawn Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include "dawn/native/d3d12/D3D12Info.h" + +#include "dawn/common/GPUInfo.h" +#include "dawn/native/d3d12/AdapterD3D12.h" +#include "dawn/native/d3d12/BackendD3D12.h" +#include "dawn/native/d3d12/D3D12Error.h" +#include "dawn/native/d3d12/PlatformFunctions.h" + +namespace dawn::native::d3d12 { + + ResultOrError<D3D12DeviceInfo> GatherDeviceInfo(const Adapter& adapter) { + D3D12DeviceInfo info = {}; + + // Newer builds replace D3D_FEATURE_DATA_ARCHITECTURE with + // D3D_FEATURE_DATA_ARCHITECTURE1. However, D3D_FEATURE_DATA_ARCHITECTURE can be used + // for backwards compat. + // https://docs.microsoft.com/en-us/windows/desktop/api/d3d12/ne-d3d12-d3d12_feature + D3D12_FEATURE_DATA_ARCHITECTURE arch = {}; + DAWN_TRY(CheckHRESULT(adapter.GetDevice()->CheckFeatureSupport(D3D12_FEATURE_ARCHITECTURE, + &arch, sizeof(arch)), + "ID3D12Device::CheckFeatureSupport")); + + info.isUMA = arch.UMA; + + D3D12_FEATURE_DATA_D3D12_OPTIONS options = {}; + DAWN_TRY(CheckHRESULT(adapter.GetDevice()->CheckFeatureSupport(D3D12_FEATURE_D3D12_OPTIONS, + &options, sizeof(options)), + "ID3D12Device::CheckFeatureSupport")); + + info.resourceHeapTier = options.ResourceHeapTier; + + // Windows builds 1809 and above can use the D3D12 render pass API. If we query + // CheckFeatureSupport for D3D12_FEATURE_D3D12_OPTIONS5 successfully, then we can use + // the render pass API. + info.supportsRenderPass = false; + D3D12_FEATURE_DATA_D3D12_OPTIONS5 featureOptions5 = {}; + if (SUCCEEDED(adapter.GetDevice()->CheckFeatureSupport( + D3D12_FEATURE_D3D12_OPTIONS5, &featureOptions5, sizeof(featureOptions5)))) { + // Performance regressions been observed when using a render pass on Intel graphics + // with RENDER_PASS_TIER_1 available, so fall back to a software emulated render + // pass on these platforms. + if (featureOptions5.RenderPassesTier < D3D12_RENDER_PASS_TIER_1 || + !gpu_info::IsIntel(adapter.GetVendorId())) { + info.supportsRenderPass = true; + } + } + + // Used to share resources cross-API. If we query CheckFeatureSupport for + // D3D12_FEATURE_D3D12_OPTIONS4 successfully, then we can use cross-API sharing. + info.supportsSharedResourceCapabilityTier1 = false; + D3D12_FEATURE_DATA_D3D12_OPTIONS4 featureOptions4 = {}; + if (SUCCEEDED(adapter.GetDevice()->CheckFeatureSupport( + D3D12_FEATURE_D3D12_OPTIONS4, &featureOptions4, sizeof(featureOptions4)))) { + // Tier 1 support additionally enables the NV12 format. Since only the NV12 format + // is used by Dawn, check for Tier 1. + if (featureOptions4.SharedResourceCompatibilityTier >= + D3D12_SHARED_RESOURCE_COMPATIBILITY_TIER_1) { + info.supportsSharedResourceCapabilityTier1 = true; + } + } + + D3D12_FEATURE_DATA_SHADER_MODEL knownShaderModels[] = {{D3D_SHADER_MODEL_6_2}, + {D3D_SHADER_MODEL_6_1}, + {D3D_SHADER_MODEL_6_0}, + {D3D_SHADER_MODEL_5_1}}; + uint32_t driverShaderModel = 0; + for (D3D12_FEATURE_DATA_SHADER_MODEL shaderModel : knownShaderModels) { + if (SUCCEEDED(adapter.GetDevice()->CheckFeatureSupport( + D3D12_FEATURE_SHADER_MODEL, &shaderModel, sizeof(shaderModel)))) { + driverShaderModel = shaderModel.HighestShaderModel; + break; + } + } + + if (driverShaderModel < D3D_SHADER_MODEL_5_1) { + return DAWN_INTERNAL_ERROR("Driver doesn't support Shader Model 5.1 or higher"); + } + + // D3D_SHADER_MODEL is encoded as 0xMm with M the major version and m the minor version + ASSERT(driverShaderModel <= 0xFF); + uint32_t shaderModelMajor = (driverShaderModel & 0xF0) >> 4; + uint32_t shaderModelMinor = (driverShaderModel & 0xF); + + ASSERT(shaderModelMajor < 10); + ASSERT(shaderModelMinor < 10); + info.shaderModel = 10 * shaderModelMajor + shaderModelMinor; + + // Profiles are always <stage>s_<minor>_<major> so we build the s_<minor>_major and add + // it to each of the stage's suffix. + std::wstring profileSuffix = L"s_M_n"; + profileSuffix[2] = wchar_t('0' + shaderModelMajor); + profileSuffix[4] = wchar_t('0' + shaderModelMinor); + + info.shaderProfiles[SingleShaderStage::Vertex] = L"v" + profileSuffix; + info.shaderProfiles[SingleShaderStage::Fragment] = L"p" + profileSuffix; + info.shaderProfiles[SingleShaderStage::Compute] = L"c" + profileSuffix; + + D3D12_FEATURE_DATA_D3D12_OPTIONS4 featureData4 = {}; + if (SUCCEEDED(adapter.GetDevice()->CheckFeatureSupport( + D3D12_FEATURE_D3D12_OPTIONS4, &featureData4, sizeof(featureData4)))) { + info.supportsShaderFloat16 = driverShaderModel >= D3D_SHADER_MODEL_6_2 && + featureData4.Native16BitShaderOpsSupported; + } + + return std::move(info); + } + +} // namespace dawn::native::d3d12
diff --git a/src/dawn/native/d3d12/D3D12Info.h b/src/dawn/native/d3d12/D3D12Info.h new file mode 100644 index 0000000..83ee837 --- /dev/null +++ b/src/dawn/native/d3d12/D3D12Info.h
@@ -0,0 +1,41 @@ +// Copyright 2019 The Dawn Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#ifndef DAWNNATIVE_D3D12_D3D12INFO_H_ +#define DAWNNATIVE_D3D12_D3D12INFO_H_ + +#include "dawn/native/Error.h" +#include "dawn/native/PerStage.h" +#include "dawn/native/d3d12/d3d12_platform.h" + +namespace dawn::native::d3d12 { + + class Adapter; + + struct D3D12DeviceInfo { + bool isUMA; + uint32_t resourceHeapTier; + bool supportsRenderPass; + bool supportsShaderFloat16; + // shaderModel indicates the maximum supported shader model, for example, the value 62 + // indicates that current driver supports the maximum shader model is shader model 6.2. + uint32_t shaderModel; + PerStage<std::wstring> shaderProfiles; + bool supportsSharedResourceCapabilityTier1; + }; + + ResultOrError<D3D12DeviceInfo> GatherDeviceInfo(const Adapter& adapter); +} // namespace dawn::native::d3d12 + +#endif // DAWNNATIVE_D3D12_D3D12INFO_H_
diff --git a/src/dawn/native/d3d12/DeviceD3D12.cpp b/src/dawn/native/d3d12/DeviceD3D12.cpp new file mode 100644 index 0000000..415b486 --- /dev/null +++ b/src/dawn/native/d3d12/DeviceD3D12.cpp
@@ -0,0 +1,744 @@ +// Copyright 2017 The Dawn Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include "dawn/native/d3d12/DeviceD3D12.h" + +#include "dawn/common/GPUInfo.h" +#include "dawn/native/DynamicUploader.h" +#include "dawn/native/Instance.h" +#include "dawn/native/d3d12/AdapterD3D12.h" +#include "dawn/native/d3d12/BackendD3D12.h" +#include "dawn/native/d3d12/BindGroupD3D12.h" +#include "dawn/native/d3d12/BindGroupLayoutD3D12.h" +#include "dawn/native/d3d12/CommandAllocatorManager.h" +#include "dawn/native/d3d12/CommandBufferD3D12.h" +#include "dawn/native/d3d12/ComputePipelineD3D12.h" +#include "dawn/native/d3d12/D3D11on12Util.h" +#include "dawn/native/d3d12/D3D12Error.h" +#include "dawn/native/d3d12/PipelineLayoutD3D12.h" +#include "dawn/native/d3d12/PlatformFunctions.h" +#include "dawn/native/d3d12/QuerySetD3D12.h" +#include "dawn/native/d3d12/QueueD3D12.h" +#include "dawn/native/d3d12/RenderPipelineD3D12.h" +#include "dawn/native/d3d12/ResidencyManagerD3D12.h" +#include "dawn/native/d3d12/ResourceAllocatorManagerD3D12.h" +#include "dawn/native/d3d12/SamplerD3D12.h" +#include "dawn/native/d3d12/SamplerHeapCacheD3D12.h" +#include "dawn/native/d3d12/ShaderModuleD3D12.h" +#include "dawn/native/d3d12/ShaderVisibleDescriptorAllocatorD3D12.h" +#include "dawn/native/d3d12/StagingBufferD3D12.h" +#include "dawn/native/d3d12/StagingDescriptorAllocatorD3D12.h" +#include "dawn/native/d3d12/SwapChainD3D12.h" +#include "dawn/native/d3d12/UtilsD3D12.h" + +#include <sstream> + +namespace dawn::native::d3d12 { + + // TODO(dawn:155): Figure out these values. + static constexpr uint16_t kShaderVisibleDescriptorHeapSize = 1024; + static constexpr uint8_t kAttachmentDescriptorHeapSize = 64; + + // Value may change in the future to better accomodate large clears. + static constexpr uint64_t kZeroBufferSize = 1024 * 1024 * 4; // 4 Mb + + static constexpr uint64_t kMaxDebugMessagesToPrint = 5; + + // static + ResultOrError<Ref<Device>> Device::Create(Adapter* adapter, + const DeviceDescriptor* descriptor) { + Ref<Device> device = AcquireRef(new Device(adapter, descriptor)); + DAWN_TRY(device->Initialize()); + return device; + } + + MaybeError Device::Initialize() { + InitTogglesFromDriver(); + + mD3d12Device = ToBackend(GetAdapter())->GetDevice(); + + ASSERT(mD3d12Device != nullptr); + + // Create device-global objects + D3D12_COMMAND_QUEUE_DESC queueDesc = {}; + queueDesc.Flags = D3D12_COMMAND_QUEUE_FLAG_NONE; + queueDesc.Type = D3D12_COMMAND_LIST_TYPE_DIRECT; + DAWN_TRY( + CheckHRESULT(mD3d12Device->CreateCommandQueue(&queueDesc, IID_PPV_ARGS(&mCommandQueue)), + "D3D12 create command queue")); + + if (IsFeatureEnabled(Feature::TimestampQuery) && + !IsToggleEnabled(Toggle::DisableTimestampQueryConversion)) { + // Get GPU timestamp counter frequency (in ticks/second). This fails if the specified + // command queue doesn't support timestamps. D3D12_COMMAND_LIST_TYPE_DIRECT queues + // always support timestamps except where there are bugs in Windows container and vGPU + // implementations. + uint64_t frequency; + DAWN_TRY(CheckHRESULT(mCommandQueue->GetTimestampFrequency(&frequency), + "D3D12 get timestamp frequency")); + // Calculate the period in nanoseconds by the frequency. + mTimestampPeriod = static_cast<float>(1e9) / frequency; + } + + // If PIX is not attached, the QueryInterface fails. Hence, no need to check the return + // value. + mCommandQueue.As(&mD3d12SharingContract); + + DAWN_TRY( + CheckHRESULT(mD3d12Device->CreateFence(uint64_t(GetLastSubmittedCommandSerial()), + D3D12_FENCE_FLAG_NONE, IID_PPV_ARGS(&mFence)), + "D3D12 create fence")); + + mFenceEvent = CreateEvent(nullptr, FALSE, FALSE, nullptr); + ASSERT(mFenceEvent != nullptr); + + // Initialize backend services + mCommandAllocatorManager = std::make_unique<CommandAllocatorManager>(this); + + // Zero sized allocator is never requested and does not need to exist. + for (uint32_t countIndex = 0; countIndex < kNumViewDescriptorAllocators; countIndex++) { + mViewAllocators[countIndex + 1] = std::make_unique<StagingDescriptorAllocator>( + this, 1u << countIndex, kShaderVisibleDescriptorHeapSize, + D3D12_DESCRIPTOR_HEAP_TYPE_CBV_SRV_UAV); + } + + for (uint32_t countIndex = 0; countIndex < kNumSamplerDescriptorAllocators; countIndex++) { + mSamplerAllocators[countIndex + 1] = std::make_unique<StagingDescriptorAllocator>( + this, 1u << countIndex, kShaderVisibleDescriptorHeapSize, + D3D12_DESCRIPTOR_HEAP_TYPE_SAMPLER); + } + + mRenderTargetViewAllocator = std::make_unique<StagingDescriptorAllocator>( + this, 1, kAttachmentDescriptorHeapSize, D3D12_DESCRIPTOR_HEAP_TYPE_RTV); + + mDepthStencilViewAllocator = std::make_unique<StagingDescriptorAllocator>( + this, 1, kAttachmentDescriptorHeapSize, D3D12_DESCRIPTOR_HEAP_TYPE_DSV); + + mSamplerHeapCache = std::make_unique<SamplerHeapCache>(this); + + mResidencyManager = std::make_unique<ResidencyManager>(this); + mResourceAllocatorManager = std::make_unique<ResourceAllocatorManager>(this); + + // ShaderVisibleDescriptorAllocators use the ResidencyManager and must be initialized after. + DAWN_TRY_ASSIGN( + mSamplerShaderVisibleDescriptorAllocator, + ShaderVisibleDescriptorAllocator::Create(this, D3D12_DESCRIPTOR_HEAP_TYPE_SAMPLER)); + + DAWN_TRY_ASSIGN( + mViewShaderVisibleDescriptorAllocator, + ShaderVisibleDescriptorAllocator::Create(this, D3D12_DESCRIPTOR_HEAP_TYPE_CBV_SRV_UAV)); + + // Initialize indirect commands + D3D12_INDIRECT_ARGUMENT_DESC argumentDesc = {}; + argumentDesc.Type = D3D12_INDIRECT_ARGUMENT_TYPE_DISPATCH; + + D3D12_COMMAND_SIGNATURE_DESC programDesc = {}; + programDesc.ByteStride = 3 * sizeof(uint32_t); + programDesc.NumArgumentDescs = 1; + programDesc.pArgumentDescs = &argumentDesc; + + GetD3D12Device()->CreateCommandSignature(&programDesc, NULL, + IID_PPV_ARGS(&mDispatchIndirectSignature)); + + argumentDesc.Type = D3D12_INDIRECT_ARGUMENT_TYPE_DRAW; + programDesc.ByteStride = 4 * sizeof(uint32_t); + + GetD3D12Device()->CreateCommandSignature(&programDesc, NULL, + IID_PPV_ARGS(&mDrawIndirectSignature)); + + argumentDesc.Type = D3D12_INDIRECT_ARGUMENT_TYPE_DRAW_INDEXED; + programDesc.ByteStride = 5 * sizeof(uint32_t); + + GetD3D12Device()->CreateCommandSignature(&programDesc, NULL, + IID_PPV_ARGS(&mDrawIndexedIndirectSignature)); + + DAWN_TRY(DeviceBase::Initialize(new Queue(this))); + // Device shouldn't be used until after DeviceBase::Initialize so we must wait until after + // device initialization to call NextSerial + DAWN_TRY(NextSerial()); + + // The environment can only use DXC when it's available. Override the decision if it is not + // applicable. + DAWN_TRY(ApplyUseDxcToggle()); + + DAWN_TRY(CreateZeroBuffer()); + + return {}; + } + + Device::~Device() { + Destroy(); + } + + ID3D12Device* Device::GetD3D12Device() const { + return mD3d12Device.Get(); + } + + ComPtr<ID3D12CommandQueue> Device::GetCommandQueue() const { + return mCommandQueue; + } + + ID3D12SharingContract* Device::GetSharingContract() const { + return mD3d12SharingContract.Get(); + } + + ComPtr<ID3D12CommandSignature> Device::GetDispatchIndirectSignature() const { + return mDispatchIndirectSignature; + } + + ComPtr<ID3D12CommandSignature> Device::GetDrawIndirectSignature() const { + return mDrawIndirectSignature; + } + + ComPtr<ID3D12CommandSignature> Device::GetDrawIndexedIndirectSignature() const { + return mDrawIndexedIndirectSignature; + } + + ComPtr<IDXGIFactory4> Device::GetFactory() const { + return ToBackend(GetAdapter())->GetBackend()->GetFactory(); + } + + MaybeError Device::ApplyUseDxcToggle() { + if (!ToBackend(GetAdapter())->GetBackend()->GetFunctions()->IsDXCAvailable()) { + ForceSetToggle(Toggle::UseDXC, false); + } else if (IsFeatureEnabled(Feature::ShaderFloat16)) { + // Currently we can only use DXC to compile HLSL shaders using float16. + ForceSetToggle(Toggle::UseDXC, true); + } + + if (IsToggleEnabled(Toggle::UseDXC)) { + DAWN_TRY(ToBackend(GetAdapter())->GetBackend()->EnsureDxcCompiler()); + DAWN_TRY(ToBackend(GetAdapter())->GetBackend()->EnsureDxcLibrary()); + DAWN_TRY(ToBackend(GetAdapter())->GetBackend()->EnsureDxcValidator()); + } + + return {}; + } + + ComPtr<IDxcLibrary> Device::GetDxcLibrary() const { + return ToBackend(GetAdapter())->GetBackend()->GetDxcLibrary(); + } + + ComPtr<IDxcCompiler> Device::GetDxcCompiler() const { + return ToBackend(GetAdapter())->GetBackend()->GetDxcCompiler(); + } + + ComPtr<IDxcValidator> Device::GetDxcValidator() const { + return ToBackend(GetAdapter())->GetBackend()->GetDxcValidator(); + } + + const PlatformFunctions* Device::GetFunctions() const { + return ToBackend(GetAdapter())->GetBackend()->GetFunctions(); + } + + CommandAllocatorManager* Device::GetCommandAllocatorManager() const { + return mCommandAllocatorManager.get(); + } + + ResidencyManager* Device::GetResidencyManager() const { + return mResidencyManager.get(); + } + + ResultOrError<CommandRecordingContext*> Device::GetPendingCommandContext() { + // Callers of GetPendingCommandList do so to record commands. Only reserve a command + // allocator when it is needed so we don't submit empty command lists + if (!mPendingCommands.IsOpen()) { + DAWN_TRY(mPendingCommands.Open(mD3d12Device.Get(), mCommandAllocatorManager.get())); + } + return &mPendingCommands; + } + + MaybeError Device::CreateZeroBuffer() { + BufferDescriptor zeroBufferDescriptor; + zeroBufferDescriptor.usage = wgpu::BufferUsage::CopySrc | wgpu::BufferUsage::CopyDst; + zeroBufferDescriptor.size = kZeroBufferSize; + zeroBufferDescriptor.label = "ZeroBuffer_Internal"; + DAWN_TRY_ASSIGN(mZeroBuffer, Buffer::Create(this, &zeroBufferDescriptor)); + + return {}; + } + + MaybeError Device::ClearBufferToZero(CommandRecordingContext* commandContext, + BufferBase* destination, + uint64_t offset, + uint64_t size) { + // TODO(crbug.com/dawn/852): It would be ideal to clear the buffer in CreateZeroBuffer, but + // the allocation of the staging buffer causes various end2end tests that monitor heap usage + // to fail if it's done during device creation. Perhaps ClearUnorderedAccessView*() can be + // used to avoid that. + if (!mZeroBuffer->IsDataInitialized()) { + DynamicUploader* uploader = GetDynamicUploader(); + UploadHandle uploadHandle; + DAWN_TRY_ASSIGN(uploadHandle, + uploader->Allocate(kZeroBufferSize, GetPendingCommandSerial(), + kCopyBufferToBufferOffsetAlignment)); + + memset(uploadHandle.mappedBuffer, 0u, kZeroBufferSize); + + CopyFromStagingToBufferImpl(commandContext, uploadHandle.stagingBuffer, + uploadHandle.startOffset, mZeroBuffer.Get(), 0, + kZeroBufferSize); + + mZeroBuffer->SetIsDataInitialized(); + } + + Buffer* dstBuffer = ToBackend(destination); + + // Necessary to ensure residency of the zero buffer. + mZeroBuffer->TrackUsageAndTransitionNow(commandContext, wgpu::BufferUsage::CopySrc); + dstBuffer->TrackUsageAndTransitionNow(commandContext, wgpu::BufferUsage::CopyDst); + + while (size > 0) { + uint64_t copySize = std::min(kZeroBufferSize, size); + commandContext->GetCommandList()->CopyBufferRegion( + dstBuffer->GetD3D12Resource(), offset, mZeroBuffer->GetD3D12Resource(), 0, + copySize); + + offset += copySize; + size -= copySize; + } + + return {}; + } + + MaybeError Device::TickImpl() { + // Perform cleanup operations to free unused objects + ExecutionSerial completedSerial = GetCompletedCommandSerial(); + + mResourceAllocatorManager->Tick(completedSerial); + DAWN_TRY(mCommandAllocatorManager->Tick(completedSerial)); + mViewShaderVisibleDescriptorAllocator->Tick(completedSerial); + mSamplerShaderVisibleDescriptorAllocator->Tick(completedSerial); + mRenderTargetViewAllocator->Tick(completedSerial); + mDepthStencilViewAllocator->Tick(completedSerial); + mUsedComObjectRefs.ClearUpTo(completedSerial); + + if (mPendingCommands.IsOpen()) { + DAWN_TRY(ExecutePendingCommandContext()); + DAWN_TRY(NextSerial()); + } + + DAWN_TRY(CheckDebugLayerAndGenerateErrors()); + + return {}; + } + + MaybeError Device::NextSerial() { + IncrementLastSubmittedCommandSerial(); + + return CheckHRESULT( + mCommandQueue->Signal(mFence.Get(), uint64_t(GetLastSubmittedCommandSerial())), + "D3D12 command queue signal fence"); + } + + MaybeError Device::WaitForSerial(ExecutionSerial serial) { + DAWN_TRY(CheckPassedSerials()); + if (GetCompletedCommandSerial() < serial) { + DAWN_TRY(CheckHRESULT(mFence->SetEventOnCompletion(uint64_t(serial), mFenceEvent), + "D3D12 set event on completion")); + WaitForSingleObject(mFenceEvent, INFINITE); + DAWN_TRY(CheckPassedSerials()); + } + return {}; + } + + ResultOrError<ExecutionSerial> Device::CheckAndUpdateCompletedSerials() { + ExecutionSerial completedSerial = ExecutionSerial(mFence->GetCompletedValue()); + if (DAWN_UNLIKELY(completedSerial == ExecutionSerial(UINT64_MAX))) { + // GetCompletedValue returns UINT64_MAX if the device was removed. + // Try to query the failure reason. + DAWN_TRY(CheckHRESULT(mD3d12Device->GetDeviceRemovedReason(), + "ID3D12Device::GetDeviceRemovedReason")); + // Otherwise, return a generic device lost error. + return DAWN_DEVICE_LOST_ERROR("Device lost"); + } + + if (completedSerial <= GetCompletedCommandSerial()) { + return ExecutionSerial(0); + } + + return completedSerial; + } + + void Device::ReferenceUntilUnused(ComPtr<IUnknown> object) { + mUsedComObjectRefs.Enqueue(object, GetPendingCommandSerial()); + } + + MaybeError Device::ExecutePendingCommandContext() { + return mPendingCommands.ExecuteCommandList(this); + } + + ResultOrError<Ref<BindGroupBase>> Device::CreateBindGroupImpl( + const BindGroupDescriptor* descriptor) { + return BindGroup::Create(this, descriptor); + } + ResultOrError<Ref<BindGroupLayoutBase>> Device::CreateBindGroupLayoutImpl( + const BindGroupLayoutDescriptor* descriptor, + PipelineCompatibilityToken pipelineCompatibilityToken) { + return BindGroupLayout::Create(this, descriptor, pipelineCompatibilityToken); + } + ResultOrError<Ref<BufferBase>> Device::CreateBufferImpl(const BufferDescriptor* descriptor) { + return Buffer::Create(this, descriptor); + } + ResultOrError<Ref<CommandBufferBase>> Device::CreateCommandBuffer( + CommandEncoder* encoder, + const CommandBufferDescriptor* descriptor) { + return CommandBuffer::Create(encoder, descriptor); + } + Ref<ComputePipelineBase> Device::CreateUninitializedComputePipelineImpl( + const ComputePipelineDescriptor* descriptor) { + return ComputePipeline::CreateUninitialized(this, descriptor); + } + ResultOrError<Ref<PipelineLayoutBase>> Device::CreatePipelineLayoutImpl( + const PipelineLayoutDescriptor* descriptor) { + return PipelineLayout::Create(this, descriptor); + } + ResultOrError<Ref<QuerySetBase>> Device::CreateQuerySetImpl( + const QuerySetDescriptor* descriptor) { + return QuerySet::Create(this, descriptor); + } + Ref<RenderPipelineBase> Device::CreateUninitializedRenderPipelineImpl( + const RenderPipelineDescriptor* descriptor) { + return RenderPipeline::CreateUninitialized(this, descriptor); + } + ResultOrError<Ref<SamplerBase>> Device::CreateSamplerImpl(const SamplerDescriptor* descriptor) { + return Sampler::Create(this, descriptor); + } + ResultOrError<Ref<ShaderModuleBase>> Device::CreateShaderModuleImpl( + const ShaderModuleDescriptor* descriptor, + ShaderModuleParseResult* parseResult) { + return ShaderModule::Create(this, descriptor, parseResult); + } + ResultOrError<Ref<SwapChainBase>> Device::CreateSwapChainImpl( + const SwapChainDescriptor* descriptor) { + return OldSwapChain::Create(this, descriptor); + } + ResultOrError<Ref<NewSwapChainBase>> Device::CreateSwapChainImpl( + Surface* surface, + NewSwapChainBase* previousSwapChain, + const SwapChainDescriptor* descriptor) { + return SwapChain::Create(this, surface, previousSwapChain, descriptor); + } + ResultOrError<Ref<TextureBase>> Device::CreateTextureImpl(const TextureDescriptor* descriptor) { + return Texture::Create(this, descriptor); + } + ResultOrError<Ref<TextureViewBase>> Device::CreateTextureViewImpl( + TextureBase* texture, + const TextureViewDescriptor* descriptor) { + return TextureView::Create(texture, descriptor); + } + void Device::InitializeComputePipelineAsyncImpl(Ref<ComputePipelineBase> computePipeline, + WGPUCreateComputePipelineAsyncCallback callback, + void* userdata) { + ComputePipeline::InitializeAsync(std::move(computePipeline), callback, userdata); + } + void Device::InitializeRenderPipelineAsyncImpl(Ref<RenderPipelineBase> renderPipeline, + WGPUCreateRenderPipelineAsyncCallback callback, + void* userdata) { + RenderPipeline::InitializeAsync(std::move(renderPipeline), callback, userdata); + } + + ResultOrError<std::unique_ptr<StagingBufferBase>> Device::CreateStagingBuffer(size_t size) { + std::unique_ptr<StagingBufferBase> stagingBuffer = + std::make_unique<StagingBuffer>(size, this); + DAWN_TRY(stagingBuffer->Initialize()); + return std::move(stagingBuffer); + } + + MaybeError Device::CopyFromStagingToBuffer(StagingBufferBase* source, + uint64_t sourceOffset, + BufferBase* destination, + uint64_t destinationOffset, + uint64_t size) { + CommandRecordingContext* commandRecordingContext; + DAWN_TRY_ASSIGN(commandRecordingContext, GetPendingCommandContext()); + + Buffer* dstBuffer = ToBackend(destination); + + bool cleared; + DAWN_TRY_ASSIGN(cleared, dstBuffer->EnsureDataInitializedAsDestination( + commandRecordingContext, destinationOffset, size)); + DAWN_UNUSED(cleared); + + CopyFromStagingToBufferImpl(commandRecordingContext, source, sourceOffset, destination, + destinationOffset, size); + + return {}; + } + + void Device::CopyFromStagingToBufferImpl(CommandRecordingContext* commandContext, + StagingBufferBase* source, + uint64_t sourceOffset, + BufferBase* destination, + uint64_t destinationOffset, + uint64_t size) { + ASSERT(commandContext != nullptr); + Buffer* dstBuffer = ToBackend(destination); + StagingBuffer* srcBuffer = ToBackend(source); + dstBuffer->TrackUsageAndTransitionNow(commandContext, wgpu::BufferUsage::CopyDst); + + commandContext->GetCommandList()->CopyBufferRegion( + dstBuffer->GetD3D12Resource(), destinationOffset, srcBuffer->GetResource(), + sourceOffset, size); + } + + MaybeError Device::CopyFromStagingToTexture(const StagingBufferBase* source, + const TextureDataLayout& src, + TextureCopy* dst, + const Extent3D& copySizePixels) { + CommandRecordingContext* commandContext; + DAWN_TRY_ASSIGN(commandContext, GetPendingCommandContext()); + Texture* texture = ToBackend(dst->texture.Get()); + + SubresourceRange range = GetSubresourcesAffectedByCopy(*dst, copySizePixels); + + if (IsCompleteSubresourceCopiedTo(texture, copySizePixels, dst->mipLevel)) { + texture->SetIsSubresourceContentInitialized(true, range); + } else { + texture->EnsureSubresourceContentInitialized(commandContext, range); + } + + texture->TrackUsageAndTransitionNow(commandContext, wgpu::TextureUsage::CopyDst, range); + + RecordBufferTextureCopyWithBufferHandle( + BufferTextureCopyDirection::B2T, commandContext->GetCommandList(), + ToBackend(source)->GetResource(), src.offset, src.bytesPerRow, src.rowsPerImage, *dst, + copySizePixels); + + return {}; + } + + void Device::DeallocateMemory(ResourceHeapAllocation& allocation) { + mResourceAllocatorManager->DeallocateMemory(allocation); + } + + ResultOrError<ResourceHeapAllocation> Device::AllocateMemory( + D3D12_HEAP_TYPE heapType, + const D3D12_RESOURCE_DESC& resourceDescriptor, + D3D12_RESOURCE_STATES initialUsage) { + return mResourceAllocatorManager->AllocateMemory(heapType, resourceDescriptor, + initialUsage); + } + + Ref<TextureBase> Device::CreateD3D12ExternalTexture( + const TextureDescriptor* descriptor, + ComPtr<ID3D12Resource> d3d12Texture, + Ref<D3D11on12ResourceCacheEntry> d3d11on12Resource, + bool isSwapChainTexture, + bool isInitialized) { + Ref<Texture> dawnTexture; + if (ConsumedError(Texture::CreateExternalImage(this, descriptor, std::move(d3d12Texture), + std::move(d3d11on12Resource), + isSwapChainTexture, isInitialized), + &dawnTexture)) { + return nullptr; + } + return {dawnTexture}; + } + + ComPtr<ID3D11On12Device> Device::GetOrCreateD3D11on12Device() { + if (mD3d11On12Device == nullptr) { + ComPtr<ID3D11Device> d3d11Device; + D3D_FEATURE_LEVEL d3dFeatureLevel; + IUnknown* const iUnknownQueue = mCommandQueue.Get(); + if (FAILED(GetFunctions()->d3d11on12CreateDevice(mD3d12Device.Get(), 0, nullptr, 0, + &iUnknownQueue, 1, 1, &d3d11Device, + nullptr, &d3dFeatureLevel))) { + return nullptr; + } + + ComPtr<ID3D11On12Device> d3d11on12Device; + HRESULT hr = d3d11Device.As(&d3d11on12Device); + ASSERT(SUCCEEDED(hr)); + + mD3d11On12Device = std::move(d3d11on12Device); + } + return mD3d11On12Device; + } + + const D3D12DeviceInfo& Device::GetDeviceInfo() const { + return ToBackend(GetAdapter())->GetDeviceInfo(); + } + + void Device::InitTogglesFromDriver() { + const bool useResourceHeapTier2 = (GetDeviceInfo().resourceHeapTier >= 2); + SetToggle(Toggle::UseD3D12ResourceHeapTier2, useResourceHeapTier2); + SetToggle(Toggle::UseD3D12RenderPass, GetDeviceInfo().supportsRenderPass); + SetToggle(Toggle::UseD3D12ResidencyManagement, true); + SetToggle(Toggle::UseDXC, false); + + // Disable optimizations when using FXC + // See https://crbug.com/dawn/1203 + SetToggle(Toggle::FxcOptimizations, false); + + // By default use the maximum shader-visible heap size allowed. + SetToggle(Toggle::UseD3D12SmallShaderVisibleHeapForTesting, false); + + uint32_t deviceId = GetAdapter()->GetDeviceId(); + uint32_t vendorId = GetAdapter()->GetVendorId(); + + // Currently this workaround is only needed on Intel Gen9 and Gen9.5 GPUs. + // See http://crbug.com/1161355 for more information. + if (gpu_info::IsIntel(vendorId) && + (gpu_info::IsSkylake(deviceId) || gpu_info::IsKabylake(deviceId) || + gpu_info::IsCoffeelake(deviceId))) { + constexpr gpu_info::D3DDriverVersion kFirstDriverVersionWithFix = {30, 0, 100, 9864}; + if (gpu_info::CompareD3DDriverVersion(vendorId, + ToBackend(GetAdapter())->GetDriverVersion(), + kFirstDriverVersionWithFix) < 0) { + SetToggle( + Toggle::UseTempBufferInSmallFormatTextureToTextureCopyFromGreaterToLessMipLevel, + true); + } + } + } + + MaybeError Device::WaitForIdleForDestruction() { + // Immediately forget about all pending commands + mPendingCommands.Release(); + + DAWN_TRY(NextSerial()); + // Wait for all in-flight commands to finish executing + DAWN_TRY(WaitForSerial(GetLastSubmittedCommandSerial())); + + return {}; + } + + MaybeError Device::CheckDebugLayerAndGenerateErrors() { + if (!GetAdapter()->GetInstance()->IsBackendValidationEnabled()) { + return {}; + } + + ComPtr<ID3D12InfoQueue> infoQueue; + DAWN_TRY(CheckHRESULT(mD3d12Device.As(&infoQueue), + "D3D12 QueryInterface ID3D12Device to ID3D12InfoQueue")); + uint64_t totalErrors = infoQueue->GetNumStoredMessagesAllowedByRetrievalFilter(); + + // Check if any errors have occurred otherwise we would be creating an empty error. Note + // that we use GetNumStoredMessagesAllowedByRetrievalFilter instead of GetNumStoredMessages + // because we only convert WARNINGS or higher messages to dawn errors. + if (totalErrors == 0) { + return {}; + } + + std::ostringstream messages; + uint64_t errorsToPrint = std::min(kMaxDebugMessagesToPrint, totalErrors); + for (uint64_t i = 0; i < errorsToPrint; ++i) { + SIZE_T messageLength = 0; + HRESULT hr = infoQueue->GetMessage(i, nullptr, &messageLength); + if (FAILED(hr)) { + messages << " ID3D12InfoQueue::GetMessage failed with " << hr << '\n'; + continue; + } + + std::unique_ptr<uint8_t[]> messageData(new uint8_t[messageLength]); + D3D12_MESSAGE* message = reinterpret_cast<D3D12_MESSAGE*>(messageData.get()); + hr = infoQueue->GetMessage(i, message, &messageLength); + if (FAILED(hr)) { + messages << " ID3D12InfoQueue::GetMessage failed with " << hr << '\n'; + continue; + } + + messages << message->pDescription << " (" << message->ID << ")\n"; + } + if (errorsToPrint < totalErrors) { + messages << (totalErrors - errorsToPrint) << " messages silenced\n"; + } + // We only print up to the first kMaxDebugMessagesToPrint errors + infoQueue->ClearStoredMessages(); + + return DAWN_INTERNAL_ERROR(messages.str()); + } + + void Device::DestroyImpl() { + ASSERT(GetState() == State::Disconnected); + + // Immediately forget about all pending commands for the case where device is lost on its + // own and WaitForIdleForDestruction isn't called. + mPendingCommands.Release(); + + if (mFenceEvent != nullptr) { + ::CloseHandle(mFenceEvent); + } + + // Release recycled resource heaps. + if (mResourceAllocatorManager != nullptr) { + mResourceAllocatorManager->DestroyPool(); + } + + // We need to handle clearing up com object refs that were enqeued after TickImpl + mUsedComObjectRefs.ClearUpTo(std::numeric_limits<ExecutionSerial>::max()); + + ASSERT(mUsedComObjectRefs.Empty()); + ASSERT(!mPendingCommands.IsOpen()); + } + + ShaderVisibleDescriptorAllocator* Device::GetViewShaderVisibleDescriptorAllocator() const { + return mViewShaderVisibleDescriptorAllocator.get(); + } + + ShaderVisibleDescriptorAllocator* Device::GetSamplerShaderVisibleDescriptorAllocator() const { + return mSamplerShaderVisibleDescriptorAllocator.get(); + } + + StagingDescriptorAllocator* Device::GetViewStagingDescriptorAllocator( + uint32_t descriptorCount) const { + ASSERT(descriptorCount <= kMaxViewDescriptorsPerBindGroup); + // This is Log2 of the next power of two, plus 1. + uint32_t allocatorIndex = descriptorCount == 0 ? 0 : Log2Ceil(descriptorCount) + 1; + return mViewAllocators[allocatorIndex].get(); + } + + StagingDescriptorAllocator* Device::GetSamplerStagingDescriptorAllocator( + uint32_t descriptorCount) const { + ASSERT(descriptorCount <= kMaxSamplerDescriptorsPerBindGroup); + // This is Log2 of the next power of two, plus 1. + uint32_t allocatorIndex = descriptorCount == 0 ? 0 : Log2Ceil(descriptorCount) + 1; + return mSamplerAllocators[allocatorIndex].get(); + } + + StagingDescriptorAllocator* Device::GetRenderTargetViewAllocator() const { + return mRenderTargetViewAllocator.get(); + } + + StagingDescriptorAllocator* Device::GetDepthStencilViewAllocator() const { + return mDepthStencilViewAllocator.get(); + } + + SamplerHeapCache* Device::GetSamplerHeapCache() { + return mSamplerHeapCache.get(); + } + + uint32_t Device::GetOptimalBytesPerRowAlignment() const { + return D3D12_TEXTURE_DATA_PITCH_ALIGNMENT; + } + + // TODO(dawn:512): Once we optimize DynamicUploader allocation with offsets we + // should make this return D3D12_TEXTURE_DATA_PLACEMENT_ALIGNMENT = 512. + // Current implementations would try to allocate additional 511 bytes, + // so we return 1 and let ComputeTextureCopySplits take care of the alignment. + uint64_t Device::GetOptimalBufferToTextureCopyOffsetAlignment() const { + return 1; + } + + float Device::GetTimestampPeriodInNS() const { + return mTimestampPeriod; + } + + bool Device::ShouldDuplicateNumWorkgroupsForDispatchIndirect( + ComputePipelineBase* computePipeline) const { + return ToBackend(computePipeline)->UsesNumWorkgroups(); + } + +} // namespace dawn::native::d3d12
diff --git a/src/dawn/native/d3d12/DeviceD3D12.h b/src/dawn/native/d3d12/DeviceD3D12.h new file mode 100644 index 0000000..1a83792 --- /dev/null +++ b/src/dawn/native/d3d12/DeviceD3D12.h
@@ -0,0 +1,265 @@ +// Copyright 2017 The Dawn Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#ifndef DAWNNATIVE_D3D12_DEVICED3D12_H_ +#define DAWNNATIVE_D3D12_DEVICED3D12_H_ + +#include "dawn/common/SerialQueue.h" +#include "dawn/native/Device.h" +#include "dawn/native/d3d12/CommandRecordingContext.h" +#include "dawn/native/d3d12/D3D12Info.h" +#include "dawn/native/d3d12/Forward.h" +#include "dawn/native/d3d12/TextureD3D12.h" + +namespace dawn::native::d3d12 { + + class CommandAllocatorManager; + class PlatformFunctions; + class ResidencyManager; + class ResourceAllocatorManager; + class SamplerHeapCache; + class ShaderVisibleDescriptorAllocator; + class StagingDescriptorAllocator; + +#define ASSERT_SUCCESS(hr) \ + do { \ + HRESULT succeeded = hr; \ + ASSERT(SUCCEEDED(succeeded)); \ + } while (0) + + // Definition of backend types + class Device final : public DeviceBase { + public: + static ResultOrError<Ref<Device>> Create(Adapter* adapter, + const DeviceDescriptor* descriptor); + ~Device() override; + + MaybeError Initialize(); + + ResultOrError<Ref<CommandBufferBase>> CreateCommandBuffer( + CommandEncoder* encoder, + const CommandBufferDescriptor* descriptor) override; + + MaybeError TickImpl() override; + + ID3D12Device* GetD3D12Device() const; + ComPtr<ID3D12CommandQueue> GetCommandQueue() const; + ID3D12SharingContract* GetSharingContract() const; + + ComPtr<ID3D12CommandSignature> GetDispatchIndirectSignature() const; + ComPtr<ID3D12CommandSignature> GetDrawIndirectSignature() const; + ComPtr<ID3D12CommandSignature> GetDrawIndexedIndirectSignature() const; + + CommandAllocatorManager* GetCommandAllocatorManager() const; + ResidencyManager* GetResidencyManager() const; + + const PlatformFunctions* GetFunctions() const; + ComPtr<IDXGIFactory4> GetFactory() const; + ComPtr<IDxcLibrary> GetDxcLibrary() const; + ComPtr<IDxcCompiler> GetDxcCompiler() const; + ComPtr<IDxcValidator> GetDxcValidator() const; + + ResultOrError<CommandRecordingContext*> GetPendingCommandContext(); + + MaybeError ClearBufferToZero(CommandRecordingContext* commandContext, + BufferBase* destination, + uint64_t destinationOffset, + uint64_t size); + + const D3D12DeviceInfo& GetDeviceInfo() const; + + MaybeError NextSerial(); + MaybeError WaitForSerial(ExecutionSerial serial); + + void ReferenceUntilUnused(ComPtr<IUnknown> object); + + MaybeError ExecutePendingCommandContext(); + + ResultOrError<std::unique_ptr<StagingBufferBase>> CreateStagingBuffer(size_t size) override; + MaybeError CopyFromStagingToBuffer(StagingBufferBase* source, + uint64_t sourceOffset, + BufferBase* destination, + uint64_t destinationOffset, + uint64_t size) override; + + void CopyFromStagingToBufferImpl(CommandRecordingContext* commandContext, + StagingBufferBase* source, + uint64_t sourceOffset, + BufferBase* destination, + uint64_t destinationOffset, + uint64_t size); + + MaybeError CopyFromStagingToTexture(const StagingBufferBase* source, + const TextureDataLayout& src, + TextureCopy* dst, + const Extent3D& copySizePixels) override; + + ResultOrError<ResourceHeapAllocation> AllocateMemory( + D3D12_HEAP_TYPE heapType, + const D3D12_RESOURCE_DESC& resourceDescriptor, + D3D12_RESOURCE_STATES initialUsage); + + void DeallocateMemory(ResourceHeapAllocation& allocation); + + ShaderVisibleDescriptorAllocator* GetViewShaderVisibleDescriptorAllocator() const; + ShaderVisibleDescriptorAllocator* GetSamplerShaderVisibleDescriptorAllocator() const; + + // Returns nullptr when descriptor count is zero. + StagingDescriptorAllocator* GetViewStagingDescriptorAllocator( + uint32_t descriptorCount) const; + + StagingDescriptorAllocator* GetSamplerStagingDescriptorAllocator( + uint32_t descriptorCount) const; + + SamplerHeapCache* GetSamplerHeapCache(); + + StagingDescriptorAllocator* GetRenderTargetViewAllocator() const; + + StagingDescriptorAllocator* GetDepthStencilViewAllocator() const; + + Ref<TextureBase> CreateD3D12ExternalTexture( + const TextureDescriptor* descriptor, + ComPtr<ID3D12Resource> d3d12Texture, + Ref<D3D11on12ResourceCacheEntry> d3d11on12Resource, + bool isSwapChainTexture, + bool isInitialized); + + ComPtr<ID3D11On12Device> GetOrCreateD3D11on12Device(); + + void InitTogglesFromDriver(); + + uint32_t GetOptimalBytesPerRowAlignment() const override; + uint64_t GetOptimalBufferToTextureCopyOffsetAlignment() const override; + + float GetTimestampPeriodInNS() const override; + + bool ShouldDuplicateNumWorkgroupsForDispatchIndirect( + ComputePipelineBase* computePipeline) const override; + + private: + using DeviceBase::DeviceBase; + + ResultOrError<Ref<BindGroupBase>> CreateBindGroupImpl( + const BindGroupDescriptor* descriptor) override; + ResultOrError<Ref<BindGroupLayoutBase>> CreateBindGroupLayoutImpl( + const BindGroupLayoutDescriptor* descriptor, + PipelineCompatibilityToken pipelineCompatibilityToken) override; + ResultOrError<Ref<BufferBase>> CreateBufferImpl( + const BufferDescriptor* descriptor) override; + ResultOrError<Ref<PipelineLayoutBase>> CreatePipelineLayoutImpl( + const PipelineLayoutDescriptor* descriptor) override; + ResultOrError<Ref<QuerySetBase>> CreateQuerySetImpl( + const QuerySetDescriptor* descriptor) override; + ResultOrError<Ref<SamplerBase>> CreateSamplerImpl( + const SamplerDescriptor* descriptor) override; + ResultOrError<Ref<ShaderModuleBase>> CreateShaderModuleImpl( + const ShaderModuleDescriptor* descriptor, + ShaderModuleParseResult* parseResult) override; + ResultOrError<Ref<SwapChainBase>> CreateSwapChainImpl( + const SwapChainDescriptor* descriptor) override; + ResultOrError<Ref<NewSwapChainBase>> CreateSwapChainImpl( + Surface* surface, + NewSwapChainBase* previousSwapChain, + const SwapChainDescriptor* descriptor) override; + ResultOrError<Ref<TextureBase>> CreateTextureImpl( + const TextureDescriptor* descriptor) override; + ResultOrError<Ref<TextureViewBase>> CreateTextureViewImpl( + TextureBase* texture, + const TextureViewDescriptor* descriptor) override; + Ref<ComputePipelineBase> CreateUninitializedComputePipelineImpl( + const ComputePipelineDescriptor* descriptor) override; + Ref<RenderPipelineBase> CreateUninitializedRenderPipelineImpl( + const RenderPipelineDescriptor* descriptor) override; + void InitializeComputePipelineAsyncImpl(Ref<ComputePipelineBase> computePipeline, + WGPUCreateComputePipelineAsyncCallback callback, + void* userdata) override; + void InitializeRenderPipelineAsyncImpl(Ref<RenderPipelineBase> renderPipeline, + WGPUCreateRenderPipelineAsyncCallback callback, + void* userdata) override; + + void DestroyImpl() override; + MaybeError WaitForIdleForDestruction() override; + + MaybeError CheckDebugLayerAndGenerateErrors(); + + MaybeError ApplyUseDxcToggle(); + + MaybeError CreateZeroBuffer(); + + ComPtr<ID3D12Fence> mFence; + HANDLE mFenceEvent = nullptr; + ResultOrError<ExecutionSerial> CheckAndUpdateCompletedSerials() override; + + ComPtr<ID3D12Device> mD3d12Device; // Device is owned by adapter and will not be outlived. + ComPtr<ID3D12CommandQueue> mCommandQueue; + ComPtr<ID3D12SharingContract> mD3d12SharingContract; + + // 11on12 device corresponding to mCommandQueue + ComPtr<ID3D11On12Device> mD3d11On12Device; + + ComPtr<ID3D12CommandSignature> mDispatchIndirectSignature; + ComPtr<ID3D12CommandSignature> mDrawIndirectSignature; + ComPtr<ID3D12CommandSignature> mDrawIndexedIndirectSignature; + + CommandRecordingContext mPendingCommands; + + SerialQueue<ExecutionSerial, ComPtr<IUnknown>> mUsedComObjectRefs; + + std::unique_ptr<CommandAllocatorManager> mCommandAllocatorManager; + std::unique_ptr<ResourceAllocatorManager> mResourceAllocatorManager; + std::unique_ptr<ResidencyManager> mResidencyManager; + + static constexpr uint32_t kMaxSamplerDescriptorsPerBindGroup = + 3 * kMaxSamplersPerShaderStage; + static constexpr uint32_t kMaxViewDescriptorsPerBindGroup = + kMaxBindingsPerPipelineLayout - kMaxSamplerDescriptorsPerBindGroup; + + static constexpr uint32_t kNumSamplerDescriptorAllocators = + ConstexprLog2Ceil(kMaxSamplerDescriptorsPerBindGroup) + 1; + static constexpr uint32_t kNumViewDescriptorAllocators = + ConstexprLog2Ceil(kMaxViewDescriptorsPerBindGroup) + 1; + + // Index corresponds to Log2Ceil(descriptorCount) where descriptorCount is in + // the range [0, kMaxSamplerDescriptorsPerBindGroup]. + std::array<std::unique_ptr<StagingDescriptorAllocator>, kNumViewDescriptorAllocators + 1> + mViewAllocators; + + // Index corresponds to Log2Ceil(descriptorCount) where descriptorCount is in + // the range [0, kMaxViewDescriptorsPerBindGroup]. + std::array<std::unique_ptr<StagingDescriptorAllocator>, kNumSamplerDescriptorAllocators + 1> + mSamplerAllocators; + + std::unique_ptr<StagingDescriptorAllocator> mRenderTargetViewAllocator; + + std::unique_ptr<StagingDescriptorAllocator> mDepthStencilViewAllocator; + + std::unique_ptr<ShaderVisibleDescriptorAllocator> mViewShaderVisibleDescriptorAllocator; + + std::unique_ptr<ShaderVisibleDescriptorAllocator> mSamplerShaderVisibleDescriptorAllocator; + + // Sampler cache needs to be destroyed before the CPU sampler allocator to ensure the final + // release is called. + std::unique_ptr<SamplerHeapCache> mSamplerHeapCache; + + // A buffer filled with zeros that is used to copy into other buffers when they need to be + // cleared. + Ref<Buffer> mZeroBuffer; + + // The number of nanoseconds required for a timestamp query to be incremented by 1 + float mTimestampPeriod = 1.0f; + }; + +} // namespace dawn::native::d3d12 + +#endif // DAWNNATIVE_D3D12_DEVICED3D12_H_
diff --git a/src/dawn/native/d3d12/Forward.h b/src/dawn/native/d3d12/Forward.h new file mode 100644 index 0000000..a7aedb7 --- /dev/null +++ b/src/dawn/native/d3d12/Forward.h
@@ -0,0 +1,69 @@ +// Copyright 2018 The Dawn Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#ifndef DAWNNATIVE_D3D12_FORWARD_H_ +#define DAWNNATIVE_D3D12_FORWARD_H_ + +#include "dawn/native/ToBackend.h" + +namespace dawn::native::d3d12 { + + class Adapter; + class BindGroup; + class BindGroupLayout; + class Buffer; + class CommandBuffer; + class ComputePipeline; + class Device; + class Heap; + class PipelineLayout; + class QuerySet; + class Queue; + class RenderPipeline; + class Sampler; + class ShaderModule; + class StagingBuffer; + class SwapChain; + class Texture; + class TextureView; + + struct D3D12BackendTraits { + using AdapterType = Adapter; + using BindGroupType = BindGroup; + using BindGroupLayoutType = BindGroupLayout; + using BufferType = Buffer; + using CommandBufferType = CommandBuffer; + using ComputePipelineType = ComputePipeline; + using DeviceType = Device; + using PipelineLayoutType = PipelineLayout; + using QuerySetType = QuerySet; + using QueueType = Queue; + using RenderPipelineType = RenderPipeline; + using ResourceHeapType = Heap; + using SamplerType = Sampler; + using ShaderModuleType = ShaderModule; + using StagingBufferType = StagingBuffer; + using SwapChainType = SwapChain; + using TextureType = Texture; + using TextureViewType = TextureView; + }; + + template <typename T> + auto ToBackend(T&& common) -> decltype(ToBackendBase<D3D12BackendTraits>(common)) { + return ToBackendBase<D3D12BackendTraits>(common); + } + +} // namespace dawn::native::d3d12 + +#endif // DAWNNATIVE_D3D12_FORWARD_H_
diff --git a/src/dawn/native/d3d12/GPUDescriptorHeapAllocationD3D12.cpp b/src/dawn/native/d3d12/GPUDescriptorHeapAllocationD3D12.cpp new file mode 100644 index 0000000..e5d4fb9 --- /dev/null +++ b/src/dawn/native/d3d12/GPUDescriptorHeapAllocationD3D12.cpp
@@ -0,0 +1,39 @@ +// Copyright 2020 The Dawn Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include "dawn/native/d3d12/GPUDescriptorHeapAllocationD3D12.h" + +namespace dawn::native::d3d12 { + + GPUDescriptorHeapAllocation::GPUDescriptorHeapAllocation( + D3D12_GPU_DESCRIPTOR_HANDLE baseDescriptor, + ExecutionSerial lastUsageSerial, + HeapVersionID heapSerial) + : mBaseDescriptor(baseDescriptor), + mLastUsageSerial(lastUsageSerial), + mHeapSerial(heapSerial) { + } + + D3D12_GPU_DESCRIPTOR_HANDLE GPUDescriptorHeapAllocation::GetBaseDescriptor() const { + return mBaseDescriptor; + } + + ExecutionSerial GPUDescriptorHeapAllocation::GetLastUsageSerial() const { + return mLastUsageSerial; + } + + HeapVersionID GPUDescriptorHeapAllocation::GetHeapSerial() const { + return mHeapSerial; + } +} // namespace dawn::native::d3d12
diff --git a/src/dawn/native/d3d12/GPUDescriptorHeapAllocationD3D12.h b/src/dawn/native/d3d12/GPUDescriptorHeapAllocationD3D12.h new file mode 100644 index 0000000..7f7ce1e --- /dev/null +++ b/src/dawn/native/d3d12/GPUDescriptorHeapAllocationD3D12.h
@@ -0,0 +1,44 @@ +// Copyright 2020 The Dawn Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#ifndef DAWNNATIVE_D3D12_GPUDESCRIPTORHEAPALLOCATION_H_ +#define DAWNNATIVE_D3D12_GPUDESCRIPTORHEAPALLOCATION_H_ + +#include "dawn/native/IntegerTypes.h" +#include "dawn/native/d3d12/IntegerTypes.h" +#include "dawn/native/d3d12/d3d12_platform.h" + +namespace dawn::native::d3d12 { + + // Wrapper for a handle into a GPU-only descriptor heap. + class GPUDescriptorHeapAllocation { + public: + GPUDescriptorHeapAllocation() = default; + GPUDescriptorHeapAllocation(D3D12_GPU_DESCRIPTOR_HANDLE baseDescriptor, + ExecutionSerial lastUsageSerial, + HeapVersionID heapSerial); + + D3D12_GPU_DESCRIPTOR_HANDLE GetBaseDescriptor() const; + ExecutionSerial GetLastUsageSerial() const; + HeapVersionID GetHeapSerial() const; + + private: + D3D12_GPU_DESCRIPTOR_HANDLE mBaseDescriptor = {0}; + ExecutionSerial mLastUsageSerial = ExecutionSerial(0); + HeapVersionID mHeapSerial = HeapVersionID(0); + }; + +} // namespace dawn::native::d3d12 + +#endif // DAWNNATIVE_D3D12_CPUDESCRIPTORHEAPALLOCATION_H_
diff --git a/src/dawn/native/d3d12/HeapAllocatorD3D12.cpp b/src/dawn/native/d3d12/HeapAllocatorD3D12.cpp new file mode 100644 index 0000000..5a26be3 --- /dev/null +++ b/src/dawn/native/d3d12/HeapAllocatorD3D12.cpp
@@ -0,0 +1,71 @@ +// Copyright 2019 The Dawn Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include "dawn/native/d3d12/HeapAllocatorD3D12.h" +#include "dawn/native/d3d12/D3D12Error.h" +#include "dawn/native/d3d12/DeviceD3D12.h" +#include "dawn/native/d3d12/HeapD3D12.h" +#include "dawn/native/d3d12/ResidencyManagerD3D12.h" + +namespace dawn::native::d3d12 { + + HeapAllocator::HeapAllocator(Device* device, + D3D12_HEAP_TYPE heapType, + D3D12_HEAP_FLAGS heapFlags, + MemorySegment memorySegment) + : mDevice(device), + mHeapType(heapType), + mHeapFlags(heapFlags), + mMemorySegment(memorySegment) { + } + + ResultOrError<std::unique_ptr<ResourceHeapBase>> HeapAllocator::AllocateResourceHeap( + uint64_t size) { + D3D12_HEAP_DESC heapDesc; + heapDesc.SizeInBytes = size; + heapDesc.Properties.Type = mHeapType; + heapDesc.Properties.CPUPageProperty = D3D12_CPU_PAGE_PROPERTY_UNKNOWN; + heapDesc.Properties.MemoryPoolPreference = D3D12_MEMORY_POOL_UNKNOWN; + heapDesc.Properties.CreationNodeMask = 0; + heapDesc.Properties.VisibleNodeMask = 0; + // It is preferred to use a size that is a multiple of the alignment. + // However, MSAA heaps are always aligned to 4MB instead of 64KB. This means + // if the heap size is too small, the VMM would fragment. + // TODO(crbug.com/dawn/849): Consider having MSAA vs non-MSAA heaps. + heapDesc.Alignment = D3D12_DEFAULT_MSAA_RESOURCE_PLACEMENT_ALIGNMENT; + heapDesc.Flags = mHeapFlags; + + // CreateHeap will implicitly make the created heap resident. We must ensure enough free + // memory exists before allocating to avoid an out-of-memory error when overcommitted. + DAWN_TRY(mDevice->GetResidencyManager()->EnsureCanAllocate(size, mMemorySegment)); + + ComPtr<ID3D12Heap> d3d12Heap; + DAWN_TRY(CheckOutOfMemoryHRESULT( + mDevice->GetD3D12Device()->CreateHeap(&heapDesc, IID_PPV_ARGS(&d3d12Heap)), + "ID3D12Device::CreateHeap")); + + std::unique_ptr<ResourceHeapBase> heapBase = + std::make_unique<Heap>(std::move(d3d12Heap), mMemorySegment, size); + + // Calling CreateHeap implicitly calls MakeResident on the new heap. We must track this to + // avoid calling MakeResident a second time. + mDevice->GetResidencyManager()->TrackResidentAllocation(ToBackend(heapBase.get())); + return std::move(heapBase); + } + + void HeapAllocator::DeallocateResourceHeap(std::unique_ptr<ResourceHeapBase> heap) { + mDevice->ReferenceUntilUnused(static_cast<Heap*>(heap.get())->GetD3D12Heap()); + } + +} // namespace dawn::native::d3d12
diff --git a/src/dawn/native/d3d12/HeapAllocatorD3D12.h b/src/dawn/native/d3d12/HeapAllocatorD3D12.h new file mode 100644 index 0000000..055f739 --- /dev/null +++ b/src/dawn/native/d3d12/HeapAllocatorD3D12.h
@@ -0,0 +1,48 @@ +// Copyright 2019 The Dawn Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#ifndef DAWNNATIVE_D3D12_HEAPALLOCATORD3D12_H_ +#define DAWNNATIVE_D3D12_HEAPALLOCATORD3D12_H_ + +#include "dawn/native/D3D12Backend.h" +#include "dawn/native/ResourceHeapAllocator.h" +#include "dawn/native/d3d12/d3d12_platform.h" + +namespace dawn::native::d3d12 { + + class Device; + + // Wrapper to allocate a D3D12 heap. + class HeapAllocator : public ResourceHeapAllocator { + public: + HeapAllocator(Device* device, + D3D12_HEAP_TYPE heapType, + D3D12_HEAP_FLAGS heapFlags, + MemorySegment memorySegment); + ~HeapAllocator() override = default; + + ResultOrError<std::unique_ptr<ResourceHeapBase>> AllocateResourceHeap( + uint64_t size) override; + void DeallocateResourceHeap(std::unique_ptr<ResourceHeapBase> allocation) override; + + private: + Device* mDevice; + D3D12_HEAP_TYPE mHeapType; + D3D12_HEAP_FLAGS mHeapFlags; + MemorySegment mMemorySegment; + }; + +} // namespace dawn::native::d3d12 + +#endif // DAWNNATIVE_D3D12_HEAPALLOCATORD3D12_H_
diff --git a/src/dawn/native/d3d12/HeapD3D12.cpp b/src/dawn/native/d3d12/HeapD3D12.cpp new file mode 100644 index 0000000..7426757 --- /dev/null +++ b/src/dawn/native/d3d12/HeapD3D12.cpp
@@ -0,0 +1,31 @@ +// Copyright 2019 The Dawn Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include "dawn/native/d3d12/HeapD3D12.h" + +namespace dawn::native::d3d12 { + Heap::Heap(ComPtr<ID3D12Pageable> d3d12Pageable, MemorySegment memorySegment, uint64_t size) + : Pageable(std::move(d3d12Pageable), memorySegment, size) { + mD3d12Pageable.As(&mD3d12Heap); + } + + // This function should only be used when mD3D12Pageable was initialized from a + // ID3D12Pageable that was initially created as an ID3D12Heap (i.e. SubAllocation). If the + // ID3D12Pageable was initially created as an ID3D12Resource (i.e. DirectAllocation), then + // use GetD3D12Pageable(). + ID3D12Heap* Heap::GetD3D12Heap() const { + return mD3d12Heap.Get(); + } + +} // namespace dawn::native::d3d12
diff --git a/src/dawn/native/d3d12/HeapD3D12.h b/src/dawn/native/d3d12/HeapD3D12.h new file mode 100644 index 0000000..c160366 --- /dev/null +++ b/src/dawn/native/d3d12/HeapD3D12.h
@@ -0,0 +1,40 @@ +// Copyright 2019 The Dawn Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#ifndef DAWNNATIVE_D3D12_HEAPD3D12_H_ +#define DAWNNATIVE_D3D12_HEAPD3D12_H_ + +#include "dawn/native/ResourceHeap.h" +#include "dawn/native/d3d12/PageableD3D12.h" +#include "dawn/native/d3d12/d3d12_platform.h" + +namespace dawn::native::d3d12 { + + class Device; + + // This class is used to represent ID3D12Heap allocations, as well as an implicit heap + // representing a directly allocated resource. It inherits from Pageable because each Heap must + // be represented in the ResidencyManager. + class Heap : public ResourceHeapBase, public Pageable { + public: + Heap(ComPtr<ID3D12Pageable> d3d12Pageable, MemorySegment memorySegment, uint64_t size); + + ID3D12Heap* GetD3D12Heap() const; + + private: + ComPtr<ID3D12Heap> mD3d12Heap; + }; +} // namespace dawn::native::d3d12 + +#endif // DAWNNATIVE_D3D12_HEAPD3D12_H_
diff --git a/src/dawn/native/d3d12/IntegerTypes.h b/src/dawn/native/d3d12/IntegerTypes.h new file mode 100644 index 0000000..1e3dbfb --- /dev/null +++ b/src/dawn/native/d3d12/IntegerTypes.h
@@ -0,0 +1,31 @@ +// Copyright 2020 The Dawn Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#ifndef DAWNNATIVE_D3D12_INTEGERTYPES_H_ +#define DAWNNATIVE_D3D12_INTEGERTYPES_H_ + +#include "dawn/common/Constants.h" +#include "dawn/common/TypedInteger.h" + +#include <cstdint> + +namespace dawn::native::d3d12 { + + // An ID used to desambiguate between multiple uses of the same descriptor heap in the + // BindGroup allocations. + using HeapVersionID = TypedInteger<struct HeapVersionIDT, uint64_t>; + +} // namespace dawn::native::d3d12 + +#endif // DAWNNATIVE_D3D12_INTEGERTYPES_H_
diff --git a/src/dawn/native/d3d12/NativeSwapChainImplD3D12.cpp b/src/dawn/native/d3d12/NativeSwapChainImplD3D12.cpp new file mode 100644 index 0000000..5156af5 --- /dev/null +++ b/src/dawn/native/d3d12/NativeSwapChainImplD3D12.cpp
@@ -0,0 +1,120 @@ +// Copyright 2018 The Dawn Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include "dawn/native/d3d12/NativeSwapChainImplD3D12.h" + +#include "dawn/common/Assert.h" +#include "dawn/native/d3d12/DeviceD3D12.h" +#include "dawn/native/d3d12/TextureD3D12.h" + +namespace dawn::native::d3d12 { + + namespace { + DXGI_USAGE D3D12SwapChainBufferUsage(WGPUTextureUsage allowedUsages) { + DXGI_USAGE usage = DXGI_CPU_ACCESS_NONE; + if (allowedUsages & WGPUTextureUsage_TextureBinding) { + usage |= DXGI_USAGE_SHADER_INPUT; + } + if (allowedUsages & WGPUTextureUsage_StorageBinding) { + usage |= DXGI_USAGE_UNORDERED_ACCESS; + } + if (allowedUsages & WGPUTextureUsage_RenderAttachment) { + usage |= DXGI_USAGE_RENDER_TARGET_OUTPUT; + } + return usage; + } + + static constexpr unsigned int kFrameCount = 3; + } // anonymous namespace + + NativeSwapChainImpl::NativeSwapChainImpl(Device* device, HWND window) + : mWindow(window), mDevice(device), mInterval(1) { + } + + NativeSwapChainImpl::~NativeSwapChainImpl() { + } + + void NativeSwapChainImpl::Init(DawnWSIContextD3D12* /*context*/) { + } + + DawnSwapChainError NativeSwapChainImpl::Configure(WGPUTextureFormat format, + WGPUTextureUsage usage, + uint32_t width, + uint32_t height) { + ASSERT(width > 0); + ASSERT(height > 0); + ASSERT(format == static_cast<WGPUTextureFormat>(GetPreferredFormat())); + + ComPtr<IDXGIFactory4> factory = mDevice->GetFactory(); + ComPtr<ID3D12CommandQueue> queue = mDevice->GetCommandQueue(); + + mInterval = mDevice->IsToggleEnabled(Toggle::TurnOffVsync) == true ? 0 : 1; + + // Create the D3D12 swapchain, assuming only two buffers for now + DXGI_SWAP_CHAIN_DESC1 swapChainDesc = {}; + swapChainDesc.Width = width; + swapChainDesc.Height = height; + swapChainDesc.Format = D3D12TextureFormat(GetPreferredFormat()); + swapChainDesc.BufferUsage = D3D12SwapChainBufferUsage(usage); + swapChainDesc.BufferCount = kFrameCount; + swapChainDesc.SwapEffect = DXGI_SWAP_EFFECT_FLIP_DISCARD; + swapChainDesc.SampleDesc.Count = 1; + swapChainDesc.SampleDesc.Quality = 0; + + ComPtr<IDXGISwapChain1> swapChain1; + ASSERT_SUCCESS(factory->CreateSwapChainForHwnd(queue.Get(), mWindow, &swapChainDesc, + nullptr, nullptr, &swapChain1)); + + ASSERT_SUCCESS(swapChain1.As(&mSwapChain)); + + // Gather the resources that will be used to present to the swapchain + mBuffers.resize(kFrameCount); + for (uint32_t i = 0; i < kFrameCount; ++i) { + ASSERT_SUCCESS(mSwapChain->GetBuffer(i, IID_PPV_ARGS(&mBuffers[i]))); + } + + // Set the initial serial of buffers to 0 so that we don't wait on them when they are first + // used + mBufferSerials.resize(kFrameCount, ExecutionSerial(0)); + + return DAWN_SWAP_CHAIN_NO_ERROR; + } + + DawnSwapChainError NativeSwapChainImpl::GetNextTexture(DawnSwapChainNextTexture* nextTexture) { + mCurrentBuffer = mSwapChain->GetCurrentBackBufferIndex(); + nextTexture->texture.ptr = mBuffers[mCurrentBuffer].Get(); + + // TODO(crbug.com/dawn/269) Currently we force the CPU to wait for the GPU to be finished + // with the buffer. Ideally the synchronization should be all done on the GPU. + ASSERT(mDevice->WaitForSerial(mBufferSerials[mCurrentBuffer]).IsSuccess()); + + return DAWN_SWAP_CHAIN_NO_ERROR; + } + + DawnSwapChainError NativeSwapChainImpl::Present() { + // This assumes the texture has already been transition to the PRESENT state. + + ASSERT_SUCCESS(mSwapChain->Present(mInterval, 0)); + // TODO(crbug.com/dawn/833): Make the serial ticking implicit. + ASSERT(mDevice->NextSerial().IsSuccess()); + + mBufferSerials[mCurrentBuffer] = mDevice->GetPendingCommandSerial(); + return DAWN_SWAP_CHAIN_NO_ERROR; + } + + wgpu::TextureFormat NativeSwapChainImpl::GetPreferredFormat() const { + return wgpu::TextureFormat::RGBA8Unorm; + } + +} // namespace dawn::native::d3d12
diff --git a/src/dawn/native/d3d12/NativeSwapChainImplD3D12.h b/src/dawn/native/d3d12/NativeSwapChainImplD3D12.h new file mode 100644 index 0000000..8ed5ee2 --- /dev/null +++ b/src/dawn/native/d3d12/NativeSwapChainImplD3D12.h
@@ -0,0 +1,60 @@ +// Copyright 2018 The Dawn Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#ifndef DAWNNATIVE_D3D12_NATIVESWAPCHAINIMPLD3D12_H_ +#define DAWNNATIVE_D3D12_NATIVESWAPCHAINIMPLD3D12_H_ + +#include "dawn/native/d3d12/d3d12_platform.h" + +#include "dawn/dawn_wsi.h" +#include "dawn/native/IntegerTypes.h" +#include "dawn/native/dawn_platform.h" + +#include <vector> + +namespace dawn::native::d3d12 { + + class Device; + + class NativeSwapChainImpl { + public: + using WSIContext = DawnWSIContextD3D12; + + NativeSwapChainImpl(Device* device, HWND window); + ~NativeSwapChainImpl(); + + void Init(DawnWSIContextD3D12* context); + DawnSwapChainError Configure(WGPUTextureFormat format, + WGPUTextureUsage, + uint32_t width, + uint32_t height); + DawnSwapChainError GetNextTexture(DawnSwapChainNextTexture* nextTexture); + DawnSwapChainError Present(); + + wgpu::TextureFormat GetPreferredFormat() const; + + private: + HWND mWindow = nullptr; + Device* mDevice = nullptr; + UINT mInterval; + + ComPtr<IDXGISwapChain3> mSwapChain = nullptr; + std::vector<ComPtr<ID3D12Resource>> mBuffers; + std::vector<ExecutionSerial> mBufferSerials; + uint32_t mCurrentBuffer; + }; + +} // namespace dawn::native::d3d12 + +#endif // DAWNNATIVE_D3D12_NATIVESWAPCHAINIMPLD3D12_H_
diff --git a/src/dawn/native/d3d12/PageableD3D12.cpp b/src/dawn/native/d3d12/PageableD3D12.cpp new file mode 100644 index 0000000..1394209 --- /dev/null +++ b/src/dawn/native/d3d12/PageableD3D12.cpp
@@ -0,0 +1,76 @@ +// Copyright 2020 The Dawn Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include "dawn/native/d3d12/PageableD3D12.h" + +namespace dawn::native::d3d12 { + Pageable::Pageable(ComPtr<ID3D12Pageable> d3d12Pageable, + MemorySegment memorySegment, + uint64_t size) + : mD3d12Pageable(std::move(d3d12Pageable)), mMemorySegment(memorySegment), mSize(size) { + } + + // When a pageable is destroyed, it no longer resides in resident memory, so we must evict + // it from the LRU cache. If this heap is not manually removed from the LRU-cache, the + // ResidencyManager will attempt to use it after it has been deallocated. + Pageable::~Pageable() { + if (IsInResidencyLRUCache()) { + RemoveFromList(); + } + } + + ID3D12Pageable* Pageable::GetD3D12Pageable() const { + return mD3d12Pageable.Get(); + } + + ExecutionSerial Pageable::GetLastUsage() const { + return mLastUsage; + } + + void Pageable::SetLastUsage(ExecutionSerial serial) { + mLastUsage = serial; + } + + ExecutionSerial Pageable::GetLastSubmission() const { + return mLastSubmission; + } + + void Pageable::SetLastSubmission(ExecutionSerial serial) { + mLastSubmission = serial; + } + + MemorySegment Pageable::GetMemorySegment() const { + return mMemorySegment; + } + + uint64_t Pageable::GetSize() const { + return mSize; + } + + bool Pageable::IsInResidencyLRUCache() const { + return IsInList(); + } + + void Pageable::IncrementResidencyLock() { + mResidencyLockRefCount++; + } + + void Pageable::DecrementResidencyLock() { + mResidencyLockRefCount--; + } + + bool Pageable::IsResidencyLocked() const { + return mResidencyLockRefCount != 0; + } +} // namespace dawn::native::d3d12
diff --git a/src/dawn/native/d3d12/PageableD3D12.h b/src/dawn/native/d3d12/PageableD3D12.h new file mode 100644 index 0000000..19355dc --- /dev/null +++ b/src/dawn/native/d3d12/PageableD3D12.h
@@ -0,0 +1,80 @@ +// Copyright 2020 The Dawn Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#ifndef DAWNNATIVE_D3D12_PAGEABLED3D12_H_ +#define DAWNNATIVE_D3D12_PAGEABLED3D12_H_ + +#include "dawn/common/LinkedList.h" +#include "dawn/native/D3D12Backend.h" +#include "dawn/native/IntegerTypes.h" +#include "dawn/native/d3d12/d3d12_platform.h" + +namespace dawn::native::d3d12 { + // This class is used to represent ID3D12Pageable allocations, and also serves as a node within + // the ResidencyManager's LRU cache. This node is inserted into the LRU-cache when it is first + // allocated, and any time it is scheduled to be used by the GPU. This node is removed from the + // LRU cache when it is evicted from resident memory due to budget constraints, or when the + // pageable allocation is released. + class Pageable : public LinkNode<Pageable> { + public: + Pageable(ComPtr<ID3D12Pageable> d3d12Pageable, MemorySegment memorySegment, uint64_t size); + ~Pageable(); + + ID3D12Pageable* GetD3D12Pageable() const; + + // We set mLastRecordingSerial to denote the serial this pageable was last recorded to be + // used. We must check this serial against the current serial when recording usages to + // ensure we do not process residency for this pageable multiple times. + ExecutionSerial GetLastUsage() const; + void SetLastUsage(ExecutionSerial serial); + + // The residency manager must know the last serial that any portion of the pageable was + // submitted to be used so that we can ensure this pageable stays resident in memory at + // least until that serial has completed. + ExecutionSerial GetLastSubmission() const; + void SetLastSubmission(ExecutionSerial serial); + + MemorySegment GetMemorySegment() const; + + uint64_t GetSize() const; + + bool IsInResidencyLRUCache() const; + + // In some scenarios, such as async buffer mapping or descriptor heaps, we must lock + // residency to ensure the pageable cannot be evicted. Because multiple buffers may be + // mapped in a single heap, we must track the number of resources currently locked. + void IncrementResidencyLock(); + void DecrementResidencyLock(); + bool IsResidencyLocked() const; + + protected: + ComPtr<ID3D12Pageable> mD3d12Pageable; + + private: + // mLastUsage denotes the last time this pageable was recorded for use. + ExecutionSerial mLastUsage = ExecutionSerial(0); + // mLastSubmission denotes the last time this pageable was submitted to the GPU. Note that + // although this variable often contains the same value as mLastUsage, it can differ in some + // situations. When some asynchronous APIs (like WriteBuffer) are called, mLastUsage is + // updated upon the call, but the backend operation is deferred until the next submission + // to the GPU. This makes mLastSubmission unique from mLastUsage, and allows us to + // accurately identify when a pageable can be evicted. + ExecutionSerial mLastSubmission = ExecutionSerial(0); + MemorySegment mMemorySegment; + uint32_t mResidencyLockRefCount = 0; + uint64_t mSize = 0; + }; +} // namespace dawn::native::d3d12 + +#endif
diff --git a/src/dawn/native/d3d12/PipelineLayoutD3D12.cpp b/src/dawn/native/d3d12/PipelineLayoutD3D12.cpp new file mode 100644 index 0000000..794a763 --- /dev/null +++ b/src/dawn/native/d3d12/PipelineLayoutD3D12.cpp
@@ -0,0 +1,377 @@ +// Copyright 2017 The Dawn Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include "dawn/native/d3d12/PipelineLayoutD3D12.h" +#include <sstream> + +#include "dawn/common/Assert.h" +#include "dawn/common/BitSetIterator.h" +#include "dawn/native/d3d12/BindGroupLayoutD3D12.h" +#include "dawn/native/d3d12/D3D12Error.h" +#include "dawn/native/d3d12/DeviceD3D12.h" +#include "dawn/native/d3d12/PlatformFunctions.h" + +using Microsoft::WRL::ComPtr; + +namespace dawn::native::d3d12 { + namespace { + + // Reserve register names for internal use. This registers map to bindings in the shader, + // but are not directly related to allocation of the root signature. + // In the root signature, it the index of the root parameter where these registers are + // used that determines the layout of the root signature. + static constexpr uint32_t kRenderOrComputeInternalRegisterSpace = kMaxBindGroups + 1; + static constexpr uint32_t kRenderOrComputeInternalBaseRegister = 0; + + static constexpr uint32_t kDynamicStorageBufferLengthsRegisterSpace = kMaxBindGroups + 2; + static constexpr uint32_t kDynamicStorageBufferLengthsBaseRegister = 0; + + static constexpr uint32_t kInvalidDynamicStorageBufferLengthsParameterIndex = + std::numeric_limits<uint32_t>::max(); + + D3D12_SHADER_VISIBILITY ShaderVisibilityType(wgpu::ShaderStage visibility) { + ASSERT(visibility != wgpu::ShaderStage::None); + + if (visibility == wgpu::ShaderStage::Vertex) { + return D3D12_SHADER_VISIBILITY_VERTEX; + } + + if (visibility == wgpu::ShaderStage::Fragment) { + return D3D12_SHADER_VISIBILITY_PIXEL; + } + + // For compute or any two combination of stages, visibility must be ALL + return D3D12_SHADER_VISIBILITY_ALL; + } + + D3D12_ROOT_PARAMETER_TYPE RootParameterType(wgpu::BufferBindingType type) { + switch (type) { + case wgpu::BufferBindingType::Uniform: + return D3D12_ROOT_PARAMETER_TYPE_CBV; + case wgpu::BufferBindingType::Storage: + case kInternalStorageBufferBinding: + return D3D12_ROOT_PARAMETER_TYPE_UAV; + case wgpu::BufferBindingType::ReadOnlyStorage: + return D3D12_ROOT_PARAMETER_TYPE_SRV; + case wgpu::BufferBindingType::Undefined: + UNREACHABLE(); + } + } + + } // anonymous namespace + + ResultOrError<Ref<PipelineLayout>> PipelineLayout::Create( + Device* device, + const PipelineLayoutDescriptor* descriptor) { + Ref<PipelineLayout> layout = AcquireRef(new PipelineLayout(device, descriptor)); + DAWN_TRY(layout->Initialize()); + return layout; + } + + MaybeError PipelineLayout::Initialize() { + Device* device = ToBackend(GetDevice()); + // Parameters are D3D12_ROOT_PARAMETER_TYPE which is either a root table, constant, or + // descriptor. + std::vector<D3D12_ROOT_PARAMETER> rootParameters; + + size_t rangesCount = 0; + for (BindGroupIndex group : IterateBitSet(GetBindGroupLayoutsMask())) { + const BindGroupLayout* bindGroupLayout = ToBackend(GetBindGroupLayout(group)); + rangesCount += bindGroupLayout->GetCbvUavSrvDescriptorRanges().size() + + bindGroupLayout->GetSamplerDescriptorRanges().size(); + } + + // We are taking pointers to `ranges`, so we cannot let it resize while we're pushing to it. + std::vector<D3D12_DESCRIPTOR_RANGE> ranges(rangesCount); + + uint32_t rangeIndex = 0; + + for (BindGroupIndex group : IterateBitSet(GetBindGroupLayoutsMask())) { + const BindGroupLayout* bindGroupLayout = ToBackend(GetBindGroupLayout(group)); + + // Set the root descriptor table parameter and copy ranges. Ranges are offset by the + // bind group index Returns whether or not the parameter was set. A root parameter is + // not set if the number of ranges is 0 + auto SetRootDescriptorTable = + [&](const std::vector<D3D12_DESCRIPTOR_RANGE>& descriptorRanges) -> bool { + auto rangeCount = descriptorRanges.size(); + if (rangeCount == 0) { + return false; + } + + D3D12_ROOT_PARAMETER rootParameter = {}; + rootParameter.ParameterType = D3D12_ROOT_PARAMETER_TYPE_DESCRIPTOR_TABLE; + rootParameter.ShaderVisibility = D3D12_SHADER_VISIBILITY_ALL; + rootParameter.DescriptorTable.NumDescriptorRanges = rangeCount; + rootParameter.DescriptorTable.pDescriptorRanges = &ranges[rangeIndex]; + + for (auto& range : descriptorRanges) { + ASSERT(range.RegisterSpace == kRegisterSpacePlaceholder); + ranges[rangeIndex] = range; + ranges[rangeIndex].RegisterSpace = static_cast<uint32_t>(group); + rangeIndex++; + } + + rootParameters.emplace_back(rootParameter); + + return true; + }; + + if (SetRootDescriptorTable(bindGroupLayout->GetCbvUavSrvDescriptorRanges())) { + mCbvUavSrvRootParameterInfo[group] = rootParameters.size() - 1; + } + if (SetRootDescriptorTable(bindGroupLayout->GetSamplerDescriptorRanges())) { + mSamplerRootParameterInfo[group] = rootParameters.size() - 1; + } + + // Init root descriptors in root signatures for dynamic buffer bindings. + // These are packed at the beginning of the layout binding info. + for (BindingIndex dynamicBindingIndex{0}; + dynamicBindingIndex < bindGroupLayout->GetDynamicBufferCount(); + ++dynamicBindingIndex) { + const BindingInfo& bindingInfo = + bindGroupLayout->GetBindingInfo(dynamicBindingIndex); + + if (bindingInfo.visibility == wgpu::ShaderStage::None) { + // Skip dynamic buffers that are not visible. D3D12 does not have None + // visibility. + continue; + } + + D3D12_ROOT_PARAMETER rootParameter = {}; + + // Setup root descriptor. + D3D12_ROOT_DESCRIPTOR rootDescriptor; + rootDescriptor.ShaderRegister = + bindGroupLayout->GetShaderRegister(dynamicBindingIndex); + rootDescriptor.RegisterSpace = static_cast<uint32_t>(group); + + // Set root descriptors in root signatures. + rootParameter.Descriptor = rootDescriptor; + mDynamicRootParameterIndices[group][dynamicBindingIndex] = rootParameters.size(); + + // Set parameter types according to bind group layout descriptor. + rootParameter.ParameterType = RootParameterType(bindingInfo.buffer.type); + + // Set visibilities according to bind group layout descriptor. + rootParameter.ShaderVisibility = ShaderVisibilityType(bindingInfo.visibility); + + rootParameters.emplace_back(rootParameter); + } + } + + // Make sure that we added exactly the number of elements we expected. If we added more, + // |ranges| will have resized and the pointers in the |rootParameter|s will be invalid. + ASSERT(rangeIndex == rangesCount); + + D3D12_ROOT_PARAMETER renderOrComputeInternalConstants{}; + renderOrComputeInternalConstants.ShaderVisibility = D3D12_SHADER_VISIBILITY_ALL; + renderOrComputeInternalConstants.ParameterType = D3D12_ROOT_PARAMETER_TYPE_32BIT_CONSTANTS; + // Always allocate 3 constants for either: + // - vertex_index and instance_index + // - num_workgroups_x, num_workgroups_y and num_workgroups_z + // NOTE: We should consider delaying root signature creation until we know how many values + // we need + renderOrComputeInternalConstants.Constants.Num32BitValues = 3; + renderOrComputeInternalConstants.Constants.RegisterSpace = + kRenderOrComputeInternalRegisterSpace; + renderOrComputeInternalConstants.Constants.ShaderRegister = + kRenderOrComputeInternalBaseRegister; + mFirstIndexOffsetParameterIndex = rootParameters.size(); + mNumWorkgroupsParameterIndex = rootParameters.size(); + // NOTE: We should consider moving this entry to earlier in the root signature since offsets + // would need to be updated often + rootParameters.emplace_back(renderOrComputeInternalConstants); + + // Loops over all of the dynamic storage buffer bindings in the layout and build + // a mapping from the binding to the next offset into the root constant array where + // that dynamic storage buffer's binding size will be stored. The next register offset + // to use is tracked with |dynamicStorageBufferLengthsShaderRegisterOffset|. + // This data will be used by shader translation to emit a load from the root constant + // array to use as the binding's size in runtime array calculations. + // Each bind group's length data is stored contiguously in the root constant array, + // so the loop also computes the first register offset for each group where the + // data should start. + uint32_t dynamicStorageBufferLengthsShaderRegisterOffset = 0; + for (BindGroupIndex group : IterateBitSet(GetBindGroupLayoutsMask())) { + const BindGroupLayoutBase* bgl = GetBindGroupLayout(group); + + mDynamicStorageBufferLengthInfo[group].firstRegisterOffset = + dynamicStorageBufferLengthsShaderRegisterOffset; + mDynamicStorageBufferLengthInfo[group].bindingAndRegisterOffsets.reserve( + bgl->GetBindingCountInfo().dynamicStorageBufferCount); + + for (BindingIndex bindingIndex(0); bindingIndex < bgl->GetDynamicBufferCount(); + ++bindingIndex) { + if (bgl->IsStorageBufferBinding(bindingIndex)) { + mDynamicStorageBufferLengthInfo[group].bindingAndRegisterOffsets.push_back( + {bgl->GetBindingInfo(bindingIndex).binding, + dynamicStorageBufferLengthsShaderRegisterOffset++}); + } + } + + ASSERT(mDynamicStorageBufferLengthInfo[group].bindingAndRegisterOffsets.size() == + bgl->GetBindingCountInfo().dynamicStorageBufferCount); + } + ASSERT(dynamicStorageBufferLengthsShaderRegisterOffset <= + kMaxDynamicStorageBuffersPerPipelineLayout); + + if (dynamicStorageBufferLengthsShaderRegisterOffset > 0) { + D3D12_ROOT_PARAMETER dynamicStorageBufferLengthConstants{}; + dynamicStorageBufferLengthConstants.ShaderVisibility = D3D12_SHADER_VISIBILITY_ALL; + dynamicStorageBufferLengthConstants.ParameterType = + D3D12_ROOT_PARAMETER_TYPE_32BIT_CONSTANTS; + dynamicStorageBufferLengthConstants.Constants.Num32BitValues = + dynamicStorageBufferLengthsShaderRegisterOffset; + dynamicStorageBufferLengthConstants.Constants.RegisterSpace = + kDynamicStorageBufferLengthsRegisterSpace; + dynamicStorageBufferLengthConstants.Constants.ShaderRegister = + kDynamicStorageBufferLengthsBaseRegister; + mDynamicStorageBufferLengthsParameterIndex = rootParameters.size(); + rootParameters.emplace_back(dynamicStorageBufferLengthConstants); + } else { + mDynamicStorageBufferLengthsParameterIndex = + kInvalidDynamicStorageBufferLengthsParameterIndex; + } + + D3D12_ROOT_SIGNATURE_DESC rootSignatureDescriptor; + rootSignatureDescriptor.NumParameters = rootParameters.size(); + rootSignatureDescriptor.pParameters = rootParameters.data(); + rootSignatureDescriptor.NumStaticSamplers = 0; + rootSignatureDescriptor.pStaticSamplers = nullptr; + rootSignatureDescriptor.Flags = + D3D12_ROOT_SIGNATURE_FLAG_ALLOW_INPUT_ASSEMBLER_INPUT_LAYOUT; + + ComPtr<ID3DBlob> signature; + ComPtr<ID3DBlob> error; + HRESULT hr = device->GetFunctions()->d3d12SerializeRootSignature( + &rootSignatureDescriptor, D3D_ROOT_SIGNATURE_VERSION_1, &signature, &error); + if (DAWN_UNLIKELY(FAILED(hr))) { + std::ostringstream messageStream; + if (error) { + messageStream << static_cast<const char*>(error->GetBufferPointer()); + + // |error| is observed to always end with a \n, but is not + // specified to do so, so we add an extra newline just in case. + messageStream << std::endl; + } + messageStream << "D3D12 serialize root signature"; + DAWN_TRY(CheckHRESULT(hr, messageStream.str().c_str())); + } + DAWN_TRY(CheckHRESULT(device->GetD3D12Device()->CreateRootSignature( + 0, signature->GetBufferPointer(), signature->GetBufferSize(), + IID_PPV_ARGS(&mRootSignature)), + "D3D12 create root signature")); + return {}; + } + + uint32_t PipelineLayout::GetCbvUavSrvRootParameterIndex(BindGroupIndex group) const { + ASSERT(group < kMaxBindGroupsTyped); + return mCbvUavSrvRootParameterInfo[group]; + } + + uint32_t PipelineLayout::GetSamplerRootParameterIndex(BindGroupIndex group) const { + ASSERT(group < kMaxBindGroupsTyped); + return mSamplerRootParameterInfo[group]; + } + + ID3D12RootSignature* PipelineLayout::GetRootSignature() const { + return mRootSignature.Get(); + } + + const PipelineLayout::DynamicStorageBufferLengthInfo& + PipelineLayout::GetDynamicStorageBufferLengthInfo() const { + return mDynamicStorageBufferLengthInfo; + } + + uint32_t PipelineLayout::GetDynamicRootParameterIndex(BindGroupIndex group, + BindingIndex bindingIndex) const { + ASSERT(group < kMaxBindGroupsTyped); + ASSERT(bindingIndex < kMaxDynamicBuffersPerPipelineLayoutTyped); + ASSERT(GetBindGroupLayout(group)->GetBindingInfo(bindingIndex).buffer.hasDynamicOffset); + ASSERT(GetBindGroupLayout(group)->GetBindingInfo(bindingIndex).visibility != + wgpu::ShaderStage::None); + return mDynamicRootParameterIndices[group][bindingIndex]; + } + + uint32_t PipelineLayout::GetFirstIndexOffsetRegisterSpace() const { + return kRenderOrComputeInternalRegisterSpace; + } + + uint32_t PipelineLayout::GetFirstIndexOffsetShaderRegister() const { + return kRenderOrComputeInternalBaseRegister; + } + + uint32_t PipelineLayout::GetFirstIndexOffsetParameterIndex() const { + return mFirstIndexOffsetParameterIndex; + } + + uint32_t PipelineLayout::GetNumWorkgroupsRegisterSpace() const { + return kRenderOrComputeInternalRegisterSpace; + } + + uint32_t PipelineLayout::GetNumWorkgroupsShaderRegister() const { + return kRenderOrComputeInternalBaseRegister; + } + + uint32_t PipelineLayout::GetNumWorkgroupsParameterIndex() const { + return mNumWorkgroupsParameterIndex; + } + + uint32_t PipelineLayout::GetDynamicStorageBufferLengthsRegisterSpace() const { + return kDynamicStorageBufferLengthsRegisterSpace; + } + + uint32_t PipelineLayout::GetDynamicStorageBufferLengthsShaderRegister() const { + return kDynamicStorageBufferLengthsBaseRegister; + } + + uint32_t PipelineLayout::GetDynamicStorageBufferLengthsParameterIndex() const { + ASSERT(mDynamicStorageBufferLengthsParameterIndex != + kInvalidDynamicStorageBufferLengthsParameterIndex); + return mDynamicStorageBufferLengthsParameterIndex; + } + + ID3D12CommandSignature* PipelineLayout::GetDispatchIndirectCommandSignatureWithNumWorkgroups() { + // mDispatchIndirectCommandSignatureWithNumWorkgroups won't be created until it is needed. + if (mDispatchIndirectCommandSignatureWithNumWorkgroups.Get() != nullptr) { + return mDispatchIndirectCommandSignatureWithNumWorkgroups.Get(); + } + + D3D12_INDIRECT_ARGUMENT_DESC argumentDescs[2] = {}; + argumentDescs[0].Type = D3D12_INDIRECT_ARGUMENT_TYPE_CONSTANT; + argumentDescs[0].Constant.RootParameterIndex = GetNumWorkgroupsParameterIndex(); + argumentDescs[0].Constant.Num32BitValuesToSet = 3; + argumentDescs[0].Constant.DestOffsetIn32BitValues = 0; + + // A command signature must contain exactly 1 Draw / Dispatch / DispatchMesh / DispatchRays + // command. That command must come last. + argumentDescs[1].Type = D3D12_INDIRECT_ARGUMENT_TYPE_DISPATCH; + + D3D12_COMMAND_SIGNATURE_DESC programDesc = {}; + programDesc.ByteStride = 6 * sizeof(uint32_t); + programDesc.NumArgumentDescs = 2; + programDesc.pArgumentDescs = argumentDescs; + + // The root signature must be specified if and only if the command signature changes one of + // the root arguments. + ToBackend(GetDevice()) + ->GetD3D12Device() + ->CreateCommandSignature( + &programDesc, GetRootSignature(), + IID_PPV_ARGS(&mDispatchIndirectCommandSignatureWithNumWorkgroups)); + return mDispatchIndirectCommandSignatureWithNumWorkgroups.Get(); + } + +} // namespace dawn::native::d3d12
diff --git a/src/dawn/native/d3d12/PipelineLayoutD3D12.h b/src/dawn/native/d3d12/PipelineLayoutD3D12.h new file mode 100644 index 0000000..d1e8453 --- /dev/null +++ b/src/dawn/native/d3d12/PipelineLayoutD3D12.h
@@ -0,0 +1,100 @@ +// Copyright 2017 The Dawn Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#ifndef DAWNNATIVE_D3D12_PIPELINELAYOUTD3D12_H_ +#define DAWNNATIVE_D3D12_PIPELINELAYOUTD3D12_H_ + +#include "dawn/common/Constants.h" +#include "dawn/common/ityp_array.h" +#include "dawn/native/BindingInfo.h" +#include "dawn/native/PipelineLayout.h" +#include "dawn/native/d3d12/d3d12_platform.h" + +namespace dawn::native::d3d12 { + + class Device; + + class PipelineLayout final : public PipelineLayoutBase { + public: + static ResultOrError<Ref<PipelineLayout>> Create( + Device* device, + const PipelineLayoutDescriptor* descriptor); + + uint32_t GetCbvUavSrvRootParameterIndex(BindGroupIndex group) const; + uint32_t GetSamplerRootParameterIndex(BindGroupIndex group) const; + + // Returns the index of the root parameter reserved for a dynamic buffer binding + uint32_t GetDynamicRootParameterIndex(BindGroupIndex group, + BindingIndex bindingIndex) const; + + uint32_t GetFirstIndexOffsetRegisterSpace() const; + uint32_t GetFirstIndexOffsetShaderRegister() const; + uint32_t GetFirstIndexOffsetParameterIndex() const; + + uint32_t GetNumWorkgroupsRegisterSpace() const; + uint32_t GetNumWorkgroupsShaderRegister() const; + uint32_t GetNumWorkgroupsParameterIndex() const; + + uint32_t GetDynamicStorageBufferLengthsRegisterSpace() const; + uint32_t GetDynamicStorageBufferLengthsShaderRegister() const; + uint32_t GetDynamicStorageBufferLengthsParameterIndex() const; + + ID3D12RootSignature* GetRootSignature() const; + + ID3D12CommandSignature* GetDispatchIndirectCommandSignatureWithNumWorkgroups(); + + struct PerBindGroupDynamicStorageBufferLengthInfo { + // First register offset for a bind group's dynamic storage buffer lengths. + // This is the index into the array of root constants where this bind group's + // lengths start. + uint32_t firstRegisterOffset; + + struct BindingAndRegisterOffset { + BindingNumber binding; + uint32_t registerOffset; + }; + // Associative list of (BindingNumber,registerOffset) pairs, which is passed into + // the shader to map the BindingPoint(thisGroup, BindingNumber) to the registerOffset + // into the root constant array which holds the dynamic storage buffer lengths. + std::vector<BindingAndRegisterOffset> bindingAndRegisterOffsets; + }; + + // Flat map from bind group index to the list of (BindingNumber,Register) pairs. + // Each pair is used in shader translation to + using DynamicStorageBufferLengthInfo = + ityp::array<BindGroupIndex, PerBindGroupDynamicStorageBufferLengthInfo, kMaxBindGroups>; + + const DynamicStorageBufferLengthInfo& GetDynamicStorageBufferLengthInfo() const; + + private: + ~PipelineLayout() override = default; + using PipelineLayoutBase::PipelineLayoutBase; + MaybeError Initialize(); + ityp::array<BindGroupIndex, uint32_t, kMaxBindGroups> mCbvUavSrvRootParameterInfo; + ityp::array<BindGroupIndex, uint32_t, kMaxBindGroups> mSamplerRootParameterInfo; + ityp::array<BindGroupIndex, + ityp::array<BindingIndex, uint32_t, kMaxDynamicBuffersPerPipelineLayout>, + kMaxBindGroups> + mDynamicRootParameterIndices; + DynamicStorageBufferLengthInfo mDynamicStorageBufferLengthInfo; + uint32_t mFirstIndexOffsetParameterIndex; + uint32_t mNumWorkgroupsParameterIndex; + uint32_t mDynamicStorageBufferLengthsParameterIndex; + ComPtr<ID3D12RootSignature> mRootSignature; + ComPtr<ID3D12CommandSignature> mDispatchIndirectCommandSignatureWithNumWorkgroups; + }; + +} // namespace dawn::native::d3d12 + +#endif // DAWNNATIVE_D3D12_PIPELINELAYOUTD3D12_H_
diff --git a/src/dawn/native/d3d12/PlatformFunctions.cpp b/src/dawn/native/d3d12/PlatformFunctions.cpp new file mode 100644 index 0000000..786ae5a --- /dev/null +++ b/src/dawn/native/d3d12/PlatformFunctions.cpp
@@ -0,0 +1,271 @@ +// Copyright 2018 The Dawn Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include "dawn/native/d3d12/PlatformFunctions.h" + +#include "dawn/common/DynamicLib.h" + +#include <comdef.h> +#include <array> +#include <sstream> + +namespace dawn::native::d3d12 { + namespace { + // Extract Version from "10.0.{Version}.0" if possible, otherwise return 0. + uint32_t GetWindowsSDKVersionFromDirectoryName(const char* directoryName) { + constexpr char kPrefix[] = "10.0."; + constexpr char kPostfix[] = ".0"; + + constexpr uint32_t kPrefixLen = sizeof(kPrefix) - 1; + constexpr uint32_t kPostfixLen = sizeof(kPostfix) - 1; + const uint32_t directoryNameLen = strlen(directoryName); + + if (directoryNameLen < kPrefixLen + kPostfixLen + 1) { + return 0; + } + + // Check if directoryName starts with "10.0.". + if (strncmp(directoryName, kPrefix, kPrefixLen) != 0) { + return 0; + } + + // Check if directoryName ends with ".0". + if (strncmp(directoryName + (directoryNameLen - kPostfixLen), kPostfix, kPostfixLen) != + 0) { + return 0; + } + + // Extract Version from "10.0.{Version}.0" and convert Version into an integer. + return atoi(directoryName + kPrefixLen); + } + + class ScopedFileHandle final { + public: + explicit ScopedFileHandle(HANDLE handle) : mHandle(handle) { + } + ~ScopedFileHandle() { + if (mHandle != INVALID_HANDLE_VALUE) { + ASSERT(FindClose(mHandle)); + } + } + HANDLE GetHandle() const { + return mHandle; + } + + private: + HANDLE mHandle; + }; + + std::string GetWindowsSDKBasePath() { + const char* kDefaultWindowsSDKPath = + "C:\\Program Files (x86)\\Windows Kits\\10\\bin\\*"; + WIN32_FIND_DATAA fileData; + ScopedFileHandle handle(FindFirstFileA(kDefaultWindowsSDKPath, &fileData)); + if (handle.GetHandle() == INVALID_HANDLE_VALUE) { + return ""; + } + + uint32_t highestWindowsSDKVersion = 0; + do { + if (!(fileData.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY)) { + continue; + } + + highestWindowsSDKVersion = + std::max(highestWindowsSDKVersion, + GetWindowsSDKVersionFromDirectoryName(fileData.cFileName)); + } while (FindNextFileA(handle.GetHandle(), &fileData)); + + if (highestWindowsSDKVersion == 0) { + return ""; + } + + // Currently we only support using DXC on x64. + std::ostringstream ostream; + ostream << "C:\\Program Files (x86)\\Windows Kits\\10\\bin\\10.0." + << highestWindowsSDKVersion << ".0\\x64\\"; + + return ostream.str(); + } + } // anonymous namespace + + PlatformFunctions::PlatformFunctions() = default; + PlatformFunctions::~PlatformFunctions() = default; + + MaybeError PlatformFunctions::LoadFunctions() { + DAWN_TRY(LoadD3D12()); + DAWN_TRY(LoadDXGI()); + LoadDXCLibraries(); + DAWN_TRY(LoadFXCompiler()); + DAWN_TRY(LoadD3D11()); + LoadPIXRuntime(); + return {}; + } + + MaybeError PlatformFunctions::LoadD3D12() { +#if DAWN_PLATFORM_WINUWP + d3d12CreateDevice = &D3D12CreateDevice; + d3d12GetDebugInterface = &D3D12GetDebugInterface; + d3d12SerializeRootSignature = &D3D12SerializeRootSignature; + d3d12CreateRootSignatureDeserializer = &D3D12CreateRootSignatureDeserializer; + d3d12SerializeVersionedRootSignature = &D3D12SerializeVersionedRootSignature; + d3d12CreateVersionedRootSignatureDeserializer = + &D3D12CreateVersionedRootSignatureDeserializer; +#else + std::string error; + if (!mD3D12Lib.Open("d3d12.dll", &error) || + !mD3D12Lib.GetProc(&d3d12CreateDevice, "D3D12CreateDevice", &error) || + !mD3D12Lib.GetProc(&d3d12GetDebugInterface, "D3D12GetDebugInterface", &error) || + !mD3D12Lib.GetProc(&d3d12SerializeRootSignature, "D3D12SerializeRootSignature", + &error) || + !mD3D12Lib.GetProc(&d3d12CreateRootSignatureDeserializer, + "D3D12CreateRootSignatureDeserializer", &error) || + !mD3D12Lib.GetProc(&d3d12SerializeVersionedRootSignature, + "D3D12SerializeVersionedRootSignature", &error) || + !mD3D12Lib.GetProc(&d3d12CreateVersionedRootSignatureDeserializer, + "D3D12CreateVersionedRootSignatureDeserializer", &error)) { + return DAWN_INTERNAL_ERROR(error.c_str()); + } +#endif + + return {}; + } + + MaybeError PlatformFunctions::LoadD3D11() { +#if DAWN_PLATFORM_WINUWP + d3d11on12CreateDevice = &D3D11On12CreateDevice; +#else + std::string error; + if (!mD3D11Lib.Open("d3d11.dll", &error) || + !mD3D11Lib.GetProc(&d3d11on12CreateDevice, "D3D11On12CreateDevice", &error)) { + return DAWN_INTERNAL_ERROR(error.c_str()); + } +#endif + + return {}; + } + + MaybeError PlatformFunctions::LoadDXGI() { +#if DAWN_PLATFORM_WINUWP +# if defined(_DEBUG) + // DXGIGetDebugInterface1 is tagged as a development-only capability + // which implies that linking to this function will cause + // the application to fail Windows store certification + // But we need it when debuging using VS Graphics Diagnostics or PIX + // So we only link to it in debug build + dxgiGetDebugInterface1 = &DXGIGetDebugInterface1; +# endif + createDxgiFactory2 = &CreateDXGIFactory2; +#else + std::string error; + if (!mDXGILib.Open("dxgi.dll", &error) || + !mDXGILib.GetProc(&dxgiGetDebugInterface1, "DXGIGetDebugInterface1", &error) || + !mDXGILib.GetProc(&createDxgiFactory2, "CreateDXGIFactory2", &error)) { + return DAWN_INTERNAL_ERROR(error.c_str()); + } +#endif + + return {}; + } + + void PlatformFunctions::LoadDXCLibraries() { + // TODO(dawn:766) + // Statically linked with dxcompiler.lib in UWP + // currently linked with dxcompiler.lib making CoreApp unable to activate + // LoadDXIL and LoadDXCompiler will fail in UWP, but LoadFunctions() can still be + // successfully executed. + + const std::string& windowsSDKBasePath = GetWindowsSDKBasePath(); + + LoadDXIL(windowsSDKBasePath); + LoadDXCompiler(windowsSDKBasePath); + } + + void PlatformFunctions::LoadDXIL(const std::string& baseWindowsSDKPath) { + const char* dxilDLLName = "dxil.dll"; + const std::array<std::string, 2> kDxilDLLPaths = { + {dxilDLLName, baseWindowsSDKPath + dxilDLLName}}; + + for (const std::string& dxilDLLPath : kDxilDLLPaths) { + if (mDXILLib.Open(dxilDLLPath, nullptr)) { + return; + } + } + ASSERT(!mDXILLib.Valid()); + } + + void PlatformFunctions::LoadDXCompiler(const std::string& baseWindowsSDKPath) { + // DXIL must be loaded before DXC, otherwise shader signing is unavailable + if (!mDXILLib.Valid()) { + return; + } + + const char* dxCompilerDLLName = "dxcompiler.dll"; + const std::array<std::string, 2> kDxCompilerDLLPaths = { + {dxCompilerDLLName, baseWindowsSDKPath + dxCompilerDLLName}}; + + DynamicLib dxCompilerLib; + for (const std::string& dxCompilerDLLName : kDxCompilerDLLPaths) { + if (dxCompilerLib.Open(dxCompilerDLLName, nullptr)) { + break; + } + } + + if (dxCompilerLib.Valid() && + dxCompilerLib.GetProc(&dxcCreateInstance, "DxcCreateInstance", nullptr)) { + mDXCompilerLib = std::move(dxCompilerLib); + } else { + mDXILLib.Close(); + } + } + + MaybeError PlatformFunctions::LoadFXCompiler() { +#if DAWN_PLATFORM_WINUWP + d3dCompile = &D3DCompile; + d3dDisassemble = &D3DDisassemble; +#else + std::string error; + if (!mFXCompilerLib.Open("d3dcompiler_47.dll", &error) || + !mFXCompilerLib.GetProc(&d3dCompile, "D3DCompile", &error) || + !mFXCompilerLib.GetProc(&d3dDisassemble, "D3DDisassemble", &error)) { + return DAWN_INTERNAL_ERROR(error.c_str()); + } +#endif + return {}; + } + + bool PlatformFunctions::IsPIXEventRuntimeLoaded() const { + return mPIXEventRuntimeLib.Valid(); + } + + bool PlatformFunctions::IsDXCAvailable() const { + return mDXILLib.Valid() && mDXCompilerLib.Valid(); + } + + void PlatformFunctions::LoadPIXRuntime() { + // TODO(dawn:766): + // In UWP PIX should be statically linked WinPixEventRuntime_UAP.lib + // So maybe we should put WinPixEventRuntime as a third party package + // Currently PIX is not going to be loaded in UWP since the following + // mPIXEventRuntimeLib.Open will fail. + if (!mPIXEventRuntimeLib.Open("WinPixEventRuntime.dll") || + !mPIXEventRuntimeLib.GetProc(&pixBeginEventOnCommandList, + "PIXBeginEventOnCommandList") || + !mPIXEventRuntimeLib.GetProc(&pixEndEventOnCommandList, "PIXEndEventOnCommandList") || + !mPIXEventRuntimeLib.GetProc(&pixSetMarkerOnCommandList, "PIXSetMarkerOnCommandList")) { + mPIXEventRuntimeLib.Close(); + } + } + +} // namespace dawn::native::d3d12
diff --git a/src/dawn/native/d3d12/PlatformFunctions.h b/src/dawn/native/d3d12/PlatformFunctions.h new file mode 100644 index 0000000..a236b1a --- /dev/null +++ b/src/dawn/native/d3d12/PlatformFunctions.h
@@ -0,0 +1,110 @@ +// Copyright 2018 The Dawn Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#ifndef DAWNNATIVE_D3D12_PLATFORMFUNCTIONS_H_ +#define DAWNNATIVE_D3D12_PLATFORMFUNCTIONS_H_ + +#include "dawn/native/d3d12/d3d12_platform.h" + +#include "dawn/common/DynamicLib.h" +#include "dawn/native/Error.h" + +#include <d3dcompiler.h> + +namespace dawn::native::d3d12 { + + // Loads the functions required from the platform dynamically so that we don't need to rely on + // them being present in the system. For example linking against d3d12.lib would prevent + // dawn_native from loading on Windows 7 system where d3d12.dll doesn't exist. + class PlatformFunctions { + public: + PlatformFunctions(); + ~PlatformFunctions(); + + MaybeError LoadFunctions(); + bool IsPIXEventRuntimeLoaded() const; + bool IsDXCAvailable() const; + + // Functions from d3d12.dll + PFN_D3D12_CREATE_DEVICE d3d12CreateDevice = nullptr; + PFN_D3D12_GET_DEBUG_INTERFACE d3d12GetDebugInterface = nullptr; + + PFN_D3D12_SERIALIZE_ROOT_SIGNATURE d3d12SerializeRootSignature = nullptr; + PFN_D3D12_CREATE_ROOT_SIGNATURE_DESERIALIZER d3d12CreateRootSignatureDeserializer = nullptr; + PFN_D3D12_SERIALIZE_VERSIONED_ROOT_SIGNATURE d3d12SerializeVersionedRootSignature = nullptr; + PFN_D3D12_CREATE_VERSIONED_ROOT_SIGNATURE_DESERIALIZER + d3d12CreateVersionedRootSignatureDeserializer = nullptr; + + // Functions from dxgi.dll + using PFN_DXGI_GET_DEBUG_INTERFACE1 = HRESULT(WINAPI*)(UINT Flags, + REFIID riid, + _COM_Outptr_ void** pDebug); + PFN_DXGI_GET_DEBUG_INTERFACE1 dxgiGetDebugInterface1 = nullptr; + + using PFN_CREATE_DXGI_FACTORY2 = HRESULT(WINAPI*)(UINT Flags, + REFIID riid, + _COM_Outptr_ void** ppFactory); + PFN_CREATE_DXGI_FACTORY2 createDxgiFactory2 = nullptr; + + // Functions from dxcompiler.dll + using PFN_DXC_CREATE_INSTANCE = HRESULT(WINAPI*)(REFCLSID rclsid, + REFIID riid, + _COM_Outptr_ void** ppCompiler); + PFN_DXC_CREATE_INSTANCE dxcCreateInstance = nullptr; + + // Functions from d3d3compiler.dll + pD3DCompile d3dCompile = nullptr; + pD3DDisassemble d3dDisassemble = nullptr; + + // Functions from WinPixEventRuntime.dll + using PFN_PIX_END_EVENT_ON_COMMAND_LIST = + HRESULT(WINAPI*)(ID3D12GraphicsCommandList* commandList); + + PFN_PIX_END_EVENT_ON_COMMAND_LIST pixEndEventOnCommandList = nullptr; + + using PFN_PIX_BEGIN_EVENT_ON_COMMAND_LIST = HRESULT( + WINAPI*)(ID3D12GraphicsCommandList* commandList, UINT64 color, _In_ PCSTR formatString); + + PFN_PIX_BEGIN_EVENT_ON_COMMAND_LIST pixBeginEventOnCommandList = nullptr; + + using PFN_SET_MARKER_ON_COMMAND_LIST = HRESULT( + WINAPI*)(ID3D12GraphicsCommandList* commandList, UINT64 color, _In_ PCSTR formatString); + + PFN_SET_MARKER_ON_COMMAND_LIST pixSetMarkerOnCommandList = nullptr; + + // Functions from D3D11.dll + PFN_D3D11ON12_CREATE_DEVICE d3d11on12CreateDevice = nullptr; + + private: + MaybeError LoadD3D12(); + MaybeError LoadD3D11(); + MaybeError LoadDXGI(); + void LoadDXCLibraries(); + void LoadDXIL(const std::string& baseWindowsSDKPath); + void LoadDXCompiler(const std::string& baseWindowsSDKPath); + MaybeError LoadFXCompiler(); + void LoadPIXRuntime(); + + DynamicLib mD3D12Lib; + DynamicLib mD3D11Lib; + DynamicLib mDXGILib; + DynamicLib mDXILLib; + DynamicLib mDXCompilerLib; + DynamicLib mFXCompilerLib; + DynamicLib mPIXEventRuntimeLib; + }; + +} // namespace dawn::native::d3d12 + +#endif // DAWNNATIVE_D3D12_PLATFORMFUNCTIONS_H_
diff --git a/src/dawn/native/d3d12/QuerySetD3D12.cpp b/src/dawn/native/d3d12/QuerySetD3D12.cpp new file mode 100644 index 0000000..458c23d --- /dev/null +++ b/src/dawn/native/d3d12/QuerySetD3D12.cpp
@@ -0,0 +1,75 @@ +// Copyright 2020 The Dawn Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include "dawn/native/d3d12/QuerySetD3D12.h" + +#include "dawn/native/d3d12/D3D12Error.h" +#include "dawn/native/d3d12/DeviceD3D12.h" +#include "dawn/native/d3d12/UtilsD3D12.h" + +namespace dawn::native::d3d12 { + + namespace { + D3D12_QUERY_HEAP_TYPE D3D12QueryHeapType(wgpu::QueryType type) { + switch (type) { + case wgpu::QueryType::Occlusion: + return D3D12_QUERY_HEAP_TYPE_OCCLUSION; + case wgpu::QueryType::PipelineStatistics: + return D3D12_QUERY_HEAP_TYPE_PIPELINE_STATISTICS; + case wgpu::QueryType::Timestamp: + return D3D12_QUERY_HEAP_TYPE_TIMESTAMP; + } + } + } // anonymous namespace + + // static + ResultOrError<Ref<QuerySet>> QuerySet::Create(Device* device, + const QuerySetDescriptor* descriptor) { + Ref<QuerySet> querySet = AcquireRef(new QuerySet(device, descriptor)); + DAWN_TRY(querySet->Initialize()); + return querySet; + } + + MaybeError QuerySet::Initialize() { + D3D12_QUERY_HEAP_DESC queryHeapDesc = {}; + queryHeapDesc.Type = D3D12QueryHeapType(GetQueryType()); + queryHeapDesc.Count = std::max(GetQueryCount(), uint32_t(1u)); + + ID3D12Device* d3d12Device = ToBackend(GetDevice())->GetD3D12Device(); + DAWN_TRY(CheckOutOfMemoryHRESULT( + d3d12Device->CreateQueryHeap(&queryHeapDesc, IID_PPV_ARGS(&mQueryHeap)), + "ID3D12Device::CreateQueryHeap")); + + SetLabelImpl(); + + return {}; + } + + ID3D12QueryHeap* QuerySet::GetQueryHeap() const { + return mQueryHeap.Get(); + } + + QuerySet::~QuerySet() = default; + + void QuerySet::DestroyImpl() { + QuerySetBase::DestroyImpl(); + ToBackend(GetDevice())->ReferenceUntilUnused(mQueryHeap); + mQueryHeap = nullptr; + } + + void QuerySet::SetLabelImpl() { + SetDebugName(ToBackend(GetDevice()), mQueryHeap.Get(), "Dawn_QuerySet", GetLabel()); + } + +} // namespace dawn::native::d3d12
diff --git a/src/dawn/native/d3d12/QuerySetD3D12.h b/src/dawn/native/d3d12/QuerySetD3D12.h new file mode 100644 index 0000000..5ace792 --- /dev/null +++ b/src/dawn/native/d3d12/QuerySetD3D12.h
@@ -0,0 +1,46 @@ +// Copyright 2020 The Dawn Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#ifndef DAWNNATIVE_D3D12_QUERYSETD3D12_H_ +#define DAWNNATIVE_D3D12_QUERYSETD3D12_H_ + +#include "dawn/native/QuerySet.h" +#include "dawn/native/d3d12/d3d12_platform.h" + +namespace dawn::native::d3d12 { + + class Device; + + class QuerySet : public QuerySetBase { + public: + static ResultOrError<Ref<QuerySet>> Create(Device* device, + const QuerySetDescriptor* descriptor); + + ID3D12QueryHeap* GetQueryHeap() const; + + private: + ~QuerySet() override; + using QuerySetBase::QuerySetBase; + MaybeError Initialize(); + + // Dawn API + void DestroyImpl() override; + void SetLabelImpl() override; + + ComPtr<ID3D12QueryHeap> mQueryHeap; + }; + +} // namespace dawn::native::d3d12 + +#endif // DAWNNATIVE_D3D12_QUERYSETD3D12_H_
diff --git a/src/dawn/native/d3d12/QueueD3D12.cpp b/src/dawn/native/d3d12/QueueD3D12.cpp new file mode 100644 index 0000000..cb92f21 --- /dev/null +++ b/src/dawn/native/d3d12/QueueD3D12.cpp
@@ -0,0 +1,54 @@ +// Copyright 2017 The Dawn Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include "dawn/native/d3d12/QueueD3D12.h" + +#include "dawn/common/Math.h" +#include "dawn/native/CommandValidation.h" +#include "dawn/native/Commands.h" +#include "dawn/native/DynamicUploader.h" +#include "dawn/native/d3d12/CommandBufferD3D12.h" +#include "dawn/native/d3d12/D3D12Error.h" +#include "dawn/native/d3d12/DeviceD3D12.h" +#include "dawn/platform/DawnPlatform.h" +#include "dawn/platform/tracing/TraceEvent.h" + +namespace dawn::native::d3d12 { + + Queue::Queue(Device* device) : QueueBase(device) { + } + + MaybeError Queue::SubmitImpl(uint32_t commandCount, CommandBufferBase* const* commands) { + Device* device = ToBackend(GetDevice()); + + DAWN_TRY(device->Tick()); + + CommandRecordingContext* commandContext; + DAWN_TRY_ASSIGN(commandContext, device->GetPendingCommandContext()); + + TRACE_EVENT_BEGIN0(GetDevice()->GetPlatform(), Recording, + "CommandBufferD3D12::RecordCommands"); + for (uint32_t i = 0; i < commandCount; ++i) { + DAWN_TRY(ToBackend(commands[i])->RecordCommands(commandContext)); + } + TRACE_EVENT_END0(GetDevice()->GetPlatform(), Recording, + "CommandBufferD3D12::RecordCommands"); + + DAWN_TRY(device->ExecutePendingCommandContext()); + + DAWN_TRY(device->NextSerial()); + return {}; + } + +} // namespace dawn::native::d3d12
diff --git a/src/dawn/native/d3d12/QueueD3D12.h b/src/dawn/native/d3d12/QueueD3D12.h new file mode 100644 index 0000000..6f15a7d --- /dev/null +++ b/src/dawn/native/d3d12/QueueD3D12.h
@@ -0,0 +1,37 @@ +// Copyright 2017 The Dawn Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#ifndef DAWNNATIVE_D3D12_QUEUED3D12_H_ +#define DAWNNATIVE_D3D12_QUEUED3D12_H_ + +#include "dawn/native/Queue.h" + +#include "dawn/native/d3d12/CommandRecordingContext.h" +#include "dawn/native/d3d12/d3d12_platform.h" + +namespace dawn::native::d3d12 { + + class Device; + + class Queue final : public QueueBase { + public: + Queue(Device* device); + + private: + MaybeError SubmitImpl(uint32_t commandCount, CommandBufferBase* const* commands) override; + }; + +} // namespace dawn::native::d3d12 + +#endif // DAWNNATIVE_D3D12_QUEUED3D12_H_
diff --git a/src/dawn/native/d3d12/RenderPassBuilderD3D12.cpp b/src/dawn/native/d3d12/RenderPassBuilderD3D12.cpp new file mode 100644 index 0000000..fc41331 --- /dev/null +++ b/src/dawn/native/d3d12/RenderPassBuilderD3D12.cpp
@@ -0,0 +1,250 @@ +// Copyright 2019 The Dawn Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include "dawn/native/d3d12/RenderPassBuilderD3D12.h" + +#include "dawn/native/Format.h" +#include "dawn/native/d3d12/CommandBufferD3D12.h" +#include "dawn/native/d3d12/Forward.h" +#include "dawn/native/d3d12/TextureD3D12.h" + +#include "dawn/native/dawn_platform.h" + +namespace dawn::native::d3d12 { + + namespace { + D3D12_RENDER_PASS_BEGINNING_ACCESS_TYPE D3D12BeginningAccessType(wgpu::LoadOp loadOp) { + switch (loadOp) { + case wgpu::LoadOp::Clear: + return D3D12_RENDER_PASS_BEGINNING_ACCESS_TYPE_CLEAR; + case wgpu::LoadOp::Load: + return D3D12_RENDER_PASS_BEGINNING_ACCESS_TYPE_PRESERVE; + case wgpu::LoadOp::Undefined: + UNREACHABLE(); + break; + } + } + + D3D12_RENDER_PASS_ENDING_ACCESS_TYPE D3D12EndingAccessType(wgpu::StoreOp storeOp) { + switch (storeOp) { + case wgpu::StoreOp::Discard: + return D3D12_RENDER_PASS_ENDING_ACCESS_TYPE_DISCARD; + case wgpu::StoreOp::Store: + return D3D12_RENDER_PASS_ENDING_ACCESS_TYPE_PRESERVE; + case wgpu::StoreOp::Undefined: + UNREACHABLE(); + break; + } + } + + D3D12_RENDER_PASS_ENDING_ACCESS_RESOLVE_PARAMETERS D3D12EndingAccessResolveParameters( + wgpu::StoreOp storeOp, + TextureView* resolveSource, + TextureView* resolveDestination) { + D3D12_RENDER_PASS_ENDING_ACCESS_RESOLVE_PARAMETERS resolveParameters; + + resolveParameters.Format = resolveDestination->GetD3D12Format(); + resolveParameters.pSrcResource = + ToBackend(resolveSource->GetTexture())->GetD3D12Resource(); + resolveParameters.pDstResource = + ToBackend(resolveDestination->GetTexture())->GetD3D12Resource(); + + // Clear or preserve the resolve source. + if (storeOp == wgpu::StoreOp::Discard) { + resolveParameters.PreserveResolveSource = false; + } else if (storeOp == wgpu::StoreOp::Store) { + resolveParameters.PreserveResolveSource = true; + } + + // RESOLVE_MODE_AVERAGE is only valid for non-integer formats. + // TODO: Investigate and determine how integer format resolves should work in WebGPU. + switch (resolveDestination->GetFormat().GetAspectInfo(Aspect::Color).baseType) { + case wgpu::TextureComponentType::Sint: + case wgpu::TextureComponentType::Uint: + resolveParameters.ResolveMode = D3D12_RESOLVE_MODE_MAX; + break; + case wgpu::TextureComponentType::Float: + resolveParameters.ResolveMode = D3D12_RESOLVE_MODE_AVERAGE; + break; + + case wgpu::TextureComponentType::DepthComparison: + UNREACHABLE(); + } + + resolveParameters.SubresourceCount = 1; + + return resolveParameters; + } + + D3D12_RENDER_PASS_ENDING_ACCESS_RESOLVE_SUBRESOURCE_PARAMETERS + D3D12EndingAccessResolveSubresourceParameters(TextureView* resolveDestination) { + D3D12_RENDER_PASS_ENDING_ACCESS_RESOLVE_SUBRESOURCE_PARAMETERS subresourceParameters; + Texture* resolveDestinationTexture = ToBackend(resolveDestination->GetTexture()); + ASSERT(resolveDestinationTexture->GetFormat().aspects == Aspect::Color); + + subresourceParameters.DstX = 0; + subresourceParameters.DstY = 0; + subresourceParameters.SrcSubresource = 0; + subresourceParameters.DstSubresource = resolveDestinationTexture->GetSubresourceIndex( + resolveDestination->GetBaseMipLevel(), resolveDestination->GetBaseArrayLayer(), + Aspect::Color); + // Resolving a specified sub-rect is only valid on hardware that supports sample + // positions. This means even {0, 0, width, height} would be invalid if unsupported. To + // avoid this, we assume sub-rect resolves never work by setting them to all zeros or + // "empty" to resolve the entire region. + subresourceParameters.SrcRect = {0, 0, 0, 0}; + + return subresourceParameters; + } + } // anonymous namespace + + RenderPassBuilder::RenderPassBuilder(bool hasUAV) { + if (hasUAV) { + mRenderPassFlags = D3D12_RENDER_PASS_FLAG_ALLOW_UAV_WRITES; + } + } + + void RenderPassBuilder::SetRenderTargetView(ColorAttachmentIndex attachmentIndex, + D3D12_CPU_DESCRIPTOR_HANDLE baseDescriptor, + bool isNullRTV) { + mRenderTargetViews[attachmentIndex] = baseDescriptor; + mRenderPassRenderTargetDescriptors[attachmentIndex].cpuDescriptor = baseDescriptor; + if (!isNullRTV) { + mHighestColorAttachmentIndexPlusOne = + std::max(mHighestColorAttachmentIndexPlusOne, + ColorAttachmentIndex{ + static_cast<uint8_t>(static_cast<uint8_t>(attachmentIndex) + 1u)}); + } + } + + void RenderPassBuilder::SetDepthStencilView(D3D12_CPU_DESCRIPTOR_HANDLE baseDescriptor) { + mRenderPassDepthStencilDesc.cpuDescriptor = baseDescriptor; + } + + ColorAttachmentIndex RenderPassBuilder::GetHighestColorAttachmentIndexPlusOne() const { + return mHighestColorAttachmentIndexPlusOne; + } + + bool RenderPassBuilder::HasDepthOrStencil() const { + return mHasDepthOrStencil; + } + + ityp::span<ColorAttachmentIndex, const D3D12_RENDER_PASS_RENDER_TARGET_DESC> + RenderPassBuilder::GetRenderPassRenderTargetDescriptors() const { + return {mRenderPassRenderTargetDescriptors.data(), mHighestColorAttachmentIndexPlusOne}; + } + + const D3D12_RENDER_PASS_DEPTH_STENCIL_DESC* + RenderPassBuilder::GetRenderPassDepthStencilDescriptor() const { + return &mRenderPassDepthStencilDesc; + } + + D3D12_RENDER_PASS_FLAGS RenderPassBuilder::GetRenderPassFlags() const { + return mRenderPassFlags; + } + + const D3D12_CPU_DESCRIPTOR_HANDLE* RenderPassBuilder::GetRenderTargetViews() const { + return mRenderTargetViews.data(); + } + + void RenderPassBuilder::SetRenderTargetBeginningAccess(ColorAttachmentIndex attachment, + wgpu::LoadOp loadOp, + dawn::native::Color clearColor, + DXGI_FORMAT format) { + mRenderPassRenderTargetDescriptors[attachment].BeginningAccess.Type = + D3D12BeginningAccessType(loadOp); + if (loadOp == wgpu::LoadOp::Clear) { + mRenderPassRenderTargetDescriptors[attachment] + .BeginningAccess.Clear.ClearValue.Color[0] = clearColor.r; + mRenderPassRenderTargetDescriptors[attachment] + .BeginningAccess.Clear.ClearValue.Color[1] = clearColor.g; + mRenderPassRenderTargetDescriptors[attachment] + .BeginningAccess.Clear.ClearValue.Color[2] = clearColor.b; + mRenderPassRenderTargetDescriptors[attachment] + .BeginningAccess.Clear.ClearValue.Color[3] = clearColor.a; + mRenderPassRenderTargetDescriptors[attachment].BeginningAccess.Clear.ClearValue.Format = + format; + } + } + + void RenderPassBuilder::SetRenderTargetEndingAccess(ColorAttachmentIndex attachment, + wgpu::StoreOp storeOp) { + mRenderPassRenderTargetDescriptors[attachment].EndingAccess.Type = + D3D12EndingAccessType(storeOp); + } + + void RenderPassBuilder::SetRenderTargetEndingAccessResolve(ColorAttachmentIndex attachment, + wgpu::StoreOp storeOp, + TextureView* resolveSource, + TextureView* resolveDestination) { + mRenderPassRenderTargetDescriptors[attachment].EndingAccess.Type = + D3D12_RENDER_PASS_ENDING_ACCESS_TYPE_RESOLVE; + mRenderPassRenderTargetDescriptors[attachment].EndingAccess.Resolve = + D3D12EndingAccessResolveParameters(storeOp, resolveSource, resolveDestination); + + mSubresourceParams[attachment] = + D3D12EndingAccessResolveSubresourceParameters(resolveDestination); + + mRenderPassRenderTargetDescriptors[attachment].EndingAccess.Resolve.pSubresourceParameters = + &mSubresourceParams[attachment]; + } + + void RenderPassBuilder::SetDepthAccess(wgpu::LoadOp loadOp, + wgpu::StoreOp storeOp, + float clearDepth, + DXGI_FORMAT format) { + mHasDepthOrStencil = true; + mRenderPassDepthStencilDesc.DepthBeginningAccess.Type = D3D12BeginningAccessType(loadOp); + if (loadOp == wgpu::LoadOp::Clear) { + mRenderPassDepthStencilDesc.DepthBeginningAccess.Clear.ClearValue.DepthStencil.Depth = + clearDepth; + mRenderPassDepthStencilDesc.DepthBeginningAccess.Clear.ClearValue.Format = format; + } + mRenderPassDepthStencilDesc.DepthEndingAccess.Type = D3D12EndingAccessType(storeOp); + } + + void RenderPassBuilder::SetStencilAccess(wgpu::LoadOp loadOp, + wgpu::StoreOp storeOp, + uint8_t clearStencil, + DXGI_FORMAT format) { + mHasDepthOrStencil = true; + mRenderPassDepthStencilDesc.StencilBeginningAccess.Type = D3D12BeginningAccessType(loadOp); + if (loadOp == wgpu::LoadOp::Clear) { + mRenderPassDepthStencilDesc.StencilBeginningAccess.Clear.ClearValue.DepthStencil + .Stencil = clearStencil; + mRenderPassDepthStencilDesc.StencilBeginningAccess.Clear.ClearValue.Format = format; + } + mRenderPassDepthStencilDesc.StencilEndingAccess.Type = D3D12EndingAccessType(storeOp); + } + + void RenderPassBuilder::SetDepthNoAccess() { + mRenderPassDepthStencilDesc.DepthBeginningAccess.Type = + D3D12_RENDER_PASS_BEGINNING_ACCESS_TYPE_NO_ACCESS; + mRenderPassDepthStencilDesc.DepthEndingAccess.Type = + D3D12_RENDER_PASS_ENDING_ACCESS_TYPE_NO_ACCESS; + } + + void RenderPassBuilder::SetDepthStencilNoAccess() { + SetDepthNoAccess(); + SetStencilNoAccess(); + } + + void RenderPassBuilder::SetStencilNoAccess() { + mRenderPassDepthStencilDesc.StencilBeginningAccess.Type = + D3D12_RENDER_PASS_BEGINNING_ACCESS_TYPE_NO_ACCESS; + mRenderPassDepthStencilDesc.StencilEndingAccess.Type = + D3D12_RENDER_PASS_ENDING_ACCESS_TYPE_NO_ACCESS; + } + +} // namespace dawn::native::d3d12
diff --git a/src/dawn/native/d3d12/RenderPassBuilderD3D12.h b/src/dawn/native/d3d12/RenderPassBuilderD3D12.h new file mode 100644 index 0000000..5731c52 --- /dev/null +++ b/src/dawn/native/d3d12/RenderPassBuilderD3D12.h
@@ -0,0 +1,101 @@ +// Copyright 2019 The Dawn Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#ifndef DAWNNATIVE_D3D12_RENDERPASSBUILDERD3D12_H_ +#define DAWNNATIVE_D3D12_RENDERPASSBUILDERD3D12_H_ + +#include "dawn/common/Constants.h" +#include "dawn/common/ityp_array.h" +#include "dawn/common/ityp_span.h" +#include "dawn/native/IntegerTypes.h" +#include "dawn/native/d3d12/d3d12_platform.h" +#include "dawn/native/dawn_platform.h" + +#include <array> + +namespace dawn::native::d3d12 { + + class TextureView; + + // RenderPassBuilder stores parameters related to render pass load and store operations. + // When the D3D12 render pass API is available, the needed descriptors can be fetched + // directly from the RenderPassBuilder. When the D3D12 render pass API is not available, the + // descriptors are still fetched and any information necessary to emulate the load and store + // operations is extracted from the descriptors. + class RenderPassBuilder { + public: + RenderPassBuilder(bool hasUAV); + + // Returns the highest color attachment index + 1. If there is no color attachment, returns + // 0. Range: [0, kMaxColorAttachments + 1) + ColorAttachmentIndex GetHighestColorAttachmentIndexPlusOne() const; + + // Returns descriptors that are fed directly to BeginRenderPass, or are used as parameter + // storage if D3D12 render pass API is unavailable. + ityp::span<ColorAttachmentIndex, const D3D12_RENDER_PASS_RENDER_TARGET_DESC> + GetRenderPassRenderTargetDescriptors() const; + const D3D12_RENDER_PASS_DEPTH_STENCIL_DESC* GetRenderPassDepthStencilDescriptor() const; + + D3D12_RENDER_PASS_FLAGS GetRenderPassFlags() const; + + // Returns attachment RTVs to use with OMSetRenderTargets. + const D3D12_CPU_DESCRIPTOR_HANDLE* GetRenderTargetViews() const; + + bool HasDepthOrStencil() const; + + // Functions that set the appropriate values in the render pass descriptors. + void SetDepthAccess(wgpu::LoadOp loadOp, + wgpu::StoreOp storeOp, + float clearDepth, + DXGI_FORMAT format); + void SetDepthNoAccess(); + void SetDepthStencilNoAccess(); + void SetRenderTargetBeginningAccess(ColorAttachmentIndex attachment, + wgpu::LoadOp loadOp, + dawn::native::Color clearColor, + DXGI_FORMAT format); + void SetRenderTargetEndingAccess(ColorAttachmentIndex attachment, wgpu::StoreOp storeOp); + void SetRenderTargetEndingAccessResolve(ColorAttachmentIndex attachment, + wgpu::StoreOp storeOp, + TextureView* resolveSource, + TextureView* resolveDestination); + void SetStencilAccess(wgpu::LoadOp loadOp, + wgpu::StoreOp storeOp, + uint8_t clearStencil, +