mirror of
https://github.com/KytyPS5/KytyPS5.git
synced 2026-10-27 16:02:37 +00:00
Compare commits
187
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
3dc3fd0197 | ||
|
|
fc86509aac | ||
|
|
569ec04d1a | ||
|
|
7d7267cc56 | ||
|
|
2aa0a64f63 | ||
|
|
fc04db7596 | ||
|
|
1275d65bb3 | ||
|
|
3742ad9980 | ||
|
|
c437de9721 | ||
|
|
ba579088d7 | ||
|
|
019929587c | ||
|
|
f68c77d785 | ||
|
|
b5ba482fc2 | ||
|
|
9860c2bbf8 | ||
|
|
ee3749d9b6 | ||
|
|
3fad77fd8b | ||
|
|
d6b2abe26f | ||
|
|
79530b4c5e | ||
|
|
d8a4412498 | ||
|
|
5ed5fdd36e | ||
|
|
ef83ba65ad | ||
|
|
d30dca78be | ||
|
|
66f1a82365 | ||
|
|
eb8a5504a3 | ||
|
|
8a5e5c5476 | ||
|
|
03c55e5712 | ||
|
|
5d1ad31c49 | ||
|
|
13fcb3a133 | ||
|
|
d6dac92da1 | ||
|
|
90471836f7 | ||
|
|
8252caee04 | ||
|
|
8b095caefe | ||
|
|
8dc9b9d532 | ||
|
|
e40438f95e | ||
|
|
a6a56c8a44 | ||
|
|
4b074cf9e9 | ||
|
|
7c9e18e73b | ||
|
|
d2df2cad74 | ||
|
|
b2a80a3a96 | ||
|
|
cfb8f7b246 | ||
|
|
d792dfa885 | ||
|
|
12ff571609 | ||
|
|
d3c182f385 | ||
|
|
bf4dcdce52 | ||
|
|
476204cd4b | ||
|
|
f9e56ef8a0 | ||
|
|
61761b65f2 | ||
|
|
ba55ba5dc5 | ||
|
|
c997cdb837 | ||
|
|
fe4f9423e3 | ||
|
|
81af130147 | ||
|
|
ada0514084 | ||
|
|
d7da244475 | ||
|
|
549495575c | ||
|
|
c3cc8454b9 | ||
|
|
b8ad1d8c4b | ||
|
|
cfb110e0aa | ||
|
|
e930657bf6 | ||
|
|
0ad9149c90 | ||
|
|
25f2699523 | ||
|
|
a849f25376 | ||
|
|
fa51da4d76 | ||
|
|
6b79b7a003 | ||
|
|
102a10f0fe | ||
|
|
8d66c4ef6d | ||
|
|
8cecaf4e30 | ||
|
|
82b2711cdf | ||
|
|
35a275e97d | ||
|
|
303a0203b6 | ||
|
|
cfbaee07f0 | ||
|
|
fe45265fcd | ||
|
|
8b294eb5b8 | ||
|
|
07c9978ac6 | ||
|
|
b61b3f2a40 | ||
|
|
001dfea501 | ||
|
|
f100f785da | ||
|
|
ba1854bbdc | ||
|
|
ea6aae8577 | ||
|
|
5b7d3343cf | ||
|
|
394e638905 | ||
|
|
c2b7b0b58c | ||
|
|
1d2f59dfa4 | ||
|
|
007d7ea2eb | ||
|
|
95d3d9d3ad | ||
|
|
bfc8cd0e0b | ||
|
|
165234b159 | ||
|
|
6493674726 | ||
|
|
cbedf5707f | ||
|
|
57c5b206ca | ||
|
|
87b4417aca | ||
|
|
104530eafc | ||
|
|
6cfde37e98 | ||
|
|
c5cebc1d19 | ||
|
|
697b18b366 | ||
|
|
5b2935e36c | ||
|
|
5dc644edb4 | ||
|
|
1211958b4e | ||
|
|
218e7200bb | ||
|
|
2bf78a94e0 | ||
|
|
cc8e02380f | ||
|
|
c66d977c1d | ||
|
|
4b77686566 | ||
|
|
a6144f794b | ||
|
|
bab1c3614c | ||
|
|
de1545e19a | ||
|
|
ce629e0391 | ||
|
|
6c03b4aaa5 | ||
|
|
0627e23e92 | ||
|
|
4e6f4f99d3 | ||
|
|
f30afc184e | ||
|
|
228cf3fbf4 | ||
|
|
47957faa16 | ||
|
|
235e24d44f | ||
|
|
e0ecffed70 | ||
|
|
fa913ec1a7 | ||
|
|
49b2eedd1c | ||
|
|
63fb822f8a | ||
|
|
f38d738ee2 | ||
|
|
eda6ba37ec | ||
|
|
b594f8c834 | ||
|
|
bc45aaf604 | ||
|
|
fb94a819c7 | ||
|
|
8589731a27 | ||
|
|
c1dccd7db1 | ||
|
|
af3a3f4bf6 | ||
|
|
349fae00d9 | ||
|
|
10cae50b34 | ||
|
|
5b541375a3 | ||
|
|
ded6964953 | ||
|
|
9b2111dc01 | ||
|
|
71f52f9d90 | ||
|
|
86e5e01403 | ||
|
|
77680ce9d2 | ||
|
|
32e6a433e8 | ||
|
|
7c9148dd47 | ||
|
|
957452d6f9 | ||
|
|
e6156bef05 | ||
|
|
441367b1a1 | ||
|
|
629ae1ac05 | ||
|
|
0d9e95d96a | ||
|
|
9cf7963f44 | ||
|
|
acd7435d31 | ||
|
|
7af28c267d | ||
|
|
79120d53ec | ||
|
|
6a2987a7b2 | ||
|
|
d1372abf46 | ||
|
|
98b5d88d67 | ||
|
|
86414760e6 | ||
|
|
a1fbc41146 | ||
|
|
5702cfc935 | ||
|
|
7b5a33f878 | ||
|
|
ea092a9472 | ||
|
|
18a1e0fadc | ||
|
|
437e69ef1b | ||
|
|
dd408ffd00 | ||
|
|
bce8924dd6 | ||
|
|
fe26c1de17 | ||
|
|
01df42a5f3 | ||
|
|
968cf3c03f | ||
|
|
586cbd4a0d | ||
|
|
940e42f130 | ||
|
|
3f80151a6f | ||
|
|
7e89b97daf | ||
|
|
d63fb62367 | ||
|
|
29e9ea6344 | ||
|
|
9a74ef15c3 | ||
|
|
a9ae04c152 | ||
|
|
8e35e1233b | ||
|
|
5610976720 | ||
|
|
1133272668 | ||
|
|
bbebb6419b | ||
|
|
b67cb81359 | ||
|
|
f3c82a317a | ||
|
|
f426d60ff1 | ||
|
|
b2fdad9e47 | ||
|
|
87971a28f5 | ||
|
|
96d62b3367 | ||
|
|
7516068662 | ||
|
|
6d1ba5870a | ||
|
|
00a16f3120 | ||
|
|
d3d7bd33f8 | ||
|
|
069f928edc | ||
|
|
0f3674f531 | ||
|
|
a21ffde34e | ||
|
|
1208d22547 | ||
|
|
3a27f8a76a | ||
|
|
dff2b19c56 |
No files matched your search
@@ -0,0 +1,37 @@
|
||||
# yaml-language-server: $schema=https://coderabbit.ai/integrations/schema.v2.json
|
||||
|
||||
# Keep inherited review profiles, automatic reviews, tools, and pre-merge checks.
|
||||
inheritance: true
|
||||
|
||||
reviews:
|
||||
# Trim supplemental output and keep the remaining walkthrough collapsed.
|
||||
high_level_summary: false
|
||||
review_status: false
|
||||
review_details: false
|
||||
collapse_walkthrough: true
|
||||
changed_files_summary: false
|
||||
sequence_diagrams: false
|
||||
estimate_code_review_effort: false
|
||||
related_issues: false
|
||||
related_prs: false
|
||||
in_progress_fortune: false
|
||||
poem: false
|
||||
enable_prompt_for_ai_agents: false
|
||||
|
||||
# Hide standard finishing-touch controls; this also disables their commands.
|
||||
finishing_touches:
|
||||
docstrings:
|
||||
enabled: false
|
||||
unit_tests:
|
||||
enabled: false
|
||||
simplify:
|
||||
enabled: false
|
||||
autofix:
|
||||
enabled: false
|
||||
fix_ci:
|
||||
enabled: false
|
||||
resolve_merge_conflict:
|
||||
enabled: false
|
||||
|
||||
chat:
|
||||
art: false
|
||||
@@ -6,7 +6,12 @@ on:
|
||||
branches: [ main, master ]
|
||||
pull_request:
|
||||
|
||||
concurrency:
|
||||
group: ${{ github.workflow }}-${{ github.event.pull_request.number || github.ref }}
|
||||
cancel-in-progress: ${{ github.event_name == 'pull_request' }}
|
||||
|
||||
env:
|
||||
SCCACHE_GHA_ENABLED: "true"
|
||||
KYTY_BUILD_ORIGIN: ${{ github.repository != 'KytyPS5/KytyPS5' && 'Fork' || github.event_name == 'push' && 'Official' || 'Source' }}
|
||||
KYTY_BUILD_REPOSITORY: ${{ github.repository }}
|
||||
|
||||
@@ -38,9 +43,6 @@ jobs:
|
||||
- name: Setup Visual Studio environment
|
||||
uses: ilammy/msvc-dev-cmd@v1
|
||||
|
||||
- name: Setup Ninja
|
||||
uses: seanmiddleditch/gha-setup-ninja@v5
|
||||
|
||||
- name: Locate vcpkg
|
||||
id: vcpkg
|
||||
shell: pwsh
|
||||
@@ -78,6 +80,9 @@ jobs:
|
||||
arch: win64_msvc2022_64
|
||||
cache: true
|
||||
|
||||
- name: Setup sccache
|
||||
uses: mozilla-actions/sccache-action@v0.0.11
|
||||
|
||||
- name: Verify toolchain
|
||||
shell: cmd
|
||||
run: |
|
||||
@@ -100,6 +105,8 @@ jobs:
|
||||
-DKYTY_RELEASE_TAG=${{ needs.metadata.outputs.release_tag }} ^
|
||||
-DCMAKE_C_COMPILER=clang-cl ^
|
||||
-DCMAKE_CXX_COMPILER=clang-cl ^
|
||||
-DCMAKE_C_COMPILER_LAUNCHER=sccache ^
|
||||
-DCMAKE_CXX_COMPILER_LAUNCHER=sccache ^
|
||||
-DCMAKE_PREFIX_PATH="%Qt6_DIR%"
|
||||
|
||||
- name: Build
|
||||
@@ -145,7 +152,7 @@ jobs:
|
||||
|
||||
- name: Install build dependencies
|
||||
shell: bash
|
||||
run: brew install glslang ninja
|
||||
run: brew install glslang
|
||||
|
||||
- name: Install Qt 6.10.3
|
||||
uses: jurplel/install-qt-action@v4
|
||||
@@ -156,6 +163,9 @@ jobs:
|
||||
arch: clang_64
|
||||
cache: true
|
||||
|
||||
- name: Setup sccache
|
||||
uses: mozilla-actions/sccache-action@v0.0.11
|
||||
|
||||
- name: Verify toolchain
|
||||
shell: bash
|
||||
run: |
|
||||
@@ -180,6 +190,8 @@ jobs:
|
||||
-DCMAKE_OSX_ARCHITECTURES=x86_64 \
|
||||
-DCMAKE_C_COMPILER=clang \
|
||||
-DCMAKE_CXX_COMPILER=clang++ \
|
||||
-DCMAKE_C_COMPILER_LAUNCHER=sccache \
|
||||
-DCMAKE_CXX_COMPILER_LAUNCHER=sccache \
|
||||
-DCMAKE_PREFIX_PATH="$QT_ROOT_DIR"
|
||||
|
||||
- name: Build
|
||||
@@ -302,8 +314,9 @@ jobs:
|
||||
libxkbcommon-dev \
|
||||
libxrandr-dev \
|
||||
libxss-dev \
|
||||
libxtst-dev \
|
||||
lld \
|
||||
ninja-build \
|
||||
pkg-config \
|
||||
wayland-protocols
|
||||
|
||||
- name: Install Qt 6.10.3
|
||||
@@ -315,6 +328,9 @@ jobs:
|
||||
arch: linux_gcc_64
|
||||
cache: true
|
||||
|
||||
- name: Setup sccache
|
||||
uses: mozilla-actions/sccache-action@v0.0.11
|
||||
|
||||
- name: Verify toolchain
|
||||
shell: bash
|
||||
run: |
|
||||
@@ -339,10 +355,12 @@ jobs:
|
||||
-DKYTY_RELEASE_TAG="${{ needs.metadata.outputs.release_tag }}" \
|
||||
-DCMAKE_C_COMPILER=clang \
|
||||
-DCMAKE_CXX_COMPILER=clang++ \
|
||||
-DCMAKE_C_COMPILER_LAUNCHER=sccache \
|
||||
-DCMAKE_CXX_COMPILER_LAUNCHER=sccache \
|
||||
-DCMAKE_PREFIX_PATH="$Qt6_DIR" 2>&1 | tee _Build/configure.log
|
||||
exit "${PIPESTATUS[0]}"
|
||||
|
||||
- name: Verify SDL2 backends
|
||||
- name: Verify SDL3 backends
|
||||
shell: bash
|
||||
run: |
|
||||
status=0
|
||||
|
||||
+4
-4
@@ -1,7 +1,7 @@
|
||||
[submodule "3rdparty/SDL2"]
|
||||
path = 3rdparty/SDL2
|
||||
[submodule "3rdparty/SDL3"]
|
||||
path = 3rdparty/SDL3
|
||||
url = https://github.com/libsdl-org/SDL.git
|
||||
branch = SDL2
|
||||
branch = release-3.4.x
|
||||
[submodule "3rdparty/magic_enum"]
|
||||
path = 3rdparty/magic_enum
|
||||
url = https://github.com/Neargye/magic_enum.git
|
||||
@@ -22,7 +22,7 @@
|
||||
url = https://github.com/GPUOpen-LibrariesAndSDKs/VulkanMemoryAllocator.git
|
||||
[submodule "3rdparty/ffmpeg-core"]
|
||||
path = 3rdparty/ffmpeg-core
|
||||
url = https://github.com/shadps4-emu/ext-ffmpeg-core.git
|
||||
url = https://github.com/KytyPS5/ext-ffmpeg-core.git
|
||||
shallow = true
|
||||
[submodule "3rdparty/fmt"]
|
||||
path = 3rdparty/fmt
|
||||
|
||||
Vendored
+6
-4
@@ -2,15 +2,17 @@
|
||||
"cmake.sourceDirectory": "${workspaceFolder}",
|
||||
"cmake.buildDirectory": "${workspaceFolder}/_Build/vscode-clang",
|
||||
"cmake.generator": "Ninja",
|
||||
"cmake.useVsDeveloperEnvironment": "always",
|
||||
"cmake.environment": {
|
||||
"CC": "clang-cl",
|
||||
"CXX": "clang-cl",
|
||||
"PATH": "${env:ProgramFiles}/Microsoft Visual Studio/18/Community/VC/Tools/Llvm/x64/bin;${env:ProgramFiles}/Microsoft Visual Studio/18/Community/Common7/IDE/CommonExtensions/Microsoft/CMake/Ninja;${env:PATH}"
|
||||
"CXX": "clang-cl"
|
||||
},
|
||||
"cmake.configureSettings": {
|
||||
"CMAKE_C_COMPILER": "clang-cl",
|
||||
"CMAKE_CXX_COMPILER": "clang-cl",
|
||||
"CMAKE_PREFIX_PATH": "C:/Qt/6.11.0/msvc2022_64"
|
||||
}
|
||||
},
|
||||
"C_Cpp.default.intelliSenseMode": "windows-clang-x64",
|
||||
"C_Cpp.default.compileCommands": "${workspaceFolder}/_Build/vscode-clang/compile_commands.json",
|
||||
"C_Cpp.default.cppStandard": "c++20"
|
||||
}
|
||||
|
||||
Vendored
+4
-1
@@ -29,7 +29,7 @@ FetchContent_Declare(
|
||||
FetchContent_MakeAvailable(xbyak zydis)
|
||||
|
||||
set(SDL_SHARED OFF CACHE BOOL "" FORCE)
|
||||
add_subdirectory(SDL2)
|
||||
add_subdirectory(SDL3)
|
||||
|
||||
add_library(Vulkan-Headers INTERFACE)
|
||||
add_library(Vulkan::Headers ALIAS Vulkan-Headers)
|
||||
@@ -86,6 +86,9 @@ set(SPDLOG_BUILD_WARNINGS OFF CACHE BOOL "" FORCE)
|
||||
set(SPDLOG_INSTALL OFF CACHE BOOL "" FORCE)
|
||||
set(SPDLOG_FMT_EXTERNAL ON CACHE BOOL "" FORCE)
|
||||
set(SPDLOG_NO_EXCEPTIONS ON CACHE BOOL "" FORCE)
|
||||
if(WIN32)
|
||||
set(SPDLOG_WCHAR_FILENAMES ON CACHE BOOL "" FORCE)
|
||||
endif()
|
||||
add_subdirectory(spdlog EXCLUDE_FROM_ALL)
|
||||
|
||||
if (NOT TARGET FFmpeg::ffmpeg)
|
||||
|
||||
Vendored
-1
Submodule 3rdparty/SDL2 deleted from 4b69833bc5.
+1
Submodule 3rdparty/SDL3 added at fa2c02bb6e.
Vendored
+1
-1
Submodule 3rdparty/ffmpeg-core updated: 94dde08c8a...9ac4cfd195.
+47
-18
@@ -1,4 +1,4 @@
|
||||
cmake_minimum_required(VERSION 3.12...4.3)
|
||||
cmake_minimum_required(VERSION 3.16...4.3)
|
||||
|
||||
if(CMAKE_VERSION VERSION_GREATER_EQUAL 4.0)
|
||||
set(CMAKE_POLICY_VERSION_MINIMUM 3.5 CACHE STRING "Minimum policy version for bundled dependencies")
|
||||
@@ -12,6 +12,22 @@ endif()
|
||||
|
||||
project(Kyty)
|
||||
|
||||
find_program(SCCACHE_PROGRAM sccache)
|
||||
find_program(CCACHE_PROGRAM ccache)
|
||||
|
||||
foreach(lang IN ITEMS C CXX)
|
||||
# An explicitly empty launcher disables caching for this language.
|
||||
if(NOT DEFINED CMAKE_${lang}_COMPILER_LAUNCHER)
|
||||
if(SCCACHE_PROGRAM)
|
||||
set(CMAKE_${lang}_COMPILER_LAUNCHER "${SCCACHE_PROGRAM}")
|
||||
message(STATUS "sccache enabled for ${lang}: ${SCCACHE_PROGRAM}")
|
||||
elseif(CCACHE_PROGRAM)
|
||||
set(CMAKE_${lang}_COMPILER_LAUNCHER "${CCACHE_PROGRAM}")
|
||||
message(STATUS "ccache enabled for ${lang}: ${CCACHE_PROGRAM}")
|
||||
endif()
|
||||
endif()
|
||||
endforeach()
|
||||
|
||||
set(KYTY_SOURCE_DIR "${CMAKE_CURRENT_SOURCE_DIR}/src")
|
||||
set(KYTY_TESTS_DIR "${CMAKE_CURRENT_SOURCE_DIR}/tests")
|
||||
set(KYTY_THIRD_PARTY_DIR "${CMAKE_CURRENT_SOURCE_DIR}/3rdparty")
|
||||
@@ -315,31 +331,16 @@ if (KYTY_CLANG_CL)
|
||||
set_source_files_properties(${kyty_emulator_src} PROPERTIES COMPILE_FLAGS "-Wno-pragma-pack -Wno-deprecated-declarations -D_TIMESPEC_DEFINED")
|
||||
endif()
|
||||
|
||||
set(kyty_emulator_link_libraries common Vulkan::Headers spirv-tools-opt spirv-tools SDL2-static imgui xxhash FFmpeg::ffmpeg fmt::fmt nlohmann_json::nlohmann_json LibAtrac9 kyty_git_version Zydis::Zydis xbyak::xbyak)
|
||||
set(kyty_emulator_link_libraries common Vulkan::Headers spirv-tools-opt spirv-tools SDL3::SDL3-static imgui xxhash FFmpeg::ffmpeg fmt::fmt nlohmann_json::nlohmann_json LibAtrac9 kyty_git_version Zydis::Zydis xbyak::xbyak)
|
||||
|
||||
# Linux system libraries required by the static FFmpeg archive.
|
||||
# Linux system libraries.
|
||||
if(LINUX)
|
||||
find_package(Threads REQUIRED)
|
||||
list(APPEND kyty_emulator_link_libraries m ${CMAKE_DL_LIBS} Threads::Threads)
|
||||
|
||||
# Optional FFmpeg dependencies.
|
||||
find_package(ZLIB)
|
||||
if(ZLIB_FOUND)
|
||||
list(APPEND kyty_emulator_link_libraries ZLIB::ZLIB)
|
||||
endif()
|
||||
find_library(KYTY_BZ2_LIBRARY bz2)
|
||||
if(KYTY_BZ2_LIBRARY)
|
||||
list(APPEND kyty_emulator_link_libraries ${KYTY_BZ2_LIBRARY})
|
||||
endif()
|
||||
find_library(KYTY_LZMA_LIBRARY lzma)
|
||||
if(KYTY_LZMA_LIBRARY)
|
||||
list(APPEND kyty_emulator_link_libraries ${KYTY_LZMA_LIBRARY})
|
||||
endif()
|
||||
endif()
|
||||
|
||||
set(inc_headers
|
||||
${KYTY_SOURCE_DIR}
|
||||
${KYTY_THIRD_PARTY_DIR}/SDL2/include
|
||||
${KYTY_THIRD_PARTY_DIR}/VulkanMemoryAllocator/include
|
||||
${KYTY_THIRD_PARTY_DIR}/SPIRV-Tools/include
|
||||
${KYTY_THIRD_PARTY_DIR}/SPIRV-Headers/include
|
||||
@@ -464,6 +465,12 @@ add_executable(audio_out2_port_tests EXCLUDE_FROM_ALL
|
||||
target_link_libraries(audio_out2_port_tests common fmt::fmt)
|
||||
target_include_directories(audio_out2_port_tests PRIVATE ${inc_headers})
|
||||
|
||||
add_executable(save_data_memory_tests EXCLUDE_FROM_ALL
|
||||
"${KYTY_TESTS_DIR}/SaveDataMemoryTests.cpp"
|
||||
)
|
||||
target_link_libraries(save_data_memory_tests common fmt::fmt)
|
||||
target_include_directories(save_data_memory_tests PRIVATE ${inc_headers})
|
||||
|
||||
add_executable(event_queue_lifetime_tests EXCLUDE_FROM_ALL
|
||||
"${KYTY_TESTS_DIR}/EventQueueLifetimeTests.cpp"
|
||||
"${KYTY_SOURCE_DIR}/kernel/eventQueue.cpp"
|
||||
@@ -548,6 +555,7 @@ if(BUILD_TESTING)
|
||||
add_test(NAME kernel_file_system COMMAND $<TARGET_FILE:kernel_file_system_tests>)
|
||||
add_test(NAME sync_on_address COMMAND $<TARGET_FILE:sync_on_address_tests>)
|
||||
add_test(NAME audio_out2_port COMMAND $<TARGET_FILE:audio_out2_port_tests>)
|
||||
add_test(NAME save_data_memory COMMAND $<TARGET_FILE:save_data_memory_tests>)
|
||||
add_test(NAME shader_recompiler_compute COMMAND $<TARGET_FILE:shader_recompiler_compute_tests>)
|
||||
add_test(NAME shader_recompiler_alignbyte
|
||||
COMMAND $<TARGET_FILE:shader_recompiler_compute_tests> --alignbyte-only)
|
||||
@@ -615,6 +623,21 @@ if(BUILD_TESTING)
|
||||
)
|
||||
endif()
|
||||
|
||||
# Test Darwin signal-context restoration for SSE4a instructions.
|
||||
if(APPLE AND (CMAKE_OSX_ARCHITECTURES STREQUAL "x86_64" OR
|
||||
(NOT CMAKE_OSX_ARCHITECTURES AND CMAKE_SYSTEM_PROCESSOR MATCHES "^(x86_64|AMD64)$")))
|
||||
add_executable(macos_sse4a_tests EXCLUDE_FROM_ALL
|
||||
"${KYTY_TESTS_DIR}/macosSse4aTests.cpp"
|
||||
"${KYTY_SOURCE_DIR}/loader/x64InstructionEmulator.cpp"
|
||||
)
|
||||
target_link_libraries(macos_sse4a_tests common fmt::fmt Zydis::Zydis)
|
||||
target_include_directories(macos_sse4a_tests PRIVATE ${inc_headers})
|
||||
if(BUILD_TESTING)
|
||||
add_test(NAME macos_sse4a COMMAND $<TARGET_FILE:macos_sse4a_tests>)
|
||||
set_tests_properties(macos_sse4a PROPERTIES TIMEOUT 15)
|
||||
add_dependencies(kyty_tests macos_sse4a_tests)
|
||||
endif()
|
||||
endif()
|
||||
|
||||
add_executable(kyty_emulator "${KYTY_SOURCE_DIR}/main.cpp" ${kyty_emulator_src})
|
||||
configure_macos_guest_address_space(kyty_emulator)
|
||||
@@ -657,6 +680,12 @@ elseif(WIN32 OR LINUX)
|
||||
set_target_properties(kyty_emulator PROPERTIES LINK_FLAGS "${KYTY_LD_OPTIONS} -Wl,-Map=${KYTY_EMULATOR_MAP_LINK_PATH}")
|
||||
endif()
|
||||
|
||||
if(LINUX)
|
||||
target_link_options(kyty_emulator PRIVATE
|
||||
$<$<CONFIG:Release>:-Wl,--strip-all>
|
||||
)
|
||||
endif()
|
||||
|
||||
if(APPLE)
|
||||
# The emulator writes x86-64 trampolines/PLT stubs into memory and executes them, and
|
||||
# runs guest code under Rosetta. That requires the JIT / unsigned-executable-memory
|
||||
|
||||
@@ -137,6 +137,7 @@ the Vulkan/SPIR-V validation rules.
|
||||
- Visual Studio 2022 or Build Tools 2022 with the **Desktop development with C++** workload and
|
||||
**C++ Clang tools for Windows** component
|
||||
- Qt 6 for MSVC 2022 64-bit, including Concurrent, Network, and Widgets
|
||||
- [glslang](https://github.com/KhronosGroup/glslang/releases) (`glslangValidator`) on `PATH`
|
||||
|
||||
The Microsoft C++ compiler (`cl.exe`) is not supported; use `clang-cl`.
|
||||
|
||||
@@ -165,15 +166,15 @@ The finished application and its runtime dependencies will be placed in
|
||||
|
||||
### Building on Linux
|
||||
|
||||
Install the toolchain and the libraries the bundled SDL2 needs. Without the audio, Wayland and
|
||||
udev development packages SDL2 quietly configures itself without those backends, and the resulting
|
||||
Install the toolchain and the libraries the bundled SDL3 needs. Without the audio, Wayland and
|
||||
udev development packages SDL3 quietly configures itself without those backends, and the resulting
|
||||
build has no working sound and no gamepad hotplug:
|
||||
|
||||
```bash
|
||||
sudo apt-get install --no-install-recommends \
|
||||
clang lld ninja-build cmake git glslang-tools \
|
||||
clang lld ninja-build cmake git glslang-tools pkg-config \
|
||||
libgl1-mesa-dev libx11-dev libxcursor-dev libxext-dev libxfixes-dev \
|
||||
libxi-dev libxrandr-dev libxss-dev libxkbcommon-dev \
|
||||
libxi-dev libxrandr-dev libxss-dev libxtst-dev libxkbcommon-dev \
|
||||
libasound2-dev libpulse-dev libudev-dev libdbus-1-dev libwayland-dev wayland-protocols
|
||||
```
|
||||
|
||||
@@ -192,13 +193,37 @@ cmake --install _Build/linux --prefix _Build/linux/install
|
||||
```
|
||||
|
||||
The install step copies the Qt libraries and plugins next to the binaries, so
|
||||
`_Build/linux/install` runs without a matching system Qt.
|
||||
`_Build/linux/install` runs without a matching system Qt. FFmpeg is linked statically
|
||||
from the pinned [KytyPS5 FFmpeg core](https://github.com/KytyPS5/ext-ffmpeg-core)
|
||||
release, including VP9 and WebM support. System FFmpeg packages are not required.
|
||||
|
||||
As on Windows, the MSVC compiler is not used; Clang is required. `cl.exe` is rejected at configure
|
||||
time.
|
||||
|
||||
The CMake source root is the repository root.
|
||||
|
||||
### Building on NixOS
|
||||
|
||||
A development shell provides Clang, CMake, Ninja, Qt 6, the Vulkan headers, and the SDL3 backend
|
||||
libraries. Enter it and configure exactly as on other Linux distributions; the shell exports
|
||||
`CMAKE_PREFIX_PATH` and `QT_PLUGIN_PATH`, so the `-DCMAKE_PREFIX_PATH="$Qt6_DIR"` argument is not
|
||||
needed:
|
||||
|
||||
```bash
|
||||
nix-shell # or: nix develop
|
||||
git submodule update --init --recursive
|
||||
|
||||
cmake -S . -B _Build/linux -G Ninja -DCMAKE_BUILD_TYPE=Release \
|
||||
-DCMAKE_C_COMPILER=clang -DCMAKE_CXX_COMPILER=clang++
|
||||
|
||||
cmake --build _Build/linux --target launcher --parallel
|
||||
cmake --install _Build/linux --prefix _Build/linux/install
|
||||
```
|
||||
|
||||
The configure step downloads the FFmpeg prebuilts and the `xbyak`/`zydis` sources, so it needs
|
||||
network access; a fully sandboxed `nix build` would require vendoring those inputs. A Vulkan 1.3
|
||||
driver must be available at runtime (on NixOS, `hardware.graphics.enable = true`).
|
||||
|
||||
### Building on macOS
|
||||
|
||||
macOS builds target x86-64 and run under Rosetta 2 on Apple Silicon, so the PS5's x86-64 game
|
||||
|
||||
Generated
+27
@@ -0,0 +1,27 @@
|
||||
{
|
||||
"nodes": {
|
||||
"nixpkgs": {
|
||||
"locked": {
|
||||
"lastModified": 1789426278,
|
||||
"narHash": "sha256-GnotcvKtbTMnoVx6G4E7ZdUX6487Dky74dqq3pU7iRk=",
|
||||
"owner": "NixOS",
|
||||
"repo": "nixpkgs",
|
||||
"rev": "efe6f071ede9d21c37462d2d6682d5e670099684",
|
||||
"type": "github"
|
||||
},
|
||||
"original": {
|
||||
"owner": "NixOS",
|
||||
"ref": "nixos-unstable",
|
||||
"repo": "nixpkgs",
|
||||
"type": "github"
|
||||
}
|
||||
},
|
||||
"root": {
|
||||
"inputs": {
|
||||
"nixpkgs": "nixpkgs"
|
||||
}
|
||||
}
|
||||
},
|
||||
"root": "root",
|
||||
"version": 7
|
||||
}
|
||||
@@ -0,0 +1,20 @@
|
||||
{
|
||||
description = "KytyPS5 development shell";
|
||||
|
||||
inputs.nixpkgs.url = "github:NixOS/nixpkgs/nixos-unstable";
|
||||
|
||||
outputs = { self, nixpkgs }:
|
||||
let
|
||||
systems = [ "x86_64-linux" ];
|
||||
forAllSystems = nixpkgs.lib.genAttrs systems;
|
||||
in
|
||||
{
|
||||
devShells = forAllSystems (system:
|
||||
let
|
||||
pkgs = import nixpkgs { inherit system; };
|
||||
in
|
||||
{
|
||||
default = import ./nix/devshell.nix { inherit pkgs; };
|
||||
});
|
||||
};
|
||||
}
|
||||
@@ -0,0 +1,85 @@
|
||||
{ pkgs }:
|
||||
|
||||
let
|
||||
# Build-time dependencies for the bundled SDL3 audio, video and input backends.
|
||||
buildDeps = with pkgs; [
|
||||
qt6.qtbase # Concurrent, Network, Widgets (launcher)
|
||||
vulkan-headers
|
||||
vulkan-loader
|
||||
vulkan-tools
|
||||
vulkan-validation-layers
|
||||
libglvnd
|
||||
# X11 / input
|
||||
libX11
|
||||
libXcursor
|
||||
libXext
|
||||
libXfixes
|
||||
libXi
|
||||
libXrandr
|
||||
libXScrnSaver
|
||||
libXtst
|
||||
libxkbcommon
|
||||
# Wayland
|
||||
wayland
|
||||
wayland-protocols
|
||||
libdecor
|
||||
# audio + device/dbus
|
||||
alsa-lib
|
||||
libpulseaudio
|
||||
systemd # libudev
|
||||
dbus
|
||||
];
|
||||
|
||||
# Libraries the built binaries need at runtime.
|
||||
runtimeLibs = with pkgs; [
|
||||
stdenv.cc.cc.lib # libstdc++ / libgcc_s
|
||||
libgcc.lib
|
||||
qt6.qtbase # libQt6Concurrent/Network/Widgets
|
||||
vulkan-loader
|
||||
libglvnd
|
||||
alsa-lib
|
||||
libpulseaudio
|
||||
libX11
|
||||
libXcursor
|
||||
libXext
|
||||
libXfixes
|
||||
libXi
|
||||
libXrandr
|
||||
libXScrnSaver
|
||||
libXtst
|
||||
libxkbcommon
|
||||
wayland
|
||||
libdecor
|
||||
systemd
|
||||
dbus
|
||||
];
|
||||
in
|
||||
pkgs.mkShell {
|
||||
name = "kytyps5";
|
||||
|
||||
nativeBuildInputs = with pkgs; [
|
||||
clang
|
||||
lld
|
||||
cmake
|
||||
ninja
|
||||
pkg-config
|
||||
git
|
||||
glslang # provides glslangValidator for the bundled shaders
|
||||
];
|
||||
|
||||
buildInputs = buildDeps;
|
||||
|
||||
shellHook = ''
|
||||
export CMAKE_PREFIX_PATH="${pkgs.qt6.qtbase}:''${CMAKE_PREFIX_PATH:-}"
|
||||
export QT_PLUGIN_PATH="${pkgs.qt6.qtbase}/lib/qt-6/plugins:${pkgs.qt6.qtbase}/lib/qt6/plugins:''${QT_PLUGIN_PATH:-}"
|
||||
export LD_LIBRARY_PATH="${pkgs.lib.makeLibraryPath runtimeLibs}:''${LD_LIBRARY_PATH:-}"
|
||||
|
||||
if [ -z "''${KYTY_SHELL_QUIET:-}" ]; then
|
||||
echo "KytyPS5 dev shell"
|
||||
echo " cmake -S . -B _Build/linux -G Ninja -DCMAKE_BUILD_TYPE=Release \\"
|
||||
echo " -DCMAKE_C_COMPILER=clang -DCMAKE_CXX_COMPILER=clang++"
|
||||
echo " cmake --build _Build/linux --target launcher --parallel"
|
||||
echo " cmake --install _Build/linux --prefix _Build/linux/install"
|
||||
fi
|
||||
'';
|
||||
}
|
||||
@@ -0,0 +1,4 @@
|
||||
let
|
||||
pkgs = import <nixpkgs> { };
|
||||
in
|
||||
import ./nix/devshell.nix { inherit pkgs; }
|
||||
@@ -1,166 +0,0 @@
|
||||
#ifndef KYTY_COMMON_BYTEBUFFER_H_
|
||||
#define KYTY_COMMON_BYTEBUFFER_H_
|
||||
|
||||
#include "common/assert.h"
|
||||
#include "common/common.h"
|
||||
|
||||
#include <algorithm>
|
||||
#include <cstddef> // IWYU pragma: export
|
||||
#include <cstring>
|
||||
#include <utility>
|
||||
#include <vector>
|
||||
|
||||
namespace Common {
|
||||
|
||||
using Byte = std::byte;
|
||||
|
||||
class ByteBuffer {
|
||||
public:
|
||||
using DataType = std::vector<Byte>;
|
||||
|
||||
static constexpr uint32_t INVALID_INDEX = static_cast<uint32_t>(-1);
|
||||
|
||||
using iterator = DataType::iterator;
|
||||
using const_iterator = DataType::const_iterator;
|
||||
|
||||
ByteBuffer() = default;
|
||||
ByteBuffer(const ByteBuffer&) = default;
|
||||
ByteBuffer(ByteBuffer&&) = default;
|
||||
|
||||
ByteBuffer& operator=(const ByteBuffer&) = default;
|
||||
ByteBuffer& operator=(ByteBuffer&&) = default;
|
||||
|
||||
explicit ByteBuffer(uint32_t size): m_data(size) {}
|
||||
|
||||
ByteBuffer(std::initializer_list<uint8_t> list) {
|
||||
for (const uint8_t& e: list) {
|
||||
m_data.emplace_back(static_cast<Byte>(e));
|
||||
}
|
||||
}
|
||||
|
||||
explicit ByteBuffer(const void* buf, uint32_t size)
|
||||
: ByteBuffer(size) // @suppress("Ambiguous problem")
|
||||
{
|
||||
EXIT_IF(buf == nullptr && size != 0);
|
||||
|
||||
if (size != 0) {
|
||||
std::memcpy(m_data.data(), buf, size);
|
||||
}
|
||||
}
|
||||
|
||||
[[nodiscard]] uint32_t Size() const { return static_cast<uint32_t>(m_data.size()); }
|
||||
[[nodiscard]] bool IsEmpty() const { return m_data.empty(); }
|
||||
|
||||
void Clear() { m_data.clear(); }
|
||||
|
||||
void Add(const Byte& val) { m_data.push_back(val); }
|
||||
void Add(Byte&& val) { m_data.push_back(std::move(val)); }
|
||||
|
||||
void Add(const Byte* val, uint32_t num) {
|
||||
EXIT_IF(val == nullptr && num != 0);
|
||||
m_data.insert(m_data.end(), val, val + num);
|
||||
}
|
||||
|
||||
void Add(const ByteBuffer& v) {
|
||||
if (this == &v) {
|
||||
const ByteBuffer copy(v);
|
||||
Add(copy.GetDataConst(), copy.Size());
|
||||
return;
|
||||
}
|
||||
Add(v.GetDataConst(), v.Size());
|
||||
}
|
||||
|
||||
void InsertAt(uint32_t index, const Byte& val) {
|
||||
EXIT_IF(index > Size());
|
||||
m_data.insert(m_data.begin() + index, val);
|
||||
}
|
||||
|
||||
void InsertAt(uint32_t index, Byte&& val) {
|
||||
EXIT_IF(index > Size());
|
||||
m_data.insert(m_data.begin() + index, std::move(val));
|
||||
}
|
||||
|
||||
[[nodiscard]] uint32_t Find(const Byte& t) const {
|
||||
const auto it = std::find(m_data.begin(), m_data.end(), t);
|
||||
return (it == m_data.end() ? INVALID_INDEX
|
||||
: static_cast<uint32_t>(std::distance(m_data.begin(), it)));
|
||||
}
|
||||
|
||||
[[nodiscard]] bool Contains(const Byte& t) const { return Find(t) != INVALID_INDEX; }
|
||||
|
||||
bool Remove(const Byte& t) {
|
||||
const auto it = std::find(m_data.begin(), m_data.end(), t);
|
||||
if (it == m_data.end()) {
|
||||
return false;
|
||||
}
|
||||
m_data.erase(it);
|
||||
return true;
|
||||
}
|
||||
|
||||
bool RemoveAt(uint32_t index, uint32_t count = 1) {
|
||||
if (index >= Size() || count == 0) {
|
||||
return false;
|
||||
}
|
||||
|
||||
const uint32_t erase_count = std::min(count, Size() - index);
|
||||
m_data.erase(m_data.begin() + index, m_data.begin() + index + erase_count);
|
||||
return true;
|
||||
}
|
||||
|
||||
[[nodiscard]] bool IndexValid(uint32_t index) const { return index < Size(); }
|
||||
|
||||
Byte& operator[](uint32_t index) {
|
||||
EXIT_IF(index >= Size());
|
||||
return m_data[index];
|
||||
}
|
||||
|
||||
const Byte& operator[](uint32_t index) const {
|
||||
EXIT_IF(index >= Size());
|
||||
return m_data[index];
|
||||
}
|
||||
|
||||
[[nodiscard]] const Byte& At(uint32_t index) const {
|
||||
EXIT_IF(index >= Size());
|
||||
return m_data[index];
|
||||
}
|
||||
|
||||
Byte* GetData() { return m_data.data(); }
|
||||
[[nodiscard]] const Byte* GetData() const { return m_data.data(); }
|
||||
[[nodiscard]] const Byte* GetDataConst() const { return m_data.data(); }
|
||||
|
||||
void Sort() {
|
||||
std::sort(m_data.begin(), m_data.end(), [](Byte a, Byte b) {
|
||||
return std::to_integer<uint8_t>(a) < std::to_integer<uint8_t>(b);
|
||||
});
|
||||
}
|
||||
|
||||
template <typename OP>
|
||||
void Sort(OP&& comp_func) {
|
||||
std::sort(m_data.begin(), m_data.end(), std::forward<OP>(comp_func));
|
||||
}
|
||||
|
||||
bool operator==(const ByteBuffer& s) const { return m_data == s.m_data; }
|
||||
bool operator!=(const ByteBuffer& s) const { return !(*this == s); }
|
||||
|
||||
iterator begin() { return m_data.begin(); } // NOLINT(readability-identifier-naming)
|
||||
iterator end() { return m_data.end(); } // NOLINT(readability-identifier-naming)
|
||||
[[nodiscard]] const_iterator begin() const {
|
||||
return m_data.begin();
|
||||
} // NOLINT(readability-identifier-naming)
|
||||
[[nodiscard]] const_iterator end() const {
|
||||
return m_data.end();
|
||||
} // NOLINT(readability-identifier-naming)
|
||||
[[nodiscard]] const_iterator cbegin() const {
|
||||
return m_data.cbegin();
|
||||
} // NOLINT(readability-identifier-naming)
|
||||
[[nodiscard]] const_iterator cend() const {
|
||||
return m_data.cend();
|
||||
} // NOLINT(readability-identifier-naming)
|
||||
|
||||
private:
|
||||
DataType m_data;
|
||||
};
|
||||
|
||||
} // namespace Common
|
||||
|
||||
#endif /* KYTY_COMMON_BYTEBUFFER_H_ */
|
||||
@@ -24,8 +24,4 @@ std::string Debug::GetLinker() {
|
||||
#endif
|
||||
}
|
||||
|
||||
std::string Debug::GetBitness() {
|
||||
return "64";
|
||||
}
|
||||
|
||||
} // namespace Common
|
||||
@@ -10,7 +10,6 @@ namespace Common {
|
||||
namespace Debug {
|
||||
std::string GetCompiler();
|
||||
std::string GetLinker();
|
||||
std::string GetBitness();
|
||||
} // namespace Debug
|
||||
|
||||
} // namespace Common
|
||||
|
||||
@@ -43,6 +43,10 @@ int32_t GetUserId() {
|
||||
return g_config->user_id;
|
||||
}
|
||||
|
||||
const std::string& GetAudioInputDevice() {
|
||||
return g_config->audio_input_device;
|
||||
}
|
||||
|
||||
PresentMode GetPresentMode() {
|
||||
return g_config->present_mode;
|
||||
}
|
||||
@@ -55,6 +59,14 @@ bool FullscreenEnabled() {
|
||||
return g_config->fullscreen_enabled;
|
||||
}
|
||||
|
||||
bool VrEnabled() {
|
||||
return g_config->vr_enabled;
|
||||
}
|
||||
|
||||
bool AmdCpuEnabled() {
|
||||
return g_config->amd_cpu_enabled;
|
||||
}
|
||||
|
||||
uint32_t GetVblankFrequency() {
|
||||
return std::clamp(g_config->vblank_frequency, 30u, 360u);
|
||||
}
|
||||
@@ -123,6 +135,10 @@ bool ReadbackLinearImagesEnabled() {
|
||||
return g_config->readback_linear_images;
|
||||
}
|
||||
|
||||
bool TessellationEnabled() {
|
||||
return g_config->tessellation_enabled;
|
||||
}
|
||||
|
||||
bool PlayGoHackEnabled() {
|
||||
return g_config->playgo_hack_enabled;
|
||||
}
|
||||
|
||||
@@ -43,9 +43,12 @@ struct ConfigOptions {
|
||||
uint32_t screen_height = 720;
|
||||
std::string user_name = "Kyty";
|
||||
int32_t user_id = DEFAULT_USER_ID;
|
||||
std::string audio_input_device;
|
||||
PresentMode present_mode = PresentMode::Mailbox;
|
||||
int32_t gpu_index = -1;
|
||||
bool fullscreen_enabled = false;
|
||||
bool vr_enabled = false;
|
||||
bool amd_cpu_enabled = false;
|
||||
uint32_t vblank_frequency = 60;
|
||||
uint32_t console_language = DEFAULT_CONSOLE_LANGUAGE;
|
||||
bool vulkan_validation_enabled = false;
|
||||
@@ -63,6 +66,7 @@ struct ConfigOptions {
|
||||
bool gpu_assisted_validation_enabled = false;
|
||||
bool renderdoc_enabled = false;
|
||||
bool readback_linear_images = false;
|
||||
bool tessellation_enabled = false;
|
||||
bool playgo_hack_enabled = false;
|
||||
#if KYTY_PLATFORM == KYTY_PLATFORM_WINDOWS
|
||||
bool red_zone_protection_enabled = false;
|
||||
@@ -76,9 +80,12 @@ uint32_t GetScreenWidth();
|
||||
uint32_t GetScreenHeight();
|
||||
const std::string& GetUserName();
|
||||
int32_t GetUserId();
|
||||
const std::string& GetAudioInputDevice();
|
||||
PresentMode GetPresentMode();
|
||||
int32_t GetGpuIndex();
|
||||
bool FullscreenEnabled();
|
||||
bool VrEnabled();
|
||||
bool AmdCpuEnabled();
|
||||
uint32_t GetVblankFrequency();
|
||||
uint32_t GetConsoleLanguage();
|
||||
bool VulkanValidationEnabled();
|
||||
@@ -104,6 +111,7 @@ bool GpuAssistedValidationEnabled();
|
||||
|
||||
bool RenderDocEnabled();
|
||||
bool ReadbackLinearImagesEnabled();
|
||||
bool TessellationEnabled();
|
||||
bool PlayGoHackEnabled();
|
||||
#if KYTY_PLATFORM == KYTY_PLATFORM_WINDOWS
|
||||
bool RedZoneProtectionEnabled();
|
||||
|
||||
+53
-128
@@ -136,10 +136,6 @@ bool File::OpenInMem(void* buf, uint32_t buf_size) {
|
||||
return true;
|
||||
}
|
||||
|
||||
bool File::OpenInMem(ByteBuffer& buf) {
|
||||
return OpenInMem(buf.GetData(), buf.Size());
|
||||
}
|
||||
|
||||
bool File::CreateInMem() {
|
||||
EXIT_IF(m_p->f != nullptr);
|
||||
|
||||
@@ -249,88 +245,78 @@ void File::Printf(const char* format, ...) {
|
||||
Write(s.data(), static_cast<uint32_t>(s.size()));
|
||||
}
|
||||
|
||||
static std::filesystem::path WithoutTrailingSeparator(
|
||||
const std::filesystem::path& path) {
|
||||
if (!path.empty() && !path.has_filename() &&
|
||||
path != path.root_path()) {
|
||||
return path.parent_path();
|
||||
}
|
||||
|
||||
return path;
|
||||
}
|
||||
|
||||
|
||||
bool File::IsDirectoryExisting(const std::filesystem::path& path) {
|
||||
auto path_str = PathToGenericString(path);
|
||||
return SysFileIsDirectoryExisting(
|
||||
Common::EndsWith(path_str, "/") ? Common::RemoveLast(path_str, 1) : path_str);
|
||||
return SysFileIsDirectoryExisting(WithoutTrailingSeparator(path));
|
||||
}
|
||||
|
||||
bool File::IsFileExisting(const std::filesystem::path& name) {
|
||||
return SysFileIsFileExisting(name);
|
||||
}
|
||||
|
||||
bool File::CreateDirectory(
|
||||
const std::filesystem::path& path) // @suppress("Member declaration not found")
|
||||
{
|
||||
auto path_str = PathToGenericString(path);
|
||||
return SysFileCreateDirectory(Common::EndsWith(path_str, "/") ? Common::RemoveLast(path_str, 1)
|
||||
: path_str);
|
||||
bool File::CreateDirectory(const std::filesystem::path& path) {
|
||||
return SysFileCreateDirectory(
|
||||
WithoutTrailingSeparator(path));
|
||||
}
|
||||
|
||||
bool File::DeleteDirectory(const std::filesystem::path& path) {
|
||||
auto path_str = PathToGenericString(path);
|
||||
return SysFileDeleteDirectory(Common::EndsWith(path_str, "/") ? Common::RemoveLast(path_str, 1)
|
||||
: path_str);
|
||||
return SysFileDeleteDirectory(
|
||||
WithoutTrailingSeparator(path));
|
||||
}
|
||||
|
||||
bool File::CreateDirectories(const std::filesystem::path& path) {
|
||||
std::string real_path = Common::ReplaceChar(PathToGenericString(path), '\\', '/');
|
||||
const auto normalized_path =
|
||||
WithoutTrailingSeparator(path);
|
||||
|
||||
std::vector<std::string> list = Common::Split(real_path, "/");
|
||||
auto current_path = normalized_path.root_path();
|
||||
|
||||
std::string p;
|
||||
for (const auto& component:
|
||||
normalized_path.relative_path()) {
|
||||
current_path /= component;
|
||||
|
||||
for (uint32_t si = 0; si < list.size(); si++) {
|
||||
const std::string& s = list[si];
|
||||
if (IsDirectoryExisting(current_path)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (si != 0 || Common::StartsWith(real_path, "/")) {
|
||||
p += "/";
|
||||
}
|
||||
if (!CreateDirectory(current_path)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
p += s;
|
||||
|
||||
if (IsDirectoryExisting(p)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (!CreateDirectory(p)) // @suppress("Invalid arguments")
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
return true;
|
||||
}
|
||||
|
||||
bool File::DeleteDirectories(const std::filesystem::path& path) {
|
||||
std::string real_path = Common::ReplaceChar(PathToGenericString(path), '\\', '/');
|
||||
const auto normalized_path =
|
||||
WithoutTrailingSeparator(path);
|
||||
|
||||
std::vector<std::string> list = Common::Split(real_path, "/");
|
||||
std::vector<std::filesystem::path> directories;
|
||||
auto current_path = normalized_path.root_path();
|
||||
|
||||
std::string p;
|
||||
std::vector<std::string> list2;
|
||||
for (const auto& component:
|
||||
normalized_path.relative_path()) {
|
||||
current_path /= component;
|
||||
directories.push_back(current_path);
|
||||
}
|
||||
|
||||
for (uint32_t si = 0; si < list.size(); si++) {
|
||||
const std::string& s = list[si];
|
||||
for (auto it = directories.rbegin();
|
||||
it != directories.rend(); ++it) {
|
||||
if (!DeleteDirectory(*it)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
if (si != 0 || Common::StartsWith(real_path, "/")) {
|
||||
p += "/";
|
||||
}
|
||||
|
||||
p += s;
|
||||
|
||||
list2.push_back(p);
|
||||
}
|
||||
|
||||
uint32_t num = list2.size();
|
||||
|
||||
for (uint32_t si = num - 1; si < num; si--) {
|
||||
if (!DeleteDirectory(list2[si])) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
return true;
|
||||
}
|
||||
|
||||
bool File::DeleteFile(
|
||||
@@ -345,7 +331,7 @@ bool File::Flush() {
|
||||
return SysFileFlush(*m_p->f);
|
||||
}
|
||||
|
||||
ByteBuffer File::ReadWholeBuffer() {
|
||||
std::vector<std::byte> File::ReadWholeBuffer() {
|
||||
EXIT_IF(IsInvalid());
|
||||
EXIT_IF(Tell() != 0);
|
||||
|
||||
@@ -353,25 +339,14 @@ ByteBuffer File::ReadWholeBuffer() {
|
||||
|
||||
EXIT_IF((s >> 32u) != 0);
|
||||
|
||||
ByteBuffer buf(s);
|
||||
const auto read_size = static_cast<uint32_t>(s);
|
||||
std::vector<std::byte> buf(read_size);
|
||||
|
||||
Read(buf.GetData(), s);
|
||||
Read(buf.data(), read_size);
|
||||
|
||||
return buf;
|
||||
}
|
||||
|
||||
ByteBuffer File::Read(uint32_t size) {
|
||||
ByteBuffer buf(size);
|
||||
uint32_t b = 0;
|
||||
Read(buf.GetData(), size, &b);
|
||||
buf.RemoveAt(b, size - b);
|
||||
return buf;
|
||||
}
|
||||
|
||||
void File::Write(const ByteBuffer& buf, uint32_t* bytes_written) {
|
||||
Write(buf.GetDataConst(), buf.Size(), bytes_written);
|
||||
}
|
||||
|
||||
DateTime File::GetLastAccessTimeUTC(const std::filesystem::path& name) {
|
||||
SysTimeStruct t {};
|
||||
SysFileToSystemTimeUtc(SysFileGetLastAccessTimeUtc(name), t);
|
||||
@@ -470,51 +445,6 @@ bool File::SetLastAccessAndWriteTimeUTC(const std::filesystem::path& name, const
|
||||
return SysFileSetLastAccessAndWriteTimeUtc(name, af, wf);
|
||||
}
|
||||
|
||||
std::vector<File::FindInfo> File::FindFiles(const std::filesystem::path& path) {
|
||||
std::vector<sys_file_find_t> files;
|
||||
|
||||
SysFileFindFiles(path, files);
|
||||
|
||||
auto path_str = PathToGenericString(path);
|
||||
uint32_t len = static_cast<uint32_t>(path_str.size());
|
||||
|
||||
if (!Common::EndsWith(path_str, "/") && !Common::EndsWith(path_str, "\\")) {
|
||||
len++;
|
||||
}
|
||||
|
||||
std::vector<File::FindInfo> ret;
|
||||
ret.reserve(files.size());
|
||||
|
||||
for (const auto& f: files) {
|
||||
File::FindInfo r {};
|
||||
|
||||
r.path_with_name = f.path_with_name;
|
||||
r.rel_path_with_name = Common::Mid(PathToGenericString(f.path_with_name), len);
|
||||
r.size = f.size;
|
||||
|
||||
SysTimeStruct at {};
|
||||
SysTimeStruct wt {};
|
||||
|
||||
SysFileToSystemTimeUtc(f.last_access_time, at);
|
||||
SysFileToSystemTimeUtc(f.last_write_time, wt);
|
||||
|
||||
if (!at.is_invalid && !wt.is_invalid) {
|
||||
r.last_access_time = DateTime(Date(at.Year, at.Month, at.Day),
|
||||
Time(at.Hour, at.Minute, at.Second, at.Milliseconds));
|
||||
r.last_write_time = DateTime(Date(wt.Year, wt.Month, wt.Day),
|
||||
Time(wt.Hour, wt.Minute, wt.Second, wt.Milliseconds));
|
||||
}
|
||||
|
||||
// printf("%s, %s, %" PRIu64", %s, %s\n", r.path_with_name.c_str(),
|
||||
// r.rel_path_with_name.c_str(), r.size, r.last_access_time.ToString().c_str(),
|
||||
// r.last_write_time.ToString().c_str());
|
||||
|
||||
ret.push_back(std::move(r));
|
||||
}
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
std::vector<File::DirEntry> File::GetDirEntries(const std::filesystem::path& path) {
|
||||
std::vector<sys_dir_entry_t> files;
|
||||
|
||||
@@ -523,13 +453,8 @@ std::vector<File::DirEntry> File::GetDirEntries(const std::filesystem::path& pat
|
||||
std::vector<File::DirEntry> ret;
|
||||
ret.reserve(files.size());
|
||||
|
||||
for (const auto& f: files) {
|
||||
File::DirEntry r {};
|
||||
|
||||
r.name = f.name;
|
||||
r.is_file = f.is_file;
|
||||
|
||||
ret.push_back(std::move(r));
|
||||
for (auto& f: files) {
|
||||
ret.emplace_back(std::move(f.name), f.is_file);
|
||||
}
|
||||
|
||||
return ret;
|
||||
|
||||
+4
-16
@@ -1,10 +1,10 @@
|
||||
#ifndef KYTY_COMMON_FILE_H_
|
||||
#define KYTY_COMMON_FILE_H_
|
||||
|
||||
#include "common/byteBuffer.h"
|
||||
#include "common/common.h"
|
||||
#include "common/dateTime.h"
|
||||
|
||||
#include <cstddef>
|
||||
#include <filesystem>
|
||||
#include <memory>
|
||||
#include <string>
|
||||
@@ -30,14 +30,6 @@ class File {
|
||||
public:
|
||||
enum class Mode { Read, Write, ReadWrite, WriteRead };
|
||||
|
||||
struct FindInfo {
|
||||
std::filesystem::path path_with_name;
|
||||
std::filesystem::path rel_path_with_name;
|
||||
DateTime last_access_time;
|
||||
DateTime last_write_time;
|
||||
uint64_t size;
|
||||
};
|
||||
|
||||
struct DirEntry {
|
||||
std::string name;
|
||||
bool is_file;
|
||||
@@ -51,7 +43,6 @@ public:
|
||||
bool Create(const std::filesystem::path& name);
|
||||
bool Open(const std::filesystem::path& name, Mode mode);
|
||||
bool OpenInMem(void* buf, uint32_t buf_size);
|
||||
bool OpenInMem(ByteBuffer& buf); // NOLINT(google-runtime-references)
|
||||
bool CreateInMem();
|
||||
|
||||
void Close();
|
||||
@@ -73,14 +64,12 @@ public:
|
||||
|
||||
void GetLastAccessAndWriteTimeUTC(DateTime* access, DateTime* write);
|
||||
|
||||
void Read(void* data, uint32_t size, uint32_t* bytes_read = nullptr);
|
||||
ByteBuffer Read(uint32_t size);
|
||||
void Write(const void* data, uint32_t size, uint32_t* bytes_written = nullptr);
|
||||
void Write(const ByteBuffer& buf, uint32_t* bytes_written = nullptr);
|
||||
void Read(void* data, uint32_t size, uint32_t* bytes_read = nullptr);
|
||||
void Write(const void* data, uint32_t size, uint32_t* bytes_written = nullptr);
|
||||
|
||||
void Printf(const char* format, ...) KYTY_FORMAT_PRINTF(2, 3);
|
||||
|
||||
ByteBuffer ReadWholeBuffer();
|
||||
std::vector<std::byte> ReadWholeBuffer();
|
||||
|
||||
static uint64_t Size(const std::filesystem::path& name);
|
||||
|
||||
@@ -101,7 +90,6 @@ public:
|
||||
static bool SetLastAccessAndWriteTimeUTC(const std::filesystem::path& name,
|
||||
const DateTime& access, const DateTime& write);
|
||||
|
||||
static std::vector<FindInfo> FindFiles(const std::filesystem::path& path);
|
||||
static std::vector<DirEntry> GetDirEntries(const std::filesystem::path& path);
|
||||
static bool CopyFile(const std::filesystem::path& src, const std::filesystem::path& dst);
|
||||
static bool RenameFile(const std::filesystem::path& src, const std::filesystem::path& dst);
|
||||
|
||||
@@ -54,103 +54,6 @@ inline uint32_t hash(const void* key, uint32_t key_len) {
|
||||
return hash;
|
||||
}
|
||||
|
||||
inline uint32_t hash8(uint8_t key) {
|
||||
uint32_t hash = 0;
|
||||
|
||||
uint8_t* ptr = &key;
|
||||
|
||||
hash += ptr[0];
|
||||
hash += (hash << 10u);
|
||||
hash ^= (hash >> 6u);
|
||||
|
||||
hash += (hash << 3u);
|
||||
hash ^= (hash >> 11u);
|
||||
hash += (hash << 15u);
|
||||
|
||||
return hash;
|
||||
}
|
||||
|
||||
inline uint32_t hash16(uint16_t key) {
|
||||
uint32_t hash = 0;
|
||||
|
||||
auto* ptr = reinterpret_cast<uint8_t*>(&key);
|
||||
|
||||
hash += ptr[0];
|
||||
hash += (hash << 10u);
|
||||
hash ^= (hash >> 6u);
|
||||
hash += ptr[1];
|
||||
hash += (hash << 10u);
|
||||
hash ^= (hash >> 6u);
|
||||
|
||||
hash += (hash << 3u);
|
||||
hash ^= (hash >> 11u);
|
||||
hash += (hash << 15u);
|
||||
|
||||
return hash;
|
||||
}
|
||||
|
||||
inline uint32_t hash32(uint32_t key) {
|
||||
uint32_t hash = 0;
|
||||
|
||||
auto* ptr = reinterpret_cast<uint8_t*>(&key);
|
||||
|
||||
hash += ptr[0];
|
||||
hash += (hash << 10u);
|
||||
hash ^= (hash >> 6u);
|
||||
hash += ptr[1];
|
||||
hash += (hash << 10u);
|
||||
hash ^= (hash >> 6u);
|
||||
hash += ptr[2];
|
||||
hash += (hash << 10u);
|
||||
hash ^= (hash >> 6u);
|
||||
hash += ptr[3];
|
||||
hash += (hash << 10u);
|
||||
hash ^= (hash >> 6u);
|
||||
|
||||
hash += (hash << 3u);
|
||||
hash ^= (hash >> 11u);
|
||||
hash += (hash << 15u);
|
||||
|
||||
return hash;
|
||||
}
|
||||
|
||||
inline uint32_t hash64(uint64_t key) {
|
||||
uint32_t hash = 0;
|
||||
|
||||
auto* ptr = reinterpret_cast<uint8_t*>(&key);
|
||||
|
||||
hash += ptr[0];
|
||||
hash += (hash << 10u);
|
||||
hash ^= (hash >> 6u);
|
||||
hash += ptr[1];
|
||||
hash += (hash << 10u);
|
||||
hash ^= (hash >> 6u);
|
||||
hash += ptr[2];
|
||||
hash += (hash << 10u);
|
||||
hash ^= (hash >> 6u);
|
||||
hash += ptr[3];
|
||||
hash += (hash << 10u);
|
||||
hash ^= (hash >> 6u);
|
||||
hash += ptr[4];
|
||||
hash += (hash << 10u);
|
||||
hash ^= (hash >> 6u);
|
||||
hash += ptr[5];
|
||||
hash += (hash << 10u);
|
||||
hash ^= (hash >> 6u);
|
||||
hash += ptr[6];
|
||||
hash += (hash << 10u);
|
||||
hash ^= (hash >> 6u);
|
||||
hash += ptr[7];
|
||||
hash += (hash << 10u);
|
||||
hash ^= (hash >> 6u);
|
||||
|
||||
hash += (hash << 3u);
|
||||
hash ^= (hash >> 11u);
|
||||
hash += (hash << 15u);
|
||||
|
||||
return hash;
|
||||
}
|
||||
|
||||
} // namespace Common
|
||||
|
||||
#endif /* KYTY_COMMON_HASH_H_ */
|
||||
@@ -3,7 +3,6 @@
|
||||
|
||||
#include "common/assert.h"
|
||||
#include "common/emulatorConfig.h"
|
||||
#include "common/stringUtils.h"
|
||||
|
||||
#include <cstdio>
|
||||
#include <filesystem>
|
||||
@@ -47,7 +46,7 @@ std::shared_ptr<spdlog::logger> MakeFileLogger(std::string name
|
||||
}
|
||||
|
||||
auto sink =
|
||||
std::make_shared<spdlog::sinks::basic_file_sink_mt>(Common::PathToString(path), true);
|
||||
std::make_shared<spdlog::sinks::basic_file_sink_mt>(path.native(), true);
|
||||
return MakeLogger(std::move(name), std::move(sink));
|
||||
}
|
||||
|
||||
@@ -129,6 +128,14 @@ static void WriteImpl(std::string_view text, fmt::text_style style = {}) {
|
||||
}
|
||||
}
|
||||
|
||||
void WriteToConsoleAndLog(std::string_view text) {
|
||||
WriteImpl(text);
|
||||
if (g_initialized && g_direction != Direction::Console) {
|
||||
WriteStdout(text);
|
||||
}
|
||||
Flush();
|
||||
}
|
||||
|
||||
void WriteFatal(std::string_view text) {
|
||||
if (g_direction == Direction::Silent || !g_initialized) {
|
||||
WriteStdout(text);
|
||||
|
||||
@@ -25,6 +25,7 @@ Direction GetDirection();
|
||||
bool IsSilent();
|
||||
void Write(std::string_view text);
|
||||
void Write(fmt::text_style style, std::string_view text);
|
||||
void WriteToConsoleAndLog(std::string_view text);
|
||||
void WriteFatal(std::string_view text);
|
||||
void WriteFatal(fmt::text_style style, std::string_view text);
|
||||
void Flush();
|
||||
|
||||
@@ -1,42 +0,0 @@
|
||||
#ifndef KYTY_COMMON_MAGICENUM_H_
|
||||
#define KYTY_COMMON_MAGICENUM_H_
|
||||
|
||||
#include "common/common.h"
|
||||
#include "common/stringUtils.h"
|
||||
#include "magic_enum.hpp" // IWYU pragma: export
|
||||
|
||||
namespace Common {
|
||||
|
||||
template <typename E>
|
||||
inline std::string EnumName(E v) {
|
||||
auto str = magic_enum::enum_name(v);
|
||||
return std::string(str.data(), str.length());
|
||||
}
|
||||
|
||||
template <typename E>
|
||||
inline std::string EnumName8(E v) {
|
||||
auto str = magic_enum::enum_name(v);
|
||||
return std::string(str.data(), str.length());
|
||||
}
|
||||
|
||||
template <typename E>
|
||||
inline E EnumValue(const std::string& str, E default_value) {
|
||||
auto v = magic_enum::enum_cast<E>(str.c_str());
|
||||
if (v.has_value()) {
|
||||
return v.value();
|
||||
}
|
||||
return default_value;
|
||||
}
|
||||
|
||||
#define KYTY_ENUM_RANGE(e, mx, mn) \
|
||||
namespace magic_enum::customize { \
|
||||
template <> \
|
||||
struct enum_range<e> { \
|
||||
static constexpr int min = (mx); \
|
||||
static constexpr int max = (mn); \
|
||||
}; \
|
||||
}
|
||||
|
||||
} // namespace Common
|
||||
|
||||
#endif /* KYTY_COMMON_MAGICENUM_H_ */
|
||||
@@ -18,14 +18,6 @@ enum sys_file_cache_type_t {
|
||||
SYS_FILE_CACHE_SEQUENTIAL_SCAN = 2 // NOLINT(readability-identifier-naming)
|
||||
};
|
||||
|
||||
// NOLINTNEXTLINE(readability-identifier-naming)
|
||||
struct sys_file_find_t {
|
||||
std::filesystem::path path_with_name;
|
||||
SysFileTimeStruct last_access_time;
|
||||
SysFileTimeStruct last_write_time;
|
||||
uint64_t size;
|
||||
};
|
||||
|
||||
// NOLINTNEXTLINE(readability-identifier-naming)
|
||||
struct sys_dir_entry_t {
|
||||
std::string name;
|
||||
@@ -52,7 +44,6 @@ uint64_t SysFileTell(sys_file_t& f); // NOLINT(google-runt
|
||||
bool SysFileTruncate(sys_file_t& f, uint64_t size); // NOLINT(google-runtime-references)
|
||||
bool SysFileUnlink(sys_file_t& f,
|
||||
const std::filesystem::path& name); // NOLINT(google-runtime-references)
|
||||
void SysFileWrite(uint32_t n, sys_file_t& f); // NOLINT(google-runtime-references)
|
||||
uint64_t SysFileSize(const std::filesystem::path& file_name);
|
||||
bool SysFileIsError(sys_file_t& f); // NOLINT(google-runtime-references)
|
||||
bool SysFileIsDirectoryExisting(const std::filesystem::path& path);
|
||||
@@ -76,8 +67,6 @@ bool SysFileSetLastWriteTimeUtc(const std::filesystem::path& name, SysFileTimeSt
|
||||
bool SysFileSetLastAccessAndWriteTimeUtc(const std::filesystem::path& name,
|
||||
SysFileTimeStruct& access, SysFileTimeStruct& write);
|
||||
// NOLINTNEXTLINE(google-runtime-references)
|
||||
void SysFileFindFiles(const std::filesystem::path& path, std::vector<sys_file_find_t>& out);
|
||||
// NOLINTNEXTLINE(google-runtime-references)
|
||||
void SysFileGetDents(const std::filesystem::path& path, std::vector<sys_dir_entry_t>& out);
|
||||
bool SysFileCopyFile(const std::filesystem::path& src, const std::filesystem::path& dst);
|
||||
bool SysFileRenameFile(const std::filesystem::path& src, const std::filesystem::path& dst);
|
||||
|
||||
@@ -11,6 +11,7 @@
|
||||
|
||||
#include <cerrno>
|
||||
#include <cstdlib>
|
||||
#include <cstring>
|
||||
#include <dirent.h>
|
||||
#include <fcntl.h>
|
||||
#include <filesystem>
|
||||
@@ -151,10 +152,6 @@ void SysFileWrite(const void* data, uint32_t size, sys_file_t& f, uint32_t* byte
|
||||
}
|
||||
}
|
||||
|
||||
void SysFileWrite(uint32_t n, sys_file_t& f) {
|
||||
SysFileWrite(&n, 4, f);
|
||||
}
|
||||
|
||||
sys_file_t* SysFileCreate(const std::filesystem::path& file_name) {
|
||||
auto* ret = new sys_file_t;
|
||||
|
||||
@@ -613,51 +610,6 @@ bool SysFileSetLastAccessAndWriteTimeUtc(const std::filesystem::path& name,
|
||||
// }
|
||||
}
|
||||
|
||||
// Recursively collect regular files.
|
||||
void SysFileFindFiles(const std::filesystem::path& path, std::vector<sys_file_find_t>& out) {
|
||||
auto real_path = get_internal_name(path);
|
||||
|
||||
DIR* dir = opendir(real_path.string().c_str());
|
||||
if (dir == nullptr) {
|
||||
return;
|
||||
}
|
||||
|
||||
for (const dirent* entry = readdir(dir); entry != nullptr; entry = readdir(dir)) {
|
||||
const std::string file_name(entry->d_name);
|
||||
|
||||
if (file_name == "." || file_name == "..") {
|
||||
continue;
|
||||
}
|
||||
|
||||
auto child = real_path / file_name;
|
||||
struct stat s {};
|
||||
|
||||
// lstat, so a symlink is never followed into a cycle during the recursive walk.
|
||||
if (0 != lstat(child.string().c_str(), &s)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (S_ISDIR(s.st_mode)) {
|
||||
SysFileFindFiles(child, out);
|
||||
} else if (S_ISREG(s.st_mode)) {
|
||||
sys_file_find_t r {};
|
||||
|
||||
r.path_with_name = child;
|
||||
r.size = static_cast<uint64_t>(s.st_size);
|
||||
r.last_access_time.is_invalid = false;
|
||||
r.last_access_time.time = s.st_atime;
|
||||
r.last_access_time.nanos = KYTY_STAT_ATIME_NS(s);
|
||||
r.last_write_time.is_invalid = false;
|
||||
r.last_write_time.time = s.st_mtime;
|
||||
r.last_write_time.nanos = KYTY_STAT_MTIME_NS(s);
|
||||
|
||||
out.push_back(r);
|
||||
}
|
||||
}
|
||||
|
||||
closedir(dir);
|
||||
}
|
||||
|
||||
// Keep "." and ".." to match FindFirstFileW.
|
||||
void SysFileGetDents(const std::filesystem::path& path, std::vector<sys_dir_entry_t>& out) {
|
||||
auto real_path = get_internal_name(path);
|
||||
|
||||
@@ -5,7 +5,6 @@
|
||||
#else
|
||||
|
||||
#include "common/assert.h"
|
||||
#include "common/platform/sysVirtual.h"
|
||||
#include "common/virtualMemory.h"
|
||||
|
||||
#include <atomic>
|
||||
@@ -28,12 +27,12 @@
|
||||
#define KYTY_FIXED_NOREPLACE
|
||||
#endif
|
||||
|
||||
namespace Common {
|
||||
namespace Common::VirtualMemory {
|
||||
|
||||
static pthread_mutex_t g_virtual_mutex {};
|
||||
static std::map<uintptr_t, size_t>* g_allocs = nullptr;
|
||||
|
||||
void SysVirtualInit() {
|
||||
void Init() {
|
||||
pthread_mutexattr_t attr {};
|
||||
|
||||
pthread_mutexattr_init(&attr);
|
||||
@@ -48,19 +47,19 @@ void SysVirtualInit() {
|
||||
g_allocs = new std::map<uintptr_t, size_t>;
|
||||
}
|
||||
|
||||
static int get_protection_flag(VirtualMemory::Mode mode) {
|
||||
static int get_protection_flag(Mode mode) {
|
||||
int protect = PROT_NONE;
|
||||
switch (mode) {
|
||||
case VirtualMemory::Mode::Read: protect = PROT_READ; break;
|
||||
case VirtualMemory::Mode::Write:
|
||||
case VirtualMemory::Mode::ReadWrite: protect = PROT_READ | PROT_WRITE; break; // NOLINT
|
||||
case VirtualMemory::Mode::Execute: protect = PROT_EXEC; break;
|
||||
case VirtualMemory::Mode::ExecuteRead: protect = PROT_EXEC | PROT_READ; break; // NOLINT
|
||||
case VirtualMemory::Mode::ExecuteWrite:
|
||||
case VirtualMemory::Mode::ExecuteReadWrite:
|
||||
case Mode::Read: protect = PROT_READ; break;
|
||||
case Mode::Write:
|
||||
case Mode::ReadWrite: protect = PROT_READ | PROT_WRITE; break; // NOLINT
|
||||
case Mode::Execute: protect = PROT_EXEC; break;
|
||||
case Mode::ExecuteRead: protect = PROT_EXEC | PROT_READ; break; // NOLINT
|
||||
case Mode::ExecuteWrite:
|
||||
case Mode::ExecuteReadWrite:
|
||||
protect = PROT_EXEC | PROT_WRITE | PROT_READ;
|
||||
break; // NOLINT
|
||||
case VirtualMemory::Mode::NoAccess:
|
||||
case Mode::NoAccess:
|
||||
default: protect = PROT_NONE; break;
|
||||
}
|
||||
return protect;
|
||||
@@ -130,7 +129,7 @@ static void* map_anonymous(uintptr_t addr, size_t size, int protect, int flags)
|
||||
return mmap(nullptr, size, protect, flags, -1, 0); // NOLINT
|
||||
}
|
||||
|
||||
uint64_t SysVirtualAlloc(uint64_t address, uint64_t size, VirtualMemory::Mode mode) {
|
||||
uint64_t Alloc(uint64_t address, uint64_t size, Mode mode) {
|
||||
EXIT_IF(g_allocs == nullptr);
|
||||
|
||||
auto addr = static_cast<uintptr_t>(address);
|
||||
@@ -154,8 +153,7 @@ static uintptr_t align_up(uintptr_t addr, uint64_t alignment) {
|
||||
return (addr + alignment - 1) & ~(alignment - 1);
|
||||
}
|
||||
|
||||
uint64_t SysVirtualAllocAligned(uint64_t address, uint64_t size, VirtualMemory::Mode mode,
|
||||
uint64_t alignment) {
|
||||
uint64_t AllocAligned(uint64_t address, uint64_t size, Mode mode, uint64_t alignment) {
|
||||
if (alignment == 0) {
|
||||
return 0;
|
||||
}
|
||||
@@ -224,7 +222,7 @@ uint64_t SysVirtualAllocAligned(uint64_t address, uint64_t size, VirtualMemory::
|
||||
}
|
||||
|
||||
if (ptr == MAP_FAILED) {
|
||||
return SysVirtualAllocAligned(address, size, mode, alignment << 1u);
|
||||
return AllocAligned(address, size, mode, alignment << 1u);
|
||||
}
|
||||
|
||||
pthread_mutex_lock(&g_virtual_mutex);
|
||||
@@ -281,7 +279,7 @@ static bool is_mapped(void* ptr, size_t length) {
|
||||
}
|
||||
#endif
|
||||
|
||||
bool SysVirtualAllocFixed(uint64_t address, uint64_t size, VirtualMemory::Mode mode) {
|
||||
bool AllocFixed(uint64_t address, uint64_t size, Mode mode) {
|
||||
EXIT_IF(g_allocs == nullptr);
|
||||
|
||||
auto addr = static_cast<uintptr_t>(address);
|
||||
@@ -318,15 +316,15 @@ bool SysVirtualAllocFixed(uint64_t address, uint64_t size, VirtualMemory::Mode m
|
||||
return false;
|
||||
}
|
||||
|
||||
bool SysVirtualCommit(uint64_t address, uint64_t size, VirtualMemory::Mode mode) {
|
||||
return SysVirtualProtect(address, size, mode);
|
||||
bool Commit(uint64_t address, uint64_t size, Mode mode) {
|
||||
return Protect(address, size, mode);
|
||||
}
|
||||
|
||||
uint64_t SysVirtualReserve(uint64_t address, uint64_t size) {
|
||||
return SysVirtualReserveAligned(address, size, 1);
|
||||
uint64_t Reserve(uint64_t address, uint64_t size) {
|
||||
return ReserveAligned(address, size, 1);
|
||||
}
|
||||
|
||||
uint64_t SysVirtualReserveAligned(uint64_t address, uint64_t size, uint64_t alignment) {
|
||||
uint64_t ReserveAligned(uint64_t address, uint64_t size, uint64_t alignment) {
|
||||
if (alignment == 0) {
|
||||
return 0;
|
||||
}
|
||||
@@ -395,7 +393,7 @@ uint64_t SysVirtualReserveAligned(uint64_t address, uint64_t size, uint64_t alig
|
||||
}
|
||||
|
||||
if (ptr == MAP_FAILED) {
|
||||
return SysVirtualReserveAligned(address, size, alignment << 1u);
|
||||
return ReserveAligned(address, size, alignment << 1u);
|
||||
}
|
||||
|
||||
pthread_mutex_lock(&g_virtual_mutex);
|
||||
@@ -405,7 +403,7 @@ uint64_t SysVirtualReserveAligned(uint64_t address, uint64_t size, uint64_t alig
|
||||
return ret_addr;
|
||||
}
|
||||
|
||||
bool SysVirtualReserveFixed(uint64_t address, uint64_t size) {
|
||||
bool ReserveFixed(uint64_t address, uint64_t size) {
|
||||
EXIT_IF(g_allocs == nullptr);
|
||||
|
||||
auto addr = static_cast<uintptr_t>(address);
|
||||
@@ -441,9 +439,9 @@ bool SysVirtualReserveFixed(uint64_t address, uint64_t size) {
|
||||
return false;
|
||||
}
|
||||
|
||||
bool SysVirtualDecommit(uint64_t address, uint64_t size) {
|
||||
bool Decommit(uint64_t address, uint64_t size) {
|
||||
// Drop physical pages while preserving the reservation.
|
||||
if (!SysVirtualProtect(address, size, VirtualMemory::Mode::NoAccess)) {
|
||||
if (!Protect(address, size, Mode::NoAccess)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
@@ -467,7 +465,7 @@ bool SysVirtualDecommit(uint64_t address, uint64_t size) {
|
||||
return true;
|
||||
}
|
||||
|
||||
bool SysVirtualFree(uint64_t address) {
|
||||
bool Free(uint64_t address) {
|
||||
EXIT_IF(g_allocs == nullptr);
|
||||
size_t size = 0;
|
||||
|
||||
@@ -487,7 +485,7 @@ bool SysVirtualFree(uint64_t address) {
|
||||
return munmap(reinterpret_cast<void*>(addr), size) == 0;
|
||||
}
|
||||
|
||||
bool SysVirtualFreeRange(uint64_t address, uint64_t size) {
|
||||
bool FreeRange(uint64_t address, uint64_t size) {
|
||||
EXIT_IF(g_allocs == nullptr);
|
||||
if (size == 0 || (address & 0xfffu) != 0 || (size & 0xfffu) != 0) {
|
||||
return false;
|
||||
@@ -543,7 +541,7 @@ bool SysVirtualFreeRange(uint64_t address, uint64_t size) {
|
||||
return true;
|
||||
}
|
||||
|
||||
bool SysVirtualProtect(uint64_t address, uint64_t size, VirtualMemory::Mode mode) {
|
||||
bool Protect(uint64_t address, uint64_t size, Mode mode) {
|
||||
const auto addr = static_cast<uintptr_t>(address);
|
||||
const auto page_start = addr >> 12u;
|
||||
const auto page_end = (addr + size - 1) >> 12u;
|
||||
@@ -551,10 +549,10 @@ bool SysVirtualProtect(uint64_t address, uint64_t size, VirtualMemory::Mode mode
|
||||
get_protection_flag(mode)) == 0;
|
||||
}
|
||||
|
||||
bool SysVirtualFlushInstructionCache(uint64_t /*address*/, uint64_t /*size*/) {
|
||||
bool FlushInstructionCache(uint64_t /*address*/, uint64_t /*size*/) {
|
||||
return true;
|
||||
}
|
||||
|
||||
} // namespace Common
|
||||
} // namespace Common::VirtualMemory
|
||||
|
||||
#endif
|
||||
@@ -1,53 +0,0 @@
|
||||
#ifndef KYTY_COMMON_PLATFORM_SYSSWAPBYTEORDER_H_
|
||||
#define KYTY_COMMON_PLATFORM_SYSSWAPBYTEORDER_H_
|
||||
|
||||
#include "common/common.h"
|
||||
|
||||
#include <type_traits>
|
||||
|
||||
inline uint16_t SwapByteOrder16(uint16_t value) {
|
||||
uint16_t hi = value << 8u;
|
||||
uint16_t lo = value >> 8u;
|
||||
return hi | lo;
|
||||
}
|
||||
|
||||
inline uint32_t SwapByteOrder32(uint32_t value) {
|
||||
return __builtin_bswap32(value);
|
||||
}
|
||||
|
||||
inline uint64_t SwapByteOrder64(uint64_t value) {
|
||||
return __builtin_bswap64(value);
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
inline void SwapByteOrder(T& x) {
|
||||
if (sizeof(x) == 2) {
|
||||
if (std::is_signed_v<T>) {
|
||||
x = static_cast<std::make_signed_t<T>>(
|
||||
SwapByteOrder16(static_cast<std::make_unsigned_t<uint16_t>>(x)));
|
||||
} else {
|
||||
x = SwapByteOrder16(x);
|
||||
}
|
||||
}
|
||||
if (sizeof(x) == 4) {
|
||||
if (std::is_signed_v<T>) {
|
||||
x = static_cast<std::make_signed_t<T>>(
|
||||
SwapByteOrder32(static_cast<std::make_unsigned_t<uint32_t>>(x)));
|
||||
} else {
|
||||
x = SwapByteOrder32(x);
|
||||
}
|
||||
}
|
||||
if (sizeof(x) == 8) {
|
||||
if (std::is_signed_v<T>) {
|
||||
x = static_cast<std::make_signed_t<T>>(
|
||||
SwapByteOrder64(static_cast<std::make_unsigned_t<uint64_t>>(x)));
|
||||
} else {
|
||||
x = SwapByteOrder64(x);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
inline void NoSwapByteOrder(T& x) {}
|
||||
|
||||
#endif /* KYTY_COMMON_PLATFORM_SYSSWAPBYTEORDER_H_ */
|
||||
@@ -151,10 +151,6 @@ inline void SysTimeTToSystem(time_t t, SysTimeStruct& s) {
|
||||
SysFileToSystemTimeUtc(ft, s);
|
||||
}
|
||||
|
||||
inline time_t SysTimegm(struct tm* tm) {
|
||||
return timegm(tm);
|
||||
}
|
||||
|
||||
inline void SysSystemToFileTimeUtc(const SysTimeStruct& f, SysFileTimeStruct& t) {
|
||||
struct tm i {};
|
||||
|
||||
@@ -165,7 +161,7 @@ inline void SysSystemToFileTimeUtc(const SysTimeStruct& f, SysFileTimeStruct& t)
|
||||
i.tm_min = f.Minute;
|
||||
i.tm_sec = f.Second;
|
||||
|
||||
t.is_invalid = (f.is_invalid || (t.time = SysTimegm(&i)) == static_cast<time_t>(-1));
|
||||
t.is_invalid = (f.is_invalid || (t.time = ::timegm(&i)) == static_cast<time_t>(-1));
|
||||
}
|
||||
|
||||
// Retrieves the current local date and time.
|
||||
|
||||
@@ -1,27 +0,0 @@
|
||||
#ifndef KYTY_COMMON_PLATFORM_SYSVIRTUAL_H_
|
||||
#define KYTY_COMMON_PLATFORM_SYSVIRTUAL_H_
|
||||
|
||||
#include "common/common.h"
|
||||
#include "common/virtualMemory.h"
|
||||
|
||||
namespace Common {
|
||||
|
||||
void SysVirtualInit();
|
||||
|
||||
uint64_t SysVirtualAlloc(uint64_t address, uint64_t size, VirtualMemory::Mode mode);
|
||||
uint64_t SysVirtualAllocAligned(uint64_t address, uint64_t size, VirtualMemory::Mode mode,
|
||||
uint64_t alignment);
|
||||
bool SysVirtualAllocFixed(uint64_t address, uint64_t size, VirtualMemory::Mode mode);
|
||||
bool SysVirtualCommit(uint64_t address, uint64_t size, VirtualMemory::Mode mode);
|
||||
uint64_t SysVirtualReserve(uint64_t address, uint64_t size);
|
||||
uint64_t SysVirtualReserveAligned(uint64_t address, uint64_t size, uint64_t alignment);
|
||||
bool SysVirtualReserveFixed(uint64_t address, uint64_t size);
|
||||
bool SysVirtualDecommit(uint64_t address, uint64_t size);
|
||||
bool SysVirtualFree(uint64_t address);
|
||||
bool SysVirtualFreeRange(uint64_t address, uint64_t size);
|
||||
bool SysVirtualProtect(uint64_t address, uint64_t size, VirtualMemory::Mode mode);
|
||||
bool SysVirtualFlushInstructionCache(uint64_t address, uint64_t size);
|
||||
|
||||
} // namespace Common
|
||||
|
||||
#endif /* KYTY_COMMON_PLATFORM_SYSVIRTUAL_H_ */
|
||||
@@ -14,6 +14,7 @@
|
||||
#include "common/stringUtils.h"
|
||||
|
||||
#include <cstdlib>
|
||||
#include <cstring>
|
||||
#include <vector>
|
||||
|
||||
// NOLINTNEXTLINE(readability-identifier-naming)
|
||||
@@ -56,10 +57,10 @@ static DWORD GetCacheAccessType(sys_file_cache_type_t t) {
|
||||
}
|
||||
|
||||
if (t == SYS_FILE_CACHE_SEQUENTIAL_SCAN) {
|
||||
return SYS_FILE_CACHE_SEQUENTIAL_SCAN;
|
||||
return FILE_FLAG_SEQUENTIAL_SCAN;
|
||||
}
|
||||
|
||||
return SYS_FILE_CACHE_AUTO;
|
||||
return FILE_ATTRIBUTE_NORMAL;
|
||||
}
|
||||
|
||||
void SysFileRead(void* data, uint32_t size, sys_file_t& f, uint32_t* bytes_read) {
|
||||
@@ -135,10 +136,6 @@ void SysFileWrite(const void* data, uint32_t size, sys_file_t& f, uint32_t* byte
|
||||
}
|
||||
}
|
||||
|
||||
void SysFileWrite(uint32_t n, sys_file_t& f) {
|
||||
SysFileWrite(&n, 4, f);
|
||||
}
|
||||
|
||||
sys_file_t* SysFileCreate(const std::filesystem::path& file_name) {
|
||||
auto* ret = new sys_file_t;
|
||||
|
||||
@@ -479,66 +476,11 @@ bool SysFileSetLastAccessAndWriteTimeUtc(const std::filesystem::path& name,
|
||||
return ok;
|
||||
}
|
||||
|
||||
void SysFileFindFiles(const std::filesystem::path& path, std::vector<sys_file_find_t>& out) {
|
||||
std::string real_path = Common::ReplaceChar(Common::PathToGenericString(path), '\\', '/');
|
||||
if (!Common::EndsWith(real_path, "/")) {
|
||||
real_path += "/";
|
||||
}
|
||||
|
||||
std::string pattern = real_path + "*";
|
||||
|
||||
HANDLE h = nullptr;
|
||||
WIN32_FIND_DATAW data;
|
||||
|
||||
auto wide_pattern = std::filesystem::path(pattern).wstring();
|
||||
h = FindFirstFileW(wide_pattern.c_str(), &data);
|
||||
|
||||
if (h == INVALID_HANDLE_VALUE) {
|
||||
return;
|
||||
}
|
||||
|
||||
do {
|
||||
std::filesystem::path file_name(data.cFileName);
|
||||
auto file_name_str = Common::PathToString(file_name);
|
||||
|
||||
if (file_name_str == "." || file_name_str == "..") {
|
||||
continue;
|
||||
}
|
||||
|
||||
if ((data.dwFileAttributes & static_cast<DWORD>(FILE_ATTRIBUTE_DIRECTORY)) != 0u) {
|
||||
SysFileFindFiles(std::filesystem::path(real_path) / file_name, out);
|
||||
} else {
|
||||
sys_file_find_t r {};
|
||||
|
||||
r.path_with_name = std::filesystem::path(real_path) / file_name;
|
||||
r.size = (static_cast<uint64_t>(data.nFileSizeHigh) << 32u) +
|
||||
static_cast<uint64_t>(data.nFileSizeLow);
|
||||
r.last_access_time.is_invalid = false;
|
||||
r.last_access_time.time = data.ftLastAccessTime;
|
||||
r.last_write_time.is_invalid = false;
|
||||
r.last_write_time.time = data.ftLastWriteTime;
|
||||
|
||||
out.push_back(r);
|
||||
}
|
||||
|
||||
} while (FindNextFileW(h, &data) != 0);
|
||||
|
||||
FindClose(h);
|
||||
}
|
||||
|
||||
void SysFileGetDents(const std::filesystem::path& path, std::vector<sys_dir_entry_t>& out) {
|
||||
std::string real_path = Common::ReplaceChar(Common::PathToGenericString(path), '\\', '/');
|
||||
if (!Common::EndsWith(real_path, "/")) {
|
||||
real_path += "/";
|
||||
}
|
||||
const auto pattern = path / L"*";
|
||||
|
||||
std::string pattern = real_path + "*";
|
||||
|
||||
HANDLE h = nullptr;
|
||||
WIN32_FIND_DATAW data;
|
||||
|
||||
auto wide_pattern = std::filesystem::path(pattern).wstring();
|
||||
h = FindFirstFileW(wide_pattern.c_str(), &data);
|
||||
WIN32_FIND_DATAW data {};
|
||||
HANDLE h = FindFirstFileW(pattern.c_str(), &data);
|
||||
|
||||
if (h == INVALID_HANDLE_VALUE) {
|
||||
return;
|
||||
@@ -552,7 +494,7 @@ void SysFileGetDents(const std::filesystem::path& path, std::vector<sys_dir_entr
|
||||
r.is_file = ((data.dwFileAttributes & static_cast<DWORD>(FILE_ATTRIBUTE_DIRECTORY)) == 0u);
|
||||
r.name = Common::PathToString(file_name);
|
||||
|
||||
out.push_back(r);
|
||||
out.push_back(std::move(r));
|
||||
|
||||
} while (FindNextFileW(h, &data) != 0);
|
||||
|
||||
|
||||
@@ -10,7 +10,6 @@
|
||||
#include <windows.h> // IWYU pragma: keep
|
||||
|
||||
#include "common/assert.h"
|
||||
#include "common/platform/sysVirtual.h"
|
||||
#include "common/virtualMemory.h"
|
||||
|
||||
// IWYU pragma: no_include <basetsd.h>
|
||||
@@ -22,33 +21,33 @@
|
||||
// IWYU pragma: no_include <winerror.h>
|
||||
// IWYU pragma: no_include <wtypes.h>
|
||||
|
||||
namespace Common {
|
||||
namespace Common::VirtualMemory {
|
||||
|
||||
static DWORD GetProtectionFlag(VirtualMemory::Mode mode) {
|
||||
static DWORD GetProtectionFlag(Mode mode) {
|
||||
DWORD protect = PAGE_NOACCESS;
|
||||
switch (mode) {
|
||||
case VirtualMemory::Mode::Read: protect = PAGE_READONLY; break;
|
||||
case Mode::Read: protect = PAGE_READONLY; break;
|
||||
|
||||
case VirtualMemory::Mode::Write:
|
||||
case VirtualMemory::Mode::ReadWrite: protect = PAGE_READWRITE; break;
|
||||
case Mode::Write:
|
||||
case Mode::ReadWrite: protect = PAGE_READWRITE; break;
|
||||
|
||||
case VirtualMemory::Mode::Execute: protect = PAGE_EXECUTE; break;
|
||||
case Mode::Execute: protect = PAGE_EXECUTE; break;
|
||||
|
||||
case VirtualMemory::Mode::ExecuteRead: protect = PAGE_EXECUTE_READ; break;
|
||||
case Mode::ExecuteRead: protect = PAGE_EXECUTE_READ; break;
|
||||
|
||||
case VirtualMemory::Mode::ExecuteWrite:
|
||||
case VirtualMemory::Mode::ExecuteReadWrite: protect = PAGE_EXECUTE_READWRITE; break;
|
||||
case Mode::ExecuteWrite:
|
||||
case Mode::ExecuteReadWrite: protect = PAGE_EXECUTE_READWRITE; break;
|
||||
|
||||
case VirtualMemory::Mode::NoAccess:
|
||||
case Mode::NoAccess:
|
||||
default: protect = PAGE_NOACCESS; break;
|
||||
}
|
||||
return protect;
|
||||
}
|
||||
|
||||
void SysVirtualInit() {}
|
||||
void Init() {}
|
||||
|
||||
uint64_t SysVirtualAlloc(uint64_t address, uint64_t size, VirtualMemory::Mode mode) {
|
||||
auto ptr = (address == 0 ? SysVirtualAllocAligned(address, size, mode, 1)
|
||||
uint64_t Alloc(uint64_t address, uint64_t size, Mode mode) {
|
||||
auto ptr = (address == 0 ? AllocAligned(address, size, mode, 1)
|
||||
: reinterpret_cast<uintptr_t>(VirtualAlloc(
|
||||
reinterpret_cast<LPVOID>(static_cast<uintptr_t>(address)), size,
|
||||
static_cast<DWORD>(MEM_COMMIT) | static_cast<DWORD>(MEM_RESERVE),
|
||||
@@ -59,7 +58,7 @@ uint64_t SysVirtualAlloc(uint64_t address, uint64_t size, VirtualMemory::Mode mo
|
||||
if (err != ERROR_INVALID_ADDRESS) {
|
||||
printf("VirtualAlloc() failed: 0x%08" PRIx32 "\n", err);
|
||||
} else {
|
||||
return SysVirtualAllocAligned(address, size, mode, 1);
|
||||
return AllocAligned(address, size, mode, 1);
|
||||
}
|
||||
}
|
||||
return ptr;
|
||||
@@ -80,8 +79,7 @@ static uint64_t AlignUp(uint64_t addr, uint64_t alignment) {
|
||||
return (addr + alignment - 1) & ~(alignment - 1);
|
||||
}
|
||||
|
||||
uint64_t SysVirtualAllocAligned(uint64_t address, uint64_t size, VirtualMemory::Mode mode,
|
||||
uint64_t alignment) {
|
||||
uint64_t AllocAligned(uint64_t address, uint64_t size, Mode mode, uint64_t alignment) {
|
||||
if (alignment == 0) {
|
||||
printf("VirtualAlloc2 failed: 0x%08" PRIx32 "\n", static_cast<uint32_t>(GetLastError()));
|
||||
return 0;
|
||||
@@ -135,13 +133,13 @@ uint64_t SysVirtualAllocAligned(uint64_t address, uint64_t size, VirtualMemory::
|
||||
printf("VirtualAlloc2(alignment = 0x%016" PRIx64 ") failed: 0x%08" PRIx32 "\n",
|
||||
alignment, err);
|
||||
} else {
|
||||
return SysVirtualAllocAligned(address, size, mode, alignment << 1u);
|
||||
return AllocAligned(address, size, mode, alignment << 1u);
|
||||
}
|
||||
}
|
||||
return ptr;
|
||||
}
|
||||
|
||||
bool SysVirtualAllocFixed(uint64_t address, uint64_t size, VirtualMemory::Mode mode) {
|
||||
bool AllocFixed(uint64_t address, uint64_t size, Mode mode) {
|
||||
auto ptr = reinterpret_cast<uintptr_t>(VirtualAlloc(
|
||||
reinterpret_cast<LPVOID>(static_cast<uintptr_t>(address)), size,
|
||||
static_cast<DWORD>(MEM_COMMIT) | static_cast<DWORD>(MEM_RESERVE), GetProtectionFlag(mode)));
|
||||
@@ -161,7 +159,7 @@ bool SysVirtualAllocFixed(uint64_t address, uint64_t size, VirtualMemory::Mode m
|
||||
return true;
|
||||
}
|
||||
|
||||
bool SysVirtualCommit(uint64_t address, uint64_t size, VirtualMemory::Mode mode) {
|
||||
bool Commit(uint64_t address, uint64_t size, Mode mode) {
|
||||
auto ptr = reinterpret_cast<uintptr_t>(
|
||||
VirtualAlloc(reinterpret_cast<LPVOID>(static_cast<uintptr_t>(address)), size,
|
||||
static_cast<DWORD>(MEM_COMMIT), GetProtectionFlag(mode)));
|
||||
@@ -179,8 +177,8 @@ bool SysVirtualCommit(uint64_t address, uint64_t size, VirtualMemory::Mode mode)
|
||||
return true;
|
||||
}
|
||||
|
||||
uint64_t SysVirtualReserve(uint64_t address, uint64_t size) {
|
||||
auto ptr = (address == 0 ? SysVirtualReserveAligned(address, size, 1)
|
||||
uint64_t Reserve(uint64_t address, uint64_t size) {
|
||||
auto ptr = (address == 0 ? ReserveAligned(address, size, 1)
|
||||
: reinterpret_cast<uintptr_t>(VirtualAlloc(
|
||||
reinterpret_cast<LPVOID>(static_cast<uintptr_t>(address)), size,
|
||||
static_cast<DWORD>(MEM_RESERVE), PAGE_NOACCESS)));
|
||||
@@ -190,13 +188,13 @@ uint64_t SysVirtualReserve(uint64_t address, uint64_t size) {
|
||||
if (err != ERROR_INVALID_ADDRESS) {
|
||||
printf("VirtualAlloc(MEM_RESERVE) failed: 0x%08" PRIx32 "\n", err);
|
||||
} else {
|
||||
return SysVirtualReserveAligned(address, size, 1);
|
||||
return ReserveAligned(address, size, 1);
|
||||
}
|
||||
}
|
||||
return ptr;
|
||||
}
|
||||
|
||||
uint64_t SysVirtualReserveAligned(uint64_t address, uint64_t size, uint64_t alignment) {
|
||||
uint64_t ReserveAligned(uint64_t address, uint64_t size, uint64_t alignment) {
|
||||
if (alignment == 0) {
|
||||
printf("VirtualAlloc2(MEM_RESERVE) failed: 0x%08" PRIx32 "\n",
|
||||
static_cast<uint32_t>(GetLastError()));
|
||||
@@ -250,13 +248,13 @@ uint64_t SysVirtualReserveAligned(uint64_t address, uint64_t size, uint64_t alig
|
||||
"\n",
|
||||
alignment, err);
|
||||
} else {
|
||||
return SysVirtualReserveAligned(address, size, alignment << 1u);
|
||||
return ReserveAligned(address, size, alignment << 1u);
|
||||
}
|
||||
}
|
||||
return ptr;
|
||||
}
|
||||
|
||||
bool SysVirtualReserveFixed(uint64_t address, uint64_t size) {
|
||||
bool ReserveFixed(uint64_t address, uint64_t size) {
|
||||
auto ptr = reinterpret_cast<uintptr_t>(
|
||||
VirtualAlloc(reinterpret_cast<LPVOID>(static_cast<uintptr_t>(address)), size,
|
||||
static_cast<DWORD>(MEM_RESERVE), PAGE_NOACCESS));
|
||||
@@ -276,7 +274,7 @@ bool SysVirtualReserveFixed(uint64_t address, uint64_t size) {
|
||||
return true;
|
||||
}
|
||||
|
||||
bool SysVirtualDecommit(uint64_t address, uint64_t size) {
|
||||
bool Decommit(uint64_t address, uint64_t size) {
|
||||
if (VirtualFree(reinterpret_cast<LPVOID>(static_cast<uintptr_t>(address)), size,
|
||||
MEM_DECOMMIT) == 0) {
|
||||
printf("VirtualFree(MEM_DECOMMIT) failed: 0x%08" PRIx32 "\n",
|
||||
@@ -286,7 +284,7 @@ bool SysVirtualDecommit(uint64_t address, uint64_t size) {
|
||||
return true;
|
||||
}
|
||||
|
||||
bool SysVirtualFree(uint64_t address) {
|
||||
bool Free(uint64_t address) {
|
||||
if (VirtualFree(reinterpret_cast<LPVOID>(static_cast<uintptr_t>(address)), 0, MEM_RELEASE) ==
|
||||
0) {
|
||||
printf("VirtualFree() failed: 0x%08" PRIx32 "\n", static_cast<uint32_t>(GetLastError()));
|
||||
@@ -295,7 +293,7 @@ bool SysVirtualFree(uint64_t address) {
|
||||
return true;
|
||||
}
|
||||
|
||||
bool SysVirtualFreeRange(uint64_t address, uint64_t size) {
|
||||
bool FreeRange(uint64_t address, uint64_t size) {
|
||||
if (address == 0 || size == 0) {
|
||||
return false;
|
||||
}
|
||||
@@ -313,10 +311,10 @@ bool SysVirtualFreeRange(uint64_t address, uint64_t size) {
|
||||
}
|
||||
current = next;
|
||||
}
|
||||
return current - address == size && SysVirtualFree(address);
|
||||
return current - address == size && Free(address);
|
||||
}
|
||||
|
||||
bool SysVirtualProtect(uint64_t address, uint64_t size, VirtualMemory::Mode mode) {
|
||||
bool Protect(uint64_t address, uint64_t size, Mode mode) {
|
||||
DWORD old_protect = 0;
|
||||
if (VirtualProtect(reinterpret_cast<LPVOID>(static_cast<uintptr_t>(address)), size,
|
||||
GetProtectionFlag(mode), &old_protect) == 0) {
|
||||
@@ -326,7 +324,7 @@ bool SysVirtualProtect(uint64_t address, uint64_t size, VirtualMemory::Mode mode
|
||||
return true;
|
||||
}
|
||||
|
||||
bool SysVirtualFlushInstructionCache(uint64_t address, uint64_t size) {
|
||||
bool FlushInstructionCache(uint64_t address, uint64_t size) {
|
||||
if (::FlushInstructionCache(GetCurrentProcess(),
|
||||
reinterpret_cast<LPVOID>(static_cast<uintptr_t>(address)),
|
||||
size) == 0) {
|
||||
@@ -337,6 +335,6 @@ bool SysVirtualFlushInstructionCache(uint64_t address, uint64_t size) {
|
||||
return true;
|
||||
}
|
||||
|
||||
} // namespace Common
|
||||
} // namespace Common::VirtualMemory
|
||||
|
||||
#endif
|
||||
+8
-178
@@ -1,7 +1,6 @@
|
||||
#ifndef KYTY_COMMON_STRING_UTILS_H_
|
||||
#define KYTY_COMMON_STRING_UTILS_H_
|
||||
|
||||
#include "common/byteBuffer.h"
|
||||
#include "common/common.h"
|
||||
|
||||
#include <algorithm>
|
||||
@@ -9,7 +8,6 @@
|
||||
#include <charconv>
|
||||
#include <codecvt>
|
||||
#include <filesystem>
|
||||
#include <fmt/format.h>
|
||||
#include <locale>
|
||||
#include <string>
|
||||
#include <string_view>
|
||||
@@ -18,8 +16,6 @@
|
||||
|
||||
namespace Common {
|
||||
|
||||
constexpr uint32_t FIND_INVALID_INDEX = static_cast<uint32_t>(-1);
|
||||
|
||||
inline std::string PathToString(const std::filesystem::path& path) {
|
||||
#if defined(__cpp_char8_t)
|
||||
auto u8 = path.u8string();
|
||||
@@ -38,8 +34,12 @@ inline std::string PathToGenericString(const std::filesystem::path& path) {
|
||||
#endif
|
||||
}
|
||||
|
||||
inline bool IsSpace(char c) {
|
||||
return std::isspace(static_cast<unsigned char>(c)) != 0;
|
||||
inline std::filesystem::path PathFromUtf8(std::string_view text) {
|
||||
#if defined(__cpp_char8_t)
|
||||
return std::filesystem::path(std::u8string(text.begin(), text.end()));
|
||||
#else
|
||||
return std::filesystem::u8path(text.begin(), text.end());
|
||||
#endif
|
||||
}
|
||||
|
||||
inline bool EqualNoCase(std::string_view lhs, std::string_view rhs) {
|
||||
@@ -63,65 +63,6 @@ inline std::string ToLower(std::string_view text) {
|
||||
return ret;
|
||||
}
|
||||
|
||||
inline bool ContainsStr(std::string_view text, std::string_view needle) {
|
||||
return text.find(needle) != std::string_view::npos;
|
||||
}
|
||||
|
||||
inline bool StartsWith(std::string_view text, std::string_view prefix) {
|
||||
return text.starts_with(prefix);
|
||||
}
|
||||
|
||||
inline bool StartsWith(std::string_view text, char prefix) {
|
||||
return !text.empty() && text.front() == prefix;
|
||||
}
|
||||
|
||||
inline bool EndsWith(std::string_view text, std::string_view suffix) {
|
||||
return text.ends_with(suffix);
|
||||
}
|
||||
|
||||
inline bool EndsWith(std::string_view text, char suffix) {
|
||||
return !text.empty() && text.back() == suffix;
|
||||
}
|
||||
|
||||
inline bool ContainsChar(std::string_view text, char needle) {
|
||||
return text.find(needle) != std::string_view::npos;
|
||||
}
|
||||
|
||||
inline bool IndexValid(std::string_view text, uint32_t index) {
|
||||
return index < text.size();
|
||||
}
|
||||
|
||||
inline uint32_t FindIndex(std::string_view text, std::string_view needle, size_t from = 0) {
|
||||
const auto pos = text.find(needle, from);
|
||||
return pos == std::string_view::npos ? FIND_INVALID_INDEX : static_cast<uint32_t>(pos);
|
||||
}
|
||||
|
||||
inline uint32_t FindIndex(std::string_view text, char needle, size_t from = 0) {
|
||||
const auto pos = text.find(needle, from);
|
||||
return pos == std::string_view::npos ? FIND_INVALID_INDEX : static_cast<uint32_t>(pos);
|
||||
}
|
||||
|
||||
inline uint32_t FindLastIndex(std::string_view text, std::string_view needle,
|
||||
size_t from = FIND_INVALID_INDEX) {
|
||||
if (needle.empty()) {
|
||||
return 0;
|
||||
}
|
||||
if (from == FIND_INVALID_INDEX || from > text.size()) {
|
||||
from = text.size();
|
||||
}
|
||||
const auto pos = text.rfind(needle, from);
|
||||
return pos == std::string_view::npos ? FIND_INVALID_INDEX : static_cast<uint32_t>(pos);
|
||||
}
|
||||
|
||||
inline uint32_t FindLastIndex(std::string_view text, char needle,
|
||||
size_t from = FIND_INVALID_INDEX) {
|
||||
if (from == FIND_INVALID_INDEX || from > text.size()) {
|
||||
from = text.size();
|
||||
}
|
||||
const auto pos = text.rfind(needle, from);
|
||||
return pos == std::string_view::npos ? FIND_INVALID_INDEX : static_cast<uint32_t>(pos);
|
||||
}
|
||||
|
||||
inline std::string Mid(std::string_view text, size_t first, size_t count = std::string_view::npos) {
|
||||
if (first >= text.size()) {
|
||||
return {};
|
||||
@@ -129,49 +70,18 @@ inline std::string Mid(std::string_view text, size_t first, size_t count = std::
|
||||
return std::string(text.substr(first, count));
|
||||
}
|
||||
|
||||
inline std::string Left(std::string_view text, size_t count) {
|
||||
return Mid(text, 0, count);
|
||||
}
|
||||
|
||||
inline std::string Right(std::string_view text, size_t count) {
|
||||
if (count >= text.size()) {
|
||||
return std::string(text);
|
||||
}
|
||||
return Mid(text, text.size() - count, count);
|
||||
}
|
||||
|
||||
inline std::string RemoveLast(std::string_view text, size_t count) {
|
||||
if (count >= text.size()) {
|
||||
return {};
|
||||
}
|
||||
return Left(text, text.size() - count);
|
||||
return std::string(text.substr(0, text.size() - count));
|
||||
}
|
||||
|
||||
inline std::string RemoveFirst(std::string_view text, size_t count) {
|
||||
if (count >= text.size()) {
|
||||
return {};
|
||||
}
|
||||
return Right(text, text.size() - count);
|
||||
}
|
||||
|
||||
inline std::string TrimRight(std::string_view text) {
|
||||
size_t end = text.size();
|
||||
while (end > 0 && IsSpace(text[end - 1])) {
|
||||
end--;
|
||||
}
|
||||
return std::string(text.substr(0, end));
|
||||
}
|
||||
|
||||
inline std::string TrimLeft(std::string_view text) {
|
||||
size_t first = 0;
|
||||
while (first < text.size() && IsSpace(text[first])) {
|
||||
first++;
|
||||
}
|
||||
return std::string(text.substr(first));
|
||||
}
|
||||
|
||||
inline std::string Trim(std::string_view text) {
|
||||
return TrimRight(TrimLeft(text));
|
||||
return std::string(text.substr(count));
|
||||
}
|
||||
|
||||
inline std::string ReplaceChar(std::string_view text, char old_char, char new_char) {
|
||||
@@ -204,16 +114,6 @@ inline std::string FilenameWithoutDirectory(std::string_view text) {
|
||||
return pos == std::string_view::npos ? std::string(text) : std::string(text.substr(pos + 1));
|
||||
}
|
||||
|
||||
inline std::string FilenameWithoutExtension(std::string_view text) {
|
||||
const auto pos = text.find_last_of('.');
|
||||
return pos == std::string_view::npos ? std::string(text) : std::string(text.substr(0, pos));
|
||||
}
|
||||
|
||||
inline std::string ExtensionWithoutFilename(std::string_view text) {
|
||||
const auto pos = text.find_last_of('.');
|
||||
return pos == std::string_view::npos ? std::string() : std::string(text.substr(pos));
|
||||
}
|
||||
|
||||
inline std::string FixFilenameSlash(std::string_view text) {
|
||||
return ReplaceChar(text, '\\', '/');
|
||||
}
|
||||
@@ -257,65 +157,12 @@ inline std::vector<std::string> Split(std::string_view text, char sep, bool keep
|
||||
return Split(text, std::string_view(&sep, 1), keep_empty);
|
||||
}
|
||||
|
||||
inline std::string Concat(const std::vector<std::string>& list, std::string_view sep) {
|
||||
std::string ret;
|
||||
for (size_t i = 0; i < list.size(); i++) {
|
||||
if (i != 0) {
|
||||
ret += sep;
|
||||
}
|
||||
ret += list[i];
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
|
||||
inline std::string Concat(const std::vector<std::string>& list, char sep) {
|
||||
return Concat(list, std::string_view(&sep, 1));
|
||||
}
|
||||
|
||||
inline uint32_t ToUint32(std::string_view text, int base = 10) {
|
||||
uint32_t value = 0;
|
||||
std::from_chars(text.data(), text.data() + text.size(), value, base);
|
||||
return value;
|
||||
}
|
||||
|
||||
inline uint64_t ToUint64(std::string_view text, int base = 10) {
|
||||
uint64_t value = 0;
|
||||
std::from_chars(text.data(), text.data() + text.size(), value, base);
|
||||
return value;
|
||||
}
|
||||
|
||||
inline int32_t ToInt32(std::string_view text, int base = 10) {
|
||||
int32_t value = 0;
|
||||
std::from_chars(text.data(), text.data() + text.size(), value, base);
|
||||
return value;
|
||||
}
|
||||
|
||||
inline int64_t ToInt64(std::string_view text, int base = 10) {
|
||||
int64_t value = 0;
|
||||
std::from_chars(text.data(), text.data() + text.size(), value, base);
|
||||
return value;
|
||||
}
|
||||
|
||||
inline double ToDouble(std::string_view text) {
|
||||
return std::stod(std::string(text));
|
||||
}
|
||||
|
||||
inline float ToFloat(std::string_view text) {
|
||||
return std::stof(std::string(text));
|
||||
}
|
||||
|
||||
inline std::string SafeCsv(std::string_view text) {
|
||||
const bool add_space =
|
||||
!text.empty() && (text.front() == '+' || text.front() == '=' || text.front() == '-');
|
||||
if (text.find('"') != std::string_view::npos || text.find(';') != std::string_view::npos ||
|
||||
text.find('+') != std::string_view::npos || text.find('=') != std::string_view::npos ||
|
||||
text.find('-') != std::string_view::npos) {
|
||||
return "\"" + std::string(add_space ? " " : "") +
|
||||
ReplaceStr(std::string(text), "\"", "\"\"") + "\"";
|
||||
}
|
||||
return std::string(text);
|
||||
}
|
||||
|
||||
inline std::string Utf16ToUtf8(std::u16string_view utf16) {
|
||||
if (utf16.empty()) {
|
||||
return {};
|
||||
@@ -324,23 +171,6 @@ inline std::string Utf16ToUtf8(std::u16string_view utf16) {
|
||||
return convert.to_bytes(utf16.data(), utf16.data() + utf16.size());
|
||||
}
|
||||
|
||||
inline ByteBuffer HexToBin(std::string_view text) {
|
||||
ByteBuffer ret;
|
||||
for (size_t i = 0; i + 1 < text.size(); i += 2) {
|
||||
ret.Add(static_cast<Byte>((ToUint32(text.substr(i, 2), 16) & 0xffu)));
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
|
||||
inline std::string HexFromBin(const ByteBuffer& bin) {
|
||||
std::string ret;
|
||||
ret.reserve(bin.Size() * 2);
|
||||
for (uint32_t i = 0; i < bin.Size(); i++) {
|
||||
ret += fmt::format("{:02X}", std::to_integer<uint8_t>(bin.At(i)));
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
|
||||
} // namespace Common
|
||||
|
||||
#endif /* KYTY_COMMON_STRING_UTILS_H_ */
|
||||
+8
-50
@@ -8,7 +8,6 @@
|
||||
#include <chrono> // IWYU pragma: keep
|
||||
#include <condition_variable> // IWYU pragma: keep
|
||||
#include <mutex>
|
||||
#include <vector>
|
||||
|
||||
#if KYTY_PLATFORM == KYTY_PLATFORM_WINDOWS && KYTY_COMPILER == KYTY_COMPILER_CLANG
|
||||
#define KYTY_WIN_CS
|
||||
@@ -197,37 +196,7 @@ struct CondVarPrivate {
|
||||
#endif
|
||||
};
|
||||
|
||||
static std::recursive_mutex g_cond_waiters_mutex;
|
||||
static std::vector<std::pair<int, CondVarPrivate*>> g_cond_waiters;
|
||||
static wait_poll_func_t g_cond_wait_poll_callback = nullptr;
|
||||
|
||||
static void WakeCondVar(CondVarPrivate* cond_var) {
|
||||
#ifdef KYTY_WIN_CS
|
||||
static auto func = ResolveWakeAllConditionVariable();
|
||||
EXIT_NOT_IMPLEMENTED(func == nullptr);
|
||||
func(&cond_var->m_cv);
|
||||
#else
|
||||
cond_var->m_cv.notify_all();
|
||||
#endif
|
||||
}
|
||||
|
||||
static void RegisterCondWaiter(CondVarPrivate* cond_var) {
|
||||
std::lock_guard lock(g_cond_waiters_mutex);
|
||||
g_cond_waiters.emplace_back(Thread::GetThreadIdUnique(), cond_var);
|
||||
}
|
||||
|
||||
static void UnregisterCondWaiter(CondVarPrivate* cond_var) {
|
||||
const auto thread_id = Thread::GetThreadIdUnique();
|
||||
std::lock_guard lock(g_cond_waiters_mutex);
|
||||
|
||||
const auto it = std::find_if(g_cond_waiters.begin(), g_cond_waiters.end(),
|
||||
[thread_id, cond_var](const auto& waiter) {
|
||||
return waiter.first == thread_id && waiter.second == cond_var;
|
||||
});
|
||||
if (it != g_cond_waiters.end()) {
|
||||
g_cond_waiters.erase(it);
|
||||
}
|
||||
}
|
||||
static wait_poll_func_t g_cond_wait_poll_callback = nullptr;
|
||||
|
||||
struct ThreadPrivate {
|
||||
ThreadPrivate(thread_func_t f, void* a): func(f), arg(a), m_thread(&Run, this) {}
|
||||
@@ -284,10 +253,6 @@ void Thread::Detach() {
|
||||
m_thread->m_thread.detach();
|
||||
}
|
||||
|
||||
void Thread::Sleep(uint32_t millis) {
|
||||
std::this_thread::sleep_for(std::chrono::milliseconds(millis));
|
||||
}
|
||||
|
||||
void Thread::SleepMicro(uint32_t micros) {
|
||||
#ifdef KYTY_WIN_CS
|
||||
SleepHighResolution100ns(static_cast<uint64_t>(micros) * 10);
|
||||
@@ -365,7 +330,6 @@ CondVar::~CondVar() {
|
||||
}
|
||||
|
||||
void CondVar::Wait(Mutex* mutex) {
|
||||
RegisterCondWaiter(m_cond_var.get());
|
||||
#ifndef KYTY_WIN_CS
|
||||
std::unique_lock<std::recursive_mutex> cpp_lock(mutex->m_mutex->m_mutex, std::adopt_lock_t());
|
||||
#endif
|
||||
@@ -406,7 +370,6 @@ void CondVar::Wait(Mutex* mutex) {
|
||||
}
|
||||
cpp_lock.release();
|
||||
#endif
|
||||
UnregisterCondWaiter(m_cond_var.get());
|
||||
}
|
||||
|
||||
void CondVar::SetWaitPollCallback(wait_poll_func_t callback) {
|
||||
@@ -415,7 +378,6 @@ void CondVar::SetWaitPollCallback(wait_poll_func_t callback) {
|
||||
|
||||
bool CondVar::WaitFor(Mutex* mutex, uint32_t micros) {
|
||||
bool ok = false;
|
||||
RegisterCondWaiter(m_cond_var.get());
|
||||
#ifndef KYTY_WIN_CS
|
||||
std::unique_lock<std::recursive_mutex> cpp_lock(mutex->m_mutex->m_mutex, std::adopt_lock_t());
|
||||
#endif
|
||||
@@ -430,7 +392,6 @@ bool CondVar::WaitFor(Mutex* mutex, uint32_t micros) {
|
||||
std::cv_status::no_timeout);
|
||||
cpp_lock.release();
|
||||
#endif
|
||||
UnregisterCondWaiter(m_cond_var.get());
|
||||
return ok;
|
||||
}
|
||||
|
||||
@@ -445,16 +406,13 @@ void CondVar::Signal() {
|
||||
}
|
||||
|
||||
void CondVar::SignalAll() {
|
||||
WakeCondVar(m_cond_var.get());
|
||||
}
|
||||
|
||||
void CondVar::SignalThread(int thread_id) {
|
||||
std::lock_guard lock(g_cond_waiters_mutex);
|
||||
for (const auto& waiter: g_cond_waiters) {
|
||||
if (waiter.first == thread_id) {
|
||||
WakeCondVar(waiter.second);
|
||||
}
|
||||
}
|
||||
#ifdef KYTY_WIN_CS
|
||||
static auto func = ResolveWakeAllConditionVariable();
|
||||
EXIT_NOT_IMPLEMENTED(func == nullptr);
|
||||
func(&m_cond_var->m_cv);
|
||||
#else
|
||||
m_cond_var->m_cv.notify_all();
|
||||
#endif
|
||||
}
|
||||
|
||||
int Thread::GetThreadIdUnique() {
|
||||
|
||||
@@ -31,7 +31,6 @@ public:
|
||||
// The id is unique and can't be reused by another thread.
|
||||
[[nodiscard]] int GetUniqueId() const;
|
||||
|
||||
static void Sleep(uint32_t millis);
|
||||
static void SleepMicro(uint32_t micros);
|
||||
static void SleepNano(uint64_t nanos);
|
||||
static bool IsMainThread();
|
||||
@@ -77,7 +76,6 @@ public:
|
||||
void Signal();
|
||||
void SignalAll();
|
||||
|
||||
static void SignalThread(int thread_id);
|
||||
static void SetWaitPollCallback(wait_poll_func_t callback);
|
||||
|
||||
KYTY_CLASS_NO_COPY(CondVar);
|
||||
|
||||
@@ -62,23 +62,6 @@ double Timer::GetTimeS() const {
|
||||
return (static_cast<double>(current_time - m_StartTime)) / static_cast<double>(m_Frequency);
|
||||
}
|
||||
|
||||
// return time in ticks
|
||||
uint64_t Timer::GetTicks() const {
|
||||
if (m_is_paused) {
|
||||
return (m_PauseTime - m_StartTime);
|
||||
}
|
||||
|
||||
uint64_t current_time = 0;
|
||||
SysQueryPerformanceCounter(¤t_time);
|
||||
|
||||
return (current_time - m_StartTime);
|
||||
}
|
||||
|
||||
// return ticks frequency
|
||||
uint64_t Timer::GetFrequency() const {
|
||||
return m_Frequency;
|
||||
}
|
||||
|
||||
uint64_t Timer::QueryPerformanceFrequency() {
|
||||
uint64_t ret = 0;
|
||||
SysQueryPerformanceFrequency(&ret);
|
||||
|
||||
@@ -24,12 +24,6 @@ public:
|
||||
// return time in seconds
|
||||
[[nodiscard]] double GetTimeS() const;
|
||||
|
||||
// return time in ticks
|
||||
[[nodiscard]] uint64_t GetTicks() const;
|
||||
|
||||
// return ticks frequency
|
||||
[[nodiscard]] uint64_t GetFrequency() const;
|
||||
|
||||
KYTY_CLASS_NO_COPY(Timer);
|
||||
|
||||
[[nodiscard]] static uint64_t QueryPerformanceFrequency();
|
||||
|
||||
@@ -1,63 +0,0 @@
|
||||
#include "common/virtualMemory.h"
|
||||
|
||||
#include "common/platform/sysVirtual.h"
|
||||
|
||||
namespace Common {
|
||||
|
||||
namespace VirtualMemory {
|
||||
|
||||
void Init() {
|
||||
SysVirtualInit();
|
||||
}
|
||||
|
||||
uint64_t Alloc(uint64_t address, uint64_t size, Mode mode) {
|
||||
return SysVirtualAlloc(address, size, mode);
|
||||
}
|
||||
|
||||
uint64_t AllocAligned(uint64_t address, uint64_t size, Mode mode, uint64_t alignment) {
|
||||
return SysVirtualAllocAligned(address, size, mode, alignment);
|
||||
}
|
||||
|
||||
bool AllocFixed(uint64_t address, uint64_t size, Mode mode) {
|
||||
return SysVirtualAllocFixed(address, size, mode);
|
||||
}
|
||||
|
||||
bool Commit(uint64_t address, uint64_t size, Mode mode) {
|
||||
return SysVirtualCommit(address, size, mode);
|
||||
}
|
||||
|
||||
uint64_t Reserve(uint64_t address, uint64_t size) {
|
||||
return SysVirtualReserve(address, size);
|
||||
}
|
||||
|
||||
uint64_t ReserveAligned(uint64_t address, uint64_t size, uint64_t alignment) {
|
||||
return SysVirtualReserveAligned(address, size, alignment);
|
||||
}
|
||||
|
||||
bool ReserveFixed(uint64_t address, uint64_t size) {
|
||||
return SysVirtualReserveFixed(address, size);
|
||||
}
|
||||
|
||||
bool Decommit(uint64_t address, uint64_t size) {
|
||||
return SysVirtualDecommit(address, size);
|
||||
}
|
||||
|
||||
bool Free(uint64_t address) {
|
||||
return SysVirtualFree(address);
|
||||
}
|
||||
|
||||
bool FreeRange(uint64_t address, uint64_t size) {
|
||||
return SysVirtualFreeRange(address, size);
|
||||
}
|
||||
|
||||
bool Protect(uint64_t address, uint64_t size, Mode mode) {
|
||||
return SysVirtualProtect(address, size, mode);
|
||||
}
|
||||
|
||||
bool FlushInstructionCache(uint64_t address, uint64_t size) {
|
||||
return SysVirtualFlushInstructionCache(address, size);
|
||||
}
|
||||
|
||||
} // namespace VirtualMemory
|
||||
|
||||
} // namespace Common
|
||||
@@ -196,6 +196,10 @@ void Run(const RunOptions& options) {
|
||||
ClearDebugTextureFolder();
|
||||
|
||||
PrintSystemInfo();
|
||||
std::string title_id;
|
||||
if (Loader::SystemContentParamSfoGetString("TITLE_ID", &title_id) && !title_id.empty()) {
|
||||
Log::WriteToConsoleAndLog(fmt::format("Title ID: {}\n", title_id));
|
||||
}
|
||||
|
||||
int ok = atexit(KytyClose);
|
||||
EXIT_NOT_IMPLEMENTED(ok != 0);
|
||||
|
||||
@@ -32,11 +32,12 @@ private:
|
||||
|
||||
struct BufferCursor {
|
||||
std::span<const uint32_t> commands;
|
||||
uint32_t offset_dw = 0;
|
||||
uint32_t deferred_advance_dw = 0;
|
||||
uint32_t offset_dw = 0;
|
||||
};
|
||||
|
||||
std::vector<BufferCursor> m_buffer_stack;
|
||||
std::span<const uint32_t> m_next_buffer;
|
||||
bool m_chain = false;
|
||||
bool m_suspended = false;
|
||||
bool m_made_progress = false;
|
||||
};
|
||||
@@ -72,6 +73,9 @@ public:
|
||||
void SetIndexBufferSize(uint32_t index_buffer_size);
|
||||
void SetDrawIndirectArgsBaseAddress(uint64_t draw_indirect_args_base_addr);
|
||||
void SetDispatchIndirectArgsBaseAddress(uint64_t dispatch_indirect_args_base_addr);
|
||||
[[nodiscard]] uint64_t GetDispatchIndirectArgsBaseAddress() const {
|
||||
return m_dispatch_indirect_args_base_addr;
|
||||
}
|
||||
void SetNumInstances(uint32_t num_instances);
|
||||
void DrawIndex(DrawIndexArgs args);
|
||||
void DrawIndexOffset(uint32_t index_offset, uint32_t index_count);
|
||||
@@ -98,7 +102,7 @@ public:
|
||||
void TriggerEopEventAtEndOfPipe(uint32_t interrupt_context_id);
|
||||
void DispatchDirect(uint32_t thread_group_x, uint32_t thread_group_y, uint32_t thread_group_z,
|
||||
uint32_t mode);
|
||||
void DispatchIndirect(uint32_t data_offset, uint32_t mode);
|
||||
void DispatchIndirect(uint64_t args_addr, uint32_t mode);
|
||||
void WaitFlipDone(uint32_t video_out_handle, uint32_t display_buffer_index);
|
||||
void TriggerEvent(uint32_t event_type, uint32_t event_index, uint64_t event_address = 0);
|
||||
|
||||
@@ -129,7 +133,7 @@ public:
|
||||
[[nodiscard]] bool ShouldSkipPredicatedPackets() const { return m_predicate_skip; }
|
||||
|
||||
Pm4ProcessResult Process(Pm4Execution& execution, std::span<const uint32_t> commands);
|
||||
void ProcessIndirectBuffer(std::span<const uint32_t> commands);
|
||||
void ProcessIndirectBuffer(std::span<const uint32_t> commands, bool chain);
|
||||
|
||||
void SetFlip(const FlipInfo& flip) { m_flip = flip; }
|
||||
|
||||
@@ -143,11 +147,10 @@ private:
|
||||
uint32_t cache_action, uint32_t event_index, uint32_t event_write_source,
|
||||
void* dst_gpu_addr, T value, uint32_t interrupt_selector,
|
||||
uint32_t interrupt_context_id);
|
||||
void ProcessPm4(Pm4Execution& execution, size_t stop_depth);
|
||||
void ProcessPm4(Pm4Execution& execution);
|
||||
void SuspendPm4();
|
||||
CommandScheduler& GetScheduler() const { return m_renderer.GetCommandScheduler(); }
|
||||
CommandBuffer& CurrentBuffer() { return GetScheduler().Current(); }
|
||||
void CheckBuffer() const { GetScheduler().CheckActive(); }
|
||||
|
||||
RenderContext& m_renderer;
|
||||
HW::Context m_ctx;
|
||||
|
||||
@@ -1331,26 +1331,13 @@ KYTY_CP_OP_PARSER(CpOpDispatchIndirect) {
|
||||
EXIT_NOT_IMPLEMENTED(cmd_id != 0xc0011600 && cmd_id != 0xc0021600);
|
||||
|
||||
if (cmd_id == 0xc0021600) {
|
||||
struct DispatchIndirectArgs {
|
||||
uint32_t thread_group_x;
|
||||
uint32_t thread_group_y;
|
||||
uint32_t thread_group_z;
|
||||
};
|
||||
|
||||
auto* args = reinterpret_cast<const DispatchIndirectArgs*>(
|
||||
buffer[0] | (static_cast<uint64_t>(buffer[1]) << 32u));
|
||||
uint32_t mode = buffer[2];
|
||||
|
||||
EXIT_NOT_IMPLEMENTED(args == nullptr);
|
||||
cp.DispatchDirect(args->thread_group_x, args->thread_group_y, args->thread_group_z, mode);
|
||||
|
||||
cp.DispatchIndirect(buffer[0] | (static_cast<uint64_t>(buffer[1]) << 32u), buffer[2]);
|
||||
return 3;
|
||||
}
|
||||
|
||||
uint32_t data_offset = buffer[0];
|
||||
uint32_t mode = buffer[1];
|
||||
|
||||
cp.DispatchIndirect(data_offset, mode);
|
||||
const auto base_addr = cp.GetDispatchIndirectArgsBaseAddress();
|
||||
EXIT_NOT_IMPLEMENTED(base_addr == 0);
|
||||
cp.DispatchIndirect(base_addr + buffer[0], buffer[1]);
|
||||
|
||||
return 2;
|
||||
}
|
||||
@@ -1495,10 +1482,10 @@ KYTY_CP_OP_PARSER(CpOpBranch) {
|
||||
reinterpret_cast<uint64_t>(else_buffer), else_num_dw);
|
||||
|
||||
if (take_then) {
|
||||
cp.ProcessIndirectBuffer({then_buffer, then_num_dw});
|
||||
cp.ProcessIndirectBuffer({then_buffer, then_num_dw}, true);
|
||||
} else if (mode == 2 && else_num_dw != 0) {
|
||||
EXIT_NOT_IMPLEMENTED(else_buffer == nullptr);
|
||||
cp.ProcessIndirectBuffer({else_buffer, else_num_dw});
|
||||
cp.ProcessIndirectBuffer({else_buffer, else_num_dw}, true);
|
||||
}
|
||||
|
||||
return payload_dw;
|
||||
@@ -1959,7 +1946,7 @@ KYTY_CP_OP_PARSER(CpOpIndirectBuffer) {
|
||||
|
||||
GraphicsDbgDumpDcb("ci", indirect_num_dw, indirect_buffer);
|
||||
|
||||
cp.ProcessIndirectBuffer({indirect_buffer, indirect_num_dw});
|
||||
cp.ProcessIndirectBuffer({indirect_buffer, indirect_num_dw}, (control & (1u << 20u)) != 0);
|
||||
|
||||
return 3;
|
||||
}
|
||||
@@ -2017,6 +2004,14 @@ KYTY_CP_OP_PARSER(CpOpIndirectCxRegs) {
|
||||
auto pfunc = g_hw_ctx_indirect_func[cmd_offset & (Pm4::CX_NUM - 1)];
|
||||
|
||||
if (pfunc == nullptr) {
|
||||
if (raw_cmd_offset == 0x24au && value == 0u) {
|
||||
static std::atomic_flag logged = ATOMIC_FLAG_INIT;
|
||||
if (!logged.test_and_set(std::memory_order_relaxed)) {
|
||||
Log::WriteToConsoleAndLog(
|
||||
"\t diagnostic: ignoring indirect CX {0x24a, 0}; hardware effect unresolved\n");
|
||||
}
|
||||
continue;
|
||||
}
|
||||
EXIT("unknown cx reg at %05" PRIx32 ": 0x%" PRIx32 "\n", num_dw - dw, cmd_offset);
|
||||
}
|
||||
|
||||
@@ -3635,6 +3630,11 @@ void GraphicsInitJmpTablesShIndirect() {
|
||||
g_hw_sh_indirect_func[Pm4::SPI_SHADER_PGM_RSRC4_GS] = [](KYTY_HW_SH_INDIRECT_ARGS) {
|
||||
HwShIgnoreShaderRegister(cmd_offset, value);
|
||||
};
|
||||
// Toolkit state restoration emits the compiler's GS-front allocation
|
||||
// metadata here. Native GS resource registers already carry that allocation.
|
||||
g_hw_sh_indirect_func[0x0ca] = [](KYTY_HW_SH_INDIRECT_ARGS) {
|
||||
HwShIgnoreShaderRegister(cmd_offset, value);
|
||||
};
|
||||
g_hw_sh_indirect_func[Pm4::SPI_GRAPHICS_SHADER_CONTROL_GS] = [](KYTY_HW_SH_INDIRECT_ARGS) {
|
||||
HwShIgnoreShaderRegister(cmd_offset, value);
|
||||
};
|
||||
@@ -3654,12 +3654,13 @@ void GraphicsInitJmpTablesShIndirect() {
|
||||
g_hw_sh_indirect_func[Pm4::SPI_GRAPHICS_SHADER_CONTROL_HS] = [](KYTY_HW_SH_INDIRECT_ARGS) {
|
||||
HwShIgnoreShaderRegister(cmd_offset, value);
|
||||
};
|
||||
g_hw_sh_indirect_func[Pm4::SPI_SHADER_USER_DATA_ADDR_LO_HS] = [](KYTY_HW_SH_INDIRECT_ARGS) {
|
||||
HwShIgnoreShaderRegister(cmd_offset, value);
|
||||
};
|
||||
g_hw_sh_indirect_func[Pm4::SPI_SHADER_USER_DATA_ADDR_HI_HS] = [](KYTY_HW_SH_INDIRECT_ARGS) {
|
||||
HwShIgnoreShaderRegister(cmd_offset, value);
|
||||
};
|
||||
for (uint32_t offset = Pm4::SPI_SHADER_USER_DATA_ADDR_LO_HS;
|
||||
offset <= Pm4::SPI_SHADER_USER_DATA_ADDR_HI_HS; offset++) {
|
||||
g_hw_sh_indirect_func[offset] = [](KYTY_HW_SH_INDIRECT_ARGS) {
|
||||
cp.GetShCtx().SetHsUserDataAddress(cmd_offset - Pm4::SPI_SHADER_USER_DATA_ADDR_LO_HS,
|
||||
value);
|
||||
};
|
||||
}
|
||||
g_hw_sh_indirect_func[Pm4::SPI_SHADER_PGM_LO_HS] = [](KYTY_HW_SH_INDIRECT_ARGS) {
|
||||
auto base = cp.GetShCtx().GetVs().hs_regs.data_addr;
|
||||
base &= 0xFFFFFF00000000FFull;
|
||||
@@ -3818,6 +3819,25 @@ void GraphicsInitJmpTablesUcIndirect() {
|
||||
for (auto& func: g_hw_uc_indirect_func) {
|
||||
func = nullptr;
|
||||
}
|
||||
for (uint32_t i = 0; i < 4; ++i) {
|
||||
g_hw_uc_indirect_func[Pm4::FSR_CONTROL_POINTS_LEFT_X + i] = [](KYTY_HW_UC_INDIRECT_ARGS) {
|
||||
cp.GetUcfg().SetFsrControlPoint(0, cmd_offset - Pm4::FSR_CONTROL_POINTS_LEFT_X, value);
|
||||
};
|
||||
g_hw_uc_indirect_func[Pm4::FSR_CONTROL_POINTS_LEFT_Y + i] = [](KYTY_HW_UC_INDIRECT_ARGS) {
|
||||
cp.GetUcfg().SetFsrControlPoint(1, cmd_offset - Pm4::FSR_CONTROL_POINTS_LEFT_Y, value);
|
||||
};
|
||||
}
|
||||
for (uint32_t i = 0; i < 2; ++i) {
|
||||
g_hw_uc_indirect_func[Pm4::FSR_ALPHA_LEFT_X + i] = [](KYTY_HW_UC_INDIRECT_ARGS) {
|
||||
cp.GetUcfg().SetFsrAlpha(0, cmd_offset - Pm4::FSR_ALPHA_LEFT_X, value);
|
||||
};
|
||||
g_hw_uc_indirect_func[Pm4::FSR_ALPHA_LEFT_Y + i] = [](KYTY_HW_UC_INDIRECT_ARGS) {
|
||||
cp.GetUcfg().SetFsrAlpha(1, cmd_offset - Pm4::FSR_ALPHA_LEFT_Y, value);
|
||||
};
|
||||
g_hw_uc_indirect_func[Pm4::FSR_WINDOW_LEFT + i] = [](KYTY_HW_UC_INDIRECT_ARGS) {
|
||||
cp.GetUcfg().SetFsrWindow(cmd_offset - Pm4::FSR_WINDOW_LEFT, value);
|
||||
};
|
||||
}
|
||||
|
||||
g_hw_uc_indirect_func[Pm4::GE_CNTL] = [](KYTY_HW_UC_INDIRECT_ARGS) {
|
||||
HW::GeControl r;
|
||||
|
||||
@@ -26,6 +26,10 @@ enum class PrimitiveType : uint32_t {
|
||||
kPolygon = 21,
|
||||
};
|
||||
|
||||
constexpr bool IsRectList(PrimitiveType type) {
|
||||
return type == PrimitiveType::kRectList || type == PrimitiveType::kRectListLegacy;
|
||||
}
|
||||
|
||||
enum class IndexType : uint32_t {
|
||||
kIndex16 = 0,
|
||||
kIndex32 = 1,
|
||||
|
||||
@@ -386,8 +386,12 @@ void CommandProcessor::WriteReferenceClock(uint64_t dst_address, uint32_t num_by
|
||||
}
|
||||
const auto value = Sync::ReadReferenceClock();
|
||||
std::memcpy(reinterpret_cast<void*>(dst_address), &value, num_bytes);
|
||||
LOGF("\t copy_data reference clock: dst=0x%016" PRIx64 " value=0x%016" PRIx64 " size=%u\n",
|
||||
dst_address, value, num_bytes);
|
||||
static std::atomic<uint32_t> clock_log_count {0};
|
||||
if (clock_log_count.fetch_add(1) < 64) {
|
||||
LOGF("\t copy_data reference clock: dst=0x%016" PRIx64 " value=0x%016" PRIx64
|
||||
" size=%u\n",
|
||||
dst_address, value, num_bytes);
|
||||
}
|
||||
}
|
||||
|
||||
void CommandProcessor::DmaData(uint8_t engine, uint8_t dst_sel, uint8_t dst_cache_policy,
|
||||
@@ -672,20 +676,16 @@ Pm4ProcessResult CommandProcessor::Process(Pm4Execution& execution,
|
||||
Pm4Execution* previous_execution;
|
||||
} execution_scope(*this, execution);
|
||||
|
||||
ProcessPm4(execution, 0);
|
||||
ProcessPm4(execution);
|
||||
return execution.m_buffer_stack.empty() ? Pm4ProcessResult::Complete
|
||||
: Pm4ProcessResult::Blocked;
|
||||
}
|
||||
|
||||
void CommandProcessor::ProcessIndirectBuffer(std::span<const uint32_t> commands) {
|
||||
void CommandProcessor::ProcessIndirectBuffer(std::span<const uint32_t> commands, bool chain) {
|
||||
EXIT_IF(g_current_execution == nullptr);
|
||||
if (commands.empty()) {
|
||||
return;
|
||||
}
|
||||
auto& execution = *g_current_execution;
|
||||
const auto stop_depth = execution.m_buffer_stack.size();
|
||||
execution.m_buffer_stack.push_back({commands});
|
||||
ProcessPm4(execution, stop_depth);
|
||||
EXIT_IF(!g_current_execution->m_next_buffer.empty());
|
||||
g_current_execution->m_next_buffer = commands;
|
||||
g_current_execution->m_chain = chain;
|
||||
}
|
||||
|
||||
void CommandProcessor::SuspendPm4() {
|
||||
@@ -693,21 +693,13 @@ void CommandProcessor::SuspendPm4() {
|
||||
g_current_execution->m_suspended = true;
|
||||
}
|
||||
|
||||
void CommandProcessor::ProcessPm4(Pm4Execution& execution, size_t stop_depth) {
|
||||
while (execution.m_buffer_stack.size() > stop_depth) {
|
||||
void CommandProcessor::ProcessPm4(Pm4Execution& execution) {
|
||||
while (!execution.m_buffer_stack.empty()) {
|
||||
if (g_gpu_state != nullptr) {
|
||||
g_gpu_state->ProcessCommands();
|
||||
}
|
||||
const auto buffer_index = execution.m_buffer_stack.size() - 1;
|
||||
auto& cursor = execution.m_buffer_stack[buffer_index];
|
||||
auto& cursor = execution.m_buffer_stack.back();
|
||||
EXIT_IF(cursor.offset_dw > cursor.commands.size());
|
||||
if (cursor.deferred_advance_dw != 0) {
|
||||
EXIT_IF(cursor.deferred_advance_dw > cursor.commands.size() - cursor.offset_dw);
|
||||
cursor.offset_dw += cursor.deferred_advance_dw;
|
||||
cursor.deferred_advance_dw = 0;
|
||||
execution.m_made_progress = true;
|
||||
continue;
|
||||
}
|
||||
if (cursor.offset_dw == cursor.commands.size()) {
|
||||
execution.m_buffer_stack.pop_back();
|
||||
continue;
|
||||
@@ -783,14 +775,19 @@ void CommandProcessor::ProcessPm4(Pm4Execution& execution, size_t stop_depth) {
|
||||
handler(*this, packet_header & ~1u, packet + 1, remaining_dw, total_dw) + 1;
|
||||
EXIT_IF(packet_dw > remaining_dw);
|
||||
if (execution.m_suspended) {
|
||||
if (execution.m_buffer_stack.size() > buffer_index + 1) {
|
||||
execution.m_buffer_stack[buffer_index].deferred_advance_dw = packet_dw;
|
||||
}
|
||||
return;
|
||||
}
|
||||
EXIT_IF(execution.m_buffer_stack.size() != buffer_index + 1);
|
||||
execution.m_buffer_stack[buffer_index].offset_dw += packet_dw;
|
||||
cursor.offset_dw += packet_dw;
|
||||
execution.m_made_progress = true;
|
||||
if (!execution.m_next_buffer.empty()) {
|
||||
// Chains and taken branches reuse the fetcher; only calls retain a return cursor.
|
||||
if (execution.m_chain) {
|
||||
cursor = {execution.m_next_buffer};
|
||||
} else {
|
||||
execution.m_buffer_stack.push_back({execution.m_next_buffer});
|
||||
}
|
||||
execution.m_next_buffer = {};
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -825,46 +822,63 @@ void CommandProcessor::SetNumInstances(uint32_t num_instances) {
|
||||
|
||||
void CommandProcessor::SetPredication(uint32_t condition, uint32_t op, uint32_t wait_op,
|
||||
const volatile void* address, uint32_t count_in_dwords) {
|
||||
if (wait_op != 0) {
|
||||
BufferFlushAndWait();
|
||||
}
|
||||
|
||||
(void)count_in_dwords;
|
||||
uint64_t value = 0;
|
||||
|
||||
switch (op) {
|
||||
case 0x00: {
|
||||
case 0x00:
|
||||
m_predicate_skip = false;
|
||||
} break;
|
||||
case 0x03: {
|
||||
return;
|
||||
case 0x01: {
|
||||
EXIT_NOT_IMPLEMENTED(address == nullptr);
|
||||
|
||||
auto value = *reinterpret_cast<const volatile uint64_t*>(address);
|
||||
|
||||
switch (condition) {
|
||||
case 0x00: m_predicate_skip = (value != 0); break;
|
||||
case 0x01: m_predicate_skip = (value == 0); break;
|
||||
default: EXIT("unknown predication condition: 0x%08" PRIx32 "\n", condition);
|
||||
}
|
||||
static std::atomic<uint32_t> log_count {0};
|
||||
if (log_count.fetch_add(1) < 128) {
|
||||
LOGF("\t bool predication: addr=0x%016" PRIx64 ", value=0x%016" PRIx64
|
||||
", condition=%" PRIu32 ", skip=%u, wait_op=%" PRIu32 "\n",
|
||||
reinterpret_cast<uint64_t>(address), value, condition,
|
||||
m_predicate_skip ? 1u : 0u, wait_op);
|
||||
// One begin/end pair per DB; bit 63 marks each counter ready.
|
||||
constexpr uint64_t ready_bit = 1ull << 63u;
|
||||
const auto* results = reinterpret_cast<const volatile uint64_t*>(address);
|
||||
for (uint32_t db = 0; db < 16u; db++) {
|
||||
const auto begin = results[db * 2u];
|
||||
const auto end = results[db * 2u + 1u];
|
||||
if ((begin & end & ready_bit) == 0) {
|
||||
if (wait_op == 0) {
|
||||
SuspendPm4();
|
||||
} else {
|
||||
m_predicate_skip = false;
|
||||
}
|
||||
return;
|
||||
}
|
||||
value += end - begin;
|
||||
}
|
||||
} break;
|
||||
case 0x03:
|
||||
if (wait_op != 0) {
|
||||
BufferFlushAndWait();
|
||||
}
|
||||
EXIT_NOT_IMPLEMENTED(address == nullptr);
|
||||
value = *reinterpret_cast<const volatile uint64_t*>(address);
|
||||
break;
|
||||
default: EXIT("unknown predication op: 0x%08" PRIx32 "\n", op);
|
||||
}
|
||||
switch (condition) {
|
||||
case 0x00: m_predicate_skip = (value != 0); break;
|
||||
case 0x01: m_predicate_skip = (value == 0); break;
|
||||
default: EXIT("unknown predication condition: 0x%08" PRIx32 "\n", condition);
|
||||
}
|
||||
if (op == 0x03) {
|
||||
static std::atomic<uint32_t> log_count {0};
|
||||
if (log_count.fetch_add(1) < 128) {
|
||||
LOGF("\t bool predication: addr=0x%016" PRIx64 ", value=0x%016" PRIx64
|
||||
", condition=%" PRIu32 ", skip=%u, wait_op=%" PRIu32 "\n",
|
||||
reinterpret_cast<uint64_t>(address), value, condition,
|
||||
m_predicate_skip ? 1u : 0u, wait_op);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void CommandProcessor::DrawIndex(DrawIndexArgs args) {
|
||||
CheckBuffer();
|
||||
|
||||
args.index_type_and_size = m_index_type_and_size;
|
||||
if (args.instance_count == 0) {
|
||||
args.instance_count = m_num_instances;
|
||||
}
|
||||
if (args.base_vertex != 0 || args.first_instance != 0) {
|
||||
if (GraphicsRunDebugDumpEnabled() && (args.base_vertex != 0 || args.first_instance != 0)) {
|
||||
LOGF("\t draw indexed offsets: base_vertex = %" PRId32 ", first_instance = %" PRIu32 "\n",
|
||||
args.base_vertex, args.first_instance);
|
||||
}
|
||||
@@ -896,17 +910,6 @@ void CommandProcessor::DrawIndirect(uint32_t data_offset, uint32_t draw_initiato
|
||||
if (!indexed) {
|
||||
DrawIndirectArgs args {};
|
||||
std::memcpy(&args, args_addr, sizeof(args));
|
||||
if (args.instance_count != 1u || args.start_vertex_location != 0u ||
|
||||
args.start_instance_location != 0u) {
|
||||
static std::atomic<uint32_t> log_count {0};
|
||||
if (log_count.fetch_add(1) < 64) {
|
||||
LOGF("\t warning: partial DrawIndirect args: vertex_count=%" PRIu32
|
||||
", instance_count=%" PRIu32 ", start_vertex=%" PRIu32
|
||||
", start_instance=%" PRIu32 "\n",
|
||||
args.vertex_count_per_instance, args.instance_count,
|
||||
args.start_vertex_location, args.start_instance_location);
|
||||
}
|
||||
}
|
||||
m_num_instances = args.instance_count;
|
||||
DrawIndexAuto({.vertex_count = args.vertex_count_per_instance,
|
||||
.instance_count = args.instance_count,
|
||||
@@ -918,16 +921,6 @@ void CommandProcessor::DrawIndirect(uint32_t data_offset, uint32_t draw_initiato
|
||||
|
||||
DrawIndexedIndirectArgs args {};
|
||||
std::memcpy(&args, args_addr, sizeof(args));
|
||||
if (args.base_vertex_location != 0u || args.start_instance_location != 0u) {
|
||||
static std::atomic<uint32_t> log_count {0};
|
||||
if (log_count.fetch_add(1) < 64) {
|
||||
LOGF("\t warning: partial DrawIndexIndirect args: index_count=%" PRIu32
|
||||
", instance_count=%" PRIu32 ", start_index=%" PRIu32 ", base_vertex=%" PRIu32
|
||||
", start_instance=%" PRIu32 "\n",
|
||||
args.index_count_per_instance, args.instance_count, args.start_index_location,
|
||||
args.base_vertex_location, args.start_instance_location);
|
||||
}
|
||||
}
|
||||
|
||||
uint64_t index_size = 0;
|
||||
switch (m_index_type_and_size) {
|
||||
@@ -989,17 +982,6 @@ void CommandProcessor::DrawIndirectMulti(uint32_t data_offset, uint32_t max_coun
|
||||
|
||||
if (!indexed) {
|
||||
auto* args = reinterpret_cast<const DrawIndirectArgs*>(args_addr);
|
||||
if (args->instance_count != 1u || args->start_vertex_location != 0u ||
|
||||
args->start_instance_location != 0u) {
|
||||
static std::atomic<uint32_t> log_count {0};
|
||||
if (log_count.fetch_add(1) < 64) {
|
||||
LOGF("\t warning: partial DrawIndirectMulti args[%u]: vertex_count=%" PRIu32
|
||||
", instance_count=%" PRIu32 ", start_vertex=%" PRIu32
|
||||
", start_instance=%" PRIu32 "\n",
|
||||
i, args->vertex_count_per_instance, args->instance_count,
|
||||
args->start_vertex_location, args->start_instance_location);
|
||||
}
|
||||
}
|
||||
m_num_instances = args->instance_count;
|
||||
DrawIndexAuto({.vertex_count = args->vertex_count_per_instance,
|
||||
.instance_count = args->instance_count,
|
||||
@@ -1010,17 +992,6 @@ void CommandProcessor::DrawIndirectMulti(uint32_t data_offset, uint32_t max_coun
|
||||
}
|
||||
|
||||
auto* args = reinterpret_cast<const DrawIndexedIndirectArgs*>(args_addr);
|
||||
if (args->base_vertex_location != 0u || args->start_instance_location != 0u) {
|
||||
static std::atomic<uint32_t> log_count {0};
|
||||
if (log_count.fetch_add(1) < 64) {
|
||||
LOGF("\t warning: partial DrawIndexIndirectMulti args[%u]: index_count=%" PRIu32
|
||||
", instance_count=%" PRIu32 ", start_index=%" PRIu32 ", base_vertex=%" PRIu32
|
||||
", start_instance=%" PRIu32 "\n",
|
||||
i, args->index_count_per_instance, args->instance_count,
|
||||
args->start_index_location, args->base_vertex_location,
|
||||
args->start_instance_location);
|
||||
}
|
||||
}
|
||||
|
||||
uint64_t index_size = 0;
|
||||
switch (m_index_type_and_size) {
|
||||
@@ -1066,7 +1037,6 @@ void CommandProcessor::DispatchDirect(uint32_t thread_group_x, uint32_t thread_g
|
||||
// uint32_t local_z = 1;
|
||||
|
||||
{
|
||||
CheckBuffer();
|
||||
frame_num = m_renderer.GetGpu().GetFrameNum();
|
||||
if (GraphicsRunDebugDumpEnabled()) {
|
||||
static std::atomic<uint32_t> log_count {0};
|
||||
@@ -1090,17 +1060,6 @@ void CommandProcessor::DispatchDirect(uint32_t thread_group_x, uint32_t thread_g
|
||||
// local_x = std::max(cs.num_thread_x, 1u);
|
||||
// local_y = std::max(cs.num_thread_y, 1u);
|
||||
// local_z = std::max(cs.num_thread_z, 1u);
|
||||
if (cs.wave_size == 64u) {
|
||||
static std::atomic_bool logged_wave64_shader {false};
|
||||
if (!logged_wave64_shader.exchange(true, std::memory_order_relaxed)) {
|
||||
LOGF("warning: executing wave64 compute shader cs=0x%016" PRIx64 "\n",
|
||||
cs.data_addr);
|
||||
std::printf("warning: executing wave64 compute shader cs=0x%016" PRIx64 "\n",
|
||||
cs.data_addr);
|
||||
std::fflush(stdout);
|
||||
}
|
||||
}
|
||||
|
||||
m_renderer.GetRenderExecutor().DispatchDirect(m_submit_id, CurrentBuffer(), thread_group_x,
|
||||
thread_group_y, thread_group_z, mode);
|
||||
}
|
||||
@@ -1128,24 +1087,18 @@ void CommandProcessor::DispatchDirect(uint32_t thread_group_x, uint32_t thread_g
|
||||
}*/
|
||||
}
|
||||
|
||||
void CommandProcessor::DispatchIndirect(uint32_t data_offset, uint32_t mode) {
|
||||
struct DispatchIndirectArgs {
|
||||
uint32_t thread_group_x;
|
||||
uint32_t thread_group_y;
|
||||
uint32_t thread_group_z;
|
||||
};
|
||||
|
||||
EXIT_NOT_IMPLEMENTED(m_dispatch_indirect_args_base_addr == 0);
|
||||
|
||||
const auto args_addr = m_dispatch_indirect_args_base_addr + data_offset;
|
||||
auto* args = reinterpret_cast<const DispatchIndirectArgs*>(args_addr);
|
||||
|
||||
DispatchDirect(args->thread_group_x, args->thread_group_y, args->thread_group_z, mode);
|
||||
void CommandProcessor::DispatchIndirect(uint64_t args_addr, uint32_t mode) {
|
||||
EXIT_NOT_IMPLEMENTED(args_addr == 0 || (args_addr & 3u) != 0);
|
||||
if ((mode & Pm4::COMPUTE_DISPATCH_INITIATOR_USE_THREAD_DIMENSIONS) != 0) {
|
||||
const auto* args = reinterpret_cast<const vk::DispatchIndirectCommand*>(args_addr);
|
||||
DispatchDirect(args->x, args->y, args->z, mode);
|
||||
return;
|
||||
}
|
||||
m_sh_ctx.SetCsWaveSize(Pm4::ComputeWaveSize(mode));
|
||||
m_renderer.GetRenderExecutor().DispatchIndirect(m_submit_id, CurrentBuffer(), args_addr, mode);
|
||||
}
|
||||
|
||||
void CommandProcessor::DrawIndexAuto(DrawAutoArgs args) {
|
||||
CheckBuffer();
|
||||
|
||||
if (args.instance_count == 0) {
|
||||
args.instance_count = m_num_instances;
|
||||
}
|
||||
@@ -1167,7 +1120,7 @@ void CommandProcessor::WriteAtEndOfPipe(uint32_t cache_policy, uint32_t event_wr
|
||||
uint32_t interrupt_context_id) {
|
||||
static_assert(sizeof(T) == sizeof(uint32_t) || sizeof(T) == sizeof(uint64_t));
|
||||
|
||||
CheckBuffer();
|
||||
auto& command = CurrentBuffer();
|
||||
|
||||
if (GraphicsRunDebugDumpEnabled()) {
|
||||
const auto bits = static_cast<unsigned>(sizeof(T) * 8u);
|
||||
@@ -1198,7 +1151,7 @@ void CommandProcessor::WriteAtEndOfPipe(uint32_t cache_policy, uint32_t event_wr
|
||||
case 0x03: with_interrupt = false; break;
|
||||
case 0x01:
|
||||
if (!IsAsyncComputeQueue()) {
|
||||
Sync::TriggerEopEventAtEndOfPipe(CurrentBuffer(), m_interrupt_event_id,
|
||||
Sync::TriggerEopEventAtEndOfPipe(command, m_interrupt_event_id,
|
||||
interrupt_context_id);
|
||||
return;
|
||||
}
|
||||
@@ -1215,17 +1168,17 @@ void CommandProcessor::WriteAtEndOfPipe(uint32_t cache_policy, uint32_t event_wr
|
||||
|
||||
if (with_interrupt) {
|
||||
if (with_writeback) {
|
||||
Sync::WriteAtEndOfPipeWithInterruptWriteBack32(m_submit_id, CurrentBuffer(), dst,
|
||||
Sync::WriteAtEndOfPipeWithInterruptWriteBack32(m_submit_id, command, dst,
|
||||
data, m_interrupt_event_id,
|
||||
interrupt_context_id);
|
||||
} else {
|
||||
Sync::WriteAtEndOfPipeWithInterrupt32(m_submit_id, CurrentBuffer(), dst, data,
|
||||
Sync::WriteAtEndOfPipeWithInterrupt32(m_submit_id, command, dst, data,
|
||||
m_interrupt_event_id, interrupt_context_id);
|
||||
}
|
||||
} else if (with_writeback) {
|
||||
Sync::WriteAtEndOfPipeWithWriteBack32(m_submit_id, CurrentBuffer(), dst, data);
|
||||
Sync::WriteAtEndOfPipeWithWriteBack32(m_submit_id, command, dst, data);
|
||||
} else {
|
||||
Sync::WriteAtEndOfPipe32(m_submit_id, CurrentBuffer(), dst, data);
|
||||
Sync::WriteAtEndOfPipe32(m_submit_id, command, dst, data);
|
||||
}
|
||||
};
|
||||
|
||||
@@ -1237,7 +1190,7 @@ void CommandProcessor::WriteAtEndOfPipe(uint32_t cache_policy, uint32_t event_wr
|
||||
SynchronizeGpu();
|
||||
Sync::ReadGds(*m_renderer.GetBufferCache().GetGdsBuffer(), dst, value & 0xffffu,
|
||||
value >> 16u);
|
||||
Sync::WriteAtEndOfPipeGds32(m_submit_id, CurrentBuffer(), dst, value & 0xffffu,
|
||||
Sync::WriteAtEndOfPipeGds32(m_submit_id, command, dst, value & 0xffffu,
|
||||
value >> 16u);
|
||||
if (with_interrupt) {
|
||||
m_renderer.TriggerInterrupt(m_interrupt_event_id, interrupt_context_id);
|
||||
@@ -1266,18 +1219,18 @@ void CommandProcessor::WriteAtEndOfPipe(uint32_t cache_policy, uint32_t event_wr
|
||||
if (with_interrupt) {
|
||||
if (with_writeback) {
|
||||
Sync::WriteAtEndOfPipeWithInterruptWriteBack64(
|
||||
m_submit_id, CurrentBuffer(), dst, value, m_interrupt_event_id,
|
||||
m_submit_id, command, dst, value, m_interrupt_event_id,
|
||||
interrupt_context_id);
|
||||
} else {
|
||||
Sync::WriteAtEndOfPipeWithInterrupt64(m_submit_id, CurrentBuffer(), dst,
|
||||
Sync::WriteAtEndOfPipeWithInterrupt64(m_submit_id, command, dst,
|
||||
value, m_interrupt_event_id,
|
||||
interrupt_context_id);
|
||||
}
|
||||
} else if (with_writeback) {
|
||||
Sync::WriteAtEndOfPipeWithWriteBack64(m_submit_id, CurrentBuffer(), dst,
|
||||
Sync::WriteAtEndOfPipeWithWriteBack64(m_submit_id, command, dst,
|
||||
value);
|
||||
} else {
|
||||
Sync::WriteAtEndOfPipe64(m_submit_id, CurrentBuffer(), dst, value);
|
||||
Sync::WriteAtEndOfPipe64(m_submit_id, command, dst, value);
|
||||
}
|
||||
};
|
||||
|
||||
@@ -1292,6 +1245,7 @@ void CommandProcessor::WriteAtEndOfPipe(uint32_t cache_policy, uint32_t event_wr
|
||||
break;
|
||||
case 0x14:
|
||||
case 0x28:
|
||||
case 0x2f:
|
||||
if (event_index == 0x00) {
|
||||
write64(false);
|
||||
return;
|
||||
@@ -1299,7 +1253,6 @@ void CommandProcessor::WriteAtEndOfPipe(uint32_t cache_policy, uint32_t event_wr
|
||||
break;
|
||||
case 0x2b:
|
||||
case 0x2d:
|
||||
case 0x2f:
|
||||
case 0x30:
|
||||
if (event_index == 0x00 && !with_interrupt) {
|
||||
write64(false);
|
||||
@@ -1358,10 +1311,10 @@ void CommandProcessor::WriteAtEndOfPipe(uint32_t cache_policy, uint32_t event_wr
|
||||
(eop_event_type == 0x28 && event_index == 0x00)) {
|
||||
if (with_interrupt) {
|
||||
Sync::WriteAtEndOfPipeWithInterrupt64(
|
||||
m_submit_id, CurrentBuffer(), dst, clock, m_interrupt_event_id,
|
||||
m_submit_id, command, dst, clock, m_interrupt_event_id,
|
||||
interrupt_context_id);
|
||||
} else {
|
||||
Sync::WriteAtEndOfPipeClockCounter(m_submit_id, CurrentBuffer(),
|
||||
Sync::WriteAtEndOfPipeClockCounter(m_submit_id, command,
|
||||
dst, clock);
|
||||
}
|
||||
return;
|
||||
@@ -1373,11 +1326,11 @@ void CommandProcessor::WriteAtEndOfPipe(uint32_t cache_policy, uint32_t event_wr
|
||||
(eop_event_type == 0x28 && event_index == 0x00)) {
|
||||
if (with_interrupt) {
|
||||
Sync::WriteAtEndOfPipeWithInterruptWriteBack64(
|
||||
m_submit_id, CurrentBuffer(), dst, clock, m_interrupt_event_id,
|
||||
m_submit_id, command, dst, clock, m_interrupt_event_id,
|
||||
interrupt_context_id);
|
||||
} else {
|
||||
Sync::WriteAtEndOfPipeClockCounterWithWriteBack(
|
||||
m_submit_id, CurrentBuffer(), dst, clock);
|
||||
m_submit_id, command, dst, clock);
|
||||
}
|
||||
return;
|
||||
}
|
||||
@@ -1415,8 +1368,6 @@ void CommandProcessor::WriteAtEndOfPipe64(uint32_t cache_policy, uint32_t event_
|
||||
}
|
||||
|
||||
void CommandProcessor::EmitGlobalBarrier() {
|
||||
CheckBuffer();
|
||||
|
||||
Common::LockGuard lock(m_renderer.GetMutex());
|
||||
|
||||
vk::MemoryBarrier2 barrier {};
|
||||
@@ -1433,8 +1384,6 @@ void CommandProcessor::EmitGlobalBarrier() {
|
||||
}
|
||||
|
||||
void CommandProcessor::TriggerEopEventAtEndOfPipe(uint32_t interrupt_context_id) {
|
||||
CheckBuffer();
|
||||
|
||||
Sync::TriggerEopEventAtEndOfPipe(CurrentBuffer(), m_interrupt_event_id, interrupt_context_id);
|
||||
}
|
||||
|
||||
@@ -1518,8 +1467,6 @@ void CommandProcessor::TriggerEvent(uint32_t event_type, uint32_t event_index,
|
||||
}
|
||||
|
||||
void CommandProcessor::Flip() {
|
||||
CheckBuffer();
|
||||
|
||||
if (GraphicsRunDebugDumpEnabled()) {
|
||||
LOGF("CommandProcessor::Flip()\n");
|
||||
}
|
||||
@@ -1533,7 +1480,7 @@ void CommandProcessor::Flip() {
|
||||
}
|
||||
|
||||
void CommandProcessor::Flip(void* dst_gpu_addr, uint32_t value) {
|
||||
CheckBuffer();
|
||||
auto& command = CurrentBuffer();
|
||||
|
||||
if (GraphicsRunDebugDumpEnabled()) {
|
||||
LOGF("CommandProcessor::Flip()\n"
|
||||
@@ -1543,7 +1490,6 @@ void CommandProcessor::Flip(void* dst_gpu_addr, uint32_t value) {
|
||||
}
|
||||
|
||||
std::memcpy(dst_gpu_addr, &value, sizeof(value));
|
||||
auto& command = CurrentBuffer();
|
||||
auto request = Sync::PrepareVideoOutFlip(command, m_flip.handle, m_flip.index, m_flip.flip_mode,
|
||||
m_flip.flip_arg);
|
||||
Sync::WriteAtEndOfPipeWithFlip32(m_submit_id, command, static_cast<uint32_t*>(dst_gpu_addr),
|
||||
@@ -1554,7 +1500,7 @@ void CommandProcessor::Flip(void* dst_gpu_addr, uint32_t value) {
|
||||
|
||||
void CommandProcessor::FlipWithInterrupt(uint32_t eop_event_type, uint32_t cache_action,
|
||||
void* dst_gpu_addr, uint32_t value) {
|
||||
CheckBuffer();
|
||||
auto& command = CurrentBuffer();
|
||||
|
||||
if (GraphicsRunDebugDumpEnabled()) {
|
||||
LOGF("CommandProcessor::FlipWithInterrupt()\n"
|
||||
@@ -1569,7 +1515,6 @@ void CommandProcessor::FlipWithInterrupt(uint32_t eop_event_type, uint32_t cache
|
||||
EXIT("unknown event type\n");
|
||||
}
|
||||
std::memcpy(dst_gpu_addr, &value, sizeof(value));
|
||||
auto& command = CurrentBuffer();
|
||||
auto request = Sync::PrepareVideoOutFlip(command, m_flip.handle, m_flip.index, m_flip.flip_mode,
|
||||
m_flip.flip_arg);
|
||||
Sync::WriteAtEndOfPipeWithInterruptWriteBackFlip32(
|
||||
@@ -1579,7 +1524,7 @@ void CommandProcessor::FlipWithInterrupt(uint32_t eop_event_type, uint32_t cache
|
||||
}
|
||||
|
||||
void CommandProcessor::PrepareCpuFlip(uint64_t request_id) {
|
||||
CheckBuffer();
|
||||
auto& command = CurrentBuffer();
|
||||
if (g_current_processor != nullptr) {
|
||||
EXIT("invalid graphics-thread CPU flip preparation\n");
|
||||
}
|
||||
@@ -1589,7 +1534,7 @@ void CommandProcessor::PrepareCpuFlip(uint64_t request_id) {
|
||||
};
|
||||
ProcessorScope processor_scope(*this);
|
||||
|
||||
m_renderer.GetVideoOut().PrepareFlip(request_id, CurrentBuffer());
|
||||
m_renderer.GetVideoOut().PrepareFlip(request_id, command);
|
||||
GetScheduler().Flush();
|
||||
m_renderer.GetVideoOut().CompleteFlip(request_id);
|
||||
}
|
||||
|
||||
@@ -498,7 +498,8 @@ struct LsStageRegisters {
|
||||
};
|
||||
|
||||
struct HsStageRegisters {
|
||||
uint64_t data_addr = 0;
|
||||
uint64_t data_addr = 0;
|
||||
uint64_t user_data_addr = 0;
|
||||
HsShaderResource1 rsrc1;
|
||||
HsShaderResource2 rsrc2;
|
||||
};
|
||||
@@ -935,6 +936,12 @@ private:
|
||||
ShaderRegisters m_sh_regs;
|
||||
};
|
||||
|
||||
struct FsrView {
|
||||
uint32_t control_points[2][4] {};
|
||||
uint32_t alphas[2][2] {};
|
||||
uint32_t window[2] {};
|
||||
};
|
||||
|
||||
class UserConfig {
|
||||
public:
|
||||
UserConfig() = default;
|
||||
@@ -964,8 +971,17 @@ public:
|
||||
void SetGdsOaCntl(uint32_t value) { m_gds_oa.cntl = value; }
|
||||
void SetGdsOaCounter(uint32_t value) { m_gds_oa.counters[m_gds_oa.GetIndex()].counter = value; }
|
||||
void SetGdsOaAddress(uint32_t value) { m_gds_oa.counters[m_gds_oa.GetIndex()].address = value; }
|
||||
void SetFsrControlPoint(uint32_t axis, uint32_t index, uint32_t value) {
|
||||
m_fsr_view.control_points[axis][index] = value;
|
||||
}
|
||||
void SetFsrAlpha(uint32_t axis, uint32_t index, uint32_t value) {
|
||||
m_fsr_view.alphas[axis][index] = value;
|
||||
}
|
||||
void SetFsrWindow(uint32_t index, uint32_t value) { m_fsr_view.window[index] = value; }
|
||||
[[nodiscard]] const FsrView& GetFsrView() const { return m_fsr_view; }
|
||||
|
||||
private:
|
||||
FsrView m_fsr_view;
|
||||
Prospero::PrimitiveType m_prim_type = Prospero::PrimitiveType::kNone;
|
||||
uint32_t m_index_offset = 0;
|
||||
uint32_t m_object_id = 0;
|
||||
@@ -988,14 +1004,14 @@ public:
|
||||
void SetEsShaderBase(uint64_t addr) { m_vs.es_regs.data_addr = addr; }
|
||||
void SetLsShaderBase(uint64_t addr) { m_vs.ls_regs.data_addr = addr; }
|
||||
void SetHsShaderBase(uint64_t addr) { m_vs.hs_regs.data_addr = addr; }
|
||||
void SetHsUserDataAddress(uint32_t word, uint32_t value) {
|
||||
SetUserDataAddressWord(m_vs.hs_regs.user_data_addr, word, value);
|
||||
}
|
||||
void SetHsShaderResource1(const HsShaderResource1& rsrc1) { m_vs.hs_regs.rsrc1 = rsrc1; }
|
||||
void SetHsShaderResource2(const HsShaderResource2& rsrc2) { m_vs.hs_regs.rsrc2 = rsrc2; }
|
||||
void SetGsShaderBase(uint64_t addr) { m_vs.gs_regs.data_addr = addr; }
|
||||
void SetGsUserDataAddress(uint32_t word, uint32_t value) {
|
||||
const auto shift = word * 32u;
|
||||
auto& address = m_vs.gs_regs.user_data_addr;
|
||||
address = (address & ~(uint64_t {0xffffffffu} << shift)) |
|
||||
(static_cast<uint64_t>(value) << shift);
|
||||
SetUserDataAddressWord(m_vs.gs_regs.user_data_addr, word, value);
|
||||
}
|
||||
void SetGsShaderResource1(const GsShaderResource1& rsrc1) { m_vs.gs_regs.rsrc1 = rsrc1; }
|
||||
void SetGsShaderResource2(const GsShaderResource2& rsrc2) { m_vs.gs_regs.rsrc2 = rsrc2; }
|
||||
@@ -1037,6 +1053,12 @@ public:
|
||||
[[nodiscard]] const ComputeShaderInfo& GetCs() const { return m_cs; }
|
||||
|
||||
private:
|
||||
static void SetUserDataAddressWord(uint64_t& address, uint32_t word, uint32_t value) {
|
||||
const auto shift = word * 32u;
|
||||
address = (address & ~(uint64_t {0xffffffffu} << shift)) |
|
||||
(static_cast<uint64_t>(value) << shift);
|
||||
}
|
||||
|
||||
VertexShaderInfo m_vs;
|
||||
PixelShaderInfo m_ps;
|
||||
ComputeShaderInfo m_cs;
|
||||
|
||||
@@ -910,6 +910,7 @@ constexpr uint32_t COMPUTE_PGM_RSRC1_WGP_MODE_MASK = 0x1;
|
||||
constexpr uint32_t COMPUTE_PGM_RSRC1_FWD_PROGRESS_SHIFT = 31;
|
||||
constexpr uint32_t COMPUTE_PGM_RSRC1_FWD_PROGRESS_MASK = 0x1;
|
||||
|
||||
constexpr uint32_t COMPUTE_DISPATCH_INITIATOR_USE_THREAD_DIMENSIONS = 1u << 5u;
|
||||
constexpr uint32_t COMPUTE_DISPATCH_INITIATOR_CS_W32_EN_SHIFT = 15;
|
||||
constexpr uint32_t COMPUTE_DISPATCH_INITIATOR_CS_W32_EN_MASK = 0x1;
|
||||
|
||||
@@ -987,6 +988,12 @@ constexpr uint32_t GE_USER_VGPR_EN_EN_USER_VGPR2_MASK = 0x1;
|
||||
constexpr uint32_t GE_USER_VGPR_EN_EN_USER_VGPR3_SHIFT = 2;
|
||||
constexpr uint32_t GE_USER_VGPR_EN_EN_USER_VGPR3_MASK = 0x1;
|
||||
|
||||
constexpr uint32_t FSR_WINDOW_LEFT = 0x2BC;
|
||||
constexpr uint32_t FSR_CONTROL_POINTS_LEFT_X = 0x2C4;
|
||||
constexpr uint32_t FSR_CONTROL_POINTS_LEFT_Y = 0x2CC;
|
||||
constexpr uint32_t FSR_ALPHA_LEFT_X = 0x2E4;
|
||||
constexpr uint32_t FSR_ALPHA_LEFT_Y = 0x2E8;
|
||||
|
||||
constexpr uint32_t TA_CS_BC_BASE_ADDR = 0x380;
|
||||
constexpr uint32_t TA_CS_BC_BASE_ADDR_HI = 0x381;
|
||||
constexpr uint32_t TEXTURE_GRADIENT_FACTORS = 0x383;
|
||||
|
||||
@@ -4,15 +4,12 @@
|
||||
#include "common/assert.h"
|
||||
#include "common/logging/log.h"
|
||||
#include "common/profiler.h"
|
||||
#include "common/stringUtils.h"
|
||||
#include "graphics/guest_gpu/gpu_defs.h"
|
||||
#include "graphics/guest_gpu/gpu_format.h"
|
||||
|
||||
#include <algorithm>
|
||||
#include <array>
|
||||
#include <bit>
|
||||
#include <fmt/format.h>
|
||||
#include <vector>
|
||||
|
||||
namespace Libs::Graphics {
|
||||
|
||||
@@ -1322,13 +1319,8 @@ void TileGetTextureSize(Prospero::BufferFormat format, uint32_t width, uint32_t
|
||||
return;
|
||||
}
|
||||
if (total_size != nullptr && total_size->size == 0) {
|
||||
std::vector<std::string> list;
|
||||
list.push_back(fmt::format("format = {}", static_cast<uint32_t>(format)));
|
||||
list.push_back(fmt::format("width = {}", width));
|
||||
list.push_back(fmt::format("height = {}", height));
|
||||
list.push_back(fmt::format("levels = {}", levels));
|
||||
list.push_back(fmt::format("tile = {}", static_cast<uint32_t>(tile)));
|
||||
EXIT("unknown format:\n%s\n", Common::Concat(list, '\n').c_str());
|
||||
EXIT("unknown format:\nformat = %u\nwidth = %u\nheight = %u\nlevels = %u\ntile = %u\n",
|
||||
static_cast<uint32_t>(format), width, height, levels, static_cast<uint32_t>(tile));
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -86,6 +86,7 @@ struct TileMipLayout {
|
||||
uint32_t padded_height = 0;
|
||||
uint32_t tail_x = 0;
|
||||
uint32_t tail_y = 0;
|
||||
bool operator==(const TileMipLayout&) const = default;
|
||||
};
|
||||
|
||||
struct TileSurfaceLayout {
|
||||
|
||||
@@ -16,6 +16,7 @@
|
||||
#include <windows.h>
|
||||
#undef min
|
||||
#undef max
|
||||
#undef MemoryBarrier
|
||||
#elif defined(__APPLE__)
|
||||
#include <pthread.h>
|
||||
#elif defined(__linux__)
|
||||
|
||||
+2
-2
@@ -129,7 +129,7 @@ bool BufferCache::DownloadBufferMemory(Buffer& buffer, uint64_t vaddr, uint64_t
|
||||
|
||||
const auto [mapped, offset] = m_download_buffer.Map(total_size, 64);
|
||||
if (mapped == nullptr) {
|
||||
EXIT("BufferCache: download exceeds 32 MiB staging buffer capacity\n");
|
||||
EXIT("BufferCache: download exceeds 64 MiB staging buffer capacity\n");
|
||||
}
|
||||
m_download_buffer.Commit();
|
||||
for (auto& copy: copies) {
|
||||
@@ -183,7 +183,7 @@ BufferCache::BufferCache(GraphicContext& graphics, CommandScheduler& scheduler,
|
||||
m_memory_tracker(page_manager),
|
||||
m_staging_buffer(graphics, scheduler, MemoryUsage::Upload, 512 * MiB),
|
||||
m_stream_buffer(graphics, scheduler, MemoryUsage::Stream, 64 * MiB),
|
||||
m_download_buffer(graphics, scheduler, MemoryUsage::Download, 32 * MiB),
|
||||
m_download_buffer(graphics, scheduler, MemoryUsage::Download, 64 * MiB),
|
||||
m_device_buffer(graphics, scheduler, MemoryUsage::DeviceLocal, 128 * MiB),
|
||||
m_texture_cache(texture_cache) {
|
||||
std::memset(m_gds_buffer.Mapped().data(), 0, static_cast<size_t>(m_gds_buffer.Size()));
|
||||
|
||||
+1
-6
@@ -44,12 +44,7 @@ FaultManager::FaultManager(GraphicContext& graphics, CommandScheduler& scheduler
|
||||
&m_fault_process_desc_layout),
|
||||
"create fault-buffer descriptor layout");
|
||||
|
||||
vk::ShaderModuleCreateInfo module_info {};
|
||||
module_info.codeSize = std::size(FAULT_BUFFER_PROCESS_SPV) * sizeof(uint32_t);
|
||||
module_info.pCode = FAULT_BUFFER_PROCESS_SPV;
|
||||
vk::ShaderModule module = nullptr;
|
||||
RequireVulkanSuccess(m_graphics.device.createShaderModule(&module_info, nullptr, &module),
|
||||
"create fault-buffer shader module");
|
||||
const auto module = CompileSPV(FAULT_BUFFER_PROCESS_SPV, m_graphics.device);
|
||||
|
||||
vk::PipelineLayoutCreateInfo pipeline_layout_info {};
|
||||
pipeline_layout_info.setLayoutCount = 1;
|
||||
|
||||
+213
-187
@@ -33,22 +33,24 @@ namespace {
|
||||
|
||||
constexpr uint64_t NumFramesBeforeRemoval = 32;
|
||||
|
||||
[[nodiscard]] bool DecodeDccClear(const TextureCache::ImageDesc& desc, vk::Format format,
|
||||
uint32_t fill, vk::ClearColorValue& clear) {
|
||||
const auto code = static_cast<uint8_t>(fill);
|
||||
if (fill != static_cast<uint32_t>(code) * 0x01010101u) {
|
||||
return false;
|
||||
[[nodiscard]] bool DecodeDccClear(const TextureCache::ImageDesc& desc, uint8_t code,
|
||||
vk::ClearColorValue& clear) {
|
||||
switch (code) {
|
||||
case 0x00:
|
||||
case 0x20:
|
||||
case 0x40:
|
||||
case 0x80:
|
||||
case 0xc0: break;
|
||||
default: return false;
|
||||
}
|
||||
const auto& metadata = desc.info.metadata;
|
||||
const auto format = desc.view_info.format;
|
||||
if (code == 0x20) {
|
||||
// Clear-to-register is a color-buffer operation; the texture pipe cannot decode it.
|
||||
return desc.type == TextureCache::BindingType::RenderTarget &&
|
||||
metadata.dcc_clear_register_valid &&
|
||||
DecodePackedColorClear(format, metadata.dcc_clear_word, clear);
|
||||
}
|
||||
if (code != 0x00 && code != 0x40 && code != 0x80 && code != 0xc0) {
|
||||
return false;
|
||||
}
|
||||
clear = {};
|
||||
if (code == 0x00) {
|
||||
return true;
|
||||
@@ -136,14 +138,15 @@ void NameImageBinding(GraphicContext& graphics, Image& image, vk::ImageView view
|
||||
}
|
||||
|
||||
[[nodiscard]] std::vector<vk::BufferImageCopy> BuildDepthCopies(const ImageInfo& info,
|
||||
uint64_t slice_stride) {
|
||||
uint64_t slice_stride,
|
||||
vk::ImageAspectFlags aspect) {
|
||||
std::vector<vk::BufferImageCopy> copies(info.resources.layers);
|
||||
for (uint32_t layer = 0; layer < info.resources.layers; ++layer) {
|
||||
auto& copy = copies[layer];
|
||||
copy.bufferOffset = slice_stride * layer;
|
||||
copy.bufferRowLength = info.pitch;
|
||||
copy.bufferImageHeight = info.extent.height;
|
||||
copy.imageSubresource = {vk::ImageAspectFlagBits::eDepth, 0, layer, 1};
|
||||
copy.imageSubresource = {aspect, 0, layer, 1};
|
||||
copy.imageExtent = {info.extent.width, info.extent.height, 1};
|
||||
}
|
||||
return copies;
|
||||
@@ -206,6 +209,10 @@ bool TextureCache::SameBacking(const ImageInfo& cached, const ImageInfo& request
|
||||
if (cached.extent != requested.extent) {
|
||||
return false;
|
||||
}
|
||||
if (cached.resources.levels < requested.resources.levels ||
|
||||
cached.resources.layers < requested.resources.layers) {
|
||||
return false;
|
||||
}
|
||||
if (cached.samples != requested.samples) {
|
||||
return false;
|
||||
}
|
||||
@@ -323,10 +330,8 @@ void TextureCache::DeleteImage(ImageId id) {
|
||||
if (image->info.HasMetadata()) {
|
||||
const auto metadata = m_surface_metas.find(image->info.metadata.range.address);
|
||||
if (metadata != m_surface_metas.end() &&
|
||||
((image->info.metadata.kind == ImageMetadataKind::Dcc &&
|
||||
metadata->second.type == MetaDataInfo::Type::Dcc) ||
|
||||
(image->info.metadata.kind == ImageMetadataKind::Htile &&
|
||||
metadata->second.type == MetaDataInfo::Type::HTile))) {
|
||||
image->info.metadata.kind == ImageMetadataKind::Htile &&
|
||||
metadata->second.type == MetaDataInfo::Type::HTile) {
|
||||
// A later binding may have reused this address for another metadata type.
|
||||
m_surface_metas.erase(metadata);
|
||||
}
|
||||
@@ -810,17 +815,10 @@ TextureCache::OverlapResult TextureCache::ResolveOverlap(const ImageInfo& reques
|
||||
const bool safe_to_delete =
|
||||
current_tick - std::min(current_tick, cached.tick_accessed_last) > NumFramesBeforeRemoval;
|
||||
|
||||
if (requested.data.address == cached.info.data.address) {
|
||||
const uint32_t requested_block = requested.bytes_per_block * requested.samples;
|
||||
const uint32_t cached_block = cached.info.bytes_per_block * cached.info.samples;
|
||||
if (requested.BlockExtent() != cached.info.BlockExtent() ||
|
||||
requested_block != cached_block) {
|
||||
if (safe_to_delete) {
|
||||
FreeImage(cached_id);
|
||||
}
|
||||
return {merged_id};
|
||||
}
|
||||
|
||||
const uint32_t requested_block = requested.bytes_per_block * requested.samples;
|
||||
const uint32_t cached_block = cached.info.bytes_per_block * cached.info.samples;
|
||||
if (requested.data.address == cached.info.data.address &&
|
||||
requested.BlockExtent() == cached.info.BlockExtent() && requested_block == cached_block) {
|
||||
if (const auto depth_id = ResolveDepthOverlap(requested, binding, cached_id)) {
|
||||
return {depth_id};
|
||||
}
|
||||
@@ -850,6 +848,16 @@ TextureCache::OverlapResult TextureCache::ResolveOverlap(const ImageInfo& reques
|
||||
ImageViewOps::FormatsCompatible(cached.info.pixel_format, requested.pixel_format)) {
|
||||
return {ExpandImage(requested, cached_id)};
|
||||
}
|
||||
// PS5 mip tails can expose more levels without increasing the guest allocation.
|
||||
if (requested.pixel_format == cached.info.pixel_format &&
|
||||
requested.type == cached.info.type && requested.resources > cached.info.resources &&
|
||||
(requested.data.size > cached.info.data.size ||
|
||||
(requested.data.size == cached.info.data.size &&
|
||||
requested.extent == cached.info.extent &&
|
||||
cached.info.resources.levels > 1 &&
|
||||
requested.resources.layers == cached.info.resources.layers))) {
|
||||
return {ExpandImage(requested, cached_id)};
|
||||
}
|
||||
if (requested.pixel_format != cached.info.pixel_format ||
|
||||
requested.data.size <= cached.info.data.size) {
|
||||
const auto result_id = merged_id ? merged_id : cached_id;
|
||||
@@ -859,9 +867,6 @@ TextureCache::OverlapResult TextureCache::ResolveOverlap(const ImageInfo& reques
|
||||
? result_id
|
||||
: ImageId {}};
|
||||
}
|
||||
if (requested.type == cached.info.type && requested.resources > cached.info.resources) {
|
||||
return {ExpandImage(requested, cached_id)};
|
||||
}
|
||||
EXIT("TextureCache: unresolvable equal-address image overlap, address=0x%016" PRIx64
|
||||
" requested=%ux%u "
|
||||
"cached=%ux%u requested_size=0x%016" PRIx64 " cached_size=0x%016" PRIx64
|
||||
@@ -873,38 +878,27 @@ TextureCache::OverlapResult TextureCache::ResolveOverlap(const ImageInfo& reques
|
||||
static_cast<uint32_t>(cached.info.tile_mode));
|
||||
}
|
||||
|
||||
if (requested.data.address > cached.info.data.address) {
|
||||
const int32_t mip = requested.MipOf(cached.info);
|
||||
if (mip >= 0) {
|
||||
const int32_t layer = requested.SliceOf(cached.info, mip);
|
||||
if (layer >= 0) {
|
||||
return {cached_id, mip, layer};
|
||||
}
|
||||
}
|
||||
if (safe_to_delete) {
|
||||
FreeImage(cached_id);
|
||||
}
|
||||
return {};
|
||||
const int32_t requested_mip = requested.MipOf(cached.info);
|
||||
if (requested_mip >= 0) {
|
||||
const int32_t layer = requested.SliceOf(cached.info, requested_mip);
|
||||
return {cached_id, requested_mip, layer};
|
||||
}
|
||||
|
||||
const int32_t mip = cached.info.MipOf(requested);
|
||||
if (mip >= 0) {
|
||||
const int32_t layer = cached.info.SliceOf(requested, mip);
|
||||
if (layer >= 0) {
|
||||
if (cached.binding.is_target) {
|
||||
cached.binding.needs_rebind = true;
|
||||
if (merged_id) {
|
||||
m_slot_images[merged_id].binding.is_target = true;
|
||||
}
|
||||
FreeImage(cached_id);
|
||||
return {merged_id};
|
||||
}
|
||||
if (merged_id) {
|
||||
CopyImageMip(merged_id, cached_id, static_cast<uint32_t>(mip),
|
||||
static_cast<uint32_t>(layer));
|
||||
FreeImage(cached_id);
|
||||
}
|
||||
if (!merged_id) {
|
||||
return {ExpandImage(requested, cached_id)};
|
||||
}
|
||||
cached.binding.needs_rebind |= cached.binding.is_bound || cached.binding.is_target;
|
||||
m_slot_images[merged_id].binding.is_target |= cached.binding.is_target;
|
||||
CopyImageMip(merged_id, cached_id, static_cast<uint32_t>(mip),
|
||||
static_cast<uint32_t>(layer));
|
||||
FreeImage(cached_id);
|
||||
return {merged_id};
|
||||
}
|
||||
if (requested.data.address >= cached.info.data.address && safe_to_delete) {
|
||||
FreeImage(cached_id);
|
||||
}
|
||||
return {merged_id};
|
||||
}
|
||||
@@ -919,7 +913,14 @@ ImageId TextureCache::ExpandImage(const ImageInfo& info, ImageId source_id) {
|
||||
source.binding.needs_rebind = true;
|
||||
}
|
||||
InitializeImage(expanded_id);
|
||||
CopyImage(expanded_id, source_id);
|
||||
const int32_t mip = source.info.MipOf(info);
|
||||
const int32_t layer = source.info.SliceOf(info, mip);
|
||||
if (layer >= 0) {
|
||||
CopyImageMip(expanded_id, source_id, static_cast<uint32_t>(mip),
|
||||
static_cast<uint32_t>(layer));
|
||||
} else {
|
||||
CopyImage(expanded_id, source_id);
|
||||
}
|
||||
FreeImage(source_id);
|
||||
return expanded_id;
|
||||
}
|
||||
@@ -1031,16 +1032,17 @@ TextureCache::ImageDownload TextureCache::BuildDownload(const Image& image) cons
|
||||
}
|
||||
|
||||
void TextureCache::UploadImage(Image& image, Buffer& source, uint64_t source_offset) {
|
||||
const auto& info = image.info;
|
||||
const auto binding = UploadBinding(image);
|
||||
auto& destination = image.depth_id ? m_slot_images[image.depth_id] : image;
|
||||
const auto binding = image.depth_id ? BindingType::DepthTarget : UploadBinding(image);
|
||||
const auto upload = [&](std::vector<vk::BufferImageCopy>& copies, TileManager::Result linear) {
|
||||
for (auto& copy: copies) {
|
||||
copy.bufferOffset += linear.offset;
|
||||
}
|
||||
image.Upload(copies, linear.buffer, linear.offset, linear.size);
|
||||
destination.Upload(copies, linear.buffer, linear.offset, linear.size);
|
||||
};
|
||||
|
||||
if (binding != BindingType::DepthTarget) {
|
||||
const auto& info = image.info;
|
||||
auto transfer = BuildTextureTransfer(image, binding, TransferDirection::Upload);
|
||||
if (!transfer.valid) {
|
||||
EXIT("TextureCache: invalid texture upload: binding=%u addr=0x%016" PRIx64
|
||||
@@ -1064,21 +1066,32 @@ void TextureCache::UploadImage(Image& image, Buffer& source, uint64_t source_off
|
||||
return;
|
||||
}
|
||||
|
||||
if (info.samples != 1 || image.backing.samples != 1 ||
|
||||
// The stencil plane has its own row pitch and shares the native image
|
||||
// with the depth plane.
|
||||
auto info = destination.info;
|
||||
if (image.depth_id) {
|
||||
info.data = image.info.data;
|
||||
info.guest_format = Prospero::BufferFormat::k8UInt;
|
||||
info.bytes_per_block = 1;
|
||||
if (info.IsTiled()) info.pitch = TileGetDepthPitch(info.extent.width, 1, 0);
|
||||
}
|
||||
if (info.samples != 1 || destination.backing.samples != 1 ||
|
||||
info.resources.layers == 0 || info.data.size % info.resources.layers != 0 ||
|
||||
Prospero::NumBytesPerElement(info.guest_format) != info.bytes_per_block) {
|
||||
EXIT("TextureCache: invalid depth upload\n");
|
||||
}
|
||||
const auto layers = info.resources.layers;
|
||||
const auto full_slice_size = info.data.size / layers;
|
||||
auto copies = BuildDepthCopies(info, full_slice_size);
|
||||
auto copies = BuildDepthCopies(info, full_slice_size, image.depth_id
|
||||
? vk::ImageAspectFlagBits::eStencil
|
||||
: vk::ImageAspectFlagBits::eDepth);
|
||||
TileManager::Result linear {source.Handle(), source_offset, source.Size() - source_offset};
|
||||
if (info.IsTiled()) {
|
||||
const auto tiles = BuildDepthTiles(info);
|
||||
linear =
|
||||
m_tiler.Detile(source.Handle(), source_offset, info.data.size, info.data.size, tiles);
|
||||
}
|
||||
const auto transfer_bytes = DepthAspectTransferBytes(info.pixel_format);
|
||||
const auto transfer_bytes = image.depth_id ? 1u : DepthAspectTransferBytes(info.pixel_format);
|
||||
if (transfer_bytes != info.bytes_per_block) {
|
||||
const uint64_t texels_per_slice = static_cast<uint64_t>(info.pitch) * info.extent.height;
|
||||
EXIT_NOT_IMPLEMENTED(info.bytes_per_block != sizeof(uint16_t) ||
|
||||
@@ -1135,59 +1148,82 @@ void TextureCache::InitializeImage(ImageId id) {
|
||||
}
|
||||
}
|
||||
|
||||
void TextureCache::PrepareDccClear(ImageId id, const ImageDesc& desc) {
|
||||
void TextureCache::MaterializeDccClear(ImageId id, const ImageDesc& desc,
|
||||
uint32_t metadata_base_layer) {
|
||||
if (desc.info.metadata.kind != ImageMetadataKind::Dcc) {
|
||||
return;
|
||||
}
|
||||
auto& image = m_slot_images[id];
|
||||
image.info.metadata = desc.info.metadata;
|
||||
auto [entry, inserted] = m_surface_metas.try_emplace(
|
||||
desc.info.metadata.range.address, MetaDataInfo {.type = MetaDataInfo::Type::Dcc});
|
||||
auto& metadata = entry->second;
|
||||
if (!inserted && metadata.type == MetaDataInfo::Type::PendingDcc) {
|
||||
metadata.type = MetaDataInfo::Type::Dcc;
|
||||
} else if (metadata.type != MetaDataInfo::Type::Dcc) {
|
||||
EXIT("TextureCache: image reuses non-DCC metadata\n");
|
||||
const auto range = desc.info.metadata.range;
|
||||
{
|
||||
std::scoped_lock lock {m_lock};
|
||||
auto& image = m_slot_images[id];
|
||||
image.info.metadata = desc.info.metadata;
|
||||
// A native DCC allocation must not retain a reused HTile/CMask/FMask interpretation.
|
||||
m_surface_metas.erase(range.address);
|
||||
if (range.size == 0 || desc.info.resources.levels != 1 || image.info.resources.levels != 1) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
if (metadata.clear_mask == 0 || image.info.resources.levels != 1 ||
|
||||
desc.info.metadata.range.size == 0 || metadata.fill_size < desc.info.metadata.range.size) {
|
||||
return;
|
||||
}
|
||||
vk::ClearValue clear {};
|
||||
if (!DecodeDccClear(desc, image.backing.format, metadata.fill_value, clear.color)) {
|
||||
return;
|
||||
const auto layers = desc.info.TransferLayers();
|
||||
// These one-mip surfaces use complete 4 KiB DCC metadata blocks.
|
||||
constexpr uint64_t MetadataBlockSize = 0x1000;
|
||||
if (!range.Valid() || range.address % MetadataBlockSize != 0 || layers == 0 ||
|
||||
range.size % layers != 0 || (range.size / layers) % MetadataBlockSize != 0) {
|
||||
EXIT("TextureCache: DCC slices must contain aligned 4 KiB blocks\n");
|
||||
}
|
||||
const auto& view = desc.view_info;
|
||||
const bool volume_texture = image.info.IsVolume() && view.type == vk::ImageViewType::e3D;
|
||||
const auto first = volume_texture ? 0u : view.base_layer;
|
||||
const auto count = volume_texture ? std::max(image.info.extent.depth >> view.base_level, 1u)
|
||||
: view.layer_count;
|
||||
if (first >= 32 || count > 32 - first) {
|
||||
return;
|
||||
const bool volume_texture = desc.info.IsVolume() && view.type == vk::ImageViewType::e3D;
|
||||
const auto first = volume_texture ? 0u : metadata_base_layer;
|
||||
const auto image_first = volume_texture ? 0u : view.base_layer;
|
||||
const auto count = volume_texture ? desc.info.extent.depth : view.layer_count;
|
||||
if (first >= layers || count > layers - first) {
|
||||
EXIT("TextureCache: DCC view exceeds its native metadata slices\n");
|
||||
}
|
||||
// The metadata fill covers the complete allocation. Consume each layer only after its
|
||||
// native image contents exist; already materialized layers may have been rendered since.
|
||||
for (uint32_t layer = first; layer < first + count;) {
|
||||
if ((metadata.clear_mask & (1u << layer)) == 0) {
|
||||
layer++;
|
||||
// Finish native metadata writes before reading backing bytes. This can submit the scheduler,
|
||||
// so discovery runs before final draw uploads and never holds the texture lock across it.
|
||||
if (m_buffer_cache.IsRegionGpuModified(range.address, range.size)) {
|
||||
m_buffer_cache.ReadMemory(range.address, range.size, false);
|
||||
}
|
||||
const auto slice_size = range.size / layers;
|
||||
for (uint32_t slice = 0; slice < count; slice++) {
|
||||
const auto address = range.address + slice_size * (first + slice);
|
||||
uint8_t code = 0;
|
||||
if (!LibKernel::Memory::TryReadBacking(address, &code, sizeof(code))) {
|
||||
EXIT("TextureCache: failed to read DCC metadata backing\n");
|
||||
}
|
||||
vk::ClearValue clear {};
|
||||
if (!DecodeDccClear(desc, code, clear.color)) {
|
||||
continue;
|
||||
}
|
||||
const auto start = layer;
|
||||
uint32_t mask = 0;
|
||||
do {
|
||||
mask |= 1u << layer++;
|
||||
} while (layer < first + count && (metadata.clear_mask & (1u << layer)) != 0);
|
||||
ClearImage(m_scheduler.Current(), id,
|
||||
{vk::ImageAspectFlagBits::eColor, view.base_level, view.level_count, start,
|
||||
layer - start},
|
||||
clear);
|
||||
metadata.clear_mask &= ~mask;
|
||||
std::vector<uint8_t> bytes(slice_size);
|
||||
if (!LibKernel::Memory::TryReadBacking(address, bytes.data(), bytes.size())) {
|
||||
EXIT("TextureCache: failed to read DCC metadata slice\n");
|
||||
}
|
||||
if (!std::all_of(bytes.begin(), bytes.end(), [code](uint8_t byte) { return byte == code; })) {
|
||||
continue;
|
||||
}
|
||||
{
|
||||
std::scoped_lock lock {m_lock};
|
||||
ClearImage(m_scheduler.Current(), id, view.format,
|
||||
{vk::ImageAspectFlagBits::eColor, view.base_level, view.level_count,
|
||||
image_first + slice, 1}, clear);
|
||||
}
|
||||
// Native expanded keys own consumption. Existing buffer tracking publishes this CPU
|
||||
// write to future GPU readers; FillBuffer can fault and must run outside the texture lock.
|
||||
if (desc.type != BindingType::VideoOut) {
|
||||
m_buffer_cache.FillBuffer(address, slice_size, UINT32_MAX, false);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void TextureCache::RefreshImage(ImageId id) {
|
||||
TrackImage(id);
|
||||
auto& image = m_slot_images[id];
|
||||
if (image.depth_id &&
|
||||
(m_slot_images[image.depth_id].info.metadata.stencil_compressed ||
|
||||
m_slot_images[image.depth_id].info.samples != 1)) {
|
||||
return;
|
||||
}
|
||||
TrackImage(id);
|
||||
if (image.IsMaybeCpuDirty()) {
|
||||
const auto hash = image.HashGuestEdges();
|
||||
if (image.NeedsMaybeCpuHash()) {
|
||||
@@ -1209,7 +1245,7 @@ void TextureCache::RefreshImage(ImageId id) {
|
||||
InitializeImage(id);
|
||||
}
|
||||
|
||||
void TextureCache::AssociateStencil(ImageId depth_id, GuestRange stencil) {
|
||||
ImageId TextureCache::AssociateStencil(ImageId depth_id, GuestRange stencil) {
|
||||
if (!stencil.Valid()) {
|
||||
EXIT("TextureCache: invalid stencil association range\n");
|
||||
}
|
||||
@@ -1221,7 +1257,8 @@ void TextureCache::AssociateStencil(ImageId depth_id, GuestRange stencil) {
|
||||
ImageId association {};
|
||||
for (const auto id: FindImagesInRegion(stencil.address, stencil.size, false)) {
|
||||
const auto owner = m_slot_images.try_get(id);
|
||||
if (owner != nullptr && owner->info.data.address == stencil.address) {
|
||||
if (owner != nullptr && owner->info.data == stencil &&
|
||||
owner->info.extent == depth.info.extent) {
|
||||
association = id;
|
||||
}
|
||||
}
|
||||
@@ -1234,6 +1271,7 @@ void TextureCache::AssociateStencil(ImageId depth_id, GuestRange stencil) {
|
||||
auto& record = m_slot_images[association];
|
||||
TouchImage(record);
|
||||
record.depth_id = depth_id;
|
||||
return association;
|
||||
}
|
||||
|
||||
ImageId TextureCache::FindImage(ImageDesc& desc, bool exact_format) {
|
||||
@@ -1246,6 +1284,7 @@ ImageId TextureCache::FindImage(ImageDesc& desc, bool exact_format) {
|
||||
std::scoped_lock lock {m_lock};
|
||||
return GetNullImage(desc);
|
||||
}
|
||||
const auto metadata_base_layer = desc.view_info.base_layer;
|
||||
|
||||
ImageId result {};
|
||||
{
|
||||
@@ -1294,16 +1333,6 @@ ImageId TextureCache::FindImage(ImageDesc& desc, bool exact_format) {
|
||||
}
|
||||
}
|
||||
auto& image = m_slot_images[result];
|
||||
if (desc.type == BindingType::VideoOut &&
|
||||
desc.info.metadata.compression != VideoOutCompression::Uncompressed) {
|
||||
const bool guest_dirty = image.IsBufferModified() || image.IsCpuDirty();
|
||||
const bool native_current =
|
||||
(image.usage.render_target || image.IsGpuModified()) && !guest_dirty;
|
||||
if (!native_current) {
|
||||
EXIT("TextureCache: compressed video-out read requires clean native GPU "
|
||||
"contents\n");
|
||||
}
|
||||
}
|
||||
if (view_mip >= 0) {
|
||||
desc.view_info.base_level = static_cast<uint32_t>(view_mip);
|
||||
}
|
||||
@@ -1313,6 +1342,19 @@ ImageId TextureCache::FindImage(ImageDesc& desc, bool exact_format) {
|
||||
image.tick_accessed_last = m_scheduler.CurrentTick();
|
||||
TouchImage(image);
|
||||
}
|
||||
MaterializeDccClear(result, desc, metadata_base_layer);
|
||||
if (desc.type == BindingType::VideoOut &&
|
||||
desc.info.metadata.compression != VideoOutCompression::Uncompressed) {
|
||||
std::scoped_lock lock {m_lock};
|
||||
const auto& image = m_slot_images[result];
|
||||
const bool guest_dirty = image.IsBufferModified() || image.IsCpuDirty();
|
||||
const bool native_current =
|
||||
(image.usage.render_target || image.IsGpuModified()) && !guest_dirty;
|
||||
if (!native_current) {
|
||||
EXIT("TextureCache: compressed video-out read requires clean native GPU "
|
||||
"contents\n");
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
@@ -1376,8 +1418,20 @@ vk::ImageView TextureCache::FindTexture(ImageId id, const ImageDesc& desc) {
|
||||
image.MarkGpuModified();
|
||||
}
|
||||
if (!image.info.data.Empty()) {
|
||||
PrepareDccClear(id, desc);
|
||||
RefreshImage(id);
|
||||
if (image.info.HasStencil() &&
|
||||
desc.info.data.address >= image.info.stencil.address &&
|
||||
desc.info.data.End() <= image.info.stencil.End()) {
|
||||
for (const auto stencil_id:
|
||||
FindImagesInRegion(image.info.stencil.address, image.info.stencil.size, false)) {
|
||||
const auto* stencil = m_slot_images.try_get(stencil_id);
|
||||
if (stencil != nullptr && stencil->depth_id == id &&
|
||||
stencil->info.data == image.info.stencil) {
|
||||
RefreshImage(stencil_id);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
switch (desc.type) {
|
||||
case BindingType::Texture: break;
|
||||
@@ -1406,7 +1460,6 @@ vk::ImageView TextureCache::FindRenderTarget(ImageId id, const ImageDesc& desc)
|
||||
TouchImage(image);
|
||||
image.MarkGpuModified();
|
||||
image.usage.render_target = true;
|
||||
PrepareDccClear(id, desc);
|
||||
RefreshImage(id);
|
||||
CommitGpuWrite(image);
|
||||
TrackImageDownload(id, image);
|
||||
@@ -1427,23 +1480,17 @@ vk::ImageView TextureCache::FindDepthTarget(ImageId id, const ImageDesc& desc) {
|
||||
TouchImage(image);
|
||||
image.MarkGpuModified();
|
||||
image.usage.depth_target = true;
|
||||
RefreshImage(id);
|
||||
image.info.stencil = desc.info.stencil;
|
||||
image.info.metadata = desc.info.metadata;
|
||||
if (desc.info.HasMetadata()) {
|
||||
image.info.metadata = desc.info.metadata;
|
||||
auto [metadata, inserted] =
|
||||
m_surface_metas.try_emplace(desc.info.metadata.range.address,
|
||||
MetaDataInfo {.type = MetaDataInfo::Type::HTile,
|
||||
.clear_mask = image.info.htile_clear_mask});
|
||||
if (!inserted && metadata->second.type != MetaDataInfo::Type::HTile) {
|
||||
// PS5 allocations can reuse DCC storage as HTile while the old color image is cached.
|
||||
// The depth binding defines the new type; incompatible fill state cannot carry over.
|
||||
metadata->second = {.type = MetaDataInfo::Type::HTile,
|
||||
.clear_mask = image.info.htile_clear_mask};
|
||||
}
|
||||
m_surface_metas.emplace(desc.info.metadata.range.address,
|
||||
MetaDataInfo {.type = MetaDataInfo::Type::HTile,
|
||||
.clear_mask = image.info.htile_clear_mask});
|
||||
}
|
||||
RefreshImage(id);
|
||||
CommitGpuWrite(image);
|
||||
if (desc.info.HasStencil()) {
|
||||
AssociateStencil(id, desc.info.stencil);
|
||||
RefreshImage(AssociateStencil(id, desc.info.stencil));
|
||||
}
|
||||
const auto view = image.FindView(desc.view_info);
|
||||
NameImageBinding(m_graphics, image, view, desc.type, desc.view_info);
|
||||
@@ -1458,11 +1505,16 @@ void TextureCache::MarkGpuWritten(ImageId id) {
|
||||
}
|
||||
TrackImage(id);
|
||||
CommitGpuWrite(image);
|
||||
if (image.info.HasStencil()) {
|
||||
const auto stencil_id = AssociateStencil(id, image.info.stencil);
|
||||
TrackImage(stencil_id);
|
||||
CommitGpuWrite(m_slot_images[stencil_id]);
|
||||
}
|
||||
}
|
||||
|
||||
void TextureCache::CommitGpuWrite(Image& image) {
|
||||
if (image.depth_id || image.backing.image == nullptr) {
|
||||
EXIT("TextureCache: stencil association cannot own image contents\n");
|
||||
if (!image.depth_id && image.backing.image == nullptr) {
|
||||
EXIT("TextureCache: GPU writes require a native image or stencil association\n");
|
||||
}
|
||||
image.ClearBufferModified();
|
||||
if (image.IsCpuDirty()) {
|
||||
@@ -1527,12 +1579,12 @@ bool TextureCache::ClearImageFromBuffer(CommandBuffer& command, uint64_t address
|
||||
}
|
||||
clear.depthStencil.stencil = stencil_clear;
|
||||
}
|
||||
ClearImage(command, selected,
|
||||
ClearImage(command, selected, image.backing.format,
|
||||
{aspect, 0, image.info.resources.levels, 0, image.info.TransferLayers()}, clear);
|
||||
return true;
|
||||
}
|
||||
|
||||
void TextureCache::ClearImage(CommandBuffer& command, ImageId id,
|
||||
void TextureCache::ClearImage(CommandBuffer& command, ImageId id, vk::Format format,
|
||||
const vk::ImageSubresourceRange& range, const vk::ClearValue& clear) {
|
||||
auto& image = m_slot_images[id];
|
||||
const auto aspects = image.info.IsDepth() ? ImageViewOps::DepthAspectMask(image.backing.format)
|
||||
@@ -1546,9 +1598,10 @@ void TextureCache::ClearImage(CommandBuffer& command, ImageId id,
|
||||
range.layerCount == 0 || range.baseArrayLayer >= layers ||
|
||||
range.layerCount > layers - range.baseArrayLayer ||
|
||||
(range.aspectMask & aspects) != range.aspectMask);
|
||||
const bool full_image = range.aspectMask == aspects && range.baseMipLevel == 0 &&
|
||||
range.levelCount == image.info.resources.levels &&
|
||||
range.baseArrayLayer == 0 && range.layerCount == layers;
|
||||
const bool full_subresources = range.baseMipLevel == 0 &&
|
||||
range.levelCount == image.info.resources.levels &&
|
||||
range.baseArrayLayer == 0 && range.layerCount == layers;
|
||||
const bool full_image = range.aspectMask == aspects && full_subresources;
|
||||
TrackImage(id);
|
||||
if (!full_image && (image.IsBufferModified() || image.IsCpuDirty())) {
|
||||
InitializeImage(id);
|
||||
@@ -1556,12 +1609,22 @@ void TextureCache::ClearImage(CommandBuffer& command, ImageId id,
|
||||
EXIT("TextureCache: image clear retained guest ownership\n");
|
||||
}
|
||||
}
|
||||
if (image.info.HasStencil() && (range.aspectMask & vk::ImageAspectFlagBits::eStencil)) {
|
||||
const auto stencil_id = AssociateStencil(id, image.info.stencil);
|
||||
if (!full_subresources) {
|
||||
RefreshImage(stencil_id);
|
||||
} else {
|
||||
TrackImage(stencil_id);
|
||||
CommitGpuWrite(m_slot_images[stencil_id]);
|
||||
}
|
||||
}
|
||||
command.EndRendering();
|
||||
if (image.info.IsVolume() && !full_image) {
|
||||
// Transfer clears use the backing format; aliased clears must encode through their view.
|
||||
if (format != image.backing.format || (image.info.IsVolume() && !full_image)) {
|
||||
EXIT_NOT_IMPLEMENTED(range.aspectMask != vk::ImageAspectFlagBits::eColor ||
|
||||
range.levelCount != 1);
|
||||
ImageViewInfo view {};
|
||||
view.format = image.backing.format;
|
||||
view.format = format;
|
||||
view.type = range.layerCount == 1 ? vk::ImageViewType::e2D : vk::ImageViewType::e2DArray;
|
||||
view.base_level = range.baseMipLevel;
|
||||
view.base_layer = range.baseArrayLayer;
|
||||
@@ -1627,7 +1690,7 @@ void TextureCache::DownloadDepth(Image& image, Buffer& destination, uint64_t des
|
||||
EXIT_NOT_IMPLEMENTED(transfer_slice > UINT64_MAX / layers);
|
||||
const uint64_t transfer_size = transfer_slice * layers;
|
||||
EXIT_NOT_IMPLEMENTED(guest_slice > full_slice_size);
|
||||
auto copies = BuildDepthCopies(info, full_slice_size);
|
||||
auto copies = BuildDepthCopies(info, full_slice_size, vk::ImageAspectFlagBits::eDepth);
|
||||
if (transfer_bytes == info.bytes_per_block) {
|
||||
if (!info.IsTiled()) {
|
||||
for (auto& copy: copies) {
|
||||
@@ -1820,7 +1883,7 @@ void TextureCache::InvalidateMemoryFromGPU(uint64_t address, uint64_t size) {
|
||||
std::scoped_lock lock {m_lock};
|
||||
for (const auto id: FindImagesInRegion(address, size, true)) {
|
||||
auto& image = m_slot_images[id];
|
||||
if (image.depth_id || !image.Overlaps(address, size)) {
|
||||
if (!image.Overlaps(address, size)) {
|
||||
continue;
|
||||
}
|
||||
if (image.IsGpuModified()) {
|
||||
@@ -1872,72 +1935,32 @@ void TextureCache::InvalidateCpuAliases(uint64_t address, uint64_t size) {
|
||||
bool TextureCache::IsMeta(uint64_t address) {
|
||||
std::scoped_lock lock {m_lock};
|
||||
const auto found = m_surface_metas.find(address);
|
||||
return found != m_surface_metas.end() && found->second.type != MetaDataInfo::Type::PendingDcc;
|
||||
return found != m_surface_metas.end();
|
||||
}
|
||||
|
||||
bool TextureCache::IsMetaCleared(uint64_t address, uint32_t slice, uint32_t* fill_value) {
|
||||
bool TextureCache::IsMetaCleared(uint64_t address, uint32_t slice) {
|
||||
std::scoped_lock lock {m_lock};
|
||||
const auto found = m_surface_metas.find(address);
|
||||
if (found == m_surface_metas.end() || found->second.type == MetaDataInfo::Type::PendingDcc ||
|
||||
slice >= 32) {
|
||||
if (found == m_surface_metas.end() || slice >= 32) {
|
||||
return false;
|
||||
}
|
||||
if (fill_value != nullptr) {
|
||||
*fill_value = found->second.fill_value;
|
||||
}
|
||||
return (found->second.clear_mask & (1u << slice)) != 0;
|
||||
}
|
||||
|
||||
bool TextureCache::ClearMeta(uint64_t address) {
|
||||
std::scoped_lock lock {m_lock};
|
||||
const auto found = m_surface_metas.find(address);
|
||||
if (found == m_surface_metas.end() || found->second.type == MetaDataInfo::Type::PendingDcc ||
|
||||
found->second.type == MetaDataInfo::Type::Dcc) {
|
||||
// Preserve the broad metadata-clear operation for CMask/FMask/HTile. DCC requires a
|
||||
// validated fill value, so an arbitrary compute write must not clear it.
|
||||
if (found == m_surface_metas.end()) {
|
||||
return false;
|
||||
}
|
||||
found->second.clear_mask = UINT32_MAX;
|
||||
return true;
|
||||
}
|
||||
|
||||
void TextureCache::TrackDccFill(uint64_t address, uint64_t size, uint32_t fill_value) {
|
||||
if (!GuestRange {address, size}.Valid()) {
|
||||
EXIT("TextureCache: invalid DCC fill range\n");
|
||||
}
|
||||
// DCC fills use a repeated byte code. Require all four bytes of the detected dword to agree,
|
||||
// and mark only recognized deferred-clear encodings as logically clear.
|
||||
const auto dcc_clear_mask = [fill_value] {
|
||||
const auto code = static_cast<uint8_t>(fill_value);
|
||||
if (fill_value != static_cast<uint32_t>(code) * 0x01010101u) {
|
||||
return 0u;
|
||||
}
|
||||
switch (code) {
|
||||
case 0x00:
|
||||
case 0x20:
|
||||
case 0x40:
|
||||
case 0x80:
|
||||
case 0xc0: return UINT32_MAX;
|
||||
default: return 0u;
|
||||
}
|
||||
}();
|
||||
std::scoped_lock lock {m_lock};
|
||||
// The guest dispatch still writes metadata. An unknown address remains PendingDcc until an
|
||||
// image descriptor confirms its role; never reinterpret CMask/FMask/HTile as DCC.
|
||||
const auto found = m_surface_metas.try_emplace(address).first;
|
||||
if (found->second.type == MetaDataInfo::Type::PendingDcc ||
|
||||
found->second.type == MetaDataInfo::Type::Dcc) {
|
||||
found->second.clear_mask = dcc_clear_mask;
|
||||
found->second.fill_value = fill_value;
|
||||
found->second.fill_size = size;
|
||||
}
|
||||
}
|
||||
|
||||
bool TextureCache::TouchMeta(uint64_t address, uint32_t slice, bool is_clear) {
|
||||
std::scoped_lock lock {m_lock};
|
||||
const auto found = m_surface_metas.find(address);
|
||||
if (found == m_surface_metas.end() || found->second.type == MetaDataInfo::Type::PendingDcc ||
|
||||
slice >= 32) {
|
||||
if (found == m_surface_metas.end() || slice >= 32) {
|
||||
return false;
|
||||
}
|
||||
if (is_clear) {
|
||||
@@ -1953,10 +1976,13 @@ void TextureCache::UnmapMemory(uint64_t address, uint64_t size) {
|
||||
EXIT("TextureCache: invalid unmap range\n");
|
||||
}
|
||||
std::scoped_lock lock {m_lock};
|
||||
const auto end = address + size;
|
||||
for (auto metadata = m_surface_metas.lower_bound(address);
|
||||
metadata != m_surface_metas.end() && metadata->first < end;) {
|
||||
metadata = m_surface_metas.erase(metadata);
|
||||
for (auto metadata = m_surface_metas.begin(); metadata != m_surface_metas.end();) {
|
||||
const auto base = metadata->first;
|
||||
if (base >= address && base < address + size) {
|
||||
metadata = m_surface_metas.erase(metadata);
|
||||
} else {
|
||||
++metadata;
|
||||
}
|
||||
}
|
||||
auto images = FindImagesInRegion(address, size, false);
|
||||
for (const auto id: images) {
|
||||
|
||||
+9
-18
@@ -64,11 +64,8 @@ public:
|
||||
[[nodiscard]] bool IsRegionGpuModified(uint64_t address, uint64_t size);
|
||||
|
||||
[[nodiscard]] bool IsMeta(uint64_t address);
|
||||
[[nodiscard]] bool IsMetaCleared(uint64_t address, uint32_t slice,
|
||||
uint32_t* fill_value = nullptr);
|
||||
[[nodiscard]] bool IsMetaCleared(uint64_t address, uint32_t slice);
|
||||
[[nodiscard]] bool ClearMeta(uint64_t address);
|
||||
// Record deferred DCC state while the original guest dispatch writes the metadata.
|
||||
void TrackDccFill(uint64_t address, uint64_t size, uint32_t fill_value);
|
||||
[[nodiscard]] bool TouchMeta(uint64_t address, uint32_t slice, bool is_clear);
|
||||
|
||||
void UnmapMemory(uint64_t address, uint64_t size);
|
||||
@@ -81,17 +78,10 @@ private:
|
||||
struct ImageDownload;
|
||||
|
||||
struct MetaDataInfo {
|
||||
// A guest metadata-fill dispatch may initialize DCC before its render target is bound.
|
||||
// PendingDcc retains that exact fill until an image binding classifies the address,
|
||||
// without exposing an unconfirmed buffer address to the normal metadata heuristics.
|
||||
// Keep all surface metadata in one entry so CMask/FMask can be
|
||||
// registered beside HTile and DCC without introducing parallel tracking paths.
|
||||
enum class Type : uint8_t { PendingDcc, CMask, FMask, HTile, Dcc };
|
||||
enum class Type : uint8_t { CMask, FMask, HTile };
|
||||
|
||||
Type type = Type::PendingDcc;
|
||||
uint32_t clear_mask = 0;
|
||||
uint32_t fill_value = 0xffffffffu;
|
||||
uint64_t fill_size = 0;
|
||||
Type type;
|
||||
uint32_t clear_mask = UINT32_MAX;
|
||||
};
|
||||
|
||||
struct OverlapResult {
|
||||
@@ -149,7 +139,8 @@ private:
|
||||
ImageId cached);
|
||||
[[nodiscard]] ImageId ExpandImage(const ImageInfo& info, ImageId source);
|
||||
void RefreshImage(ImageId id);
|
||||
void PrepareDccClear(ImageId id, const ImageDesc& desc);
|
||||
void MaterializeDccClear(ImageId id, const ImageDesc& desc,
|
||||
uint32_t metadata_base_layer);
|
||||
void InitializeImage(ImageId id);
|
||||
[[nodiscard]] TextureTransfer
|
||||
BuildTextureTransfer(const Image& image, BindingType binding, TransferDirection direction) const;
|
||||
@@ -160,13 +151,13 @@ private:
|
||||
void DownloadDepth(Image& image, Buffer& destination, uint64_t destination_offset);
|
||||
void CommitGpuWrite(Image& image);
|
||||
// Caller holds m_lock. Volume layer ranges select depth slices.
|
||||
void ClearImage(CommandBuffer& command, ImageId id, const vk::ImageSubresourceRange& range,
|
||||
const vk::ClearValue& clear);
|
||||
void ClearImage(CommandBuffer& command, ImageId id, vk::Format format,
|
||||
const vk::ImageSubresourceRange& range, const vk::ClearValue& clear);
|
||||
void PrepareImageCopy(Image& image);
|
||||
void RefreshCopySource(ImageId id);
|
||||
[[nodiscard]] bool CopyD16(Image& destination, Image& source);
|
||||
void CopyImage(ImageId destination, ImageId source);
|
||||
void AssociateStencil(ImageId depth, GuestRange stencil);
|
||||
[[nodiscard]] ImageId AssociateStencil(ImageId depth, GuestRange stencil);
|
||||
void CopyImageMip(ImageId destination, ImageId source, uint32_t mip, uint32_t layer);
|
||||
void ValidateImageDesc(const ImageDesc& desc) const;
|
||||
|
||||
|
||||
@@ -209,17 +209,16 @@ static void RtCheck(const HW::RenderTarget& rt) {
|
||||
}
|
||||
if (rt.info.fmask_compression_enable) {
|
||||
EXIT_NOT_IMPLEMENTED(rt.attrib.num_samples == 0 && rt.attrib.num_fragments == 0);
|
||||
// Native MSAA stores expanded samples, independent of FMASK metadata compression.
|
||||
static bool logged = false;
|
||||
if (!logged) {
|
||||
LOGF("RenderTarget: using native Vulkan MSAA without guest FMASK metadata, "
|
||||
LOGF("RenderTarget: using expanded native Vulkan MSAA samples, "
|
||||
"fmask=0x%016" PRIx64 "\n",
|
||||
rt.fmask.addr);
|
||||
logged = true;
|
||||
}
|
||||
}
|
||||
|
||||
EXIT_NOT_IMPLEMENTED(rt.info.fmask_data_compression_disable != false);
|
||||
|
||||
if (rt.info.cmask_fast_clear_enable || rt.info.dcc_compression_enable) {
|
||||
static bool logged = false;
|
||||
if (!logged) {
|
||||
|
||||
@@ -19,6 +19,7 @@
|
||||
#include "graphics/host_gpu/vulkanCommon.h"
|
||||
|
||||
#include <algorithm>
|
||||
#include <array>
|
||||
#include <atomic>
|
||||
#include <cmath>
|
||||
#include <cstdarg>
|
||||
@@ -45,8 +46,12 @@ static vk::StencilOp ConvertStencilOp(uint8_t value, uint8_t write_mask, uint8_t
|
||||
switch (static_cast<Prospero::StencilOp>(value)) {
|
||||
case Prospero::StencilOp::kKeep: return vk::StencilOp::eKeep;
|
||||
case Prospero::StencilOp::kZero: return vk::StencilOp::eZero;
|
||||
case Prospero::StencilOp::kReplaceTest:
|
||||
case Prospero::StencilOp::kReplaceOp: return vk::StencilOp::eReplace;
|
||||
case Prospero::StencilOp::kReplaceTest: return vk::StencilOp::eReplace;
|
||||
case Prospero::StencilOp::kReplaceOp:
|
||||
if ((op_value & write_mask) == 0) {
|
||||
return vk::StencilOp::eZero;
|
||||
}
|
||||
return vk::StencilOp::eReplace;
|
||||
case Prospero::StencilOp::kAddClamp: return vk::StencilOp::eIncrementAndClamp;
|
||||
case Prospero::StencilOp::kSubClamp: return vk::StencilOp::eDecrementAndClamp;
|
||||
case Prospero::StencilOp::kInvert: return vk::StencilOp::eInvert;
|
||||
@@ -66,9 +71,38 @@ static vk::StencilOp ConvertStencilOp(uint8_t value, uint8_t write_mask, uint8_t
|
||||
}
|
||||
}
|
||||
|
||||
static bool UsesStencilOpValue(uint8_t fail, uint8_t pass, uint8_t depth_fail) {
|
||||
constexpr auto replace_op = static_cast<uint8_t>(Prospero::StencilOp::kReplaceOp);
|
||||
return fail == replace_op || pass == replace_op || depth_fail == replace_op;
|
||||
static vk::StencilOpState ConvertStencilState(
|
||||
uint8_t compare, const std::array<uint8_t, 3>& operations, uint8_t op_value,
|
||||
const vk::StencilOpState& state) {
|
||||
const auto test_value = state.reference;
|
||||
auto reference = test_value;
|
||||
auto required_bits = state.compareMask;
|
||||
if (compare == static_cast<uint8_t>(vk::CompareOp::eAlways) ||
|
||||
compare == static_cast<uint8_t>(vk::CompareOp::eNever)) {
|
||||
required_bits = 0;
|
||||
}
|
||||
std::array<vk::StencilOp, 3> converted {};
|
||||
for (size_t i = 0; i < operations.size(); i++) {
|
||||
converted[i] = ConvertStencilOp(operations[i], state.writeMask, op_value);
|
||||
if (converted[i] != vk::StencilOp::eReplace) {
|
||||
continue;
|
||||
}
|
||||
auto replacement = test_value;
|
||||
if (static_cast<Prospero::StencilOp>(operations[i]) == Prospero::StencilOp::kReplaceOp) {
|
||||
replacement = op_value;
|
||||
}
|
||||
if (((reference ^ replacement) & required_bits & state.writeMask) != 0) {
|
||||
DepthFatal("unsupported stencil replacement: compare=%u, compare mask=0x%02" PRIx32
|
||||
", write mask=0x%02" PRIx32 ", operation value=0x%02" PRIx8
|
||||
", test value=0x%02" PRIx32,
|
||||
compare, state.compareMask, state.writeMask, op_value, test_value);
|
||||
}
|
||||
// Vulkan shares one reference between comparison and every replacement on this face.
|
||||
reference = (reference & ~state.writeMask) | (replacement & state.writeMask);
|
||||
required_bits |= state.writeMask;
|
||||
}
|
||||
return {converted[0], converted[1], converted[2], static_cast<vk::CompareOp>(compare),
|
||||
state.compareMask, state.writeMask, reference};
|
||||
}
|
||||
|
||||
[[nodiscard]] static vk::Format ResolveHostDepthAttachmentFormat(const CommandBuffer& buffer,
|
||||
@@ -125,8 +159,8 @@ static TextureCache::ImageDesc MakeDepthTargetDesc(const CommandBuffer& buffer,
|
||||
DepthFatal("invalid depth view: base=%u last=%u", z.depth_view.slice_start,
|
||||
z.depth_view.slice_max);
|
||||
}
|
||||
if (z.z_info.expclear_enabled || z.stencil_info.expclear_enabled ||
|
||||
z.z_info.partially_resident ||
|
||||
// EXPCLEAR permits an HTile acceleration state; the host attachment is already expanded.
|
||||
if (z.z_info.partially_resident ||
|
||||
z.stencil_info.partially_resident || z.z_info.max_mip_level != 0 ||
|
||||
z.depth_view.current_mip_level != 0 || unsupported_shading_rate_encoding ||
|
||||
depth_address == 0 || (depth_address & 0xffffu) != 0) {
|
||||
@@ -296,31 +330,20 @@ void RenderExecutor::ResolveRenderDepthTarget(CommandBuffer& buffer, RenderDepth
|
||||
const uint8_t back_write_mask = stencil_ops_disabled ? 0 : sm.stencil_writemask_bf;
|
||||
if (dc.stencilfunc > static_cast<uint8_t>(vk::CompareOp::eAlways) ||
|
||||
(dc.backface_enable &&
|
||||
dc.stencilfunc_bf > static_cast<uint8_t>(vk::CompareOp::eAlways)) ||
|
||||
(front_write_mask != 0 &&
|
||||
UsesStencilOpValue(sc.stencil_fail, sc.stencil_zpass, sc.stencil_zfail) &&
|
||||
sm.stencil_opval != sm.stencil_testval) ||
|
||||
(dc.backface_enable && back_write_mask != 0 &&
|
||||
UsesStencilOpValue(sc.stencil_fail_bf, sc.stencil_zpass_bf, sc.stencil_zfail_bf) &&
|
||||
sm.stencil_opval_bf != sm.stencil_testval_bf)) {
|
||||
DepthFatal("unsupported stencil compare or replacement state");
|
||||
dc.stencilfunc_bf > static_cast<uint8_t>(vk::CompareOp::eAlways))) {
|
||||
DepthFatal("unsupported stencil compare state");
|
||||
}
|
||||
r.stencil_static_front = {
|
||||
ConvertStencilOp(sc.stencil_fail, front_write_mask, sm.stencil_opval),
|
||||
ConvertStencilOp(sc.stencil_zpass, front_write_mask, sm.stencil_opval),
|
||||
ConvertStencilOp(sc.stencil_zfail, front_write_mask, sm.stencil_opval),
|
||||
static_cast<vk::CompareOp>(dc.stencilfunc)};
|
||||
r.stencil_dynamic_front = {sm.stencil_mask, front_write_mask, sm.stencil_testval};
|
||||
r.stencil_front = ConvertStencilState(
|
||||
dc.stencilfunc, {sc.stencil_fail, sc.stencil_zpass, sc.stencil_zfail},
|
||||
sm.stencil_opval, {.compareMask = sm.stencil_mask, .writeMask = front_write_mask,
|
||||
.reference = sm.stencil_testval});
|
||||
if (dc.backface_enable) {
|
||||
r.stencil_static_back = {
|
||||
ConvertStencilOp(sc.stencil_fail_bf, back_write_mask, sm.stencil_opval_bf),
|
||||
ConvertStencilOp(sc.stencil_zpass_bf, back_write_mask, sm.stencil_opval_bf),
|
||||
ConvertStencilOp(sc.stencil_zfail_bf, back_write_mask, sm.stencil_opval_bf),
|
||||
static_cast<vk::CompareOp>(dc.stencilfunc_bf)};
|
||||
r.stencil_dynamic_back = {sm.stencil_mask_bf, back_write_mask, sm.stencil_testval_bf};
|
||||
r.stencil_back = ConvertStencilState(
|
||||
dc.stencilfunc_bf, {sc.stencil_fail_bf, sc.stencil_zpass_bf, sc.stencil_zfail_bf},
|
||||
sm.stencil_opval_bf, {.compareMask = sm.stencil_mask_bf, .writeMask = back_write_mask,
|
||||
.reference = sm.stencil_testval_bf});
|
||||
} else {
|
||||
r.stencil_static_back = r.stencil_static_front;
|
||||
r.stencil_dynamic_back = r.stencil_dynamic_front;
|
||||
r.stencil_back = r.stencil_front;
|
||||
}
|
||||
}
|
||||
auto& cache = m_context.GetTextureCache();
|
||||
@@ -406,14 +429,13 @@ vk::ImageAspectFlags RenderDepthInfo::AttachmentWriteAspects() const {
|
||||
return writes;
|
||||
}
|
||||
|
||||
const auto face_writes = [&](const PipelineStencilStaticState& state,
|
||||
const PipelineStencilDynamicState& dynamic) {
|
||||
if (dynamic.writeMask == 0) {
|
||||
const auto face_writes = [&](const vk::StencilOpState& state) {
|
||||
if (state.writeMask == 0) {
|
||||
return false;
|
||||
}
|
||||
bool can_pass = state.compareOp != vk::CompareOp::eNever;
|
||||
bool can_fail = state.compareOp != vk::CompareOp::eAlways;
|
||||
if (dynamic.compareMask == 0) {
|
||||
if (state.compareMask == 0) {
|
||||
switch (state.compareOp) {
|
||||
case vk::CompareOp::eEqual:
|
||||
case vk::CompareOp::eLessOrEqual:
|
||||
@@ -439,8 +461,7 @@ vk::ImageAspectFlags RenderDepthInfo::AttachmentWriteAspects() const {
|
||||
(can_pass && depth_fail && state.depthFailOp != vk::StencilOp::eKeep);
|
||||
};
|
||||
if (stencil_clear_enable ||
|
||||
(stencil_test_enable && (face_writes(stencil_static_front, stencil_dynamic_front) ||
|
||||
face_writes(stencil_static_back, stencil_dynamic_back)))) {
|
||||
(stencil_test_enable && (face_writes(stencil_front) || face_writes(stencil_back)))) {
|
||||
writes |= vk::ImageAspectFlagBits::eStencil;
|
||||
}
|
||||
return writes;
|
||||
|
||||
@@ -34,10 +34,8 @@ struct RenderDepthInfo {
|
||||
bool stencil_clear_enable = false;
|
||||
uint8_t stencil_clear_value = 0;
|
||||
bool stencil_test_enable = false;
|
||||
PipelineStencilStaticState stencil_static_front;
|
||||
PipelineStencilStaticState stencil_static_back;
|
||||
PipelineStencilDynamicState stencil_dynamic_front;
|
||||
PipelineStencilDynamicState stencil_dynamic_back;
|
||||
vk::StencilOpState stencil_front;
|
||||
vk::StencilOpState stencil_back;
|
||||
ImageId image_id;
|
||||
|
||||
[[nodiscard]] vk::ImageAspectFlags AttachmentWriteAspects() const;
|
||||
|
||||
@@ -10,7 +10,6 @@
|
||||
|
||||
#include <algorithm>
|
||||
#include <array>
|
||||
#include <iterator>
|
||||
|
||||
namespace Libs::Graphics {
|
||||
|
||||
@@ -37,9 +36,8 @@ BlitHelper::BlitHelper(GraphicContext& graphics, CommandScheduler& scheduler)
|
||||
m_graphics.device.createPipelineLayout(&layout_info, nullptr, &m_pipeline_layout),
|
||||
"create BlitHelper pipeline layout");
|
||||
|
||||
m_vertex_shader = CreateShader(GPU_BLIT_FS_TRIANGLE_SPV, std::size(GPU_BLIT_FS_TRIANGLE_SPV));
|
||||
m_fragment_shader =
|
||||
CreateShader(GPU_BLIT_COLOR_TO_MS_DEPTH_SPV, std::size(GPU_BLIT_COLOR_TO_MS_DEPTH_SPV));
|
||||
m_vertex_shader = CompileSPV(GPU_BLIT_FS_TRIANGLE_SPV, m_graphics.device);
|
||||
m_fragment_shader = CompileSPV(GPU_BLIT_COLOR_TO_MS_DEPTH_SPV, m_graphics.device);
|
||||
}
|
||||
|
||||
BlitHelper::~BlitHelper() {
|
||||
@@ -60,17 +58,6 @@ BlitHelper::~BlitHelper() {
|
||||
}
|
||||
}
|
||||
|
||||
vk::ShaderModule BlitHelper::CreateShader(const uint32_t* code, size_t words) const {
|
||||
EXIT_IF(code == nullptr || words == 0);
|
||||
vk::ShaderModuleCreateInfo create {};
|
||||
create.codeSize = words * sizeof(uint32_t);
|
||||
create.pCode = code;
|
||||
vk::ShaderModule module = nullptr;
|
||||
RequireVulkanSuccess(m_graphics.device.createShaderModule(&create, nullptr, &module),
|
||||
"create BlitHelper shader module");
|
||||
return module;
|
||||
}
|
||||
|
||||
vk::Pipeline BlitHelper::GetPipeline(PipelineKey key) {
|
||||
const auto cached = std::ranges::find(m_pipelines, key, &Pipeline::key);
|
||||
if (cached != m_pipelines.end()) {
|
||||
|
||||
@@ -37,8 +37,7 @@ private:
|
||||
vk::Pipeline handle = nullptr;
|
||||
};
|
||||
|
||||
[[nodiscard]] vk::ShaderModule CreateShader(const uint32_t* code, size_t words) const;
|
||||
[[nodiscard]] vk::Pipeline GetPipeline(PipelineKey key);
|
||||
[[nodiscard]] vk::Pipeline GetPipeline(PipelineKey key);
|
||||
|
||||
GraphicContext& m_graphics;
|
||||
CommandScheduler& m_scheduler;
|
||||
|
||||
@@ -94,9 +94,24 @@ struct ImageInfo {
|
||||
bytes_per_block == other.bytes_per_block;
|
||||
}
|
||||
[[nodiscard]] int32_t MipOf(const ImageInfo& container) const noexcept {
|
||||
if (!IsCompatible(container) || tile_mode != container.tile_mode || resources.levels != 1 ||
|
||||
container.resources.layers == 0 ||
|
||||
container.resources.levels > container.mip_layout.size()) {
|
||||
if (container.resources.levels > container.mip_layout.size()) {
|
||||
return -1;
|
||||
}
|
||||
for (uint32_t level = 0; level < container.resources.levels; level++) {
|
||||
if (SliceOf(container, static_cast<int32_t>(level)) >= 0) {
|
||||
return static_cast<int32_t>(level);
|
||||
}
|
||||
}
|
||||
return -1;
|
||||
}
|
||||
[[nodiscard]] int32_t SliceOf(const ImageInfo& container, int32_t mip) const noexcept {
|
||||
if (!IsCompatible(container) || tile_mode != container.tile_mode || type != container.type ||
|
||||
IsVolume() || resources.levels != 1 || resources.layers == 0 ||
|
||||
container.resources.layers == 0 || mip < 0 ||
|
||||
static_cast<uint32_t>(mip) >= container.resources.levels ||
|
||||
container.resources.levels > container.mip_layout.size() ||
|
||||
!data.Valid() || !container.data.Valid() || data.address < container.data.address ||
|
||||
data.End() > container.data.End()) {
|
||||
return -1;
|
||||
}
|
||||
if (HasStencil() != container.HasStencil() ||
|
||||
@@ -105,75 +120,36 @@ struct ImageInfo {
|
||||
return -1;
|
||||
}
|
||||
|
||||
int32_t mip = -1;
|
||||
for (uint32_t level = 0; level < container.resources.levels; level++) {
|
||||
const auto& layout = container.mip_layout[level];
|
||||
if (layout.size == 0 || layout.size % container.resources.layers != 0 ||
|
||||
container.data.address > UINT64_MAX - layout.offset) {
|
||||
continue;
|
||||
}
|
||||
const auto mip_base = container.data.address + layout.offset;
|
||||
const auto slice_size = layout.size / container.resources.layers;
|
||||
if (slice_size == 0 || mip_base > UINT64_MAX - layout.size) {
|
||||
continue;
|
||||
}
|
||||
const auto mip_end = mip_base + layout.size;
|
||||
if (data.address >= mip_base && data.address < mip_end &&
|
||||
(data.address - mip_base) % slice_size == 0) {
|
||||
mip = static_cast<int32_t>(level);
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (mip < 0) {
|
||||
const auto level = static_cast<uint32_t>(mip);
|
||||
const auto& layout = container.mip_layout[level];
|
||||
if (extent.width != std::max(container.extent.width >> level, 1u) ||
|
||||
extent.height != std::max(container.extent.height >> level, 1u) ||
|
||||
extent.depth != container.extent.depth || mip_layout[0].pitch != layout.pitch ||
|
||||
mip_layout[0].height != layout.height || layout.size == 0 ||
|
||||
layout.size % container.resources.layers != 0 ||
|
||||
container.data.size % container.resources.layers != 0 ||
|
||||
data.size % resources.layers != 0) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
const auto level = static_cast<uint32_t>(mip);
|
||||
if (extent.width != std::max(container.extent.width >> level, 1u) ||
|
||||
extent.height != std::max(container.extent.height >> level, 1u)) {
|
||||
// PS5 block slices contain the entire reversed mip chain.
|
||||
const auto slice_stride = container.data.size / container.resources.layers;
|
||||
const auto child_stride = data.size / resources.layers;
|
||||
if (child_stride != layout.size / container.resources.layers ||
|
||||
layout.offset >= slice_stride || child_stride > slice_stride - layout.offset ||
|
||||
(resources.layers > 1 && child_stride != slice_stride)) {
|
||||
return -1;
|
||||
}
|
||||
const auto mip_depth = std::max(container.extent.depth >> level, 1u);
|
||||
if (container.type == Prospero::ImageType::kColor3D &&
|
||||
type == Prospero::ImageType::kColor2D) {
|
||||
if (resources.layers != mip_depth) {
|
||||
return -1;
|
||||
}
|
||||
} else if (type != container.type) {
|
||||
const auto address_delta = data.address - container.data.address;
|
||||
if (address_delta < layout.offset || (address_delta - layout.offset) % slice_stride != 0) {
|
||||
return -1;
|
||||
}
|
||||
return mip;
|
||||
}
|
||||
[[nodiscard]] int32_t SliceOf(const ImageInfo& container, int32_t mip) const noexcept {
|
||||
if (!IsCompatible(container) || type != container.type || mip < 0 ||
|
||||
static_cast<uint32_t>(mip) >= container.resources.levels ||
|
||||
container.resources.levels > container.mip_layout.size() ||
|
||||
container.resources.layers == 0 || data.size == 0) {
|
||||
const auto layer = (address_delta - layout.offset) / slice_stride;
|
||||
if (layer > INT32_MAX || layer >= container.resources.layers ||
|
||||
resources.layers > container.resources.layers - layer) {
|
||||
return -1;
|
||||
}
|
||||
const auto level = static_cast<uint32_t>(mip);
|
||||
if (extent.width != std::max(container.extent.width >> level, 1u) ||
|
||||
extent.height != std::max(container.extent.height >> level, 1u)) {
|
||||
return -1;
|
||||
}
|
||||
const auto& layout = container.mip_layout[level];
|
||||
if (layout.size == 0 || layout.size % container.resources.layers != 0 ||
|
||||
container.data.address > UINT64_MAX - layout.offset) {
|
||||
return -1;
|
||||
}
|
||||
const auto slice_size = layout.size / container.resources.layers;
|
||||
if (slice_size == 0 || data.size % slice_size != 0) {
|
||||
return -1;
|
||||
}
|
||||
const auto mip_base = container.data.address + layout.offset;
|
||||
if (data.address < mip_base) {
|
||||
return -1;
|
||||
}
|
||||
const auto address_delta = data.address - mip_base;
|
||||
if (address_delta % data.size != 0 || address_delta / data.size > INT32_MAX) {
|
||||
return -1;
|
||||
}
|
||||
return static_cast<int32_t>(address_delta / data.size);
|
||||
return static_cast<int32_t>(layer);
|
||||
}
|
||||
};
|
||||
|
||||
@@ -183,6 +159,7 @@ struct ImageViewInfo {
|
||||
vk::ImageAspectFlags aspect = vk::ImageAspectFlagBits::eColor;
|
||||
uint32_t base_level = 0;
|
||||
uint32_t level_count = 1;
|
||||
uint32_t min_lod = 0; // U4.8 clamp relative to base_level.
|
||||
uint32_t base_layer = 0;
|
||||
uint32_t layer_count = 1;
|
||||
vk::ComponentMapping mapping = {};
|
||||
@@ -191,9 +168,10 @@ struct ImageViewInfo {
|
||||
[[nodiscard]] bool operator==(const ImageViewInfo& rhs) const noexcept {
|
||||
return format == rhs.format && type == rhs.type && aspect == rhs.aspect &&
|
||||
base_level == rhs.base_level && level_count == rhs.level_count &&
|
||||
base_layer == rhs.base_layer && layer_count == rhs.layer_count &&
|
||||
mapping.r == rhs.mapping.r && mapping.g == rhs.mapping.g &&
|
||||
mapping.b == rhs.mapping.b && mapping.a == rhs.mapping.a && usage == rhs.usage;
|
||||
min_lod == rhs.min_lod && base_layer == rhs.base_layer &&
|
||||
layer_count == rhs.layer_count && mapping.r == rhs.mapping.r &&
|
||||
mapping.g == rhs.mapping.g && mapping.b == rhs.mapping.b &&
|
||||
mapping.a == rhs.mapping.a && usage == rhs.usage;
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
@@ -362,6 +362,12 @@ vk::ImageView Image::FindView(const ImageViewInfo& view_info) {
|
||||
if (!is_storage) {
|
||||
usage.usage &= ~vk::ImageUsageFlagBits::eStorage;
|
||||
}
|
||||
vk::ImageViewMinLodCreateInfoEXT min_lod {};
|
||||
if (normalized.min_lod != 0) {
|
||||
min_lod.minLod = static_cast<float>(normalized.base_level) +
|
||||
static_cast<float>(normalized.min_lod) / 256.0f;
|
||||
usage.pNext = &min_lod;
|
||||
}
|
||||
vk::ImageViewCreateInfo create {};
|
||||
create.pNext = &usage;
|
||||
create.image = image.image;
|
||||
|
||||
@@ -260,12 +260,8 @@ vk::Pipeline TileManager::GetPipeline(uint32_t slot) {
|
||||
const uint32_t values[] {1u << element_index, direction_index};
|
||||
const vk::SpecializationMapEntry entries[] {{0, 0, 4}, {1, 4, 4}};
|
||||
const vk::SpecializationInfo specialization {2, entries, sizeof(values), values};
|
||||
vk::ShaderModuleCreateInfo module_info {};
|
||||
module_info.codeSize = shaders[family_index].words * sizeof(uint32_t);
|
||||
module_info.pCode = shaders[family_index].code;
|
||||
vk::ShaderModule module = nullptr;
|
||||
RequireVulkanSuccess(m_graphics.device.createShaderModule(&module_info, nullptr, &module),
|
||||
"create TileManager shader module");
|
||||
const auto module =
|
||||
CompileSPV({shaders[family_index].code, shaders[family_index].words}, m_graphics.device);
|
||||
vk::PipelineShaderStageCreateInfo stage {};
|
||||
stage.stage = vk::ShaderStageFlagBits::eCompute;
|
||||
stage.module = module;
|
||||
@@ -472,12 +468,7 @@ void TileManager::ConvertD16(Result source, Result target, D16Direction directio
|
||||
code = GPU_TILER_DEMOTE_D16_SPV;
|
||||
words = std::size(GPU_TILER_DEMOTE_D16_SPV);
|
||||
}
|
||||
vk::ShaderModuleCreateInfo module_info {};
|
||||
module_info.codeSize = words * sizeof(uint32_t);
|
||||
module_info.pCode = code;
|
||||
vk::ShaderModule module = nullptr;
|
||||
RequireVulkanSuccess(m_graphics.device.createShaderModule(&module_info, nullptr, &module),
|
||||
"create D16 conversion shader module");
|
||||
const auto module = CompileSPV({code, words}, m_graphics.device);
|
||||
vk::PipelineShaderStageCreateInfo stage {};
|
||||
stage.stage = vk::ShaderStageFlagBits::eCompute;
|
||||
stage.module = module;
|
||||
@@ -611,12 +602,7 @@ void TileManager::ConvertD16(Result source, Result target, D16Direction directio
|
||||
|
||||
void TileManager::SwapBgra16(Result input, Result output, uint32_t pixels) {
|
||||
if (m_swap_bgra16 == nullptr) {
|
||||
vk::ShaderModuleCreateInfo module_info {};
|
||||
module_info.codeSize = std::size(GPU_TILER_SWAP_BGRA16_SPV) * sizeof(uint32_t);
|
||||
module_info.pCode = GPU_TILER_SWAP_BGRA16_SPV;
|
||||
vk::ShaderModule module = nullptr;
|
||||
RequireVulkanSuccess(m_graphics.device.createShaderModule(&module_info, nullptr, &module),
|
||||
"create BGRA16 swap shader module");
|
||||
const auto module = CompileSPV(GPU_TILER_SWAP_BGRA16_SPV, m_graphics.device);
|
||||
vk::PipelineShaderStageCreateInfo stage {};
|
||||
stage.stage = vk::ShaderStageFlagBits::eCompute;
|
||||
stage.module = module;
|
||||
|
||||
@@ -15,6 +15,7 @@
|
||||
#include "graphics/guest_gpu/tile.h"
|
||||
#include "graphics/host_gpu/graphicContext.h"
|
||||
#include "graphics/host_gpu/hostMemory.h"
|
||||
#include "graphics/host_gpu/renderer/colorRenderTarget.h"
|
||||
#include "graphics/host_gpu/renderer/debug.h"
|
||||
#include "graphics/host_gpu/renderer/image/imageView.h"
|
||||
#include "graphics/host_gpu/renderer/image/textureCommon.h"
|
||||
@@ -93,22 +94,15 @@ static const char* ShaderStageResourceName(ShaderType stage) {
|
||||
switch (stage) {
|
||||
case ShaderType::Vertex: return "Vertex";
|
||||
case ShaderType::Mesh: return "Mesh";
|
||||
case ShaderType::Local: return "Local";
|
||||
case ShaderType::TessellationControl: return "Hull";
|
||||
case ShaderType::TessellationEvaluation: return "Domain";
|
||||
case ShaderType::Pixel: return "Pixel";
|
||||
case ShaderType::Compute: return "Compute";
|
||||
default: return "Unknown";
|
||||
}
|
||||
}
|
||||
|
||||
static vk::ShaderStageFlags NativeShaderStage(ShaderType stage) {
|
||||
switch (stage) {
|
||||
case ShaderType::Vertex: return vk::ShaderStageFlagBits::eVertex;
|
||||
case ShaderType::Mesh: return vk::ShaderStageFlagBits::eMeshEXT;
|
||||
case ShaderType::Pixel: return vk::ShaderStageFlagBits::eFragment;
|
||||
case ShaderType::Compute: return vk::ShaderStageFlagBits::eCompute;
|
||||
default: EXIT("unknown native shader stage\n");
|
||||
}
|
||||
}
|
||||
|
||||
static Prospero::ImageType TextureType(const ShaderTextureResource& descriptor) {
|
||||
const auto type = descriptor.Type();
|
||||
return type == Prospero::ImageType::kCube ? Prospero::ImageType::kColor2DArray : type;
|
||||
@@ -154,7 +148,7 @@ NativeStorageBuffer(RenderContext& context, const PreparedBindings::BufferSource
|
||||
}
|
||||
buffer_offset = static_cast<uint32_t>(adjustment);
|
||||
const vk::DescriptorBufferInfo result {buffer->Handle(), aligned_offset, size + adjustment};
|
||||
if (resource.formatted && resource.written) {
|
||||
if (resource.written) {
|
||||
context.GetTextureCache().InvalidateMemoryFromGPU(address, size);
|
||||
}
|
||||
const char* access = "Read";
|
||||
@@ -190,17 +184,18 @@ bool IsSupportedDepthTextureEncoding(const ShaderTextureResource& descriptor, bo
|
||||
}
|
||||
const bool full = common && descriptor.fields[4] == field4_expected &&
|
||||
descriptor.fields[5] == field5_expected;
|
||||
if (!full || (descriptor.fields[6] == 0 && descriptor.fields[7] != 0) ||
|
||||
if (!full ||
|
||||
(descriptor.MsaaDepth() && !IsMultisampledTexture(descriptor.Type()))) {
|
||||
return false;
|
||||
}
|
||||
if (descriptor.fields[6] == 0) {
|
||||
const auto metadata_control = descriptor.fields[6] & 0x00ffffffu;
|
||||
if (metadata_control == 0) {
|
||||
return true;
|
||||
}
|
||||
constexpr uint32_t htile_control = 0x00280000u;
|
||||
const uint32_t expected_control = htile_control | (descriptor.MsaaDepth() ? (1u << 10u) : 0u);
|
||||
const auto metadata_addr = descriptor.MetaAddr() << 8u;
|
||||
return (descriptor.fields[6] & 0x00ffffffu) == expected_control && metadata_addr != 0 &&
|
||||
return metadata_control == expected_control && metadata_addr != 0 &&
|
||||
metadata_addr < TRACKER_ADDRESS_SIZE && (metadata_addr & 0x7fffu) == 0 &&
|
||||
descriptor.TileMode() == Prospero::TileMode::kDepth;
|
||||
}
|
||||
@@ -258,10 +253,10 @@ static bool IsSupportedStorageTextureDescriptor(const ShaderRecompiler::IR::Imag
|
||||
(is_color_2d_array && descriptor.BaseArray5() <= descriptor.Depth());
|
||||
const bool is_2d =
|
||||
resource.dimension == ShaderRecompiler::Decoder::ImageDimension::Dim2D && valid_2d_slice;
|
||||
// Storage cube coordinates address individual faces, including partial cube views.
|
||||
const bool is_cube = resource.cube && descriptor.Type() == Prospero::ImageType::kCube &&
|
||||
descriptor.Width5() == descriptor.Height5() &&
|
||||
descriptor.BaseArray5() <= descriptor.Depth() &&
|
||||
(descriptor.Depth() - descriptor.BaseArray5() + 1u) % 6u == 0;
|
||||
descriptor.BaseArray5() <= descriptor.Depth();
|
||||
const bool is_2d_array =
|
||||
resource.dimension == ShaderRecompiler::Decoder::ImageDimension::Dim2DArray &&
|
||||
((!resource.cube && is_color_2d_array && descriptor.BaseArray5() <= descriptor.Depth()) ||
|
||||
@@ -299,13 +294,8 @@ static bool IsSupportedStorageTextureDescriptor(const ShaderRecompiler::IR::Imag
|
||||
const bool supported_swizzle =
|
||||
IsValidImageSwizzle(swizzle) &&
|
||||
(swizzle == DstSel(4, 5, 6, 7) || !resource.read || resource.atomic);
|
||||
const auto max_mip = resource.r128 ? descriptor.LastLevel() : descriptor.MaxMip();
|
||||
const auto view_last_level =
|
||||
resource.mip_mode == ShaderRecompiler::IR::ImageMipMode::DynamicStorage
|
||||
? descriptor.LastLevel()
|
||||
: std::min(descriptor.LastLevel(), max_mip);
|
||||
return (is_1d || is_1d_array || is_2d || is_2d_array || is_3d) && supported_tile &&
|
||||
descriptor.BaseLevel() <= view_last_level && view_last_level <= max_mip &&
|
||||
descriptor.BaseLevel() <= descriptor.LastLevel() &&
|
||||
descriptor.MinLod() == 0 && supported_swizzle && descriptor.BCSwizzle() == 0 &&
|
||||
!descriptor.MsaaDepth();
|
||||
}
|
||||
@@ -463,6 +453,14 @@ static ImageViewInfo TextureViewInfo(const ShaderRecompiler::IR::ImageResource&
|
||||
view.aspect = vk::ImageAspectFlagBits::eColor;
|
||||
view.base_level = descriptor.BaseLevel();
|
||||
view.level_count = view_levels;
|
||||
if (descriptor.MinLod() > descriptor.LastLevel() * 256u) {
|
||||
EXIT("texture minimum LOD exceeds last mip level: min_lod=%u last_level=%u\n",
|
||||
descriptor.MinLod(), descriptor.LastLevel());
|
||||
}
|
||||
const auto base_lod = view.base_level * 256u;
|
||||
if (descriptor.MinLod() > base_lod) {
|
||||
view.min_lod = descriptor.MinLod() - base_lod;
|
||||
}
|
||||
view.usage = storage ? vk::ImageUsageFlagBits::eStorage : vk::ImageUsageFlagBits::eSampled;
|
||||
view.mapping =
|
||||
storage || surface_format.conversion_format != Prospero::BufferFormat::kInvalid
|
||||
@@ -513,6 +511,21 @@ static ImageViewInfo TextureViewInfo(const ShaderRecompiler::IR::ImageResource&
|
||||
return view;
|
||||
}
|
||||
|
||||
static bool TextureViewPreservesMipLayout(const TileSurfaceDescription& description,
|
||||
uint32_t view_levels) {
|
||||
TileSurfaceLayout physical {};
|
||||
TileSurfaceLayout view {};
|
||||
auto view_description = description;
|
||||
view_description.levels = view_levels;
|
||||
return TileGetTiledTextureLayout(description, physical) &&
|
||||
TileGetTiledTextureLayout(view_description, view) &&
|
||||
physical.first_tail_level == view.first_tail_level &&
|
||||
physical.block_slice_size == view.block_slice_size &&
|
||||
physical.total_size == view.total_size &&
|
||||
std::equal(std::begin(physical.mips), std::begin(physical.mips) + description.levels,
|
||||
std::begin(view.mips));
|
||||
}
|
||||
|
||||
TextureBinding RenderExecutor::ResolveTexture(const ShaderRecompiler::IR::ImageResource& resource,
|
||||
const ShaderRecompiler::IR::DescriptorValue& value) {
|
||||
auto descriptor = DecodeNativeDescriptor<ShaderTextureResource>(value);
|
||||
@@ -529,24 +542,28 @@ TextureBinding RenderExecutor::ResolveTexture(const ShaderRecompiler::IR::ImageR
|
||||
return {id, nullptr, std::move(desc)};
|
||||
}
|
||||
|
||||
const auto address = descriptor.Base40();
|
||||
const auto width = static_cast<uint32_t>(descriptor.Width5()) + 1u;
|
||||
const auto height = static_cast<uint32_t>(descriptor.Height5()) + 1u;
|
||||
const auto base_level = descriptor.BaseLevel();
|
||||
const auto last_level = descriptor.LastLevel();
|
||||
const auto type = TextureType(descriptor);
|
||||
const bool multisampled = IsMultisampledTexture(type);
|
||||
const auto max_mip = resource.r128 ? last_level : descriptor.MaxMip();
|
||||
const auto levels = multisampled ? 1u : static_cast<uint32_t>(max_mip) + 1u;
|
||||
const bool dynamic_storage =
|
||||
storage && resource.mip_mode == ShaderRecompiler::IR::ImageMipMode::DynamicStorage;
|
||||
const auto view_last_level =
|
||||
!multisampled && !dynamic_storage ? std::min(last_level, max_mip) : last_level;
|
||||
const auto address = descriptor.Base40();
|
||||
const auto width = static_cast<uint32_t>(descriptor.Width5()) + 1u;
|
||||
const auto height = static_cast<uint32_t>(descriptor.Height5()) + 1u;
|
||||
const auto base_level = descriptor.BaseLevel();
|
||||
const auto last_level = descriptor.LastLevel();
|
||||
const auto type = TextureType(descriptor);
|
||||
const bool multisampled = IsMultisampledTexture(type);
|
||||
const auto max_mip = resource.r128 ? last_level : descriptor.MaxMip();
|
||||
const auto physical_levels = multisampled ? 1u : static_cast<uint32_t>(max_mip) + 1u;
|
||||
// IMAGE_STORE addresses BASE_LEVEL; only IMAGE_STORE_MIP selects other view mips.
|
||||
const bool single_storage_mip =
|
||||
storage && resource.mip_mode != ShaderRecompiler::IR::ImageMipMode::DynamicStorage;
|
||||
const auto view_levels = multisampled || single_storage_mip
|
||||
? 1u
|
||||
: static_cast<uint32_t>(last_level - base_level) + 1u;
|
||||
const auto levels =
|
||||
multisampled ? 1u : std::max(physical_levels, base_level + view_levels);
|
||||
const auto tile = descriptor.TileMode();
|
||||
const bool depth_tile = tile == Prospero::TileMode::kDepth;
|
||||
const bool msaa_tile = depth_tile || tile == Prospero::TileMode::kRenderTarget;
|
||||
const bool msaa_array = type == Prospero::ImageType::kColor2DMsaaArray;
|
||||
if ((!multisampled && (base_level > view_last_level || view_last_level >= levels)) ||
|
||||
if ((!multisampled && base_level > last_level) ||
|
||||
(multisampled &&
|
||||
(base_level != 0 || last_level == 0 || last_level > 3 || max_mip != last_level ||
|
||||
!msaa_tile || (descriptor.MsaaDepth() && !depth_tile) ||
|
||||
@@ -564,8 +581,6 @@ TextureBinding RenderExecutor::ResolveTexture(const ShaderRecompiler::IR::ImageR
|
||||
descriptor.fields[6], descriptor.fields[7]);
|
||||
}
|
||||
const auto samples = multisampled ? 1u << last_level : 1u;
|
||||
const auto view_levels =
|
||||
multisampled ? 1u : static_cast<uint32_t>(view_last_level - base_level) + 1u;
|
||||
const auto depth = static_cast<uint32_t>(descriptor.Depth()) + 1u;
|
||||
const auto format = descriptor.Format();
|
||||
const auto surface_format = TextureGetSurfaceFormatInfo(format);
|
||||
@@ -584,7 +599,19 @@ TextureBinding RenderExecutor::ResolveTexture(const ShaderRecompiler::IR::ImageR
|
||||
type == Prospero::ImageType::kColor2DArray ||
|
||||
type == Prospero::ImageType::kColor2DMsaaArray;
|
||||
const auto image_layers = layered ? depth : 1u;
|
||||
uint32_t pitch = 0;
|
||||
if (levels > physical_levels) {
|
||||
const TileSurfaceDescription physical {
|
||||
format, tile, volume ? TileSurfaceDimension::Dim3D : TileSurfaceDimension::Dim2D,
|
||||
width, height, volume ? depth : 1u, physical_levels, image_layers};
|
||||
// Texture mip views take precedence over the resource count, but must keep its storage layout.
|
||||
if (!TextureViewPreservesMipLayout(physical, levels)) {
|
||||
EXIT("unsupported texture mip view changes physical layout: base=%u last=%u max=%u "
|
||||
"extent=%ux%ux%u tile=%u\n",
|
||||
base_level, last_level, max_mip, width, height, depth,
|
||||
static_cast<uint32_t>(tile));
|
||||
}
|
||||
}
|
||||
uint32_t pitch = 0;
|
||||
TileSizeAlign size {};
|
||||
if (multisampled) {
|
||||
const auto bytes = Prospero::NumBytesPerElement(format);
|
||||
@@ -597,8 +624,8 @@ TextureBinding RenderExecutor::ResolveTexture(const ShaderRecompiler::IR::ImageR
|
||||
size.size *= image_layers;
|
||||
} else {
|
||||
pitch = TileGetTexturePitch(format, width, tile);
|
||||
TileGetTextureTotalSize(format, width, height, volume ? depth : image_layers, levels, tile,
|
||||
volume, size);
|
||||
TileGetTextureTotalSize(format, width, height, volume ? depth : image_layers,
|
||||
physical_levels, tile, volume, size);
|
||||
}
|
||||
EXIT_NOT_IMPLEMENTED(size.size == 0 || size.align == 0 ||
|
||||
(address & (static_cast<uint64_t>(size.align) - 1u)) != 0);
|
||||
@@ -635,7 +662,7 @@ TextureBinding RenderExecutor::ResolveTexture(const ShaderRecompiler::IR::ImageR
|
||||
!desc.info.IsDepth()) {
|
||||
TileSizeAlign metadata_size {};
|
||||
(void)TileGetDccSize(width, height, volume ? depth : image_layers,
|
||||
desc.info.bytes_per_block, levels, tile, metadata_size,
|
||||
desc.info.bytes_per_block, physical_levels, tile, metadata_size,
|
||||
std::countr_zero(samples));
|
||||
desc.info.metadata.kind = ImageMetadataKind::Dcc;
|
||||
desc.info.metadata.range = {descriptor.MetaAddr() << 8u, metadata_size.size};
|
||||
@@ -726,7 +753,7 @@ PreparedBindings RenderExecutor::PrepareBindings(const ShaderStageRuntime& runti
|
||||
KYTY_PROFILER_FUNCTION();
|
||||
EXIT_IF(!runtime);
|
||||
const auto& program = *runtime.program;
|
||||
const auto& snapshot = runtime.resources;
|
||||
const auto& snapshot = *runtime.resources;
|
||||
PreparedBindings prepared;
|
||||
prepared.runtime = &runtime;
|
||||
prepared.images.reserve(program.info.images.size());
|
||||
@@ -755,7 +782,7 @@ void RenderExecutor::FindBuffers(PreparedBindings& prepared) {
|
||||
KYTY_PROFILER_FUNCTION();
|
||||
EXIT_IF(prepared.runtime == nullptr || !*prepared.runtime);
|
||||
const auto& program = *prepared.runtime->program;
|
||||
const auto& snapshot = prepared.runtime->resources;
|
||||
const auto& snapshot = *prepared.runtime->resources;
|
||||
auto& cache = m_context.GetBufferCache();
|
||||
|
||||
prepared.buffer_sources.clear();
|
||||
@@ -777,7 +804,7 @@ void RenderExecutor::RebindBuffers(PreparedBindings& prepared) {
|
||||
KYTY_PROFILER_FUNCTION();
|
||||
EXIT_IF(prepared.runtime == nullptr || !*prepared.runtime);
|
||||
const auto& program = *prepared.runtime->program;
|
||||
const auto& snapshot = prepared.runtime->resources;
|
||||
const auto& snapshot = *prepared.runtime->resources;
|
||||
const auto& layout = program.bindings;
|
||||
EXIT_IF(prepared.buffer_sources.size() != program.info.buffers.size());
|
||||
|
||||
@@ -812,7 +839,7 @@ void RenderExecutor::RebindImages(PreparedBindings& prepared) {
|
||||
KYTY_PROFILER_FUNCTION();
|
||||
EXIT_IF(prepared.runtime == nullptr || !*prepared.runtime);
|
||||
const auto& program = *prepared.runtime->program;
|
||||
const auto& snapshot = prepared.runtime->resources;
|
||||
const auto& snapshot = *prepared.runtime->resources;
|
||||
auto& images = prepared.images;
|
||||
EXIT_IF(images.size() != program.info.images.size());
|
||||
auto& texture_cache = m_context.GetTextureCache();
|
||||
@@ -844,11 +871,7 @@ void RenderExecutor::RebindImages(PreparedBindings& prepared) {
|
||||
}
|
||||
binding.image_view = binding.mip_views.front();
|
||||
} else {
|
||||
auto desc = binding.desc;
|
||||
if (desc.type == TextureCache::BindingType::Storage) {
|
||||
desc.view_info.level_count = 1;
|
||||
}
|
||||
binding.image_view = texture_cache.FindTexture(binding.image_id, desc);
|
||||
binding.image_view = texture_cache.FindTexture(binding.image_id, binding.desc);
|
||||
}
|
||||
auto& image = texture_cache.GetImage(binding.image_id);
|
||||
const bool storage = binding.desc.type == TextureCache::BindingType::Storage;
|
||||
@@ -857,32 +880,39 @@ void RenderExecutor::RebindImages(PreparedBindings& prepared) {
|
||||
}
|
||||
}
|
||||
|
||||
RenderExecutor::GraphicsBindings
|
||||
RenderExecutor::PrepareGraphicsBindings(const ShaderStageRuntime& vertex,
|
||||
const ShaderStageRuntime& pixel, bool pixel_active) {
|
||||
GraphicsBindings bindings {
|
||||
.vertex = PrepareBindings(vertex),
|
||||
};
|
||||
if (pixel_active) {
|
||||
bindings.pixel.emplace(PrepareBindings(pixel));
|
||||
void RenderExecutor::PrepareGraphicsBindings(std::span<PreparedBindings* const> stages,
|
||||
std::span<RenderColorInfo> colors) {
|
||||
bool uses_dma = false;
|
||||
for (auto* stage: stages) {
|
||||
FindBuffers(*stage);
|
||||
uses_dma |= stage->runtime->program->info.uses_dma;
|
||||
}
|
||||
FindBuffers(bindings.vertex);
|
||||
if (bindings.pixel) {
|
||||
FindBuffers(*bindings.pixel);
|
||||
}
|
||||
if (bindings.vertex.runtime->program->info.uses_dma ||
|
||||
(bindings.pixel && bindings.pixel->runtime->program->info.uses_dma)) {
|
||||
if (uses_dma) {
|
||||
m_context.PrepareBda();
|
||||
}
|
||||
RebindBuffers(bindings.vertex);
|
||||
if (bindings.pixel) {
|
||||
RebindBuffers(*bindings.pixel);
|
||||
for (auto* stage: stages) {
|
||||
RebindImages(*stage);
|
||||
}
|
||||
RebindImages(bindings.vertex);
|
||||
if (bindings.pixel) {
|
||||
RebindImages(*bindings.pixel);
|
||||
auto& cache = m_context.GetTextureCache();
|
||||
for (auto& target: colors) {
|
||||
EXIT_IF(!target.image_id);
|
||||
const auto old_image = cache.m_slot_images.try_get(target.image_id);
|
||||
if (old_image == nullptr || (!old_image->registered && !old_image->info.data.Empty()) ||
|
||||
old_image->binding.needs_rebind) {
|
||||
if (old_image != nullptr) {
|
||||
old_image->binding = {};
|
||||
}
|
||||
target.desc.view_info.base_level = target.guest_mip_level;
|
||||
target.desc.view_info.base_layer = target.guest_array_layer;
|
||||
target.image_id = cache.FindImage(target.desc);
|
||||
BindRenderTarget(target.image_id);
|
||||
}
|
||||
}
|
||||
// Discovery can read back PS5 metadata and submit the scheduler. Reserve draw buffers only
|
||||
// after image identities are final; attachment layout transitions follow buffer alias copies.
|
||||
for (auto* stage: stages) {
|
||||
RebindBuffers(*stage);
|
||||
}
|
||||
return bindings;
|
||||
}
|
||||
|
||||
void RenderExecutor::CommitBindings(CommandBuffer& buffer,
|
||||
@@ -895,9 +925,10 @@ void RenderExecutor::CommitBindings(CommandBuffer& buffer,
|
||||
size_t write_count = 0;
|
||||
ShaderRecompiler::IR::PushData push_data;
|
||||
bool has_push_data = false;
|
||||
constexpr auto GraphicsStages = vk::ShaderStageFlagBits::eVertex |
|
||||
vk::ShaderStageFlagBits::eMeshEXT |
|
||||
vk::ShaderStageFlagBits::eFragment;
|
||||
constexpr auto GraphicsStages =
|
||||
vk::ShaderStageFlagBits::eVertex | vk::ShaderStageFlagBits::eMeshEXT |
|
||||
vk::ShaderStageFlagBits::eTessellationControl |
|
||||
vk::ShaderStageFlagBits::eTessellationEvaluation | vk::ShaderStageFlagBits::eFragment;
|
||||
vk::ShaderStageFlags push_stages = pipeline_bind_point == vk::PipelineBindPoint::eGraphics
|
||||
? vk::ShaderStageFlagBits::eFragment
|
||||
: vk::ShaderStageFlags {};
|
||||
|
||||
@@ -89,17 +89,12 @@ template <typename... Args>
|
||||
void PipelineCacheLog(fmt::format_string<Args...> format, Args&&... args) {
|
||||
auto message = fmt::format(format, std::forward<Args>(args)...);
|
||||
message += '\n';
|
||||
if (Log::GetDirection() != Log::Direction::Console) {
|
||||
std::fwrite(message.data(), 1, message.size(), stdout);
|
||||
std::fflush(stdout);
|
||||
}
|
||||
Log::Write(message);
|
||||
Log::Flush();
|
||||
Log::WriteToConsoleAndLog(message);
|
||||
}
|
||||
|
||||
bool ReadShaderGuestMemory(void*, uint64_t address, uint32_t* value) {
|
||||
return value != nullptr &&
|
||||
Libs::LibKernel::Memory::TryReadGpuCleanBacking(address, value, sizeof(*value));
|
||||
bool ReadShaderGuestMemory(void*, uint64_t address, std::span<uint32_t> values) {
|
||||
return !values.empty() &&
|
||||
Libs::LibKernel::Memory::TryReadGpuCleanBacking(address, values.data(), values.size_bytes());
|
||||
}
|
||||
|
||||
void DumpShaderSpirv(const char* stage_name, uint64_t shader_hash,
|
||||
@@ -205,8 +200,10 @@ struct PipelineCache::ProgramCache {
|
||||
permutations.reserve(8);
|
||||
}
|
||||
|
||||
ShaderRecompiler::IR::ResourcePlan resource_plan;
|
||||
std::vector<Permutation> permutations;
|
||||
ShaderRecompiler::IR::ResourcePlan resource_plan;
|
||||
ShaderRecompiler::IR::ResourceSnapshot resources;
|
||||
ShaderRecompiler::IR::ResourceSpecialization specialization;
|
||||
std::vector<Permutation> permutations;
|
||||
};
|
||||
|
||||
struct ProgramKeyHash {
|
||||
@@ -236,6 +233,9 @@ struct PipelineCache::ProgramCache {
|
||||
switch (options.stage) {
|
||||
case ShaderType::Vertex: stage_name = "vs"; break;
|
||||
case ShaderType::Mesh: stage_name = "ms"; break;
|
||||
case ShaderType::Local: stage_name = "ls"; break;
|
||||
case ShaderType::TessellationControl: stage_name = "hs"; break;
|
||||
case ShaderType::TessellationEvaluation: stage_name = "ds"; break;
|
||||
case ShaderType::Pixel: stage_name = "ps"; break;
|
||||
case ShaderType::Compute: stage_name = "cs"; break;
|
||||
default: EXIT("invalid pipeline shader stage\n");
|
||||
@@ -250,12 +250,7 @@ struct PipelineCache::ProgramCache {
|
||||
}
|
||||
DumpShaderSpirv(stage_name, options.shader_hash, result.spirv);
|
||||
|
||||
vk::ShaderModuleCreateInfo create_info {};
|
||||
create_info.codeSize = result.spirv.size() * sizeof(uint32_t);
|
||||
create_info.pCode = result.spirv.data();
|
||||
vk::ShaderModule module = nullptr;
|
||||
RequireVulkanSuccess(device.createShaderModule(&create_info, nullptr, &module),
|
||||
"create recompiled shader module");
|
||||
const auto module = CompileSPV(result.spirv, device);
|
||||
EXIT_IF(module == nullptr);
|
||||
if (options.dump_ir) {
|
||||
LOGF("%s SPIR-V words=%" PRIu64 " wave_size=%u\n", options.dump_label,
|
||||
@@ -273,7 +268,7 @@ struct PipelineCache::ProgramCache {
|
||||
uint32_t& push_data_cursor) {
|
||||
ShaderType stage;
|
||||
if constexpr (std::is_same_v<InputInfo, ShaderVertexInputInfo>) {
|
||||
stage = input_info.mesh.threads_num[0] != 0 ? ShaderType::Mesh : ShaderType::Vertex;
|
||||
stage = input_info.logical_stage;
|
||||
} else if constexpr (std::is_same_v<InputInfo, ShaderPixelInputInfo>) {
|
||||
stage = ShaderType::Pixel;
|
||||
} else {
|
||||
@@ -281,33 +276,33 @@ struct PipelineCache::ProgramCache {
|
||||
stage = ShaderType::Compute;
|
||||
}
|
||||
|
||||
const auto user_data = std::span(params.user_data).first(params.user_data_count);
|
||||
lookup_key.stage = stage;
|
||||
lookup_key.hash = params.hash;
|
||||
lookup_key.user_data_count = static_cast<uint32_t>(params.user_data.size());
|
||||
lookup_key.user_data_count = params.user_data_count;
|
||||
lookup_key.code_size = static_cast<uint32_t>(params.code.size());
|
||||
BuildStageStaticKey(input_info, lookup_key.static_state);
|
||||
auto entry = programs.find(lookup_key);
|
||||
ShaderRecompiler::IR::ResourceSnapshot resources;
|
||||
ShaderRecompiler::IR::ResourceSpecialization specialization;
|
||||
const ShaderRecompiler::IR::SrtRuntime runtime {
|
||||
.user_data = params.user_data,
|
||||
.user_data = user_data,
|
||||
.shader_base = params.Base(),
|
||||
.read_specialization_memory = ReadShaderGuestMemory,
|
||||
};
|
||||
if (entry != programs.end()) {
|
||||
EXIT_IF(!ShaderRecompiler::IR::MaterializeResources(
|
||||
entry->second.resource_plan, runtime, resources, specialization));
|
||||
entry->second.resource_plan, runtime, entry->second.resources,
|
||||
entry->second.specialization));
|
||||
if (const auto permutation = std::ranges::find_if(
|
||||
entry->second.permutations, [&](const Permutation& candidate) {
|
||||
const auto& layout = candidate.program.bindings;
|
||||
return layout.push_data_start_dword ==
|
||||
ShaderRecompiler::IR::PushData::StartFor(
|
||||
push_data_cursor, layout.ShaderDataDwords()) &&
|
||||
candidate.specialization == specialization;
|
||||
candidate.specialization == entry->second.specialization;
|
||||
});
|
||||
permutation != entry->second.permutations.end()) {
|
||||
input_info.stage = {.program = &permutation->program,
|
||||
.resources = std::move(resources)};
|
||||
.resources = &entry->second.resources};
|
||||
permutation->program.bindings.AdvancePushData(push_data_cursor);
|
||||
return permutation->handle;
|
||||
}
|
||||
@@ -325,6 +320,9 @@ struct PipelineCache::ProgramCache {
|
||||
switch (stage) {
|
||||
case ShaderType::Vertex: label = "ShaderRecompiler VS"; break;
|
||||
case ShaderType::Mesh: label = "ShaderRecompiler MS"; break;
|
||||
case ShaderType::Local: label = "ShaderRecompiler LS"; break;
|
||||
case ShaderType::TessellationControl: label = "ShaderRecompiler HS"; break;
|
||||
case ShaderType::TessellationEvaluation: label = "ShaderRecompiler DS"; break;
|
||||
case ShaderType::Pixel: label = "ShaderRecompiler PS"; break;
|
||||
case ShaderType::Compute: label = "ShaderRecompiler CS"; break;
|
||||
default: EXIT("invalid pipeline shader stage\n");
|
||||
@@ -332,7 +330,7 @@ struct PipelineCache::ProgramCache {
|
||||
ShaderRecompiler::CompileOptions options;
|
||||
options.stage = stage;
|
||||
options.shader_hash = params.hash;
|
||||
options.user_data = params.user_data;
|
||||
options.user_data = user_data;
|
||||
options.back_code = params.back_code;
|
||||
options.dump_ir = Config::GetShaderLogDirection() != Config::LogDirection::Silent;
|
||||
options.early_dump = options.dump_ir;
|
||||
@@ -341,36 +339,41 @@ struct PipelineCache::ProgramCache {
|
||||
|
||||
if constexpr (std::is_same_v<InputInfo, ShaderVertexInputInfo>) {
|
||||
options.user_data_base = 8;
|
||||
if (stage == ShaderType::Mesh) {
|
||||
options.wave_size = input_info.wave_size;
|
||||
if (stage == ShaderType::Mesh || stage == ShaderType::TessellationControl) {
|
||||
options.user_data_base = 0;
|
||||
options.wave_size = input_info.mesh.wave_size;
|
||||
options.wave_size = stage == ShaderType::Mesh ? input_info.mesh.wave_size : 64u;
|
||||
}
|
||||
} else if constexpr (std::is_same_v<InputInfo, ShaderComputeInputInfo>) {
|
||||
} else {
|
||||
options.wave_size = input_info.wave_size;
|
||||
}
|
||||
auto translated = ShaderRecompiler::TranslateProgram(params.code, options);
|
||||
if (entry == programs.end()) {
|
||||
auto resource_plan = ShaderRecompiler::IR::ExtractResourcePlan(translated.program);
|
||||
EXIT_IF(!ShaderRecompiler::IR::MaterializeResources(resource_plan, runtime, resources,
|
||||
specialization));
|
||||
entry = programs.try_emplace(lookup_key, std::move(resource_plan)).first;
|
||||
entry = programs.try_emplace(lookup_key,
|
||||
ShaderRecompiler::IR::ExtractResourcePlan(translated.program)).first;
|
||||
EXIT_IF(!ShaderRecompiler::IR::MaterializeResources(
|
||||
entry->second.resource_plan, runtime, entry->second.resources,
|
||||
entry->second.specialization));
|
||||
}
|
||||
entry->second.permutations.push_back(CompilePermutation(
|
||||
params, options, std::move(translated), std::move(specialization), push_data_cursor));
|
||||
params, options, std::move(translated), entry->second.specialization, push_data_cursor));
|
||||
const auto& permutation = entry->second.permutations.back();
|
||||
input_info.stage = {.program = &permutation.program, .resources = std::move(resources)};
|
||||
input_info.stage = {.program = &permutation.program, .resources = &entry->second.resources};
|
||||
permutation.program.bindings.AdvancePushData(push_data_cursor);
|
||||
|
||||
std::array<size_t, static_cast<size_t>(ShaderType::Mesh) + 1> counts {};
|
||||
std::array<size_t, static_cast<size_t>(ShaderType::TessellationEvaluation) + 1> counts {};
|
||||
for (const auto& [key, source]: programs) {
|
||||
counts[static_cast<size_t>(key.stage)] += source.permutations.size();
|
||||
}
|
||||
// Guest geometry shaders are compiled through the host mesh stage.
|
||||
std::printf("Shaders: VS %zu | PS %zu | CS %zu | GS %zu\n",
|
||||
std::printf("Shaders: VS %zu | PS %zu | CS %zu | GS %zu | LS %zu | HS %zu | TES %zu\n",
|
||||
counts[static_cast<size_t>(ShaderType::Vertex)],
|
||||
counts[static_cast<size_t>(ShaderType::Pixel)],
|
||||
counts[static_cast<size_t>(ShaderType::Compute)],
|
||||
counts[static_cast<size_t>(ShaderType::Mesh)]);
|
||||
counts[static_cast<size_t>(ShaderType::Mesh)],
|
||||
counts[static_cast<size_t>(ShaderType::Local)],
|
||||
counts[static_cast<size_t>(ShaderType::TessellationControl)],
|
||||
counts[static_cast<size_t>(ShaderType::TessellationEvaluation)]);
|
||||
return permutation.handle;
|
||||
}
|
||||
|
||||
@@ -564,12 +567,18 @@ PipelineCache::GraphicsPrograms PipelineCache::GetGraphicsPrograms(
|
||||
const HW::VertexShaderInfo& vertex_regs, const HW::PixelShaderInfo& pixel_regs,
|
||||
const HW::ShaderRegisters& sh, const HW::Context& context, const HW::UserConfig& user_config,
|
||||
std::span<const Prospero::ColorComponentMapping, 8> target_export_mapping, bool pixel_active,
|
||||
ShaderVertexInputInfo& vertex_info, ShaderPixelInputInfo& pixel_info) {
|
||||
const auto vertex_params = PrepareProgram(vertex_regs, context, user_config, vertex_info);
|
||||
const bool mesh_active = vertex_info.mesh.threads_num[0] != 0;
|
||||
std::array<ShaderVertexInputInfo, 3>& vertex_info, ShaderPixelInputInfo& pixel_info) {
|
||||
const bool tess_active = user_config.GetPrimType() == Prospero::PrimitiveType::kPatch;
|
||||
std::array<ShaderParams, 3> vertex_params;
|
||||
if (tess_active) {
|
||||
vertex_params = PrepareTessellationPrograms(vertex_regs, context, vertex_info);
|
||||
} else {
|
||||
vertex_params[0] = PrepareProgram(vertex_regs, context, user_config, vertex_info[0]);
|
||||
}
|
||||
const bool mesh_active = vertex_info[0].logical_stage == ShaderType::Mesh;
|
||||
if (mesh_active) {
|
||||
EXIT_NOT_IMPLEMENTED(!m_graphics.mesh_shader_enabled);
|
||||
auto& mesh = vertex_info.mesh;
|
||||
auto& mesh = vertex_info[0].mesh;
|
||||
mesh.host_subgroup_size = m_graphics.subgroup_size;
|
||||
const auto& limits = m_graphics.mesh_shader_properties;
|
||||
const auto logical_threads =
|
||||
@@ -588,11 +597,26 @@ PipelineCache::GraphicsPrograms PipelineCache::GetGraphicsPrograms(
|
||||
ShaderParams pixel_params;
|
||||
if (pixel_active) {
|
||||
pixel_params = PrepareProgram(pixel_regs, sh, target_export_mapping, pixel_info);
|
||||
const auto& blend = context.GetBlendControl(0);
|
||||
const auto is_dual_source = [](uint8_t factor) {
|
||||
return factor >= static_cast<uint8_t>(Prospero::BlendFactor::kSrc1Color) &&
|
||||
factor <= static_cast<uint8_t>(Prospero::BlendFactor::kOneMinusSrc1Alpha);
|
||||
};
|
||||
pixel_info.dual_source_blending =
|
||||
blend.enable && !context.GetRenderTarget(0).info.blend_bypass &&
|
||||
(is_dual_source(blend.color_srcblend) || is_dual_source(blend.color_destblend) ||
|
||||
(blend.separate_alpha_blend &&
|
||||
(is_dual_source(blend.alpha_srcblend) || is_dual_source(blend.alpha_destblend))));
|
||||
if (pixel_info.dual_source_blending) {
|
||||
// MRT1 supplies a second blend source for the same render target as MRT0.
|
||||
pixel_info.target_output_mode[1] = pixel_info.target_output_mode[0];
|
||||
pixel_info.target_export_mapping[1] = pixel_info.target_export_mapping[0];
|
||||
}
|
||||
}
|
||||
if (context.GetClipControl().clip_disable) {
|
||||
const auto& viewport = context.GetScreenViewport().viewports[0];
|
||||
const auto& limits = m_graphics.GetPhysicalDeviceProperties().limits;
|
||||
auto& clip = vertex_info.clip_space;
|
||||
auto& clip = vertex_info[tess_active ? 2u : 0u].clip_space;
|
||||
clip.scale[0] = viewport.xscale;
|
||||
clip.scale[1] = viewport.yscale;
|
||||
clip.offset[0] = viewport.xoffset;
|
||||
@@ -610,7 +634,9 @@ PipelineCache::GraphicsPrograms PipelineCache::GetGraphicsPrograms(
|
||||
if (pixel_active) {
|
||||
result.pixel = m_program_cache->Get(pixel_params, pixel_info, push_data_cursor);
|
||||
}
|
||||
result.vertex = m_program_cache->Get(vertex_params, vertex_info, push_data_cursor);
|
||||
for (uint32_t i = 0; i < (tess_active ? 3u : 1u); i++) {
|
||||
result.vertex[i] = m_program_cache->Get(vertex_params[i], vertex_info[i], push_data_cursor);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
@@ -630,10 +656,12 @@ bool PipelineStaticParameters::operator==(const PipelineStaticParameters& other)
|
||||
|
||||
PipelineCache::Pipeline& PipelineCache::GetGraphicsPipeline(
|
||||
std::span<const RenderColorInfo> colors, const RenderDepthInfo& depth,
|
||||
const ShaderVertexInputInfo& vs_input_info, CommandBuffer& command,
|
||||
std::span<const ShaderVertexInputInfo> vertex_info, CommandBuffer& command,
|
||||
const ShaderPixelInputInfo* ps_input_info, vk::PrimitiveTopology topology,
|
||||
bool primitive_restart_enable, const ShaderProgram& vertex_program,
|
||||
const ShaderProgram& pixel_program) {
|
||||
bool primitive_restart_enable, const GraphicsPrograms& programs) {
|
||||
const auto& vs_input_info = vertex_info.front();
|
||||
const auto& vertex_program = programs.vertex[0];
|
||||
const auto& pixel_program = programs.pixel;
|
||||
KYTY_PROFILER_BLOCK("PipelineCache::CreatePipeline(Gfx)", profiler::colors::DeepOrangeA200);
|
||||
|
||||
EXIT_IF(colors.size() > RENDER_COLOR_ATTACHMENTS_MAX);
|
||||
@@ -651,17 +679,22 @@ PipelineCache::Pipeline& PipelineCache::GetGraphicsPipeline(
|
||||
const auto ps_id = ps_active ? pixel_program.id : 0;
|
||||
|
||||
GraphicsPipelineKey key {};
|
||||
key.vs_shader_id = vs_id;
|
||||
for (uint32_t i = 0; i < programs.vertex.size(); i++) {
|
||||
key.vertex_shader_ids[i] = programs.vertex[i].id;
|
||||
}
|
||||
key.ps_shader_id = ps_id;
|
||||
auto& static_params = key.static_params;
|
||||
auto& rendering = key.rendering;
|
||||
rendering.color_count = color_count;
|
||||
rendering.color_count = 0;
|
||||
uint32_t attachment_samples = 0;
|
||||
for (uint32_t i = 0; i < color_count; i++) {
|
||||
const auto slot = colors[i].target_slot;
|
||||
EXIT_IF(slot >= RENDER_COLOR_ATTACHMENTS_MAX);
|
||||
rendering.color_count = std::max(rendering.color_count, slot + 1);
|
||||
EXIT_IF(!colors[i].image_id || colors[i].desc.view_info.format == vk::Format::eUndefined);
|
||||
static_params.color_mask[i] = colors[i].export_mapping.ApplyMask(
|
||||
static_params.color_mask[slot] = colors[i].export_mapping.ApplyMask(
|
||||
render_target_mask_slot(ctx.GetRenderTargetMask(), colors[i].target_slot));
|
||||
rendering.color_formats[i] = colors[i].desc.view_info.format;
|
||||
rendering.color_formats[slot] = colors[i].desc.view_info.format;
|
||||
if (attachment_samples == 0) {
|
||||
attachment_samples = colors[i].desc.info.samples;
|
||||
} else if (attachment_samples != colors[i].desc.info.samples) {
|
||||
@@ -670,14 +703,14 @@ PipelineCache::Pipeline& PipelineCache::GetGraphicsPipeline(
|
||||
}
|
||||
const auto& rt = ctx.GetRenderTarget(colors[i].target_slot);
|
||||
const auto& bc = ctx.GetBlendControl(colors[i].target_slot);
|
||||
static_params.color_srcblend[i] = bc.color_srcblend;
|
||||
static_params.color_comb_fcn[i] = bc.color_comb_fcn;
|
||||
static_params.color_destblend[i] = bc.color_destblend;
|
||||
static_params.alpha_srcblend[i] = bc.alpha_srcblend;
|
||||
static_params.alpha_comb_fcn[i] = bc.alpha_comb_fcn;
|
||||
static_params.alpha_destblend[i] = bc.alpha_destblend;
|
||||
static_params.separate_alpha_blend[i] = bc.separate_alpha_blend;
|
||||
static_params.blend_enable[i] = bc.enable && !rt.info.blend_bypass;
|
||||
static_params.color_srcblend[slot] = bc.color_srcblend;
|
||||
static_params.color_comb_fcn[slot] = bc.color_comb_fcn;
|
||||
static_params.color_destblend[slot] = bc.color_destblend;
|
||||
static_params.alpha_srcblend[slot] = bc.alpha_srcblend;
|
||||
static_params.alpha_comb_fcn[slot] = bc.alpha_comb_fcn;
|
||||
static_params.alpha_destblend[slot] = bc.alpha_destblend;
|
||||
static_params.separate_alpha_blend[slot] = bc.separate_alpha_blend;
|
||||
static_params.blend_enable[slot] = bc.enable && !rt.info.blend_bypass;
|
||||
}
|
||||
const bool with_depth =
|
||||
depth.desc.view_info.format != vk::Format::eUndefined && static_cast<bool>(depth.image_id);
|
||||
@@ -726,10 +759,7 @@ PipelineCache::Pipeline& PipelineCache::GetGraphicsPipeline(
|
||||
static_params.depth_bounds_test_enable = depth.depth_bounds_test_enable;
|
||||
static_params.depth_min_bounds = depth.depth_min_bounds;
|
||||
static_params.depth_max_bounds = depth.depth_max_bounds;
|
||||
static_params.stencil_test_enable = depth.stencil_test_enable;
|
||||
static_params.stencil_front = depth.stencil_static_front;
|
||||
static_params.stencil_back = depth.stencil_static_back;
|
||||
const bool rect_list = topology == vk::PrimitiveTopology::ePatchList;
|
||||
const bool rect_list = Prospero::IsRectList(command.GetUserConfig().GetPrimType());
|
||||
static_params.cull_back = !rect_list && mc.cull_back;
|
||||
static_params.cull_front = !rect_list && mc.cull_front;
|
||||
static_params.face = mc.face;
|
||||
@@ -780,9 +810,8 @@ PipelineCache::Pipeline& PipelineCache::GetGraphicsPipeline(
|
||||
|
||||
auto cached = std::make_unique<Pipeline>();
|
||||
LogPipelineTrace("CreatePipelineInternal begin", vs_id, ps_id);
|
||||
CreatePipelineInternal(m_graphics, *cached, rendering, key.vertex_input, vs_input_info,
|
||||
vertex_program, ps_input_info, pixel_program, static_params,
|
||||
m_driver_cache);
|
||||
CreatePipelineInternal(m_graphics, *cached, rendering, key.vertex_input, vertex_info,
|
||||
ps_input_info, programs, static_params, m_driver_cache);
|
||||
LogPipelineTrace("CreatePipelineInternal done", vs_id, ps_id);
|
||||
|
||||
EXIT_NOT_IMPLEMENTED(cached->pipeline == nullptr);
|
||||
|
||||
@@ -42,9 +42,6 @@ struct PipelineStaticParameters {
|
||||
bool depth_bounds_test_enable = false;
|
||||
float depth_min_bounds = 0.0f;
|
||||
float depth_max_bounds = 0.0f;
|
||||
bool stencil_test_enable = false;
|
||||
PipelineStencilStaticState stencil_front;
|
||||
PipelineStencilStaticState stencil_back;
|
||||
uint32_t color_mask[RENDER_COLOR_ATTACHMENTS_MAX] = {};
|
||||
bool cull_front = false;
|
||||
bool cull_back = false;
|
||||
@@ -68,7 +65,7 @@ struct PipelineStaticParameters {
|
||||
static_assert(std::is_trivially_copyable_v<PipelineStaticParameters>);
|
||||
static_assert(std::is_standard_layout_v<PipelineStaticParameters>);
|
||||
static_assert(alignof(PipelineStaticParameters) == 1);
|
||||
static_assert(sizeof(PipelineStaticParameters) == 158);
|
||||
static_assert(sizeof(PipelineStaticParameters) == 125);
|
||||
|
||||
struct PipelineRenderingState {
|
||||
std::array<vk::Format, RENDER_COLOR_ATTACHMENTS_MAX> color_formats {};
|
||||
@@ -121,8 +118,10 @@ public:
|
||||
};
|
||||
|
||||
struct GraphicsPrograms {
|
||||
ShaderProgram vertex;
|
||||
std::array<ShaderProgram, 3> vertex;
|
||||
ShaderProgram pixel;
|
||||
|
||||
[[nodiscard]] uint32_t VertexStageCount() const { return vertex[1] ? 3u : 1u; }
|
||||
};
|
||||
|
||||
GraphicsPrograms
|
||||
@@ -130,18 +129,18 @@ public:
|
||||
const HW::PixelShaderInfo& pixel_regs, const HW::ShaderRegisters& sh,
|
||||
const HW::Context& context, const HW::UserConfig& user_config,
|
||||
std::span<const Prospero::ColorComponentMapping, 8> target_export_mapping,
|
||||
bool pixel_active, ShaderVertexInputInfo& vertex_info,
|
||||
bool pixel_active, std::array<ShaderVertexInputInfo, 3>& vertex_info,
|
||||
ShaderPixelInputInfo& pixel_info);
|
||||
ShaderProgram GetComputeProgram(const HW::ComputeShaderInfo& regs,
|
||||
const HW::ShaderRegisters& sh,
|
||||
ShaderComputeInputInfo& input_info);
|
||||
|
||||
Pipeline&
|
||||
GetGraphicsPipeline(std::span<const RenderColorInfo> colors, const RenderDepthInfo& depth,
|
||||
const ShaderVertexInputInfo& vs_input_info, CommandBuffer& command,
|
||||
const ShaderPixelInputInfo* ps_input_info,
|
||||
vk::PrimitiveTopology topology, bool primitive_restart_enable,
|
||||
const ShaderProgram& vertex_program, const ShaderProgram& pixel_program);
|
||||
Pipeline& GetGraphicsPipeline(std::span<const RenderColorInfo> colors,
|
||||
const RenderDepthInfo& depth,
|
||||
std::span<const ShaderVertexInputInfo> vertex_info,
|
||||
CommandBuffer& command, const ShaderPixelInputInfo* ps_input_info,
|
||||
vk::PrimitiveTopology topology, bool primitive_restart_enable,
|
||||
const GraphicsPrograms& programs);
|
||||
Pipeline& GetComputePipeline(const ShaderComputeInputInfo& input_info,
|
||||
const ShaderProgram& compute_program);
|
||||
|
||||
@@ -150,13 +149,13 @@ private:
|
||||
|
||||
struct GraphicsPipelineKey {
|
||||
PipelineRenderingState rendering;
|
||||
uint64_t vs_shader_id = 0;
|
||||
std::array<uint64_t, 3> vertex_shader_ids {};
|
||||
uint64_t ps_shader_id = 0;
|
||||
PipelineVertexInputState vertex_input;
|
||||
PipelineStaticParameters static_params;
|
||||
|
||||
bool operator==(const GraphicsPipelineKey& other) const {
|
||||
return rendering == other.rendering && vs_shader_id == other.vs_shader_id &&
|
||||
return rendering == other.rendering && vertex_shader_ids == other.vertex_shader_ids &&
|
||||
ps_shader_id == other.ps_shader_id && vertex_input == other.vertex_input &&
|
||||
static_params == other.static_params;
|
||||
}
|
||||
@@ -189,7 +188,9 @@ private:
|
||||
std::size_t operator()(const GraphicsPipelineKey& key) const {
|
||||
std::size_t hash = 0;
|
||||
PipelineKeyHash::MixRendering(hash, key.rendering);
|
||||
PipelineKeyHash::Mix(hash, key.vs_shader_id);
|
||||
for (const auto id: key.vertex_shader_ids) {
|
||||
PipelineKeyHash::Mix(hash, id);
|
||||
}
|
||||
PipelineKeyHash::Mix(hash, key.ps_shader_id);
|
||||
PipelineKeyHash::Mix(hash, key.vertex_input.binding_count);
|
||||
for (uint32_t i = 0; i < key.vertex_input.binding_count; i++) {
|
||||
@@ -219,12 +220,14 @@ private:
|
||||
};
|
||||
|
||||
void LogPipelineTrace(const char* phase, uint64_t vertex_program_id, uint64_t pixel_program_id);
|
||||
void CreatePipelineInternal(
|
||||
GraphicContext& graphics, PipelineCache::Pipeline& pipeline,
|
||||
const PipelineRenderingState& rendering, const PipelineVertexInputState& vertex_input,
|
||||
const ShaderVertexInputInfo& vs_input_info, const ShaderProgram& vertex_program,
|
||||
const ShaderPixelInputInfo* ps_input_info, const ShaderProgram& pixel_program,
|
||||
const PipelineStaticParameters& static_params, vk::PipelineCache driver_cache);
|
||||
void CreatePipelineInternal(GraphicContext& graphics, PipelineCache::Pipeline& pipeline,
|
||||
const PipelineRenderingState& rendering,
|
||||
const PipelineVertexInputState& vertex_input,
|
||||
std::span<const ShaderVertexInputInfo> vertex_info,
|
||||
const ShaderPixelInputInfo* ps_input_info,
|
||||
const PipelineCache::GraphicsPrograms& programs,
|
||||
const PipelineStaticParameters& static_params,
|
||||
vk::PipelineCache driver_cache);
|
||||
void CreatePipelineInternal(GraphicContext& graphics, PipelineCache::Pipeline& pipeline,
|
||||
const ShaderComputeInputInfo& input_info,
|
||||
vk::ShaderModule compute_module, vk::PipelineCache driver_cache);
|
||||
|
||||
@@ -8,6 +8,20 @@
|
||||
|
||||
namespace Libs::Graphics {
|
||||
|
||||
vk::ShaderStageFlagBits NativeShaderStage(ShaderType stage) {
|
||||
switch (stage) {
|
||||
case ShaderType::Local:
|
||||
case ShaderType::Vertex: return vk::ShaderStageFlagBits::eVertex;
|
||||
case ShaderType::Mesh: return vk::ShaderStageFlagBits::eMeshEXT;
|
||||
case ShaderType::TessellationControl: return vk::ShaderStageFlagBits::eTessellationControl;
|
||||
case ShaderType::TessellationEvaluation:
|
||||
return vk::ShaderStageFlagBits::eTessellationEvaluation;
|
||||
case ShaderType::Pixel: return vk::ShaderStageFlagBits::eFragment;
|
||||
case ShaderType::Compute: return vk::ShaderStageFlagBits::eCompute;
|
||||
default: EXIT("unknown native shader stage\n");
|
||||
}
|
||||
}
|
||||
|
||||
vk::PipelineStageFlags ShaderPipelineStages(vk::ShaderStageFlags stages) {
|
||||
vk::PipelineStageFlags result = {};
|
||||
if (stages & vk::ShaderStageFlagBits::eVertex) {
|
||||
@@ -16,6 +30,12 @@ vk::PipelineStageFlags ShaderPipelineStages(vk::ShaderStageFlags stages) {
|
||||
if (stages & vk::ShaderStageFlagBits::eMeshEXT) {
|
||||
result |= vk::PipelineStageFlagBits::eMeshShaderEXT;
|
||||
}
|
||||
if (stages & vk::ShaderStageFlagBits::eTessellationControl) {
|
||||
result |= vk::PipelineStageFlagBits::eTessellationControlShader;
|
||||
}
|
||||
if (stages & vk::ShaderStageFlagBits::eTessellationEvaluation) {
|
||||
result |= vk::PipelineStageFlagBits::eTessellationEvaluationShader;
|
||||
}
|
||||
if (stages & vk::ShaderStageFlagBits::eFragment) {
|
||||
result |= vk::PipelineStageFlagBits::eFragmentShader;
|
||||
}
|
||||
@@ -26,8 +46,8 @@ vk::PipelineStageFlags ShaderPipelineStages(vk::ShaderStageFlags stages) {
|
||||
return result;
|
||||
}
|
||||
|
||||
VulkanMemoryBarrier MakeShaderWriteDependency() {
|
||||
VulkanMemoryBarrier barrier {};
|
||||
vk::MemoryBarrier MakeShaderWriteDependency() {
|
||||
vk::MemoryBarrier barrier {};
|
||||
barrier.srcAccessMask = vk::AccessFlagBits::eShaderWrite;
|
||||
barrier.dstAccessMask = vk::AccessFlagBits::eShaderRead | vk::AccessFlagBits::eShaderWrite |
|
||||
vk::AccessFlagBits::eVertexAttributeRead |
|
||||
@@ -38,15 +58,15 @@ VulkanMemoryBarrier MakeShaderWriteDependency() {
|
||||
return barrier;
|
||||
}
|
||||
|
||||
VulkanMemoryBarrier MakeShaderAccessDependency() {
|
||||
VulkanMemoryBarrier barrier {};
|
||||
vk::MemoryBarrier MakeShaderAccessDependency() {
|
||||
vk::MemoryBarrier barrier {};
|
||||
barrier.srcAccessMask = vk::AccessFlagBits::eShaderRead | vk::AccessFlagBits::eShaderWrite;
|
||||
barrier.dstAccessMask = vk::AccessFlagBits::eMemoryRead | vk::AccessFlagBits::eMemoryWrite;
|
||||
return barrier;
|
||||
}
|
||||
|
||||
VulkanMemoryBarrier MakeShaderWriteHazardDependency() {
|
||||
VulkanMemoryBarrier barrier {};
|
||||
vk::MemoryBarrier MakeShaderWriteHazardDependency() {
|
||||
vk::MemoryBarrier barrier {};
|
||||
barrier.srcAccessMask = vk::AccessFlagBits::eMemoryRead | vk::AccessFlagBits::eMemoryWrite;
|
||||
barrier.dstAccessMask = vk::AccessFlagBits::eShaderRead | vk::AccessFlagBits::eShaderWrite;
|
||||
return barrier;
|
||||
@@ -70,7 +90,7 @@ vk::BufferMemoryBarrier MakeGdsDependency(vk::Buffer buffer) {
|
||||
bool HasShaderBufferWrites(const ShaderStageRuntime& runtime) {
|
||||
EXIT_IF(!runtime);
|
||||
const auto& program = *runtime.program;
|
||||
const auto& resources = runtime.resources;
|
||||
const auto& resources = *runtime.resources;
|
||||
EXIT_IF(resources.buffers.size() != program.info.buffers.size());
|
||||
bool has_writes = false;
|
||||
for (uint32_t i = 0; i < program.info.buffers.size(); i++) {
|
||||
|
||||
@@ -8,10 +8,11 @@ namespace Libs::Graphics {
|
||||
|
||||
struct ShaderStageRuntime;
|
||||
|
||||
vk::ShaderStageFlagBits NativeShaderStage(ShaderType stage);
|
||||
vk::PipelineStageFlags ShaderPipelineStages(vk::ShaderStageFlags stages);
|
||||
VulkanMemoryBarrier MakeShaderAccessDependency();
|
||||
VulkanMemoryBarrier MakeShaderWriteHazardDependency();
|
||||
VulkanMemoryBarrier MakeShaderWriteDependency();
|
||||
vk::MemoryBarrier MakeShaderAccessDependency();
|
||||
vk::MemoryBarrier MakeShaderWriteHazardDependency();
|
||||
vk::MemoryBarrier MakeShaderWriteDependency();
|
||||
vk::BufferMemoryBarrier MakeGdsDependency(vk::Buffer buffer);
|
||||
bool HasShaderBufferWrites(const ShaderStageRuntime& runtime);
|
||||
void ShaderAccessBarrier(vk::CommandBuffer vk_buffer, vk::PipelineStageFlags source_stages);
|
||||
|
||||
@@ -8,6 +8,7 @@
|
||||
#include "graphics/host_gpu/renderer/debug.h"
|
||||
#include "graphics/host_gpu/renderer/pipeline/descriptors.h"
|
||||
#include "graphics/host_gpu/renderer/pipeline/pipelineCache.h"
|
||||
#include "graphics/host_gpu/renderer/pipeline/shaderResourceBarrier.h"
|
||||
#include "graphics/host_gpu/renderer/render.h"
|
||||
#include "graphics/host_gpu/renderer/renderContext.h"
|
||||
#include "graphics/host_gpu/renderer/renderTarget.h"
|
||||
@@ -205,12 +206,18 @@ static void CreateDescriptorLayout(GraphicContext& graphics, PipelineCache::Pipe
|
||||
}
|
||||
|
||||
// NOLINTNEXTLINE(readability-function-cognitive-complexity)
|
||||
void CreatePipelineInternal(
|
||||
GraphicContext& graphics, PipelineCache::Pipeline& pipeline,
|
||||
const PipelineRenderingState& rendering, const PipelineVertexInputState& vertex_input,
|
||||
const ShaderVertexInputInfo& vs_input_info, const ShaderProgram& vertex_program,
|
||||
const ShaderPixelInputInfo* ps_input_info, const ShaderProgram& pixel_program,
|
||||
const PipelineStaticParameters& static_params, vk::PipelineCache driver_cache) {
|
||||
void CreatePipelineInternal(GraphicContext& graphics, PipelineCache::Pipeline& pipeline,
|
||||
const PipelineRenderingState& rendering,
|
||||
const PipelineVertexInputState& vertex_input,
|
||||
std::span<const ShaderVertexInputInfo> vertex_info,
|
||||
const ShaderPixelInputInfo* ps_input_info,
|
||||
const PipelineCache::GraphicsPrograms& programs,
|
||||
const PipelineStaticParameters& static_params,
|
||||
vk::PipelineCache driver_cache) {
|
||||
const auto& vs_input_info = vertex_info.front();
|
||||
const auto& vertex_program = programs.vertex[0];
|
||||
const auto& pixel_program = programs.pixel;
|
||||
const bool tessellation = vertex_info.size() == 3;
|
||||
const bool ps_active = ps_input_info != nullptr;
|
||||
EXIT_IF(!vertex_program || (ps_active && !pixel_program));
|
||||
const bool with_depth = rendering.depth_format != vk::Format::eUndefined ||
|
||||
@@ -218,46 +225,39 @@ void CreatePipelineInternal(
|
||||
EXIT_IF(!vs_input_info.stage);
|
||||
const bool mesh = vs_input_info.stage.program->stage == ShaderType::Mesh;
|
||||
EXIT_NOT_IMPLEMENTED(mesh && !graphics.mesh_shader_enabled);
|
||||
const auto vertex_stage =
|
||||
mesh ? vk::ShaderStageFlagBits::eMeshEXT : vk::ShaderStageFlagBits::eVertex;
|
||||
|
||||
const bool rect_list = !mesh && static_params.topology == vk::PrimitiveTopology::ePatchList;
|
||||
const bool rect_list =
|
||||
!mesh && !tessellation && static_params.topology == vk::PrimitiveTopology::ePatchList;
|
||||
|
||||
vk::ShaderModule tess_control_shader_module = nullptr;
|
||||
vk::ShaderModule tess_eval_shader_module = nullptr;
|
||||
|
||||
vk::ShaderModuleCreateInfo create_info {};
|
||||
vk::Result result {};
|
||||
if (rect_list) {
|
||||
const auto shaders =
|
||||
BuildRectListShaders(vs_input_info, ps_active ? ps_input_info : nullptr);
|
||||
create_info.codeSize = shaders.control.size() * 4;
|
||||
create_info.pCode = shaders.control.data();
|
||||
result =
|
||||
graphics.device.createShaderModule(&create_info, nullptr, &tess_control_shader_module);
|
||||
tess_control_shader_module = CompileSPV(shaders.control, graphics.device);
|
||||
if (graphics_debug_dump_enabled()) {
|
||||
LOGF("PipelineTrace: vkCreateShaderModule RectList TCS done result=%s module=%p\n",
|
||||
vk::to_string(result).c_str(), static_cast<void*>(tess_control_shader_module));
|
||||
LOGF("PipelineTrace: vkCreateShaderModule RectList TCS done module=%p\n",
|
||||
static_cast<void*>(tess_control_shader_module));
|
||||
}
|
||||
EXIT_NOT_IMPLEMENTED(result != vk::Result::eSuccess);
|
||||
|
||||
create_info.codeSize = shaders.evaluation.size() * 4;
|
||||
create_info.pCode = shaders.evaluation.data();
|
||||
result =
|
||||
graphics.device.createShaderModule(&create_info, nullptr, &tess_eval_shader_module);
|
||||
tess_eval_shader_module = CompileSPV(shaders.evaluation, graphics.device);
|
||||
if (graphics_debug_dump_enabled()) {
|
||||
LOGF("PipelineTrace: vkCreateShaderModule RectList TES done result=%s module=%p\n",
|
||||
vk::to_string(result).c_str(), static_cast<void*>(tess_eval_shader_module));
|
||||
LOGF("PipelineTrace: vkCreateShaderModule RectList TES done module=%p\n",
|
||||
static_cast<void*>(tess_eval_shader_module));
|
||||
}
|
||||
EXIT_NOT_IMPLEMENTED(result != vk::Result::eSuccess);
|
||||
}
|
||||
|
||||
EXIT_NOT_IMPLEMENTED(
|
||||
rect_list && (tess_control_shader_module == nullptr || tess_eval_shader_module == nullptr));
|
||||
|
||||
vk::PipelineShaderStageCreateInfo shader_stages[4] = {
|
||||
{.stage = vertex_stage, .module = vertex_program.module, .pName = "main"}};
|
||||
uint32_t shader_stage_count = 1;
|
||||
vk::PipelineShaderStageCreateInfo shader_stages[4] {};
|
||||
uint32_t shader_stage_count = 0;
|
||||
for (uint32_t i = 0; i < vertex_info.size(); i++) {
|
||||
shader_stages[shader_stage_count++] = {.stage =
|
||||
NativeShaderStage(vertex_info[i].logical_stage),
|
||||
.module = programs.vertex[i].module,
|
||||
.pName = "main"};
|
||||
}
|
||||
if (rect_list) {
|
||||
shader_stages[shader_stage_count++] = {.stage =
|
||||
vk::ShaderStageFlagBits::eTessellationControl,
|
||||
@@ -432,15 +432,19 @@ void CreatePipelineInternal(
|
||||
color_blending.pAttachments = color_blend_attachment;
|
||||
|
||||
std::vector<vk::DescriptorSetLayoutBinding> descriptor_bindings;
|
||||
AddLayoutBindings(descriptor_bindings, *vs_input_info.stage.program, vertex_stage);
|
||||
vk::ShaderStageFlags graphics_stages = vk::ShaderStageFlagBits::eFragment;
|
||||
for (const auto& stage: vertex_info) {
|
||||
const auto native_stage = NativeShaderStage(stage.logical_stage);
|
||||
AddLayoutBindings(descriptor_bindings, *stage.stage.program, native_stage);
|
||||
graphics_stages |= native_stage;
|
||||
}
|
||||
if (ps_active) {
|
||||
EXIT_IF(!ps_input_info->stage);
|
||||
AddLayoutBindings(descriptor_bindings, *ps_input_info->stage.program,
|
||||
vk::ShaderStageFlagBits::eFragment);
|
||||
}
|
||||
CreateDescriptorLayout(graphics, pipeline, descriptor_bindings);
|
||||
const auto GraphicsStages = vertex_stage | vk::ShaderStageFlagBits::eFragment;
|
||||
const vk::PushConstantRange push_constants {GraphicsStages, 0,
|
||||
const vk::PushConstantRange push_constants {graphics_stages, 0,
|
||||
ShaderRecompiler::IR::NativePushConstantSize};
|
||||
|
||||
vk::PipelineLayoutCreateInfo pipeline_layout_info {};
|
||||
@@ -456,8 +460,8 @@ void CreatePipelineInternal(
|
||||
" set_layouts=1 push_constants=%" PRIu32 "\n",
|
||||
vertex_program.id, ps_active ? pixel_program.id : 0, 1u);
|
||||
}
|
||||
result = graphics.device.createPipelineLayout(&pipeline_layout_info, nullptr,
|
||||
&pipeline.pipeline_layout);
|
||||
auto result = graphics.device.createPipelineLayout(&pipeline_layout_info, nullptr,
|
||||
&pipeline.pipeline_layout);
|
||||
if (graphics_debug_dump_enabled()) {
|
||||
LOGF("PipelineTrace: vkCreatePipelineLayout done result=%s layout=%p\n",
|
||||
vk::to_string(result).c_str(), static_cast<void*>(pipeline.pipeline_layout));
|
||||
@@ -473,15 +477,6 @@ void CreatePipelineInternal(
|
||||
#else
|
||||
(static_params.depth_bounds_test_enable ? VK_TRUE : VK_FALSE);
|
||||
#endif
|
||||
depth_stencil_info.stencilTestEnable = (static_params.stencil_test_enable ? VK_TRUE : VK_FALSE);
|
||||
depth_stencil_info.front.failOp = static_params.stencil_front.failOp;
|
||||
depth_stencil_info.front.passOp = static_params.stencil_front.passOp;
|
||||
depth_stencil_info.front.depthFailOp = static_params.stencil_front.depthFailOp;
|
||||
depth_stencil_info.front.compareOp = static_params.stencil_front.compareOp;
|
||||
depth_stencil_info.back.failOp = static_params.stencil_back.failOp;
|
||||
depth_stencil_info.back.passOp = static_params.stencil_back.passOp;
|
||||
depth_stencil_info.back.depthFailOp = static_params.stencil_back.depthFailOp;
|
||||
depth_stencil_info.back.compareOp = static_params.stencil_back.compareOp;
|
||||
depth_stencil_info.minDepthBounds = static_params.depth_min_bounds;
|
||||
depth_stencil_info.maxDepthBounds = static_params.depth_max_bounds;
|
||||
|
||||
@@ -494,6 +489,8 @@ void CreatePipelineInternal(
|
||||
vk::DynamicState::eDepthCompareOp,
|
||||
vk::DynamicState::eDepthBiasEnable,
|
||||
vk::DynamicState::eDepthBias,
|
||||
vk::DynamicState::eStencilTestEnable,
|
||||
vk::DynamicState::eStencilOp,
|
||||
vk::DynamicState::eStencilCompareMask,
|
||||
vk::DynamicState::eStencilReference,
|
||||
vk::DynamicState::eStencilWriteMask,
|
||||
@@ -524,8 +521,9 @@ void CreatePipelineInternal(
|
||||
pipeline_info.pVertexInputState = mesh ? nullptr : &vertex_input_info;
|
||||
pipeline_info.pInputAssemblyState = mesh ? nullptr : &input_assembly;
|
||||
vk::PipelineTessellationStateCreateInfo tessellation_state {};
|
||||
tessellation_state.patchControlPoints = 3;
|
||||
pipeline_info.pTessellationState = (rect_list ? &tessellation_state : nullptr);
|
||||
tessellation_state.patchControlPoints =
|
||||
tessellation ? vs_input_info.tess.input_control_points : 3u;
|
||||
pipeline_info.pTessellationState = (rect_list || tessellation) ? &tessellation_state : nullptr;
|
||||
pipeline_info.pViewportState = &viewport_state;
|
||||
pipeline_info.pRasterizationState = &rasterizer;
|
||||
pipeline_info.pMultisampleState = &multisampling;
|
||||
|
||||
@@ -45,6 +45,7 @@ enum class CommandBufferDebugOp : uint32_t {
|
||||
EopFlip,
|
||||
EopWriteBackFlip,
|
||||
EopOnlyFlip,
|
||||
DispatchIndirect,
|
||||
Unknown,
|
||||
};
|
||||
|
||||
@@ -156,6 +157,8 @@ public:
|
||||
|
||||
void DispatchDirect(uint64_t submit_id, CommandBuffer& buffer, uint32_t thread_group_x,
|
||||
uint32_t thread_group_y, uint32_t thread_group_z, uint32_t mode);
|
||||
void DispatchIndirect(uint64_t submit_id, CommandBuffer& buffer, uint64_t args_addr,
|
||||
uint32_t mode);
|
||||
|
||||
[[nodiscard]] PreparedBindings PrepareBindings(const ShaderStageRuntime& runtime);
|
||||
void FindBuffers(PreparedBindings& bindings);
|
||||
@@ -170,15 +173,14 @@ private:
|
||||
void DrawAuto(uint64_t submit_id, CommandBuffer& buffer, const DrawAutoArgs& args);
|
||||
|
||||
struct GraphicsBindings {
|
||||
PreparedBindings vertex;
|
||||
std::array<PreparedBindings, 3> vertex;
|
||||
std::optional<PreparedBindings> pixel;
|
||||
};
|
||||
|
||||
[[nodiscard]] TextureBinding ResolveTexture(const ShaderRecompiler::IR::ImageResource& resource,
|
||||
const ShaderRecompiler::IR::DescriptorValue& value);
|
||||
[[nodiscard]] GraphicsBindings PrepareGraphicsBindings(const ShaderStageRuntime& vertex,
|
||||
const ShaderStageRuntime& pixel,
|
||||
bool pixel_active);
|
||||
void PrepareGraphicsBindings(std::span<PreparedBindings* const> stages,
|
||||
std::span<RenderColorInfo> colors);
|
||||
void ResolveRenderColorTarget(CommandBuffer& buffer, RenderColorInfo& target,
|
||||
uint32_t render_target_slice_offset, uint32_t render_target_slot,
|
||||
bool ignore_target_mask = false, bool exact_format = false);
|
||||
|
||||
@@ -9,6 +9,7 @@
|
||||
#include "graphics/guest_gpu/gpu_defs.h"
|
||||
#include "graphics/guest_gpu/graphicsRun.h"
|
||||
#include "graphics/guest_gpu/hardwareContext.h"
|
||||
#include "graphics/guest_gpu/pm4.h"
|
||||
#include "graphics/host_gpu/graphicContext.h"
|
||||
#include "graphics/host_gpu/renderer/image/imageInfo.h"
|
||||
#include "graphics/host_gpu/renderer/pipeline/descriptors.h"
|
||||
@@ -51,7 +52,7 @@ static bool FillSourcesDisjoint(std::span<const ShaderRecompiler::IR::Descriptor
|
||||
bool RenderExecutor::TryConsumeComputeMetaClear(const ShaderComputeInputInfo& input,
|
||||
const CommandBuffer& buffer) {
|
||||
const auto& program = *input.stage.program;
|
||||
const auto& resources = input.stage.resources;
|
||||
const auto& resources = *input.stage.resources;
|
||||
if (resources.buffers.size() != program.info.buffers.size()) {
|
||||
EXIT("compute runtime buffer count does not match shader metadata\n");
|
||||
}
|
||||
@@ -85,7 +86,7 @@ bool ResolveComputeBufferFill(const ShaderComputeInputInfo& input, uint32_t grou
|
||||
uint32_t group_y, uint32_t group_z, uint32_t mode,
|
||||
ShaderBufferResource& resolved_descriptor, uint32_t& resolved_clear,
|
||||
uint64_t& resolved_size) {
|
||||
const auto& resources = input.stage.resources;
|
||||
const auto& resources = *input.stage.resources;
|
||||
const auto& fill = resources.uniform_fill;
|
||||
if (fill.kind != ShaderRecompiler::IR::UniformFillKind::Buffer) {
|
||||
return false;
|
||||
@@ -128,7 +129,7 @@ bool RenderExecutor::TryConsumeComputeImageClear(const ShaderComputeInputInfo& i
|
||||
CommandBuffer& command, uint32_t group_x,
|
||||
uint32_t group_y, uint32_t group_z, uint32_t mode) {
|
||||
const auto& program = *input.stage.program;
|
||||
const auto& resources = input.stage.resources;
|
||||
const auto& resources = *input.stage.resources;
|
||||
const auto& fill = resources.uniform_fill;
|
||||
auto& cache = command.GetContext().GetTextureCache();
|
||||
if (fill.kind == ShaderRecompiler::IR::UniformFillKind::Image) {
|
||||
@@ -172,7 +173,7 @@ bool RenderExecutor::TryConsumeComputeImageClear(const ShaderComputeInputInfo& i
|
||||
1, view.base_layer, view.layer_count};
|
||||
vk::ClearValue clear {};
|
||||
clear.depthStencil = vk::ClearDepthStencilValue {0.0f, fill.value};
|
||||
cache.ClearImage(command, binding.image_id, range, clear);
|
||||
cache.ClearImage(command, binding.image_id, image.backing.format, range, clear);
|
||||
return true;
|
||||
}
|
||||
ShaderBufferResource descriptor;
|
||||
@@ -183,14 +184,6 @@ bool RenderExecutor::TryConsumeComputeImageClear(const ShaderComputeInputInfo& i
|
||||
return false;
|
||||
}
|
||||
if (!cache.ClearImageFromBuffer(command, descriptor.Base48(), size, packed_clear)) {
|
||||
// Track deferred DCC state while the original dispatch writes the metadata allocation.
|
||||
cache.TrackDccFill(descriptor.Base48(), size, packed_clear);
|
||||
static std::atomic<uint32_t> logged_metadata_clears {0};
|
||||
if (logged_metadata_clears.fetch_add(1, std::memory_order_relaxed) < 32) {
|
||||
LOGF("GraphicsRenderDispatchDirect: metadata fill shader=0x%016" PRIx64
|
||||
" addr=0x%016" PRIx64 " size=0x%016" PRIx64 " value=0x%08" PRIx32 "\n",
|
||||
input.stage.program->shader_hash, descriptor.Base48(), size, packed_clear);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
static std::atomic<uint32_t> logged_clears {0};
|
||||
@@ -214,6 +207,17 @@ void RenderExecutor::DispatchDirect(uint64_t submit_id, CommandBuffer& buffer,
|
||||
thread_group_x, thread_group_y, thread_group_z, mode,
|
||||
sh_ctx.GetCs().cs_regs.data_addr);
|
||||
|
||||
if (thread_group_x == 0 || thread_group_y == 0 || thread_group_z == 0) {
|
||||
static std::atomic<uint32_t> log_count {0};
|
||||
if (log_count.fetch_add(1, std::memory_order_relaxed) < 32) {
|
||||
LOGF("GraphicsRenderDispatchDirect: skipping zero-sized dispatch groups=%ux%ux%u "
|
||||
"mode=0x%08" PRIx32 " shader=0x%016" PRIx64 "\n",
|
||||
thread_group_x, thread_group_y, thread_group_z, mode,
|
||||
sh_ctx.GetCs().cs_regs.data_addr);
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
Common::LockGuard lock(m_context.GetMutex());
|
||||
if (sh_ctx.GetCs().cs_regs.data_addr == 0) {
|
||||
LOGF("GraphicsRenderDispatchDirect: temporary: ignoring dispatch with null CS shader, "
|
||||
@@ -222,7 +226,7 @@ void RenderExecutor::DispatchDirect(uint64_t submit_id, CommandBuffer& buffer,
|
||||
return;
|
||||
}
|
||||
|
||||
if (!ShaderAddressValid(sh_ctx.GetCs().cs_regs.data_addr)) {
|
||||
if (sh_ctx.GetCs().cs_regs.data_addr == 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -259,7 +263,7 @@ void RenderExecutor::DispatchDirect(uint64_t submit_id, CommandBuffer& buffer,
|
||||
}
|
||||
|
||||
const auto& program = *input_info.stage.program;
|
||||
const auto& resources = input_info.stage.resources;
|
||||
const auto& resources = *input_info.stage.resources;
|
||||
if (TryConsumeComputeMetaClear(input_info, buffer)) {
|
||||
ResetBindings();
|
||||
return;
|
||||
@@ -355,17 +359,6 @@ void RenderExecutor::DispatchDirect(uint64_t submit_id, CommandBuffer& buffer,
|
||||
}
|
||||
}
|
||||
|
||||
if (thread_group_x == 0 || thread_group_y == 0 || thread_group_z == 0) {
|
||||
static std::atomic<uint32_t> log_count {0};
|
||||
if (log_count.fetch_add(1, std::memory_order_relaxed) < 32) {
|
||||
LOGF("GraphicsRenderDispatchDirect: skipping zero-sized dispatch groups=%ux%ux%u "
|
||||
"mode=0x%08" PRIx32 " shader=0x%016" PRIx64 "\n",
|
||||
thread_group_x, thread_group_y, thread_group_z, mode,
|
||||
sh_ctx.GetCs().cs_regs.data_addr);
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
buffer.EndRendering();
|
||||
auto& pipeline =
|
||||
m_context.GetPipelineCache().GetComputePipeline(input_info, compute_program);
|
||||
@@ -374,8 +367,8 @@ void RenderExecutor::DispatchDirect(uint64_t submit_id, CommandBuffer& buffer,
|
||||
if (program.info.uses_dma) {
|
||||
m_context.PrepareBda();
|
||||
}
|
||||
RebindBuffers(bindings);
|
||||
RebindImages(bindings);
|
||||
RebindBuffers(bindings);
|
||||
|
||||
auto vk_buffer = buffer.Handle();
|
||||
PreparedBindings* descriptor_stage = &bindings;
|
||||
@@ -403,4 +396,60 @@ void RenderExecutor::DispatchDirect(uint64_t submit_id, CommandBuffer& buffer,
|
||||
ResetBindings();
|
||||
}
|
||||
|
||||
void RenderExecutor::DispatchIndirect(uint64_t submit_id, CommandBuffer& buffer,
|
||||
uint64_t args_addr, uint32_t mode) {
|
||||
EXIT_IF(buffer.IsInvalid() || args_addr == 0 || (args_addr & 3u) != 0 ||
|
||||
(mode & Pm4::COMPUTE_DISPATCH_INITIATOR_USE_THREAD_DIMENSIONS) != 0);
|
||||
m_context.GetCommandScheduler().PopPendingOperations();
|
||||
buffer.SetDebugInfo(static_cast<uint32_t>(CommandBufferDebugOp::DispatchIndirect), submit_id,
|
||||
static_cast<uint32_t>(args_addr), static_cast<uint32_t>(args_addr >> 32u),
|
||||
0, mode, buffer.GetShaders().GetCs().cs_regs.data_addr);
|
||||
Common::LockGuard lock(m_context.GetMutex());
|
||||
const auto& cs_regs = buffer.GetShaders().GetCs();
|
||||
if (cs_regs.cs_regs.data_addr == 0) {
|
||||
return;
|
||||
}
|
||||
ShaderComputeInputInfo input_info {};
|
||||
const auto compute_program = m_context.GetPipelineCache().GetComputeProgram(
|
||||
cs_regs, buffer.GetRegisters().GetShaderRegisters(), input_info);
|
||||
buffer.EndRendering();
|
||||
auto& pipeline = m_context.GetPipelineCache().GetComputePipeline(input_info, compute_program);
|
||||
auto bindings = PrepareBindings(input_info.stage);
|
||||
FindBuffers(bindings);
|
||||
const auto& program = *input_info.stage.program;
|
||||
if (program.info.uses_dma) {
|
||||
m_context.PrepareBda();
|
||||
}
|
||||
RebindImages(bindings);
|
||||
// Acquiring arguments can merge cache buffers; finalize shader bindings afterward.
|
||||
const auto [args_buffer, args_offset] = m_context.GetBufferCache().ObtainBuffer(
|
||||
args_addr, sizeof(vk::DispatchIndirectCommand), false);
|
||||
EXIT_IF(args_buffer == nullptr || (args_offset & 3u) != 0);
|
||||
RebindBuffers(bindings);
|
||||
PreparedBindings* descriptor_stage = &bindings;
|
||||
CommitBindings(buffer, vk::PipelineBindPoint::eCompute, pipeline,
|
||||
std::span {&descriptor_stage, 1u});
|
||||
const auto vk_buffer = buffer.Handle();
|
||||
const bool has_storage_writes = HasShaderBufferWrites(input_info.stage) ||
|
||||
std::any_of(program.info.images.begin(), program.info.images.end(), [](const auto& image) {
|
||||
return image.written && image.resource_class ==
|
||||
ShaderRecompiler::IR::ImageResourceClass::Storage;
|
||||
});
|
||||
if (has_storage_writes) {
|
||||
ShaderWriteHazardBarrier(vk_buffer, vk::PipelineStageFlagBits::eComputeShader);
|
||||
}
|
||||
vk::MemoryBarrier barrier {};
|
||||
barrier.srcAccessMask = vk::AccessFlagBits::eShaderWrite | vk::AccessFlagBits::eTransferWrite;
|
||||
barrier.dstAccessMask = vk::AccessFlagBits::eIndirectCommandRead;
|
||||
vk_buffer.pipelineBarrier(vk::PipelineStageFlagBits::eAllGraphics |
|
||||
vk::PipelineStageFlagBits::eComputeShader |
|
||||
vk::PipelineStageFlagBits::eTransfer,
|
||||
vk::PipelineStageFlagBits::eDrawIndirect, {},
|
||||
1, &barrier, 0, nullptr, 0, nullptr);
|
||||
vk_buffer.bindPipeline(vk::PipelineBindPoint::eCompute, pipeline.pipeline);
|
||||
vk_buffer.dispatchIndirect(args_buffer->Handle(), args_offset);
|
||||
ShaderAccessBarrier(vk_buffer, vk::PipelineStageFlagBits::eComputeShader);
|
||||
ResetBindings();
|
||||
}
|
||||
|
||||
} // namespace Libs::Graphics
|
||||
@@ -2,6 +2,7 @@
|
||||
|
||||
#include "common/assert.h"
|
||||
#include "common/common.h"
|
||||
#include "common/emulatorConfig.h"
|
||||
#include "common/file.h"
|
||||
#include "common/logging/log.h"
|
||||
#include "common/profiler.h"
|
||||
@@ -15,6 +16,7 @@
|
||||
#include "graphics/host_gpu/renderer/colorRenderTarget.h"
|
||||
#include "graphics/host_gpu/renderer/debug.h"
|
||||
#include "graphics/host_gpu/renderer/depthRenderTarget.h"
|
||||
#include "graphics/host_gpu/renderer/image/textureCommon.h"
|
||||
#include "graphics/host_gpu/renderer/pipeline/pipelineCache.h"
|
||||
#include "graphics/host_gpu/renderer/pipeline/shaderResourceBarrier.h"
|
||||
#include "graphics/host_gpu/renderer/render.h"
|
||||
@@ -56,7 +58,7 @@ std::pair<int32_t, uint32_t> ResolveDrawOffsets(uint32_t index_offset,
|
||||
|
||||
EXIT_IF(!vs_input_info.stage);
|
||||
const auto& program = *vs_input_info.stage.program;
|
||||
const auto& resources = vs_input_info.stage.resources;
|
||||
const auto& resources = *vs_input_info.stage.resources;
|
||||
if (index_offset == 0 &&
|
||||
program.info.vertex_offset_sgpr >= static_cast<int32_t>(program.user_data_base)) {
|
||||
const auto index =
|
||||
@@ -308,24 +310,12 @@ static void LogDrawInputState(const CommandBuffer& buffer, const RenderColorInfo
|
||||
|
||||
static void SetGraphicsDynamicParams(const CommandBuffer& buffer, vk::CommandBuffer vk_buffer,
|
||||
const ShaderVertexInputInfo& vs_input_info,
|
||||
const RenderColorInfo* colors, uint32_t color_count,
|
||||
const RenderDepthInfo& depth) {
|
||||
const RenderDepthInfo& depth, const RenderState& rendering) {
|
||||
KYTY_PROFILER_FUNCTION();
|
||||
|
||||
EXIT_IF(colors == nullptr);
|
||||
const auto& ctx = buffer.GetRegisters();
|
||||
|
||||
const auto& vp = ctx.GetScreenViewport();
|
||||
vk::Extent2D framebuffer_extent {};
|
||||
if (color_count > 0 && colors[0].image_id) {
|
||||
framebuffer_extent = colors[0].Extent();
|
||||
} else if (depth.image_id) {
|
||||
framebuffer_extent = {depth.desc.info.extent.width, depth.desc.info.extent.height};
|
||||
} else {
|
||||
const auto& limits = buffer.GetGraphics().GetPhysicalDeviceProperties().limits;
|
||||
framebuffer_extent = {limits.maxFramebufferWidth, limits.maxFramebufferHeight};
|
||||
}
|
||||
|
||||
const auto& vp = ctx.GetScreenViewport();
|
||||
const vk::Extent2D framebuffer_extent {rendering.width, rendering.height};
|
||||
const auto& outputs = vs_input_info.stage.program->info.outputs;
|
||||
const bool indexed_viewports =
|
||||
std::any_of(outputs.begin(), outputs.end(), [](const auto& output) {
|
||||
@@ -403,19 +393,16 @@ static void SetGraphicsDynamicParams(const CommandBuffer& buffer, vk::CommandBuf
|
||||
vk_buffer.setDepthBias(constant_factor, poly_offset.clamp, slope_factor);
|
||||
}
|
||||
|
||||
vk_buffer.setStencilTestEnable(depth.stencil_test_enable ? VK_TRUE : VK_FALSE);
|
||||
if (depth.stencil_test_enable) {
|
||||
vk_buffer.setStencilCompareMask(vk::StencilFaceFlagBits::eFront,
|
||||
depth.stencil_dynamic_front.compareMask);
|
||||
vk_buffer.setStencilCompareMask(vk::StencilFaceFlagBits::eBack,
|
||||
depth.stencil_dynamic_back.compareMask);
|
||||
vk_buffer.setStencilWriteMask(vk::StencilFaceFlagBits::eFront,
|
||||
depth.stencil_dynamic_front.writeMask);
|
||||
vk_buffer.setStencilWriteMask(vk::StencilFaceFlagBits::eBack,
|
||||
depth.stencil_dynamic_back.writeMask);
|
||||
vk_buffer.setStencilReference(vk::StencilFaceFlagBits::eFront,
|
||||
depth.stencil_dynamic_front.reference);
|
||||
vk_buffer.setStencilReference(vk::StencilFaceFlagBits::eBack,
|
||||
depth.stencil_dynamic_back.reference);
|
||||
const auto set_stencil = [&](vk::StencilFaceFlagBits face, const vk::StencilOpState& state) {
|
||||
vk_buffer.setStencilOp(face, state.failOp, state.passOp, state.depthFailOp, state.compareOp);
|
||||
vk_buffer.setStencilCompareMask(face, state.compareMask);
|
||||
vk_buffer.setStencilWriteMask(face, state.writeMask);
|
||||
vk_buffer.setStencilReference(face, state.reference);
|
||||
};
|
||||
set_stencil(vk::StencilFaceFlagBits::eFront, depth.stencil_front);
|
||||
set_stencil(vk::StencilFaceFlagBits::eBack, depth.stencil_back);
|
||||
}
|
||||
|
||||
#if defined(__APPLE__)
|
||||
@@ -423,15 +410,11 @@ static void SetGraphicsDynamicParams(const CommandBuffer& buffer, vk::CommandBuf
|
||||
// eColorWriteEnableEXT dynamic state and relies on the static colorWriteMask instead.
|
||||
#else
|
||||
vk::Bool32 enable[RENDER_COLOR_ATTACHMENTS_MAX] = {};
|
||||
// Color-control operation selects special color-buffer paths, not the normal component write
|
||||
// mask. Attachment availability therefore follows the target write mask.
|
||||
for (uint32_t i = 0; i < color_count; i++) {
|
||||
enable[i] = render_target_mask_slot(ctx.GetRenderTargetMask(), colors[i].target_slot) != 0
|
||||
? VK_TRUE
|
||||
: VK_FALSE;
|
||||
for (uint32_t slot = 0; slot < rendering.num_color_attachments; slot++) {
|
||||
enable[slot] = rendering.color_attachments[slot].image_view != nullptr;
|
||||
}
|
||||
if (color_count != 0) {
|
||||
vk_buffer.setColorWriteEnableEXT(color_count, enable);
|
||||
if (rendering.num_color_attachments != 0) {
|
||||
vk_buffer.setColorWriteEnableEXT(rendering.num_color_attachments, enable);
|
||||
}
|
||||
#endif
|
||||
}
|
||||
@@ -439,7 +422,7 @@ static void SetGraphicsDynamicParams(const CommandBuffer& buffer, vk::CommandBuf
|
||||
static bool DrawHasValidVertexShader(const HW::Shader& sh_ctx) {
|
||||
|
||||
const auto& vs = sh_ctx.GetVs();
|
||||
return ShaderAddressValid(vs.es_regs.data_addr);
|
||||
return vs.es_regs.data_addr != 0;
|
||||
}
|
||||
|
||||
static bool PixelShaderHasDepthOrCoverageSideEffects(const HW::ShaderRegisters& sh_regs) {
|
||||
@@ -453,7 +436,7 @@ struct DrawRenderState {
|
||||
RenderColorInfo color_info[RENDER_COLOR_ATTACHMENTS_MAX] = {};
|
||||
uint32_t color_count = 0;
|
||||
bool ps_active = true;
|
||||
ShaderVertexInputInfo vs_input_info;
|
||||
std::array<ShaderVertexInputInfo, 3> vertex_info;
|
||||
ShaderPixelInputInfo ps_input_info;
|
||||
PipelineCache::GraphicsPrograms programs;
|
||||
};
|
||||
@@ -477,19 +460,14 @@ RenderState RenderExecutor::AcquireRenderTargets(CommandBuffer& buffer, RenderCo
|
||||
state.width = std::numeric_limits<uint32_t>::max();
|
||||
state.height = std::numeric_limits<uint32_t>::max();
|
||||
state.num_layers = std::numeric_limits<uint32_t>::max();
|
||||
state.num_color_attachments = color_count;
|
||||
uint32_t attachment_samples = 0;
|
||||
state.num_color_attachments = 0;
|
||||
for (uint32_t i = 0; i < color_count; i++) {
|
||||
auto& target = colors[i];
|
||||
EXIT_IF(!target.image_id);
|
||||
const auto old_image = cache.m_slot_images.try_get(target.image_id);
|
||||
if (old_image == nullptr || (!old_image->registered && !old_image->info.data.Empty()) ||
|
||||
old_image->binding.needs_rebind) {
|
||||
if (old_image != nullptr) {
|
||||
old_image->binding = {};
|
||||
}
|
||||
target.image_id = cache.FindImage(target.desc);
|
||||
BindRenderTarget(target.image_id);
|
||||
const auto owner = cache.m_slot_images.try_get(target.image_id);
|
||||
if (owner == nullptr || (!owner->registered && !owner->info.data.Empty()) ||
|
||||
owner->binding.needs_rebind) {
|
||||
EXIT("color target changed after render-state discovery\n");
|
||||
}
|
||||
const auto image_view = cache.FindRenderTarget(target.image_id, target.desc);
|
||||
auto& image = cache.GetImage(target.image_id);
|
||||
@@ -503,12 +481,6 @@ RenderState RenderExecutor::AcquireRenderTargets(CommandBuffer& buffer, RenderCo
|
||||
target.desc.view_info.base_level, target.desc.view_info.base_layer,
|
||||
target.desc.view_info.layer_count);
|
||||
EXIT_IF(image.backing.samples != target.desc.info.samples || image_view == nullptr);
|
||||
if (attachment_samples == 0) {
|
||||
attachment_samples = target.desc.info.samples;
|
||||
} else if (attachment_samples != target.desc.info.samples) {
|
||||
EXIT("mixed color attachment sample counts are unsupported: %u and %u\n",
|
||||
attachment_samples, target.desc.info.samples);
|
||||
}
|
||||
const auto& view = target.desc.view_info;
|
||||
const auto layout = image.binding.is_bound ? vk::ImageLayout::eGeneral
|
||||
: vk::ImageLayout::eColorAttachmentOptimal;
|
||||
@@ -523,7 +495,8 @@ RenderState RenderExecutor::AcquireRenderTargets(CommandBuffer& buffer, RenderCo
|
||||
state.width = std::min(state.width, extent.width);
|
||||
state.height = std::min(state.height, extent.height);
|
||||
state.num_layers = std::min(state.num_layers, view.layer_count);
|
||||
auto& attachment = state.color_attachments[i];
|
||||
state.num_color_attachments = std::max(state.num_color_attachments, target.target_slot + 1);
|
||||
auto& attachment = state.color_attachments[target.target_slot];
|
||||
attachment.image_view = image_view;
|
||||
attachment.image_layout = layout;
|
||||
}
|
||||
@@ -556,12 +529,6 @@ RenderState RenderExecutor::AcquireRenderTargets(CommandBuffer& buffer, RenderCo
|
||||
image.info.data.address, depth.desc.view_info.base_layer,
|
||||
depth.desc.view_info.layer_count);
|
||||
EXIT_IF(image_view == nullptr || image.backing.samples != depth.desc.info.samples);
|
||||
if (attachment_samples == 0) {
|
||||
attachment_samples = depth.desc.info.samples;
|
||||
} else if (attachment_samples != depth.desc.info.samples) {
|
||||
EXIT("mixed color/depth sample counts are unsupported: %u and %u\n", attachment_samples,
|
||||
depth.desc.info.samples);
|
||||
}
|
||||
const bool feedback = depth.depth_write_enable && pixel &&
|
||||
std::ranges::any_of(pixel->images, [&](const TextureBinding& binding) {
|
||||
if (binding.image_id != depth.image_id ||
|
||||
@@ -612,9 +579,6 @@ RenderState RenderExecutor::AcquireRenderTargets(CommandBuffer& buffer, RenderCo
|
||||
const auto& limits = buffer.GetGraphics().GetPhysicalDeviceProperties().limits;
|
||||
state.width = limits.maxFramebufferWidth;
|
||||
state.height = limits.maxFramebufferHeight;
|
||||
} else if (attachment_samples == 0 ||
|
||||
vulkan_sample_count(attachment_samples) == vk::SampleCountFlagBits {}) {
|
||||
EXIT("render state has no valid attachments\n");
|
||||
}
|
||||
if (state.num_layers == std::numeric_limits<uint32_t>::max()) {
|
||||
state.num_layers = 1;
|
||||
@@ -629,7 +593,7 @@ static bool DrawHasActivePixelShader(const CommandBuffer& buffer) {
|
||||
const auto& ctx = buffer.GetRegisters();
|
||||
const auto& sh_regs = ctx.GetShaderRegisters();
|
||||
const bool has_color_output = (ctx.GetRenderTargetMask() & sh_regs.m_cbShaderMask) != 0;
|
||||
return ShaderAddressValid(buffer.GetShaders().GetPs().ps_regs.data_addr) &&
|
||||
return buffer.GetShaders().GetPs().ps_regs.data_addr != 0 &&
|
||||
(has_color_output || PixelShaderHasDepthOrCoverageSideEffects(sh_regs));
|
||||
}
|
||||
|
||||
@@ -709,6 +673,7 @@ struct PreparedVertexBuffers {
|
||||
|
||||
std::array<vk::Buffer, MaxBuffers> buffers {};
|
||||
std::array<vk::DeviceSize, MaxBuffers> offsets {};
|
||||
std::array<vk::DeviceSize, MaxBuffers> sizes {};
|
||||
uint32_t count = 0;
|
||||
};
|
||||
|
||||
@@ -793,6 +758,7 @@ static PreparedVertexBuffers AcquireVertexBuffers(CommandBuffer& b
|
||||
|
||||
prepared.buffers[i] = range->binding.first->Handle();
|
||||
prepared.offsets[i] = range->binding.second + vertex.addr - range->base_address;
|
||||
prepared.sizes[i] = std::min(size, range->acquired_end - vertex.addr);
|
||||
SetVulkanObjectNameF(
|
||||
buffer.GetContext().GetGraphics().device, prepared.buffers[i],
|
||||
"Kyty.VertexBuffer[slot={} guest=0x{:016x} size=0x{:x} stride={} records={}]", i,
|
||||
@@ -808,8 +774,7 @@ static void SetDrawDebugPhase(CommandBuffer& buffer, uint64_t submit_id, const D
|
||||
draw.instance_count, draw.first_instance);
|
||||
}
|
||||
|
||||
static bool GetDrawTopology(const HW::UserConfig& ucfg, bool auto_draw,
|
||||
vk::PrimitiveTopology& topology) {
|
||||
static bool GetDrawTopology(const HW::UserConfig& ucfg, vk::PrimitiveTopology& topology) {
|
||||
|
||||
topology = vk::PrimitiveTopology::ePointList;
|
||||
|
||||
@@ -831,14 +796,12 @@ static bool GetDrawTopology(const HW::UserConfig& ucfg, bool auto_draw,
|
||||
case Prospero::PrimitiveType::kTriStrip:
|
||||
topology = vk::PrimitiveTopology::eTriangleStrip;
|
||||
break;
|
||||
case Prospero::PrimitiveType::kPatch:
|
||||
if (!Config::TessellationEnabled()) return false;
|
||||
[[fallthrough]];
|
||||
case Prospero::PrimitiveType::kRectList:
|
||||
topology = vk::PrimitiveTopology::ePatchList;
|
||||
break;
|
||||
case Prospero::PrimitiveType::kRectListLegacy:
|
||||
if (!auto_draw) {
|
||||
EXIT("unknown primitive type: %u\n", static_cast<uint32_t>(ucfg.GetPrimType()));
|
||||
}
|
||||
topology = vk::PrimitiveTopology::eTriangleStrip;
|
||||
topology = vk::PrimitiveTopology::ePatchList;
|
||||
break;
|
||||
case Prospero::PrimitiveType::kQuadListLegacy:
|
||||
topology = vk::PrimitiveTopology::eTriangleFan;
|
||||
@@ -895,42 +858,6 @@ static bool ResolvePrimitiveRestart(const CommandBuffer& buffer,
|
||||
return false;
|
||||
}
|
||||
|
||||
bool RenderExecutor::PrepareDrawRenderState(CommandBuffer& buffer, const DrawCallInfo& draw,
|
||||
uint32_t render_target_slice_offset,
|
||||
DrawRenderState& state) {
|
||||
auto& ctx = buffer.GetRegisters();
|
||||
|
||||
if (ResolveColorTargets(buffer, render_target_slice_offset)) {
|
||||
return false;
|
||||
}
|
||||
if (draw.IsIndexed()) {
|
||||
LogDrawPhase(draw.Name(), "ResolveRenderColorTarget");
|
||||
}
|
||||
for (uint32_t slot = 0; slot < RENDER_COLOR_ATTACHMENTS_MAX; slot++) {
|
||||
if (slot == 0 || (render_target_mask_slot(ctx.GetRenderTargetMask(), slot) != 0 &&
|
||||
ctx.GetRenderTarget(slot).base.addr != 0)) {
|
||||
ResolveRenderColorTarget(buffer, state.color_info[state.color_count],
|
||||
render_target_slice_offset, slot);
|
||||
if (state.color_info[state.color_count].image_id) {
|
||||
state.color_count++;
|
||||
}
|
||||
}
|
||||
}
|
||||
if (draw.IsIndexed()) {
|
||||
LogDrawPhase(draw.Name(), "ResolveRenderDepthTarget");
|
||||
}
|
||||
ResolveRenderDepthTarget(buffer, state.depth_info);
|
||||
|
||||
state.ps_active = DrawHasActivePixelShader(buffer);
|
||||
if (state.color_count == 0 && !state.depth_info.image_id && !state.ps_active) {
|
||||
LogFramebufferSkip(draw.Name(), state.color_info[0], state.depth_info, buffer,
|
||||
draw.index_count, 0);
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
static void RefreshShaders(CommandBuffer& buffer, const DrawCallInfo& draw,
|
||||
DrawRenderState& state) {
|
||||
auto& ctx = buffer.GetRegisters();
|
||||
@@ -944,8 +871,14 @@ static void RefreshShaders(CommandBuffer& buffer, const DrawCallInfo& draw,
|
||||
state.ps_input_info = {};
|
||||
std::array<Prospero::ColorComponentMapping, RENDER_COLOR_ATTACHMENTS_MAX>
|
||||
target_export_mapping {};
|
||||
for (uint32_t i = 0; i < state.color_count; i++) {
|
||||
target_export_mapping[state.color_info[i].target_slot] = state.color_info[i].export_mapping;
|
||||
for (uint32_t slot = 0; slot < RENDER_COLOR_ATTACHMENTS_MAX; slot++) {
|
||||
const auto& rt = ctx.GetRenderTarget(slot);
|
||||
if (rt.base.addr != 0 && render_target_mask_slot(ctx.GetRenderTargetMask(), slot) != 0) {
|
||||
target_export_mapping[slot] =
|
||||
TextureGetRenderTargetFormat(rt.info.format, rt.info.channel_type,
|
||||
rt.info.channel_order)
|
||||
.export_mapping;
|
||||
}
|
||||
}
|
||||
auto& pipeline_cache = buffer.GetContext().GetPipelineCache();
|
||||
if (draw.IsIndexed()) {
|
||||
@@ -953,7 +886,46 @@ static void RefreshShaders(CommandBuffer& buffer, const DrawCallInfo& draw,
|
||||
}
|
||||
state.programs = pipeline_cache.GetGraphicsPrograms(
|
||||
vertex_shader_info, pixel_shader_info, shader_regs, ctx, buffer.GetUserConfig(),
|
||||
target_export_mapping, state.ps_active, state.vs_input_info, state.ps_input_info);
|
||||
target_export_mapping, state.ps_active, state.vertex_info, state.ps_input_info);
|
||||
}
|
||||
|
||||
bool RenderExecutor::PrepareDrawRenderState(CommandBuffer& buffer, const DrawCallInfo& draw,
|
||||
uint32_t render_target_slice_offset,
|
||||
DrawRenderState& state) {
|
||||
state.ps_active = DrawHasActivePixelShader(buffer);
|
||||
RefreshShaders(buffer, draw, state);
|
||||
uint32_t mrt_mask = 0;
|
||||
if (state.ps_active) {
|
||||
for (const auto& output: state.ps_input_info.stage.program->info.outputs) {
|
||||
if (output.kind == ShaderRecompiler::IR::StageOutputKind::Mrt) {
|
||||
mrt_mask |= 1u << output.index;
|
||||
}
|
||||
}
|
||||
}
|
||||
if (draw.IsIndexed()) {
|
||||
LogDrawPhase(draw.Name(), "ResolveRenderColorTarget");
|
||||
}
|
||||
for (uint32_t slot = 0; slot < RENDER_COLOR_ATTACHMENTS_MAX; slot++) {
|
||||
if ((mrt_mask & (1u << slot)) != 0) {
|
||||
ResolveRenderColorTarget(buffer, state.color_info[state.color_count],
|
||||
render_target_slice_offset, slot);
|
||||
if (state.color_info[state.color_count].image_id) {
|
||||
state.color_count++;
|
||||
}
|
||||
}
|
||||
}
|
||||
if (draw.IsIndexed()) {
|
||||
LogDrawPhase(draw.Name(), "ResolveRenderDepthTarget");
|
||||
}
|
||||
ResolveRenderDepthTarget(buffer, state.depth_info);
|
||||
|
||||
if (state.color_count == 0 && !state.depth_info.image_id && !state.ps_active) {
|
||||
LogFramebufferSkip(draw.Name(), state.color_info[0], state.depth_info, buffer,
|
||||
draw.index_count, 0);
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
static PreparedIndexBuffer PrepareIndexBuffer(CommandBuffer& buffer,
|
||||
@@ -988,8 +960,9 @@ static void CommitVertexBuffers(vk::CommandBuffer vk_buffer,
|
||||
EXIT_IF(prepared.buffers[i] == nullptr);
|
||||
}
|
||||
if (prepared.count != 0) {
|
||||
vk_buffer.bindVertexBuffers(0, prepared.count, prepared.buffers.data(),
|
||||
prepared.offsets.data());
|
||||
// Guest descriptor bounds must survive allocation merging in the cache.
|
||||
vk_buffer.bindVertexBuffers2(0, prepared.count, prepared.buffers.data(),
|
||||
prepared.offsets.data(), prepared.sizes.data(), nullptr);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1007,8 +980,7 @@ static void LogDrawStateIfNeeded(const CommandBuffer& buffer, const DrawCallInfo
|
||||
return;
|
||||
}
|
||||
|
||||
if (!draw.IsIndexed() &&
|
||||
buffer.GetUserConfig().GetPrimType() != Prospero::PrimitiveType::kRectListLegacy) {
|
||||
if (!draw.IsIndexed() && !Prospero::IsRectList(buffer.GetUserConfig().GetPrimType())) {
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -1016,13 +988,12 @@ static void LogDrawStateIfNeeded(const CommandBuffer& buffer, const DrawCallInfo
|
||||
LogDrawTargetState(draw.Name(), state.color_info[0], state.depth_info, buffer,
|
||||
state.ps_input_info, draw.index_count, 0);
|
||||
}
|
||||
LogDrawInputState(buffer, state.color_info[0], state.vs_input_info, index_type_and_size,
|
||||
LogDrawInputState(buffer, state.color_info[0], state.vertex_info[0], index_type_and_size,
|
||||
draw.index_count, index_addr);
|
||||
}
|
||||
|
||||
static void EmitDrawPrimitives(const HW::UserConfig& ucfg, vk::CommandBuffer vk_buffer,
|
||||
const ShaderVertexInputInfo& vs_input_info, const DrawCallInfo& draw,
|
||||
const DrawEmitInfo& emit) {
|
||||
const DrawCallInfo& draw, const DrawEmitInfo& emit) {
|
||||
switch (ucfg.GetPrimType()) {
|
||||
case Prospero::PrimitiveType::kPointList:
|
||||
case Prospero::PrimitiveType::kLineList:
|
||||
@@ -1031,6 +1002,8 @@ static void EmitDrawPrimitives(const HW::UserConfig& ucfg, vk::CommandBuffer vk_
|
||||
case Prospero::PrimitiveType::kTriFan:
|
||||
case Prospero::PrimitiveType::kTriStrip:
|
||||
case Prospero::PrimitiveType::kRectList:
|
||||
case Prospero::PrimitiveType::kRectListLegacy:
|
||||
case Prospero::PrimitiveType::kPatch:
|
||||
if (draw.IsIndexed()) {
|
||||
vk_buffer.drawIndexed(draw.index_count, draw.instance_count, 0, emit.vertex_offset,
|
||||
emit.first_instance);
|
||||
@@ -1039,14 +1012,6 @@ static void EmitDrawPrimitives(const HW::UserConfig& ucfg, vk::CommandBuffer vk_
|
||||
emit.first_instance);
|
||||
}
|
||||
break;
|
||||
case Prospero::PrimitiveType::kRectListLegacy:
|
||||
if (draw.IsIndexed()) {
|
||||
EXIT("unknown primitive type: %u\n", static_cast<uint32_t>(ucfg.GetPrimType()));
|
||||
}
|
||||
// Sarah
|
||||
EXIT_NOT_IMPLEMENTED(!(draw.index_count == 3 && vs_input_info.buffers_num == 0));
|
||||
vk_buffer.draw(4, draw.instance_count, emit.first_vertex, emit.first_instance);
|
||||
break;
|
||||
case Prospero::PrimitiveType::kQuadListLegacy:
|
||||
EXIT_NOT_IMPLEMENTED((draw.index_count & 0x3u) != 0);
|
||||
for (uint32_t i = 0; i < draw.index_count; i += 4) {
|
||||
@@ -1069,11 +1034,19 @@ void RenderExecutor::ExecutePreparedDraw(uint64_t submit_id, CommandBuffer& buff
|
||||
const DrawIndexBufferSource& index_source,
|
||||
bool primitive_restart_enable) {
|
||||
auto& ucfg = buffer.GetUserConfig();
|
||||
const bool mesh_active = state.vs_input_info.stage.program->stage == ShaderType::Mesh;
|
||||
const auto vertex_stages =
|
||||
std::span {state.vertex_info.data(), state.programs.VertexStageCount()};
|
||||
const bool mesh_active = state.vertex_info[0].stage.program->stage == ShaderType::Mesh;
|
||||
uint32_t mesh_groups = 0;
|
||||
if (mesh_active) {
|
||||
const auto& mesh = state.vs_input_info.mesh;
|
||||
if (primitive_restart_enable || mesh.primitives_per_group == 0) {
|
||||
const auto& mesh = state.vertex_info[0].mesh;
|
||||
static std::atomic_bool restart_warned = false;
|
||||
if (primitive_restart_enable && !restart_warned.exchange(true, std::memory_order_relaxed)) {
|
||||
std::printf("Warning: primitive restart is not implemented for mesh shaders; "
|
||||
"continuing draw (primitive=%u indexed=%u)\n",
|
||||
static_cast<uint32_t>(ucfg.GetPrimType()), draw.IsIndexed());
|
||||
}
|
||||
if (mesh.primitives_per_group == 0) {
|
||||
EXIT("unsupported mesh draw: primitive=%u indexed=%u restart=%u\n",
|
||||
static_cast<uint32_t>(ucfg.GetPrimType()), draw.IsIndexed(), primitive_restart_enable);
|
||||
}
|
||||
@@ -1100,26 +1073,36 @@ void RenderExecutor::ExecutePreparedDraw(uint64_t submit_id, CommandBuffer& buff
|
||||
index_source.guest_element_size);
|
||||
}
|
||||
LogDrawPhase(draw.Name(), "PrepareBindings");
|
||||
auto bindings = PrepareGraphicsBindings(state.vs_input_info.stage, state.ps_input_info.stage,
|
||||
state.ps_active);
|
||||
GraphicsBindings bindings;
|
||||
std::array<PreparedBindings*, 4> descriptor_stages {};
|
||||
uint32_t stage_count = 0;
|
||||
for (uint32_t i = 0; i < vertex_stages.size(); i++) {
|
||||
bindings.vertex[i] = PrepareBindings(state.vertex_info[i].stage);
|
||||
descriptor_stages[stage_count++] = &bindings.vertex[i];
|
||||
}
|
||||
if (state.ps_active) {
|
||||
bindings.pixel.emplace(PrepareBindings(state.ps_input_info.stage));
|
||||
descriptor_stages[stage_count++] = &*bindings.pixel;
|
||||
}
|
||||
const auto stages = std::span {descriptor_stages.data(), stage_count};
|
||||
PrepareGraphicsBindings(stages, std::span {state.color_info, state.color_count});
|
||||
PreparedVertexBuffers vertex_bindings;
|
||||
PreparedIndexBuffer index_binding;
|
||||
if (!mesh_active) {
|
||||
LogDrawPhase(draw.Name(), "PrepareVertexBuffers");
|
||||
vertex_bindings = AcquireVertexBuffers(buffer, state.vs_input_info);
|
||||
vertex_bindings = AcquireVertexBuffers(buffer, state.vertex_info[0]);
|
||||
index_binding = PrepareIndexBuffer(buffer, index_source);
|
||||
}
|
||||
const auto rendering =
|
||||
AcquireRenderTargets(buffer, state.color_info, state.color_count, state.depth_info,
|
||||
bindings.pixel);
|
||||
|
||||
if (draw.IsIndexed()) {
|
||||
LogDrawPhase(draw.Name(), "CreatePipeline");
|
||||
}
|
||||
auto& pipeline = m_context.GetPipelineCache().GetGraphicsPipeline(
|
||||
std::span {state.color_info, state.color_count}, state.depth_info, state.vs_input_info, buffer,
|
||||
std::span {state.color_info, state.color_count}, state.depth_info, vertex_stages, buffer,
|
||||
state.ps_active ? &state.ps_input_info : nullptr, topology, primitive_restart_enable,
|
||||
state.programs.vertex, state.programs.pixel);
|
||||
state.programs);
|
||||
const auto rendering =
|
||||
AcquireRenderTargets(buffer, state.color_info, state.color_count, state.depth_info,
|
||||
bindings.pixel);
|
||||
|
||||
// Resource preparation above may synchronously finish and restart the scheduler. From this
|
||||
// point onward, every operation targets the current command buffer and cannot touch guest
|
||||
@@ -1132,13 +1115,7 @@ void RenderExecutor::ExecutePreparedDraw(uint64_t submit_id, CommandBuffer& buff
|
||||
if (bindings.pixel && !draw.IsIndexed()) {
|
||||
SetDrawDebugPhase(buffer, submit_id, draw, 0x300u);
|
||||
}
|
||||
std::array<PreparedBindings*, 2> descriptor_stages {&bindings.vertex, nullptr};
|
||||
const size_t descriptor_stage_count = bindings.pixel.has_value() ? 2u : 1u;
|
||||
if (bindings.pixel) {
|
||||
descriptor_stages[1] = &*bindings.pixel;
|
||||
}
|
||||
CommitBindings(buffer, vk::PipelineBindPoint::eGraphics, pipeline,
|
||||
std::span {descriptor_stages.data(), descriptor_stage_count});
|
||||
CommitBindings(buffer, vk::PipelineBindPoint::eGraphics, pipeline, stages);
|
||||
if (mesh_active) {
|
||||
const uint32_t draw_data[] {
|
||||
draw.index_count,
|
||||
@@ -1155,8 +1132,7 @@ void RenderExecutor::ExecutePreparedDraw(uint64_t submit_id, CommandBuffer& buff
|
||||
CommitIndexBuffer(vk_buffer, index_binding);
|
||||
}
|
||||
|
||||
SetGraphicsDynamicParams(buffer, vk_buffer, state.vs_input_info, state.color_info,
|
||||
state.color_count, state.depth_info);
|
||||
SetGraphicsDynamicParams(buffer, vk_buffer, vertex_stages.back(), state.depth_info, rendering);
|
||||
if (m_context.GetGraphics().attachment_feedback_loop_enabled) {
|
||||
vk_buffer.setAttachmentFeedbackLoopEnableEXT(
|
||||
rendering.depth_stencil_attachment.image_layout ==
|
||||
@@ -1177,16 +1153,17 @@ void RenderExecutor::ExecutePreparedDraw(uint64_t submit_id, CommandBuffer& buff
|
||||
if (mesh_active) {
|
||||
vk_buffer.drawMeshTasksEXT(mesh_groups, draw.instance_count, 1);
|
||||
} else {
|
||||
EmitDrawPrimitives(ucfg, vk_buffer, state.vs_input_info, draw, emit);
|
||||
EmitDrawPrimitives(ucfg, vk_buffer, draw, emit);
|
||||
}
|
||||
|
||||
if (!draw.IsIndexed()) {
|
||||
SetDrawDebugPhase(buffer, submit_id, draw, 0x600u);
|
||||
}
|
||||
vk::PipelineStageFlags shader_write_stages = {};
|
||||
if (HasShaderBufferWrites(state.vs_input_info.stage)) {
|
||||
shader_write_stages |= mesh_active ? vk::PipelineStageFlagBits::eMeshShaderEXT
|
||||
: vk::PipelineStageFlagBits::eVertexShader;
|
||||
for (const auto& stage: vertex_stages) {
|
||||
if (HasShaderBufferWrites(stage.stage)) {
|
||||
shader_write_stages |= ShaderPipelineStages(NativeShaderStage(stage.logical_stage));
|
||||
}
|
||||
}
|
||||
if (state.ps_active && HasShaderBufferWrites(state.ps_input_info.stage)) {
|
||||
shader_write_stages |= vk::PipelineStageFlagBits::eFragmentShader;
|
||||
@@ -1220,7 +1197,8 @@ void RenderExecutor::DrawIndex(uint64_t submit_id, CommandBuffer& buffer,
|
||||
return;
|
||||
}
|
||||
|
||||
if (ConsumeMetadataColorOperation(buffer) || DepthStencilCopy(buffer)) {
|
||||
if (ConsumeMetadataColorOperation(buffer) || DepthStencilCopy(buffer) ||
|
||||
ResolveColorTargets(buffer, args.render_target_slice_offset)) {
|
||||
ResetBindings();
|
||||
return;
|
||||
}
|
||||
@@ -1251,7 +1229,7 @@ void RenderExecutor::DrawIndex(uint64_t submit_id, CommandBuffer& buffer,
|
||||
hw_check(buffer);
|
||||
|
||||
vk::PrimitiveTopology topology = vk::PrimitiveTopology::ePointList;
|
||||
if (!GetDrawTopology(ucfg, false, topology)) {
|
||||
if (!GetDrawTopology(ucfg, topology)) {
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -1294,15 +1272,13 @@ void RenderExecutor::DrawIndex(uint64_t submit_id, CommandBuffer& buffer,
|
||||
return;
|
||||
}
|
||||
|
||||
RefreshShaders(buffer, draw, state);
|
||||
|
||||
LogDrawStateIfNeeded(buffer, draw, state, args.index_type_and_size,
|
||||
args.index_addr);
|
||||
|
||||
const bool indirect = args.offset_source == DrawOffsetSource::IndirectArgs;
|
||||
const auto [vertex_offset, instance_offset] =
|
||||
indirect ? std::pair<int32_t, uint32_t> {0, args.first_instance}
|
||||
: ResolveDrawOffsets(ucfg.GetIndexOffset(), state.vs_input_info);
|
||||
: ResolveDrawOffsets(ucfg.GetIndexOffset(), state.vertex_info[0]);
|
||||
|
||||
DrawEmitInfo emit {};
|
||||
emit.vertex_offset = vertex_offset + args.base_vertex;
|
||||
@@ -1332,7 +1308,8 @@ void RenderExecutor::DrawAuto(uint64_t submit_id, CommandBuffer& buffer, const D
|
||||
return;
|
||||
}
|
||||
|
||||
if (ConsumeMetadataColorOperation(buffer) || DepthStencilCopy(buffer)) {
|
||||
if (ConsumeMetadataColorOperation(buffer) || DepthStencilCopy(buffer) ||
|
||||
ResolveColorTargets(buffer, args.render_target_slice_offset)) {
|
||||
ResetBindings();
|
||||
return;
|
||||
}
|
||||
@@ -1361,22 +1338,20 @@ void RenderExecutor::DrawAuto(uint64_t submit_id, CommandBuffer& buffer, const D
|
||||
const DrawCallInfo draw {CommandBufferDebugOp::DrawIndexAuto,
|
||||
args.vertex_count, args.instance_count, args.first_instance};
|
||||
|
||||
vk::PrimitiveTopology topology = vk::PrimitiveTopology::ePointList;
|
||||
if (!GetDrawTopology(ucfg, topology)) {
|
||||
ResetBindings();
|
||||
return;
|
||||
}
|
||||
DrawRenderState state {};
|
||||
if (!PrepareDrawRenderState(buffer, draw, args.render_target_slice_offset, state)) {
|
||||
ResetBindings();
|
||||
return;
|
||||
}
|
||||
|
||||
vk::PrimitiveTopology topology = vk::PrimitiveTopology::ePointList;
|
||||
if (!GetDrawTopology(ucfg, true, topology)) {
|
||||
ResetBindings();
|
||||
return;
|
||||
}
|
||||
RefreshShaders(buffer, draw, state);
|
||||
|
||||
const bool rect_list = topology == vk::PrimitiveTopology::ePatchList;
|
||||
if (rect_list && state.vs_input_info.buffers_num == 0 &&
|
||||
state.vs_input_info.stage.program->param_export_mask == 0 &&
|
||||
const bool rect_list = Prospero::IsRectList(ucfg.GetPrimType());
|
||||
if (rect_list && state.vertex_info[0].buffers_num == 0 &&
|
||||
state.vertex_info[0].stage.program->param_export_mask == 0 &&
|
||||
state.ps_input_info.input_num != 0) {
|
||||
if (graphics_debug_dump_enabled()) {
|
||||
LOGF("DrawIndexAuto: skipping rect-list draw with no VS param exports and PS inputs: "
|
||||
@@ -1393,7 +1368,7 @@ void RenderExecutor::DrawAuto(uint64_t submit_id, CommandBuffer& buffer, const D
|
||||
const bool indirect = args.offset_source == DrawOffsetSource::IndirectArgs;
|
||||
const auto [vertex_offset, instance_offset] =
|
||||
indirect ? std::pair<int32_t, uint32_t> {0, args.first_instance}
|
||||
: ResolveDrawOffsets(ucfg.GetIndexOffset(), state.vs_input_info);
|
||||
: ResolveDrawOffsets(ucfg.GetIndexOffset(), state.vertex_info[0]);
|
||||
DrawEmitInfo emit {};
|
||||
emit.first_vertex = static_cast<uint32_t>(vertex_offset + static_cast<int32_t>(args.first_vertex));
|
||||
emit.first_instance = instance_offset;
|
||||
|
||||
@@ -5,7 +5,6 @@
|
||||
|
||||
#include <array>
|
||||
#include <cstdint>
|
||||
#include <type_traits>
|
||||
|
||||
namespace Libs::Graphics {
|
||||
|
||||
@@ -67,33 +66,6 @@ inline constexpr TargetViewInfo ResolveTargetViewInfo(uint32_t base_layer, uint3
|
||||
base_layer, last_layer - base_layer + 1u, last_layer + 1u};
|
||||
}
|
||||
|
||||
#pragma pack(push, 1)
|
||||
|
||||
struct PipelineStencilStaticState {
|
||||
vk::StencilOp failOp = vk::StencilOp::eKeep;
|
||||
vk::StencilOp passOp = vk::StencilOp::eKeep;
|
||||
vk::StencilOp depthFailOp = vk::StencilOp::eKeep;
|
||||
vk::CompareOp compareOp = vk::CompareOp::eNever;
|
||||
};
|
||||
|
||||
struct PipelineStencilDynamicState {
|
||||
uint32_t compareMask = 0;
|
||||
uint32_t writeMask = 0;
|
||||
uint32_t reference = 0;
|
||||
};
|
||||
|
||||
#pragma pack(pop)
|
||||
|
||||
static_assert(std::is_trivially_copyable_v<PipelineStencilStaticState>);
|
||||
static_assert(std::is_standard_layout_v<PipelineStencilStaticState>);
|
||||
static_assert(alignof(PipelineStencilStaticState) == 1);
|
||||
static_assert(sizeof(PipelineStencilStaticState) ==
|
||||
sizeof(vk::StencilOp) * 3 + sizeof(vk::CompareOp));
|
||||
static_assert(std::is_trivially_copyable_v<PipelineStencilDynamicState>);
|
||||
static_assert(std::is_standard_layout_v<PipelineStencilDynamicState>);
|
||||
static_assert(alignof(PipelineStencilDynamicState) == 1);
|
||||
static_assert(sizeof(PipelineStencilDynamicState) == sizeof(uint32_t) * 3);
|
||||
|
||||
} // namespace Libs::Graphics
|
||||
|
||||
#endif // EMULATOR_SRC_GRAPHICS_HOST_GPU_RENDERER_RENDERTARGET_H_
|
||||
@@ -123,4 +123,14 @@ void RequireVulkanSuccess(vk::Result result, const char* operation) {
|
||||
}
|
||||
}
|
||||
|
||||
vk::ShaderModule CompileSPV(std::span<const uint32_t> code, vk::Device device) {
|
||||
vk::ShaderModuleCreateInfo create_info {};
|
||||
create_info.codeSize = code.size_bytes();
|
||||
create_info.pCode = code.data();
|
||||
vk::ShaderModule module = nullptr;
|
||||
RequireVulkanSuccess(device.createShaderModule(&create_info, nullptr, &module),
|
||||
"create SPIR-V shader module");
|
||||
return module;
|
||||
}
|
||||
|
||||
} // namespace Libs::Graphics
|
||||
@@ -15,6 +15,7 @@
|
||||
|
||||
#include <cstdint>
|
||||
#include <fmt/format.h>
|
||||
#include <span>
|
||||
#include <string>
|
||||
#include <utility>
|
||||
#include <vector>
|
||||
@@ -23,10 +24,9 @@
|
||||
|
||||
namespace Libs::Graphics {
|
||||
|
||||
using VulkanMemoryBarrier = vk::MemoryBarrier;
|
||||
|
||||
vk::Format VulkanFormat(Prospero::BufferFormat guest_format);
|
||||
void RequireVulkanSuccess(vk::Result result, const char* operation);
|
||||
vk::ShaderModule CompileSPV(std::span<const uint32_t> code, vk::Device device);
|
||||
|
||||
template <typename Handle, typename... Args>
|
||||
void SetVulkanObjectNameF(vk::Device device, Handle handle, fmt::format_string<Args...> format,
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
#include "graphics/presentation/systemOverlay.h"
|
||||
|
||||
#include "SDL.h"
|
||||
#include <SDL3/SDL.h>
|
||||
|
||||
#include "common/assert.h"
|
||||
#include "common/stringUtils.h"
|
||||
#include "graphics/host_gpu/graphicContext.h"
|
||||
@@ -176,6 +177,7 @@ bool g_input_multiline = false;
|
||||
bool g_input_lifecycle_active = false;
|
||||
bool g_controller_captured = false;
|
||||
OverlaySession g_session;
|
||||
SDL_Window* g_input_window = nullptr;
|
||||
|
||||
void ClearInputEvents() {
|
||||
std::scoped_lock lock(g_input_mutex);
|
||||
@@ -212,7 +214,7 @@ void RetryVisibilityWakeup() {
|
||||
}
|
||||
SDL_Event event {};
|
||||
event.type = type;
|
||||
if (SDL_PushEvent(&event) > 0) {
|
||||
if (SDL_PushEvent(&event)) {
|
||||
g_missing_visibility_wakeups--;
|
||||
}
|
||||
}
|
||||
@@ -252,7 +254,7 @@ void RefreshVisibility() {
|
||||
std::scoped_lock input_lock(g_input_mutex);
|
||||
g_visibility_updates.push_back({snapshot.session, visible, capture_controller,
|
||||
capture_keyboard, text_input, multiline});
|
||||
if (g_missing_visibility_wakeups != 0 || SDL_PushEvent(&event) <= 0) {
|
||||
if (g_missing_visibility_wakeups != 0 || !SDL_PushEvent(&event)) {
|
||||
g_missing_visibility_wakeups++;
|
||||
}
|
||||
}
|
||||
@@ -268,16 +270,16 @@ void OnDialogVisibilityChanged(bool, uint64_t) {
|
||||
|
||||
uint32_t ExternalKeyStatus(SDL_Keymod modifiers, bool character_valid) {
|
||||
uint32_t status = 0x00000001 | (character_valid ? 0x00000002 : 0);
|
||||
if ((modifiers & KMOD_LCTRL) != 0) status |= 0x00000100;
|
||||
if ((modifiers & KMOD_LSHIFT) != 0) status |= 0x00000200;
|
||||
if ((modifiers & KMOD_LALT) != 0) status |= 0x00000400;
|
||||
if ((modifiers & KMOD_LGUI) != 0) status |= 0x00000800;
|
||||
if ((modifiers & KMOD_RCTRL) != 0) status |= 0x00001000;
|
||||
if ((modifiers & KMOD_RSHIFT) != 0) status |= 0x00002000;
|
||||
if ((modifiers & KMOD_RALT) != 0) status |= 0x00004000;
|
||||
if ((modifiers & KMOD_RGUI) != 0) status |= 0x00008000;
|
||||
if ((modifiers & KMOD_NUM) != 0) status |= 0x00010000;
|
||||
if ((modifiers & KMOD_CAPS) != 0) status |= 0x00020000;
|
||||
if ((modifiers & SDL_KMOD_LCTRL) != 0) status |= 0x00000100;
|
||||
if ((modifiers & SDL_KMOD_LSHIFT) != 0) status |= 0x00000200;
|
||||
if ((modifiers & SDL_KMOD_LALT) != 0) status |= 0x00000400;
|
||||
if ((modifiers & SDL_KMOD_LGUI) != 0) status |= 0x00000800;
|
||||
if ((modifiers & SDL_KMOD_RCTRL) != 0) status |= 0x00001000;
|
||||
if ((modifiers & SDL_KMOD_RSHIFT) != 0) status |= 0x00002000;
|
||||
if ((modifiers & SDL_KMOD_RALT) != 0) status |= 0x00004000;
|
||||
if ((modifiers & SDL_KMOD_RGUI) != 0) status |= 0x00008000;
|
||||
if ((modifiers & SDL_KMOD_NUM) != 0) status |= 0x00010000;
|
||||
if ((modifiers & SDL_KMOD_CAPS) != 0) status |= 0x00020000;
|
||||
return status;
|
||||
}
|
||||
|
||||
@@ -392,14 +394,14 @@ float AlignmentPivot(Ime::Alignment alignment) {
|
||||
|
||||
ImGuiKey ControllerButtonToKey(int button) {
|
||||
switch (button) {
|
||||
case SDL_CONTROLLER_BUTTON_A: return ImGuiKey_GamepadFaceDown;
|
||||
case SDL_CONTROLLER_BUTTON_B: return ImGuiKey_GamepadFaceRight;
|
||||
case SDL_CONTROLLER_BUTTON_X: return ImGuiKey_GamepadFaceLeft;
|
||||
case SDL_CONTROLLER_BUTTON_Y: return ImGuiKey_GamepadFaceUp;
|
||||
case SDL_CONTROLLER_BUTTON_DPAD_LEFT: return ImGuiKey_GamepadDpadLeft;
|
||||
case SDL_CONTROLLER_BUTTON_DPAD_RIGHT: return ImGuiKey_GamepadDpadRight;
|
||||
case SDL_CONTROLLER_BUTTON_DPAD_UP: return ImGuiKey_GamepadDpadUp;
|
||||
case SDL_CONTROLLER_BUTTON_DPAD_DOWN: return ImGuiKey_GamepadDpadDown;
|
||||
case SDL_GAMEPAD_BUTTON_SOUTH: return ImGuiKey_GamepadFaceDown;
|
||||
case SDL_GAMEPAD_BUTTON_EAST: return ImGuiKey_GamepadFaceRight;
|
||||
case SDL_GAMEPAD_BUTTON_WEST: return ImGuiKey_GamepadFaceLeft;
|
||||
case SDL_GAMEPAD_BUTTON_NORTH: return ImGuiKey_GamepadFaceUp;
|
||||
case SDL_GAMEPAD_BUTTON_DPAD_LEFT: return ImGuiKey_GamepadDpadLeft;
|
||||
case SDL_GAMEPAD_BUTTON_DPAD_RIGHT: return ImGuiKey_GamepadDpadRight;
|
||||
case SDL_GAMEPAD_BUTTON_DPAD_UP: return ImGuiKey_GamepadDpadUp;
|
||||
case SDL_GAMEPAD_BUTTON_DPAD_DOWN: return ImGuiKey_GamepadDpadDown;
|
||||
default: return ImGuiKey_None;
|
||||
}
|
||||
}
|
||||
@@ -415,9 +417,11 @@ void CheckVulkanResult(VkResult result) {
|
||||
|
||||
} // namespace
|
||||
|
||||
void InitializeSystemOverlayInput() {
|
||||
void InitializeSystemOverlayInput(SDL_Window* window) {
|
||||
EXIT_IF(window == nullptr);
|
||||
const Uint32 type = SDL_RegisterEvents(1);
|
||||
EXIT_IF(type == static_cast<Uint32>(-1));
|
||||
g_input_window = window;
|
||||
{
|
||||
std::scoped_lock lock(g_visibility_mutex);
|
||||
g_input_lifecycle_active = true;
|
||||
@@ -453,9 +457,10 @@ void ShutdownSystemOverlayInput() {
|
||||
g_input_controller = false;
|
||||
g_input_keyboard = false;
|
||||
g_input_multiline = false;
|
||||
if (SDL_IsTextInputActive() == SDL_TRUE) {
|
||||
SDL_StopTextInput();
|
||||
if (g_input_window != nullptr && SDL_TextInputActive(g_input_window)) {
|
||||
SDL_StopTextInput(g_input_window);
|
||||
}
|
||||
g_input_window = nullptr;
|
||||
}
|
||||
|
||||
SystemOverlayVisualState GetSystemOverlayVisualState() noexcept {
|
||||
@@ -487,13 +492,13 @@ bool ProcessSystemOverlayInput(const SDL_Event& event) {
|
||||
g_input_keyboard = update.capture_keyboard;
|
||||
g_input_multiline = update.multiline;
|
||||
if (update.text_input) {
|
||||
SDL_StartTextInput();
|
||||
} else if (SDL_IsTextInputActive() == SDL_TRUE) {
|
||||
SDL_StopTextInput();
|
||||
SDL_StartTextInput(g_input_window);
|
||||
} else if (SDL_TextInputActive(g_input_window)) {
|
||||
SDL_StopTextInput(g_input_window);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
if (event.type == SDL_CONTROLLERDEVICEREMOVED) {
|
||||
if (event.type == SDL_EVENT_GAMEPAD_REMOVED) {
|
||||
if (g_input_active && g_input_controller) {
|
||||
QueueInput({InputKind::ResetController, g_input_session, 0, 0.0f, 0.0f});
|
||||
}
|
||||
@@ -505,26 +510,26 @@ bool ProcessSystemOverlayInput(const SDL_Event& event) {
|
||||
|
||||
const auto session = g_input_session;
|
||||
const uint64_t generation = session.generation;
|
||||
const bool controller_event = event.type == SDL_CONTROLLERBUTTONDOWN ||
|
||||
event.type == SDL_CONTROLLERBUTTONUP ||
|
||||
event.type == SDL_CONTROLLERAXISMOTION;
|
||||
const bool controller_event = event.type == SDL_EVENT_GAMEPAD_BUTTON_DOWN ||
|
||||
event.type == SDL_EVENT_GAMEPAD_BUTTON_UP ||
|
||||
event.type == SDL_EVENT_GAMEPAD_AXIS_MOTION;
|
||||
if (controller_event && !g_input_controller) {
|
||||
return false;
|
||||
}
|
||||
const bool keyboard_event = event.type == SDL_TEXTINPUT || event.type == SDL_TEXTEDITING ||
|
||||
event.type == SDL_KEYDOWN || event.type == SDL_KEYUP;
|
||||
const bool keyboard_event = event.type == SDL_EVENT_TEXT_INPUT || event.type == SDL_EVENT_TEXT_EDITING ||
|
||||
event.type == SDL_EVENT_KEY_DOWN || event.type == SDL_EVENT_KEY_UP;
|
||||
if (keyboard_event && !g_input_keyboard) {
|
||||
return false;
|
||||
}
|
||||
if (keyboard_event && session.kind == OverlayKind::Error) {
|
||||
if (event.type == SDL_KEYDOWN && event.key.repeat == 0 &&
|
||||
(event.key.keysym.sym == SDLK_RETURN || event.key.keysym.sym == SDLK_KP_ENTER)) {
|
||||
if (event.type == SDL_EVENT_KEY_DOWN && !event.key.repeat &&
|
||||
(event.key.key == SDLK_RETURN || event.key.key == SDLK_KP_ENTER)) {
|
||||
ErrorDialog::HostAccept(generation);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
switch (event.type) {
|
||||
case SDL_TEXTINPUT: {
|
||||
case SDL_EVENT_TEXT_INPUT: {
|
||||
const auto text = Utf8ToUtf16(event.text.text);
|
||||
if (!text.empty()) {
|
||||
auto input = MakeExternalInput(Ime::ExternalAction::Text, g_last_external_keycode,
|
||||
@@ -535,26 +540,24 @@ bool ProcessSystemOverlayInput(const SDL_Event& event) {
|
||||
}
|
||||
return true;
|
||||
}
|
||||
case SDL_TEXTEDITING: return true;
|
||||
case SDL_KEYDOWN: {
|
||||
g_last_external_keycode = static_cast<uint16_t>(event.key.keysym.scancode);
|
||||
g_last_external_status =
|
||||
ExternalKeyStatus(static_cast<SDL_Keymod>(event.key.keysym.mod), false);
|
||||
case SDL_EVENT_TEXT_EDITING: return true;
|
||||
case SDL_EVENT_KEY_DOWN: {
|
||||
g_last_external_keycode = static_cast<uint16_t>(event.key.scancode);
|
||||
g_last_external_status = ExternalKeyStatus(event.key.mod, false);
|
||||
auto action = Ime::ExternalAction::Text;
|
||||
bool queue = true;
|
||||
if (event.key.keysym.sym == SDLK_BACKSPACE) {
|
||||
if (event.key.key == SDLK_BACKSPACE) {
|
||||
action = Ime::ExternalAction::Backspace;
|
||||
} else if (event.key.keysym.sym == SDLK_LEFT) {
|
||||
} else if (event.key.key == SDLK_LEFT) {
|
||||
action = Ime::ExternalAction::MoveLeft;
|
||||
} else if (event.key.keysym.sym == SDLK_RIGHT) {
|
||||
} else if (event.key.key == SDLK_RIGHT) {
|
||||
action = Ime::ExternalAction::MoveRight;
|
||||
} else if (event.key.keysym.sym == SDLK_ESCAPE) {
|
||||
} else if (event.key.key == SDLK_ESCAPE) {
|
||||
action = Ime::ExternalAction::Cancel;
|
||||
} else if (event.key.keysym.sym == SDLK_RETURN ||
|
||||
event.key.keysym.sym == SDLK_KP_ENTER) {
|
||||
} else if (event.key.key == SDLK_RETURN || event.key.key == SDLK_KP_ENTER) {
|
||||
action =
|
||||
g_input_multiline ? Ime::ExternalAction::Newline : Ime::ExternalAction::Accept;
|
||||
} else if (event.key.keysym.sym == SDLK_TAB) {
|
||||
} else if (event.key.key == SDLK_TAB) {
|
||||
action = Ime::ExternalAction::None;
|
||||
} else {
|
||||
queue = false;
|
||||
@@ -566,28 +569,28 @@ bool ProcessSystemOverlayInput(const SDL_Event& event) {
|
||||
}
|
||||
return true;
|
||||
}
|
||||
case SDL_KEYUP: return true;
|
||||
case SDL_CONTROLLERBUTTONDOWN:
|
||||
case SDL_CONTROLLERBUTTONUP:
|
||||
QueueInput({InputKind::Button, session, event.cbutton.button,
|
||||
event.type == SDL_CONTROLLERBUTTONDOWN ? 1.0f : 0.0f, 0.0f});
|
||||
case SDL_EVENT_KEY_UP: return true;
|
||||
case SDL_EVENT_GAMEPAD_BUTTON_DOWN:
|
||||
case SDL_EVENT_GAMEPAD_BUTTON_UP:
|
||||
QueueInput({InputKind::Button, session, event.gbutton.button,
|
||||
event.type == SDL_EVENT_GAMEPAD_BUTTON_DOWN ? 1.0f : 0.0f, 0.0f});
|
||||
return true;
|
||||
case SDL_CONTROLLERAXISMOTION:
|
||||
QueueInput({InputKind::Axis, session, event.caxis.axis,
|
||||
static_cast<float>(event.caxis.value), 0.0f});
|
||||
case SDL_EVENT_GAMEPAD_AXIS_MOTION:
|
||||
QueueInput({InputKind::Axis, session, event.gaxis.axis,
|
||||
static_cast<float>(event.gaxis.value), 0.0f});
|
||||
return true;
|
||||
case SDL_MOUSEMOTION:
|
||||
case SDL_EVENT_MOUSE_MOTION:
|
||||
QueueInput({InputKind::MousePosition, session, 0, static_cast<float>(event.motion.x),
|
||||
static_cast<float>(event.motion.y)});
|
||||
return true;
|
||||
case SDL_MOUSEBUTTONDOWN:
|
||||
case SDL_MOUSEBUTTONUP:
|
||||
case SDL_EVENT_MOUSE_BUTTON_DOWN:
|
||||
case SDL_EVENT_MOUSE_BUTTON_UP:
|
||||
QueueInput({InputKind::MousePosition, session, 0, static_cast<float>(event.button.x),
|
||||
static_cast<float>(event.button.y)});
|
||||
QueueInput({InputKind::MouseButton, session, event.button.button,
|
||||
event.type == SDL_MOUSEBUTTONDOWN ? 1.0f : 0.0f, 0.0f});
|
||||
event.type == SDL_EVENT_MOUSE_BUTTON_DOWN ? 1.0f : 0.0f, 0.0f});
|
||||
return true;
|
||||
case SDL_MOUSEWHEEL:
|
||||
case SDL_EVENT_MOUSE_WHEEL:
|
||||
QueueInput({InputKind::MouseWheel, session, 0, static_cast<float>(event.wheel.x),
|
||||
static_cast<float>(event.wheel.y)});
|
||||
return true;
|
||||
@@ -681,9 +684,9 @@ struct SystemOverlay::Impl {
|
||||
case InputKind::Button: {
|
||||
const bool down = event.x != 0.0f;
|
||||
if (session.kind == OverlayKind::Ime && down) {
|
||||
if (event.id == SDL_CONTROLLER_BUTTON_B) {
|
||||
if (event.id == SDL_GAMEPAD_BUTTON_EAST) {
|
||||
Ime::HostCancel(session.generation);
|
||||
} else if (event.id == SDL_CONTROLLER_BUTTON_Y) {
|
||||
} else if (event.id == SDL_GAMEPAD_BUTTON_NORTH) {
|
||||
Ime::HostBackspace(session.generation);
|
||||
}
|
||||
}
|
||||
@@ -695,19 +698,19 @@ struct SystemOverlay::Impl {
|
||||
}
|
||||
case InputKind::Axis: {
|
||||
const float value = event.x / (event.x < 0.0f ? 32768.0f : 32767.0f);
|
||||
if (event.id == SDL_CONTROLLER_AXIS_LEFTX) {
|
||||
if (event.id == SDL_GAMEPAD_AXIS_LEFTX) {
|
||||
io.AddKeyAnalogEvent(ImGuiKey_GamepadLStickLeft, value < -0.25f,
|
||||
std::max(-value, 0.0f));
|
||||
io.AddKeyAnalogEvent(ImGuiKey_GamepadLStickRight, value > 0.25f,
|
||||
std::max(value, 0.0f));
|
||||
} else if (event.id == SDL_CONTROLLER_AXIS_LEFTY) {
|
||||
} else if (event.id == SDL_GAMEPAD_AXIS_LEFTY) {
|
||||
io.AddKeyAnalogEvent(ImGuiKey_GamepadLStickUp, value < -0.25f,
|
||||
std::max(-value, 0.0f));
|
||||
io.AddKeyAnalogEvent(ImGuiKey_GamepadLStickDown, value > 0.25f,
|
||||
std::max(value, 0.0f));
|
||||
} else if (event.id == SDL_CONTROLLER_AXIS_RIGHTX) {
|
||||
} else if (event.id == SDL_GAMEPAD_AXIS_RIGHTX) {
|
||||
right_stick.x = std::abs(value) > 0.2f ? value : 0.0f;
|
||||
} else if (event.id == SDL_CONTROLLER_AXIS_RIGHTY) {
|
||||
} else if (event.id == SDL_GAMEPAD_AXIS_RIGHTY) {
|
||||
right_stick.y = std::abs(value) > 0.2f ? value : 0.0f;
|
||||
}
|
||||
break;
|
||||
|
||||
@@ -7,6 +7,7 @@
|
||||
#include <memory>
|
||||
|
||||
union SDL_Event;
|
||||
struct SDL_Window;
|
||||
|
||||
namespace Libs::Graphics {
|
||||
|
||||
@@ -17,7 +18,7 @@ struct SystemOverlayVisualState {
|
||||
uint64_t revision;
|
||||
};
|
||||
|
||||
void InitializeSystemOverlayInput();
|
||||
void InitializeSystemOverlayInput(SDL_Window* window);
|
||||
void ShutdownSystemOverlayInput();
|
||||
bool ProcessSystemOverlayInput(const SDL_Event& event);
|
||||
SystemOverlayVisualState GetSystemOverlayVisualState() noexcept;
|
||||
|
||||
@@ -20,6 +20,7 @@
|
||||
#include "kernel/pthread.h"
|
||||
#include "libs/errno.h"
|
||||
#include "libs/libs.h"
|
||||
#include "loader/systemContent.h"
|
||||
|
||||
#include <algorithm>
|
||||
#include <array>
|
||||
@@ -237,7 +238,9 @@ private:
|
||||
|
||||
struct VideoOutDriver::Impl {
|
||||
public:
|
||||
static constexpr int VIDEO_OUT_NUM_MAX = 4;
|
||||
static constexpr std::array VIDEO_OUT_BUSES {
|
||||
VIDEO_OUT_BUS_TYPE_MAIN, VIDEO_OUT_BUS_TYPE_OVERLAY, VIDEO_OUT_BUS_TYPE_SUB};
|
||||
static constexpr int VIDEO_OUT_NUM_MAX = VIDEO_OUT_BUSES.size() + 1;
|
||||
|
||||
Impl(uint32_t width, uint32_t height, Graphics::Presenter& presenter)
|
||||
: m_renderer(presenter.Renderer()), m_presenter(presenter), m_flip_queue(presenter) {
|
||||
@@ -248,7 +251,7 @@ public:
|
||||
~Impl();
|
||||
KYTY_CLASS_NO_COPY(Impl);
|
||||
|
||||
int Open(int bus_type);
|
||||
int Open(int bus_type, int index);
|
||||
bool Close(int handle);
|
||||
VideoOutConfig* Get(int handle);
|
||||
VideoOutConfig* Get(int handle, uint64_t& generation);
|
||||
@@ -562,7 +565,12 @@ Graphics::ImageInfo BufferAttributeGroup::ImageInfo(const VideoOutBuffer& buffer
|
||||
info.bgra16 = pixel_format.bgra16;
|
||||
info.mip_layout[0] = {0, total.size, pitch, attribute.height};
|
||||
if (compression != Graphics::VideoOutCompression::Uncompressed) {
|
||||
info.metadata.range = {buffer.metadata_address, 0};
|
||||
Graphics::TileSizeAlign dcc_size {};
|
||||
if (!Graphics::TileGetDccSize(attribute.width, attribute.height, 1,
|
||||
pixel_format.bytes_per_element, 1, tile_mode, dcc_size)) {
|
||||
EXIT("invalid video-out DCC footprint\n");
|
||||
}
|
||||
info.metadata.range = {buffer.metadata_address, dcc_size.size};
|
||||
info.metadata.kind = Graphics::ImageMetadataKind::Dcc;
|
||||
info.metadata.control = attribute.dcc_control;
|
||||
info.metadata.compression = compression;
|
||||
@@ -610,12 +618,17 @@ void VideoOutDriver::Impl::Init(uint32_t width, uint32_t height) {
|
||||
}
|
||||
}
|
||||
|
||||
int VideoOutDriver::Impl::Open(int bus_type) {
|
||||
int VideoOutDriver::Impl::Open(int bus_type, int index) {
|
||||
const auto bus = std::find(VIDEO_OUT_BUSES.begin(), VIDEO_OUT_BUSES.end(), bus_type);
|
||||
if (bus == VIDEO_OUT_BUSES.end()) {
|
||||
return VIDEO_OUT_ERROR_INVALID_VALUE;
|
||||
}
|
||||
EXIT_NOT_IMPLEMENTED(index != 0);
|
||||
Common::LockGuard lock(m_mutex);
|
||||
|
||||
const int handle = bus_type + 1;
|
||||
const int handle = static_cast<int>(bus - VIDEO_OUT_BUSES.begin()) + 1;
|
||||
if (m_video_out_ctx[handle].opened) {
|
||||
return -1;
|
||||
return VIDEO_OUT_ERROR_RESOURCE_BUSY;
|
||||
}
|
||||
auto& config = m_video_out_ctx[handle];
|
||||
Common::LockGuard config_lock(config.mutex);
|
||||
@@ -1169,21 +1182,9 @@ KYTY_SYSV_ABI int VideoOutOpen(int user_id, int bus_type, int index, const void*
|
||||
PRINT_NAME();
|
||||
|
||||
EXIT_NOT_IMPLEMENTED(user_id != 255 && user_id != 0);
|
||||
if (bus_type != VIDEO_OUT_BUS_TYPE_MAIN && bus_type != VIDEO_OUT_BUS_TYPE_OVERLAY &&
|
||||
bus_type != VIDEO_OUT_BUS_TYPE_SUB) {
|
||||
return VIDEO_OUT_ERROR_INVALID_VALUE;
|
||||
}
|
||||
EXIT_NOT_IMPLEMENTED(index != 0);
|
||||
|
||||
LOGF("\t param = 0x%016" PRIx64 "\n", reinterpret_cast<uint64_t>(param));
|
||||
|
||||
int handle = DriverState().Open(bus_type);
|
||||
|
||||
if (handle < 0) {
|
||||
return VIDEO_OUT_ERROR_RESOURCE_BUSY;
|
||||
}
|
||||
|
||||
return handle;
|
||||
return DriverState().Open(bus_type, index);
|
||||
}
|
||||
|
||||
KYTY_SYSV_ABI int VideoOutClose(int handle) {
|
||||
@@ -1659,8 +1660,12 @@ KYTY_SYSV_ABI int VideoOutGetOutputStatus(int handle, VideoOutOutputStatus* stat
|
||||
return VIDEO_OUT_ERROR_INVALID_HANDLE;
|
||||
}
|
||||
|
||||
int32_t attribute3 = 0;
|
||||
Loader::SystemContentParamSfoGetInt("ATTRIBUTE3", &attribute3);
|
||||
ctx->mutex.Lock();
|
||||
status->resolution = (ctx->width >= 3840 || ctx->height >= 2160 ? 2u : 1u);
|
||||
// Primary output reports 4K unless param.json Video-out Info enables resolution detection.
|
||||
status->resolution =
|
||||
((attribute3 & 4) != 0 && ctx->width < 3840 && ctx->height < 2160 ? 1u : 2u);
|
||||
status->dynamicRange = 1;
|
||||
status->refreshRate =
|
||||
(ctx->output_mode == VIDEO_OUT_OUTPUT_MODE_119_88HZ || Config::GetVblankFrequency() >= 119
|
||||
@@ -1675,6 +1680,21 @@ KYTY_SYSV_ABI int VideoOutGetOutputStatus(int handle, VideoOutOutputStatus* stat
|
||||
return OK;
|
||||
}
|
||||
|
||||
KYTY_SYSV_ABI int VideoOutGetVrrStatus(int handle, int32_t* status) {
|
||||
PRINT_NAME();
|
||||
|
||||
if (status == nullptr) {
|
||||
return VIDEO_OUT_ERROR_INVALID_ADDRESS;
|
||||
}
|
||||
if (DriverState().Get(handle) == nullptr) {
|
||||
return VIDEO_OUT_ERROR_INVALID_HANDLE;
|
||||
}
|
||||
|
||||
// Kyty currently presents at a fixed refresh rate and does not negotiate VRR.
|
||||
*status = 0;
|
||||
return OK;
|
||||
}
|
||||
|
||||
static int ValidateOutputConfig(int handle, uint64_t mode, const VideoOutOutputOptions* options,
|
||||
void* reserved_ptr, uint64_t reserved) {
|
||||
if (!DriverState().IsOpened(handle)) {
|
||||
|
||||
@@ -87,6 +87,7 @@ KYTY_SYSV_ABI int VideoOutGetEventData(const LibKernel::EventQueue::KernelEvent*
|
||||
KYTY_SYSV_ABI int VideoOutGetEventCount(const LibKernel::EventQueue::KernelEvent* ev);
|
||||
KYTY_SYSV_ABI int VideoOutWaitVblank(int handle);
|
||||
KYTY_SYSV_ABI int VideoOutGetOutputStatus(int handle, VideoOutOutputStatus* status);
|
||||
KYTY_SYSV_ABI int VideoOutGetVrrStatus(int handle, int32_t* status);
|
||||
KYTY_SYSV_ABI int VideoOutInitializeOutputOptions(VideoOutOutputOptions* options);
|
||||
KYTY_SYSV_ABI int VideoOutIsOutputSupported(int handle, uint64_t mode,
|
||||
const VideoOutOutputOptions* options,
|
||||
|
||||
@@ -1,12 +1,7 @@
|
||||
#include "graphics/presentation/window/hostInput.h"
|
||||
|
||||
#include "SDL_error.h"
|
||||
#include "SDL_events.h"
|
||||
#include "SDL_keyboard.h"
|
||||
#include "SDL_keycode.h"
|
||||
#include "SDL_mouse.h"
|
||||
#include "SDL_timer.h"
|
||||
#include "SDL_video.h"
|
||||
#include <SDL3/SDL.h>
|
||||
|
||||
#include "common/assert.h"
|
||||
#include "common/emulatorConfig.h"
|
||||
#include "common/logging/log.h"
|
||||
@@ -80,6 +75,7 @@ struct MouseJoystickState {
|
||||
};
|
||||
|
||||
MouseJoystickState g_mouse;
|
||||
SDL_Window* g_mouse_window = nullptr;
|
||||
|
||||
std::size_t ControlFromName(std::string_view name) {
|
||||
const auto info = std::find_if(CONTROL_INFO.begin(), CONTROL_INFO.end(),
|
||||
@@ -141,9 +137,8 @@ public:
|
||||
}
|
||||
}
|
||||
|
||||
const bool reserved = binding.key == SDLK_ESCAPE || binding.key == SDLK_SPACE ||
|
||||
binding.key == SDLK_F1 || binding.key == SDLK_F7 ||
|
||||
binding.key == SDLK_F11;
|
||||
const bool reserved = binding.key == SDLK_ESCAPE || binding.key == SDLK_F1 ||
|
||||
binding.key == SDLK_F7 || binding.key == SDLK_F11;
|
||||
if (binding.control == INVALID_CONTROL || reserved ||
|
||||
(binding.key == SDLK_UNKNOWN && binding.mouse_button == 0)) {
|
||||
EXIT("Invalid input mapping: %s\n", value.c_str());
|
||||
@@ -214,12 +209,14 @@ uint32_t DefaultKeyboardButton(int key_code) {
|
||||
case SDLK_LEFT: return Controller::PAD_BUTTON_LEFT;
|
||||
case SDLK_DOWN: return Controller::PAD_BUTTON_DOWN;
|
||||
case SDLK_RIGHT: return Controller::PAD_BUTTON_RIGHT;
|
||||
case SDLK_j: return Controller::PAD_BUTTON_CROSS;
|
||||
case SDLK_i: return Controller::PAD_BUTTON_TRIANGLE;
|
||||
case SDLK_k: return Controller::PAD_BUTTON_SQUARE;
|
||||
case SDLK_l: return Controller::PAD_BUTTON_CIRCLE;
|
||||
case SDLK_q: return Controller::PAD_BUTTON_L1;
|
||||
case SDLK_e: return Controller::PAD_BUTTON_R1;
|
||||
case SDLK_J: return Controller::PAD_BUTTON_CROSS;
|
||||
case SDLK_I: return Controller::PAD_BUTTON_TRIANGLE;
|
||||
case SDLK_K: return Controller::PAD_BUTTON_SQUARE;
|
||||
case SDLK_L: return Controller::PAD_BUTTON_CIRCLE;
|
||||
case SDLK_Q: return Controller::PAD_BUTTON_L1;
|
||||
case SDLK_E: return Controller::PAD_BUTTON_R1;
|
||||
case SDLK_Z: return Controller::PAD_BUTTON_L2;
|
||||
case SDLK_C: return Controller::PAD_BUTTON_R2;
|
||||
case SDLK_LSHIFT: return Controller::PAD_BUTTON_L3;
|
||||
case SDLK_LCTRL: return Controller::PAD_BUTTON_R3;
|
||||
case SDLK_RETURN:
|
||||
@@ -283,35 +280,35 @@ void DefaultKeyboardInput(int key_code, bool down) {
|
||||
switch (NormalizeKey(static_cast<SDL_Keycode>(key_code))) {
|
||||
case SDLK_BACKSPACE: SetTouchPad(0.25f, down); return;
|
||||
case SDLK_TAB: SetTouchPad(0.75f, down); return;
|
||||
case SDLK_a:
|
||||
case SDLK_A:
|
||||
left.left = down;
|
||||
SetStickAxis(Controller::Axis::LeftX, left.left, left.right);
|
||||
return;
|
||||
case SDLK_d:
|
||||
case SDLK_D:
|
||||
left.right = down;
|
||||
SetStickAxis(Controller::Axis::LeftX, left.left, left.right);
|
||||
return;
|
||||
case SDLK_w:
|
||||
case SDLK_W:
|
||||
left.up = down;
|
||||
SetStickAxis(Controller::Axis::LeftY, left.up, left.down);
|
||||
return;
|
||||
case SDLK_s:
|
||||
case SDLK_S:
|
||||
left.down = down;
|
||||
SetStickAxis(Controller::Axis::LeftY, left.up, left.down);
|
||||
return;
|
||||
case SDLK_f:
|
||||
case SDLK_F:
|
||||
right.left = down;
|
||||
SetStickAxis(Controller::Axis::RightX, right.left, right.right);
|
||||
return;
|
||||
case SDLK_h:
|
||||
case SDLK_H:
|
||||
right.right = down;
|
||||
SetStickAxis(Controller::Axis::RightX, right.left, right.right);
|
||||
return;
|
||||
case SDLK_t:
|
||||
case SDLK_T:
|
||||
right.up = down;
|
||||
SetStickAxis(Controller::Axis::RightY, right.up, right.down);
|
||||
return;
|
||||
case SDLK_g:
|
||||
case SDLK_G:
|
||||
right.down = down;
|
||||
SetStickAxis(Controller::Axis::RightY, right.up, right.down);
|
||||
return;
|
||||
@@ -319,11 +316,11 @@ void DefaultKeyboardInput(int key_code, bool down) {
|
||||
}
|
||||
}
|
||||
|
||||
void MouseToJoystick(int delta_x, int delta_y) {
|
||||
void MouseToJoystick(float delta_x, float delta_y) {
|
||||
const double distance = std::hypot(delta_x, delta_y);
|
||||
const double scale =
|
||||
std::clamp(distance * GetInputMap().MouseSensitivity() + 16.0, 64.0, 128.0) / distance;
|
||||
const auto map_axis = [scale](int delta) {
|
||||
const auto map_axis = [scale](float delta) {
|
||||
return std::clamp(128 + static_cast<int>(std::lround(delta * scale)), 0, 255);
|
||||
};
|
||||
Controller::SetRightStick(Controller::HOST_INPUT_CONTROLLER_ID, map_axis(delta_x),
|
||||
@@ -339,16 +336,45 @@ void CenterMouseStick() {
|
||||
}
|
||||
|
||||
bool SetRelativeMouseMode(bool enabled) {
|
||||
if (SDL_SetRelativeMouseMode(enabled ? SDL_TRUE : SDL_FALSE) == 0) {
|
||||
if (SDL_SetWindowRelativeMouseMode(g_mouse_window, enabled)) {
|
||||
return true;
|
||||
}
|
||||
LOGF("Mouse-to-joystick relative mode failed: %s\n", SDL_GetError());
|
||||
return false;
|
||||
}
|
||||
|
||||
int PollMouse(uint64_t now_ms) {
|
||||
if (now_ms < g_mouse.next_poll) {
|
||||
return static_cast<int>(g_mouse.next_poll - now_ms);
|
||||
}
|
||||
g_mouse.next_poll = now_ms + MOUSE_POLL_INTERVAL_MS;
|
||||
|
||||
float delta_x = 0;
|
||||
float delta_y = 0;
|
||||
SDL_GetRelativeMouseState(&delta_x, &delta_y);
|
||||
if (delta_x == 0 && delta_y == 0) {
|
||||
CenterMouseStick();
|
||||
} else {
|
||||
MouseToJoystick(delta_x, delta_y);
|
||||
g_mouse.output = true;
|
||||
}
|
||||
return MOUSE_POLL_INTERVAL_MS;
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
void HostInputInit() {
|
||||
void HostInputInit(SDL_Window* window) {
|
||||
GetInputMap();
|
||||
g_mouse_window = window;
|
||||
}
|
||||
|
||||
void HostInputShutdown() {
|
||||
if (g_mouse.enabled) {
|
||||
SetRelativeMouseMode(false);
|
||||
CenterMouseStick();
|
||||
g_mouse = {};
|
||||
}
|
||||
g_mouse_window = nullptr;
|
||||
}
|
||||
|
||||
void HostInputKey(int key_code, bool down) {
|
||||
@@ -379,51 +405,33 @@ void HostInputToggleMouseToJoystick() {
|
||||
if (!SetRelativeMouseMode(true)) {
|
||||
return;
|
||||
}
|
||||
int ignored_x = 0;
|
||||
int ignored_y = 0;
|
||||
SDL_GetRelativeMouseState(&ignored_x, &ignored_y);
|
||||
g_mouse.enabled = true;
|
||||
g_mouse.next_poll = SDL_GetTicks64() + MOUSE_POLL_INTERVAL_MS;
|
||||
g_mouse.enabled = true;
|
||||
LOGF("Mouse to right stick: enabled (F7 to release)\n");
|
||||
}
|
||||
|
||||
int PollMouse(uint64_t now_ms) {
|
||||
if (now_ms < g_mouse.next_poll) {
|
||||
return static_cast<int>(g_mouse.next_poll - now_ms);
|
||||
}
|
||||
g_mouse.next_poll = now_ms + MOUSE_POLL_INTERVAL_MS;
|
||||
|
||||
int delta_x = 0;
|
||||
int delta_y = 0;
|
||||
SDL_GetRelativeMouseState(&delta_x, &delta_y);
|
||||
if (delta_x == 0 && delta_y == 0) {
|
||||
CenterMouseStick();
|
||||
return MOUSE_POLL_INTERVAL_MS;
|
||||
}
|
||||
|
||||
MouseToJoystick(delta_x, delta_y);
|
||||
g_mouse.output = true;
|
||||
return MOUSE_POLL_INTERVAL_MS;
|
||||
}
|
||||
|
||||
bool HostInputWaitEvent(SDL_Event* event) {
|
||||
if (!g_mouse.enabled || SDL_GetKeyboardFocus() == nullptr) {
|
||||
bool has_event;
|
||||
if (!g_mouse.enabled || SDL_GetKeyboardFocus() != g_mouse_window) {
|
||||
g_mouse.next_poll = 0;
|
||||
CenterMouseStick();
|
||||
if (SDL_WaitEvent(event) == 0) {
|
||||
has_event = SDL_WaitEvent(event);
|
||||
if (!has_event) {
|
||||
EXIT("%s\n", SDL_GetError());
|
||||
}
|
||||
return true;
|
||||
} else {
|
||||
if (g_mouse.next_poll == 0) {
|
||||
SDL_GetRelativeMouseState(nullptr, nullptr);
|
||||
g_mouse.next_poll = SDL_GetTicks() + MOUSE_POLL_INTERVAL_MS;
|
||||
}
|
||||
has_event = SDL_WaitEventTimeout(event, PollMouse(SDL_GetTicks()));
|
||||
}
|
||||
|
||||
const int timeout_ms = PollMouse(SDL_GetTicks64());
|
||||
SDL_ClearError();
|
||||
if (SDL_WaitEventTimeout(event, timeout_ms) != 0) {
|
||||
return true;
|
||||
if (has_event && event->type == SDL_EVENT_WINDOW_FOCUS_LOST &&
|
||||
event->window.windowID == SDL_GetWindowID(g_mouse_window)) {
|
||||
g_mouse.next_poll = 0;
|
||||
CenterMouseStick();
|
||||
}
|
||||
if (SDL_GetError()[0] != '\0') {
|
||||
EXIT("%s\n", SDL_GetError());
|
||||
}
|
||||
return false;
|
||||
return has_event;
|
||||
}
|
||||
|
||||
} // namespace Libs::Graphics
|
||||
@@ -3,11 +3,13 @@
|
||||
|
||||
#include <cstdint>
|
||||
|
||||
struct SDL_Window;
|
||||
union SDL_Event;
|
||||
|
||||
namespace Libs::Graphics {
|
||||
|
||||
void HostInputInit();
|
||||
void HostInputInit(SDL_Window* window);
|
||||
void HostInputShutdown();
|
||||
void HostInputKey(int key_code, bool down);
|
||||
void HostInputMouseButton(uint8_t mouse_button, bool down);
|
||||
void HostInputToggleMouseToJoystick();
|
||||
|
||||
@@ -22,9 +22,6 @@
|
||||
|
||||
// IWYU pragma: no_include <intrin.h>
|
||||
|
||||
#define KYTY_ENABLE_DEBUG_PRINTF
|
||||
#define KYTY_DBG_INPUT
|
||||
|
||||
namespace Libs::Graphics {
|
||||
|
||||
struct Presenter::Frame {
|
||||
@@ -174,6 +171,13 @@ void Presenter::Frame::Configure(GraphicContext& graphics, vk::Extent2D extent,
|
||||
EXIT("unsupported prepared frame, extent=%ux%u format=%d\n", extent.width, extent.height,
|
||||
static_cast<int>(format));
|
||||
}
|
||||
auto& dst = image;
|
||||
const bool compatible = dst.image != nullptr && dst.extent.width == extent.width &&
|
||||
dst.extent.height == extent.height && dst.format == format;
|
||||
if (compatible) {
|
||||
return;
|
||||
}
|
||||
|
||||
const auto features = graphics.GetFormatProperties(format).optimalTilingFeatures;
|
||||
const auto required =
|
||||
vk::FormatFeatureFlagBits::eBlitSrc | vk::FormatFeatureFlagBits::eSampledImageFilterLinear |
|
||||
@@ -183,12 +187,6 @@ void Presenter::Frame::Configure(GraphicContext& graphics, vk::Extent2D extent,
|
||||
static_cast<int>(format), static_cast<vk::FormatFeatureFlags::MaskType>(features));
|
||||
}
|
||||
|
||||
auto& dst = image;
|
||||
const bool compatible = dst.image != nullptr && dst.extent.width == extent.width &&
|
||||
dst.extent.height == extent.height && dst.format == format;
|
||||
if (compatible) {
|
||||
return;
|
||||
}
|
||||
if (dst.image != nullptr) {
|
||||
graphics.DeleteImage(dst);
|
||||
}
|
||||
@@ -282,6 +280,7 @@ public:
|
||||
|
||||
void Create();
|
||||
void Recreate(bool surface_lost = false);
|
||||
[[nodiscard]] bool NeedsResize() const;
|
||||
[[nodiscard]] Status AcquireNextImage();
|
||||
[[nodiscard]] bool PrepareSystemOverlay();
|
||||
void RecordPresentCommands(CommandBuffer& command, VulkanImage& source,
|
||||
@@ -301,6 +300,8 @@ private:
|
||||
vk::SwapchainKHR m_handle = nullptr;
|
||||
vk::Format m_format = vk::Format::eUndefined;
|
||||
vk::Extent2D m_extent {};
|
||||
// Drawable pixel size observed when this swapchain was created.
|
||||
vk::Extent2D m_window_extent {};
|
||||
std::vector<vk::Image> m_images;
|
||||
std::vector<vk::ImageView> m_image_views;
|
||||
std::vector<vk::Semaphore> m_image_acquired;
|
||||
@@ -360,19 +361,25 @@ void Swapchain::Create() {
|
||||
EXIT_IF(graphics.device == nullptr);
|
||||
EXIT_IF(m_window.surface == nullptr);
|
||||
|
||||
Common::LockGuard lock(m_window.mutex);
|
||||
EXIT_IF(graphics.screen_width == 0);
|
||||
EXIT_IF(graphics.screen_height == 0);
|
||||
// Capture the size before querying surface capabilities. If the window changes
|
||||
// during creation, the next presentation will detect the newer size.
|
||||
{
|
||||
Common::LockGuard lock(m_window.mutex);
|
||||
m_window_extent = {graphics.screen_width, graphics.screen_height};
|
||||
}
|
||||
EXIT_IF(m_window_extent.width == 0);
|
||||
EXIT_IF(m_window_extent.height == 0);
|
||||
m_window.RefreshSurfaceCapabilities();
|
||||
const auto& surface = m_window.surface_capabilities;
|
||||
EXIT_NOT_IMPLEMENTED(surface.formats.empty());
|
||||
|
||||
m_extent = surface.capabilities.currentExtent;
|
||||
if (m_extent.width == std::numeric_limits<uint32_t>::max()) {
|
||||
m_extent.width =
|
||||
std::clamp(graphics.screen_width, surface.capabilities.minImageExtent.width,
|
||||
std::clamp(m_window_extent.width, surface.capabilities.minImageExtent.width,
|
||||
surface.capabilities.maxImageExtent.width);
|
||||
m_extent.height =
|
||||
std::clamp(graphics.screen_height, surface.capabilities.minImageExtent.height,
|
||||
std::clamp(m_window_extent.height, surface.capabilities.minImageExtent.height,
|
||||
surface.capabilities.maxImageExtent.height);
|
||||
}
|
||||
uint32_t image_count = surface.capabilities.minImageCount + 1;
|
||||
@@ -476,8 +483,8 @@ void Swapchain::Create() {
|
||||
graphics.device.createSemaphore(&semaphore_info, nullptr, &m_render_complete[i]),
|
||||
"create swapchain render-complete semaphore");
|
||||
}
|
||||
m_image_index = static_cast<uint32_t>(-1);
|
||||
m_frame_index = 0;
|
||||
m_image_index = static_cast<uint32_t>(-1);
|
||||
m_frame_index = 0;
|
||||
}
|
||||
|
||||
Swapchain::~Swapchain() {
|
||||
@@ -518,11 +525,12 @@ void Swapchain::Destroy() {
|
||||
graphics.device.destroySwapchainKHR(m_handle, nullptr);
|
||||
}
|
||||
|
||||
m_handle = nullptr;
|
||||
m_format = vk::Format::eUndefined;
|
||||
m_extent = {};
|
||||
m_image_index = static_cast<uint32_t>(-1);
|
||||
m_frame_index = 0;
|
||||
m_handle = nullptr;
|
||||
m_format = vk::Format::eUndefined;
|
||||
m_extent = {};
|
||||
m_window_extent = {};
|
||||
m_image_index = static_cast<uint32_t>(-1);
|
||||
m_frame_index = 0;
|
||||
m_images.clear();
|
||||
m_image_views.clear();
|
||||
m_image_acquired.clear();
|
||||
@@ -535,15 +543,22 @@ void Swapchain::Recreate(bool surface_lost) {
|
||||
#if defined(__APPLE__)
|
||||
// Surface recreation goes through SDL_Vulkan_CreateSurface, which touches the
|
||||
// window's view/layer and must run on the main thread on macOS.
|
||||
m_window.RunOnMainThread([this] { m_window.RecreateSurface(); });
|
||||
EXIT_IF(!SDL_RunOnMainThread(
|
||||
[](void* window) { static_cast<WindowContext*>(window)->RecreateSurface(); },
|
||||
&m_window, true));
|
||||
#else
|
||||
m_window.RecreateSurface();
|
||||
#endif
|
||||
}
|
||||
m_window.RefreshSurfaceCapabilities();
|
||||
Create();
|
||||
}
|
||||
|
||||
bool Swapchain::NeedsResize() const {
|
||||
Common::LockGuard lock(m_window.mutex);
|
||||
return m_window_extent.width != m_window.graphic_ctx.screen_width ||
|
||||
m_window_extent.height != m_window.graphic_ctx.screen_height;
|
||||
}
|
||||
|
||||
Swapchain::Status Swapchain::AcquireNextImage() {
|
||||
EXIT_IF(m_handle == nullptr || m_frame_index >= m_image_acquired.size());
|
||||
m_image_index = static_cast<uint32_t>(-1);
|
||||
@@ -769,6 +784,10 @@ void Presenter::Present(Frame& frame, bool reuse) {
|
||||
|
||||
const auto overlay_visual = GetSystemOverlayVisualState();
|
||||
auto& swapchain = m_impl->swapchain;
|
||||
// Some window systems keep presenting an old swapchain after a resize.
|
||||
if (swapchain.NeedsResize()) {
|
||||
m_impl->RecoverSwapchain(Swapchain::Status::Recreate);
|
||||
}
|
||||
for (uint32_t attempt = 0; attempt < 2; attempt++) {
|
||||
auto status = swapchain.AcquireNextImage();
|
||||
if (status != Swapchain::Status::Success) {
|
||||
|
||||
@@ -1,20 +1,6 @@
|
||||
#include "SDL.h"
|
||||
#include "SDL_error.h"
|
||||
#include "SDL_events.h"
|
||||
#include "SDL_gamecontroller.h"
|
||||
#include "SDL_hints.h"
|
||||
#include "SDL_joystick.h"
|
||||
#include "SDL_keyboard.h"
|
||||
#include "SDL_keycode.h"
|
||||
#include "SDL_mouse.h"
|
||||
#include "SDL_pixels.h"
|
||||
#include "SDL_rwops.h"
|
||||
#include "SDL_stdinc.h"
|
||||
#include "SDL_surface.h"
|
||||
#include "SDL_thread.h"
|
||||
#include "SDL_touch.h"
|
||||
#include "SDL_video.h"
|
||||
#include "SDL_vulkan.h"
|
||||
#include <SDL3/SDL.h>
|
||||
#include <SDL3/SDL_vulkan.h>
|
||||
|
||||
#include "common/assert.h"
|
||||
#include "common/common.h"
|
||||
#include "common/emulatorConfig.h"
|
||||
@@ -49,7 +35,6 @@
|
||||
// IWYU pragma: no_include <intrin.h>
|
||||
|
||||
#define KYTY_ENABLE_DEBUG_PRINTF
|
||||
#define KYTY_DBG_INPUT
|
||||
|
||||
namespace Libs::Graphics {
|
||||
|
||||
@@ -201,6 +186,8 @@ static void VulkanFindPhysicalDevice(vk::Instance instance, vk::SurfaceKHR surfa
|
||||
vk::PhysicalDeviceVulkan13Features features13 {};
|
||||
|
||||
vk::PhysicalDeviceColorWriteEnableFeaturesEXT color_write_ext {};
|
||||
vk::PhysicalDeviceImageViewMinLodFeaturesEXT image_view_min_lod {};
|
||||
color_write_ext.pNext = &image_view_min_lod;
|
||||
|
||||
vk::PhysicalDeviceDepthClipEnableFeaturesEXT depth_clip_enable {};
|
||||
depth_clip_enable.pNext = &color_write_ext;
|
||||
@@ -235,6 +222,10 @@ static void VulkanFindPhysicalDevice(vk::Instance instance, vk::SurfaceKHR surfa
|
||||
skip_device = true;
|
||||
#endif
|
||||
}
|
||||
if (image_view_min_lod.minLod != VK_TRUE) {
|
||||
LOGF("image view minLod is not supported\n");
|
||||
skip_device = true;
|
||||
}
|
||||
|
||||
if (depth_clip_control.depthClipControl != VK_TRUE) {
|
||||
LOGF("depthClipControl is not supported\n");
|
||||
@@ -514,11 +505,14 @@ static vk::Device VulkanCreateDevice(GraphicContext& graphics,
|
||||
depth_clip_enable.depthClipEnable = VK_TRUE;
|
||||
|
||||
vk::PhysicalDeviceDepthClipControlFeaturesEXT depth_clip_control {};
|
||||
vk::PhysicalDeviceImageViewMinLodFeaturesEXT image_view_min_lod {};
|
||||
image_view_min_lod.minLod = VK_TRUE;
|
||||
depth_clip_control.pNext = &image_view_min_lod;
|
||||
// MoltenVK lacks VK_EXT_depth_clip_enable and VK_EXT_color_write_enable, so drop those
|
||||
// feature structs from the chain on macOS (the renderer falls back to default depth
|
||||
// clipping and static color-write masks).
|
||||
#if !defined(__APPLE__)
|
||||
depth_clip_control.pNext = &depth_clip_enable;
|
||||
image_view_min_lod.pNext = &depth_clip_enable;
|
||||
#endif
|
||||
depth_clip_control.depthClipControl = VK_TRUE;
|
||||
|
||||
@@ -602,10 +596,11 @@ static vk::Device VulkanCreateDevice(GraphicContext& graphics,
|
||||
graphics.mesh_shader_properties.maxMeshOutputPrimitives,
|
||||
graphics.mesh_shader_properties.maxMeshSharedMemorySize);
|
||||
}
|
||||
// VulkanFindPhysicalDevice already checked the required creation features. These two
|
||||
// VulkanFindPhysicalDevice already checked the required creation features. These
|
||||
// requirements are specific to this creation path and are not part of device selection.
|
||||
EXIT_NOT_IMPLEMENTED(supported_features2.features.shaderInt64 != VK_TRUE);
|
||||
EXIT_NOT_IMPLEMENTED(supported_features2.features.vertexPipelineStoresAndAtomics != VK_TRUE);
|
||||
EXIT_NOT_IMPLEMENTED(supported_features2.features.dualSrcBlend != VK_TRUE);
|
||||
vk::PhysicalDeviceFeatures device_features {};
|
||||
device_features.fragmentStoresAndAtomics = VK_TRUE;
|
||||
device_features.samplerAnisotropy = VK_TRUE;
|
||||
@@ -616,6 +611,7 @@ static vk::Device VulkanCreateDevice(GraphicContext& graphics,
|
||||
device_features.shaderStorageImageWriteWithoutFormat = VK_TRUE;
|
||||
device_features.shaderImageGatherExtended = VK_TRUE;
|
||||
device_features.independentBlend = VK_TRUE;
|
||||
device_features.dualSrcBlend = VK_TRUE;
|
||||
device_features.tessellationShader = VK_TRUE;
|
||||
device_features.sampleRateShading = VK_TRUE;
|
||||
device_features.depthBiasClamp = VK_TRUE;
|
||||
@@ -690,25 +686,13 @@ static vk::Device VulkanCreateDevice(GraphicContext& graphics,
|
||||
return device;
|
||||
}
|
||||
|
||||
static void VulkanGetExtensions(SDL_Window* window, VulkanExtensions& r) {
|
||||
EXIT_IF(window == nullptr);
|
||||
|
||||
static void VulkanGetExtensions(VulkanExtensions& r) {
|
||||
uint32_t required_extensions_count = 0;
|
||||
|
||||
auto sdl_result = SDL_Vulkan_GetInstanceExtensions(window, &required_extensions_count, nullptr);
|
||||
|
||||
EXIT_NOT_IMPLEMENTED(sdl_result == SDL_FALSE);
|
||||
const char* const* extensions = SDL_Vulkan_GetInstanceExtensions(&required_extensions_count);
|
||||
EXIT_NOT_IMPLEMENTED(extensions == nullptr);
|
||||
EXIT_NOT_IMPLEMENTED(required_extensions_count == 0);
|
||||
|
||||
r.required_extensions =
|
||||
std::vector<const char*>(required_extensions_count); // @suppress("Ambiguous problem")
|
||||
|
||||
sdl_result = SDL_Vulkan_GetInstanceExtensions(window, &required_extensions_count,
|
||||
r.required_extensions.data());
|
||||
|
||||
EXIT_NOT_IMPLEMENTED(sdl_result == SDL_FALSE);
|
||||
EXIT_NOT_IMPLEMENTED(required_extensions_count == 0);
|
||||
EXIT_NOT_IMPLEMENTED(required_extensions_count != r.required_extensions.size());
|
||||
r.required_extensions.assign(extensions, extensions + required_extensions_count);
|
||||
|
||||
r.available_extensions =
|
||||
EnumerateVulkan<vk::ExtensionProperties>( // @suppress("Ambiguous problem")
|
||||
@@ -889,7 +873,7 @@ void WindowContext::CreateVulkan() {
|
||||
VULKAN_HPP_DEFAULT_DISPATCHER.init(get_instance_proc_addr);
|
||||
|
||||
VulkanExtensions r;
|
||||
VulkanGetExtensions(window, r);
|
||||
VulkanGetExtensions(r);
|
||||
VulkanCheckInstanceVersion();
|
||||
|
||||
vk::ApplicationInfo app_info {};
|
||||
@@ -976,15 +960,16 @@ void WindowContext::CreateVulkan() {
|
||||
}
|
||||
|
||||
vk::SurfaceKHR::CType native_surface = VK_NULL_HANDLE;
|
||||
if (SDL_Vulkan_CreateSurface(window, static_cast<vk::Instance::CType>(graphic_ctx.instance),
|
||||
&native_surface) == SDL_FALSE) {
|
||||
if (!SDL_Vulkan_CreateSurface(window, static_cast<vk::Instance::CType>(graphic_ctx.instance),
|
||||
nullptr, &native_surface)) {
|
||||
EXIT("Could not create a Vulkan surface");
|
||||
}
|
||||
surface = native_surface;
|
||||
|
||||
std::vector<const char*> device_extensions = {
|
||||
VK_KHR_SWAPCHAIN_EXTENSION_NAME, VK_EXT_DEPTH_CLIP_CONTROL_EXTENSION_NAME,
|
||||
VK_KHR_PUSH_DESCRIPTOR_EXTENSION_NAME, "VK_KHR_maintenance1"};
|
||||
VK_EXT_IMAGE_VIEW_MIN_LOD_EXTENSION_NAME, VK_KHR_PUSH_DESCRIPTOR_EXTENSION_NAME,
|
||||
"VK_KHR_maintenance1"};
|
||||
|
||||
#if defined(__APPLE__)
|
||||
// MoltenVK lacks VK_EXT_depth_clip_enable and VK_EXT_color_write_enable; the renderer
|
||||
@@ -1089,8 +1074,8 @@ void WindowContext::RecreateSurface() {
|
||||
surface = nullptr;
|
||||
}
|
||||
vk::SurfaceKHR::CType native_surface = VK_NULL_HANDLE;
|
||||
if (SDL_Vulkan_CreateSurface(window, static_cast<vk::Instance::CType>(graphic_ctx.instance),
|
||||
&native_surface) == SDL_FALSE) {
|
||||
if (!SDL_Vulkan_CreateSurface(window, static_cast<vk::Instance::CType>(graphic_ctx.instance),
|
||||
nullptr, &native_surface)) {
|
||||
EXIT("Could not recreate the Vulkan surface: %s\n", SDL_GetError());
|
||||
}
|
||||
surface = native_surface;
|
||||
@@ -1126,7 +1111,7 @@ WindowContext::~WindowContext() {
|
||||
SDL_DestroyWindow(window);
|
||||
window = nullptr;
|
||||
}
|
||||
SDL_QuitSubSystem(SDL_INIT_VIDEO | SDL_INIT_GAMECONTROLLER);
|
||||
SDL_QuitSubSystem(SDL_INIT_VIDEO | SDL_INIT_GAMEPAD);
|
||||
}
|
||||
|
||||
} // namespace Libs::Graphics
|
||||
@@ -1,23 +1,7 @@
|
||||
#include "graphics/presentation/window.h"
|
||||
|
||||
#include "SDL.h"
|
||||
#include "SDL_error.h"
|
||||
#include "SDL_events.h"
|
||||
#include "SDL_filesystem.h"
|
||||
#include "SDL_gamecontroller.h"
|
||||
#include "SDL_hints.h"
|
||||
#include "SDL_joystick.h"
|
||||
#include "SDL_keyboard.h"
|
||||
#include "SDL_keycode.h"
|
||||
#include "SDL_mouse.h"
|
||||
#include "SDL_pixels.h"
|
||||
#include "SDL_rwops.h"
|
||||
#include "SDL_stdinc.h"
|
||||
#include "SDL_surface.h"
|
||||
#include "SDL_thread.h"
|
||||
#include "SDL_touch.h"
|
||||
#include "SDL_video.h"
|
||||
#include "SDL_vulkan.h"
|
||||
#include <SDL3/SDL.h>
|
||||
|
||||
#include "common/assert.h"
|
||||
#include "common/common.h"
|
||||
#include "common/emulatorConfig.h"
|
||||
@@ -27,6 +11,7 @@
|
||||
#include "common/systemInfo.h"
|
||||
#include "common/threads.h"
|
||||
#include "common/timer.h"
|
||||
#include "common/stringUtils.h"
|
||||
#include "graphics/host_gpu/graphicContext.h"
|
||||
#include "graphics/host_gpu/renderer/render.h"
|
||||
#include "graphics/host_gpu/renderer/renderContext.h"
|
||||
@@ -45,6 +30,7 @@
|
||||
#include <string>
|
||||
#include <vector>
|
||||
#include <vulkan/vk_platform.h>
|
||||
#include <filesystem>
|
||||
|
||||
#define STB_IMAGE_IMPLEMENTATION
|
||||
#define STBI_NO_SIMD
|
||||
@@ -52,7 +38,6 @@
|
||||
|
||||
// IWYU pragma: no_include <intrin.h>
|
||||
|
||||
#define KYTY_ENABLE_DEBUG_PRINTF
|
||||
#define KYTY_DBG_INPUT
|
||||
|
||||
namespace Libs::Graphics {
|
||||
@@ -71,39 +56,39 @@ struct EventKeyboard {
|
||||
|
||||
static uint32_t ControllerButtonToPadButton(int button) {
|
||||
switch (button) {
|
||||
case SDL_CONTROLLER_BUTTON_A: return Controller::PAD_BUTTON_CROSS;
|
||||
case SDL_CONTROLLER_BUTTON_B: return Controller::PAD_BUTTON_CIRCLE;
|
||||
case SDL_CONTROLLER_BUTTON_X: return Controller::PAD_BUTTON_SQUARE;
|
||||
case SDL_CONTROLLER_BUTTON_Y: return Controller::PAD_BUTTON_TRIANGLE;
|
||||
case SDL_CONTROLLER_BUTTON_START: return Controller::PAD_BUTTON_OPTIONS;
|
||||
case SDL_CONTROLLER_BUTTON_LEFTSTICK: return Controller::PAD_BUTTON_L3;
|
||||
case SDL_CONTROLLER_BUTTON_RIGHTSTICK: return Controller::PAD_BUTTON_R3;
|
||||
case SDL_CONTROLLER_BUTTON_LEFTSHOULDER: return Controller::PAD_BUTTON_L1;
|
||||
case SDL_CONTROLLER_BUTTON_RIGHTSHOULDER: return Controller::PAD_BUTTON_R1;
|
||||
case SDL_CONTROLLER_BUTTON_DPAD_UP: return Controller::PAD_BUTTON_UP;
|
||||
case SDL_CONTROLLER_BUTTON_DPAD_DOWN: return Controller::PAD_BUTTON_DOWN;
|
||||
case SDL_CONTROLLER_BUTTON_DPAD_LEFT: return Controller::PAD_BUTTON_LEFT;
|
||||
case SDL_CONTROLLER_BUTTON_DPAD_RIGHT: return Controller::PAD_BUTTON_RIGHT;
|
||||
case SDL_CONTROLLER_BUTTON_TOUCHPAD: return Controller::PAD_BUTTON_TOUCH_PAD;
|
||||
case SDL_GAMEPAD_BUTTON_SOUTH: return Controller::PAD_BUTTON_CROSS;
|
||||
case SDL_GAMEPAD_BUTTON_EAST: return Controller::PAD_BUTTON_CIRCLE;
|
||||
case SDL_GAMEPAD_BUTTON_WEST: return Controller::PAD_BUTTON_SQUARE;
|
||||
case SDL_GAMEPAD_BUTTON_NORTH: return Controller::PAD_BUTTON_TRIANGLE;
|
||||
case SDL_GAMEPAD_BUTTON_START: return Controller::PAD_BUTTON_OPTIONS;
|
||||
case SDL_GAMEPAD_BUTTON_LEFT_STICK: return Controller::PAD_BUTTON_L3;
|
||||
case SDL_GAMEPAD_BUTTON_RIGHT_STICK: return Controller::PAD_BUTTON_R3;
|
||||
case SDL_GAMEPAD_BUTTON_LEFT_SHOULDER: return Controller::PAD_BUTTON_L1;
|
||||
case SDL_GAMEPAD_BUTTON_RIGHT_SHOULDER: return Controller::PAD_BUTTON_R1;
|
||||
case SDL_GAMEPAD_BUTTON_DPAD_UP: return Controller::PAD_BUTTON_UP;
|
||||
case SDL_GAMEPAD_BUTTON_DPAD_DOWN: return Controller::PAD_BUTTON_DOWN;
|
||||
case SDL_GAMEPAD_BUTTON_DPAD_LEFT: return Controller::PAD_BUTTON_LEFT;
|
||||
case SDL_GAMEPAD_BUTTON_DPAD_RIGHT: return Controller::PAD_BUTTON_RIGHT;
|
||||
case SDL_GAMEPAD_BUTTON_TOUCHPAD: return Controller::PAD_BUTTON_TOUCH_PAD;
|
||||
default: return 0;
|
||||
}
|
||||
}
|
||||
|
||||
static Controller::Axis ControllerAxisFromSdl(int axis_id) {
|
||||
switch (axis_id) {
|
||||
case SDL_CONTROLLER_AXIS_LEFTX: return Controller::Axis::LeftX;
|
||||
case SDL_CONTROLLER_AXIS_LEFTY: return Controller::Axis::LeftY;
|
||||
case SDL_CONTROLLER_AXIS_RIGHTX: return Controller::Axis::RightX;
|
||||
case SDL_CONTROLLER_AXIS_RIGHTY: return Controller::Axis::RightY;
|
||||
case SDL_CONTROLLER_AXIS_TRIGGERLEFT: return Controller::Axis::TriggerLeft;
|
||||
case SDL_CONTROLLER_AXIS_TRIGGERRIGHT: return Controller::Axis::TriggerRight;
|
||||
case SDL_GAMEPAD_AXIS_LEFTX: return Controller::Axis::LeftX;
|
||||
case SDL_GAMEPAD_AXIS_LEFTY: return Controller::Axis::LeftY;
|
||||
case SDL_GAMEPAD_AXIS_RIGHTX: return Controller::Axis::RightX;
|
||||
case SDL_GAMEPAD_AXIS_RIGHTY: return Controller::Axis::RightY;
|
||||
case SDL_GAMEPAD_AXIS_LEFT_TRIGGER: return Controller::Axis::TriggerLeft;
|
||||
case SDL_GAMEPAD_AXIS_RIGHT_TRIGGER: return Controller::Axis::TriggerRight;
|
||||
default: return Controller::Axis::AxisMax;
|
||||
}
|
||||
}
|
||||
|
||||
static bool ControllerAxisIsTrigger(int axis_id) {
|
||||
return axis_id == SDL_CONTROLLER_AXIS_TRIGGERLEFT ||
|
||||
axis_id == SDL_CONTROLLER_AXIS_TRIGGERRIGHT;
|
||||
return axis_id == SDL_GAMEPAD_AXIS_LEFT_TRIGGER ||
|
||||
axis_id == SDL_GAMEPAD_AXIS_RIGHT_TRIGGER;
|
||||
}
|
||||
|
||||
static int ControllerAxisValueFromSdl(int axis_id, int axis_value) {
|
||||
@@ -164,28 +149,6 @@ struct EventController {
|
||||
double timestamp_seconds;
|
||||
};
|
||||
|
||||
enum class DisplayOrientation {
|
||||
Unknown, /* The display orientation can't be determined */
|
||||
Landscape, /* The display is in landscape mode, with the right side up, relative to portrait
|
||||
mode */
|
||||
LandscapeFlipped, /* The display is in landscape mode, with the left side up, relative to
|
||||
portrait mode */
|
||||
Portrait, /* The display is in portrait mode */
|
||||
PortraitFlipped, /* The display is in portrait mode, upside down */
|
||||
|
||||
DisplayEventOrientation = 0xF0
|
||||
};
|
||||
|
||||
struct EventDisplay {
|
||||
DisplayOrientation orientation;
|
||||
};
|
||||
|
||||
constexpr uint32_t KYTY_SDL_BUTTON_LMASK = SDL_BUTTON_LMASK; // NOLINT(hicpp-signed-bitwise)
|
||||
constexpr uint32_t KYTY_SDL_BUTTON_MMASK = SDL_BUTTON_MMASK; // NOLINT(hicpp-signed-bitwise)
|
||||
constexpr uint32_t KYTY_SDL_BUTTON_RMASK = SDL_BUTTON_RMASK; // NOLINT(hicpp-signed-bitwise)
|
||||
constexpr uint32_t KYTY_SDL_BUTTON_X1MASK = SDL_BUTTON_X1MASK; // NOLINT(hicpp-signed-bitwise)
|
||||
constexpr uint32_t KYTY_SDL_BUTTON_X2MASK = SDL_BUTTON_X2MASK; // NOLINT(hicpp-signed-bitwise)
|
||||
|
||||
namespace {
|
||||
|
||||
std::unique_ptr<WindowContext> g_window;
|
||||
@@ -193,7 +156,6 @@ std::unique_ptr<WindowContext> g_window;
|
||||
} // namespace
|
||||
|
||||
constexpr const char* KYTY_SDL_WINDOW_CAPTION = "Game";
|
||||
constexpr int KYTY_SDL_WINDOWPOS_CENTERED = SDL_WINDOWPOS_CENTERED; /*NOLINT(hicpp-signed-bitwise)*/
|
||||
|
||||
static void SetPause(WindowLoopState& game, bool flag) {
|
||||
LOGF("Pause: %s\n", flag ? "true" : "false");
|
||||
@@ -219,16 +181,13 @@ static void ToggleDesktopFullscreen() {
|
||||
}
|
||||
|
||||
const auto flags = static_cast<uint32_t>(SDL_GetWindowFlags(g_window->window));
|
||||
const bool fullscreen =
|
||||
(flags & static_cast<uint32_t>(SDL_WINDOW_FULLSCREEN_DESKTOP)) != 0u;
|
||||
const auto mode =
|
||||
fullscreen ? 0u : static_cast<uint32_t>(SDL_WINDOW_FULLSCREEN_DESKTOP);
|
||||
if (SDL_SetWindowFullscreen(g_window->window, mode) != 0) {
|
||||
const bool fullscreen = (flags & SDL_WINDOW_FULLSCREEN) != 0u;
|
||||
if (!SDL_SetWindowFullscreen(g_window->window, !fullscreen)) {
|
||||
LOGF("Toggle fullscreen failed: %s\n", SDL_GetError());
|
||||
}
|
||||
}
|
||||
|
||||
static void GameEventKeyboard(WindowLoopState& game, const EventKeyboard& key) {
|
||||
static void GameEventKeyboard(const EventKeyboard& key) {
|
||||
static SDL_Keycode fullscreen_key = SDLK_UNKNOWN;
|
||||
|
||||
#ifdef KYTY_DBG_INPUT
|
||||
@@ -241,7 +200,6 @@ static void GameEventKeyboard(WindowLoopState& game, const EventKeyboard& key) {
|
||||
#if KYTY_PLATFORM == KYTY_PLATFORM_WINDOWS || KYTY_PLATFORM == KYTY_PLATFORM_LINUX
|
||||
if (key.down) {
|
||||
switch (key.key_code) {
|
||||
case SDLK_SPACE: SetPause(game, !game.paused.load(std::memory_order_acquire)); break;
|
||||
case SDLK_F1:
|
||||
if (!key.repeat) {
|
||||
RenderDocRequestCapture();
|
||||
@@ -254,7 +212,7 @@ static void GameEventKeyboard(WindowLoopState& game, const EventKeyboard& key) {
|
||||
break;
|
||||
case SDLK_RETURN:
|
||||
case SDLK_KP_ENTER:
|
||||
if (!key.repeat && (key.mod & KMOD_ALT) != 0) {
|
||||
if (!key.repeat && (key.mod & SDL_KMOD_ALT) != 0) {
|
||||
ToggleDesktopFullscreen();
|
||||
fullscreen_key = key.key_code;
|
||||
}
|
||||
@@ -347,15 +305,15 @@ static void GameEventController([[maybe_unused]] const EventController& f) {
|
||||
#endif
|
||||
|
||||
if (f.added) {
|
||||
auto* pad = SDL_GameControllerOpen(f.id);
|
||||
auto* pad = SDL_OpenGamepad(f.id);
|
||||
EXIT_NOT_IMPLEMENTED(pad == nullptr);
|
||||
int id = SDL_JoystickInstanceID(SDL_GameControllerGetJoystick(pad));
|
||||
int id = SDL_GetJoystickID(SDL_GetGamepadJoystick(pad));
|
||||
Controller::Connect(id);
|
||||
}
|
||||
|
||||
if (f.removed) {
|
||||
Controller::Disconnect(f.id);
|
||||
SDL_GameControllerClose(SDL_GameControllerFromInstanceID(f.id));
|
||||
SDL_CloseGamepad(SDL_GetGamepadFromID(f.id));
|
||||
}
|
||||
|
||||
if (f.down || f.up) {
|
||||
@@ -407,81 +365,73 @@ void WindowContext::Resize(uint32_t new_width, uint32_t new_height) {
|
||||
|
||||
void WindowContext::ProcessWindowEvent(const SDL_WindowEvent& event) {
|
||||
const auto& window_event = event;
|
||||
switch (window_event.event) {
|
||||
case SDL_WINDOWEVENT_SHOWN:
|
||||
switch (window_event.type) {
|
||||
case SDL_EVENT_WINDOW_SHOWN:
|
||||
LOGF("Window %" PRIu32 " shown\n", window_event.windowID);
|
||||
break;
|
||||
|
||||
case SDL_WINDOWEVENT_HIDDEN:
|
||||
case SDL_EVENT_WINDOW_HIDDEN:
|
||||
LOGF("Window %" PRIu32 " hidden\n", window_event.windowID);
|
||||
break;
|
||||
|
||||
case SDL_WINDOWEVENT_EXPOSED:
|
||||
case SDL_EVENT_WINDOW_EXPOSED:
|
||||
LOGF("Window %" PRIu32 " exposed\n", window_event.windowID);
|
||||
break;
|
||||
|
||||
case SDL_WINDOWEVENT_MOVED:
|
||||
case SDL_EVENT_WINDOW_MOVED:
|
||||
LOGF("Window %" PRIu32 " moved to %" PRId32 ",%" PRId32 "\n", window_event.windowID,
|
||||
window_event.data1, window_event.data2);
|
||||
break;
|
||||
|
||||
case SDL_WINDOWEVENT_RESIZED:
|
||||
case SDL_EVENT_WINDOW_RESIZED:
|
||||
LOGF("Window %" PRIu32 " resized to %" PRId32 "x%" PRId32 "\n", window_event.windowID,
|
||||
window_event.data1, window_event.data2);
|
||||
|
||||
LOGF("m: %d\n", static_cast<int>(SDL_ThreadID()));
|
||||
Resize(window_event.data1, window_event.data2);
|
||||
|
||||
break;
|
||||
|
||||
case SDL_WINDOWEVENT_SIZE_CHANGED:
|
||||
case SDL_EVENT_WINDOW_PIXEL_SIZE_CHANGED:
|
||||
LOGF("Window %" PRIu32 " size changed to %" PRId32 "x%" PRId32 "\n",
|
||||
window_event.windowID, window_event.data1, window_event.data2);
|
||||
|
||||
LOGF("m: %d\n", static_cast<int>(SDL_ThreadID()));
|
||||
Resize(window_event.data1, window_event.data2);
|
||||
|
||||
break;
|
||||
|
||||
case SDL_WINDOWEVENT_MINIMIZED:
|
||||
case SDL_EVENT_WINDOW_MINIMIZED:
|
||||
LOGF("Window %" PRIu32 " minimized\n", window_event.windowID);
|
||||
break;
|
||||
case SDL_WINDOWEVENT_MAXIMIZED:
|
||||
case SDL_EVENT_WINDOW_MAXIMIZED:
|
||||
LOGF("Window %" PRIu32 " maximized\n", window_event.windowID);
|
||||
break;
|
||||
case SDL_WINDOWEVENT_RESTORED:
|
||||
case SDL_EVENT_WINDOW_RESTORED:
|
||||
LOGF("Window %" PRIu32 " restored\n", window_event.windowID);
|
||||
break;
|
||||
case SDL_WINDOWEVENT_ENTER:
|
||||
case SDL_EVENT_WINDOW_MOUSE_ENTER:
|
||||
LOGF("Mouse entered window %" PRIu32 "\n", window_event.windowID);
|
||||
break;
|
||||
case SDL_WINDOWEVENT_LEAVE:
|
||||
case SDL_EVENT_WINDOW_MOUSE_LEAVE:
|
||||
LOGF("Mouse left window %" PRIu32 "\n", window_event.windowID);
|
||||
break;
|
||||
case SDL_WINDOWEVENT_FOCUS_GAINED:
|
||||
case SDL_EVENT_WINDOW_FOCUS_GAINED:
|
||||
LOGF("Window %" PRIu32 " gained keyboard focus\n", window_event.windowID);
|
||||
break;
|
||||
case SDL_WINDOWEVENT_FOCUS_LOST:
|
||||
case SDL_EVENT_WINDOW_FOCUS_LOST:
|
||||
LOGF("Window %" PRIu32 " lost keyboard focus\n", window_event.windowID);
|
||||
break;
|
||||
case SDL_WINDOWEVENT_CLOSE:
|
||||
case SDL_EVENT_WINDOW_CLOSE_REQUESTED:
|
||||
LOGF("Window %" PRIu32 " closed\n", window_event.windowID);
|
||||
break;
|
||||
default:
|
||||
LOGF("Window %" PRIu32 " got unknown event %" PRIu8 "\n", window_event.windowID,
|
||||
window_event.event);
|
||||
LOGF("Window %" PRIu32 " got unknown event %" PRIu32 "\n", window_event.windowID,
|
||||
static_cast<uint32_t>(window_event.type));
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
void WindowContext::ProcessDisplayEvent(const SDL_DisplayEvent& display) {
|
||||
bool sdl = false;
|
||||
|
||||
switch (display.event) {
|
||||
case SDL_DISPLAYEVENT_ORIENTATION: sdl = true; [[fallthrough]];
|
||||
case static_cast<Uint8>(DisplayOrientation::DisplayEventOrientation): {
|
||||
LOGF("Display %" PRIu32 "[%s] changed orientation to %d - ", display.display,
|
||||
sdl ? "SDL" : "Kyty", static_cast<int>(display.data1));
|
||||
switch (display.type) {
|
||||
case SDL_EVENT_DISPLAY_ORIENTATION: {
|
||||
LOGF("Display %" PRIu32 " changed orientation to %d - ", display.displayID,
|
||||
static_cast<int>(display.data1));
|
||||
|
||||
switch (display.data1) {
|
||||
case SDL_ORIENTATION_UNKNOWN: LOGF("UNKNOWN\n"); break;
|
||||
@@ -495,8 +445,8 @@ void WindowContext::ProcessDisplayEvent(const SDL_DisplayEvent& display) {
|
||||
break;
|
||||
}
|
||||
default:
|
||||
LOGF("Display %" PRIu32 " got unknown event 0x%" PRIx8 "\n", display.display,
|
||||
display.event);
|
||||
LOGF("Display %" PRIu32 " got unknown event 0x%" PRIx32 "\n", display.displayID,
|
||||
static_cast<uint32_t>(display.type));
|
||||
break;
|
||||
}
|
||||
}
|
||||
@@ -504,10 +454,9 @@ void WindowContext::ProcessDisplayEvent(const SDL_DisplayEvent& display) {
|
||||
void WindowContext::ProcessEvent(double time_s) {
|
||||
auto& game = loop;
|
||||
auto* event = &game.event;
|
||||
EXIT_IF(SDL_GetEventState(SDL_DISPLAYEVENT) != SDL_ENABLE);
|
||||
if ((event->type == SDL_KEYDOWN || event->type == SDL_KEYUP) &&
|
||||
event->key.keysym.sym == SDLK_F7) {
|
||||
if (event->type == SDL_KEYDOWN && event->key.repeat == 0) {
|
||||
if ((event->type == SDL_EVENT_KEY_DOWN || event->type == SDL_EVENT_KEY_UP) &&
|
||||
event->key.key == SDLK_F7) {
|
||||
if (event->type == SDL_EVENT_KEY_DOWN && event->key.repeat == 0) {
|
||||
HostInputToggleMouseToJoystick();
|
||||
}
|
||||
return;
|
||||
@@ -515,59 +464,63 @@ void WindowContext::ProcessEvent(double time_s) {
|
||||
if (ProcessSystemOverlayInput(*event)) {
|
||||
return;
|
||||
}
|
||||
if (event->type >= SDL_EVENT_WINDOW_FIRST && event->type <= SDL_EVENT_WINDOW_LAST) {
|
||||
ProcessWindowEvent(event->window);
|
||||
return;
|
||||
}
|
||||
if (event->type >= SDL_EVENT_DISPLAY_FIRST && event->type <= SDL_EVENT_DISPLAY_LAST) {
|
||||
ProcessDisplayEvent(event->display);
|
||||
return;
|
||||
}
|
||||
|
||||
switch (event->type) {
|
||||
case SDL_QUIT: GameEventQuit(game); break;
|
||||
case SDL_EVENT_QUIT: GameEventQuit(game); break;
|
||||
|
||||
case SDL_APP_TERMINATING: GameEventTerminate(game); break;
|
||||
case SDL_EVENT_TERMINATING: GameEventTerminate(game); break;
|
||||
|
||||
case SDL_APP_LOWMEMORY: GameEventLowMemory(); break;
|
||||
case SDL_EVENT_LOW_MEMORY: GameEventLowMemory(); break;
|
||||
|
||||
case SDL_APP_WILLENTERBACKGROUND: GameEventWillEnterBackground(game); break;
|
||||
case SDL_EVENT_WILL_ENTER_BACKGROUND: GameEventWillEnterBackground(game); break;
|
||||
|
||||
case SDL_APP_DIDENTERBACKGROUND: GameEventDidEnterBackground(); break;
|
||||
case SDL_EVENT_DID_ENTER_BACKGROUND: GameEventDidEnterBackground(); break;
|
||||
|
||||
case SDL_APP_WILLENTERFOREGROUND: GameEventWillEnterForeground(); break;
|
||||
case SDL_EVENT_WILL_ENTER_FOREGROUND: GameEventWillEnterForeground(); break;
|
||||
|
||||
case SDL_APP_DIDENTERFOREGROUND: GameEventDidEnterForeground(game); break;
|
||||
case SDL_EVENT_DID_ENTER_FOREGROUND: GameEventDidEnterForeground(game); break;
|
||||
|
||||
case SDL_KEYDOWN:
|
||||
case SDL_KEYUP: {
|
||||
case SDL_EVENT_KEY_DOWN:
|
||||
case SDL_EVENT_KEY_UP: {
|
||||
EventKeyboard key {};
|
||||
|
||||
key.down = (event->type == SDL_KEYDOWN);
|
||||
key.up = (event->type == SDL_KEYUP);
|
||||
key.pressed = (event->key.state == SDL_PRESSED);
|
||||
key.released = (event->key.state == SDL_RELEASED);
|
||||
key.down = (event->type == SDL_EVENT_KEY_DOWN);
|
||||
key.up = (event->type == SDL_EVENT_KEY_UP);
|
||||
key.pressed = event->key.down;
|
||||
key.released = !event->key.down;
|
||||
key.repeat = (event->key.repeat != 0u);
|
||||
key.scan_code = event->key.keysym.scancode;
|
||||
key.key_code = event->key.keysym.sym;
|
||||
key.mod = event->key.keysym.mod;
|
||||
key.scan_code = event->key.scancode;
|
||||
key.key_code = event->key.key;
|
||||
key.mod = event->key.mod;
|
||||
key.timestamp_seconds = time_s;
|
||||
|
||||
GameEventKeyboard(game, key);
|
||||
GameEventKeyboard(key);
|
||||
|
||||
break;
|
||||
}
|
||||
|
||||
case SDL_WINDOWEVENT: ProcessWindowEvent(event->window); break;
|
||||
|
||||
case SDL_DISPLAYEVENT: ProcessDisplayEvent(event->display); break;
|
||||
|
||||
case SDL_MOUSEBUTTONDOWN:
|
||||
case SDL_MOUSEBUTTONUP: {
|
||||
case SDL_EVENT_MOUSE_BUTTON_DOWN:
|
||||
case SDL_EVENT_MOUSE_BUTTON_UP: {
|
||||
EventMouse mb {};
|
||||
|
||||
mb.down = (event->button.type == SDL_MOUSEBUTTONDOWN);
|
||||
mb.up = (event->button.type == SDL_MOUSEBUTTONUP);
|
||||
mb.down = (event->button.type == SDL_EVENT_MOUSE_BUTTON_DOWN);
|
||||
mb.up = (event->button.type == SDL_EVENT_MOUSE_BUTTON_UP);
|
||||
mb.left = (event->button.button == SDL_BUTTON_LEFT);
|
||||
mb.middle = (event->button.button == SDL_BUTTON_MIDDLE);
|
||||
mb.right = (event->button.button == SDL_BUTTON_RIGHT);
|
||||
mb.x1 = (event->button.button == SDL_BUTTON_X1);
|
||||
mb.x2 = (event->button.button == SDL_BUTTON_X2);
|
||||
mb.touch = (event->button.which == SDL_TOUCH_MOUSEID);
|
||||
mb.pressed = (event->button.state == SDL_PRESSED);
|
||||
mb.released = (event->button.state == SDL_RELEASED);
|
||||
mb.pressed = event->button.down;
|
||||
mb.released = !event->button.down;
|
||||
mb.num_of_clicks = event->button.clicks;
|
||||
mb.wheel = false;
|
||||
mb.x = event->button.x;
|
||||
@@ -582,7 +535,7 @@ void WindowContext::ProcessEvent(double time_s) {
|
||||
break;
|
||||
}
|
||||
|
||||
case SDL_MOUSEWHEEL: {
|
||||
case SDL_EVENT_MOUSE_WHEEL: {
|
||||
EventMouse mb {};
|
||||
|
||||
mb.down = false;
|
||||
@@ -609,16 +562,16 @@ void WindowContext::ProcessEvent(double time_s) {
|
||||
break;
|
||||
}
|
||||
|
||||
case SDL_MOUSEMOTION: {
|
||||
case SDL_EVENT_MOUSE_MOTION: {
|
||||
EventMouse mb {};
|
||||
|
||||
mb.down = false;
|
||||
mb.up = false;
|
||||
mb.left = ((event->motion.state & KYTY_SDL_BUTTON_LMASK) != 0u);
|
||||
mb.middle = ((event->motion.state & KYTY_SDL_BUTTON_MMASK) != 0u);
|
||||
mb.right = ((event->motion.state & KYTY_SDL_BUTTON_RMASK) != 0u);
|
||||
mb.x1 = ((event->motion.state & KYTY_SDL_BUTTON_X1MASK) != 0u);
|
||||
mb.x2 = ((event->motion.state & KYTY_SDL_BUTTON_X2MASK) != 0u);
|
||||
mb.left = ((event->motion.state & SDL_BUTTON_LMASK) != 0u);
|
||||
mb.middle = ((event->motion.state & SDL_BUTTON_MMASK) != 0u);
|
||||
mb.right = ((event->motion.state & SDL_BUTTON_RMASK) != 0u);
|
||||
mb.x1 = ((event->motion.state & SDL_BUTTON_X1MASK) != 0u);
|
||||
mb.x2 = ((event->motion.state & SDL_BUTTON_X2MASK) != 0u);
|
||||
mb.touch = (event->motion.which == SDL_TOUCH_MOUSEID);
|
||||
mb.pressed = false;
|
||||
mb.released = false;
|
||||
@@ -636,16 +589,16 @@ void WindowContext::ProcessEvent(double time_s) {
|
||||
break;
|
||||
}
|
||||
|
||||
case SDL_FINGERMOTION:
|
||||
case SDL_FINGERDOWN:
|
||||
case SDL_FINGERUP: {
|
||||
case SDL_EVENT_FINGER_MOTION:
|
||||
case SDL_EVENT_FINGER_DOWN:
|
||||
case SDL_EVENT_FINGER_UP: {
|
||||
EventFinger f {};
|
||||
|
||||
f.down = (event->tfinger.type == SDL_FINGERDOWN);
|
||||
f.up = (event->tfinger.type == SDL_FINGERUP);
|
||||
f.motion = (event->tfinger.type == SDL_FINGERMOTION);
|
||||
f.finger_id = static_cast<int>(event->tfinger.fingerId);
|
||||
f.touch_id = static_cast<int>(event->tfinger.touchId);
|
||||
f.down = (event->tfinger.type == SDL_EVENT_FINGER_DOWN);
|
||||
f.up = (event->tfinger.type == SDL_EVENT_FINGER_UP);
|
||||
f.motion = (event->tfinger.type == SDL_EVENT_FINGER_MOTION);
|
||||
f.finger_id = static_cast<int>(event->tfinger.fingerID);
|
||||
f.touch_id = static_cast<int>(event->tfinger.touchID);
|
||||
f.x = event->tfinger.x;
|
||||
f.y = event->tfinger.y;
|
||||
f.dx = event->tfinger.dx;
|
||||
@@ -658,13 +611,13 @@ void WindowContext::ProcessEvent(double time_s) {
|
||||
break;
|
||||
}
|
||||
|
||||
case SDL_CONTROLLERAXISMOTION: {
|
||||
case SDL_EVENT_GAMEPAD_AXIS_MOTION: {
|
||||
EventController c {};
|
||||
|
||||
c.id = event->caxis.which;
|
||||
c.button = SDL_CONTROLLER_BUTTON_INVALID;
|
||||
c.axis_id = event->caxis.axis;
|
||||
c.axis_value = event->caxis.value;
|
||||
c.id = event->gaxis.which;
|
||||
c.button = SDL_GAMEPAD_BUTTON_INVALID;
|
||||
c.axis_id = event->gaxis.axis;
|
||||
c.axis_value = event->gaxis.value;
|
||||
c.down = false;
|
||||
c.up = false;
|
||||
c.added = false;
|
||||
@@ -680,22 +633,22 @@ void WindowContext::ProcessEvent(double time_s) {
|
||||
break;
|
||||
}
|
||||
|
||||
case SDL_CONTROLLERBUTTONDOWN:
|
||||
case SDL_CONTROLLERBUTTONUP: {
|
||||
case SDL_EVENT_GAMEPAD_BUTTON_DOWN:
|
||||
case SDL_EVENT_GAMEPAD_BUTTON_UP: {
|
||||
EventController c {};
|
||||
|
||||
c.id = event->cbutton.which;
|
||||
c.button = event->cbutton.button;
|
||||
c.axis_id = SDL_CONTROLLER_AXIS_INVALID;
|
||||
c.id = event->gbutton.which;
|
||||
c.button = event->gbutton.button;
|
||||
c.axis_id = SDL_GAMEPAD_AXIS_INVALID;
|
||||
c.axis_value = 0;
|
||||
c.down = (event->cbutton.type == SDL_CONTROLLERBUTTONDOWN);
|
||||
c.up = (event->cbutton.type == SDL_CONTROLLERBUTTONUP);
|
||||
c.down = event->gbutton.down;
|
||||
c.up = !event->gbutton.down;
|
||||
c.added = false;
|
||||
c.removed = false;
|
||||
c.remapped = false;
|
||||
c.axis = false;
|
||||
c.pressed = (event->cbutton.state == SDL_PRESSED);
|
||||
c.released = (event->cbutton.state == SDL_RELEASED);
|
||||
c.pressed = event->gbutton.down;
|
||||
c.released = !event->gbutton.down;
|
||||
c.timestamp_seconds = time_s;
|
||||
|
||||
GameEventController(c);
|
||||
@@ -703,30 +656,44 @@ void WindowContext::ProcessEvent(double time_s) {
|
||||
break;
|
||||
}
|
||||
|
||||
case SDL_CONTROLLERTOUCHPADDOWN:
|
||||
case SDL_CONTROLLERTOUCHPADMOTION:
|
||||
case SDL_CONTROLLERTOUCHPADUP:
|
||||
if (event->ctouchpad.touchpad == 0) {
|
||||
Controller::SetTouchPad(event->ctouchpad.which, event->ctouchpad.finger,
|
||||
event->ctouchpad.type != SDL_CONTROLLERTOUCHPADUP,
|
||||
event->ctouchpad.x, event->ctouchpad.y);
|
||||
case SDL_EVENT_GAMEPAD_TOUCHPAD_DOWN:
|
||||
case SDL_EVENT_GAMEPAD_TOUCHPAD_MOTION:
|
||||
case SDL_EVENT_GAMEPAD_TOUCHPAD_UP:
|
||||
if (event->gtouchpad.touchpad == 0) {
|
||||
Controller::SetTouchPad(event->gtouchpad.which, event->gtouchpad.finger,
|
||||
event->gtouchpad.type != SDL_EVENT_GAMEPAD_TOUCHPAD_UP,
|
||||
event->gtouchpad.x, event->gtouchpad.y);
|
||||
}
|
||||
break;
|
||||
|
||||
case SDL_CONTROLLERDEVICEADDED:
|
||||
case SDL_CONTROLLERDEVICEREMOVED:
|
||||
case SDL_CONTROLLERDEVICEREMAPPED: {
|
||||
case SDL_EVENT_GAMEPAD_SENSOR_UPDATE: {
|
||||
const auto& sensor = event->gsensor;
|
||||
if (sensor.sensor == SDL_SENSOR_ACCEL || sensor.sensor == SDL_SENSOR_GYRO) {
|
||||
Controller::SetSensor(sensor.which,
|
||||
sensor.sensor == SDL_SENSOR_ACCEL ? Controller::Sensor::Accel
|
||||
: Controller::Sensor::Gyro,
|
||||
sensor.data,
|
||||
sensor.sensor_timestamp != 0
|
||||
? sensor.sensor_timestamp / 1000
|
||||
: sensor.timestamp / 1000);
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
case SDL_EVENT_GAMEPAD_ADDED:
|
||||
case SDL_EVENT_GAMEPAD_REMOVED:
|
||||
case SDL_EVENT_GAMEPAD_REMAPPED: {
|
||||
EventController c {};
|
||||
|
||||
c.id = event->cdevice.which;
|
||||
c.button = SDL_CONTROLLER_BUTTON_INVALID;
|
||||
c.axis_id = SDL_CONTROLLER_AXIS_INVALID;
|
||||
c.id = event->gdevice.which;
|
||||
c.button = SDL_GAMEPAD_BUTTON_INVALID;
|
||||
c.axis_id = SDL_GAMEPAD_AXIS_INVALID;
|
||||
c.axis_value = 0;
|
||||
c.down = false;
|
||||
c.up = false;
|
||||
c.added = (event->cdevice.type == SDL_CONTROLLERDEVICEADDED);
|
||||
c.removed = (event->cdevice.type == SDL_CONTROLLERDEVICEREMOVED);
|
||||
c.remapped = (event->cdevice.type == SDL_CONTROLLERDEVICEREMAPPED);
|
||||
c.added = (event->gdevice.type == SDL_EVENT_GAMEPAD_ADDED);
|
||||
c.removed = (event->gdevice.type == SDL_EVENT_GAMEPAD_REMOVED);
|
||||
c.remapped = (event->gdevice.type == SDL_EVENT_GAMEPAD_REMAPPED);
|
||||
c.axis = false;
|
||||
c.pressed = false;
|
||||
c.released = false;
|
||||
@@ -739,47 +706,6 @@ void WindowContext::ProcessEvent(double time_s) {
|
||||
}
|
||||
}
|
||||
|
||||
void WindowContext::RunOnMainThread(std::function<void()> task) {
|
||||
if (Common::Thread::IsMainThread()) {
|
||||
task();
|
||||
return;
|
||||
}
|
||||
|
||||
uint64_t ticket = 0;
|
||||
{
|
||||
Common::LockGuard lock(main_task_mutex);
|
||||
main_tasks.push_back(std::move(task));
|
||||
ticket = ++main_tasks_queued;
|
||||
}
|
||||
|
||||
// Wake the main loop in case it is blocked in SDL_WaitEvent.
|
||||
SDL_Event event {};
|
||||
event.type = SDL_USEREVENT;
|
||||
SDL_PushEvent(&event);
|
||||
|
||||
Common::LockGuard lock(main_task_mutex);
|
||||
while (main_tasks_run < ticket) {
|
||||
main_task_done.Wait(&main_task_mutex);
|
||||
}
|
||||
}
|
||||
|
||||
void WindowContext::DrainMainThreadTasks() {
|
||||
std::vector<std::function<void()>> tasks;
|
||||
{
|
||||
Common::LockGuard lock(main_task_mutex);
|
||||
tasks.swap(main_tasks);
|
||||
}
|
||||
if (tasks.empty()) {
|
||||
return;
|
||||
}
|
||||
for (auto& task: tasks) {
|
||||
task();
|
||||
}
|
||||
Common::LockGuard lock(main_task_mutex);
|
||||
main_tasks_run += tasks.size();
|
||||
main_task_done.SignalAll();
|
||||
}
|
||||
|
||||
void WindowContext::Run() {
|
||||
Common::Timer timer;
|
||||
timer.Start();
|
||||
@@ -789,7 +715,6 @@ void WindowContext::Run() {
|
||||
loop.paused.store(false, std::memory_order_release);
|
||||
|
||||
while (!loop.need_exit) {
|
||||
DrainMainThreadTasks();
|
||||
if (loop.paused.load(std::memory_order_acquire)) {
|
||||
if (!timer.IsPaused()) {
|
||||
timer.Pause();
|
||||
@@ -813,17 +738,15 @@ static void WindowCreate(WindowContext& context) {
|
||||
int width = static_cast<int>(context.graphic_ctx.screen_width);
|
||||
int height = static_cast<int>(context.graphic_ctx.screen_height);
|
||||
|
||||
SDL_SetHint(SDL_HINT_JOYSTICK_HIDAPI_PS4_RUMBLE, "1");
|
||||
SDL_SetHint(SDL_HINT_JOYSTICK_HIDAPI_PS5_RUMBLE, "1");
|
||||
#if KYTY_PLATFORM == KYTY_PLATFORM_WINDOWS
|
||||
SDL_SetHint(SDL_HINT_WINDOWS_DPI_SCALING, "0");
|
||||
#if KYTY_PLATFORM == KYTY_PLATFORM_LINUX
|
||||
// RenderDoc can hide Wayland Vulkan surface support; prefer X11 for captures.
|
||||
if (Config::RenderDocEnabled()) {
|
||||
SDL_SetHint(SDL_HINT_VIDEO_DRIVER, "x11,wayland");
|
||||
}
|
||||
#endif
|
||||
|
||||
if (SDL_InitSubSystem(SDL_INIT_VIDEO | SDL_INIT_GAMECONTROLLER) < 0) {
|
||||
if (!SDL_InitSubSystem(SDL_INIT_VIDEO | SDL_INIT_GAMEPAD)) {
|
||||
EXIT("%s\n", SDL_GetError());
|
||||
}
|
||||
HostInputInit();
|
||||
InitializeSystemOverlayInput();
|
||||
|
||||
LOGF("WindowCreate(): width = %d, height = %d\n", width, height);
|
||||
|
||||
@@ -833,15 +756,14 @@ static void WindowCreate(WindowContext& context) {
|
||||
// MoltenVK loader before calling SDL_CreateWindow. Keep an explicit user
|
||||
// override, and fall back to SDL's normal loader search when no bundle is present.
|
||||
if (std::getenv("SDL_VULKAN_LIBRARY") == nullptr) {
|
||||
if (char* base_path = SDL_GetBasePath(); base_path != nullptr) {
|
||||
if (const char* base_path = SDL_GetBasePath(); base_path != nullptr) {
|
||||
const std::string base_path_str = base_path;
|
||||
SDL_free(base_path);
|
||||
std::string moltenvk_path = base_path_str + "libMoltenVK.dylib";
|
||||
if (!Common::File::IsFileExisting(moltenvk_path)) {
|
||||
moltenvk_path = base_path_str + "../Frameworks/libMoltenVK.dylib";
|
||||
}
|
||||
if (Common::File::IsFileExisting(moltenvk_path) &&
|
||||
SDL_setenv("SDL_VULKAN_LIBRARY", moltenvk_path.c_str(), 0) == 0) {
|
||||
SDL_setenv_unsafe("SDL_VULKAN_LIBRARY", moltenvk_path.c_str(), 0) == 0) {
|
||||
LOGF("Vulkan loader: %s\n", moltenvk_path.c_str());
|
||||
}
|
||||
}
|
||||
@@ -854,21 +776,22 @@ static void WindowCreate(WindowContext& context) {
|
||||
window_flags |= static_cast<uint32_t>(SDL_WINDOW_BORDERLESS);
|
||||
}
|
||||
#endif
|
||||
context.window = SDL_CreateWindow(KYTY_SDL_WINDOW_CAPTION, KYTY_SDL_WINDOWPOS_CENTERED,
|
||||
KYTY_SDL_WINDOWPOS_CENTERED, width, height, window_flags);
|
||||
context.window = SDL_CreateWindow(KYTY_SDL_WINDOW_CAPTION, width, height, window_flags);
|
||||
|
||||
if (context.window == nullptr) {
|
||||
EXIT("%s\n", SDL_GetError());
|
||||
}
|
||||
|
||||
SDL_SetWindowResizable(context.window, SDL_FALSE);
|
||||
SDL_SetWindowPosition(context.window, SDL_WINDOWPOS_CENTERED, SDL_WINDOWPOS_CENTERED);
|
||||
HostInputInit(context.window);
|
||||
InitializeSystemOverlayInput(context.window);
|
||||
context.UpdateIcon();
|
||||
}
|
||||
|
||||
uint32_t WindowContext::InitialWindowFlags(bool fullscreen) noexcept {
|
||||
auto flags = static_cast<uint32_t>(SDL_WINDOW_VULKAN);
|
||||
auto flags = static_cast<uint32_t>(SDL_WINDOW_VULKAN | SDL_WINDOW_RESIZABLE);
|
||||
if (fullscreen) {
|
||||
flags |= static_cast<uint32_t>(SDL_WINDOW_FULLSCREEN_DESKTOP);
|
||||
flags |= static_cast<uint32_t>(SDL_WINDOW_FULLSCREEN);
|
||||
}
|
||||
return flags;
|
||||
}
|
||||
@@ -899,6 +822,7 @@ void WindowRun() {
|
||||
|
||||
void WindowShutdown() {
|
||||
if (g_window != nullptr) {
|
||||
HostInputShutdown();
|
||||
Controller::EmergencyShutdown();
|
||||
g_window.reset();
|
||||
}
|
||||
@@ -934,15 +858,15 @@ struct WindowIcon {
|
||||
void* pixels = nullptr;
|
||||
|
||||
~WindowIcon() {
|
||||
SDL_FreeSurface(surface);
|
||||
SDL_DestroySurface(surface);
|
||||
stbi_image_free(pixels);
|
||||
}
|
||||
};
|
||||
|
||||
static void WindowLoadPngIcon(const std::string& path, WindowIcon* icon) {
|
||||
static void WindowLoadPngIcon(const std::filesystem::path& path, WindowIcon* icon) {
|
||||
Common::File f;
|
||||
if (!f.Open(path, Common::File::Mode::Read)) {
|
||||
EXIT("Can't open icon file %s\n", path.c_str());
|
||||
EXIT("Can't open icon file %s\n", Common::PathToString(path).c_str());
|
||||
}
|
||||
|
||||
int width = 0;
|
||||
@@ -958,8 +882,8 @@ static void WindowLoadPngIcon(const std::string& path, WindowIcon* icon) {
|
||||
|
||||
EXIT_IF(icon->pixels == nullptr);
|
||||
|
||||
icon->surface = SDL_CreateRGBSurfaceWithFormatFrom(icon->pixels, width, height, 32, width * 4,
|
||||
SDL_PIXELFORMAT_RGBA32);
|
||||
icon->surface = SDL_CreateSurfaceFrom(width, height, SDL_PIXELFORMAT_RGBA32, icon->pixels,
|
||||
width * 4);
|
||||
EXIT_NOT_IMPLEMENTED(icon->surface == nullptr);
|
||||
}
|
||||
|
||||
@@ -968,7 +892,7 @@ void WindowContext::UpdateIcon() {
|
||||
static bool icon_loaded = false;
|
||||
|
||||
if (!icon_loaded) {
|
||||
std::string icon_path;
|
||||
std::filesystem::path icon_path;
|
||||
if (Loader::SystemContentGetIconPath(&icon_path)) {
|
||||
WindowLoadPngIcon(icon_path, &icon);
|
||||
}
|
||||
@@ -1021,7 +945,16 @@ void WindowContext::UpdateTitle() {
|
||||
(has_title_id ? ", " : ""), (has_app_ver ? app_ver : ""), (has_app_ver ? " " : ""),
|
||||
device_name, processor_name, frame_num, current_fps);
|
||||
|
||||
RunOnMainThread([this, text = std::move(text)] { SDL_SetWindowTitle(window, text.c_str()); });
|
||||
struct TitleUpdate {
|
||||
SDL_Window* window;
|
||||
std::string* text;
|
||||
} update {window, &text};
|
||||
EXIT_IF(!SDL_RunOnMainThread(
|
||||
[](void* data) {
|
||||
auto& title = *static_cast<TitleUpdate*>(data);
|
||||
SDL_SetWindowTitle(title.window, title.text->c_str());
|
||||
},
|
||||
&update, true));
|
||||
}
|
||||
|
||||
} // namespace Libs::Graphics
|
||||
@@ -1,15 +1,14 @@
|
||||
#ifndef EMULATOR_SRC_GRAPHICS_PRESENTATION_WINDOW_WINDOWINTERNAL_H_
|
||||
#define EMULATOR_SRC_GRAPHICS_PRESENTATION_WINDOW_WINDOWINTERNAL_H_
|
||||
|
||||
#include "SDL_events.h"
|
||||
#include "SDL_video.h"
|
||||
#include <SDL3/SDL.h>
|
||||
|
||||
#include "common/threads.h"
|
||||
#include "graphics/host_gpu/graphicContext.h"
|
||||
#include "graphics/host_gpu/vulkanCommon.h"
|
||||
|
||||
#include <atomic>
|
||||
#include <cstdint>
|
||||
#include <functional>
|
||||
#include <memory>
|
||||
#include <vector>
|
||||
|
||||
@@ -48,9 +47,6 @@ struct WindowContext {
|
||||
void ProcessDisplayEvent(const SDL_DisplayEvent& event);
|
||||
void ProcessEvent(double time_seconds);
|
||||
void Run();
|
||||
// SDL window operations must complete on the main thread.
|
||||
void RunOnMainThread(std::function<void()> task);
|
||||
void DrainMainThreadTasks();
|
||||
|
||||
GraphicContext graphic_ctx;
|
||||
SDL_Window* window = nullptr;
|
||||
@@ -61,12 +57,6 @@ struct WindowContext {
|
||||
WindowLoopState loop;
|
||||
|
||||
Common::Mutex mutex;
|
||||
|
||||
Common::Mutex main_task_mutex;
|
||||
Common::CondVar main_task_done;
|
||||
std::vector<std::function<void()>> main_tasks; // guarded by main_task_mutex
|
||||
uint64_t main_tasks_queued = 0; // guarded by main_task_mutex
|
||||
uint64_t main_tasks_run = 0; // guarded by main_task_mutex
|
||||
};
|
||||
|
||||
} // namespace Libs::Graphics
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
#include "graphics/shader/recompiler/ShaderRecompiler.h"
|
||||
#include "graphics/shader/recompiler/Tessellation.h"
|
||||
|
||||
#include "common/assert.h"
|
||||
#include "common/logging/log.h"
|
||||
@@ -47,6 +48,9 @@ const char* StageName(ShaderType stage) {
|
||||
switch (stage) {
|
||||
case ShaderType::Compute: return "CS";
|
||||
case ShaderType::Vertex: return "VS";
|
||||
case ShaderType::Local: return "LS";
|
||||
case ShaderType::TessellationControl: return "HS";
|
||||
case ShaderType::TessellationEvaluation: return "TES";
|
||||
case ShaderType::Mesh: return "MS";
|
||||
case ShaderType::Pixel: return "PS";
|
||||
default: return "unknown";
|
||||
@@ -83,7 +87,6 @@ struct EmbeddedFetchSgprInfo {
|
||||
EmbeddedFetchValueType type = EmbeddedFetchValueType::Unknown;
|
||||
int attrib_id = 0;
|
||||
uint32_t value = 0;
|
||||
std::vector<uint32_t> prolog_loads;
|
||||
};
|
||||
|
||||
using EmbeddedFetchVectorLanes = std::map<uint64_t, EmbeddedFetchSgprInfo>;
|
||||
@@ -102,8 +105,6 @@ void ClearEmbeddedFetchVectorLanes(EmbeddedFetchVectorLanes* lanes, uint32_t reg
|
||||
lanes->erase(first, last);
|
||||
}
|
||||
|
||||
using EmbeddedFetchData = Frontend::EmbeddedFetchPlan;
|
||||
|
||||
bool IsDecodedSgpr(const Decoder::Operand& op) {
|
||||
return op.kind == Decoder::OperandKind::Sgpr || op.kind == Decoder::OperandKind::VccLo ||
|
||||
op.kind == Decoder::OperandKind::VccHi;
|
||||
@@ -207,11 +208,12 @@ int BufferTableAttribFromOffset(uint32_t raw_offset, int dword) {
|
||||
return static_cast<int>((raw_offset + static_cast<uint32_t>(dword) * 4u) / 16u);
|
||||
}
|
||||
|
||||
EmbeddedFetchData DetectEmbeddedVertexFetch(const Decoder::Program& decoded,
|
||||
const ShaderVertexInputInfo* input_info,
|
||||
uint32_t user_data_base, uint32_t user_data_count,
|
||||
uint32_t wave_size) {
|
||||
EmbeddedFetchData data;
|
||||
Frontend::EmbeddedFetchPlan DetectEmbeddedVertexFetch(
|
||||
const Decoder::Program& decoded, const ShaderVertexInputInfo* input_info,
|
||||
uint32_t user_data_base, uint32_t user_data_count, uint32_t wave_size) {
|
||||
const uint32_t vertex_index_reg = input_info->logical_stage == ShaderType::Local ? 2u : 5u;
|
||||
const uint32_t instance_index_reg = input_info->logical_stage == ShaderType::Local ? 5u : 8u;
|
||||
Frontend::EmbeddedFetchPlan data;
|
||||
data.loads.reserve(input_info->resources_num);
|
||||
int32_t vertex_offset_candidate = -1;
|
||||
int32_t instance_offset_candidate = -1;
|
||||
@@ -251,16 +253,17 @@ EmbeddedFetchData DetectEmbeddedVertexFetch(const Decoder::Program& decoded
|
||||
// corresponding direct-draw offsets before fetching.
|
||||
const bool vertex_index_accumulator =
|
||||
IsDecodedVgpr(inst.dst) &&
|
||||
(inst.dst.reg == 0 || (user_data_base == 8 && inst.dst.reg == 5));
|
||||
(inst.dst.reg == 0 || (user_data_base == 8 && inst.dst.reg == vertex_index_reg));
|
||||
const bool instance_index_accumulator =
|
||||
IsDecodedVgpr(inst.dst) && (inst.dst.reg == (user_data_base == 8 ? 8u : 3u));
|
||||
IsDecodedVgpr(inst.dst) &&
|
||||
(inst.dst.reg == (user_data_base == 8 ? instance_index_reg : 3u));
|
||||
uint32_t sad_zero = 0;
|
||||
const bool index_offset_add =
|
||||
(vertex_index_accumulator || instance_index_accumulator) &&
|
||||
IsDecodedSgpr(inst.src0) &&
|
||||
(vertex_index_accumulator || instance_index_accumulator) && IsDecodedSgpr(inst.src0) &&
|
||||
((inst.opcode == Decoder::Opcode::V_ADD_I32 && IsDecodedVgpr(inst.src1) &&
|
||||
inst.src1.reg == inst.dst.reg) ||
|
||||
(user_data_base == 8 && (inst.dst.reg == 5 || inst.dst.reg == 8) &&
|
||||
(user_data_base == 8 &&
|
||||
(inst.dst.reg == vertex_index_reg || inst.dst.reg == instance_index_reg) &&
|
||||
inst.opcode == Decoder::Opcode::V_SAD_U32 && IsDecodedVgpr(inst.src2) &&
|
||||
inst.src2.reg == inst.dst.reg &&
|
||||
TryDecodedOperandConstant(sgprs, inst.src1, sad_zero) && sad_zero == 0));
|
||||
@@ -319,7 +322,6 @@ EmbeddedFetchData DetectEmbeddedVertexFetch(const Decoder::Program& decoded
|
||||
auto& dst = sgprs[DecodedSgprReg(inst.dst)];
|
||||
dst.type = EmbeddedFetchValueType::Constant;
|
||||
dst.value = value;
|
||||
dst.prolog_loads.clear();
|
||||
} else {
|
||||
ClearEmbeddedFetchSgprs(sgprs, inst.dst, 1);
|
||||
}
|
||||
@@ -330,7 +332,6 @@ EmbeddedFetchData DetectEmbeddedVertexFetch(const Decoder::Program& decoded
|
||||
auto& dst = sgprs[DecodedSgprReg(inst.dst)];
|
||||
dst.type = EmbeddedFetchValueType::Constant;
|
||||
dst.value = inst.src0.value;
|
||||
dst.prolog_loads.clear();
|
||||
}
|
||||
break;
|
||||
default:
|
||||
@@ -347,7 +348,6 @@ EmbeddedFetchData DetectEmbeddedVertexFetch(const Decoder::Program& decoded
|
||||
auto& dst = sgprs[register_id + i];
|
||||
dst.type = EmbeddedFetchValueType::Attrib;
|
||||
dst.attrib_id = index + static_cast<int>(i);
|
||||
dst.prolog_loads = {inst.pc};
|
||||
}
|
||||
} else {
|
||||
ClearEmbeddedFetchSgprs(sgprs, inst.dst, DecodedDstSize(inst));
|
||||
@@ -365,7 +365,6 @@ EmbeddedFetchData DetectEmbeddedVertexFetch(const Decoder::Program& decoded
|
||||
dst.type = EmbeddedFetchValueType::Buffer;
|
||||
dst.attrib_id =
|
||||
BufferTableAttribFromOffset(raw_offset, static_cast<int>(i));
|
||||
dst.prolog_loads = {inst.pc};
|
||||
}
|
||||
} else if (IsDecodedSgpr(inst.src1) &&
|
||||
DecodedSgprReg(inst.src1) < sgprs.size() &&
|
||||
@@ -377,8 +376,6 @@ EmbeddedFetchData DetectEmbeddedVertexFetch(const Decoder::Program& decoded
|
||||
auto& dst = sgprs[register_id + i];
|
||||
dst.type = EmbeddedFetchValueType::Buffer;
|
||||
dst.attrib_id = sgprs[DecodedSgprReg(inst.src1)].attrib_id;
|
||||
dst.prolog_loads = sgprs[DecodedSgprReg(inst.src1)].prolog_loads;
|
||||
dst.prolog_loads.push_back(inst.pc);
|
||||
}
|
||||
} else {
|
||||
ClearEmbeddedFetchSgprs(sgprs, inst.dst, DecodedDstSize(inst));
|
||||
@@ -391,8 +388,8 @@ EmbeddedFetchData DetectEmbeddedVertexFetch(const Decoder::Program& decoded
|
||||
ClearEmbeddedFetchVectorLanes(&vector_lanes, inst.dst.reg);
|
||||
}
|
||||
if (IsDecodedVgpr(inst.dst) && inst.dst.reg < vgpr_is_index.size() &&
|
||||
IsDecodedVgpr(inst.src0) && inst.src0.reg == 8 &&
|
||||
IsDecodedVgpr(inst.src1) && inst.src1.reg == 5) {
|
||||
IsDecodedVgpr(inst.src0) && inst.src0.reg == instance_index_reg &&
|
||||
IsDecodedVgpr(inst.src1) && inst.src1.reg == vertex_index_reg) {
|
||||
vgpr_is_index[inst.dst.reg] = true;
|
||||
}
|
||||
} else if (IsEmbeddedFetchAttribPropagationAlu(inst)) {
|
||||
@@ -417,7 +414,6 @@ EmbeddedFetchData DetectEmbeddedVertexFetch(const Decoder::Program& decoded
|
||||
break;
|
||||
default: dst.value = src0 + src1; break;
|
||||
}
|
||||
dst.prolog_loads.clear();
|
||||
} else {
|
||||
ClearEmbeddedFetchSgprs(sgprs, inst.dst, 1);
|
||||
}
|
||||
@@ -440,7 +436,6 @@ EmbeddedFetchData DetectEmbeddedVertexFetch(const Decoder::Program& decoded
|
||||
load.pc = inst.pc;
|
||||
load.attrib_id = buffer.attrib_id;
|
||||
load.components = DecodedDstSize(inst);
|
||||
load.prolog_loads = buffer.prolog_loads;
|
||||
}
|
||||
}
|
||||
break;
|
||||
@@ -462,22 +457,9 @@ EmbeddedFetchData DetectEmbeddedVertexFetch(const Decoder::Program& decoded
|
||||
Decoder::Program DecodeFusedProgram(std::span<const uint32_t> front, std::span<const uint32_t> back,
|
||||
std::vector<uint32_t>& joined_code) {
|
||||
EXIT_IF(back.empty());
|
||||
Decoder::Program result;
|
||||
uint32_t front_words = 0;
|
||||
while (front_words < front.size()) {
|
||||
auto& inst = result.instructions.emplace_back();
|
||||
Decoder::DecodeInstruction(front, front_words, inst);
|
||||
front_words += inst.word_count;
|
||||
if (inst.opcode == Decoder::Opcode::S_SETPC_B64) {
|
||||
EXIT_NOT_IMPLEMENTED(inst.src0.kind != Decoder::OperandKind::Sgpr ||
|
||||
inst.src0.reg != 6u);
|
||||
break;
|
||||
}
|
||||
EXIT_NOT_IMPLEMENTED(inst.opcode == Decoder::Opcode::S_ENDPGM);
|
||||
}
|
||||
EXIT_IF(result.instructions.empty() ||
|
||||
result.instructions.back().opcode != Decoder::Opcode::S_SETPC_B64);
|
||||
joined_code.assign(front.begin(), front.begin() + front_words);
|
||||
auto result = Decoder::DecodeFrontProgram(front);
|
||||
const auto front_words = static_cast<uint32_t>(result.code.size());
|
||||
joined_code.assign(result.code.begin(), result.code.end());
|
||||
joined_code.insert(joined_code.end(), back.begin(), back.end());
|
||||
// The merged-stage ABI passes the back shader in s[6:7]. Give that handoff an
|
||||
// ordinary CFG edge, retaining both bodies in one register and LDS lifetime.
|
||||
@@ -505,7 +487,9 @@ TranslateResult TranslateProgram(std::span<const uint32_t> code, const CompileOp
|
||||
EXIT("shader recompiler input is empty\n");
|
||||
}
|
||||
if (options.stage != ShaderType::Compute && options.stage != ShaderType::Vertex &&
|
||||
options.stage != ShaderType::Pixel && options.stage != ShaderType::Mesh) {
|
||||
options.stage != ShaderType::Pixel && options.stage != ShaderType::Mesh &&
|
||||
options.stage != ShaderType::Local && options.stage != ShaderType::TessellationControl &&
|
||||
options.stage != ShaderType::TessellationEvaluation) {
|
||||
EXIT("shader recompiler received unsupported stage %u\n",
|
||||
static_cast<unsigned>(options.stage));
|
||||
}
|
||||
@@ -525,6 +509,12 @@ TranslateResult TranslateProgram(std::span<const uint32_t> code, const CompileOp
|
||||
std::vector<uint32_t> joined_code;
|
||||
if (!options.back_code.empty()) {
|
||||
decoded = DecodeFusedProgram(code, options.back_code, joined_code);
|
||||
} else if (options.stage == ShaderType::Local) {
|
||||
decoded = Decoder::DecodeFrontProgram(code);
|
||||
// The separately compiled hull half runs in the next Vulkan stage.
|
||||
auto& handoff = decoded.instructions.back();
|
||||
handoff.opcode = Decoder::Opcode::S_ENDPGM;
|
||||
handoff.src_count = 0;
|
||||
} else {
|
||||
Decoder::DecodeProgram(code, decoded);
|
||||
}
|
||||
@@ -567,9 +557,9 @@ TranslateResult TranslateProgram(std::span<const uint32_t> code, const CompileOp
|
||||
static_cast<uint64_t>(cfg.natural_loops.size()), phase_ms());
|
||||
}
|
||||
|
||||
EmbeddedFetchData embedded_fetch;
|
||||
if (options.stage == ShaderType::Vertex && options.input_info.vertex != nullptr &&
|
||||
options.input_info.vertex->fetch_embedded) {
|
||||
Frontend::EmbeddedFetchPlan embedded_fetch;
|
||||
if ((options.stage == ShaderType::Vertex || options.stage == ShaderType::Local) &&
|
||||
options.input_info.vertex != nullptr && options.input_info.vertex->fetch_embedded) {
|
||||
embedded_fetch = DetectEmbeddedVertexFetch(
|
||||
decoded, options.input_info.vertex, options.user_data_base,
|
||||
static_cast<uint32_t>(options.user_data.size()), options.wave_size);
|
||||
@@ -608,6 +598,7 @@ TranslateResult TranslateProgram(std::span<const uint32_t> code, const CompileOp
|
||||
IR::RemoveIdentities(ir.blocks);
|
||||
IR::EliminateDeadCode(ir.blocks);
|
||||
}
|
||||
LowerTessellationMemory(ir, options);
|
||||
IR::BuildSrtPlan(ir);
|
||||
IR::EliminateDeadCode(ir.blocks);
|
||||
IR::TrackResources(ir);
|
||||
|
||||
@@ -0,0 +1,330 @@
|
||||
#include "graphics/shader/recompiler/Tessellation.h"
|
||||
|
||||
#include "common/assert.h"
|
||||
#include "common/logging/log.h"
|
||||
#include "graphics/shader/recompiler/ShaderRecompiler.h"
|
||||
#include "graphics/shader/recompiler/frontend/decode/ShaderDecoder.h"
|
||||
#include "graphics/shader/recompiler/ir/ShaderIR.h"
|
||||
#include "graphics/shader/recompiler/ir/passes/ConstantPropagation.h"
|
||||
#include "graphics/shader/recompiler/ir/passes/DeadCodeElimination.h"
|
||||
|
||||
#include <algorithm>
|
||||
#include <array>
|
||||
#include <unordered_map>
|
||||
#include <utility>
|
||||
|
||||
namespace Libs::Graphics::ShaderRecompiler {
|
||||
namespace {
|
||||
|
||||
struct TessellationAddress {
|
||||
enum class Kind { Unknown, Affine, PackedControlPoint };
|
||||
Kind kind = Kind::Unknown;
|
||||
uint32_t coefficient = 0;
|
||||
uint32_t constant = 0;
|
||||
};
|
||||
|
||||
uint32_t ReflectTessellationStride(const Decoder::Program& program, bool local,
|
||||
uint32_t control_points, uint32_t input_stride) {
|
||||
using namespace Decoder;
|
||||
using Address = TessellationAddress;
|
||||
using Kind = Address::Kind;
|
||||
std::array<Address, IR::NumVectorRegs> registers {};
|
||||
registers[local ? 3u : 1u] =
|
||||
local ? Address {Kind::Affine, 1u, 0u} : Address {Kind::PackedControlPoint};
|
||||
const auto constant = [](uint32_t value) { return Address {Kind::Affine, 0u, value}; };
|
||||
const auto read = [&](const Operand& operand) -> Address {
|
||||
if (operand.absolute || operand.negate || operand.sdwa_sext || operand.op_sel ||
|
||||
operand.op_sel_hi || operand.negate_hi || operand.dpp)
|
||||
return {};
|
||||
Address value;
|
||||
if (operand.kind == OperandKind::IntegerInlineConstant ||
|
||||
operand.kind == OperandKind::LiteralConstant) {
|
||||
value = constant(operand.value);
|
||||
} else if (operand.kind == OperandKind::Vgpr) {
|
||||
value = registers.at(operand.reg);
|
||||
}
|
||||
// The native packed HS ID's low byte is the patch ordinal. The logical
|
||||
// interface addresses one patch, as in the frontend's v1 initialization.
|
||||
if (value.kind == Kind::PackedControlPoint && operand.sdwa_sel == 0u) return constant(0u);
|
||||
return operand.sdwa_sel == 6u ? value : Address {};
|
||||
};
|
||||
const auto affine = [](uint64_t coefficient, uint64_t offset) {
|
||||
return coefficient <= UINT32_MAX && offset <= UINT32_MAX
|
||||
? Address {Kind::Affine, static_cast<uint32_t>(coefficient),
|
||||
static_cast<uint32_t>(offset)}
|
||||
: Address {};
|
||||
};
|
||||
const auto add = [&](Address lhs, Address rhs) {
|
||||
return lhs.kind == Kind::Affine && rhs.kind == Kind::Affine
|
||||
? affine(uint64_t {lhs.coefficient} + rhs.coefficient,
|
||||
uint64_t {lhs.constant} + rhs.constant)
|
||||
: Address {};
|
||||
};
|
||||
const auto multiply = [&](Address lhs, Address rhs) {
|
||||
if ((lhs.kind == Kind::Affine && lhs.coefficient == 0u && lhs.constant == 0u) ||
|
||||
(rhs.kind == Kind::Affine && rhs.coefficient == 0u && rhs.constant == 0u))
|
||||
return constant(0u);
|
||||
if (lhs.kind != Kind::Affine || rhs.kind != Kind::Affine) return Address {};
|
||||
if (rhs.coefficient != 0u) std::swap(lhs, rhs);
|
||||
return rhs.coefficient == 0u ? affine(uint64_t {lhs.coefficient} * rhs.constant,
|
||||
uint64_t {lhs.constant} * rhs.constant)
|
||||
: Address {};
|
||||
};
|
||||
const auto low24 = [&](Address value) {
|
||||
if (value.kind == Kind::Affine && value.coefficient == 0u)
|
||||
return constant(value.constant & 0xffffffu);
|
||||
return value.kind == Kind::Affine &&
|
||||
uint64_t {value.coefficient} * (control_points - 1u) + value.constant <=
|
||||
0xffffffu
|
||||
? value
|
||||
: Address {};
|
||||
};
|
||||
uint32_t stride = 0;
|
||||
for (const auto& inst: program.instructions) {
|
||||
// Stage exits preserve the active path's definitions. An internal join
|
||||
// would require merging register definitions.
|
||||
EXIT_NOT_IMPLEMENTED(IsDirectBranch(inst.opcode) &&
|
||||
inst.branch_target != program.instructions.back().pc);
|
||||
const bool local_store =
|
||||
inst.opcode == Opcode::DS_WRITE_B32 || inst.opcode == Opcode::DS_WRITE2_B32;
|
||||
const bool buffer_store = inst.opcode == Opcode::BUFFER_STORE_DWORD ||
|
||||
inst.opcode == Opcode::BUFFER_STORE_DWORDX2 ||
|
||||
inst.opcode == Opcode::BUFFER_STORE_DWORDX3 ||
|
||||
inst.opcode == Opcode::BUFFER_STORE_DWORDX4;
|
||||
const bool control_store =
|
||||
!local && buffer_store && inst.src2.kind == OperandKind::Sgpr && inst.src2.reg == 2u;
|
||||
const bool control_read =
|
||||
!local && (inst.opcode == Opcode::DS_READ_B32 || inst.opcode == Opcode::DS_READ2_B32);
|
||||
if ((local && local_store) || control_store || control_read) {
|
||||
const auto address = read(inst.src0);
|
||||
if (address.kind != Kind::Affine) {
|
||||
EXIT("%s tessellation address is not affine at pc 0x%08x\n", local ? "LS" : "HS",
|
||||
inst.pc);
|
||||
}
|
||||
if (address.coefficient != 0u) {
|
||||
const auto expected = control_read ? input_stride : stride;
|
||||
EXIT_NOT_IMPLEMENTED((address.coefficient & 3u) != 0u ||
|
||||
(expected != 0u && expected != address.coefficient));
|
||||
if (!control_read) stride = address.coefficient;
|
||||
}
|
||||
}
|
||||
// Store vdata is a source, despite occupying the decoder's dst field.
|
||||
if (local_store || buffer_store || inst.dst.kind != OperandKind::Vgpr) continue;
|
||||
const auto lhs = read(inst.src0);
|
||||
const auto rhs = read(inst.src1);
|
||||
const auto third = read(inst.src2);
|
||||
Address value;
|
||||
switch (inst.opcode) {
|
||||
case Opcode::V_BFE_U32:
|
||||
if (lhs.kind == Kind::PackedControlPoint && rhs.kind == Kind::Affine &&
|
||||
rhs.coefficient == 0u && third.kind == Kind::Affine &&
|
||||
third.coefficient == 0u) {
|
||||
if (rhs.constant == 8u && third.constant == 5u) value = {Kind::Affine, 1u, 0u};
|
||||
if (rhs.constant == 0u && third.constant == 8u) value = constant(0u);
|
||||
}
|
||||
break;
|
||||
case Opcode::V_MUL_U32_U24: value = multiply(low24(lhs), low24(rhs)); break;
|
||||
case Opcode::V_MAD_U32_U24: value = add(multiply(low24(lhs), low24(rhs)), third); break;
|
||||
case Opcode::V_LSHL_ADD_U32:
|
||||
if (rhs.kind == Kind::Affine && rhs.coefficient == 0u && rhs.constant < 32u)
|
||||
value = add(multiply(lhs, constant(1u << rhs.constant)), third);
|
||||
break;
|
||||
case Opcode::V_LSHLREV_B32:
|
||||
if (lhs.kind == Kind::Affine && lhs.coefficient == 0u && lhs.constant < 32u)
|
||||
value = multiply(rhs, constant(1u << lhs.constant));
|
||||
break;
|
||||
case Opcode::V_SUB_NC_U32:
|
||||
if (lhs.kind == Kind::Affine && rhs.kind == Kind::Affine &&
|
||||
lhs.coefficient >= rhs.coefficient && lhs.constant >= rhs.constant)
|
||||
value = affine(lhs.coefficient - rhs.coefficient, lhs.constant - rhs.constant);
|
||||
break;
|
||||
default: break;
|
||||
}
|
||||
if (inst.dst.sdwa_sel != 6u || inst.dst.op_sel || inst.dst.omod || inst.dst.clamp ||
|
||||
inst.dst.dpp)
|
||||
value = {};
|
||||
for (uint32_t index = 0;
|
||||
index < std::max(inst.data_dwords, 1u) && inst.dst.reg + index < registers.size(); index++) {
|
||||
registers[inst.dst.reg + index] = {};
|
||||
}
|
||||
registers.at(inst.dst.reg) = value;
|
||||
}
|
||||
EXIT_NOT_IMPLEMENTED(stride == 0u);
|
||||
return stride;
|
||||
}
|
||||
|
||||
const IR::Inst* TessellationBufferBase(const IR::Inst& inst) {
|
||||
if (IR::BufferAccessOf(inst.GetOpcode()) == IR::BufferAccess::None || inst.NumArgs() <= 3u)
|
||||
return nullptr;
|
||||
const auto* base = inst.Arg(3).Resolve().TryInstruction();
|
||||
return base != nullptr && base->GetOpcode() == IR::ValueOpcode::TessellationBase ? base
|
||||
: nullptr;
|
||||
}
|
||||
|
||||
IR::Value ActiveAddress(IR::Block& block, IR::Block::iterator before, IR::Value value,
|
||||
IR::Value predicate, std::unordered_map<IR::Inst*, IR::Value>& resolved) {
|
||||
using namespace IR;
|
||||
value = value.Resolve();
|
||||
auto* source = value.TryInstruction();
|
||||
if (source == nullptr) return value;
|
||||
if (const auto found = resolved.find(source); found != resolved.end()) return found->second;
|
||||
if (source->GetOpcode() == ValueOpcode::SelectU32) {
|
||||
return source->Arg(0).Resolve() == predicate.Resolve()
|
||||
? ActiveAddress(block, before, source->Arg(1), predicate, resolved)
|
||||
: value;
|
||||
}
|
||||
if (source->GetOpcode() != ValueOpcode::IAdd32 && source->GetOpcode() != ValueOpcode::ISub32)
|
||||
return value;
|
||||
const auto lhs = ActiveAddress(block, before, source->Arg(0), predicate, resolved);
|
||||
const auto rhs = ActiveAddress(block, before, source->Arg(1), predicate, resolved);
|
||||
if (lhs != source->Arg(0).Resolve() || rhs != source->Arg(1).Resolve()) {
|
||||
auto copy = block.PrependNewInst(before, source->GetOpcode(), {lhs, rhs},
|
||||
source->Flags<uint64_t>());
|
||||
value = Value(&*copy);
|
||||
}
|
||||
resolved.emplace(source, value);
|
||||
return value;
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
void AnalyzeTessellationPrograms(std::span<const uint32_t> local, std::span<const uint32_t> control,
|
||||
ShaderTessellationInputInfo& info) {
|
||||
const auto local_program = Decoder::DecodeFrontProgram(local);
|
||||
Decoder::Program control_program;
|
||||
Decoder::DecodeProgram(control, control_program);
|
||||
info.ls_stride = ReflectTessellationStride(local_program, true, info.input_control_points, 0u);
|
||||
info.hs_stride = ReflectTessellationStride(control_program, false, info.output_control_points,
|
||||
info.ls_stride);
|
||||
LOGF("Tessellation interface: input_cp=%u output_cp=%u ls_stride=%u hs_stride=%u\n",
|
||||
info.input_control_points, info.output_control_points, info.ls_stride, info.hs_stride);
|
||||
}
|
||||
|
||||
void LowerTessellationMemory(IR::Program& program, const CompileOptions& options) {
|
||||
using namespace IR;
|
||||
if (options.stage != ShaderType::Local && options.stage != ShaderType::TessellationControl &&
|
||||
options.stage != ShaderType::TessellationEvaluation) {
|
||||
return;
|
||||
}
|
||||
const auto& tess = options.input_info.vertex->tess;
|
||||
// Ring addresses can reuse data VGPRs. Their inactive values are irrelevant to
|
||||
// a store guarded by the same EXEC predicate, but must remain intact elsewhere.
|
||||
for (auto* block: program.blocks) {
|
||||
for (auto it = block->begin(); it != block->end(); ++it) {
|
||||
if (TessellationBufferBase(*it) == nullptr) continue;
|
||||
std::unordered_map<Inst*, Value> resolved;
|
||||
it->SetArg(
|
||||
2, ActiveAddress(*block, it, it->Arg(2), it->Arg(it->NumArgs() - 1u), resolved));
|
||||
}
|
||||
}
|
||||
ConstantPropagationPass(program.blocks);
|
||||
uint32_t reads = 0, writes = 0, factors = 0;
|
||||
for (auto* block: program.blocks) {
|
||||
for (auto it = block->begin(); it != block->end(); ++it) {
|
||||
auto& inst = *it;
|
||||
const auto shared = SharedAccessOf(inst.GetOpcode());
|
||||
const auto buffer = BufferAccessOf(inst.GetOpcode());
|
||||
if (shared == SharedAccess::None && buffer == BufferAccess::None) {
|
||||
continue;
|
||||
}
|
||||
const auto& memory = program.memory_info.at(inst.Flags<MemoryFlags>().index);
|
||||
bool write = false;
|
||||
uint32_t components = 0;
|
||||
TessellationAttribute kind;
|
||||
Value address, predicate;
|
||||
if (shared != SharedAccess::None && memory.kind == ResourceKind::Lds) {
|
||||
write = shared == SharedAccess::Write;
|
||||
EXIT_NOT_IMPLEMENTED(memory.data_bits != 32u ||
|
||||
(options.stage == ShaderType::Local
|
||||
? !write
|
||||
: options.stage != ShaderType::TessellationControl ||
|
||||
shared != SharedAccess::Read));
|
||||
kind = write ? TessellationAttribute::LocalOutput
|
||||
: TessellationAttribute::ControlInput;
|
||||
components = SharedComponentCount(inst.GetOpcode());
|
||||
address = inst.Arg(0);
|
||||
predicate = inst.Arg(inst.NumArgs() - 1u);
|
||||
} else if (buffer != BufferAccess::None && memory.kind == ResourceKind::Buffer) {
|
||||
const auto* base = TessellationBufferBase(inst);
|
||||
if (base == nullptr) {
|
||||
continue;
|
||||
}
|
||||
EXIT_NOT_IMPLEMENTED(memory.data_bits != 32u || memory.formatted || memory.idxen ||
|
||||
!memory.offen || buffer == BufferAccess::Atomic);
|
||||
write = buffer == BufferAccess::Write;
|
||||
components = BufferComponentCount(inst.GetOpcode());
|
||||
address = inst.Arg(2).Resolve();
|
||||
predicate = inst.Arg(inst.NumArgs() - 1u);
|
||||
if (base->Arg(0).U32() == 1u) {
|
||||
EXIT_NOT_IMPLEMENTED(!write ||
|
||||
options.stage != ShaderType::TessellationControl);
|
||||
kind = TessellationAttribute::Factor;
|
||||
factors += components;
|
||||
} else {
|
||||
kind = write ? TessellationAttribute::ControlOutput
|
||||
: TessellationAttribute::EvaluationInput;
|
||||
EXIT_NOT_IMPLEMENTED(write
|
||||
? options.stage != ShaderType::TessellationControl
|
||||
: options.stage != ShaderType::TessellationEvaluation);
|
||||
if (address.IsImmediate() &&
|
||||
address.U32() >= tess.hs_stride * tess.output_control_points) {
|
||||
EXIT_NOT_IMPLEMENTED(!write);
|
||||
kind = TessellationAttribute::PatchOutput;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
continue;
|
||||
}
|
||||
const auto emit = [&](ValueOpcode opcode, std::initializer_list<Value> args) {
|
||||
return Value(&*block->PrependNewInst(it, opcode, args));
|
||||
};
|
||||
std::array<Value, 4> values;
|
||||
for (uint32_t component = 0; component < components; component++) {
|
||||
const auto offset = memory.offset + 4u * component;
|
||||
const auto byte_address =
|
||||
offset == 0u ? address : emit(ValueOpcode::IAdd32, {address, Value(offset)});
|
||||
if (write) {
|
||||
Value data = shared != SharedAccess::None ? inst.Arg(component + 1u)
|
||||
: components == 1u
|
||||
? inst.Arg(4)
|
||||
: emit(components == 2u ? ValueOpcode::CompositeExtractU32x2
|
||||
: components == 3u ? ValueOpcode::CompositeExtractU32x3
|
||||
: ValueOpcode::CompositeExtractU32x4,
|
||||
{inst.Arg(4), Value(component)});
|
||||
emit(ValueOpcode::SetTessellationAttribute,
|
||||
{Value(static_cast<uint32_t>(kind)), byte_address, data, predicate});
|
||||
writes++;
|
||||
} else {
|
||||
values[component] =
|
||||
emit(ValueOpcode::GetTessellationAttribute,
|
||||
{Value(static_cast<uint32_t>(kind)), byte_address, predicate});
|
||||
reads++;
|
||||
}
|
||||
}
|
||||
if (!write) {
|
||||
Value replacement = values[0];
|
||||
if (components == 2u)
|
||||
replacement =
|
||||
emit(ValueOpcode::CompositeConstructU32x2, {values[0], values[1]});
|
||||
if (components == 3u)
|
||||
replacement = emit(ValueOpcode::CompositeConstructU32x3,
|
||||
{values[0], values[1], values[2]});
|
||||
if (components == 4u)
|
||||
replacement = emit(ValueOpcode::CompositeConstructU32x4,
|
||||
{values[0], values[1], values[2], values[3]});
|
||||
inst.ReplaceUsesWith(replacement);
|
||||
}
|
||||
inst.Invalidate();
|
||||
}
|
||||
}
|
||||
ConstantPropagationPass(program.blocks);
|
||||
RemoveIdentities(program.blocks);
|
||||
EliminateDeadCode(program.blocks);
|
||||
LOGF("%s tessellation lowering: reads=%u writes=%u factors=%u\n",
|
||||
options.stage == ShaderType::Local ? "LS"
|
||||
: options.stage == ShaderType::TessellationControl ? "HS"
|
||||
: "TES",
|
||||
reads, writes, factors);
|
||||
}
|
||||
|
||||
} // namespace Libs::Graphics::ShaderRecompiler
|
||||
@@ -0,0 +1,21 @@
|
||||
#pragma once
|
||||
|
||||
#include <cstdint>
|
||||
#include <span>
|
||||
|
||||
namespace Libs::Graphics {
|
||||
struct ShaderTessellationInputInfo;
|
||||
}
|
||||
|
||||
namespace Libs::Graphics::ShaderRecompiler {
|
||||
|
||||
struct CompileOptions;
|
||||
namespace IR {
|
||||
struct Program;
|
||||
}
|
||||
|
||||
void AnalyzeTessellationPrograms(std::span<const uint32_t> local, std::span<const uint32_t> control,
|
||||
ShaderTessellationInputInfo& info);
|
||||
void LowerTessellationMemory(IR::Program& program, const CompileOptions& options);
|
||||
|
||||
} // namespace Libs::Graphics::ShaderRecompiler
|
||||
@@ -15,8 +15,8 @@ static void AppendInstructionWords(std::vector<uint32_t>& section, const uint32_
|
||||
if (words_num == 0) {
|
||||
return;
|
||||
}
|
||||
const auto opcode = words[0];
|
||||
const auto word_count = static_cast<uint32_t>(words_num);
|
||||
const uint32_t opcode = words[0];
|
||||
const auto word_count = static_cast<uint32_t>(words_num);
|
||||
section.push_back((word_count << spv::WordCountShift) | opcode);
|
||||
section.insert(section.end(), words + 1, words + words_num);
|
||||
}
|
||||
@@ -38,7 +38,7 @@ void Builder::RequireVersion(uint32_t version) {
|
||||
|
||||
void Builder::RequireCapability(spv::Capability capability) {
|
||||
if (m_required_capabilities.insert(capability).second) {
|
||||
AppendInstruction(m_capabilities, spv::OpCapability, {capability});
|
||||
AppendInstruction(m_capabilities, spv::OpCapability, capability);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -62,37 +62,25 @@ uint32_t Builder::Import(const char* name) {
|
||||
return id;
|
||||
}
|
||||
|
||||
uint32_t Builder::Type(spv::Op opcode, std::initializer_list<uint32_t> operands) {
|
||||
return Type(opcode, std::vector<uint32_t>(operands));
|
||||
}
|
||||
|
||||
uint32_t Builder::Type(spv::Op opcode, const std::vector<uint32_t>& operands) {
|
||||
std::vector<uint32_t> key;
|
||||
key.reserve(operands.size() + 2u);
|
||||
key.push_back(opcode);
|
||||
key.push_back(static_cast<uint32_t>(operands.size()));
|
||||
key.insert(key.end(), operands.begin(), operands.end());
|
||||
uint32_t Builder::DeclareType(spv::Op opcode, std::vector<uint32_t> key) {
|
||||
if (const auto it = m_declaration_ids.find(key); it != m_declaration_ids.end()) {
|
||||
return it->second;
|
||||
}
|
||||
const auto id = AllocateId();
|
||||
AppendInstruction(m_declarations, opcode, id, std::span<const uint32_t>(key).subspan(2));
|
||||
m_declaration_ids.emplace(std::move(key), id);
|
||||
std::vector<uint32_t> words {opcode, id};
|
||||
words.insert(words.end(), operands.begin(), operands.end());
|
||||
AppendInstructionWords(m_declarations, words.data(), words.size());
|
||||
return id;
|
||||
}
|
||||
|
||||
uint32_t Builder::DecoratedType(spv::Op opcode, std::initializer_list<uint32_t> operands,
|
||||
std::initializer_list<TypeAnnotation> annotations) {
|
||||
uint32_t Builder::DeclareDecoratedType(spv::Op opcode, std::vector<uint32_t> key,
|
||||
std::initializer_list<TypeAnnotation> annotations) {
|
||||
if (annotations.size() == 0) {
|
||||
return Type(opcode, operands);
|
||||
return DeclareType(opcode, std::move(key));
|
||||
}
|
||||
std::vector<uint32_t> key {opcode, static_cast<uint32_t>(operands.size())};
|
||||
key.insert(key.end(), operands.begin(), operands.end());
|
||||
const auto operand_count = key[1];
|
||||
key.push_back(static_cast<uint32_t>(annotations.size()));
|
||||
for (const auto& annotation: annotations) {
|
||||
key.push_back(annotation.opcode);
|
||||
AppendOperand(key, annotation.opcode);
|
||||
key.push_back(static_cast<uint32_t>(annotation.operands.size()));
|
||||
key.insert(key.end(), annotation.operands.begin(), annotation.operands.end());
|
||||
}
|
||||
@@ -100,37 +88,23 @@ uint32_t Builder::DecoratedType(spv::Op opcode, std::initializer_list<uint32_t>
|
||||
return it->second;
|
||||
}
|
||||
const auto id = AllocateId();
|
||||
AppendInstruction(m_declarations, opcode, id,
|
||||
std::span<const uint32_t>(key).subspan(2, operand_count));
|
||||
m_declaration_ids.emplace(std::move(key), id);
|
||||
std::vector<uint32_t> words {opcode, id};
|
||||
words.insert(words.end(), operands.begin(), operands.end());
|
||||
AppendInstructionWords(m_declarations, words.data(), words.size());
|
||||
for (const auto& annotation: annotations) {
|
||||
words = {annotation.opcode, id};
|
||||
words.insert(words.end(), annotation.operands.begin(), annotation.operands.end());
|
||||
AppendInstructionWords(m_annotations, words.data(), words.size());
|
||||
AppendInstruction(m_annotations, annotation.opcode, id, annotation.operands);
|
||||
}
|
||||
return id;
|
||||
}
|
||||
|
||||
uint32_t Builder::Constant(spv::Op opcode, uint32_t type,
|
||||
std::initializer_list<uint32_t> operands) {
|
||||
return Constant(opcode, type, std::vector<uint32_t>(operands));
|
||||
}
|
||||
|
||||
uint32_t Builder::Constant(spv::Op opcode, uint32_t type, const std::vector<uint32_t>& operands) {
|
||||
std::vector<uint32_t> key;
|
||||
key.reserve(operands.size() + 2u);
|
||||
key.push_back(opcode);
|
||||
key.push_back(type);
|
||||
key.insert(key.end(), operands.begin(), operands.end());
|
||||
uint32_t Builder::DeclareConstant(spv::Op opcode, std::vector<uint32_t> key) {
|
||||
if (const auto it = m_declaration_ids.find(key); it != m_declaration_ids.end()) {
|
||||
return it->second;
|
||||
}
|
||||
const auto id = AllocateId();
|
||||
AppendInstruction(m_declarations, opcode, key[1], id,
|
||||
std::span<const uint32_t>(key).subspan(2));
|
||||
m_declaration_ids.emplace(std::move(key), id);
|
||||
std::vector<uint32_t> words {opcode, type, id};
|
||||
words.insert(words.end(), operands.begin(), operands.end());
|
||||
AppendInstructionWords(m_declarations, words.data(), words.size());
|
||||
return id;
|
||||
}
|
||||
|
||||
@@ -142,7 +116,7 @@ uint32_t Builder::DefineGlobalVariable(uint32_t pointer_type, spv::StorageClass
|
||||
|
||||
void Builder::DefineGlobalVariable(uint32_t id, uint32_t pointer_type,
|
||||
spv::StorageClass storage_class) {
|
||||
AppendInstruction(m_declarations, spv::OpVariable, {pointer_type, id, storage_class});
|
||||
AppendInstruction(m_declarations, spv::OpVariable, pointer_type, id, storage_class);
|
||||
}
|
||||
|
||||
void Builder::AppendString(std::vector<uint32_t>& words, const char* text) {
|
||||
@@ -161,27 +135,14 @@ void Builder::AppendString(std::vector<uint32_t>& words, const char* text) {
|
||||
}
|
||||
}
|
||||
|
||||
void Builder::AppendInstruction(std::vector<uint32_t>& section, spv::Op opcode,
|
||||
const std::vector<uint32_t>& operands) {
|
||||
const uint32_t word_count = static_cast<uint32_t>(operands.size() + 1u);
|
||||
section.push_back((word_count << spv::WordCountShift) | opcode);
|
||||
section.insert(section.end(), operands.begin(), operands.end());
|
||||
}
|
||||
|
||||
void Builder::AppendInstruction(std::vector<uint32_t>& section, spv::Op opcode,
|
||||
std::initializer_list<uint32_t> operands) {
|
||||
const uint32_t word_count = static_cast<uint32_t>(operands.size() + 1u);
|
||||
section.push_back((word_count << spv::WordCountShift) | opcode);
|
||||
section.insert(section.end(), operands.begin(), operands.end());
|
||||
}
|
||||
|
||||
void Builder::AddMemoryModel(std::initializer_list<uint32_t> operands) {
|
||||
AppendInstruction(m_memory_model, spv::OpMemoryModel, operands);
|
||||
void Builder::AddMemoryModel(spv::AddressingModel addressing_model, spv::MemoryModel memory_model) {
|
||||
AppendInstruction(m_memory_model, spv::OpMemoryModel, addressing_model, memory_model);
|
||||
}
|
||||
|
||||
void Builder::AddEntryPoint(spv::ExecutionModel execution_model, uint32_t entry_point,
|
||||
const char* name, const std::vector<uint32_t>& interfaces) {
|
||||
std::vector<uint32_t> operands = {execution_model, entry_point};
|
||||
std::vector<uint32_t> operands;
|
||||
AppendOperands(operands, execution_model, entry_point);
|
||||
AppendString(operands, name);
|
||||
operands.insert(operands.end(), interfaces.begin(), interfaces.end());
|
||||
if (m_version >= 0x00010400u) {
|
||||
@@ -199,25 +160,13 @@ void Builder::AddEntryPoint(spv::ExecutionModel execution_model, uint32_t entry_
|
||||
AppendInstruction(m_entry_points, spv::OpEntryPoint, operands);
|
||||
}
|
||||
|
||||
void Builder::AddExecutionMode(std::initializer_list<uint32_t> operands) {
|
||||
AppendInstruction(m_execution_modes, spv::OpExecutionMode, operands);
|
||||
}
|
||||
|
||||
void Builder::AddName(uint32_t target, const char* name) {
|
||||
std::vector<uint32_t> operands = {target};
|
||||
AppendString(operands, name);
|
||||
AppendInstruction(m_debug, spv::OpName, operands);
|
||||
}
|
||||
|
||||
void Builder::AddAnnotation(std::initializer_list<uint32_t> words) {
|
||||
AppendInstructionWords(m_annotations, words.begin(), words.size());
|
||||
}
|
||||
|
||||
void Builder::AddFunction(std::initializer_list<uint32_t> words) {
|
||||
AppendInstructionWords(m_functions, words.begin(), words.size());
|
||||
}
|
||||
|
||||
void Builder::AddFunction(const std::vector<uint32_t>& words) {
|
||||
void Builder::AddFunction(std::span<const uint32_t> words) {
|
||||
AppendInstructionWords(m_functions, words.data(), words.size());
|
||||
}
|
||||
|
||||
|
||||
@@ -4,17 +4,21 @@
|
||||
#include "common/common.h"
|
||||
|
||||
#include <initializer_list>
|
||||
#include <iterator>
|
||||
#include <map>
|
||||
#include <set>
|
||||
#include <span>
|
||||
#include <spirv/unified1/spirv.hpp>
|
||||
#include <string>
|
||||
#include <type_traits>
|
||||
#include <utility>
|
||||
#include <vector>
|
||||
|
||||
namespace Libs::Graphics::ShaderRecompiler::Spirv {
|
||||
|
||||
struct TypeAnnotation {
|
||||
spv::Op opcode = spv::OpNop;
|
||||
std::vector<uint32_t> operands;
|
||||
std::vector<uint32_t> operands; // Already encoded operand words.
|
||||
};
|
||||
|
||||
struct DeferredPhi {
|
||||
@@ -32,33 +36,109 @@ public:
|
||||
void RequireCapability(spv::Capability capability);
|
||||
void RequireExtension(const char* name);
|
||||
uint32_t Import(const char* name);
|
||||
uint32_t Type(spv::Op opcode, std::initializer_list<uint32_t> operands = {});
|
||||
uint32_t Type(spv::Op opcode, const std::vector<uint32_t>& operands);
|
||||
uint32_t DecoratedType(spv::Op opcode, std::initializer_list<uint32_t> operands,
|
||||
std::initializer_list<TypeAnnotation> annotations);
|
||||
uint32_t Constant(spv::Op opcode, uint32_t type, std::initializer_list<uint32_t> operands = {});
|
||||
uint32_t Constant(spv::Op opcode, uint32_t type, const std::vector<uint32_t>& operands);
|
||||
template <typename... Args>
|
||||
uint32_t Type(spv::Op opcode, const Args&... operands) {
|
||||
return DeclareType(opcode, MakeTypeKey(opcode, operands...));
|
||||
}
|
||||
|
||||
template <typename... Args>
|
||||
uint32_t DecoratedType(spv::Op opcode, std::initializer_list<TypeAnnotation> annotations,
|
||||
const Args&... operands) {
|
||||
return DeclareDecoratedType(opcode, MakeTypeKey(opcode, operands...), annotations);
|
||||
}
|
||||
|
||||
template <typename... Args>
|
||||
uint32_t Constant(spv::Op opcode, uint32_t type, const Args&... operands) {
|
||||
std::vector<uint32_t> key;
|
||||
key.reserve(2u + (0u + ... + OperandWordCount(operands)));
|
||||
AppendOperands(key, opcode, type, operands...);
|
||||
return DeclareConstant(opcode, std::move(key));
|
||||
}
|
||||
|
||||
uint32_t DefineGlobalVariable(uint32_t pointer_type, spv::StorageClass storage_class);
|
||||
void DefineGlobalVariable(uint32_t id, uint32_t pointer_type, spv::StorageClass storage_class);
|
||||
|
||||
void AddMemoryModel(std::initializer_list<uint32_t> operands);
|
||||
void AddMemoryModel(spv::AddressingModel addressing_model, spv::MemoryModel memory_model);
|
||||
void AddEntryPoint(spv::ExecutionModel execution_model, uint32_t entry_point, const char* name,
|
||||
const std::vector<uint32_t>& interfaces);
|
||||
void AddExecutionMode(std::initializer_list<uint32_t> operands);
|
||||
void AddName(uint32_t target, const char* name);
|
||||
void AddAnnotation(std::initializer_list<uint32_t> words);
|
||||
void AddFunction(std::initializer_list<uint32_t> words);
|
||||
void AddFunction(const std::vector<uint32_t>& words);
|
||||
template <typename... Args>
|
||||
void AddExecutionMode(uint32_t entry_point, spv::ExecutionMode mode, const Args&... operands) {
|
||||
AppendInstruction(m_execution_modes, spv::OpExecutionMode, entry_point, mode, operands...);
|
||||
}
|
||||
|
||||
void AddName(uint32_t target, const char* name);
|
||||
template <typename... Args>
|
||||
void AddAnnotation(spv::Op opcode, const Args&... operands) {
|
||||
AppendInstruction(m_annotations, opcode, operands...);
|
||||
}
|
||||
|
||||
template <typename... Args>
|
||||
void AddFunction(spv::Op opcode, const Args&... operands) {
|
||||
AppendInstruction(m_functions, opcode, operands...);
|
||||
}
|
||||
|
||||
void AddFunction(std::span<const uint32_t> words);
|
||||
DeferredPhi AddDeferredPhi(uint32_t type, uint32_t result, size_t incoming_count);
|
||||
void PatchDeferredPhi(DeferredPhi phi, size_t incoming, uint32_t value, uint32_t parent);
|
||||
|
||||
[[nodiscard]] std::vector<uint32_t> Build() const;
|
||||
|
||||
private:
|
||||
static void AppendOperand(std::vector<uint32_t>& words, uint32_t value) {
|
||||
words.push_back(value);
|
||||
}
|
||||
|
||||
static void AppendOperand(std::vector<uint32_t>& words, int32_t value) {
|
||||
words.push_back(static_cast<uint32_t>(value));
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
requires std::is_enum_v<T>
|
||||
static void AppendOperand(std::vector<uint32_t>& words, T value) {
|
||||
static_assert(sizeof(T) == sizeof(uint32_t));
|
||||
words.push_back(static_cast<uint32_t>(value));
|
||||
}
|
||||
|
||||
static void AppendOperand(std::vector<uint32_t>& words, std::span<const uint32_t> values) {
|
||||
words.insert(words.end(), values.begin(), values.end());
|
||||
}
|
||||
|
||||
template <typename... Args>
|
||||
static void AppendOperands(std::vector<uint32_t>& words, const Args&... operands) {
|
||||
(AppendOperand(words, operands), ...);
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
static size_t OperandWordCount(const T& operand) {
|
||||
if constexpr (std::is_integral_v<T> || std::is_enum_v<T>) {
|
||||
return 1;
|
||||
} else {
|
||||
return std::size(operand);
|
||||
}
|
||||
}
|
||||
|
||||
template <typename... Args>
|
||||
static std::vector<uint32_t> MakeTypeKey(spv::Op opcode, const Args&... operands) {
|
||||
const auto operand_count = static_cast<uint32_t>((0u + ... + OperandWordCount(operands)));
|
||||
std::vector<uint32_t> key;
|
||||
key.reserve(2u + operand_count);
|
||||
AppendOperands(key, opcode, operand_count, operands...);
|
||||
return key;
|
||||
}
|
||||
|
||||
template <typename... Args>
|
||||
static void AppendInstruction(std::vector<uint32_t>& section, spv::Op opcode,
|
||||
const std::vector<uint32_t>& operands);
|
||||
static void AppendInstruction(std::vector<uint32_t>& section, spv::Op opcode,
|
||||
std::initializer_list<uint32_t> operands);
|
||||
const Args&... operands) {
|
||||
const auto offset = section.size();
|
||||
AppendOperands(section, opcode, operands...);
|
||||
const auto word_count = static_cast<uint32_t>(section.size() - offset);
|
||||
section[offset] |= word_count << spv::WordCountShift;
|
||||
}
|
||||
|
||||
uint32_t DeclareType(spv::Op opcode, std::vector<uint32_t> key);
|
||||
uint32_t DeclareDecoratedType(spv::Op opcode, std::vector<uint32_t> key,
|
||||
std::initializer_list<TypeAnnotation> annotations);
|
||||
uint32_t DeclareConstant(spv::Op opcode, std::vector<uint32_t> key);
|
||||
static void AppendString(std::vector<uint32_t>& words, const char* text);
|
||||
|
||||
uint32_t m_next_id = 1;
|
||||
|
||||
@@ -132,12 +132,23 @@ void ValidateNativeProgram(const IR::Program& program) {
|
||||
program.memory_info[index].planning_only;
|
||||
});
|
||||
};
|
||||
const auto indirect_buffer_handle = [&](const IR::Inst& handle) {
|
||||
return program.info.uses_dma && handle.NumArgs() == 4u && !handle.Uses().empty() &&
|
||||
std::ranges::all_of(handle.Uses(), [&](const IR::Use& use) {
|
||||
if (IR::BufferAccessOf(use.user->GetOpcode()) != IR::BufferAccess::Read) {
|
||||
return false;
|
||||
}
|
||||
const auto index = use.user->Flags<IR::MemoryFlags>().index;
|
||||
return index < program.memory_info.size() &&
|
||||
program.memory_info[index].kind == IR::ResourceKind::IndirectBuffer;
|
||||
});
|
||||
};
|
||||
for (const auto* block: program.blocks) {
|
||||
for (const auto& inst: *block) {
|
||||
const auto dense = inst.Flags<uint32_t>();
|
||||
switch (inst.GetOpcode()) {
|
||||
case IR::ValueOpcode::GetBufferResource:
|
||||
if (planning_only_handle(inst)) {
|
||||
if (planning_only_handle(inst) || indirect_buffer_handle(inst)) {
|
||||
break;
|
||||
}
|
||||
if (dense >= program.info.buffers.size()) {
|
||||
@@ -212,6 +223,9 @@ Emitter::SpirvRequirements Emitter::AnalyzeProgramRequirements(const IR::Program
|
||||
Fail(program, "buffer operation has invalid memory metadata");
|
||||
}
|
||||
const auto& memory = program.memory_info[memory_index];
|
||||
if (memory.kind == IR::ResourceKind::IndirectBuffer) {
|
||||
requirements.subgroup_local_invocation_id = true;
|
||||
}
|
||||
if (memory.kind == IR::ResourceKind::Buffer) {
|
||||
if (memory.resource >= program.info.buffers.size()) {
|
||||
Fail(program, "buffer operation has invalid resource metadata");
|
||||
@@ -277,7 +291,8 @@ Emitter::SpirvRequirements Emitter::AnalyzeProgramRequirements(const IR::Program
|
||||
break;
|
||||
}
|
||||
case IR::ValueOpcode::LaneId:
|
||||
requirements.subgroup_local_invocation_id = true;
|
||||
requirements.subgroup_local_invocation_id |=
|
||||
program.stage != ShaderType::TessellationControl;
|
||||
break;
|
||||
case IR::ValueOpcode::ImageQueryLod: requirements.compute_derivatives = true; break;
|
||||
case IR::ValueOpcode::ImageGatherRaw:
|
||||
@@ -306,8 +321,10 @@ std::vector<uint32_t> EmitProgram(const IR::Program& program,
|
||||
using namespace Emitter;
|
||||
|
||||
if (program.stage != ShaderType::Compute && program.stage != ShaderType::Vertex &&
|
||||
program.stage != ShaderType::Pixel && program.stage != ShaderType::Mesh) {
|
||||
Fail(program, "binary SPIR-V emitter supports compute, vertex, and pixel shaders");
|
||||
program.stage != ShaderType::Pixel && program.stage != ShaderType::Mesh &&
|
||||
program.stage != ShaderType::Local && program.stage != ShaderType::TessellationControl &&
|
||||
program.stage != ShaderType::TessellationEvaluation) {
|
||||
Fail(program, "binary SPIR-V emitter received an unsupported shader stage");
|
||||
}
|
||||
if (!program.srt_plan_complete || !program.resource_tracking_complete ||
|
||||
!program.shader_info_complete || !program.binding_layout_complete) {
|
||||
|
||||
@@ -12,14 +12,14 @@ struct Pair {
|
||||
|
||||
Pair ExtractPair(EmitterState& state, uint32_t value) {
|
||||
Pair result {state.builder.AllocateId(), state.builder.AllocateId()};
|
||||
state.builder.AddFunction({spv::OpCompositeExtract, TypeU32(state), result.low, value, 0});
|
||||
state.builder.AddFunction({spv::OpCompositeExtract, TypeU32(state), result.high, value, 1});
|
||||
state.builder.AddFunction(spv::OpCompositeExtract, TypeU32(state), result.low, value, 0);
|
||||
state.builder.AddFunction(spv::OpCompositeExtract, TypeU32(state), result.high, value, 1);
|
||||
return result;
|
||||
}
|
||||
|
||||
uint32_t MakePair(EmitterState& state, uint32_t low, uint32_t high) {
|
||||
const auto result = state.builder.AllocateId();
|
||||
state.builder.AddFunction({spv::OpCompositeConstruct, TypeU64(state), result, low, high});
|
||||
state.builder.AddFunction(spv::OpCompositeConstruct, TypeU64(state), result, low, high);
|
||||
return result;
|
||||
}
|
||||
|
||||
@@ -31,7 +31,7 @@ uint32_t CompareEqual64(EmitterState& state, uint32_t lhs_value, uint32_t rhs_va
|
||||
}
|
||||
|
||||
uint32_t CompareOrdered64(EmitterState& state, uint32_t lhs_value, uint32_t rhs_value,
|
||||
uint32_t high_compare, uint32_t low_compare) {
|
||||
spv::Op high_compare, spv::Op low_compare) {
|
||||
const auto lhs = ExtractPair(state, lhs_value);
|
||||
const auto rhs = ExtractPair(state, rhs_value);
|
||||
const auto high_equal = Binary(state, spv::OpIEqual, TypeBool(state), lhs.high, rhs.high);
|
||||
@@ -51,10 +51,10 @@ uint32_t EmitMulHigh(EmitterState& state, uint32_t lhs, uint32_t rhs, bool signe
|
||||
rhs_operand = Unary(state, spv::OpBitcast, TypeI32(state), rhs);
|
||||
}
|
||||
const auto extended = state.builder.AllocateId();
|
||||
state.builder.AddFunction({signed_value ? spv::OpSMulExtended : spv::OpUMulExtended, pair_type,
|
||||
extended, lhs_operand, rhs_operand});
|
||||
state.builder.AddFunction(signed_value ? spv::OpSMulExtended : spv::OpUMulExtended, pair_type,
|
||||
extended, lhs_operand, rhs_operand);
|
||||
const auto high = state.builder.AllocateId();
|
||||
state.builder.AddFunction({spv::OpCompositeExtract, operand_type, high, extended, 1});
|
||||
state.builder.AddFunction(spv::OpCompositeExtract, operand_type, high, extended, 1);
|
||||
return signed_value ? Unary(state, spv::OpBitcast, TypeU32(state), high) : high;
|
||||
}
|
||||
|
||||
@@ -96,7 +96,7 @@ uint32_t EmitShift64(EmitterState& state, spv::Op opcode, uint32_t value, uint32
|
||||
Select(state, TypeU32(state), at_least_32, fill, high));
|
||||
}
|
||||
|
||||
uint32_t EmitConstantShift64(EmitterState& state, uint32_t opcode, uint32_t value, uint32_t shift) {
|
||||
uint32_t EmitConstantShift64(EmitterState& state, spv::Op opcode, uint32_t value, uint32_t shift) {
|
||||
shift &= 63u;
|
||||
if (shift == 0u) {
|
||||
return value;
|
||||
@@ -173,10 +173,10 @@ uint32_t EmitF32ToU32(EmitterState& state, uint32_t src, bool signed_value) {
|
||||
const auto converted_raw = state.builder.AllocateId();
|
||||
if (signed_value) {
|
||||
const auto converted_i = state.builder.AllocateId();
|
||||
state.builder.AddFunction({spv::OpConvertFToS, TypeI32(state), converted_i, trunc});
|
||||
state.builder.AddFunction({spv::OpBitcast, TypeU32(state), converted_raw, converted_i});
|
||||
state.builder.AddFunction(spv::OpConvertFToS, TypeI32(state), converted_i, trunc);
|
||||
state.builder.AddFunction(spv::OpBitcast, TypeU32(state), converted_raw, converted_i);
|
||||
} else {
|
||||
state.builder.AddFunction({spv::OpConvertFToU, TypeU32(state), converted_raw, trunc});
|
||||
state.builder.AddFunction(spv::OpConvertFToU, TypeU32(state), converted_raw, trunc);
|
||||
}
|
||||
const auto nan = EmitClassifyF32(state, src).nan;
|
||||
if (signed_value) {
|
||||
@@ -218,10 +218,10 @@ uint32_t EmitFindUMsb64(EmitterState& state, uint32_t value) {
|
||||
const auto pair = ExtractPair(state, value);
|
||||
const auto high_i = state.builder.AllocateId();
|
||||
const auto low_i = state.builder.AllocateId();
|
||||
state.builder.AddFunction(
|
||||
{spv::OpExtInst, TypeI32(state), high_i, GlslStd450(state), GLSLstd450FindUMsb, pair.high});
|
||||
state.builder.AddFunction(
|
||||
{spv::OpExtInst, TypeI32(state), low_i, GlslStd450(state), GLSLstd450FindUMsb, pair.low});
|
||||
state.builder.AddFunction(spv::OpExtInst, TypeI32(state), high_i, GlslStd450(state),
|
||||
GLSLstd450FindUMsb, pair.high);
|
||||
state.builder.AddFunction(spv::OpExtInst, TypeI32(state), low_i, GlslStd450(state),
|
||||
GLSLstd450FindUMsb, pair.low);
|
||||
const auto high = Unary(state, spv::OpBitcast, TypeU32(state), high_i);
|
||||
const auto low = Unary(state, spv::OpBitcast, TypeU32(state), low_i);
|
||||
const auto high_nonzero =
|
||||
@@ -260,9 +260,9 @@ uint32_t EmitIAdd64(EmitterState& state, uint32_t lhs_value, uint32_t rhs_value)
|
||||
const auto low_pair = state.builder.AllocateId();
|
||||
const auto low = state.builder.AllocateId();
|
||||
const auto carry = state.builder.AllocateId();
|
||||
state.builder.AddFunction({spv::OpIAddCarry, TypeU32Pair(state), low_pair, lhs.low, rhs.low});
|
||||
state.builder.AddFunction({spv::OpCompositeExtract, TypeU32(state), low, low_pair, 0});
|
||||
state.builder.AddFunction({spv::OpCompositeExtract, TypeU32(state), carry, low_pair, 1});
|
||||
state.builder.AddFunction(spv::OpIAddCarry, TypeU32Pair(state), low_pair, lhs.low, rhs.low);
|
||||
state.builder.AddFunction(spv::OpCompositeExtract, TypeU32(state), low, low_pair, 0);
|
||||
state.builder.AddFunction(spv::OpCompositeExtract, TypeU32(state), carry, low_pair, 1);
|
||||
const auto high0 = Binary(state, spv::OpIAdd, TypeU32(state), lhs.high, rhs.high);
|
||||
const auto high = Binary(state, spv::OpIAdd, TypeU32(state), high0, carry);
|
||||
return MakePair(state, low, high);
|
||||
@@ -278,8 +278,8 @@ uint32_t EmitConvertU8U32(EmitterState& state, uint32_t arg0) {
|
||||
|
||||
uint32_t EmitConvertF16F32(EmitterState& state, uint32_t arg0) {
|
||||
const auto pair = state.builder.AllocateId();
|
||||
state.builder.AddFunction(
|
||||
{spv::OpCompositeConstruct, TypeF32Vector(state, 2), pair, arg0, ConstantF32(state, 0)});
|
||||
state.builder.AddFunction(spv::OpCompositeConstruct, TypeF32Vector(state, 2), pair, arg0,
|
||||
ConstantF32(state, 0));
|
||||
return EmitPackHalf2x16(state, pair);
|
||||
}
|
||||
|
||||
|
||||
@@ -4,35 +4,35 @@ namespace Libs::Graphics::ShaderRecompiler::Spirv::Emitter {
|
||||
|
||||
uint32_t EmitAndConstant(EmitterState& state, uint32_t value, uint32_t mask) {
|
||||
const auto ret = state.builder.AllocateId();
|
||||
state.builder.AddFunction(
|
||||
{spv::OpBitwiseAnd, TypeU32(state), ret, value, ConstantU32(state, mask)});
|
||||
state.builder.AddFunction(spv::OpBitwiseAnd, TypeU32(state), ret, value,
|
||||
ConstantU32(state, mask));
|
||||
return ret;
|
||||
}
|
||||
|
||||
uint32_t EmitShiftRightConstant(EmitterState& state, uint32_t value, uint32_t shift) {
|
||||
const auto ret = state.builder.AllocateId();
|
||||
state.builder.AddFunction(
|
||||
{spv::OpShiftRightLogical, TypeU32(state), ret, value, ConstantU32(state, shift)});
|
||||
state.builder.AddFunction(spv::OpShiftRightLogical, TypeU32(state), ret, value,
|
||||
ConstantU32(state, shift));
|
||||
return ret;
|
||||
}
|
||||
|
||||
uint32_t EmitCompareU32Constant(EmitterState& state, uint32_t opcode, uint32_t value,
|
||||
uint32_t EmitCompareU32Constant(EmitterState& state, spv::Op opcode, uint32_t value,
|
||||
uint32_t constant) {
|
||||
const auto ret = state.builder.AllocateId();
|
||||
state.builder.AddFunction({opcode, TypeBool(state), ret, value, ConstantU32(state, constant)});
|
||||
state.builder.AddFunction(opcode, TypeBool(state), ret, value, ConstantU32(state, constant));
|
||||
return ret;
|
||||
}
|
||||
|
||||
uint32_t EmitSubConstantMinusU32(EmitterState& state, uint32_t constant, uint32_t value) {
|
||||
const auto ret = state.builder.AllocateId();
|
||||
state.builder.AddFunction(
|
||||
{spv::OpISub, TypeU32(state), ret, ConstantU32(state, constant), value});
|
||||
state.builder.AddFunction(spv::OpISub, TypeU32(state), ret, ConstantU32(state, constant),
|
||||
value);
|
||||
return ret;
|
||||
}
|
||||
|
||||
uint32_t EmitF32ToF16RtzBits(EmitterState& state, uint32_t f32) {
|
||||
const auto bits = state.builder.AllocateId();
|
||||
state.builder.AddFunction({spv::OpBitcast, TypeU32(state), bits, f32});
|
||||
state.builder.AddFunction(spv::OpBitcast, TypeU32(state), bits, f32);
|
||||
|
||||
const auto sign = EmitAndConstant(state, EmitShiftRightConstant(state, bits, 16), 0x8000u);
|
||||
const auto exp = EmitAndConstant(state, EmitShiftRightConstant(state, bits, 23), 0xffu);
|
||||
@@ -43,13 +43,12 @@ uint32_t EmitF32ToF16RtzBits(EmitterState& state, uint32_t f32) {
|
||||
const auto normal_mant = EmitShiftRightConstant(state, mant, 13);
|
||||
const auto normal_payload = state.builder.AllocateId();
|
||||
const auto normal = state.builder.AllocateId();
|
||||
state.builder.AddFunction(
|
||||
{spv::OpISub, TypeU32(state), half_exp, exp, ConstantU32(state, 112)});
|
||||
state.builder.AddFunction(
|
||||
{spv::OpShiftLeftLogical, TypeU32(state), normal_exp, half_exp, ConstantU32(state, 10)});
|
||||
state.builder.AddFunction(
|
||||
{spv::OpBitwiseOr, TypeU32(state), normal_payload, normal_exp, normal_mant});
|
||||
state.builder.AddFunction({spv::OpBitwiseOr, TypeU32(state), normal, sign, normal_payload});
|
||||
state.builder.AddFunction(spv::OpISub, TypeU32(state), half_exp, exp, ConstantU32(state, 112));
|
||||
state.builder.AddFunction(spv::OpShiftLeftLogical, TypeU32(state), normal_exp, half_exp,
|
||||
ConstantU32(state, 10));
|
||||
state.builder.AddFunction(spv::OpBitwiseOr, TypeU32(state), normal_payload, normal_exp,
|
||||
normal_mant);
|
||||
state.builder.AddFunction(spv::OpBitwiseOr, TypeU32(state), normal, sign, normal_payload);
|
||||
|
||||
const auto mant_with_hidden = EmitOrU32(state, mant, ConstantU32(state, 0x00800000u));
|
||||
const auto raw_sub_shift = EmitSubConstantMinusU32(state, 126, exp);
|
||||
@@ -61,9 +60,9 @@ uint32_t EmitF32ToF16RtzBits(EmitterState& state, uint32_t f32) {
|
||||
EmitSelectValueU32(state, exp_gt_112, ConstantU32(state, 14), sub_shift_low);
|
||||
const auto sub_mant = state.builder.AllocateId();
|
||||
const auto subnormal = state.builder.AllocateId();
|
||||
state.builder.AddFunction(
|
||||
{spv::OpShiftRightLogical, TypeU32(state), sub_mant, mant_with_hidden, sub_shift});
|
||||
state.builder.AddFunction({spv::OpBitwiseOr, TypeU32(state), subnormal, sign, sub_mant});
|
||||
state.builder.AddFunction(spv::OpShiftRightLogical, TypeU32(state), sub_mant, mant_with_hidden,
|
||||
sub_shift);
|
||||
state.builder.AddFunction(spv::OpBitwiseOr, TypeU32(state), subnormal, sign, sub_mant);
|
||||
|
||||
const auto nan_payload =
|
||||
EmitOrU32(state, EmitShiftRightConstant(state, mant, 13), ConstantU32(state, 0x0200u));
|
||||
@@ -86,18 +85,18 @@ uint32_t EmitF32ToF16RtzBits(EmitterState& state, uint32_t f32) {
|
||||
uint32_t EmitMinMaxU32Value(EmitterState& state, uint32_t lhs, uint32_t rhs, bool max_value) {
|
||||
const auto cond = state.builder.AllocateId();
|
||||
const auto ret = state.builder.AllocateId();
|
||||
state.builder.AddFunction(
|
||||
{max_value ? spv::OpUGreaterThan : spv::OpULessThan, TypeBool(state), cond, lhs, rhs});
|
||||
state.builder.AddFunction({spv::OpSelect, TypeU32(state), ret, cond, lhs, rhs});
|
||||
state.builder.AddFunction(max_value ? spv::OpUGreaterThan : spv::OpULessThan, TypeBool(state),
|
||||
cond, lhs, rhs);
|
||||
state.builder.AddFunction(spv::OpSelect, TypeU32(state), ret, cond, lhs, rhs);
|
||||
return ret;
|
||||
}
|
||||
|
||||
uint32_t EmitMinMaxI32Value(EmitterState& state, uint32_t lhs, uint32_t rhs, bool max_value) {
|
||||
const auto cond = state.builder.AllocateId();
|
||||
const auto ret = state.builder.AllocateId();
|
||||
state.builder.AddFunction(
|
||||
{max_value ? spv::OpSGreaterThan : spv::OpSLessThan, TypeBool(state), cond, lhs, rhs});
|
||||
state.builder.AddFunction({spv::OpSelect, TypeU32(state), ret, cond, lhs, rhs});
|
||||
state.builder.AddFunction(max_value ? spv::OpSGreaterThan : spv::OpSLessThan, TypeBool(state),
|
||||
cond, lhs, rhs);
|
||||
state.builder.AddFunction(spv::OpSelect, TypeU32(state), ret, cond, lhs, rhs);
|
||||
return ret;
|
||||
}
|
||||
|
||||
@@ -184,8 +183,8 @@ uint32_t EmitMinMaxF32Value(EmitterState& state, uint32_t lhs, uint32_t rhs, boo
|
||||
const auto rhs_class = EmitClassifyF32(state, rhs);
|
||||
|
||||
const auto numeric_cond = state.builder.AllocateId();
|
||||
state.builder.AddFunction({max_value ? spv::OpFOrdGreaterThanEqual : spv::OpFOrdLessThan,
|
||||
TypeBool(state), numeric_cond, lhs, rhs});
|
||||
state.builder.AddFunction(max_value ? spv::OpFOrdGreaterThanEqual : spv::OpFOrdLessThan,
|
||||
TypeBool(state), numeric_cond, lhs, rhs);
|
||||
const auto ordered_bits =
|
||||
EmitSelectValueU32(state, numeric_cond, lhs_class.bits, rhs_class.bits);
|
||||
|
||||
@@ -209,18 +208,18 @@ uint32_t EmitFlushF32DenormToSignedZero(EmitterState& state, uint32_t value) {
|
||||
const auto flush = state.builder.AllocateId();
|
||||
const auto selected = state.builder.AllocateId();
|
||||
const auto ret = state.builder.AllocateId();
|
||||
state.builder.AddFunction({spv::OpBitcast, TypeU32(state), bits, value});
|
||||
state.builder.AddFunction(
|
||||
{spv::OpBitwiseAnd, TypeU32(state), abs_bits, bits, ConstantU32(state, 0x7fffffffu)});
|
||||
state.builder.AddFunction(
|
||||
{spv::OpBitwiseAnd, TypeU32(state), sign_bits, bits, ConstantU32(state, 0x80000000u)});
|
||||
state.builder.AddFunction(
|
||||
{spv::OpINotEqual, TypeBool(state), non_zero, abs_bits, ConstantU32(state, 0)});
|
||||
state.builder.AddFunction(
|
||||
{spv::OpULessThan, TypeBool(state), subnormal, abs_bits, ConstantU32(state, 0x00800000u)});
|
||||
state.builder.AddFunction({spv::OpLogicalAnd, TypeBool(state), flush, non_zero, subnormal});
|
||||
state.builder.AddFunction({spv::OpSelect, TypeU32(state), selected, flush, sign_bits, bits});
|
||||
state.builder.AddFunction({spv::OpBitcast, TypeF32(state), ret, selected});
|
||||
state.builder.AddFunction(spv::OpBitcast, TypeU32(state), bits, value);
|
||||
state.builder.AddFunction(spv::OpBitwiseAnd, TypeU32(state), abs_bits, bits,
|
||||
ConstantU32(state, 0x7fffffffu));
|
||||
state.builder.AddFunction(spv::OpBitwiseAnd, TypeU32(state), sign_bits, bits,
|
||||
ConstantU32(state, 0x80000000u));
|
||||
state.builder.AddFunction(spv::OpINotEqual, TypeBool(state), non_zero, abs_bits,
|
||||
ConstantU32(state, 0));
|
||||
state.builder.AddFunction(spv::OpULessThan, TypeBool(state), subnormal, abs_bits,
|
||||
ConstantU32(state, 0x00800000u));
|
||||
state.builder.AddFunction(spv::OpLogicalAnd, TypeBool(state), flush, non_zero, subnormal);
|
||||
state.builder.AddFunction(spv::OpSelect, TypeU32(state), selected, flush, sign_bits, bits);
|
||||
state.builder.AddFunction(spv::OpBitcast, TypeF32(state), ret, selected);
|
||||
return ret;
|
||||
}
|
||||
|
||||
@@ -232,35 +231,35 @@ uint32_t EmitTrigCycleF32(EmitterState& state, uint32_t src, bool preserve_signe
|
||||
const auto finite = state.builder.AllocateId();
|
||||
const auto large_finite = state.builder.AllocateId();
|
||||
const auto reduced = state.builder.AllocateId();
|
||||
state.builder.AddFunction(
|
||||
{spv::OpExtInst, TypeF32(state), fract, GlslStd450(state), GLSLstd450Fract, src});
|
||||
state.builder.AddFunction({spv::OpBitcast, TypeU32(state), bits, src});
|
||||
state.builder.AddFunction(
|
||||
{spv::OpBitwiseAnd, TypeU32(state), abs_bits, bits, ConstantU32(state, 0x7fffffffu)});
|
||||
state.builder.AddFunction({spv::OpUGreaterThanEqual, TypeBool(state), large, abs_bits,
|
||||
ConstantU32(state, 0x4b000000u)});
|
||||
state.builder.AddFunction(
|
||||
{spv::OpULessThan, TypeBool(state), finite, abs_bits, ConstantU32(state, 0x7f800000u)});
|
||||
state.builder.AddFunction({spv::OpLogicalAnd, TypeBool(state), large_finite, large, finite});
|
||||
state.builder.AddFunction(
|
||||
{spv::OpSelect, TypeF32(state), reduced, large_finite, ConstantF32(state, 0), fract});
|
||||
state.builder.AddFunction(spv::OpExtInst, TypeF32(state), fract, GlslStd450(state),
|
||||
GLSLstd450Fract, src);
|
||||
state.builder.AddFunction(spv::OpBitcast, TypeU32(state), bits, src);
|
||||
state.builder.AddFunction(spv::OpBitwiseAnd, TypeU32(state), abs_bits, bits,
|
||||
ConstantU32(state, 0x7fffffffu));
|
||||
state.builder.AddFunction(spv::OpUGreaterThanEqual, TypeBool(state), large, abs_bits,
|
||||
ConstantU32(state, 0x4b000000u));
|
||||
state.builder.AddFunction(spv::OpULessThan, TypeBool(state), finite, abs_bits,
|
||||
ConstantU32(state, 0x7f800000u));
|
||||
state.builder.AddFunction(spv::OpLogicalAnd, TypeBool(state), large_finite, large, finite);
|
||||
state.builder.AddFunction(spv::OpSelect, TypeF32(state), reduced, large_finite,
|
||||
ConstantF32(state, 0), fract);
|
||||
if (!preserve_signed_zero) {
|
||||
return reduced;
|
||||
}
|
||||
const auto zero = state.builder.AllocateId();
|
||||
const auto ret = state.builder.AllocateId();
|
||||
state.builder.AddFunction(
|
||||
{spv::OpIEqual, TypeBool(state), zero, abs_bits, ConstantU32(state, 0)});
|
||||
state.builder.AddFunction({spv::OpSelect, TypeF32(state), ret, zero, src, reduced});
|
||||
state.builder.AddFunction(spv::OpIEqual, TypeBool(state), zero, abs_bits,
|
||||
ConstantU32(state, 0));
|
||||
state.builder.AddFunction(spv::OpSelect, TypeF32(state), ret, zero, src, reduced);
|
||||
return ret;
|
||||
}
|
||||
|
||||
uint32_t EmitF16BitsToF32(EmitterState& state, uint32_t bits) {
|
||||
const auto unpacked = state.builder.AllocateId();
|
||||
const auto ret = state.builder.AllocateId();
|
||||
state.builder.AddFunction({spv::OpExtInst, TypeF32Vector(state, 2), unpacked, GlslStd450(state),
|
||||
GLSLstd450UnpackHalf2x16, bits});
|
||||
state.builder.AddFunction({spv::OpCompositeExtract, TypeF32(state), ret, unpacked, 0});
|
||||
state.builder.AddFunction(spv::OpExtInst, TypeF32Vector(state, 2), unpacked, GlslStd450(state),
|
||||
GLSLstd450UnpackHalf2x16, bits);
|
||||
state.builder.AddFunction(spv::OpCompositeExtract, TypeF32(state), ret, unpacked, 0);
|
||||
return ret;
|
||||
}
|
||||
|
||||
|
||||
@@ -82,8 +82,8 @@ uint32_t DescriptorElementPointer(EmitterState& state, uint32_t result_ptr_type,
|
||||
ExitDescriptorBindingFailure(state, kind, resource, variable_name);
|
||||
}
|
||||
const auto pointer = state.builder.AllocateId();
|
||||
state.builder.AddFunction({spv::OpAccessChain, result_ptr_type, pointer, variable_id,
|
||||
ConstantU32(state, array_index)});
|
||||
state.builder.AddFunction(spv::OpAccessChain, result_ptr_type, pointer, variable_id,
|
||||
ConstantU32(state, array_index));
|
||||
return pointer;
|
||||
}
|
||||
|
||||
@@ -109,8 +109,7 @@ uint32_t ImageScalarType(EmitterState& state, Prospero::TextureNumericClass nume
|
||||
|
||||
uint32_t ImageVectorType(EmitterState& state, Prospero::TextureNumericClass numeric_class,
|
||||
uint32_t components) {
|
||||
return state.builder.Type(spv::OpTypeVector,
|
||||
{ImageScalarType(state, numeric_class), components});
|
||||
return state.builder.Type(spv::OpTypeVector, ImageScalarType(state, numeric_class), components);
|
||||
}
|
||||
|
||||
uint32_t ImageType(EmitterState& state, const IR::ImageResource& image) {
|
||||
@@ -131,10 +130,9 @@ uint32_t ImageType(EmitterState& state, const IR::ImageResource& image) {
|
||||
EXIT("invalid image resource class");
|
||||
}
|
||||
const auto& info = ImageDimensionInfoFor(image.dimension);
|
||||
return state.builder.Type(spv::OpTypeImage,
|
||||
{ImageScalarType(state, image.numeric_class), info.spirv_dimension,
|
||||
image.depth_compare ? 1u : 0u, info.arrayed, info.multisampled,
|
||||
sampled, format});
|
||||
return state.builder.Type(spv::OpTypeImage, ImageScalarType(state, image.numeric_class),
|
||||
info.spirv_dimension, image.depth_compare ? 1u : 0u, info.arrayed,
|
||||
info.multisampled, sampled, format);
|
||||
}
|
||||
|
||||
uint32_t ImageViewSizeType(EmitterState& state, ImageDimension dimension) {
|
||||
@@ -154,12 +152,12 @@ uint32_t LoadSampledImageDescriptor(EmitterState& state, uint32_t resource) {
|
||||
const auto array_index = ResourceForDescriptor(state, *kind, resource);
|
||||
const auto variable = state.image_variables[IR::ImageBindingIndex(*kind)];
|
||||
const auto pointer_type = state.builder.Type(
|
||||
spv::OpTypePointer, {spv::StorageClassUniformConstant, ImageType(state, image_resource)});
|
||||
spv::OpTypePointer, spv::StorageClassUniformConstant, ImageType(state, image_resource));
|
||||
const auto pointer =
|
||||
DescriptorElementPointer(state, pointer_type, variable, array_index, *kind, resource,
|
||||
"sampled image descriptor array was not emitted");
|
||||
const auto image = state.builder.AllocateId();
|
||||
state.builder.AddFunction({spv::OpLoad, ImageType(state, image_resource), image, pointer});
|
||||
state.builder.AddFunction(spv::OpLoad, ImageType(state, image_resource), image, pointer);
|
||||
return image;
|
||||
}
|
||||
|
||||
@@ -168,12 +166,12 @@ uint32_t LoadSamplerDescriptor(EmitterState& state, uint32_t sampler) {
|
||||
ResourceForDescriptor(state, IR::DescriptorBindingKind::Samplers, sampler);
|
||||
const auto sampler_type = state.builder.Type(spv::OpTypeSampler);
|
||||
const auto pointer_type =
|
||||
state.builder.Type(spv::OpTypePointer, {spv::StorageClassUniformConstant, sampler_type});
|
||||
state.builder.Type(spv::OpTypePointer, spv::StorageClassUniformConstant, sampler_type);
|
||||
const auto pointer = DescriptorElementPointer(
|
||||
state, pointer_type, state.sampler_variable, array_index,
|
||||
IR::DescriptorBindingKind::Samplers, sampler, "sampler descriptor array was not emitted");
|
||||
const auto sampler_id = state.builder.AllocateId();
|
||||
state.builder.AddFunction({spv::OpLoad, sampler_type, sampler_id, pointer});
|
||||
state.builder.AddFunction(spv::OpLoad, sampler_type, sampler_id, pointer);
|
||||
return sampler_id;
|
||||
}
|
||||
|
||||
@@ -183,9 +181,8 @@ uint32_t MakeSampledImage(EmitterState& state, uint32_t resource, uint32_t sampl
|
||||
const auto sampler_id = LoadSamplerDescriptor(state, sampler);
|
||||
const auto sampled_image = state.builder.AllocateId();
|
||||
const auto sampled_type =
|
||||
state.builder.Type(spv::OpTypeSampledImage, {ImageType(state, image_resource)});
|
||||
state.builder.AddFunction(
|
||||
{spv::OpSampledImage, sampled_type, sampled_image, image, sampler_id});
|
||||
state.builder.Type(spv::OpTypeSampledImage, ImageType(state, image_resource));
|
||||
state.builder.AddFunction(spv::OpSampledImage, sampled_type, sampled_image, image, sampler_id);
|
||||
return sampled_image;
|
||||
}
|
||||
|
||||
@@ -194,9 +191,9 @@ uint32_t StorageImageDescriptorPointer(EmitterState& state, uint32_t resource) {
|
||||
EXIT_IF(image.resource_class != IR::ImageResourceClass::Storage);
|
||||
const auto kind = IR::DescriptorBindingForImage(image);
|
||||
EXIT_IF(!kind.has_value());
|
||||
const auto array_index = ResourceForDescriptor(state, *kind, resource);
|
||||
const auto array_index = ResourceForDescriptor(state, *kind, resource);
|
||||
const auto pointer_type = state.builder.Type(
|
||||
spv::OpTypePointer, {spv::StorageClassUniformConstant, ImageType(state, image)});
|
||||
spv::OpTypePointer, spv::StorageClassUniformConstant, ImageType(state, image));
|
||||
const auto variable = state.image_variables[IR::ImageBindingIndex(*kind)];
|
||||
return DescriptorElementPointer(state, pointer_type, variable, array_index, *kind, resource,
|
||||
"storage image descriptor array was not emitted");
|
||||
@@ -214,18 +211,18 @@ void EmitStorageImageWrite(EmitterState& state, uint32_t resource, uint32_t mip_
|
||||
const auto array_index = ResourceForDescriptor(state, *kind, resource);
|
||||
const auto image_type = ImageType(state, image);
|
||||
const auto pointer_type =
|
||||
state.builder.Type(spv::OpTypePointer, {spv::StorageClassUniformConstant, image_type});
|
||||
state.builder.Type(spv::OpTypePointer, spv::StorageClassUniformConstant, image_type);
|
||||
const auto variable = state.image_variables[IR::ImageBindingIndex(*kind)];
|
||||
const auto LoadAt = [&](uint32_t array_index) {
|
||||
const auto pointer =
|
||||
DescriptorElementPointer(state, pointer_type, variable, array_index, *kind, resource,
|
||||
"storage image descriptor array was not emitted");
|
||||
const auto descriptor = state.builder.AllocateId();
|
||||
state.builder.AddFunction({spv::OpLoad, image_type, descriptor, pointer});
|
||||
state.builder.AddFunction(spv::OpLoad, image_type, descriptor, pointer);
|
||||
return descriptor;
|
||||
};
|
||||
if (image.mip_mode != IR::ImageMipMode::DynamicStorage) {
|
||||
state.builder.AddFunction({spv::OpImageWrite, LoadAt(array_index), coord, texel});
|
||||
state.builder.AddFunction(spv::OpImageWrite, LoadAt(array_index), coord, texel);
|
||||
return;
|
||||
}
|
||||
if (image.mip_count == 0u) {
|
||||
@@ -241,19 +238,22 @@ void EmitStorageImageWrite(EmitterState& state, uint32_t resource, uint32_t mip_
|
||||
words.push_back(mip);
|
||||
words.push_back(labels[mip]);
|
||||
}
|
||||
state.builder.AddFunction({spv::OpSelectionMerge, merge_label, spv::SelectionControlMaskNone});
|
||||
state.builder.AddFunction(spv::OpSelectionMerge, merge_label, spv::SelectionControlMaskNone);
|
||||
state.builder.AddFunction(words);
|
||||
for (uint32_t mip = 0; mip < image.mip_count; mip++) {
|
||||
EmitLabel(state, labels[mip]);
|
||||
state.builder.AddFunction({spv::OpImageWrite, LoadAt(array_index + mip), coord, texel});
|
||||
state.builder.AddFunction({spv::OpBranch, merge_label});
|
||||
state.builder.AddFunction(spv::OpImageWrite, LoadAt(array_index + mip), coord, texel);
|
||||
state.builder.AddFunction(spv::OpBranch, merge_label);
|
||||
}
|
||||
EmitLabel(state, merge_label);
|
||||
}
|
||||
|
||||
spv::ExecutionModel ExecutionModelForStage(ShaderType stage) {
|
||||
switch (stage) {
|
||||
case ShaderType::Local:
|
||||
case ShaderType::Vertex: return spv::ExecutionModelVertex;
|
||||
case ShaderType::TessellationControl: return spv::ExecutionModelTessellationControl;
|
||||
case ShaderType::TessellationEvaluation: return spv::ExecutionModelTessellationEvaluation;
|
||||
case ShaderType::Mesh: return spv::ExecutionModelMeshEXT; // MeshEXT
|
||||
case ShaderType::Pixel: return spv::ExecutionModelFragment;
|
||||
default: return spv::ExecutionModelGLCompute;
|
||||
|
||||
@@ -1,6 +1,9 @@
|
||||
#include "graphics/shader/recompiler/backend/spirv/spirvEmitterInstructions.h"
|
||||
|
||||
#include "common/logging/log.h"
|
||||
|
||||
#include <algorithm>
|
||||
#include <atomic>
|
||||
|
||||
namespace Libs::Graphics::ShaderRecompiler::Spirv::Emitter {
|
||||
namespace {
|
||||
@@ -38,47 +41,60 @@ uint32_t EmitBuiltinU32(EmitterState& state, IR::StageInputKind kind, uint32_t c
|
||||
return EmitAddU32(state, local,
|
||||
EmitBinaryU32(state, spv::OpIMul, group, ConstantU32(state, size)));
|
||||
}
|
||||
const auto variable = InputVariableForKind(state, kind);
|
||||
const bool centroid = kind == IR::StageInputKind::BaryCoordSmoothCentroid;
|
||||
const auto variable = InputVariableForKind(
|
||||
state, centroid ? IR::StageInputKind::BaryCoordSmooth : kind);
|
||||
if (variable == 0) {
|
||||
return ConstantU32(state, 0);
|
||||
}
|
||||
if (kind == IR::StageInputKind::FrontFacing) {
|
||||
const auto value = state.builder.AllocateId();
|
||||
const auto bits = state.builder.AllocateId();
|
||||
state.builder.AddFunction({spv::OpLoad, TypeBool(state), value, variable});
|
||||
state.builder.AddFunction({spv::OpSelect, TypeU32(state), bits, value,
|
||||
ConstantU32(state, 1), ConstantU32(state, 0)});
|
||||
state.builder.AddFunction(spv::OpLoad, TypeBool(state), value, variable);
|
||||
// PS5 initializes v_front_face with float +1.0/-1.0 bits.
|
||||
state.builder.AddFunction(spv::OpSelect, TypeU32(state), bits, value,
|
||||
ConstantU32(state, 0x3f800000u), ConstantU32(state, 0xbf800000u));
|
||||
return bits;
|
||||
}
|
||||
if (kind == IR::StageInputKind::VertexIndex || kind == IR::StageInputKind::InstanceIndex ||
|
||||
kind == IR::StageInputKind::InvocationId || kind == IR::StageInputKind::PrimitiveId ||
|
||||
kind == IR::StageInputKind::Layer || kind == IR::StageInputKind::SampleId) {
|
||||
const auto value = state.builder.AllocateId();
|
||||
const auto bits = state.builder.AllocateId();
|
||||
state.builder.AddFunction({spv::OpLoad, TypeI32(state), value, variable});
|
||||
state.builder.AddFunction({spv::OpBitcast, TypeU32(state), bits, value});
|
||||
state.builder.AddFunction(spv::OpLoad, TypeI32(state), value, variable);
|
||||
state.builder.AddFunction(spv::OpBitcast, TypeU32(state), bits, value);
|
||||
return bits;
|
||||
}
|
||||
if (kind == IR::StageInputKind::FragCoord) {
|
||||
if (kind == IR::StageInputKind::FragCoord || kind == IR::StageInputKind::TessCoord) {
|
||||
const auto pointer = state.builder.AllocateId();
|
||||
const auto value = state.builder.AllocateId();
|
||||
const auto bits = state.builder.AllocateId();
|
||||
state.builder.AddFunction({spv::OpAccessChain,
|
||||
TypePointer(state, spv::StorageClassInput, TypeF32(state)),
|
||||
pointer, variable, ConstantU32(state, component)});
|
||||
state.builder.AddFunction({spv::OpLoad, TypeF32(state), value, pointer});
|
||||
state.builder.AddFunction({spv::OpBitcast, TypeU32(state), bits, value});
|
||||
state.builder.AddFunction(spv::OpAccessChain,
|
||||
TypePointer(state, spv::StorageClassInput, TypeF32(state)),
|
||||
pointer, variable, ConstantU32(state, component));
|
||||
state.builder.AddFunction(spv::OpLoad, TypeF32(state), value, pointer);
|
||||
state.builder.AddFunction(spv::OpBitcast, TypeU32(state), bits, value);
|
||||
return bits;
|
||||
}
|
||||
if (kind == IR::StageInputKind::BaryCoordSmooth ||
|
||||
if (centroid || kind == IR::StageInputKind::BaryCoordSmooth ||
|
||||
kind == IR::StageInputKind::BaryCoordNoPerspective) {
|
||||
const auto pointer = state.builder.AllocateId();
|
||||
const auto value = state.builder.AllocateId();
|
||||
const auto bits = state.builder.AllocateId();
|
||||
state.builder.AddFunction({spv::OpAccessChain,
|
||||
TypePointer(state, spv::StorageClassInput, TypeF32(state)),
|
||||
pointer, variable, ConstantU32(state, component + 1u)});
|
||||
state.builder.AddFunction({spv::OpLoad, TypeF32(state), value, pointer});
|
||||
state.builder.AddFunction({spv::OpBitcast, TypeU32(state), bits, value});
|
||||
if (centroid) {
|
||||
const auto coordinates = state.builder.AllocateId();
|
||||
state.builder.RequireCapability(spv::CapabilityInterpolationFunction);
|
||||
state.builder.AddFunction(spv::OpExtInst, TypeF32Vector(state, 3), coordinates,
|
||||
GlslStd450(state), GLSLstd450InterpolateAtCentroid, variable);
|
||||
state.builder.AddFunction(spv::OpCompositeExtract, TypeF32(state), value,
|
||||
coordinates, component + 1u);
|
||||
} else {
|
||||
const auto pointer = state.builder.AllocateId();
|
||||
state.builder.AddFunction(spv::OpAccessChain,
|
||||
TypePointer(state, spv::StorageClassInput, TypeF32(state)),
|
||||
pointer, variable, ConstantU32(state, component + 1u));
|
||||
state.builder.AddFunction(spv::OpLoad, TypeF32(state), value, pointer);
|
||||
}
|
||||
state.builder.AddFunction(spv::OpBitcast, TypeU32(state), bits, value);
|
||||
return bits;
|
||||
}
|
||||
return EmitInputComponentU32(state, kind, component);
|
||||
@@ -99,37 +115,37 @@ uint32_t EmitDppWriteCondition(ValueEmitContext& ctx, const IR::DppMoveFlags& fl
|
||||
const auto bank_ok = state.builder.AllocateId();
|
||||
const auto row_ok = state.builder.AllocateId();
|
||||
const auto masks_ok = state.builder.AllocateId();
|
||||
state.builder.AddFunction(
|
||||
{spv::OpShiftRightLogical, TypeU32(state), bank_shift, lane, ConstantU32(state, 2)});
|
||||
state.builder.AddFunction(
|
||||
{spv::OpShiftRightLogical, TypeU32(state), row_shift, lane, ConstantU32(state, 4)});
|
||||
state.builder.AddFunction(
|
||||
{spv::OpBitwiseAnd, TypeU32(state), bank, bank_shift, ConstantU32(state, 3)});
|
||||
state.builder.AddFunction(
|
||||
{spv::OpBitwiseAnd, TypeU32(state), row, row_shift, ConstantU32(state, 3)});
|
||||
state.builder.AddFunction(
|
||||
{spv::OpShiftLeftLogical, TypeU32(state), bank_bit, ConstantU32(state, 1), bank});
|
||||
state.builder.AddFunction(
|
||||
{spv::OpShiftLeftLogical, TypeU32(state), row_bit, ConstantU32(state, 1), row});
|
||||
state.builder.AddFunction({spv::OpBitwiseAnd, TypeU32(state), bank_hit,
|
||||
ConstantU32(state, flags.bank_mask), bank_bit});
|
||||
state.builder.AddFunction(
|
||||
{spv::OpBitwiseAnd, TypeU32(state), row_hit, ConstantU32(state, flags.row_mask), row_bit});
|
||||
state.builder.AddFunction(
|
||||
{spv::OpINotEqual, TypeBool(state), bank_ok, bank_hit, ConstantU32(state, 0)});
|
||||
state.builder.AddFunction(
|
||||
{spv::OpINotEqual, TypeBool(state), row_ok, row_hit, ConstantU32(state, 0)});
|
||||
state.builder.AddFunction({spv::OpLogicalAnd, TypeBool(state), masks_ok, bank_ok, row_ok});
|
||||
state.builder.AddFunction(spv::OpShiftRightLogical, TypeU32(state), bank_shift, lane,
|
||||
ConstantU32(state, 2));
|
||||
state.builder.AddFunction(spv::OpShiftRightLogical, TypeU32(state), row_shift, lane,
|
||||
ConstantU32(state, 4));
|
||||
state.builder.AddFunction(spv::OpBitwiseAnd, TypeU32(state), bank, bank_shift,
|
||||
ConstantU32(state, 3));
|
||||
state.builder.AddFunction(spv::OpBitwiseAnd, TypeU32(state), row, row_shift,
|
||||
ConstantU32(state, 3));
|
||||
state.builder.AddFunction(spv::OpShiftLeftLogical, TypeU32(state), bank_bit,
|
||||
ConstantU32(state, 1), bank);
|
||||
state.builder.AddFunction(spv::OpShiftLeftLogical, TypeU32(state), row_bit,
|
||||
ConstantU32(state, 1), row);
|
||||
state.builder.AddFunction(spv::OpBitwiseAnd, TypeU32(state), bank_hit,
|
||||
ConstantU32(state, flags.bank_mask), bank_bit);
|
||||
state.builder.AddFunction(spv::OpBitwiseAnd, TypeU32(state), row_hit,
|
||||
ConstantU32(state, flags.row_mask), row_bit);
|
||||
state.builder.AddFunction(spv::OpINotEqual, TypeBool(state), bank_ok, bank_hit,
|
||||
ConstantU32(state, 0));
|
||||
state.builder.AddFunction(spv::OpINotEqual, TypeBool(state), row_ok, row_hit,
|
||||
ConstantU32(state, 0));
|
||||
state.builder.AddFunction(spv::OpLogicalAnd, TypeBool(state), masks_ok, bank_ok, row_ok);
|
||||
uint32_t writable = masks_ok;
|
||||
if (!flags.bound_control) {
|
||||
const auto target = EmitDppTargetLane(state, flags.control);
|
||||
const auto target = EmitDppTargetLane(state, flags);
|
||||
const auto bounded = state.builder.AllocateId();
|
||||
state.builder.AddFunction(
|
||||
{spv::OpLogicalAnd, TypeBool(state), bounded, writable, target.valid});
|
||||
state.builder.AddFunction(spv::OpLogicalAnd, TypeBool(state), bounded, writable,
|
||||
target.valid);
|
||||
writable = bounded;
|
||||
}
|
||||
const auto result = state.builder.AllocateId();
|
||||
state.builder.AddFunction({spv::OpLogicalAnd, TypeBool(state), result, exec, writable});
|
||||
state.builder.AddFunction(spv::OpLogicalAnd, TypeBool(state), result, exec, writable);
|
||||
return result;
|
||||
}
|
||||
|
||||
@@ -138,17 +154,16 @@ uint32_t EmitAttribute(EmitterState& state, uint32_t attr, uint32_t chan) {
|
||||
if (input == nullptr || input->variable_id == 0) {
|
||||
return ConstantU32(state, 0);
|
||||
}
|
||||
if (state.program.stage == ShaderType::Vertex) {
|
||||
if (state.program.stage == ShaderType::Vertex || state.program.stage == ShaderType::Local) {
|
||||
return EmitVertexParameterComponentU32(state, *input, chan & 3u);
|
||||
}
|
||||
const auto load_per_vertex = [&](uint32_t vertex) {
|
||||
const auto pointer = state.builder.AllocateId();
|
||||
const auto value = state.builder.AllocateId();
|
||||
state.builder.AddFunction({spv::OpAccessChain,
|
||||
TypePointer(state, spv::StorageClassInput, TypeF32(state)),
|
||||
pointer, input->variable_id, ConstantU32(state, vertex),
|
||||
ConstantU32(state, chan & 3u)});
|
||||
state.builder.AddFunction({spv::OpLoad, TypeF32(state), value, pointer});
|
||||
state.builder.AddFunction(
|
||||
spv::OpAccessChain, TypePointer(state, spv::StorageClassInput, TypeF32(state)), pointer,
|
||||
input->variable_id, ConstantU32(state, vertex), ConstantU32(state, chan & 3u));
|
||||
state.builder.AddFunction(spv::OpLoad, TypeF32(state), value, pointer);
|
||||
return value;
|
||||
};
|
||||
if (input->per_vertex) {
|
||||
@@ -161,31 +176,31 @@ uint32_t EmitAttribute(EmitterState& state, uint32_t attr, uint32_t chan) {
|
||||
const auto pointer = state.builder.AllocateId();
|
||||
const auto weight = state.builder.AllocateId();
|
||||
const auto product = state.builder.AllocateId();
|
||||
state.builder.AddFunction({spv::OpAccessChain,
|
||||
TypePointer(state, spv::StorageClassInput, TypeF32(state)),
|
||||
pointer, barycentric, ConstantU32(state, vertex)});
|
||||
state.builder.AddFunction({spv::OpLoad, TypeF32(state), weight, pointer});
|
||||
state.builder.AddFunction(
|
||||
{spv::OpFMul, TypeF32(state), product, load_per_vertex(vertex), weight});
|
||||
state.builder.AddFunction(spv::OpAccessChain,
|
||||
TypePointer(state, spv::StorageClassInput, TypeF32(state)),
|
||||
pointer, barycentric, ConstantU32(state, vertex));
|
||||
state.builder.AddFunction(spv::OpLoad, TypeF32(state), weight, pointer);
|
||||
state.builder.AddFunction(spv::OpFMul, TypeF32(state), product, load_per_vertex(vertex),
|
||||
weight);
|
||||
if (vertex == 0u) {
|
||||
sum = product;
|
||||
} else {
|
||||
const auto next = state.builder.AllocateId();
|
||||
state.builder.AddFunction({spv::OpFAdd, TypeF32(state), next, sum, product});
|
||||
state.builder.AddFunction(spv::OpFAdd, TypeF32(state), next, sum, product);
|
||||
sum = next;
|
||||
}
|
||||
}
|
||||
const auto bits = state.builder.AllocateId();
|
||||
state.builder.AddFunction({spv::OpBitcast, TypeU32(state), bits, sum});
|
||||
state.builder.AddFunction(spv::OpBitcast, TypeU32(state), bits, sum);
|
||||
return bits;
|
||||
}
|
||||
const auto vector = state.builder.AllocateId();
|
||||
const auto component = state.builder.AllocateId();
|
||||
const auto bits = state.builder.AllocateId();
|
||||
state.builder.AddFunction({spv::OpLoad, TypeF32Vector(state, 4), vector, input->variable_id});
|
||||
state.builder.AddFunction(
|
||||
{spv::OpCompositeExtract, TypeF32(state), component, vector, chan & 3u});
|
||||
state.builder.AddFunction({spv::OpBitcast, TypeU32(state), bits, component});
|
||||
state.builder.AddFunction(spv::OpLoad, TypeF32Vector(state, 4), vector, input->variable_id);
|
||||
state.builder.AddFunction(spv::OpCompositeExtract, TypeF32(state), component, vector,
|
||||
chan & 3u);
|
||||
state.builder.AddFunction(spv::OpBitcast, TypeU32(state), bits, component);
|
||||
return bits;
|
||||
}
|
||||
|
||||
@@ -199,11 +214,10 @@ uint32_t EmitInterpolationParameter(ValueEmitContext& ctx, uint32_t attr, uint32
|
||||
const auto load_vertex = [&](uint32_t vertex) {
|
||||
const auto pointer = state.builder.AllocateId();
|
||||
const auto value = state.builder.AllocateId();
|
||||
state.builder.AddFunction({spv::OpAccessChain,
|
||||
TypePointer(state, spv::StorageClassInput, TypeF32(state)),
|
||||
pointer, input->variable_id, ConstantU32(state, vertex),
|
||||
ConstantU32(state, chan & 3u)});
|
||||
state.builder.AddFunction({spv::OpLoad, TypeF32(state), value, pointer});
|
||||
state.builder.AddFunction(
|
||||
spv::OpAccessChain, TypePointer(state, spv::StorageClassInput, TypeF32(state)), pointer,
|
||||
input->variable_id, ConstantU32(state, vertex), ConstantU32(state, chan & 3u));
|
||||
state.builder.AddFunction(spv::OpLoad, TypeF32(state), value, pointer);
|
||||
return value;
|
||||
};
|
||||
|
||||
@@ -211,11 +225,11 @@ uint32_t EmitInterpolationParameter(ValueEmitContext& ctx, uint32_t attr, uint32
|
||||
uint32_t value = load_vertex(selected_vertex);
|
||||
if (!PixelParameterIsCustom(state, attr) && mode < 2u) {
|
||||
const auto delta = state.builder.AllocateId();
|
||||
state.builder.AddFunction({spv::OpFSub, TypeF32(state), delta, value, load_vertex(0)});
|
||||
state.builder.AddFunction(spv::OpFSub, TypeF32(state), delta, value, load_vertex(0));
|
||||
value = delta;
|
||||
}
|
||||
const auto bits = state.builder.AllocateId();
|
||||
state.builder.AddFunction({spv::OpBitcast, TypeU32(state), bits, value});
|
||||
state.builder.AddFunction(spv::OpBitcast, TypeU32(state), bits, value);
|
||||
return bits;
|
||||
}
|
||||
|
||||
@@ -229,8 +243,8 @@ uint32_t MrtOutputMode(const EmitterState& state, const IR::ExportInfo& exp) {
|
||||
|
||||
uint32_t ExportRawComponent(ValueEmitContext& ctx, uint32_t vector, uint32_t component) {
|
||||
const auto value = ctx.state.builder.AllocateId();
|
||||
ctx.state.builder.AddFunction(
|
||||
{spv::OpCompositeExtract, TypeU32(ctx.state), value, vector, component});
|
||||
ctx.state.builder.AddFunction(spv::OpCompositeExtract, TypeU32(ctx.state), value, vector,
|
||||
component);
|
||||
return value;
|
||||
}
|
||||
|
||||
@@ -248,22 +262,21 @@ uint32_t ExportVector(ValueEmitContext& ctx, uint32_t data, const IR::ExportInfo
|
||||
}
|
||||
const auto packed = state.builder.AllocateId();
|
||||
const auto unpacked = state.builder.AllocateId();
|
||||
state.builder.AddFunction(
|
||||
{spv::OpCompositeExtract, TypeU32(state), packed, data, pair});
|
||||
state.builder.AddFunction({spv::OpExtInst, TypeF32Vector(state, 2), unpacked,
|
||||
GlslStd450(state), unpack, packed});
|
||||
state.builder.AddFunction(spv::OpCompositeExtract, TypeU32(state), packed, data, pair);
|
||||
state.builder.AddFunction(spv::OpExtInst, TypeF32Vector(state, 2), unpacked,
|
||||
GlslStd450(state), unpack, packed);
|
||||
for (uint32_t lane = 0; lane < 2u; lane++) {
|
||||
const auto component = pair * 2u + lane;
|
||||
if (((exp.en >> component) & 1u) != 0u) {
|
||||
f32[component] = state.builder.AllocateId();
|
||||
state.builder.AddFunction(
|
||||
{spv::OpCompositeExtract, TypeF32(state), f32[component], unpacked, lane});
|
||||
state.builder.AddFunction(spv::OpCompositeExtract, TypeF32(state),
|
||||
f32[component], unpacked, lane);
|
||||
}
|
||||
}
|
||||
}
|
||||
const auto vector = state.builder.AllocateId();
|
||||
state.builder.AddFunction({spv::OpCompositeConstruct, TypeF32Vector(state, 4), vector,
|
||||
f32[0], f32[1], f32[2], f32[3]});
|
||||
state.builder.AddFunction(spv::OpCompositeConstruct, TypeF32Vector(state, 4), vector,
|
||||
f32[0], f32[1], f32[2], f32[3]);
|
||||
return vector;
|
||||
}
|
||||
uint32_t raw[4] = {
|
||||
@@ -284,9 +297,9 @@ uint32_t ExportVector(ValueEmitContext& ctx, uint32_t data, const IR::ExportInfo
|
||||
continue;
|
||||
}
|
||||
raw[component] = state.builder.AllocateId();
|
||||
state.builder.AddFunction({spv::OpBitFieldUExtract, TypeU32(state), raw[component],
|
||||
packed, ConstantU32(state, lane * 16u),
|
||||
ConstantU32(state, 16)});
|
||||
state.builder.AddFunction(spv::OpBitFieldUExtract, TypeU32(state), raw[component],
|
||||
packed, ConstantU32(state, lane * 16u),
|
||||
ConstantU32(state, 16));
|
||||
}
|
||||
}
|
||||
} else {
|
||||
@@ -298,18 +311,18 @@ uint32_t ExportVector(ValueEmitContext& ctx, uint32_t data, const IR::ExportInfo
|
||||
}
|
||||
if (uint_output) {
|
||||
const auto vector = state.builder.AllocateId();
|
||||
state.builder.AddFunction({spv::OpCompositeConstruct, TypeU32Vector(state, 4), vector,
|
||||
raw[0], raw[1], raw[2], raw[3]});
|
||||
state.builder.AddFunction(spv::OpCompositeConstruct, TypeU32Vector(state, 4), vector,
|
||||
raw[0], raw[1], raw[2], raw[3]);
|
||||
return vector;
|
||||
}
|
||||
uint32_t f32[4] {};
|
||||
for (uint32_t component = 0; component < 4u; component++) {
|
||||
f32[component] = state.builder.AllocateId();
|
||||
state.builder.AddFunction({spv::OpBitcast, TypeF32(state), f32[component], raw[component]});
|
||||
state.builder.AddFunction(spv::OpBitcast, TypeF32(state), f32[component], raw[component]);
|
||||
}
|
||||
const auto vector = state.builder.AllocateId();
|
||||
state.builder.AddFunction({spv::OpCompositeConstruct, TypeF32Vector(state, 4), vector, f32[0],
|
||||
f32[1], f32[2], f32[3]});
|
||||
state.builder.AddFunction(spv::OpCompositeConstruct, TypeF32Vector(state, 4), vector, f32[0],
|
||||
f32[1], f32[2], f32[3]);
|
||||
return vector;
|
||||
}
|
||||
|
||||
@@ -325,19 +338,19 @@ void EmitAuxPositionExport(ValueEmitContext& ctx, uint32_t data, const IR::Expor
|
||||
const auto raw = ExportRawComponent(ctx, data, component);
|
||||
if (output.layer) {
|
||||
const auto layer = state.builder.AllocateId();
|
||||
state.builder.AddFunction(
|
||||
{spv::OpBitwiseAnd, TypeU32(state), layer, raw, ConstantU32(state, 0x7ffu)});
|
||||
state.builder.AddFunction(spv::OpBitwiseAnd, TypeU32(state), layer, raw,
|
||||
ConstantU32(state, 0x7ffu));
|
||||
const auto pointer = state.program.stage == ShaderType::Mesh
|
||||
? MeshOutputPointer(state, IR::StageOutputKind::Layer)
|
||||
: state.layer_variable;
|
||||
state.builder.AddFunction({spv::OpStore, pointer, layer});
|
||||
state.builder.AddFunction(spv::OpStore, pointer, layer);
|
||||
}
|
||||
if (output.viewport) {
|
||||
// GFX10 MISC.z packs the viewport index in bits 16..19 alongside the layer.
|
||||
const auto viewport = state.builder.AllocateId();
|
||||
state.builder.AddFunction({spv::OpBitFieldUExtract, TypeU32(state), viewport, raw,
|
||||
ConstantU32(state, 16), ConstantU32(state, 4)});
|
||||
state.builder.AddFunction({spv::OpStore, state.viewport_index_variable, viewport});
|
||||
state.builder.AddFunction(spv::OpBitFieldUExtract, TypeU32(state), viewport, raw,
|
||||
ConstantU32(state, 16), ConstantU32(state, 4));
|
||||
state.builder.AddFunction(spv::OpStore, state.viewport_index_variable, viewport);
|
||||
}
|
||||
continue;
|
||||
}
|
||||
@@ -348,9 +361,9 @@ void EmitAuxPositionExport(ValueEmitContext& ctx, uint32_t data, const IR::Expor
|
||||
|
||||
const auto raw = ExportRawComponent(ctx, data, component);
|
||||
const auto f32 = state.builder.AllocateId();
|
||||
state.builder.AddFunction({spv::OpBitcast, TypeF32(state), f32, raw});
|
||||
state.builder.AddFunction(spv::OpBitcast, TypeF32(state), f32, raw);
|
||||
if (output.point_size) {
|
||||
state.builder.AddFunction({spv::OpStore, state.point_size_variable, f32});
|
||||
state.builder.AddFunction(spv::OpStore, state.point_size_variable, f32);
|
||||
continue;
|
||||
}
|
||||
auto StoreDistance = [&](uint32_t variable, uint32_t index) {
|
||||
@@ -358,10 +371,10 @@ void EmitAuxPositionExport(ValueEmitContext& ctx, uint32_t data, const IR::Expor
|
||||
return;
|
||||
}
|
||||
const auto pointer = state.builder.AllocateId();
|
||||
state.builder.AddFunction({spv::OpAccessChain,
|
||||
TypePointer(state, spv::StorageClassOutput, TypeF32(state)),
|
||||
pointer, variable, ConstantU32(state, index)});
|
||||
state.builder.AddFunction({spv::OpStore, pointer, f32});
|
||||
state.builder.AddFunction(spv::OpAccessChain,
|
||||
TypePointer(state, spv::StorageClassOutput, TypeF32(state)),
|
||||
pointer, variable, ConstantU32(state, index));
|
||||
state.builder.AddFunction(spv::OpStore, pointer, f32);
|
||||
};
|
||||
StoreDistance(state.clip_distance_variable, output.clip_distance);
|
||||
StoreDistance(state.cull_distance_variable, output.cull_distance);
|
||||
@@ -374,14 +387,14 @@ uint32_t ConvertClipCoordinate(EmitterState& state, uint32_t coordinate, float s
|
||||
const auto biased = state.builder.AllocateId();
|
||||
const auto divided = state.builder.AllocateId();
|
||||
const auto ndc = state.builder.AllocateId();
|
||||
state.builder.AddFunction(
|
||||
{spv::OpFMul, TypeF32(state), window, coordinate, ConstantF32Value(state, scale)});
|
||||
state.builder.AddFunction(
|
||||
{spv::OpFAdd, TypeF32(state), biased, window, ConstantF32Value(state, offset)});
|
||||
state.builder.AddFunction(
|
||||
{spv::OpFDiv, TypeF32(state), divided, biased, ConstantF32Value(state, half_extent)});
|
||||
state.builder.AddFunction(
|
||||
{spv::OpFSub, TypeF32(state), ndc, divided, ConstantF32Value(state, 1.0f)});
|
||||
state.builder.AddFunction(spv::OpFMul, TypeF32(state), window, coordinate,
|
||||
ConstantF32Value(state, scale));
|
||||
state.builder.AddFunction(spv::OpFAdd, TypeF32(state), biased, window,
|
||||
ConstantF32Value(state, offset));
|
||||
state.builder.AddFunction(spv::OpFDiv, TypeF32(state), divided, biased,
|
||||
ConstantF32Value(state, half_extent));
|
||||
state.builder.AddFunction(spv::OpFSub, TypeF32(state), ndc, divided,
|
||||
ConstantF32Value(state, 1.0f));
|
||||
return ndc;
|
||||
}
|
||||
|
||||
@@ -390,16 +403,16 @@ uint32_t ConvertPositionToClipSpace(EmitterState& state, uint32_t position) {
|
||||
uint32_t components[4] {};
|
||||
for (uint32_t i = 0; i < 4; i++) {
|
||||
components[i] = state.builder.AllocateId();
|
||||
state.builder.AddFunction(
|
||||
{spv::OpCompositeExtract, TypeF32(state), components[i], position, i});
|
||||
state.builder.AddFunction(spv::OpCompositeExtract, TypeF32(state), components[i], position,
|
||||
i);
|
||||
}
|
||||
components[0] = ConvertClipCoordinate(state, components[0], transform.scale[0],
|
||||
transform.offset[0], transform.half_extent[0]);
|
||||
components[1] = ConvertClipCoordinate(state, components[1], transform.scale[1],
|
||||
transform.offset[1], transform.half_extent[1]);
|
||||
const auto converted = state.builder.AllocateId();
|
||||
state.builder.AddFunction({spv::OpCompositeConstruct, TypeF32Vector(state, 4), converted,
|
||||
components[0], components[1], components[2], components[3]});
|
||||
state.builder.AddFunction(spv::OpCompositeConstruct, TypeF32Vector(state, 4), converted,
|
||||
components[0], components[1], components[2], components[3]);
|
||||
return converted;
|
||||
}
|
||||
|
||||
@@ -411,17 +424,17 @@ uint32_t EmitWqmU64(EmitterState& state, uint32_t value) {
|
||||
const auto merged_two = state.builder.AllocateId();
|
||||
const auto quad_bits = state.builder.AllocateId();
|
||||
const auto result = state.builder.AllocateId();
|
||||
state.builder.AddFunction({spv::OpShiftRightLogical, TypeU64(state), shifted_one, value,
|
||||
ConstantU64(state, 0x0000000100000001ull)});
|
||||
state.builder.AddFunction({spv::OpBitwiseOr, TypeU64(state), merged_one, value, shifted_one});
|
||||
state.builder.AddFunction({spv::OpShiftRightLogical, TypeU64(state), shifted_two, merged_one,
|
||||
ConstantU64(state, 0x0000000200000002ull)});
|
||||
state.builder.AddFunction(
|
||||
{spv::OpBitwiseOr, TypeU64(state), merged_two, merged_one, shifted_two});
|
||||
state.builder.AddFunction({spv::OpBitwiseAnd, TypeU64(state), quad_bits, merged_two,
|
||||
ConstantU64(state, 0x1111111111111111ull)});
|
||||
state.builder.AddFunction({spv::OpIMul, TypeU64(state), result, quad_bits,
|
||||
ConstantU64(state, 0x0000000f0000000full)});
|
||||
state.builder.AddFunction(spv::OpShiftRightLogical, TypeU64(state), shifted_one, value,
|
||||
ConstantU64(state, 0x0000000100000001ull));
|
||||
state.builder.AddFunction(spv::OpBitwiseOr, TypeU64(state), merged_one, value, shifted_one);
|
||||
state.builder.AddFunction(spv::OpShiftRightLogical, TypeU64(state), shifted_two, merged_one,
|
||||
ConstantU64(state, 0x0000000200000002ull));
|
||||
state.builder.AddFunction(spv::OpBitwiseOr, TypeU64(state), merged_two, merged_one,
|
||||
shifted_two);
|
||||
state.builder.AddFunction(spv::OpBitwiseAnd, TypeU64(state), quad_bits, merged_two,
|
||||
ConstantU64(state, 0x1111111111111111ull));
|
||||
state.builder.AddFunction(spv::OpIMul, TypeU64(state), result, quad_bits,
|
||||
ConstantU64(state, 0x0000000f0000000full));
|
||||
return result;
|
||||
}
|
||||
|
||||
@@ -432,9 +445,9 @@ void EmitSetAttribute(ValueEmitContext& ctx, const IR::Inst& inst) {
|
||||
if (state.program.stage == ShaderType::Pixel && exp.vm && state.requirements.pixel_valid_mask &&
|
||||
state.pixel_valid_mask_variable != 0) {
|
||||
const auto value = state.builder.AllocateId();
|
||||
state.builder.AddFunction({spv::OpSelect, TypeU32(state), value, exec,
|
||||
ConstantU32(state, 1), ConstantU32(state, 0)});
|
||||
state.builder.AddFunction({spv::OpStore, state.pixel_valid_mask_variable, value});
|
||||
state.builder.AddFunction(spv::OpSelect, TypeU32(state), value, exec, ConstantU32(state, 1),
|
||||
ConstantU32(state, 0));
|
||||
state.builder.AddFunction(spv::OpStore, state.pixel_valid_mask_variable, value);
|
||||
}
|
||||
if (exp.kind == IR::ExportTargetKind::Null || exp.en == 0u) {
|
||||
return;
|
||||
@@ -443,8 +456,8 @@ void EmitSetAttribute(ValueEmitContext& ctx, const IR::Inst& inst) {
|
||||
const auto data = ctx.Arg(inst, 0);
|
||||
if (exp.kind == IR::ExportTargetKind::Primitive) {
|
||||
if (state.program.stage == ShaderType::Mesh) {
|
||||
state.builder.AddFunction(
|
||||
{spv::OpStore, MeshPrimitivePointer(state), ExportRawComponent(ctx, data, 0)});
|
||||
state.builder.AddFunction(spv::OpStore, MeshPrimitivePointer(state),
|
||||
ExportRawComponent(ctx, data, 0));
|
||||
}
|
||||
return;
|
||||
}
|
||||
@@ -456,19 +469,18 @@ void EmitSetAttribute(ValueEmitContext& ctx, const IR::Inst& inst) {
|
||||
if ((exp.en & 1u) != 0u && state.depth_variable != 0) {
|
||||
const auto raw = ExportRawComponent(ctx, data, 0);
|
||||
const auto f32 = state.builder.AllocateId();
|
||||
state.builder.AddFunction({spv::OpBitcast, TypeF32(state), f32, raw});
|
||||
state.builder.AddFunction({spv::OpStore, state.depth_variable, f32});
|
||||
state.builder.AddFunction(spv::OpBitcast, TypeF32(state), f32, raw);
|
||||
state.builder.AddFunction(spv::OpStore, state.depth_variable, f32);
|
||||
}
|
||||
if ((exp.en & 4u) != 0u && state.sample_mask_variable != 0) {
|
||||
const auto raw = ExportRawComponent(ctx, data, 2);
|
||||
const auto value = state.builder.AllocateId();
|
||||
const auto pointer = state.builder.AllocateId();
|
||||
state.builder.AddFunction({spv::OpBitcast, TypeI32(state), value, raw});
|
||||
state.builder.AddFunction(spv::OpBitcast, TypeI32(state), value, raw);
|
||||
state.builder.AddFunction(
|
||||
{spv::OpAccessChain,
|
||||
TypePointer(state, spv::StorageClassOutput, TypeI32(state)), pointer,
|
||||
state.sample_mask_variable, ConstantU32(state, 0)});
|
||||
state.builder.AddFunction({spv::OpStore, pointer, value});
|
||||
spv::OpAccessChain, TypePointer(state, spv::StorageClassOutput, TypeI32(state)),
|
||||
pointer, state.sample_mask_variable, ConstantU32(state, 0));
|
||||
state.builder.AddFunction(spv::OpStore, pointer, value);
|
||||
}
|
||||
return;
|
||||
}
|
||||
@@ -485,9 +497,9 @@ void EmitSetAttribute(ValueEmitContext& ctx, const IR::Inst& inst) {
|
||||
const auto mapping = state.input_info.pixel->target_export_mapping[exp.index];
|
||||
if (!mapping.IsIdentity()) {
|
||||
const auto mapped = state.builder.AllocateId();
|
||||
state.builder.AddFunction({spv::OpVectorShuffle, vector_type, mapped, value, value,
|
||||
mapping.Map(0), mapping.Map(1), mapping.Map(2),
|
||||
mapping.Map(3)});
|
||||
state.builder.AddFunction(spv::OpVectorShuffle, vector_type, mapped, value, value,
|
||||
mapping.Map(0), mapping.Map(1), mapping.Map(2),
|
||||
mapping.Map(3));
|
||||
value = mapped;
|
||||
}
|
||||
}
|
||||
@@ -499,17 +511,42 @@ void EmitSetAttribute(ValueEmitContext& ctx, const IR::Inst& inst) {
|
||||
const auto kind = exp.kind == IR::ExportTargetKind::Position
|
||||
? IR::StageOutputKind::Position
|
||||
: IR::StageOutputKind::Parameter;
|
||||
state.builder.AddFunction(
|
||||
{spv::OpStore, MeshOutputPointer(state, kind, exp.index), value});
|
||||
state.builder.AddFunction(spv::OpStore, MeshOutputPointer(state, kind, exp.index),
|
||||
value);
|
||||
} else if (exp.kind == IR::ExportTargetKind::Position) {
|
||||
if (state.invalid_position_clip_distance != UINT32_MAX) {
|
||||
const auto zero = state.builder.Constant(spv::OpConstantNull, TypeF32Vector(state, 4));
|
||||
const auto equal = state.builder.AllocateId();
|
||||
const auto invalid = state.builder.AllocateId();
|
||||
const auto distance = state.builder.AllocateId();
|
||||
const auto distance_pointer = state.builder.AllocateId();
|
||||
state.builder.AddFunction(spv::OpFOrdEqual, TypeBoolVector(state, 4), equal,
|
||||
value, zero);
|
||||
state.builder.AddFunction(spv::OpAll, TypeBool(state), invalid, equal);
|
||||
// Zero at valid vertices makes a primitive containing an invalid position
|
||||
// collapse to its remaining edge, before the undefined 0/0 perspective divide.
|
||||
state.builder.AddFunction(spv::OpSelect, TypeF32(state), distance, invalid,
|
||||
ConstantF32Value(state, -1.0f),
|
||||
ConstantF32Value(state, 0.0f));
|
||||
state.builder.AddFunction(
|
||||
spv::OpAccessChain, TypePointer(state, spv::StorageClassOutput, TypeF32(state)),
|
||||
distance_pointer, state.clip_distance_variable,
|
||||
ConstantU32(state, state.invalid_position_clip_distance));
|
||||
state.builder.AddFunction(spv::OpStore, distance_pointer, distance);
|
||||
static std::atomic_bool logged = false;
|
||||
if (!logged.exchange(true, std::memory_order_relaxed)) {
|
||||
Log::WriteToConsoleAndLog(
|
||||
"Shader: emitted zero-position clip guard\n");
|
||||
}
|
||||
}
|
||||
const auto pointer = state.builder.AllocateId();
|
||||
state.builder.AddFunction(
|
||||
{spv::OpAccessChain,
|
||||
TypePointer(state, spv::StorageClassOutput, TypeF32Vector(state, 4)), pointer,
|
||||
variable, ConstantU32(state, 0)});
|
||||
state.builder.AddFunction({spv::OpStore, pointer, value});
|
||||
spv::OpAccessChain,
|
||||
TypePointer(state, spv::StorageClassOutput, TypeF32Vector(state, 4)), pointer,
|
||||
variable, ConstantU32(state, 0));
|
||||
state.builder.AddFunction(spv::OpStore, pointer, value);
|
||||
} else {
|
||||
state.builder.AddFunction({spv::OpStore, variable, value});
|
||||
state.builder.AddFunction(spv::OpStore, variable, value);
|
||||
}
|
||||
});
|
||||
}
|
||||
@@ -521,11 +558,23 @@ uint32_t EmitIdentity(ValueEmitContext&, uint32_t value) {
|
||||
void EmitVoid(ValueEmitContext&) {}
|
||||
|
||||
void EmitBarrier(EmitterState& state) {
|
||||
const auto semantics =
|
||||
spv::MemorySemanticsAcquireReleaseMask | spv::MemorySemanticsWorkgroupMemoryMask;
|
||||
state.builder.AddFunction({spv::OpControlBarrier, ConstantU32(state, spv::ScopeWorkgroup),
|
||||
ConstantU32(state, spv::ScopeWorkgroup),
|
||||
ConstantU32(state, semantics)});
|
||||
const auto tessellation = state.program.stage == ShaderType::TessellationControl;
|
||||
if (!tessellation && ShaderWorkgroupInput(state.program.stage, state.input_info) == nullptr) {
|
||||
// Independent graphics invocations have no native workgroup left to synchronize.
|
||||
return;
|
||||
}
|
||||
const auto memory_scope = tessellation ? spv::ScopeInvocation : spv::ScopeWorkgroup;
|
||||
const auto semantics = tessellation ? spv::MemorySemanticsMaskNone
|
||||
: spv::MemorySemanticsAcquireReleaseMask |
|
||||
spv::MemorySemanticsWorkgroupMemoryMask;
|
||||
state.builder.AddFunction(spv::OpControlBarrier, ConstantU32(state, spv::ScopeWorkgroup),
|
||||
ConstantU32(state, memory_scope), ConstantU32(state, semantics));
|
||||
}
|
||||
|
||||
uint32_t EmitLaneId(EmitterState& state) {
|
||||
return state.program.stage == ShaderType::TessellationControl
|
||||
? EmitBuiltinU32(state, IR::StageInputKind::InvocationId, 0)
|
||||
: EmitSubgroupLocalInvocationId(state);
|
||||
}
|
||||
|
||||
uint32_t EmitMeshDrawParameter(ValueEmitContext& ctx, const IR::Inst& inst) {
|
||||
@@ -536,10 +585,10 @@ uint32_t EmitMeshDrawParameter(ValueEmitContext& ctx, const IR::Inst& inst) {
|
||||
ctx.Fail(inst, "invalid mesh draw parameter");
|
||||
}
|
||||
const auto pointer = state.builder.AllocateId();
|
||||
state.builder.AddFunction({spv::OpAccessChain, TypePushConstantElementPointer(state), pointer,
|
||||
state.push_constant_variable, ConstantU32(state, 0),
|
||||
ConstantU32(state, index)});
|
||||
state.builder.AddFunction({spv::OpLoad, TypeU32(state), result, pointer});
|
||||
state.builder.AddFunction(spv::OpAccessChain, TypePushConstantElementPointer(state), pointer,
|
||||
state.push_constant_variable, ConstantU32(state, 0),
|
||||
ConstantU32(state, index));
|
||||
state.builder.AddFunction(spv::OpLoad, TypeU32(state), result, pointer);
|
||||
return result;
|
||||
}
|
||||
|
||||
@@ -559,14 +608,14 @@ uint32_t EmitGetBuiltin(ValueEmitContext& ctx, IR::Value kind, IR::Value index)
|
||||
|
||||
uint32_t EmitUndefU1(EmitterState& state, const IR::Inst& inst) {
|
||||
const auto result = state.builder.AllocateId();
|
||||
state.builder.AddFunction({spv::OpUndef, TypeId(state, inst.GetType()), result});
|
||||
state.builder.AddFunction(spv::OpUndef, TypeId(state, inst.GetType()), result);
|
||||
return result;
|
||||
}
|
||||
|
||||
uint32_t EmitDppMoveU32(ValueEmitContext& ctx, const IR::Inst& inst) {
|
||||
auto& state = ctx.state;
|
||||
const auto flags = inst.Flags<IR::DppMoveFlags>();
|
||||
const auto target = EmitDppTargetLane(state, flags.control);
|
||||
const auto target = EmitDppTargetLane(state, flags);
|
||||
const auto shuffled = ctx.Shuffle(inst, 0, target.lane);
|
||||
if (flags.fetch_inactive) {
|
||||
return shuffled;
|
||||
@@ -574,8 +623,8 @@ uint32_t EmitDppMoveU32(ValueEmitContext& ctx, const IR::Inst& inst) {
|
||||
const auto ballot = ctx.Ballot(inst.Arg(1));
|
||||
const auto source_active = EmitBallotLaneActiveBool(state, ballot, target.lane);
|
||||
const auto can_fetch = state.builder.AllocateId();
|
||||
state.builder.AddFunction(
|
||||
{spv::OpLogicalAnd, TypeBool(state), can_fetch, target.valid, source_active});
|
||||
state.builder.AddFunction(spv::OpLogicalAnd, TypeBool(state), can_fetch, target.valid,
|
||||
source_active);
|
||||
return EmitNative<spv::OpSelect, IR::Type::U32>(ctx.state, can_fetch, shuffled,
|
||||
ConstantU32(state, 0));
|
||||
}
|
||||
@@ -604,8 +653,8 @@ uint32_t EmitReadLane(ValueEmitContext& ctx, const IR::Inst& inst) {
|
||||
uint32_t EmitWriteLane(ValueEmitContext& ctx, const IR::Inst& inst) {
|
||||
auto& state = ctx.state;
|
||||
const auto hit = state.builder.AllocateId();
|
||||
state.builder.AddFunction({spv::OpIEqual, TypeBool(state), hit,
|
||||
EmitSubgroupLocalInvocationId(state), ctx.Arg(inst, 2)});
|
||||
state.builder.AddFunction(spv::OpIEqual, TypeBool(state), hit,
|
||||
EmitSubgroupLocalInvocationId(state), ctx.Arg(inst, 2));
|
||||
return EmitNative<spv::OpSelect, IR::Type::U32>(ctx.state, hit, ctx.Arg(inst, 1),
|
||||
ctx.Arg(inst, 0));
|
||||
}
|
||||
@@ -624,35 +673,35 @@ uint32_t EmitPermlane16U32(ValueEmitContext& ctx, const IR::Inst& inst) {
|
||||
const auto shifted = state.builder.AllocateId();
|
||||
const auto index = state.builder.AllocateId();
|
||||
const auto target = state.builder.AllocateId();
|
||||
state.builder.AddFunction(
|
||||
{spv::OpBitwiseAnd, TypeU32(state), row, subid, ConstantU32(state, 0xfffffff0u)});
|
||||
state.builder.AddFunction(spv::OpBitwiseAnd, TypeU32(state), row, subid,
|
||||
ConstantU32(state, 0xfffffff0u));
|
||||
if (flags.x16) {
|
||||
state.builder.AddFunction(
|
||||
{spv::OpBitwiseXor, TypeU32(state), row_value, row, ConstantU32(state, 16)});
|
||||
state.builder.AddFunction(spv::OpBitwiseXor, TypeU32(state), row_value, row,
|
||||
ConstantU32(state, 16));
|
||||
} else {
|
||||
state.builder.AddFunction({spv::OpCopyObject, TypeU32(state), row_value, row});
|
||||
state.builder.AddFunction(spv::OpCopyObject, TypeU32(state), row_value, row);
|
||||
}
|
||||
state.builder.AddFunction(
|
||||
{spv::OpBitwiseAnd, TypeU32(state), lane, subid, ConstantU32(state, 15)});
|
||||
state.builder.AddFunction(
|
||||
{spv::OpBitwiseAnd, TypeU32(state), lane8, lane, ConstantU32(state, 7)});
|
||||
state.builder.AddFunction(
|
||||
{spv::OpShiftLeftLogical, TypeU32(state), shift, lane8, ConstantU32(state, 2)});
|
||||
state.builder.AddFunction(
|
||||
{spv::OpUGreaterThanEqual, TypeBool(state), upper, lane, ConstantU32(state, 8)});
|
||||
state.builder.AddFunction(
|
||||
{spv::OpSelect, TypeU32(state), selected, upper, ctx.Arg(inst, 2), ctx.Arg(inst, 1)});
|
||||
state.builder.AddFunction({spv::OpShiftRightLogical, TypeU32(state), shifted, selected, shift});
|
||||
state.builder.AddFunction(
|
||||
{spv::OpBitwiseAnd, TypeU32(state), index, shifted, ConstantU32(state, 15)});
|
||||
state.builder.AddFunction({spv::OpBitwiseOr, TypeU32(state), target, row_value, index});
|
||||
state.builder.AddFunction(spv::OpBitwiseAnd, TypeU32(state), lane, subid,
|
||||
ConstantU32(state, 15));
|
||||
state.builder.AddFunction(spv::OpBitwiseAnd, TypeU32(state), lane8, lane,
|
||||
ConstantU32(state, 7));
|
||||
state.builder.AddFunction(spv::OpShiftLeftLogical, TypeU32(state), shift, lane8,
|
||||
ConstantU32(state, 2));
|
||||
state.builder.AddFunction(spv::OpUGreaterThanEqual, TypeBool(state), upper, lane,
|
||||
ConstantU32(state, 8));
|
||||
state.builder.AddFunction(spv::OpSelect, TypeU32(state), selected, upper, ctx.Arg(inst, 2),
|
||||
ctx.Arg(inst, 1));
|
||||
state.builder.AddFunction(spv::OpShiftRightLogical, TypeU32(state), shifted, selected, shift);
|
||||
state.builder.AddFunction(spv::OpBitwiseAnd, TypeU32(state), index, shifted,
|
||||
ConstantU32(state, 15));
|
||||
state.builder.AddFunction(spv::OpBitwiseOr, TypeU32(state), target, row_value, index);
|
||||
const auto shuffled = ctx.Shuffle(inst, 0, target);
|
||||
uint32_t result = shuffled;
|
||||
if (!flags.fetch_inactive) {
|
||||
const auto source_exec = ctx.Shuffle(inst, 3, target);
|
||||
result = state.builder.AllocateId();
|
||||
state.builder.AddFunction(
|
||||
{spv::OpSelect, TypeU32(state), result, source_exec, shuffled, ConstantU32(state, 0)});
|
||||
state.builder.AddFunction(spv::OpSelect, TypeU32(state), result, source_exec, shuffled,
|
||||
ConstantU32(state, 0));
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
@@ -2,24 +2,26 @@
|
||||
|
||||
namespace Libs::Graphics::ShaderRecompiler::Spirv::Emitter {
|
||||
|
||||
DppTargetLane EmitDppQuadPermTargetLane(EmitterState& state, uint32_t subid, uint32_t control) {
|
||||
const auto quad_base = state.builder.AllocateId();
|
||||
DppTargetLane EmitDppPermTargetLane(EmitterState& state, uint32_t subid, uint32_t control,
|
||||
uint32_t lane_bits) {
|
||||
const auto lane_mask = (1u << lane_bits) - 1u;
|
||||
const auto group_base = state.builder.AllocateId();
|
||||
const auto lane = state.builder.AllocateId();
|
||||
const auto shift = state.builder.AllocateId();
|
||||
const auto selected0 = state.builder.AllocateId();
|
||||
const auto selected = state.builder.AllocateId();
|
||||
const auto target = state.builder.AllocateId();
|
||||
state.builder.AddFunction(
|
||||
{spv::OpBitwiseAnd, TypeU32(state), quad_base, subid, ConstantU32(state, 0xfffffffcu)});
|
||||
state.builder.AddFunction(
|
||||
{spv::OpBitwiseAnd, TypeU32(state), lane, subid, ConstantU32(state, 3)});
|
||||
state.builder.AddFunction(
|
||||
{spv::OpShiftLeftLogical, TypeU32(state), shift, lane, ConstantU32(state, 1)});
|
||||
state.builder.AddFunction(
|
||||
{spv::OpShiftRightLogical, TypeU32(state), selected0, ConstantU32(state, control), shift});
|
||||
state.builder.AddFunction(
|
||||
{spv::OpBitwiseAnd, TypeU32(state), selected, selected0, ConstantU32(state, 3)});
|
||||
state.builder.AddFunction({spv::OpBitwiseOr, TypeU32(state), target, quad_base, selected});
|
||||
state.builder.AddFunction(spv::OpBitwiseAnd, TypeU32(state), group_base, subid,
|
||||
ConstantU32(state, ~lane_mask));
|
||||
state.builder.AddFunction(spv::OpBitwiseAnd, TypeU32(state), lane, subid,
|
||||
ConstantU32(state, lane_mask));
|
||||
state.builder.AddFunction(spv::OpIMul, TypeU32(state), shift, lane,
|
||||
ConstantU32(state, lane_bits));
|
||||
state.builder.AddFunction(spv::OpShiftRightLogical, TypeU32(state), selected0,
|
||||
ConstantU32(state, control), shift);
|
||||
state.builder.AddFunction(spv::OpBitwiseAnd, TypeU32(state), selected, selected0,
|
||||
ConstantU32(state, lane_mask));
|
||||
state.builder.AddFunction(spv::OpBitwiseOr, TypeU32(state), target, group_base, selected);
|
||||
return {target, ConstantBool(state, true)};
|
||||
}
|
||||
|
||||
@@ -30,22 +32,22 @@ DppTargetLane EmitDppRowShiftTargetLane(EmitterState& state, uint32_t subid, uin
|
||||
const auto lane_shifted = state.builder.AllocateId();
|
||||
const auto target = state.builder.AllocateId();
|
||||
const auto valid = state.builder.AllocateId();
|
||||
state.builder.AddFunction(
|
||||
{spv::OpBitwiseAnd, TypeU32(state), row, subid, ConstantU32(state, 0xfffffff0u)});
|
||||
state.builder.AddFunction(
|
||||
{spv::OpBitwiseAnd, TypeU32(state), lane, subid, ConstantU32(state, 15)});
|
||||
state.builder.AddFunction(spv::OpBitwiseAnd, TypeU32(state), row, subid,
|
||||
ConstantU32(state, 0xfffffff0u));
|
||||
state.builder.AddFunction(spv::OpBitwiseAnd, TypeU32(state), lane, subid,
|
||||
ConstantU32(state, 15));
|
||||
if (left) {
|
||||
state.builder.AddFunction(
|
||||
{spv::OpIAdd, TypeU32(state), lane_shifted, lane, ConstantU32(state, amount)});
|
||||
state.builder.AddFunction(
|
||||
{spv::OpULessThan, TypeBool(state), valid, lane, ConstantU32(state, 16u - amount)});
|
||||
state.builder.AddFunction(spv::OpIAdd, TypeU32(state), lane_shifted, lane,
|
||||
ConstantU32(state, amount));
|
||||
state.builder.AddFunction(spv::OpULessThan, TypeBool(state), valid, lane,
|
||||
ConstantU32(state, 16u - amount));
|
||||
} else {
|
||||
state.builder.AddFunction(
|
||||
{spv::OpISub, TypeU32(state), lane_shifted, lane, ConstantU32(state, amount)});
|
||||
state.builder.AddFunction(
|
||||
{spv::OpUGreaterThanEqual, TypeBool(state), valid, lane, ConstantU32(state, amount)});
|
||||
state.builder.AddFunction(spv::OpISub, TypeU32(state), lane_shifted, lane,
|
||||
ConstantU32(state, amount));
|
||||
state.builder.AddFunction(spv::OpUGreaterThanEqual, TypeBool(state), valid, lane,
|
||||
ConstantU32(state, amount));
|
||||
}
|
||||
state.builder.AddFunction({spv::OpBitwiseOr, TypeU32(state), target, row, lane_shifted});
|
||||
state.builder.AddFunction(spv::OpBitwiseOr, TypeU32(state), target, row, lane_shifted);
|
||||
return {target, valid};
|
||||
}
|
||||
|
||||
@@ -58,18 +60,17 @@ DppTargetLane EmitDppRowRotateRightTargetLane(EmitterState& state, uint32_t subi
|
||||
const auto plus = state.builder.AllocateId();
|
||||
const auto selected = state.builder.AllocateId();
|
||||
const auto target = state.builder.AllocateId();
|
||||
state.builder.AddFunction(
|
||||
{spv::OpBitwiseAnd, TypeU32(state), row, subid, ConstantU32(state, 0xfffffff0u)});
|
||||
state.builder.AddFunction(
|
||||
{spv::OpBitwiseAnd, TypeU32(state), lane, subid, ConstantU32(state, 15)});
|
||||
state.builder.AddFunction(
|
||||
{spv::OpUGreaterThanEqual, TypeBool(state), in_high, lane, ConstantU32(state, amount)});
|
||||
state.builder.AddFunction(
|
||||
{spv::OpISub, TypeU32(state), minus, lane, ConstantU32(state, amount)});
|
||||
state.builder.AddFunction(
|
||||
{spv::OpIAdd, TypeU32(state), plus, lane, ConstantU32(state, 16u - amount)});
|
||||
state.builder.AddFunction({spv::OpSelect, TypeU32(state), selected, in_high, minus, plus});
|
||||
state.builder.AddFunction({spv::OpBitwiseOr, TypeU32(state), target, row, selected});
|
||||
state.builder.AddFunction(spv::OpBitwiseAnd, TypeU32(state), row, subid,
|
||||
ConstantU32(state, 0xfffffff0u));
|
||||
state.builder.AddFunction(spv::OpBitwiseAnd, TypeU32(state), lane, subid,
|
||||
ConstantU32(state, 15));
|
||||
state.builder.AddFunction(spv::OpUGreaterThanEqual, TypeBool(state), in_high, lane,
|
||||
ConstantU32(state, amount));
|
||||
state.builder.AddFunction(spv::OpISub, TypeU32(state), minus, lane, ConstantU32(state, amount));
|
||||
state.builder.AddFunction(spv::OpIAdd, TypeU32(state), plus, lane,
|
||||
ConstantU32(state, 16u - amount));
|
||||
state.builder.AddFunction(spv::OpSelect, TypeU32(state), selected, in_high, minus, plus);
|
||||
state.builder.AddFunction(spv::OpBitwiseOr, TypeU32(state), target, row, selected);
|
||||
return {target, ConstantBool(state, true)};
|
||||
}
|
||||
|
||||
@@ -80,20 +81,24 @@ DppTargetLane EmitDppMirrorTargetLane(EmitterState& state, uint32_t subid, bool
|
||||
const auto lane = state.builder.AllocateId();
|
||||
const auto mirrored = state.builder.AllocateId();
|
||||
const auto target = state.builder.AllocateId();
|
||||
state.builder.AddFunction(
|
||||
{spv::OpBitwiseAnd, TypeU32(state), base, subid, ConstantU32(state, base_mask)});
|
||||
state.builder.AddFunction(
|
||||
{spv::OpBitwiseAnd, TypeU32(state), lane, subid, ConstantU32(state, lane_mask)});
|
||||
state.builder.AddFunction(
|
||||
{spv::OpISub, TypeU32(state), mirrored, ConstantU32(state, lane_mask), lane});
|
||||
state.builder.AddFunction({spv::OpBitwiseOr, TypeU32(state), target, base, mirrored});
|
||||
state.builder.AddFunction(spv::OpBitwiseAnd, TypeU32(state), base, subid,
|
||||
ConstantU32(state, base_mask));
|
||||
state.builder.AddFunction(spv::OpBitwiseAnd, TypeU32(state), lane, subid,
|
||||
ConstantU32(state, lane_mask));
|
||||
state.builder.AddFunction(spv::OpISub, TypeU32(state), mirrored, ConstantU32(state, lane_mask),
|
||||
lane);
|
||||
state.builder.AddFunction(spv::OpBitwiseOr, TypeU32(state), target, base, mirrored);
|
||||
return {target, ConstantBool(state, true)};
|
||||
}
|
||||
|
||||
DppTargetLane EmitDppTargetLane(EmitterState& state, uint32_t control) {
|
||||
DppTargetLane EmitDppTargetLane(EmitterState& state, const IR::DppMoveFlags& flags) {
|
||||
const auto subid = EmitSubgroupLocalInvocationId(state);
|
||||
const auto control = flags.control;
|
||||
if (flags.dpp8) {
|
||||
return EmitDppPermTargetLane(state, subid, control, 3u);
|
||||
}
|
||||
if (control <= 0xffu) {
|
||||
return EmitDppQuadPermTargetLane(state, subid, control);
|
||||
return EmitDppPermTargetLane(state, subid, control, 2u);
|
||||
}
|
||||
if (control >= 0x101u && control <= 0x10fu) {
|
||||
return EmitDppRowShiftTargetLane(state, subid, control & 0xfu, true);
|
||||
@@ -112,8 +117,8 @@ DppTargetLane EmitDppTargetLane(EmitterState& state, uint32_t control) {
|
||||
}
|
||||
if (control >= 0x160u && control <= 0x16fu) {
|
||||
const auto target = state.builder.AllocateId();
|
||||
state.builder.AddFunction(
|
||||
{spv::OpBitwiseXor, TypeU32(state), target, subid, ConstantU32(state, control & 0xfu)});
|
||||
state.builder.AddFunction(spv::OpBitwiseXor, TypeU32(state), target, subid,
|
||||
ConstantU32(state, control & 0xfu));
|
||||
return {target, ConstantBool(state, true)};
|
||||
}
|
||||
return {subid, ConstantBool(state, true)};
|
||||
@@ -124,8 +129,8 @@ uint32_t EmitSubgroupLocalInvocationId(EmitterState& state) {
|
||||
EXIT("SubgroupLocalInvocationId was not declared before SPIR-V function emission\n");
|
||||
}
|
||||
const auto value = state.builder.AllocateId();
|
||||
state.builder.AddFunction(
|
||||
{spv::OpLoad, TypeU32(state), value, state.subgroup_local_invocation_id_variable});
|
||||
state.builder.AddFunction(spv::OpLoad, TypeU32(state), value,
|
||||
state.subgroup_local_invocation_id_variable);
|
||||
return state.lane_half == 0 ? value : EmitAddU32(state, value, ConstantU32(state, 32));
|
||||
}
|
||||
|
||||
@@ -154,10 +159,10 @@ uint32_t EmitInputComponentU32(EmitterState& state, IR::StageInputKind kind, uin
|
||||
}
|
||||
const auto pointer = state.builder.AllocateId();
|
||||
const auto value = state.builder.AllocateId();
|
||||
state.builder.AddFunction({spv::OpAccessChain,
|
||||
TypePointer(state, spv::StorageClassInput, TypeU32(state)), pointer,
|
||||
variable, ConstantU32(state, component)});
|
||||
state.builder.AddFunction({spv::OpLoad, TypeU32(state), value, pointer});
|
||||
state.builder.AddFunction(spv::OpAccessChain,
|
||||
TypePointer(state, spv::StorageClassInput, TypeU32(state)), pointer,
|
||||
variable, ConstantU32(state, component));
|
||||
state.builder.AddFunction(spv::OpLoad, TypeU32(state), value, pointer);
|
||||
return value;
|
||||
}
|
||||
|
||||
@@ -167,7 +172,7 @@ uint32_t EmitLocalInvocationIndex(EmitterState& state) {
|
||||
return ConstantU32(state, 0);
|
||||
}
|
||||
const auto value = state.builder.AllocateId();
|
||||
state.builder.AddFunction({spv::OpLoad, TypeU32(state), value, variable});
|
||||
state.builder.AddFunction(spv::OpLoad, TypeU32(state), value, variable);
|
||||
if (state.lane_count == 2) {
|
||||
const auto wave_base =
|
||||
EmitBinaryU32(state, spv::OpBitwiseAnd, value, ConstantU32(state, ~31u));
|
||||
@@ -184,13 +189,13 @@ uint32_t EmitVertexParameterComponentU32(EmitterState& state, const InputBinding
|
||||
const auto scalar_type = VertexParameterScalarType(state, kind);
|
||||
uint32_t raw = state.builder.AllocateId();
|
||||
if (count == 1u) {
|
||||
state.builder.AddFunction({spv::OpLoad, scalar_type, raw, input.variable_id});
|
||||
state.builder.AddFunction(spv::OpLoad, scalar_type, raw, input.variable_id);
|
||||
} else {
|
||||
const auto pointer_type = TypePointer(state, spv::StorageClassInput, scalar_type);
|
||||
const auto pointer = state.builder.AllocateId();
|
||||
state.builder.AddFunction({spv::OpAccessChain, pointer_type, pointer, input.variable_id,
|
||||
ConstantU32(state, component)});
|
||||
state.builder.AddFunction({spv::OpLoad, scalar_type, raw, pointer});
|
||||
state.builder.AddFunction(spv::OpAccessChain, pointer_type, pointer, input.variable_id,
|
||||
ConstantU32(state, component));
|
||||
state.builder.AddFunction(spv::OpLoad, scalar_type, raw, pointer);
|
||||
}
|
||||
|
||||
if (kind == VertexInputScalarKind::Uint) {
|
||||
@@ -198,49 +203,48 @@ uint32_t EmitVertexParameterComponentU32(EmitterState& state, const InputBinding
|
||||
}
|
||||
|
||||
const auto bits = state.builder.AllocateId();
|
||||
state.builder.AddFunction({spv::OpBitcast, TypeU32(state), bits, raw});
|
||||
state.builder.AddFunction(spv::OpBitcast, TypeU32(state), bits, raw);
|
||||
return bits;
|
||||
}
|
||||
|
||||
uint32_t EmitSubgroupLaneActiveBool(EmitterState& state, uint32_t lane) {
|
||||
const auto active_ballot = state.builder.AllocateId();
|
||||
state.builder.AddFunction({spv::OpGroupNonUniformBallot, TypeU32Vector(state, 4), active_ballot,
|
||||
ConstantU32(state, spv::ScopeSubgroup), ConstantBool(state, true)});
|
||||
state.builder.AddFunction(spv::OpGroupNonUniformBallot, TypeU32Vector(state, 4), active_ballot,
|
||||
ConstantU32(state, spv::ScopeSubgroup), ConstantBool(state, true));
|
||||
return EmitBallotLaneActiveBool(state, active_ballot, lane);
|
||||
}
|
||||
uint32_t EmitBallotLaneActiveBool(EmitterState& state, uint32_t active_ballot, uint32_t lane) {
|
||||
const auto low = state.builder.AllocateId();
|
||||
state.builder.AddFunction({spv::OpCompositeExtract, TypeU32(state), low, active_ballot, 0});
|
||||
state.builder.AddFunction(spv::OpCompositeExtract, TypeU32(state), low, active_ballot, 0);
|
||||
uint32_t mask = low;
|
||||
if (state.program.wave_size == 64u) {
|
||||
const auto high = state.builder.AllocateId();
|
||||
const auto in_high = state.builder.AllocateId();
|
||||
const auto selected = state.builder.AllocateId();
|
||||
state.builder.AddFunction(
|
||||
{spv::OpCompositeExtract, TypeU32(state), high, active_ballot, 1});
|
||||
state.builder.AddFunction(
|
||||
{spv::OpUGreaterThanEqual, TypeBool(state), in_high, lane, ConstantU32(state, 32)});
|
||||
state.builder.AddFunction({spv::OpSelect, TypeU32(state), selected, in_high, high, low});
|
||||
state.builder.AddFunction(spv::OpCompositeExtract, TypeU32(state), high, active_ballot, 1);
|
||||
state.builder.AddFunction(spv::OpUGreaterThanEqual, TypeBool(state), in_high, lane,
|
||||
ConstantU32(state, 32));
|
||||
state.builder.AddFunction(spv::OpSelect, TypeU32(state), selected, in_high, high, low);
|
||||
mask = selected;
|
||||
}
|
||||
|
||||
const auto lane_low = state.builder.AllocateId();
|
||||
const auto bit = state.builder.AllocateId();
|
||||
state.builder.AddFunction(
|
||||
{spv::OpBitwiseAnd, TypeU32(state), lane_low, lane, ConstantU32(state, 31)});
|
||||
state.builder.AddFunction(
|
||||
{spv::OpShiftLeftLogical, TypeU32(state), bit, ConstantU32(state, 1), lane_low});
|
||||
state.builder.AddFunction(spv::OpBitwiseAnd, TypeU32(state), lane_low, lane,
|
||||
ConstantU32(state, 31));
|
||||
state.builder.AddFunction(spv::OpShiftLeftLogical, TypeU32(state), bit, ConstantU32(state, 1),
|
||||
lane_low);
|
||||
|
||||
const auto hit = state.builder.AllocateId();
|
||||
const auto active = state.builder.AllocateId();
|
||||
const auto in_range = state.builder.AllocateId();
|
||||
const auto ret = state.builder.AllocateId();
|
||||
state.builder.AddFunction({spv::OpBitwiseAnd, TypeU32(state), hit, mask, bit});
|
||||
state.builder.AddFunction(
|
||||
{spv::OpINotEqual, TypeBool(state), active, hit, ConstantU32(state, 0)});
|
||||
state.builder.AddFunction({spv::OpULessThan, TypeBool(state), in_range, lane,
|
||||
ConstantU32(state, state.program.wave_size)});
|
||||
state.builder.AddFunction({spv::OpLogicalAnd, TypeBool(state), ret, active, in_range});
|
||||
state.builder.AddFunction(spv::OpBitwiseAnd, TypeU32(state), hit, mask, bit);
|
||||
state.builder.AddFunction(spv::OpINotEqual, TypeBool(state), active, hit,
|
||||
ConstantU32(state, 0));
|
||||
state.builder.AddFunction(spv::OpULessThan, TypeBool(state), in_range, lane,
|
||||
ConstantU32(state, state.program.wave_size));
|
||||
state.builder.AddFunction(spv::OpLogicalAnd, TypeBool(state), ret, active, in_range);
|
||||
return ret;
|
||||
}
|
||||
|
||||
|
||||
@@ -3,7 +3,9 @@
|
||||
#include "graphics/shader/recompiler/frontend/decode/ImageOps.h"
|
||||
|
||||
#include <algorithm>
|
||||
#include <atomic>
|
||||
#include <bit>
|
||||
#include <cstdio>
|
||||
|
||||
namespace Libs::Graphics::ShaderRecompiler::Spirv::Emitter {
|
||||
namespace {
|
||||
@@ -61,10 +63,10 @@ uint32_t AddressF32(ValueEmitContext& ctx, const IR::MemoryInfo& mem, const IR::
|
||||
: Unary(ctx.state, spv::OpBitcast, TypeF32(ctx.state), value);
|
||||
}
|
||||
|
||||
ImageSampleLayout Layout(const IR::MemoryInfo& mem, ImageDimension dimension) {
|
||||
ImageSampleLayout Layout(const IR::MemoryInfo& mem) {
|
||||
ImageSampleLayout layout;
|
||||
uint32_t cursor = 0;
|
||||
const auto& info = ImageDimensionInfoFor(dimension);
|
||||
const auto& info = ImageDimensionInfoFor(mem.image_dimension);
|
||||
if (HasFlag(mem, Decoder::ImageSampleFlagOffset)) layout.offset = cursor++;
|
||||
if (HasFlag(mem, Decoder::ImageSampleFlagBias)) layout.bias = cursor++;
|
||||
if (HasFlag(mem, Decoder::ImageSampleFlagCompare)) layout.dref = cursor++;
|
||||
@@ -90,20 +92,19 @@ uint32_t CubeAxis(EmitterState& state, uint32_t value) {
|
||||
|
||||
uint32_t CubeLayer(EmitterState& state, uint32_t value) {
|
||||
const auto guest = state.builder.AllocateId();
|
||||
state.builder.AddFunction({spv::OpConvertFToU, TypeU32(state), guest, value});
|
||||
state.builder.AddFunction(spv::OpConvertFToU, TypeU32(state), guest, value);
|
||||
const auto padding = Binary(
|
||||
state, spv::OpShiftLeftLogical, TypeU32(state),
|
||||
Binary(state, spv::OpShiftRightLogical, TypeU32(state), guest, ConstantU32(state, 3)),
|
||||
ConstantU32(state, 1));
|
||||
const auto host = Binary(state, spv::OpISub, TypeU32(state), guest, padding);
|
||||
const auto result = state.builder.AllocateId();
|
||||
state.builder.AddFunction({spv::OpConvertUToF, TypeF32(state), result, host});
|
||||
state.builder.AddFunction(spv::OpConvertUToF, TypeF32(state), result, host);
|
||||
return result;
|
||||
}
|
||||
|
||||
uint32_t CoordF32(ValueEmitContext& ctx, const IR::MemoryInfo& mem, const IR::Inst& address,
|
||||
uint32_t first, uint32_t components) {
|
||||
const bool cube = ctx.state.program.info.images.at(mem.resource).cube;
|
||||
uint32_t first, uint32_t components, bool cube = false) {
|
||||
auto x = AddressF32(ctx, mem, address, first);
|
||||
if (components == 1u) return x;
|
||||
auto y = mem.image_address_components > first + 1u ? AddressF32(ctx, mem, address, first + 1u)
|
||||
@@ -118,11 +119,11 @@ uint32_t CoordF32(ValueEmitContext& ctx, const IR::MemoryInfo& mem, const IR::In
|
||||
? AddressF32(ctx, mem, address, first + 2u)
|
||||
: ZeroF32(ctx.state);
|
||||
if (cube) z = CubeLayer(ctx.state, z);
|
||||
ctx.state.builder.AddFunction(
|
||||
{spv::OpCompositeConstruct, TypeF32Vector(ctx.state, 3), result, x, y, z});
|
||||
ctx.state.builder.AddFunction(spv::OpCompositeConstruct, TypeF32Vector(ctx.state, 3),
|
||||
result, x, y, z);
|
||||
} else {
|
||||
ctx.state.builder.AddFunction(
|
||||
{spv::OpCompositeConstruct, TypeF32Vector(ctx.state, 2), result, x, y});
|
||||
ctx.state.builder.AddFunction(spv::OpCompositeConstruct, TypeF32Vector(ctx.state, 2),
|
||||
result, x, y);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
@@ -138,11 +139,11 @@ uint32_t CoordU32(ValueEmitContext& ctx, const IR::MemoryInfo& mem, const IR::In
|
||||
if (components == 3u) {
|
||||
const auto z = mem.image_address_components > 2u ? AddressU32(ctx, mem, address, 2)
|
||||
: ConstantU32(ctx.state, 0);
|
||||
ctx.state.builder.AddFunction(
|
||||
{spv::OpCompositeConstruct, TypeU32Vector(ctx.state, 3), result, x, y, z});
|
||||
ctx.state.builder.AddFunction(spv::OpCompositeConstruct, TypeU32Vector(ctx.state, 3),
|
||||
result, x, y, z);
|
||||
} else {
|
||||
ctx.state.builder.AddFunction(
|
||||
{spv::OpCompositeConstruct, TypeU32Vector(ctx.state, 2), result, x, y});
|
||||
ctx.state.builder.AddFunction(spv::OpCompositeConstruct, TypeU32Vector(ctx.state, 2),
|
||||
result, x, y);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
@@ -193,9 +194,9 @@ uint32_t ResultVector(ValueEmitContext& ctx, uint32_t value,
|
||||
if (dref) return value;
|
||||
const auto component = gather ? index : DmaskComponent(mem.dmask, index);
|
||||
const auto result = ctx.state.builder.AllocateId();
|
||||
ctx.state.builder.AddFunction({spv::OpCompositeExtract,
|
||||
ImageScalarType(ctx.state, value_class), result, value,
|
||||
component});
|
||||
ctx.state.builder.AddFunction(spv::OpCompositeExtract,
|
||||
ImageScalarType(ctx.state, value_class), result, value,
|
||||
component);
|
||||
return result;
|
||||
};
|
||||
for (uint32_t word = 0; word < mem.data_dwords; word++) {
|
||||
@@ -218,17 +219,16 @@ uint32_t ResultVector(ValueEmitContext& ctx, uint32_t value,
|
||||
ConstantU32(ctx.state, 16u)));
|
||||
} else {
|
||||
const auto pair = ctx.state.builder.AllocateId();
|
||||
ctx.state.builder.AddFunction(
|
||||
{spv::OpCompositeConstruct, TypeF32Vector(ctx.state, 2), pair, low, high});
|
||||
ctx.state.builder.AddFunction(spv::OpCompositeConstruct,
|
||||
TypeF32Vector(ctx.state, 2), pair, low, high);
|
||||
packed[word] = ctx.state.builder.AllocateId();
|
||||
ctx.state.builder.AddFunction({spv::OpExtInst, TypeU32(ctx.state), packed[word],
|
||||
GlslStd450(ctx.state), GLSLstd450PackHalf2x16,
|
||||
pair});
|
||||
ctx.state.builder.AddFunction(spv::OpExtInst, TypeU32(ctx.state), packed[word],
|
||||
GlslStd450(ctx.state), GLSLstd450PackHalf2x16, pair);
|
||||
}
|
||||
}
|
||||
const auto result = ctx.state.builder.AllocateId();
|
||||
ctx.state.builder.AddFunction({spv::OpCompositeConstruct, TypeU32Vector(ctx.state, 4),
|
||||
result, packed[0], packed[1], packed[2], packed[3]});
|
||||
ctx.state.builder.AddFunction(spv::OpCompositeConstruct, TypeU32Vector(ctx.state, 4),
|
||||
result, packed[0], packed[1], packed[2], packed[3]);
|
||||
return result;
|
||||
}
|
||||
uint32_t component[4] {};
|
||||
@@ -238,14 +238,13 @@ uint32_t ResultVector(ValueEmitContext& ctx, uint32_t value,
|
||||
continue;
|
||||
}
|
||||
const auto scalar = ctx.state.builder.AllocateId();
|
||||
ctx.state.builder.AddFunction({spv::OpCompositeExtract,
|
||||
ImageScalarType(ctx.state, value_class), scalar, value,
|
||||
index});
|
||||
ctx.state.builder.AddFunction(
|
||||
spv::OpCompositeExtract, ImageScalarType(ctx.state, value_class), scalar, value, index);
|
||||
component[index] = SampledComponentBits(ctx, scalar, value_class);
|
||||
}
|
||||
const auto result = ctx.state.builder.AllocateId();
|
||||
ctx.state.builder.AddFunction({spv::OpCompositeConstruct, TypeU32Vector(ctx.state, 4), result,
|
||||
component[0], component[1], component[2], component[3]});
|
||||
ctx.state.builder.AddFunction(spv::OpCompositeConstruct, TypeU32Vector(ctx.state, 4), result,
|
||||
component[0], component[1], component[2], component[3]);
|
||||
return result;
|
||||
}
|
||||
|
||||
@@ -256,12 +255,12 @@ uint32_t QueryDimensions(ValueEmitContext& ctx, const IR::MemoryInfo& mem,
|
||||
const auto image = LoadSampledImageDescriptor(ctx.state, mem.resource);
|
||||
const auto size = ctx.state.builder.AllocateId();
|
||||
if (info.multisampled != 0u) {
|
||||
ctx.state.builder.AddFunction(
|
||||
{spv::OpImageQuerySize, ImageViewSizeType(ctx.state, dimension), size, image});
|
||||
ctx.state.builder.AddFunction(spv::OpImageQuerySize,
|
||||
ImageViewSizeType(ctx.state, dimension), size, image);
|
||||
} else {
|
||||
ctx.state.builder.AddFunction({spv::OpImageQuerySizeLod,
|
||||
ImageViewSizeType(ctx.state, dimension), size, image,
|
||||
AddressU32(ctx, mem, address, 0)});
|
||||
ctx.state.builder.AddFunction(spv::OpImageQuerySizeLod,
|
||||
ImageViewSizeType(ctx.state, dimension), size, image,
|
||||
AddressU32(ctx, mem, address, 0));
|
||||
}
|
||||
const auto components = info.coordinate_components;
|
||||
uint32_t result[4] = {ConstantU32(ctx.state, 0), ConstantU32(ctx.state, 0),
|
||||
@@ -271,18 +270,18 @@ uint32_t QueryDimensions(ValueEmitContext& ctx, const IR::MemoryInfo& mem,
|
||||
} else {
|
||||
for (uint32_t index = 0; index < components; index++) {
|
||||
result[index] = ctx.state.builder.AllocateId();
|
||||
ctx.state.builder.AddFunction(
|
||||
{spv::OpCompositeExtract, TypeU32(ctx.state), result[index], size, index});
|
||||
ctx.state.builder.AddFunction(spv::OpCompositeExtract, TypeU32(ctx.state),
|
||||
result[index], size, index);
|
||||
}
|
||||
}
|
||||
if (info.multisampled == 0u) {
|
||||
result[3] = ctx.state.builder.AllocateId();
|
||||
ctx.state.builder.AddFunction(
|
||||
{spv::OpImageQueryLevels, TypeU32(ctx.state), result[3], image});
|
||||
ctx.state.builder.AddFunction(spv::OpImageQueryLevels, TypeU32(ctx.state), result[3],
|
||||
image);
|
||||
}
|
||||
const auto vector = ctx.state.builder.AllocateId();
|
||||
ctx.state.builder.AddFunction({spv::OpCompositeConstruct, TypeU32Vector(ctx.state, 4), vector,
|
||||
result[0], result[1], result[2], result[3]});
|
||||
ctx.state.builder.AddFunction(spv::OpCompositeConstruct, TypeU32Vector(ctx.state, 4), vector,
|
||||
result[0], result[1], result[2], result[3]);
|
||||
return vector;
|
||||
}
|
||||
|
||||
@@ -294,11 +293,11 @@ uint32_t PackedOffset(ValueEmitContext& ctx, const IR::MemoryInfo& mem, const IR
|
||||
if (components == 1u) return zero;
|
||||
const auto result = ctx.state.builder.AllocateId();
|
||||
if (components == 3u) {
|
||||
ctx.state.builder.AddFunction(
|
||||
{spv::OpCompositeConstruct, TypeI32Vector(ctx.state, 3), result, zero, zero, zero});
|
||||
ctx.state.builder.AddFunction(spv::OpCompositeConstruct, TypeI32Vector(ctx.state, 3),
|
||||
result, zero, zero, zero);
|
||||
} else {
|
||||
ctx.state.builder.AddFunction(
|
||||
{spv::OpCompositeConstruct, TypeI32Vector(ctx.state, 2), result, zero, zero});
|
||||
ctx.state.builder.AddFunction(spv::OpCompositeConstruct, TypeI32Vector(ctx.state, 2),
|
||||
result, zero, zero);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
@@ -307,18 +306,18 @@ uint32_t PackedOffset(ValueEmitContext& ctx, const IR::MemoryInfo& mem, const IR
|
||||
uint32_t values[3] = {zero, zero, zero};
|
||||
for (uint32_t index = 0; index < components; index++) {
|
||||
values[index] = ctx.state.builder.AllocateId();
|
||||
ctx.state.builder.AddFunction({spv::OpBitFieldSExtract, TypeI32(ctx.state), values[index],
|
||||
packed, ConstantU32(ctx.state, index * 8u),
|
||||
ConstantU32(ctx.state, 6)});
|
||||
ctx.state.builder.AddFunction(spv::OpBitFieldSExtract, TypeI32(ctx.state), values[index],
|
||||
packed, ConstantU32(ctx.state, index * 8u),
|
||||
ConstantU32(ctx.state, 6));
|
||||
}
|
||||
if (components == 1u) return values[0];
|
||||
const auto result = ctx.state.builder.AllocateId();
|
||||
if (components == 3u) {
|
||||
ctx.state.builder.AddFunction({spv::OpCompositeConstruct, TypeI32Vector(ctx.state, 3),
|
||||
result, values[0], values[1], values[2]});
|
||||
ctx.state.builder.AddFunction(spv::OpCompositeConstruct, TypeI32Vector(ctx.state, 3),
|
||||
result, values[0], values[1], values[2]);
|
||||
} else {
|
||||
ctx.state.builder.AddFunction(
|
||||
{spv::OpCompositeConstruct, TypeI32Vector(ctx.state, 2), result, values[0], values[1]});
|
||||
ctx.state.builder.AddFunction(spv::OpCompositeConstruct, TypeI32Vector(ctx.state, 2),
|
||||
result, values[0], values[1]);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
@@ -327,7 +326,7 @@ uint32_t HorizontalOffsets(EmitterState& state, ImageDimension dimension) {
|
||||
const auto components = ImageDimensionInfoFor(dimension).spatial_components;
|
||||
const auto count = ConstantU32(state, 4);
|
||||
const auto element_type = components == 1u ? TypeI32(state) : TypeI32Vector(state, 2);
|
||||
const auto array_type = state.builder.Type(spv::OpTypeArray, {element_type, count});
|
||||
const auto array_type = state.builder.Type(spv::OpTypeArray, element_type, count);
|
||||
uint32_t offsets[4] {};
|
||||
for (uint32_t index = 0; index < 4u; index++) {
|
||||
const auto x = ConstantI32(state, static_cast<int32_t>(index) - 1);
|
||||
@@ -335,11 +334,11 @@ uint32_t HorizontalOffsets(EmitterState& state, ImageDimension dimension) {
|
||||
offsets[index] = x;
|
||||
} else {
|
||||
offsets[index] = state.builder.Constant(
|
||||
spv::OpConstantComposite, TypeI32Vector(state, 2), {x, ConstantI32(state, 0)});
|
||||
spv::OpConstantComposite, TypeI32Vector(state, 2), x, ConstantI32(state, 0));
|
||||
}
|
||||
}
|
||||
return state.builder.Constant(spv::OpConstantComposite, array_type,
|
||||
{offsets[0], offsets[1], offsets[2], offsets[3]});
|
||||
return state.builder.Constant(spv::OpConstantComposite, array_type, offsets[0], offsets[1],
|
||||
offsets[2], offsets[3]);
|
||||
}
|
||||
|
||||
uint32_t InverseSwizzle(uint32_t swizzle, uint32_t component) {
|
||||
@@ -367,15 +366,15 @@ uint32_t UnpackImageTexel(ValueEmitContext& ctx, const IR::MemoryInfo& mem, uint
|
||||
if (info.format == Prospero::BufferFormat::kInvalid) return texel;
|
||||
|
||||
const auto packed = ctx.state.builder.AllocateId();
|
||||
ctx.state.builder.AddFunction({spv::OpCompositeExtract, TypeU32(ctx.state), packed, texel, 0u});
|
||||
ctx.state.builder.AddFunction(spv::OpCompositeExtract, TypeU32(ctx.state), packed, texel, 0u);
|
||||
uint32_t components[4] = {ConstantU32(ctx.state, 0), ConstantU32(ctx.state, 0),
|
||||
ConstantU32(ctx.state, 0), ConstantU32(ctx.state, 0)};
|
||||
for (uint32_t component = 0; component < info.component_count; component++) {
|
||||
components[component] = ctx.state.builder.AllocateId();
|
||||
ctx.state.builder.AddFunction({spv::OpBitFieldUExtract, TypeU32(ctx.state),
|
||||
components[component], packed,
|
||||
ConstantU32(ctx.state, info.component_bit_offset[component]),
|
||||
ConstantU32(ctx.state, info.component_bits[component])});
|
||||
ctx.state.builder.AddFunction(spv::OpBitFieldUExtract, TypeU32(ctx.state),
|
||||
components[component], packed,
|
||||
ConstantU32(ctx.state, info.component_bit_offset[component]),
|
||||
ConstantU32(ctx.state, info.component_bits[component]));
|
||||
}
|
||||
for (uint32_t component = info.component_count; component < 4u; component++) {
|
||||
components[component] = components[component % info.component_count];
|
||||
@@ -394,8 +393,8 @@ uint32_t UnpackImageTexel(ValueEmitContext& ctx, const IR::MemoryInfo& mem, uint
|
||||
}
|
||||
}
|
||||
const auto result = ctx.state.builder.AllocateId();
|
||||
ctx.state.builder.AddFunction({spv::OpCompositeConstruct, TypeU32Vector(ctx.state, 4), result,
|
||||
selected[0], selected[1], selected[2], selected[3]});
|
||||
ctx.state.builder.AddFunction(spv::OpCompositeConstruct, TypeU32Vector(ctx.state, 4), result,
|
||||
selected[0], selected[1], selected[2], selected[3]);
|
||||
return result;
|
||||
}
|
||||
|
||||
@@ -409,24 +408,24 @@ uint32_t UnpackImageGather(ValueEmitContext& ctx, const IR::MemoryInfo& mem, uin
|
||||
if (selector < 4u) {
|
||||
const auto value = ConstantU32(ctx.state, selector == 1u ? 1u : 0u);
|
||||
return ctx.state.builder.Constant(spv::OpConstantComposite, TypeU32Vector(ctx.state, 4),
|
||||
{value, value, value, value});
|
||||
value, value, value, value);
|
||||
}
|
||||
|
||||
uint32_t values[4] {};
|
||||
for (uint32_t lane = 0; lane < 4u; lane++) {
|
||||
const auto packed = ctx.state.builder.AllocateId();
|
||||
ctx.state.builder.AddFunction(
|
||||
{spv::OpCompositeExtract, TypeU32(ctx.state), packed, gathered, lane});
|
||||
ctx.state.builder.AddFunction(spv::OpCompositeExtract, TypeU32(ctx.state), packed, gathered,
|
||||
lane);
|
||||
values[lane] = ctx.state.builder.AllocateId();
|
||||
const auto physical = (selector - 4u) % info.component_count;
|
||||
ctx.state.builder.AddFunction({spv::OpBitFieldUExtract, TypeU32(ctx.state), values[lane],
|
||||
packed,
|
||||
ConstantU32(ctx.state, info.component_bit_offset[physical]),
|
||||
ConstantU32(ctx.state, info.component_bits[physical])});
|
||||
ctx.state.builder.AddFunction(spv::OpBitFieldUExtract, TypeU32(ctx.state), values[lane],
|
||||
packed,
|
||||
ConstantU32(ctx.state, info.component_bit_offset[physical]),
|
||||
ConstantU32(ctx.state, info.component_bits[physical]));
|
||||
}
|
||||
const auto result = ctx.state.builder.AllocateId();
|
||||
ctx.state.builder.AddFunction({spv::OpCompositeConstruct, TypeU32Vector(ctx.state, 4), result,
|
||||
values[0], values[1], values[2], values[3]});
|
||||
ctx.state.builder.AddFunction(spv::OpCompositeConstruct, TypeU32Vector(ctx.state, 4), result,
|
||||
values[0], values[1], values[2], values[3]);
|
||||
return result;
|
||||
}
|
||||
|
||||
@@ -436,16 +435,16 @@ uint32_t EmitOneDimensionalGatherLz(ValueEmitContext& ctx, const IR::MemoryInfo&
|
||||
state.builder.RequireCapability(spv::CapabilityImageQuery);
|
||||
const auto image = LoadSampledImageDescriptor(state, mem.resource);
|
||||
const auto width = state.builder.AllocateId();
|
||||
state.builder.AddFunction(
|
||||
{spv::OpImageQuerySizeLod, TypeU32(state), width, image, ConstantU32(state, 0)});
|
||||
state.builder.AddFunction(spv::OpImageQuerySizeLod, TypeU32(state), width, image,
|
||||
ConstantU32(state, 0));
|
||||
const auto width_f32 = state.builder.AllocateId();
|
||||
state.builder.AddFunction({spv::OpConvertUToF, TypeF32(state), width_f32, width});
|
||||
state.builder.AddFunction(spv::OpConvertUToF, TypeF32(state), width_f32, width);
|
||||
const auto left = state.builder.AllocateId();
|
||||
state.builder.AddFunction({spv::OpExtInst, TypeF32(state), left, GlslStd450(state),
|
||||
GLSLstd450Floor,
|
||||
Binary(state, spv::OpFSub, TypeF32(state),
|
||||
Binary(state, spv::OpFMul, TypeF32(state), coord, width_f32),
|
||||
ConstantF32(state, 0x3f000000u))});
|
||||
state.builder.AddFunction(spv::OpExtInst, TypeF32(state), left, GlslStd450(state),
|
||||
GLSLstd450Floor,
|
||||
Binary(state, spv::OpFSub, TypeF32(state),
|
||||
Binary(state, spv::OpFMul, TypeF32(state), coord, width_f32),
|
||||
ConstantF32(state, 0x3f000000u)));
|
||||
|
||||
const auto sampled = MakeSampledImage(state, mem.resource, mem.sampler);
|
||||
const auto vector_type = ImageVectorType(state, numeric_class, 4);
|
||||
@@ -462,15 +461,15 @@ uint32_t EmitOneDimensionalGatherLz(ValueEmitContext& ctx, const IR::MemoryInfo&
|
||||
ConstantF32(state, index == 0u ? 0x3f000000u : 0x3fc00000u)),
|
||||
width_f32);
|
||||
const auto texel = state.builder.AllocateId();
|
||||
state.builder.AddFunction({spv::OpImageSampleExplicitLod, vector_type, texel, sampled,
|
||||
sample_coord, spv::ImageOperandsLodMask, ZeroF32(state)});
|
||||
state.builder.AddFunction(spv::OpImageSampleExplicitLod, vector_type, texel, sampled,
|
||||
sample_coord, spv::ImageOperandsLodMask, ZeroF32(state));
|
||||
values[index] = state.builder.AllocateId();
|
||||
state.builder.AddFunction(
|
||||
{spv::OpCompositeExtract, scalar_type, values[index], texel, component});
|
||||
state.builder.AddFunction(spv::OpCompositeExtract, scalar_type, values[index], texel,
|
||||
component);
|
||||
}
|
||||
const auto result = state.builder.AllocateId();
|
||||
state.builder.AddFunction({spv::OpCompositeConstruct, vector_type, result, values[0], values[1],
|
||||
values[1], values[0]});
|
||||
state.builder.AddFunction(spv::OpCompositeConstruct, vector_type, result, values[0], values[1],
|
||||
values[1], values[0]);
|
||||
return result;
|
||||
}
|
||||
|
||||
@@ -481,8 +480,8 @@ uint32_t PackImageTexel(ValueEmitContext& ctx, const IR::MemoryInfo& mem, uint32
|
||||
auto packed = ConstantU32(ctx.state, 0u);
|
||||
for (uint32_t component = 0; component < info.component_count; component++) {
|
||||
const auto value = ctx.state.builder.AllocateId();
|
||||
ctx.state.builder.AddFunction(
|
||||
{spv::OpCompositeExtract, TypeU32(ctx.state), value, texel, component});
|
||||
ctx.state.builder.AddFunction(spv::OpCompositeExtract, TypeU32(ctx.state), value, texel,
|
||||
component);
|
||||
const auto maximum =
|
||||
ConstantU32(ctx.state, info.component_bits[component] == 32u
|
||||
? UINT32_MAX
|
||||
@@ -490,8 +489,8 @@ uint32_t PackImageTexel(ValueEmitContext& ctx, const IR::MemoryInfo& mem, uint32
|
||||
const auto within =
|
||||
Binary(ctx.state, spv::OpULessThan, TypeBool(ctx.state), value, maximum);
|
||||
const auto clamped = ctx.state.builder.AllocateId();
|
||||
ctx.state.builder.AddFunction(
|
||||
{spv::OpSelect, TypeU32(ctx.state), clamped, within, value, maximum});
|
||||
ctx.state.builder.AddFunction(spv::OpSelect, TypeU32(ctx.state), clamped, within, value,
|
||||
maximum);
|
||||
const auto shifted =
|
||||
info.component_bit_offset[component] == 0u
|
||||
? clamped
|
||||
@@ -501,8 +500,8 @@ uint32_t PackImageTexel(ValueEmitContext& ctx, const IR::MemoryInfo& mem, uint32
|
||||
}
|
||||
const auto zero = ConstantU32(ctx.state, 0u);
|
||||
const auto result = ctx.state.builder.AllocateId();
|
||||
ctx.state.builder.AddFunction(
|
||||
{spv::OpCompositeConstruct, TypeU32Vector(ctx.state, 4), result, packed, zero, zero, zero});
|
||||
ctx.state.builder.AddFunction(spv::OpCompositeConstruct, TypeU32Vector(ctx.state, 4), result,
|
||||
packed, zero, zero, zero);
|
||||
return result;
|
||||
}
|
||||
|
||||
@@ -516,9 +515,8 @@ uint32_t StoreTexel(ValueEmitContext& ctx, const IR::MemoryInfo& mem, uint32_t d
|
||||
if (source < 4u && ((dmask >> source) & 1u) != 0u) {
|
||||
const auto packed_index = DmaskComponentIndex(dmask, source);
|
||||
raw = ctx.state.builder.AllocateId();
|
||||
ctx.state.builder.AddFunction(
|
||||
{spv::OpCompositeExtract, TypeU32(ctx.state), raw, data,
|
||||
mem.data_bits == 16u ? packed_index / 2u : packed_index});
|
||||
ctx.state.builder.AddFunction(spv::OpCompositeExtract, TypeU32(ctx.state), raw, data,
|
||||
mem.data_bits == 16u ? packed_index / 2u : packed_index);
|
||||
if (mem.data_bits == 16u) {
|
||||
if ((packed_index & 1u) != 0u) {
|
||||
raw = Binary(ctx.state, spv::OpShiftRightLogical, TypeU32(ctx.state), raw,
|
||||
@@ -534,14 +532,14 @@ uint32_t StoreTexel(ValueEmitContext& ctx, const IR::MemoryInfo& mem, uint32_t d
|
||||
: Unary(ctx.state, spv::OpBitcast, TypeF32(ctx.state), raw);
|
||||
}
|
||||
const auto texel = ctx.state.builder.AllocateId();
|
||||
ctx.state.builder.AddFunction(
|
||||
{spv::OpCompositeConstruct,
|
||||
integer ? TypeU32Vector(ctx.state, 4) : TypeF32Vector(ctx.state, 4), texel, values[0],
|
||||
values[1], values[2], values[3]});
|
||||
ctx.state.builder.AddFunction(spv::OpCompositeConstruct,
|
||||
integer ? TypeU32Vector(ctx.state, 4)
|
||||
: TypeF32Vector(ctx.state, 4),
|
||||
texel, values[0], values[1], values[2], values[3]);
|
||||
return PackImageTexel(ctx, mem, texel);
|
||||
}
|
||||
|
||||
uint32_t ImageAtomicOpcode(IR::ValueOpcode opcode) {
|
||||
spv::Op ImageAtomicOpcode(IR::ValueOpcode opcode) {
|
||||
switch (opcode) {
|
||||
case IR::ValueOpcode::ImageAtomicSwap32: return spv::OpAtomicExchange;
|
||||
case IR::ValueOpcode::ImageAtomicIAdd32: return spv::OpAtomicIAdd;
|
||||
@@ -550,7 +548,7 @@ uint32_t ImageAtomicOpcode(IR::ValueOpcode opcode) {
|
||||
case IR::ValueOpcode::ImageAtomicAnd32: return spv::OpAtomicAnd;
|
||||
case IR::ValueOpcode::ImageAtomicOr32: return spv::OpAtomicOr;
|
||||
case IR::ValueOpcode::ImageAtomicXor32: return spv::OpAtomicXor;
|
||||
default: return 0;
|
||||
default: return spv::OpNop;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -576,19 +574,20 @@ void EmitImage(ValueEmitContext& ctx, const IR::Inst& inst) {
|
||||
const auto sampled = MakeSampledImage(state, mem.resource, mem.sampler);
|
||||
const auto lod = state.builder.AllocateId();
|
||||
state.builder.AddFunction(
|
||||
{spv::OpImageQueryLod, TypeF32Vector(state, 2), lod, sampled,
|
||||
CoordF32(ctx, mem, *address, 0, ImageDimensionInfoFor(dimension).spatial_components)});
|
||||
spv::OpImageQueryLod, TypeF32Vector(state, 2), lod, sampled,
|
||||
CoordF32(ctx, mem, *address, 0, ImageDimensionInfoFor(dimension).spatial_components,
|
||||
image.cube));
|
||||
uint32_t values[4] = {ConstantU32(state, 0), ConstantU32(state, 0), ConstantU32(state, 0),
|
||||
ConstantU32(state, 0)};
|
||||
for (uint32_t index = 0; index < 2u; index++) {
|
||||
const auto component = state.builder.AllocateId();
|
||||
state.builder.AddFunction(
|
||||
{spv::OpCompositeExtract, TypeF32(state), component, lod, index});
|
||||
state.builder.AddFunction(spv::OpCompositeExtract, TypeF32(state), component, lod,
|
||||
index);
|
||||
values[index] = FloatBits(ctx, component);
|
||||
}
|
||||
const auto result = state.builder.AllocateId();
|
||||
state.builder.AddFunction({spv::OpCompositeConstruct, TypeU32Vector(state, 4), result,
|
||||
values[0], values[1], values[2], values[3]});
|
||||
state.builder.AddFunction(spv::OpCompositeConstruct, TypeU32Vector(state, 4), result,
|
||||
values[0], values[1], values[2], values[3]);
|
||||
ctx.Define(inst, result);
|
||||
return;
|
||||
}
|
||||
@@ -607,14 +606,14 @@ void EmitImage(ValueEmitContext& ctx, const IR::Inst& inst) {
|
||||
const auto coord = CoordU32(ctx, mem, *address, dimension);
|
||||
if (dimension_info.multisampled != 0u) {
|
||||
state.builder.AddFunction(
|
||||
{spv::OpImageFetch, ImageVectorType(state, numeric_class, 4), color,
|
||||
descriptor, coord, spv::ImageOperandsSampleMask,
|
||||
AddressU32(ctx, mem, *address, dimension_info.coordinate_components)});
|
||||
spv::OpImageFetch, ImageVectorType(state, numeric_class, 4), color,
|
||||
descriptor, coord, spv::ImageOperandsSampleMask,
|
||||
AddressU32(ctx, mem, *address, dimension_info.coordinate_components));
|
||||
} else {
|
||||
state.builder.AddFunction({spv::OpImageFetch,
|
||||
ImageVectorType(state, numeric_class, 4), color,
|
||||
descriptor, coord, spv::ImageOperandsLodMask,
|
||||
LodU32(ctx, mem, *address, dimension)});
|
||||
state.builder.AddFunction(spv::OpImageFetch,
|
||||
ImageVectorType(state, numeric_class, 4), color,
|
||||
descriptor, coord, spv::ImageOperandsLodMask,
|
||||
LodU32(ctx, mem, *address, dimension));
|
||||
}
|
||||
return ResultVector(ctx, UnpackImageTexel(ctx, mem, color), numeric_class,
|
||||
false, mem);
|
||||
@@ -638,7 +637,7 @@ void EmitImage(ValueEmitContext& ctx, const IR::Inst& inst) {
|
||||
if (op == IR::ValueOpcode::ImageSampleRaw || op == IR::ValueOpcode::ImageGatherRaw) {
|
||||
const auto dimension = image.dimension;
|
||||
const auto& dimension_info = ImageDimensionInfoFor(dimension);
|
||||
const auto layout = Layout(mem, dimension);
|
||||
const auto layout = Layout(mem);
|
||||
const auto numeric_class = image.numeric_class;
|
||||
const bool dref = HasFlag(mem, Decoder::ImageSampleFlagCompare);
|
||||
if (dref && state.program.info.images[mem.resource].conversion_format !=
|
||||
@@ -646,9 +645,16 @@ void EmitImage(ValueEmitContext& ctx, const IR::Inst& inst) {
|
||||
ctx.Fail(inst, "uses depth comparison with a packed integer image");
|
||||
return;
|
||||
}
|
||||
const auto coord =
|
||||
CoordF32(ctx, mem, *address, layout.coord, dimension_info.coordinate_components);
|
||||
if (op == IR::ValueOpcode::ImageGatherRaw) {
|
||||
const auto coord = CoordF32(ctx, mem, *address, layout.coord,
|
||||
dimension_info.coordinate_components, image.cube);
|
||||
if (HasFlag(mem, Decoder::ImageSampleFlagLod)) {
|
||||
static std::atomic_flag warned = ATOMIC_FLAG_INIT;
|
||||
if (!warned.test_and_set(std::memory_order_relaxed)) {
|
||||
std::fputs("Warning: approximating IMAGE_GATHER4_L at mip level 0; explicit LOD is ignored.\n",
|
||||
stderr);
|
||||
}
|
||||
}
|
||||
if (dimension == ImageDimension::Dim1D) {
|
||||
if (dref || !HasFlag(mem, Decoder::ImageSampleFlagLevelZero) ||
|
||||
HasFlag(mem, Decoder::ImageSampleFlagOffset) ||
|
||||
@@ -711,7 +717,7 @@ void EmitImage(ValueEmitContext& ctx, const IR::Inst& inst) {
|
||||
HasFlag(mem, Decoder::ImageSampleFlagLod) ||
|
||||
HasFlag(mem, Decoder::ImageSampleFlagLevelZero) ||
|
||||
state.program.stage != ShaderType::Pixel;
|
||||
uint32_t opcode = spv::OpImageSampleImplicitLod;
|
||||
auto opcode = spv::OpImageSampleImplicitLod;
|
||||
if (explicit_lod) {
|
||||
opcode = dref ? spv::OpImageSampleDrefExplicitLod : spv::OpImageSampleExplicitLod;
|
||||
} else if (dref) {
|
||||
@@ -746,17 +752,22 @@ void EmitImage(ValueEmitContext& ctx, const IR::Inst& inst) {
|
||||
operands.push_back(AddressF32(ctx, mem, *address, layout.bias));
|
||||
}
|
||||
const auto EmitSample = [&](uint32_t resource) {
|
||||
const auto& candidate = state.program.info.images[resource];
|
||||
const auto coord =
|
||||
CoordF32(ctx, mem, *address, layout.coord,
|
||||
ImageDimensionInfoFor(candidate.dimension).coordinate_components,
|
||||
candidate.cube);
|
||||
const auto sampled = MakeSampledImage(state, resource, mem.sampler);
|
||||
const auto sample = state.builder.AllocateId();
|
||||
std::vector<uint32_t> words {opcode, result_type, sample, sampled, coord};
|
||||
std::vector<uint32_t> sample_operands {result_type, sample, sampled, coord};
|
||||
if (dref) {
|
||||
words.push_back(dref_value);
|
||||
sample_operands.push_back(dref_value);
|
||||
}
|
||||
if (operand_mask != 0u) {
|
||||
words.push_back(operand_mask);
|
||||
words.insert(words.end(), operands.begin(), operands.end());
|
||||
sample_operands.push_back(operand_mask);
|
||||
sample_operands.insert(sample_operands.end(), operands.begin(), operands.end());
|
||||
}
|
||||
state.builder.AddFunction(words);
|
||||
state.builder.AddFunction(opcode, sample_operands);
|
||||
return sample;
|
||||
};
|
||||
if (image.indirect_root != mem.resource) {
|
||||
@@ -785,11 +796,11 @@ void EmitImage(ValueEmitContext& ctx, const IR::Inst& inst) {
|
||||
}
|
||||
const auto LoadMapping = [&](uint32_t index) {
|
||||
const auto pointer = state.builder.AllocateId();
|
||||
state.builder.AddFunction({spv::OpAccessChain, TypeStorageBufferElementPointer(state),
|
||||
pointer, state.flattened_srt_variable, ConstantU32(state, 0),
|
||||
index});
|
||||
state.builder.AddFunction(spv::OpAccessChain, TypeStorageBufferElementPointer(state),
|
||||
pointer, state.flattened_srt_variable, ConstantU32(state, 0),
|
||||
index);
|
||||
const auto value = state.builder.AllocateId();
|
||||
state.builder.AddFunction({spv::OpLoad, TypeU32(state), value, pointer});
|
||||
state.builder.AddFunction(spv::OpLoad, TypeU32(state), value, pointer);
|
||||
return value;
|
||||
};
|
||||
const auto mapping = ConstantU32(state, image.indirect_mapping_offset);
|
||||
@@ -802,9 +813,9 @@ void EmitImage(ValueEmitContext& ctx, const IR::Inst& inst) {
|
||||
const auto mid = Binary(state, spv::OpShiftRightLogical, TypeU32(state),
|
||||
Binary(state, spv::OpIAdd, TypeU32(state), low, high),
|
||||
ConstantU32(state, 1u));
|
||||
const auto probe = state.builder.AllocateId();
|
||||
state.builder.AddFunction(
|
||||
{spv::OpSelect, TypeU32(state), probe, active, mid, ConstantU32(state, 0u)});
|
||||
const auto probe = state.builder.AllocateId();
|
||||
state.builder.AddFunction(spv::OpSelect, TypeU32(state), probe, active, mid,
|
||||
ConstantU32(state, 0u));
|
||||
const auto entry = Binary(state, spv::OpIAdd, TypeU32(state), mapping,
|
||||
Binary(state, spv::OpIAdd, TypeU32(state),
|
||||
Binary(state, spv::OpShiftLeftLogical, TypeU32(state),
|
||||
@@ -816,8 +827,8 @@ void EmitImage(ValueEmitContext& ctx, const IR::Inst& inst) {
|
||||
const auto equal = Binary(state, spv::OpIEqual, TypeBool(state), mapped_key, key);
|
||||
const auto match = Binary(state, spv::OpLogicalAnd, TypeBool(state), active, equal);
|
||||
const auto next_selected = state.builder.AllocateId();
|
||||
state.builder.AddFunction(
|
||||
{spv::OpSelect, TypeU32(state), next_selected, match, candidate, selected});
|
||||
state.builder.AddFunction(spv::OpSelect, TypeU32(state), next_selected, match,
|
||||
candidate, selected);
|
||||
selected = next_selected;
|
||||
const auto less = Binary(state, spv::OpULessThan, TypeBool(state), mapped_key, key);
|
||||
const auto take_upper = Binary(state, spv::OpLogicalAnd, TypeBool(state), active, less);
|
||||
@@ -825,12 +836,12 @@ void EmitImage(ValueEmitContext& ctx, const IR::Inst& inst) {
|
||||
Unary(state, spv::OpLogicalNot, TypeBool(state), less));
|
||||
const auto next_low = state.builder.AllocateId();
|
||||
state.builder.AddFunction(
|
||||
{spv::OpSelect, TypeU32(state), next_low, take_upper,
|
||||
Binary(state, spv::OpIAdd, TypeU32(state), mid, ConstantU32(state, 1u)), low});
|
||||
spv::OpSelect, TypeU32(state), next_low, take_upper,
|
||||
Binary(state, spv::OpIAdd, TypeU32(state), mid, ConstantU32(state, 1u)), low);
|
||||
low = next_low;
|
||||
const auto next_high = state.builder.AllocateId();
|
||||
state.builder.AddFunction(
|
||||
{spv::OpSelect, TypeU32(state), next_high, take_lower, mid, high});
|
||||
state.builder.AddFunction(spv::OpSelect, TypeU32(state), next_high, take_lower, mid,
|
||||
high);
|
||||
high = next_high;
|
||||
}
|
||||
const auto default_label = state.builder.AllocateId();
|
||||
@@ -842,19 +853,19 @@ void EmitImage(ValueEmitContext& ctx, const IR::Inst& inst) {
|
||||
switch_words.push_back(candidate);
|
||||
switch_words.push_back(labels[candidate - 1u]);
|
||||
}
|
||||
state.builder.AddFunction(
|
||||
{spv::OpSelectionMerge, merge_label, spv::SelectionControlMaskNone});
|
||||
state.builder.AddFunction(spv::OpSelectionMerge, merge_label,
|
||||
spv::SelectionControlMaskNone);
|
||||
state.builder.AddFunction(switch_words);
|
||||
std::vector<uint32_t> phi_words {spv::OpPhi, result_type, state.builder.AllocateId()};
|
||||
EmitLabel(state, default_label);
|
||||
phi_words.push_back(EmitSample(image.indirect_resources[0]));
|
||||
phi_words.push_back(default_label);
|
||||
state.builder.AddFunction({spv::OpBranch, merge_label});
|
||||
state.builder.AddFunction(spv::OpBranch, merge_label);
|
||||
for (uint32_t candidate = 1; candidate < image.indirect_resources.size(); candidate++) {
|
||||
EmitLabel(state, labels[candidate - 1u]);
|
||||
phi_words.push_back(EmitSample(image.indirect_resources[candidate]));
|
||||
phi_words.push_back(labels[candidate - 1u]);
|
||||
state.builder.AddFunction({spv::OpBranch, merge_label});
|
||||
state.builder.AddFunction(spv::OpBranch, merge_label);
|
||||
}
|
||||
EmitLabel(state, merge_label);
|
||||
state.builder.AddFunction(phi_words);
|
||||
@@ -866,21 +877,21 @@ void EmitImage(ValueEmitContext& ctx, const IR::Inst& inst) {
|
||||
return;
|
||||
}
|
||||
const auto atomic_opcode = ImageAtomicOpcode(op);
|
||||
if (atomic_opcode != 0u) {
|
||||
if (atomic_opcode != spv::OpNop) {
|
||||
const auto dimension = image.dimension;
|
||||
ctx.Define(inst, EmitValueOrZeroIfCondition(state, ctx.Arg(inst, 3), [&]() {
|
||||
const auto pointer = state.builder.AllocateId();
|
||||
const auto pointer = state.builder.AllocateId();
|
||||
const auto pointer_type = state.builder.Type(
|
||||
spv::OpTypePointer, {spv::StorageClassImage, TypeU32(state)});
|
||||
state.builder.AddFunction(
|
||||
{spv::OpImageTexelPointer, pointer_type, pointer,
|
||||
StorageImageDescriptorPointer(state, mem.resource),
|
||||
CoordU32(ctx, mem, *address, dimension), ConstantU32(state, 0)});
|
||||
spv::OpTypePointer, spv::StorageClassImage, TypeU32(state));
|
||||
state.builder.AddFunction(spv::OpImageTexelPointer, pointer_type, pointer,
|
||||
StorageImageDescriptorPointer(state, mem.resource),
|
||||
CoordU32(ctx, mem, *address, dimension),
|
||||
ConstantU32(state, 0));
|
||||
const auto old = state.builder.AllocateId();
|
||||
state.builder.AddFunction({atomic_opcode, TypeU32(state), old, pointer,
|
||||
ConstantU32(state, spv::ScopeDevice),
|
||||
ConstantU32(state, spv::MemorySemanticsMaskNone),
|
||||
ctx.Arg(inst, 2)});
|
||||
state.builder.AddFunction(atomic_opcode, TypeU32(state), old, pointer,
|
||||
ConstantU32(state, spv::ScopeDevice),
|
||||
ConstantU32(state, spv::MemorySemanticsMaskNone),
|
||||
ctx.Arg(inst, 2));
|
||||
EmitDeviceAtomicMemoryBarrier(state);
|
||||
return old;
|
||||
}));
|
||||
|
||||
@@ -155,6 +155,8 @@ inline constexpr auto EmitInstPrefetch = EmitVoid;
|
||||
void EmitBarrier(EmitterState& state);
|
||||
void EmitMeshAllocate(ValueEmitContext& ctx, const IR::Inst& inst);
|
||||
uint32_t EmitMeshDrawParameter(ValueEmitContext& ctx, const IR::Inst& inst);
|
||||
uint32_t EmitGetTessellationAttribute(ValueEmitContext& ctx, const IR::Inst& inst);
|
||||
void EmitSetTessellationAttribute(ValueEmitContext& ctx, const IR::Inst& inst);
|
||||
uint32_t EmitGetUserData(EmitterState& state, IR::ScalarReg reg);
|
||||
uint32_t EmitGetBuiltin(ValueEmitContext& ctx, IR::Value kind, IR::Value index);
|
||||
uint32_t EmitUndefU1(EmitterState& state, const IR::Inst& inst);
|
||||
@@ -165,7 +167,7 @@ inline constexpr auto EmitUndefU64 = EmitUndefU1;
|
||||
uint32_t EmitDppMoveU32(ValueEmitContext& ctx, const IR::Inst& inst);
|
||||
uint32_t EmitDppUpdateU32(ValueEmitContext& ctx, const IR::Inst& inst);
|
||||
uint32_t EmitWqmU64(EmitterState& state, uint32_t value);
|
||||
inline constexpr auto EmitLaneId = EmitSubgroupLocalInvocationId;
|
||||
uint32_t EmitLaneId(EmitterState& state);
|
||||
uint32_t EmitBallot(ValueEmitContext& ctx, IR::Value predicate);
|
||||
uint32_t EmitReadFirstLane(ValueEmitContext& ctx, const IR::Inst& inst);
|
||||
uint32_t EmitReadLane(ValueEmitContext& ctx, const IR::Inst& inst);
|
||||
@@ -271,6 +273,7 @@ inline constexpr auto EmitImageAtomicOr32 = EmitImage;
|
||||
inline constexpr auto EmitImageAtomicXor32 = EmitImage;
|
||||
void EmitUnreachable(ValueEmitContext& ctx, const IR::Inst& inst);
|
||||
inline constexpr auto EmitPhi = EmitUnreachable;
|
||||
inline constexpr auto EmitTessellationBase = EmitUnreachable;
|
||||
inline constexpr auto EmitGetThreadBitScalarRegister = EmitUnreachable;
|
||||
inline constexpr auto EmitSetThreadBitScalarRegister = EmitUnreachable;
|
||||
inline constexpr auto EmitGetScalarMaskTag = EmitUnreachable;
|
||||
|
||||
@@ -80,6 +80,10 @@ struct EmitterState {
|
||||
Builder builder;
|
||||
const IR::Program& program;
|
||||
ShaderStageInputInfo input_info;
|
||||
std::array<uint32_t, 6> tess_variables {};
|
||||
uint32_t tess_inner_variable = 0;
|
||||
uint32_t tess_patch_base = 0;
|
||||
|
||||
const SpirvRequirements requirements;
|
||||
uint32_t lane_count = 1;
|
||||
uint32_t lane_half = 0;
|
||||
@@ -112,6 +116,7 @@ struct EmitterState {
|
||||
uint32_t per_vertex_variable = 0;
|
||||
uint32_t point_size_variable = 0;
|
||||
uint32_t clip_distance_variable = 0;
|
||||
uint32_t invalid_position_clip_distance = UINT32_MAX;
|
||||
uint32_t cull_distance_variable = 0;
|
||||
uint32_t layer_variable = 0;
|
||||
uint32_t viewport_index_variable = 0;
|
||||
@@ -151,7 +156,7 @@ uint32_t TypeU32ElementPointer(EmitterState& state, spv::StorageClass storage_cl
|
||||
|
||||
inline void EmitLabel(EmitterState& state, uint32_t label) {
|
||||
state.current_label = label;
|
||||
state.builder.AddFunction({spv::OpLabel, label});
|
||||
state.builder.AddFunction(spv::OpLabel, label);
|
||||
}
|
||||
|
||||
uint32_t TypeId(EmitterState& state, IR::Type type);
|
||||
@@ -160,7 +165,7 @@ uint32_t TypeId(EmitterState& state, IR::Type type);
|
||||
template <spv::Op opcode, IR::Type type, typename... Args>
|
||||
uint32_t EmitNative(EmitterState& state, Args... args) {
|
||||
const auto result = state.builder.AllocateId();
|
||||
state.builder.AddFunction({opcode, TypeId(state, type), result, args...});
|
||||
state.builder.AddFunction(opcode, TypeId(state, type), result, args...);
|
||||
return result;
|
||||
}
|
||||
|
||||
@@ -171,23 +176,23 @@ uint32_t EmitGlsl(EmitterState& state, Args... args) {
|
||||
return EmitNative<spv::OpExtInst, type>(state, GlslStd450(state), opcode, args...);
|
||||
}
|
||||
|
||||
inline uint32_t Unary(EmitterState& state, uint32_t opcode, uint32_t type, uint32_t value) {
|
||||
inline uint32_t Unary(EmitterState& state, spv::Op opcode, uint32_t type, uint32_t value) {
|
||||
const auto result = state.builder.AllocateId();
|
||||
state.builder.AddFunction({opcode, type, result, value});
|
||||
state.builder.AddFunction(opcode, type, result, value);
|
||||
return result;
|
||||
}
|
||||
|
||||
inline uint32_t Binary(EmitterState& state, uint32_t opcode, uint32_t type, uint32_t lhs,
|
||||
inline uint32_t Binary(EmitterState& state, spv::Op opcode, uint32_t type, uint32_t lhs,
|
||||
uint32_t rhs) {
|
||||
const auto result = state.builder.AllocateId();
|
||||
state.builder.AddFunction({opcode, type, result, lhs, rhs});
|
||||
state.builder.AddFunction(opcode, type, result, lhs, rhs);
|
||||
return result;
|
||||
}
|
||||
|
||||
inline uint32_t Select(EmitterState& state, uint32_t type, uint32_t condition, uint32_t true_value,
|
||||
uint32_t false_value) {
|
||||
const auto result = state.builder.AllocateId();
|
||||
state.builder.AddFunction({spv::OpSelect, type, result, condition, true_value, false_value});
|
||||
state.builder.AddFunction(spv::OpSelect, type, result, condition, true_value, false_value);
|
||||
return result;
|
||||
}
|
||||
|
||||
@@ -303,8 +308,6 @@ uint32_t ConstantI32(EmitterState& state, int32_t value);
|
||||
|
||||
uint32_t ConstantF32(EmitterState& state, uint32_t bits);
|
||||
|
||||
uint32_t FloatBits(float value);
|
||||
|
||||
uint32_t ConstantF32Value(EmitterState& state, float value);
|
||||
|
||||
uint32_t ConstantBool(EmitterState& state, bool value);
|
||||
@@ -313,14 +316,19 @@ uint32_t ConstantU64(EmitterState& state, uint64_t value);
|
||||
|
||||
uint32_t ConstantU32CompositeZero(EmitterState& state, uint32_t components);
|
||||
|
||||
uint32_t DefineInterfaceVariable(EmitterState& state, uint32_t type, spv::StorageClass storage,
|
||||
const char* name);
|
||||
void DefineModule(EmitterState& state);
|
||||
void DefineTessellationInterfaces(EmitterState& state);
|
||||
void DefineTessellationExecutionModes(EmitterState& state);
|
||||
void DefineMeshOutputs(EmitterState& state);
|
||||
void EmitMeshEntryPoint(EmitterState& state);
|
||||
void EmitMeshAllocate(ValueEmitContext& ctx, const IR::Inst& inst);
|
||||
uint32_t MeshOutputPointer(EmitterState& state, IR::StageOutputKind kind, uint32_t index = 0);
|
||||
uint32_t MeshPrimitivePointer(EmitterState& state);
|
||||
|
||||
DppTargetLane EmitDppQuadPermTargetLane(EmitterState& state, uint32_t subid, uint32_t control);
|
||||
DppTargetLane EmitDppPermTargetLane(EmitterState& state, uint32_t subid, uint32_t control,
|
||||
uint32_t lane_bits);
|
||||
|
||||
DppTargetLane EmitDppRowShiftTargetLane(EmitterState& state, uint32_t subid, uint32_t amount,
|
||||
bool left);
|
||||
@@ -329,7 +337,7 @@ DppTargetLane EmitDppRowRotateRightTargetLane(EmitterState& state, uint32_t subi
|
||||
|
||||
DppTargetLane EmitDppMirrorTargetLane(EmitterState& state, uint32_t subid, bool half_row);
|
||||
|
||||
DppTargetLane EmitDppTargetLane(EmitterState& state, uint32_t control);
|
||||
DppTargetLane EmitDppTargetLane(EmitterState& state, const IR::DppMoveFlags& flags);
|
||||
|
||||
uint32_t EmitSubgroupLocalInvocationId(EmitterState& state);
|
||||
|
||||
@@ -350,7 +358,7 @@ uint32_t EmitSubgroupLaneActiveBool(EmitterState& state, uint32_t lane);
|
||||
|
||||
inline constexpr auto EmitAddU32 = EmitNative<spv::OpIAdd, IR::Type::U32, uint32_t, uint32_t>;
|
||||
|
||||
uint32_t EmitBinaryU32(EmitterState& state, uint32_t opcode, uint32_t lhs, uint32_t rhs);
|
||||
uint32_t EmitBinaryU32(EmitterState& state, spv::Op opcode, uint32_t lhs, uint32_t rhs);
|
||||
|
||||
uint32_t EmitShaderDataDwordLoad(EmitterState& state, uint32_t dword_index);
|
||||
|
||||
@@ -417,7 +425,7 @@ uint32_t EmitShiftRightConstant(EmitterState& state, uint32_t value, uint32_t sh
|
||||
|
||||
inline constexpr auto EmitOrU32 = EmitNative<spv::OpBitwiseOr, IR::Type::U32, uint32_t, uint32_t>;
|
||||
|
||||
uint32_t EmitCompareU32Constant(EmitterState& state, uint32_t opcode, uint32_t value,
|
||||
uint32_t EmitCompareU32Constant(EmitterState& state, spv::Op opcode, uint32_t value,
|
||||
uint32_t constant);
|
||||
|
||||
uint32_t EmitSubConstantMinusU32(EmitterState& state, uint32_t constant, uint32_t value);
|
||||
@@ -474,11 +482,11 @@ template <typename Fn>
|
||||
void EmitIfCondition(EmitterState& state, uint32_t condition, Fn&& fn) {
|
||||
const auto then_label = state.builder.AllocateId();
|
||||
const auto merge_label = state.builder.AllocateId();
|
||||
state.builder.AddFunction({spv::OpSelectionMerge, merge_label, spv::SelectionControlMaskNone});
|
||||
state.builder.AddFunction({spv::OpBranchConditional, condition, then_label, merge_label});
|
||||
state.builder.AddFunction(spv::OpSelectionMerge, merge_label, spv::SelectionControlMaskNone);
|
||||
state.builder.AddFunction(spv::OpBranchConditional, condition, then_label, merge_label);
|
||||
EmitLabel(state, then_label);
|
||||
fn();
|
||||
state.builder.AddFunction({spv::OpBranch, merge_label});
|
||||
state.builder.AddFunction(spv::OpBranch, merge_label);
|
||||
EmitLabel(state, merge_label);
|
||||
}
|
||||
|
||||
@@ -486,22 +494,18 @@ template <typename Fn>
|
||||
uint32_t EmitValueOrDefaultIfCondition(EmitterState& state, uint32_t condition, uint32_t type,
|
||||
uint32_t default_value, Fn&& fn) {
|
||||
const auto then_label = state.builder.AllocateId();
|
||||
const auto then_exit = state.builder.AllocateId();
|
||||
const auto else_label = state.builder.AllocateId();
|
||||
const auto header = state.current_label;
|
||||
const auto merge_label = state.builder.AllocateId();
|
||||
state.builder.AddFunction({spv::OpSelectionMerge, merge_label, spv::SelectionControlMaskNone});
|
||||
state.builder.AddFunction({spv::OpBranchConditional, condition, then_label, else_label});
|
||||
state.builder.AddFunction(spv::OpSelectionMerge, merge_label, spv::SelectionControlMaskNone);
|
||||
state.builder.AddFunction(spv::OpBranchConditional, condition, then_label, merge_label);
|
||||
EmitLabel(state, then_label);
|
||||
const auto then_value = fn();
|
||||
state.builder.AddFunction({spv::OpBranch, then_exit});
|
||||
EmitLabel(state, then_exit);
|
||||
state.builder.AddFunction({spv::OpBranch, merge_label});
|
||||
EmitLabel(state, else_label);
|
||||
state.builder.AddFunction({spv::OpBranch, merge_label});
|
||||
const auto then_exit = state.current_label;
|
||||
state.builder.AddFunction(spv::OpBranch, merge_label);
|
||||
EmitLabel(state, merge_label);
|
||||
const auto value = state.builder.AllocateId();
|
||||
state.builder.AddFunction(
|
||||
{spv::OpPhi, type, value, then_value, then_exit, default_value, else_label});
|
||||
state.builder.AddFunction(spv::OpPhi, type, value, then_value, then_exit, default_value,
|
||||
header);
|
||||
return value;
|
||||
}
|
||||
|
||||
@@ -528,30 +532,29 @@ uint32_t AtomicUpdate(EmitterState& state, uint32_t pointer, IR::ResourceKind ki
|
||||
const auto initial = state.builder.AllocateId();
|
||||
const auto observed = state.builder.AllocateId();
|
||||
const auto exchanged = state.builder.AllocateId();
|
||||
state.builder.AddFunction({spv::OpBranch, preheader});
|
||||
state.builder.AddFunction(spv::OpBranch, preheader);
|
||||
EmitLabel(state, preheader);
|
||||
state.builder.AddFunction({spv::OpAtomicLoad, TypeU32(state), initial, pointer,
|
||||
ConstantU32(state, scope),
|
||||
ConstantU32(state, spv::MemorySemanticsMaskNone)});
|
||||
state.builder.AddFunction({spv::OpBranch, header});
|
||||
state.builder.AddFunction(spv::OpAtomicLoad, TypeU32(state), initial, pointer,
|
||||
ConstantU32(state, scope),
|
||||
ConstantU32(state, spv::MemorySemanticsMaskNone));
|
||||
state.builder.AddFunction(spv::OpBranch, header);
|
||||
EmitLabel(state, header);
|
||||
state.builder.AddFunction(
|
||||
{spv::OpPhi, TypeU32(state), observed, initial, preheader, exchanged, cont});
|
||||
state.builder.AddFunction(spv::OpPhi, TypeU32(state), observed, initial, preheader, exchanged,
|
||||
cont);
|
||||
const auto next = desired(observed);
|
||||
state.builder.AddFunction({spv::OpAtomicCompareExchange, TypeU32(state), exchanged, pointer,
|
||||
ConstantU32(state, scope),
|
||||
ConstantU32(state, spv::MemorySemanticsMaskNone),
|
||||
ConstantU32(state, spv::MemorySemanticsMaskNone), next, observed});
|
||||
state.builder.AddFunction(spv::OpAtomicCompareExchange, TypeU32(state), exchanged, pointer,
|
||||
ConstantU32(state, scope),
|
||||
ConstantU32(state, spv::MemorySemanticsMaskNone),
|
||||
ConstantU32(state, spv::MemorySemanticsMaskNone), next, observed);
|
||||
const auto success = state.builder.AllocateId();
|
||||
state.builder.AddFunction({spv::OpIEqual, TypeBool(state), success, exchanged, observed});
|
||||
state.builder.AddFunction({spv::OpLoopMerge, merge, cont, spv::LoopControlMaskNone});
|
||||
state.builder.AddFunction({spv::OpBranchConditional, success, merge, cont});
|
||||
state.builder.AddFunction(spv::OpIEqual, TypeBool(state), success, exchanged, observed);
|
||||
state.builder.AddFunction(spv::OpLoopMerge, merge, cont, spv::LoopControlMaskNone);
|
||||
state.builder.AddFunction(spv::OpBranchConditional, success, merge, cont);
|
||||
EmitLabel(state, cont);
|
||||
state.builder.AddFunction({spv::OpBranch, header});
|
||||
state.builder.AddFunction(spv::OpBranch, header);
|
||||
EmitLabel(state, merge);
|
||||
state.builder.AddFunction(
|
||||
{spv::OpMemoryBarrier, ConstantU32(state, scope),
|
||||
ConstantU32(state, spv::MemorySemanticsAcquireReleaseMask | memory)});
|
||||
state.builder.AddFunction(spv::OpMemoryBarrier, ConstantU32(state, scope),
|
||||
ConstantU32(state, spv::MemorySemanticsAcquireReleaseMask | memory));
|
||||
return observed;
|
||||
}
|
||||
|
||||
|
||||
@@ -17,71 +17,81 @@ uint32_t EmitDsMaskedLaneRead(EmitterState& state, uint32_t source, uint32_t tar
|
||||
target = Binary(state, spv::OpBitwiseAnd, TypeU32(state), target, ConstantU32(state, 31));
|
||||
}
|
||||
const auto shuffled = state.builder.AllocateId();
|
||||
state.builder.AddFunction({spv::OpGroupNonUniformShuffle, TypeU32(state), shuffled,
|
||||
ConstantU32(state, spv::ScopeSubgroup), source, target});
|
||||
state.builder.AddFunction(spv::OpGroupNonUniformShuffle, TypeU32(state), shuffled,
|
||||
ConstantU32(state, spv::ScopeSubgroup), source, target);
|
||||
const auto source_exec = state.builder.AllocateId();
|
||||
state.builder.AddFunction({spv::OpGroupNonUniformShuffle, TypeBool(state), source_exec,
|
||||
ConstantU32(state, spv::ScopeSubgroup), exec, target});
|
||||
state.builder.AddFunction(spv::OpGroupNonUniformShuffle, TypeBool(state), source_exec,
|
||||
ConstantU32(state, spv::ScopeSubgroup), exec, target);
|
||||
const auto source_active =
|
||||
AndCondition(state, source_exec, EmitSubgroupLaneActiveBool(state, target));
|
||||
return Select(state, TypeU32(state), source_active, shuffled, ConstantU32(state, 0));
|
||||
}
|
||||
|
||||
uint32_t BufferByteAddress(ValueEmitContext& ctx, const IR::Inst& inst, const IR::MemoryInfo& mem,
|
||||
uint32_t index, uint32_t offset, uint32_t soffset) {
|
||||
auto& state = ctx.state;
|
||||
const uint32_t packed = StorageBufferPackedStride(state, mem);
|
||||
const uint32_t stride = packed & 0x3fffu;
|
||||
const bool swizzle = stride != 0u && ((packed >> 14u) & 1u) != 0u;
|
||||
if (((packed >> 20u) & 1u) != 0u) {
|
||||
const auto lane = Binary(state, spv::OpBitwiseAnd, TypeU32(state),
|
||||
EmitSubgroupLocalInvocationId(state), ConstantU32(state, 63));
|
||||
index = Binary(state, spv::OpIAdd, TypeU32(state), index, lane);
|
||||
}
|
||||
if (mem.offset != 0u) {
|
||||
offset = Binary(state, spv::OpIAdd, TypeU32(state), offset, ConstantU32(state, mem.offset));
|
||||
}
|
||||
struct BufferAddress {
|
||||
uint32_t offset;
|
||||
uint32_t byte;
|
||||
};
|
||||
|
||||
uint32_t address = 0;
|
||||
if (!swizzle) {
|
||||
if (stride == 0u) {
|
||||
address = offset;
|
||||
} else {
|
||||
const auto indexed = stride == 1u ? index
|
||||
: Binary(state, spv::OpIMul, TypeU32(state), index,
|
||||
ConstantU32(state, stride));
|
||||
address = Binary(state, spv::OpIAdd, TypeU32(state), indexed, offset);
|
||||
}
|
||||
} else {
|
||||
const uint32_t stride_enum = (packed >> 16u) & 3u;
|
||||
const uint32_t index_stride = 8u << stride_enum;
|
||||
const auto index_msb = Binary(state, spv::OpShiftRightLogical, TypeU32(state), index,
|
||||
ConstantU32(state, stride_enum + 3u));
|
||||
const auto index_lsb = Binary(state, spv::OpBitwiseAnd, TypeU32(state), index,
|
||||
ConstantU32(state, index_stride - 1u));
|
||||
const auto offset_msb =
|
||||
BufferAddress CalculateBufferAddress(EmitterState& state, uint32_t index, uint32_t offset,
|
||||
uint32_t soffset, uint32_t immediate, uint32_t stride,
|
||||
uint32_t swizzle, uint32_t index_stride) {
|
||||
const auto zero = ConstantU32(state, 0);
|
||||
const auto one = ConstantU32(state, 1);
|
||||
const auto add = [&](uint32_t lhs, uint32_t rhs) {
|
||||
return lhs == zero ? rhs : rhs == zero ? lhs
|
||||
: Binary(state, spv::OpIAdd, TypeU32(state), lhs, rhs);
|
||||
};
|
||||
const auto mul = [&](uint32_t lhs, uint32_t rhs) {
|
||||
return lhs == zero || rhs == zero ? zero
|
||||
: lhs == one ? rhs
|
||||
: rhs == one ? lhs
|
||||
: Binary(state, spv::OpIMul, TypeU32(state), lhs, rhs);
|
||||
};
|
||||
if (immediate != 0u) {
|
||||
offset = add(offset, ConstantU32(state, immediate));
|
||||
}
|
||||
auto address = add(mul(index, stride), offset);
|
||||
if (swizzle != 0u) {
|
||||
const auto index_shift =
|
||||
Binary(state, spv::OpIAdd, TypeU32(state), index_stride, ConstantU32(state, 3));
|
||||
const auto indices = Binary(state, spv::OpShiftLeftLogical, TypeU32(state),
|
||||
ConstantU32(state, 1), index_shift);
|
||||
const auto index_msb =
|
||||
Binary(state, spv::OpShiftRightLogical, TypeU32(state), index, index_shift);
|
||||
const auto index_lsb =
|
||||
Binary(state, spv::OpBitwiseAnd, TypeU32(state), index,
|
||||
Binary(state, spv::OpISub, TypeU32(state), indices, ConstantU32(state, 1)));
|
||||
const auto offset_msb =
|
||||
Binary(state, spv::OpBitwiseAnd, TypeU32(state), offset, ConstantU32(state, ~3u));
|
||||
const auto offset_lsb =
|
||||
Binary(state, spv::OpBitwiseAnd, TypeU32(state), offset, ConstantU32(state, 3u));
|
||||
const auto indexed_msb = stride == 1u ? index_msb
|
||||
: Binary(state, spv::OpIMul, TypeU32(state),
|
||||
index_msb, ConstantU32(state, stride));
|
||||
const auto msb = Binary(state, spv::OpIMul, TypeU32(state),
|
||||
Binary(state, spv::OpIAdd, TypeU32(state), indexed_msb, offset_msb),
|
||||
ConstantU32(state, index_stride));
|
||||
const auto lsb = Binary(state, spv::OpIAdd, TypeU32(state),
|
||||
Binary(state, spv::OpShiftLeftLogical, TypeU32(state), index_lsb,
|
||||
ConstantU32(state, 2u)),
|
||||
offset_lsb);
|
||||
address = Binary(state, spv::OpIAdd, TypeU32(state), msb, lsb);
|
||||
const auto msb = mul(add(mul(index_msb, stride), offset_msb), indices);
|
||||
const auto lsb = add(Binary(state, spv::OpShiftLeftLogical, TypeU32(state), index_lsb,
|
||||
ConstantU32(state, 2u)), offset_lsb);
|
||||
address = Select(state, TypeU32(state), swizzle, add(msb, lsb), address);
|
||||
}
|
||||
return {offset, add(address, soffset)};
|
||||
}
|
||||
|
||||
const auto soffset_value = inst.Arg(3).Resolve();
|
||||
if (soffset_value.IsImmediate() && soffset_value.GetType() == IR::Type::U32 &&
|
||||
soffset_value.U32() == 0u) {
|
||||
return address;
|
||||
uint32_t BufferLane(EmitterState& state) {
|
||||
return Binary(state, spv::OpBitwiseAnd, TypeU32(state), EmitSubgroupLocalInvocationId(state),
|
||||
ConstantU32(state, 63));
|
||||
}
|
||||
|
||||
uint32_t BufferByteAddress(ValueEmitContext& ctx, const IR::Inst& inst, const IR::MemoryInfo& mem) {
|
||||
auto& state = ctx.state;
|
||||
const auto packed = StorageBufferPackedStride(state, mem);
|
||||
const auto stride = packed & 0x3fffu;
|
||||
auto index = ctx.Arg(inst, 1);
|
||||
if ((packed & (1u << 20u)) != 0u) {
|
||||
index = Binary(state, spv::OpIAdd, TypeU32(state), index, BufferLane(state));
|
||||
}
|
||||
return Binary(state, spv::OpIAdd, TypeU32(state), address, soffset);
|
||||
const bool swizzle = stride != 0u && (packed & (1u << 14u)) != 0u;
|
||||
return CalculateBufferAddress(state, index, ctx.Arg(inst, 2), ctx.Arg(inst, 3), mem.offset,
|
||||
ConstantU32(state, stride),
|
||||
swizzle ? ConstantBool(state, true) : 0u,
|
||||
ConstantU32(state, (packed >> 16u) & 3u))
|
||||
.byte;
|
||||
}
|
||||
|
||||
uint32_t AddU64Low(EmitterState& state, uint32_t low, uint32_t high, uint32_t add_low,
|
||||
@@ -107,9 +117,9 @@ uint32_t ScratchByteAddress(ValueEmitContext& ctx, const IR::MemoryInfo& mem, ui
|
||||
}
|
||||
|
||||
uint32_t ConstantDeviceAddress(EmitterState& state, uint64_t value) {
|
||||
return state.builder.Constant(
|
||||
spv::OpConstant, TypeScalarU64(state),
|
||||
{static_cast<uint32_t>(value), static_cast<uint32_t>(value >> 32u)});
|
||||
return state.builder.Constant(spv::OpConstant, TypeScalarU64(state),
|
||||
static_cast<uint32_t>(value),
|
||||
static_cast<uint32_t>(value >> 32u));
|
||||
}
|
||||
|
||||
uint32_t DeviceAddressFromWords(EmitterState& state, uint32_t low, uint32_t high) {
|
||||
@@ -153,8 +163,8 @@ uint32_t GuestAddress(ValueEmitContext& ctx, const IR::Inst& inst, const IR::Mem
|
||||
|
||||
uint32_t FaultElementPointer(EmitterState& state, uint32_t index) {
|
||||
const auto pointer = state.builder.AllocateId();
|
||||
state.builder.AddFunction({spv::OpAccessChain, TypeStorageBufferElementPointer(state), pointer,
|
||||
state.fault_buffer_variable, ConstantU32(state, 0), index});
|
||||
state.builder.AddFunction(spv::OpAccessChain, TypeStorageBufferElementPointer(state), pointer,
|
||||
state.fault_buffer_variable, ConstantU32(state, 0), index);
|
||||
return pointer;
|
||||
}
|
||||
|
||||
@@ -166,16 +176,16 @@ void RecordBdaFault(EmitterState& state, uint32_t page) {
|
||||
Binary(state, spv::OpBitwiseAnd, TypeU32(state), page, ConstantU32(state, 31)));
|
||||
const auto pointer = FaultElementPointer(state, word);
|
||||
const auto value = state.builder.AllocateId();
|
||||
state.builder.AddFunction({spv::OpLoad, TypeU32(state), value, pointer});
|
||||
state.builder.AddFunction(
|
||||
{spv::OpStore, pointer, Binary(state, spv::OpBitwiseOr, TypeU32(state), value, bit)});
|
||||
state.builder.AddFunction(spv::OpLoad, TypeU32(state), value, pointer);
|
||||
state.builder.AddFunction(spv::OpStore, pointer,
|
||||
Binary(state, spv::OpBitwiseOr, TypeU32(state), value, bit));
|
||||
}
|
||||
|
||||
uint32_t GetBdaPointer(ValueEmitContext& ctx, uint32_t address) {
|
||||
auto& state = ctx.state;
|
||||
const auto result = state.builder.AllocateId();
|
||||
state.builder.AddFunction(
|
||||
{spv::OpFunctionCall, TypeScalarU64(state), result, state.bda_pointer_function, address});
|
||||
state.builder.AddFunction(spv::OpFunctionCall, TypeScalarU64(state), result,
|
||||
state.bda_pointer_function, address);
|
||||
return result;
|
||||
}
|
||||
|
||||
@@ -186,20 +196,18 @@ uint32_t LoadBdaDword(ValueEmitContext& ctx, uint32_t address) {
|
||||
Binary(state, spv::OpINotEqual, TypeBool(state), bda, ConstantDeviceAddress(state, 0));
|
||||
return EmitValueOrZeroIfCondition(state, present, [&]() {
|
||||
const auto pointer = state.builder.AllocateId();
|
||||
state.builder.AddFunction(
|
||||
{spv::OpConvertUToPtr, TypePhysicalU32Pointer(state), pointer, bda});
|
||||
const auto value = state.builder.AllocateId();
|
||||
state.builder.AddFunction({spv::OpLoad, TypeU32(state), value, pointer,
|
||||
spv::MemoryAccessAlignedMask, sizeof(uint32_t)});
|
||||
state.builder.AddFunction(spv::OpConvertUToPtr, TypePhysicalU32Pointer(state), pointer,
|
||||
bda);
|
||||
const auto value = state.builder.AllocateId();
|
||||
constexpr uint32_t alignment = sizeof(uint32_t);
|
||||
state.builder.AddFunction(spv::OpLoad, TypeU32(state), value, pointer,
|
||||
spv::MemoryAccessAlignedMask, alignment);
|
||||
return value;
|
||||
});
|
||||
}
|
||||
|
||||
uint32_t LoadBda(ValueEmitContext& ctx, const IR::Inst& inst, const IR::MemoryInfo& mem,
|
||||
uint32_t bits) {
|
||||
auto& state = ctx.state;
|
||||
const auto address = GuestAddress(ctx, inst, mem);
|
||||
const auto active = ctx.Arg(inst, inst.NumArgs() - 1);
|
||||
uint32_t LoadBda(ValueEmitContext& ctx, uint32_t address, uint32_t active, uint32_t bits) {
|
||||
auto& state = ctx.state;
|
||||
return EmitValueOrZeroIfCondition(state, active, [&]() {
|
||||
const auto aligned = Binary(state, spv::OpBitwiseAnd, TypeScalarU64(state), address,
|
||||
ConstantDeviceAddress(state, ~uint64_t {3}));
|
||||
@@ -235,8 +243,7 @@ uint32_t LoadBda(ValueEmitContext& ctx, const IR::Inst& inst, const IR::MemoryIn
|
||||
|
||||
uint32_t ByteAddress(ValueEmitContext& ctx, const IR::Inst& inst, const IR::MemoryInfo& mem) {
|
||||
if (mem.kind == IR::ResourceKind::Buffer) {
|
||||
return BufferByteAddress(ctx, inst, mem, ctx.Arg(inst, 1), ctx.Arg(inst, 2),
|
||||
ctx.Arg(inst, 3));
|
||||
return BufferByteAddress(ctx, inst, mem);
|
||||
}
|
||||
if (mem.kind == IR::ResourceKind::Lds || mem.kind == IR::ResourceKind::Gds) {
|
||||
if (mem.offset == 0u) {
|
||||
@@ -306,7 +313,7 @@ uint32_t LoadWordInBounds(ValueEmitContext& ctx, const MemoryResourceAccess& res
|
||||
uint32_t index) {
|
||||
const auto value = ctx.state.builder.AllocateId();
|
||||
const auto pointer = EmitMemoryElementPointer(ctx.state, resource, index);
|
||||
ctx.state.builder.AddFunction({spv::OpLoad, TypeU32(ctx.state), value, pointer});
|
||||
ctx.state.builder.AddFunction(spv::OpLoad, TypeU32(ctx.state), value, pointer);
|
||||
return value;
|
||||
}
|
||||
|
||||
@@ -401,11 +408,11 @@ uint32_t LoadFormattedComponent(ValueEmitContext& ctx, const IR::MemoryInfo& mem
|
||||
const auto source_value =
|
||||
type == TypeI32(ctx.state) ? Unary(ctx.state, spv::OpBitcast, type, raw) : raw;
|
||||
const auto extracted = ctx.state.builder.AllocateId();
|
||||
ctx.state.builder.AddFunction(
|
||||
{IsSignedFormatComponent(info.type) ? spv::OpBitFieldSExtract : spv::OpBitFieldUExtract,
|
||||
type, extracted, source_value,
|
||||
ConstantU32(ctx.state, info.component_bit_offset[component]),
|
||||
ConstantU32(ctx.state, bits)});
|
||||
ctx.state.builder.AddFunction(IsSignedFormatComponent(info.type) ? spv::OpBitFieldSExtract
|
||||
: spv::OpBitFieldUExtract,
|
||||
type, extracted, source_value,
|
||||
ConstantU32(ctx.state, info.component_bit_offset[component]),
|
||||
ConstantU32(ctx.state, bits));
|
||||
raw = type == TypeI32(ctx.state)
|
||||
? Unary(ctx.state, spv::OpBitcast, TypeU32(ctx.state), extracted)
|
||||
: extracted;
|
||||
@@ -465,8 +472,8 @@ void StoreSubwordInBounds(ValueEmitContext& ctx, const IR::MemoryInfo& mem,
|
||||
};
|
||||
if (mem.kind == IR::ResourceKind::Scratch) {
|
||||
const auto old = ctx.state.builder.AllocateId();
|
||||
ctx.state.builder.AddFunction({spv::OpLoad, TypeU32(ctx.state), old, pointer});
|
||||
ctx.state.builder.AddFunction({spv::OpStore, pointer, merge(old)});
|
||||
ctx.state.builder.AddFunction(spv::OpLoad, TypeU32(ctx.state), old, pointer);
|
||||
ctx.state.builder.AddFunction(spv::OpStore, pointer, merge(old));
|
||||
} else {
|
||||
AtomicUpdate(ctx.state, pointer, mem.kind, merge);
|
||||
}
|
||||
@@ -495,15 +502,15 @@ void StoreWordPrepared(ValueEmitContext& ctx, const IR::Inst& inst, const IR::Me
|
||||
const MemoryResourceAccess& resource, uint32_t data) {
|
||||
const auto index = EmitMemoryElementIndex(ctx.state, resource, DwordIndex(ctx, inst, mem));
|
||||
EmitIfCondition(ctx.state, EmitMemoryElementInBounds(ctx.state, resource, index), [&]() {
|
||||
ctx.state.builder.AddFunction(
|
||||
{spv::OpStore, EmitMemoryElementPointer(ctx.state, resource, index), data});
|
||||
ctx.state.builder.AddFunction(spv::OpStore,
|
||||
EmitMemoryElementPointer(ctx.state, resource, index), data);
|
||||
});
|
||||
}
|
||||
|
||||
void StoreWordInBounds(ValueEmitContext& ctx, const MemoryResourceAccess& resource, uint32_t index,
|
||||
uint32_t data) {
|
||||
ctx.state.builder.AddFunction(
|
||||
{spv::OpStore, EmitMemoryElementPointer(ctx.state, resource, index), data});
|
||||
ctx.state.builder.AddFunction(spv::OpStore,
|
||||
EmitMemoryElementPointer(ctx.state, resource, index), data);
|
||||
}
|
||||
|
||||
void StoreWord(ValueEmitContext& ctx, const IR::Inst& inst, IR::MemoryInfo mem) {
|
||||
@@ -513,32 +520,7 @@ void StoreWord(ValueEmitContext& ctx, const IR::Inst& inst, IR::MemoryInfo mem)
|
||||
});
|
||||
}
|
||||
|
||||
void FormattedStorePrepared(ValueEmitContext& ctx, const IR::Inst& inst, const IR::MemoryInfo& mem,
|
||||
uint32_t component, const MemoryResourceAccess& resource,
|
||||
uint32_t data) {
|
||||
const auto info = Format::GetFormatInfo(BufferFormat(ctx, mem));
|
||||
if (info.type == Format::ComponentType::Unknown) {
|
||||
StoreWordPrepared(ctx, inst, RebaseRawComponent(mem, component), resource, data);
|
||||
return;
|
||||
}
|
||||
if (component >= info.component_count) return;
|
||||
const auto bits = info.component_bits[component];
|
||||
const auto component_mem = RebaseFormattedComponent(mem, info, component);
|
||||
if (bits == 8u || bits == 16u) {
|
||||
StoreSubwordPrepared(ctx, inst, component_mem, resource, bits, data);
|
||||
} else {
|
||||
StoreWordPrepared(ctx, inst, component_mem, resource, data);
|
||||
}
|
||||
}
|
||||
|
||||
void FormattedStore(ValueEmitContext& ctx, const IR::Inst& inst, const IR::MemoryInfo& mem) {
|
||||
EmitIfCondition(ctx.state, ctx.Arg(inst, inst.NumArgs() - 1), [&]() {
|
||||
const auto resource = PrepareMemoryResourceAccess(ctx.state, mem);
|
||||
FormattedStorePrepared(ctx, inst, mem, 0u, resource, ctx.Arg(inst, inst.NumArgs() - 2));
|
||||
});
|
||||
}
|
||||
|
||||
uint32_t SpirvAtomicOpcode(IR::ValueOpcode opcode) {
|
||||
spv::Op SpirvAtomicOpcode(IR::ValueOpcode opcode) {
|
||||
switch (opcode) {
|
||||
case IR::ValueOpcode::BufferAtomicCmpSwap32: return spv::OpAtomicCompareExchange;
|
||||
case IR::ValueOpcode::BufferAtomicSwap32:
|
||||
@@ -563,7 +545,7 @@ uint32_t SpirvAtomicOpcode(IR::ValueOpcode opcode) {
|
||||
case IR::ValueOpcode::SharedAtomicOr32: return spv::OpAtomicOr;
|
||||
case IR::ValueOpcode::BufferAtomicXor32:
|
||||
case IR::ValueOpcode::SharedAtomicXor32: return spv::OpAtomicXor;
|
||||
default: return 0;
|
||||
default: return spv::OpNop;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -574,15 +556,14 @@ uint32_t EmitAtomicOperation(ValueEmitContext& ctx, const IR::Inst& inst, uint32
|
||||
const auto desired = ctx.Arg(inst, inst.NumArgs() - 3);
|
||||
const auto comparator = ctx.Arg(inst, inst.NumArgs() - 2);
|
||||
ctx.state.builder.AddFunction(
|
||||
{spv::OpAtomicCompareExchange, TypeU32(ctx.state), old, pointer,
|
||||
ConstantU32(ctx.state, scope), ConstantU32(ctx.state, spv::MemorySemanticsMaskNone),
|
||||
ConstantU32(ctx.state, spv::MemorySemanticsMaskNone), desired, comparator});
|
||||
spv::OpAtomicCompareExchange, TypeU32(ctx.state), old, pointer,
|
||||
ConstantU32(ctx.state, scope), ConstantU32(ctx.state, spv::MemorySemanticsMaskNone),
|
||||
ConstantU32(ctx.state, spv::MemorySemanticsMaskNone), desired, comparator);
|
||||
} else {
|
||||
const auto value = ctx.Arg(inst, inst.NumArgs() - 2);
|
||||
ctx.state.builder.AddFunction({SpirvAtomicOpcode(inst.GetOpcode()), TypeU32(ctx.state), old,
|
||||
pointer, ConstantU32(ctx.state, scope),
|
||||
ConstantU32(ctx.state, spv::MemorySemanticsMaskNone),
|
||||
value});
|
||||
ctx.state.builder.AddFunction(SpirvAtomicOpcode(inst.GetOpcode()), TypeU32(ctx.state), old,
|
||||
pointer, ConstantU32(ctx.state, scope),
|
||||
ConstantU32(ctx.state, spv::MemorySemanticsMaskNone), value);
|
||||
}
|
||||
return old;
|
||||
}
|
||||
@@ -729,6 +710,11 @@ void StoreFormattedInBounds(ValueEmitContext& ctx, const IR::MemoryInfo& mem,
|
||||
uint32_t data) {
|
||||
if (component >= plan.info.component_count) return;
|
||||
const auto bits = plan.info.component_bits[component];
|
||||
if (plan.info.type == Format::ComponentType::Snorm && bits == 16u) {
|
||||
const auto value = EmitBitCastF32U32(ctx.state, data);
|
||||
data = EmitPackSnorm2x16(
|
||||
ctx.state, EmitCompositeConstructF32x2(ctx.state, value, ConstantF32Value(ctx.state, 0.0f)));
|
||||
}
|
||||
if (bits == 8u || bits == 16u) {
|
||||
StoreSubwordInBounds(ctx, mem, plan.resource, plan.addresses[component],
|
||||
plan.indices[component], bits, data);
|
||||
@@ -737,12 +723,103 @@ void StoreFormattedInBounds(ValueEmitContext& ctx, const IR::MemoryInfo& mem,
|
||||
}
|
||||
}
|
||||
|
||||
void FormattedStore(ValueEmitContext& ctx, const IR::Inst& inst, const IR::MemoryInfo& mem) {
|
||||
EmitIfCondition(ctx.state, ctx.Arg(inst, inst.NumArgs() - 1), [&]() {
|
||||
const auto resource = PrepareMemoryResourceAccess(ctx.state, mem);
|
||||
const auto data = ctx.Arg(inst, inst.NumArgs() - 2);
|
||||
const auto info = Format::GetFormatInfo(BufferFormat(ctx, mem));
|
||||
if (info.type == Format::ComponentType::Unknown) {
|
||||
StoreWordPrepared(ctx, inst, RebaseRawComponent(mem, 0u), resource, data);
|
||||
return;
|
||||
}
|
||||
const auto plan = PrepareFormattedMemory(ctx, inst, mem, resource, info, 1u,
|
||||
FormattedAccess::Store);
|
||||
EmitIfCondition(ctx.state, plan.in_bounds, [&]() {
|
||||
StoreFormattedInBounds(ctx, mem, plan, 0u, data);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
uint32_t LoadIndirectBuffer(ValueEmitContext& ctx, const IR::Inst& inst, uint32_t components) {
|
||||
auto& state = ctx.state;
|
||||
const auto& handle = *inst.Arg(0).ResolveInstruction();
|
||||
const auto word1 = ctx.Arg(handle, 1);
|
||||
const auto records = ctx.Arg(handle, 2);
|
||||
const auto word3 = ctx.Arg(handle, 3);
|
||||
const auto field = [&](uint32_t word, uint32_t first, uint32_t count) {
|
||||
return EmitBitFieldUExtract(state, word, ConstantU32(state, first),
|
||||
ConstantU32(state, count));
|
||||
};
|
||||
const auto nonzero = [&](uint32_t value) {
|
||||
return Binary(state, spv::OpINotEqual, TypeBool(state), value, ConstantU32(state, 0));
|
||||
};
|
||||
const auto has_dword = [&](uint32_t offset, uint32_t size) {
|
||||
return AndCondition(
|
||||
state,
|
||||
Binary(state, spv::OpUGreaterThanEqual, TypeBool(state), size, ConstantU32(state, 4)),
|
||||
Binary(state, spv::OpULessThanEqual, TypeBool(state), offset,
|
||||
Binary(state, spv::OpISub, TypeU32(state), size, ConstantU32(state, 4))));
|
||||
};
|
||||
const auto stride = field(word1, 16, 14);
|
||||
const auto swizzle = AndCondition(state, nonzero(stride), nonzero(field(word1, 31, 1)));
|
||||
const auto index_stride = field(word3, 21, 2);
|
||||
const auto add_tid = nonzero(field(word3, 23, 1));
|
||||
const auto index =
|
||||
Binary(state, spv::OpIAdd, TypeU32(state), ctx.Arg(inst, 1),
|
||||
Select(state, TypeU32(state), add_tid, BufferLane(state), ConstantU32(state, 0)));
|
||||
const auto soffset = ctx.Arg(inst, 3);
|
||||
const auto base = DeviceAddressFromWords(state, ctx.Arg(handle, 0), field(word1, 0, 16));
|
||||
const auto valid_format = nonzero(field(word3, 12, 7));
|
||||
const auto mode = field(word3, 28, 2);
|
||||
const auto index_in_bounds = Binary(state, spv::OpULessThan, TypeBool(state), index, records);
|
||||
const auto scalar_in_bounds =
|
||||
Binary(state, spv::OpULessThanEqual, TypeBool(state), soffset, records);
|
||||
const auto raw_records = Binary(state, spv::OpISub, TypeU32(state), records, soffset);
|
||||
const auto raw_index_in_bounds =
|
||||
Binary(state, spv::OpULessThan, TypeBool(state), index, raw_records);
|
||||
std::array<uint32_t, 4> values {};
|
||||
for (uint32_t component = 0; component < components; component++) {
|
||||
const auto address = CalculateBufferAddress(state, index, ctx.Arg(inst, 2), soffset,
|
||||
ctx.Memory(inst).offset + component * 4u,
|
||||
stride, swizzle, index_stride);
|
||||
// RDNA2 raw DWORD vectors check each component, unlike formatted accesses.
|
||||
const auto structured_bounds =
|
||||
AndCondition(state, index_in_bounds,
|
||||
Binary(state, spv::OpULessThan, TypeBool(state), address.offset, stride));
|
||||
// OOB_SELECT=3 reduces NUM_RECORDS by SOFFSET before the offset/index check.
|
||||
const auto raw_bounds = AndCondition(
|
||||
state, scalar_in_bounds,
|
||||
Select(state, TypeBool(state), swizzle,
|
||||
AndCondition(state, raw_index_in_bounds, has_dword(address.offset, stride)),
|
||||
has_dword(address.offset, raw_records)));
|
||||
auto in_bounds =
|
||||
Select(state, TypeBool(state),
|
||||
Binary(state, spv::OpIEqual, TypeBool(state), mode, ConstantU32(state, 0)),
|
||||
structured_bounds, index_in_bounds);
|
||||
in_bounds = Select(
|
||||
state, TypeBool(state),
|
||||
Binary(state, spv::OpULessThan, TypeBool(state), mode, ConstantU32(state, 2)),
|
||||
in_bounds,
|
||||
Select(state, TypeBool(state),
|
||||
Binary(state, spv::OpIEqual, TypeBool(state), mode, ConstantU32(state, 2)),
|
||||
nonzero(records), raw_bounds));
|
||||
const auto guest =
|
||||
Binary(state, spv::OpIAdd, TypeScalarU64(state), base,
|
||||
Unary(state, spv::OpUConvert, TypeScalarU64(state), address.byte));
|
||||
values[component] = LoadBda(ctx, guest, AndCondition(state, valid_format, in_bounds), 32u);
|
||||
}
|
||||
return ConstructU32Composite(state, components, values);
|
||||
}
|
||||
|
||||
uint32_t LoadWideBuffer(ValueEmitContext& ctx, const IR::Inst& inst, uint32_t components) {
|
||||
auto& state = ctx.state;
|
||||
return EmitValueOrDefaultIfCondition(
|
||||
state, ctx.Arg(inst, inst.NumArgs() - 1), TypeU32Composite(state, components),
|
||||
ConstantU32CompositeZero(state, components), [&]() {
|
||||
const auto mem = ctx.Memory(inst);
|
||||
if (mem.kind == IR::ResourceKind::IndirectBuffer) {
|
||||
return LoadIndirectBuffer(ctx, inst, components);
|
||||
}
|
||||
const auto resource = PrepareMemoryResourceAccess(state, mem);
|
||||
const auto info = Format::GetFormatInfo(
|
||||
mem.formatted ? BufferFormat(ctx, mem) : Prospero::BufferFormat::kInvalid);
|
||||
@@ -782,8 +859,8 @@ void StoreWideBuffer(ValueEmitContext& ctx, const IR::Inst& inst, uint32_t compo
|
||||
EmitIfCondition(state, plan.in_bounds, [&]() {
|
||||
for (uint32_t component = 0; component < components; component++) {
|
||||
const auto data = state.builder.AllocateId();
|
||||
state.builder.AddFunction(
|
||||
{spv::OpCompositeExtract, TypeU32(state), data, composite, component});
|
||||
state.builder.AddFunction(spv::OpCompositeExtract, TypeU32(state), data,
|
||||
composite, component);
|
||||
StoreFormattedInBounds(ctx, mem, plan, component, data);
|
||||
}
|
||||
});
|
||||
@@ -791,8 +868,8 @@ void StoreWideBuffer(ValueEmitContext& ctx, const IR::Inst& inst, uint32_t compo
|
||||
}
|
||||
for (uint32_t component = 0; component < components; component++) {
|
||||
const auto data = state.builder.AllocateId();
|
||||
state.builder.AddFunction(
|
||||
{spv::OpCompositeExtract, TypeU32(state), data, composite, component});
|
||||
state.builder.AddFunction(spv::OpCompositeExtract, TypeU32(state), data, composite,
|
||||
component);
|
||||
StoreWordPrepared(ctx, inst, RebaseRawComponent(mem, component), resource, data);
|
||||
}
|
||||
});
|
||||
@@ -851,49 +928,49 @@ void DefineGetBdaPointer(EmitterState& state) {
|
||||
return;
|
||||
}
|
||||
const auto type = TypeScalarU64(state);
|
||||
const auto function_type = state.builder.Type(spv::OpTypeFunction, {type, type});
|
||||
const auto function_type = state.builder.Type(spv::OpTypeFunction, type, type);
|
||||
state.bda_pointer_function = state.builder.AllocateId();
|
||||
const auto address = state.builder.AllocateId();
|
||||
const auto entry_label = state.builder.AllocateId();
|
||||
state.builder.AddName(state.bda_pointer_function, "get_bda_pointer");
|
||||
state.builder.AddFunction({spv::OpFunction, type, state.bda_pointer_function,
|
||||
spv::FunctionControlMaskNone, function_type});
|
||||
state.builder.AddFunction({spv::OpFunctionParameter, type, address});
|
||||
state.builder.AddFunction(spv::OpFunction, type, state.bda_pointer_function,
|
||||
spv::FunctionControlMaskNone, function_type);
|
||||
state.builder.AddFunction(spv::OpFunctionParameter, type, address);
|
||||
EmitLabel(state, entry_label);
|
||||
|
||||
const auto page64 = Binary(state, spv::OpShiftRightLogical, type, address,
|
||||
ConstantDeviceAddress(state, BufferCache::CACHING_PAGEBITS));
|
||||
const auto page = Unary(state, spv::OpUConvert, TypeU32(state), page64);
|
||||
const auto entry_pointer = state.builder.AllocateId();
|
||||
state.builder.AddFunction({spv::OpAccessChain, TypeStorageBufferU64ElementPointer(state),
|
||||
entry_pointer, state.bda_pagetable_variable, ConstantU32(state, 0),
|
||||
page});
|
||||
state.builder.AddFunction(spv::OpAccessChain, TypeStorageBufferU64ElementPointer(state),
|
||||
entry_pointer, state.bda_pagetable_variable, ConstantU32(state, 0),
|
||||
page);
|
||||
const auto base = state.builder.AllocateId();
|
||||
state.builder.AddFunction({spv::OpLoad, type, base, entry_pointer});
|
||||
state.builder.AddFunction(spv::OpLoad, type, base, entry_pointer);
|
||||
const auto missing =
|
||||
Binary(state, spv::OpIEqual, TypeBool(state), base, ConstantDeviceAddress(state, 0));
|
||||
const auto fault_label = state.builder.AllocateId();
|
||||
const auto available_label = state.builder.AllocateId();
|
||||
const auto merge_label = state.builder.AllocateId();
|
||||
state.builder.AddFunction({spv::OpSelectionMerge, merge_label, spv::SelectionControlMaskNone});
|
||||
state.builder.AddFunction({spv::OpBranchConditional, missing, fault_label, available_label});
|
||||
state.builder.AddFunction(spv::OpSelectionMerge, merge_label, spv::SelectionControlMaskNone);
|
||||
state.builder.AddFunction(spv::OpBranchConditional, missing, fault_label, available_label);
|
||||
|
||||
EmitLabel(state, fault_label);
|
||||
RecordBdaFault(state, page);
|
||||
state.builder.AddFunction({spv::OpBranch, merge_label});
|
||||
state.builder.AddFunction(spv::OpBranch, merge_label);
|
||||
|
||||
EmitLabel(state, available_label);
|
||||
const auto offset = Binary(state, spv::OpBitwiseAnd, type, address,
|
||||
ConstantDeviceAddress(state, BufferCache::CACHING_PAGESIZE - 1));
|
||||
const auto available = Binary(state, spv::OpIAdd, type, base, offset);
|
||||
state.builder.AddFunction({spv::OpBranch, merge_label});
|
||||
state.builder.AddFunction(spv::OpBranch, merge_label);
|
||||
|
||||
EmitLabel(state, merge_label);
|
||||
const auto result = state.builder.AllocateId();
|
||||
state.builder.AddFunction({spv::OpPhi, type, result, ConstantDeviceAddress(state, 0),
|
||||
fault_label, available, available_label});
|
||||
state.builder.AddFunction({spv::OpReturnValue, result});
|
||||
state.builder.AddFunction({spv::OpFunctionEnd});
|
||||
state.builder.AddFunction(spv::OpPhi, type, result, ConstantDeviceAddress(state, 0),
|
||||
fault_label, available, available_label);
|
||||
state.builder.AddFunction(spv::OpReturnValue, result);
|
||||
state.builder.AddFunction(spv::OpFunctionEnd);
|
||||
}
|
||||
|
||||
uint32_t EmitAtomic32(ValueEmitContext& ctx, const IR::Inst& inst) {
|
||||
@@ -905,8 +982,8 @@ uint32_t EmitAtomic32(ValueEmitContext& ctx, const IR::Inst& inst) {
|
||||
if (mem.kind == IR::ResourceKind::Lds) {
|
||||
const auto semantics =
|
||||
spv::MemorySemanticsAcquireReleaseMask | spv::MemorySemanticsWorkgroupMemoryMask;
|
||||
ctx.state.builder.AddFunction({spv::OpMemoryBarrier, ConstantU32(ctx.state, scope),
|
||||
ConstantU32(ctx.state, semantics)});
|
||||
ctx.state.builder.AddFunction(spv::OpMemoryBarrier, ConstantU32(ctx.state, scope),
|
||||
ConstantU32(ctx.state, semantics));
|
||||
} else {
|
||||
EmitDeviceAtomicMemoryBarrier(ctx.state);
|
||||
}
|
||||
@@ -932,11 +1009,11 @@ uint32_t EmitBufferAtomic64(ValueEmitContext& ctx, const IR::Inst& inst) {
|
||||
ctx.Arg(inst, inst.NumArgs() - 2));
|
||||
const auto old = state.builder.AllocateId();
|
||||
state.builder.AddFunction(
|
||||
{SpirvAtomicOpcode(inst.GetOpcode()), TypeScalarU64(state), old,
|
||||
EmitStorageBufferElementPointer(state, resource, index,
|
||||
TypeStorageBufferU64ElementPointer(state)),
|
||||
ConstantU32(state, spv::ScopeDevice),
|
||||
ConstantU32(state, spv::MemorySemanticsMaskNone), value});
|
||||
SpirvAtomicOpcode(inst.GetOpcode()), TypeScalarU64(state), old,
|
||||
EmitStorageBufferElementPointer(state, resource, index,
|
||||
TypeStorageBufferU64ElementPointer(state)),
|
||||
ConstantU32(state, spv::ScopeDevice),
|
||||
ConstantU32(state, spv::MemorySemanticsMaskNone), value);
|
||||
EmitDeviceAtomicMemoryBarrier(state);
|
||||
return Unary(state, spv::OpBitcast, TypeU64(state), old);
|
||||
});
|
||||
@@ -959,11 +1036,11 @@ void EmitSharedFloatAtomic(ValueEmitContext& ctx, const IR::Inst& inst) {
|
||||
const auto access = PrepareMemoryElement(ctx, mem, DwordIndex(ctx, inst, mem));
|
||||
EmitIfCondition(
|
||||
ctx.state, EmitMemoryElementInBounds(ctx.state, access.resource, access.index), [&]() {
|
||||
ctx.state.builder.AddFunction(
|
||||
{spv::OpStore, ctx.scratch_u32_variable, ctx.Arg(inst, 1)});
|
||||
ctx.state.builder.AddFunction(spv::OpStore, ctx.scratch_u32_variable,
|
||||
ctx.Arg(inst, 1));
|
||||
const auto data = ctx.state.builder.AllocateId();
|
||||
ctx.state.builder.AddFunction(
|
||||
{spv::OpLoad, TypeU32(ctx.state), data, ctx.scratch_u32_variable});
|
||||
ctx.state.builder.AddFunction(spv::OpLoad, TypeU32(ctx.state), data,
|
||||
ctx.scratch_u32_variable);
|
||||
AtomicUpdate(
|
||||
ctx.state, EmitMemoryElementPointer(ctx.state, access.resource, access.index),
|
||||
mem.kind, [&](uint32_t old) {
|
||||
@@ -1006,8 +1083,8 @@ uint32_t EmitAppendConsume(ValueEmitContext& ctx, const IR::Inst& inst) {
|
||||
const auto ballot = ctx.Ballot(inst.Arg(1));
|
||||
const auto low = state.builder.AllocateId();
|
||||
const auto high = state.builder.AllocateId();
|
||||
state.builder.AddFunction({spv::OpCompositeExtract, TypeU32(state), low, ballot, 0});
|
||||
state.builder.AddFunction({spv::OpCompositeExtract, TypeU32(state), high, ballot, 1});
|
||||
state.builder.AddFunction(spv::OpCompositeExtract, TypeU32(state), low, ballot, 0);
|
||||
state.builder.AddFunction(spv::OpCompositeExtract, TypeU32(state), high, ballot, 1);
|
||||
const auto count = Binary(state, spv::OpIAdd, TypeU32(state),
|
||||
Unary(state, spv::OpBitCount, TypeU32(state), low),
|
||||
Unary(state, spv::OpBitCount, TypeU32(state), high));
|
||||
@@ -1033,17 +1110,17 @@ uint32_t EmitAppendConsume(ValueEmitContext& ctx, const IR::Inst& inst) {
|
||||
AndCondition(state, storage_bounds, m0_bounds)));
|
||||
const auto atomic = EmitValueOrZeroIfCondition(state, condition, [&]() {
|
||||
const auto value = state.builder.AllocateId();
|
||||
state.builder.AddFunction(
|
||||
{append ? spv::OpAtomicIAdd : spv::OpAtomicISub, TypeU32(state), value,
|
||||
EmitMemoryElementPointer(state, access, index),
|
||||
ConstantU32(state, mem.kind == IR::ResourceKind::Gds ? spv::ScopeDevice
|
||||
: spv::ScopeWorkgroup),
|
||||
ConstantU32(state, spv::MemorySemanticsMaskNone), count});
|
||||
state.builder.AddFunction(append ? spv::OpAtomicIAdd : spv::OpAtomicISub, TypeU32(state),
|
||||
value, EmitMemoryElementPointer(state, access, index),
|
||||
ConstantU32(state, mem.kind == IR::ResourceKind::Gds
|
||||
? spv::ScopeDevice
|
||||
: spv::ScopeWorkgroup),
|
||||
ConstantU32(state, spv::MemorySemanticsMaskNone), count);
|
||||
return value;
|
||||
});
|
||||
const auto result = state.builder.AllocateId();
|
||||
state.builder.AddFunction({spv::OpGroupNonUniformShuffle, TypeU32(state), result,
|
||||
ConstantU32(state, spv::ScopeSubgroup), atomic, source_lane});
|
||||
state.builder.AddFunction(spv::OpGroupNonUniformShuffle, TypeU32(state), result,
|
||||
ConstantU32(state, spv::ScopeSubgroup), atomic, source_lane);
|
||||
return result;
|
||||
}
|
||||
|
||||
@@ -1053,9 +1130,9 @@ uint32_t EmitReadConst(ValueEmitContext& ctx, const IR::Inst& inst) {
|
||||
ctx.Fail(inst, "requires the flattened SRT descriptor");
|
||||
}
|
||||
const auto pointer = state.builder.AllocateId();
|
||||
state.builder.AddFunction({spv::OpAccessChain, TypeStorageBufferElementPointer(state), pointer,
|
||||
state.flattened_srt_variable, ConstantU32(state, 0),
|
||||
ctx.Arg(inst, 1)});
|
||||
state.builder.AddFunction(spv::OpAccessChain, TypeStorageBufferElementPointer(state), pointer,
|
||||
state.flattened_srt_variable, ConstantU32(state, 0),
|
||||
ctx.Arg(inst, 1));
|
||||
return EmitNative<spv::OpLoad, IR::Type::U32>(state, pointer);
|
||||
}
|
||||
|
||||
@@ -1091,7 +1168,8 @@ void EmitLoadMemory(ValueEmitContext& ctx, const IR::Inst& inst) {
|
||||
value = LoadWideShared(ctx, inst, shared_components);
|
||||
else if (address_info.access == IR::AddressAccess::Read &&
|
||||
mem.kind != IR::ResourceKind::Scratch)
|
||||
value = LoadBda(ctx, inst, mem, address_info.data_bits);
|
||||
value = LoadBda(ctx, GuestAddress(ctx, inst, mem), ctx.Arg(inst, inst.NumArgs() - 1),
|
||||
address_info.data_bits);
|
||||
else if (op == IR::ValueOpcode::LoadBufferU32 && mem.formatted)
|
||||
value = FormattedLoad(ctx, inst, mem);
|
||||
else if (inst.GetType() == IR::Type::U8)
|
||||
@@ -1131,7 +1209,7 @@ uint32_t EmitSharedIncDec(ValueEmitContext& ctx, const IR::Inst& inst) {
|
||||
|
||||
uint32_t EmitSwizzleU32(ValueEmitContext& ctx, const IR::Inst& inst) {
|
||||
auto& state = ctx.state;
|
||||
state.builder.AddFunction({spv::OpStore, ctx.scratch_u32_variable, ctx.Arg(inst, 0)});
|
||||
state.builder.AddFunction(spv::OpStore, ctx.scratch_u32_variable, ctx.Arg(inst, 0));
|
||||
const auto source = EmitNative<spv::OpLoad, IR::Type::U32>(state, ctx.scratch_u32_variable);
|
||||
const auto target = EmitDsSwizzleTargetLane(state, EmitSubgroupLocalInvocationId(state),
|
||||
inst.Arg(1).IsImmediate() ? inst.Arg(1).U32() : 0);
|
||||
|
||||
@@ -7,27 +7,27 @@ uint32_t EmitShaderDataDwordLoad(EmitterState& state, uint32_t dword_index) {
|
||||
dword_index += state.program.bindings.push_data_start_dword;
|
||||
const auto pointer = state.builder.AllocateId();
|
||||
const auto value = state.builder.AllocateId();
|
||||
state.builder.AddFunction({spv::OpAccessChain, TypePushConstantElementPointer(state),
|
||||
pointer, state.push_constant_variable, ConstantU32(state, 0),
|
||||
ConstantU32(state, dword_index)});
|
||||
state.builder.AddFunction({spv::OpLoad, TypeU32(state), value, pointer});
|
||||
state.builder.AddFunction(spv::OpAccessChain, TypePushConstantElementPointer(state),
|
||||
pointer, state.push_constant_variable, ConstantU32(state, 0),
|
||||
ConstantU32(state, dword_index));
|
||||
state.builder.AddFunction(spv::OpLoad, TypeU32(state), value, pointer);
|
||||
return value;
|
||||
}
|
||||
if (state.shader_data_storage_variable != 0) {
|
||||
const auto pointer = state.builder.AllocateId();
|
||||
const auto value = state.builder.AllocateId();
|
||||
state.builder.AddFunction({spv::OpAccessChain, TypeStorageBufferElementPointer(state),
|
||||
pointer, state.shader_data_storage_variable,
|
||||
ConstantU32(state, 0), ConstantU32(state, dword_index)});
|
||||
state.builder.AddFunction({spv::OpLoad, TypeU32(state), value, pointer});
|
||||
state.builder.AddFunction(spv::OpAccessChain, TypeStorageBufferElementPointer(state),
|
||||
pointer, state.shader_data_storage_variable,
|
||||
ConstantU32(state, 0), ConstantU32(state, dword_index));
|
||||
state.builder.AddFunction(spv::OpLoad, TypeU32(state), value, pointer);
|
||||
return value;
|
||||
}
|
||||
return ConstantU32(state, 0);
|
||||
}
|
||||
|
||||
uint32_t EmitBinaryU32(EmitterState& state, uint32_t opcode, uint32_t lhs, uint32_t rhs) {
|
||||
uint32_t EmitBinaryU32(EmitterState& state, spv::Op opcode, uint32_t lhs, uint32_t rhs) {
|
||||
const auto ret = state.builder.AllocateId();
|
||||
state.builder.AddFunction({opcode, TypeU32(state), ret, lhs, rhs});
|
||||
state.builder.AddFunction(opcode, TypeU32(state), ret, lhs, rhs);
|
||||
return ret;
|
||||
}
|
||||
|
||||
@@ -88,12 +88,12 @@ MemoryResourceAccess PrepareStorageBufferResourceAccess(EmitterState& state,
|
||||
ResourceForDescriptor(state, IR::DescriptorBindingKind::Buffers, mem.resource);
|
||||
MemoryResourceAccess access {.kind = mem.kind};
|
||||
access.object_pointer = state.builder.AllocateId();
|
||||
state.builder.AddFunction({spv::OpAccessChain, pointer_type, access.object_pointer, variable,
|
||||
ConstantU32(state, array_index)});
|
||||
state.builder.AddFunction(spv::OpAccessChain, pointer_type, access.object_pointer, variable,
|
||||
ConstantU32(state, array_index));
|
||||
access.byte_offset = state.memory_byte_offsets[array_index];
|
||||
access.length = state.builder.AllocateId();
|
||||
state.builder.AddFunction(
|
||||
{spv::OpArrayLength, TypeU32(state), access.length, access.object_pointer, 0});
|
||||
state.builder.AddFunction(spv::OpArrayLength, TypeU32(state), access.length,
|
||||
access.object_pointer, 0);
|
||||
return access;
|
||||
}
|
||||
|
||||
@@ -139,8 +139,8 @@ MemoryResourceAccess PrepareMemoryResourceAccess(EmitterState& state, const IR::
|
||||
default: EXIT("unsupported memory resource kind: %u\n", static_cast<unsigned>(mem.kind));
|
||||
}
|
||||
access.length = state.builder.AllocateId();
|
||||
state.builder.AddFunction(
|
||||
{spv::OpArrayLength, TypeU32(state), access.length, access.object_pointer, 0});
|
||||
state.builder.AddFunction(spv::OpArrayLength, TypeU32(state), access.length,
|
||||
access.object_pointer, 0);
|
||||
return access;
|
||||
}
|
||||
|
||||
@@ -152,7 +152,7 @@ uint32_t EmitMemoryElementIndex(EmitterState& state, const MemoryResourceAccess&
|
||||
uint32_t EmitMemoryElementInBounds(EmitterState& state, const MemoryResourceAccess& access,
|
||||
uint32_t index) {
|
||||
const auto in_bounds = state.builder.AllocateId();
|
||||
state.builder.AddFunction({spv::OpULessThan, TypeBool(state), in_bounds, index, access.length});
|
||||
state.builder.AddFunction(spv::OpULessThan, TypeBool(state), in_bounds, index, access.length);
|
||||
return in_bounds;
|
||||
}
|
||||
|
||||
@@ -165,8 +165,8 @@ uint32_t EmitMemoryElementPointer(EmitterState& state, const MemoryResourceAcces
|
||||
: ShaderWorkgroupInput(state.program.stage, state.input_info) != nullptr
|
||||
? spv::StorageClassWorkgroup
|
||||
: spv::StorageClassFunction;
|
||||
state.builder.AddFunction({spv::OpAccessChain, TypeU32ElementPointer(state, storage_class),
|
||||
pointer, access.object_pointer, index});
|
||||
state.builder.AddFunction(spv::OpAccessChain, TypeU32ElementPointer(state, storage_class),
|
||||
pointer, access.object_pointer, index);
|
||||
return pointer;
|
||||
}
|
||||
return EmitStorageBufferElementPointer(state, access, index,
|
||||
@@ -177,14 +177,14 @@ uint32_t EmitStorageBufferElementPointer(EmitterState& state,
|
||||
const MemoryResourceAccess& access, uint32_t index,
|
||||
uint32_t pointer_type) {
|
||||
const auto pointer = state.builder.AllocateId();
|
||||
state.builder.AddFunction({spv::OpAccessChain, pointer_type, pointer, access.object_pointer,
|
||||
ConstantU32(state, 0), index});
|
||||
state.builder.AddFunction(spv::OpAccessChain, pointer_type, pointer, access.object_pointer,
|
||||
ConstantU32(state, 0), index);
|
||||
return pointer;
|
||||
}
|
||||
|
||||
uint32_t EmitTBufferBitcastU32ToI32(EmitterState& state, uint32_t value) {
|
||||
const auto ret = state.builder.AllocateId();
|
||||
state.builder.AddFunction({spv::OpBitcast, TypeI32(state), ret, value});
|
||||
state.builder.AddFunction(spv::OpBitcast, TypeI32(state), ret, value);
|
||||
return ret;
|
||||
}
|
||||
|
||||
@@ -218,10 +218,10 @@ uint32_t EmitUFloatToF32Bits(EmitterState& state, uint32_t raw, uint32_t bits) {
|
||||
|
||||
const auto mantissa_f32 = state.builder.AllocateId();
|
||||
const auto subnormal = state.builder.AllocateId();
|
||||
state.builder.AddFunction({spv::OpConvertUToF, TypeF32(state), mantissa_f32, mantissa});
|
||||
state.builder.AddFunction(spv::OpConvertUToF, TypeF32(state), mantissa_f32, mantissa);
|
||||
state.builder.AddFunction(
|
||||
{spv::OpFMul, TypeF32(state), subnormal, mantissa_f32,
|
||||
ConstantF32Value(state, std::ldexp(1.0f, 1 - 15 - static_cast<int>(mantissa_bits)))});
|
||||
spv::OpFMul, TypeF32(state), subnormal, mantissa_f32,
|
||||
ConstantF32Value(state, std::ldexp(1.0f, 1 - 15 - static_cast<int>(mantissa_bits))));
|
||||
|
||||
const auto zero_exp = EmitCompareU32Constant(state, spv::OpIEqual, exponent, 0);
|
||||
const auto special_exp = EmitCompareU32Constant(state, spv::OpIEqual, exponent, 31);
|
||||
@@ -238,22 +238,22 @@ uint32_t NormalizeFormatComponent(EmitterState& state, const Format::BufferForma
|
||||
case Format::ComponentType::Sint: return raw;
|
||||
case Format::ComponentType::Uscaled: {
|
||||
const auto value = state.builder.AllocateId();
|
||||
state.builder.AddFunction({spv::OpConvertUToF, TypeF32(state), value, raw});
|
||||
state.builder.AddFunction(spv::OpConvertUToF, TypeF32(state), value, raw);
|
||||
return EmitBitcastF32ToU32(state, value);
|
||||
}
|
||||
case Format::ComponentType::Sscaled: {
|
||||
const auto signed_raw = EmitTBufferBitcastU32ToI32(state, raw);
|
||||
const auto value = state.builder.AllocateId();
|
||||
state.builder.AddFunction({spv::OpConvertSToF, TypeF32(state), value, signed_raw});
|
||||
state.builder.AddFunction(spv::OpConvertSToF, TypeF32(state), value, signed_raw);
|
||||
return EmitBitcastF32ToU32(state, value);
|
||||
}
|
||||
case Format::ComponentType::Unorm: {
|
||||
const auto value = state.builder.AllocateId();
|
||||
const auto normalized = state.builder.AllocateId();
|
||||
const auto max_value = static_cast<float>((1u << bits) - 1u);
|
||||
state.builder.AddFunction({spv::OpConvertUToF, TypeF32(state), value, raw});
|
||||
state.builder.AddFunction({spv::OpFDiv, TypeF32(state), normalized, value,
|
||||
ConstantF32Value(state, max_value)});
|
||||
state.builder.AddFunction(spv::OpConvertUToF, TypeF32(state), value, raw);
|
||||
state.builder.AddFunction(spv::OpFDiv, TypeF32(state), normalized, value,
|
||||
ConstantF32Value(state, max_value));
|
||||
return EmitBitcastF32ToU32(state, normalized);
|
||||
}
|
||||
case Format::ComponentType::Snorm: {
|
||||
@@ -262,11 +262,11 @@ uint32_t NormalizeFormatComponent(EmitterState& state, const Format::BufferForma
|
||||
const auto normalized = state.builder.AllocateId();
|
||||
const auto clamped = state.builder.AllocateId();
|
||||
const auto max_value = static_cast<float>((1u << (bits - 1u)) - 1u);
|
||||
state.builder.AddFunction({spv::OpConvertSToF, TypeF32(state), value, signed_raw});
|
||||
state.builder.AddFunction({spv::OpFDiv, TypeF32(state), normalized, value,
|
||||
ConstantF32Value(state, max_value)});
|
||||
state.builder.AddFunction({spv::OpExtInst, TypeF32(state), clamped, GlslStd450(state),
|
||||
GLSLstd450FMax, normalized, ConstantF32Value(state, -1.0f)});
|
||||
state.builder.AddFunction(spv::OpConvertSToF, TypeF32(state), value, signed_raw);
|
||||
state.builder.AddFunction(spv::OpFDiv, TypeF32(state), normalized, value,
|
||||
ConstantF32Value(state, max_value));
|
||||
state.builder.AddFunction(spv::OpExtInst, TypeF32(state), clamped, GlslStd450(state),
|
||||
GLSLstd450FMax, normalized, ConstantF32Value(state, -1.0f));
|
||||
return EmitBitcastF32ToU32(state, clamped);
|
||||
}
|
||||
case Format::ComponentType::Float:
|
||||
@@ -284,8 +284,8 @@ uint32_t NormalizeFormatComponent(EmitterState& state, const Format::BufferForma
|
||||
void EmitDeviceAtomicMemoryBarrier(EmitterState& state) {
|
||||
const auto semantics =
|
||||
spv::MemorySemanticsAcquireReleaseMask | spv::MemorySemanticsUniformMemoryMask;
|
||||
state.builder.AddFunction({spv::OpMemoryBarrier, ConstantU32(state, spv::ScopeDevice),
|
||||
ConstantU32(state, semantics)});
|
||||
state.builder.AddFunction(spv::OpMemoryBarrier, ConstantU32(state, spv::ScopeDevice),
|
||||
ConstantU32(state, semantics));
|
||||
}
|
||||
|
||||
uint32_t EmitFloatAtomicReplacement(EmitterState& state, uint32_t old, uint32_t source,
|
||||
@@ -300,7 +300,7 @@ uint32_t EmitFloatAtomicReplacement(EmitterState& state, uint32_t old, uint32_t
|
||||
const auto negative = EmitCompareU32Constant(state, spv::OpINotEqual,
|
||||
EmitAndConstant(state, bits, 0x80000000u), 0u);
|
||||
const auto negative_key = state.builder.AllocateId();
|
||||
state.builder.AddFunction({spv::OpNot, TypeU32(state), negative_key, bits});
|
||||
state.builder.AddFunction(spv::OpNot, TypeU32(state), negative_key, bits);
|
||||
const auto positive_key =
|
||||
EmitBinaryU32(state, spv::OpBitwiseXor, bits, ConstantU32(state, 0x80000000u));
|
||||
return OrderedBits {
|
||||
@@ -313,8 +313,8 @@ uint32_t EmitFloatAtomicReplacement(EmitterState& state, uint32_t old, uint32_t
|
||||
state, EmitLogicalOrBool(state, source_class.nan, old_class.nan),
|
||||
EmitLogicalAndBool(state, source_class.zero, old_class.zero));
|
||||
const auto compare = state.builder.AllocateId();
|
||||
state.builder.AddFunction({max_value ? spv::OpUGreaterThan : spv::OpULessThan, TypeBool(state),
|
||||
compare, source_class.key, old_class.key});
|
||||
state.builder.AddFunction(max_value ? spv::OpUGreaterThan : spv::OpULessThan, TypeBool(state),
|
||||
compare, source_class.key, old_class.key);
|
||||
return EmitSelectValueU32(
|
||||
state, EmitLogicalAndBool(state, EmitLogicalNotBool(state, unordered), compare), source,
|
||||
old);
|
||||
@@ -342,17 +342,17 @@ uint32_t EmitDsSwizzleTargetLane(EmitterState& state, uint32_t subid, uint32_t c
|
||||
const auto perm = state.builder.AllocateId();
|
||||
const auto base = state.builder.AllocateId();
|
||||
const auto target = state.builder.AllocateId();
|
||||
state.builder.AddFunction(
|
||||
{spv::OpBitwiseAnd, TypeU32(state), lane2, subid, ConstantU32(state, 3)});
|
||||
state.builder.AddFunction(
|
||||
{spv::OpShiftLeftLogical, TypeU32(state), shift, lane2, ConstantU32(state, 1)});
|
||||
state.builder.AddFunction(
|
||||
{spv::OpShiftRightLogical, TypeU32(state), perm0, ConstantU32(state, control), shift});
|
||||
state.builder.AddFunction(
|
||||
{spv::OpBitwiseAnd, TypeU32(state), perm, perm0, ConstantU32(state, 3)});
|
||||
state.builder.AddFunction(
|
||||
{spv::OpBitwiseAnd, TypeU32(state), base, subid, ConstantU32(state, 0xfffffffcu)});
|
||||
state.builder.AddFunction({spv::OpBitwiseOr, TypeU32(state), target, base, perm});
|
||||
state.builder.AddFunction(spv::OpBitwiseAnd, TypeU32(state), lane2, subid,
|
||||
ConstantU32(state, 3));
|
||||
state.builder.AddFunction(spv::OpShiftLeftLogical, TypeU32(state), shift, lane2,
|
||||
ConstantU32(state, 1));
|
||||
state.builder.AddFunction(spv::OpShiftRightLogical, TypeU32(state), perm0,
|
||||
ConstantU32(state, control), shift);
|
||||
state.builder.AddFunction(spv::OpBitwiseAnd, TypeU32(state), perm, perm0,
|
||||
ConstantU32(state, 3));
|
||||
state.builder.AddFunction(spv::OpBitwiseAnd, TypeU32(state), base, subid,
|
||||
ConstantU32(state, 0xfffffffcu));
|
||||
state.builder.AddFunction(spv::OpBitwiseOr, TypeU32(state), target, base, perm);
|
||||
return target;
|
||||
}
|
||||
|
||||
@@ -362,17 +362,17 @@ uint32_t EmitDsSwizzleTargetLane(EmitterState& state, uint32_t subid, uint32_t c
|
||||
const auto xored = state.builder.AllocateId();
|
||||
const auto base = state.builder.AllocateId();
|
||||
const auto target = state.builder.AllocateId();
|
||||
state.builder.AddFunction(
|
||||
{spv::OpBitwiseAnd, TypeU32(state), lane, subid, ConstantU32(state, 31)});
|
||||
state.builder.AddFunction(
|
||||
{spv::OpBitwiseAnd, TypeU32(state), masked, lane, ConstantU32(state, control & 0x1fu)});
|
||||
state.builder.AddFunction({spv::OpBitwiseOr, TypeU32(state), ored, masked,
|
||||
ConstantU32(state, (control >> 5u) & 0x1fu)});
|
||||
state.builder.AddFunction({spv::OpBitwiseXor, TypeU32(state), xored, ored,
|
||||
ConstantU32(state, (control >> 10u) & 0x1fu)});
|
||||
state.builder.AddFunction(
|
||||
{spv::OpBitwiseAnd, TypeU32(state), base, subid, ConstantU32(state, 0xffffffe0u)});
|
||||
state.builder.AddFunction({spv::OpBitwiseOr, TypeU32(state), target, base, xored});
|
||||
state.builder.AddFunction(spv::OpBitwiseAnd, TypeU32(state), lane, subid,
|
||||
ConstantU32(state, 31));
|
||||
state.builder.AddFunction(spv::OpBitwiseAnd, TypeU32(state), masked, lane,
|
||||
ConstantU32(state, control & 0x1fu));
|
||||
state.builder.AddFunction(spv::OpBitwiseOr, TypeU32(state), ored, masked,
|
||||
ConstantU32(state, (control >> 5u) & 0x1fu));
|
||||
state.builder.AddFunction(spv::OpBitwiseXor, TypeU32(state), xored, ored,
|
||||
ConstantU32(state, (control >> 10u) & 0x1fu));
|
||||
state.builder.AddFunction(spv::OpBitwiseAnd, TypeU32(state), base, subid,
|
||||
ConstantU32(state, 0xffffffe0u));
|
||||
state.builder.AddFunction(spv::OpBitwiseOr, TypeU32(state), target, base, xored);
|
||||
return target;
|
||||
}
|
||||
|
||||
|
||||
@@ -1,18 +1,18 @@
|
||||
#include "graphics/shader/recompiler/backend/spirv/spirvEmitterInternal.h"
|
||||
#include "graphics/shader/recompiler/backend/spirv/spirvEmitterInstructions.h"
|
||||
|
||||
namespace Libs::Graphics::ShaderRecompiler::Spirv::Emitter {
|
||||
namespace {
|
||||
|
||||
uint32_t MeshArray(EmitterState& state, spv::StorageClass storage, uint32_t type, uint32_t count) {
|
||||
const auto array = state.builder.Type(spv::OpTypeArray, {type, ConstantU32(state, count)});
|
||||
const auto array = state.builder.Type(spv::OpTypeArray, type, ConstantU32(state, count));
|
||||
return state.builder.DefineGlobalVariable(TypePointer(state, storage, array), storage);
|
||||
}
|
||||
|
||||
uint32_t MeshElement(EmitterState& state, uint32_t variable, spv::StorageClass storage,
|
||||
uint32_t type, uint32_t index) {
|
||||
const auto pointer = state.builder.AllocateId();
|
||||
state.builder.AddFunction(
|
||||
{spv::OpAccessChain, TypePointer(state, storage, type), pointer, variable, index});
|
||||
state.builder.AddFunction(spv::OpAccessChain, TypePointer(state, storage, type), pointer,
|
||||
variable, index);
|
||||
return pointer;
|
||||
}
|
||||
|
||||
@@ -20,7 +20,7 @@ uint32_t MeshLoad(EmitterState& state, uint32_t variable, spv::StorageClass stor
|
||||
uint32_t index) {
|
||||
const auto pointer = MeshElement(state, variable, storage, type, index);
|
||||
const auto value = state.builder.AllocateId();
|
||||
state.builder.AddFunction({spv::OpLoad, type, value, pointer});
|
||||
state.builder.AddFunction(spv::OpLoad, type, value, pointer);
|
||||
return value;
|
||||
}
|
||||
|
||||
@@ -50,17 +50,17 @@ void DefineMeshOutputs(EmitterState& state) {
|
||||
state.interface_variables.push_back(output.variable_id);
|
||||
state.builder.AddName(output.variable_id, output.debug_name.c_str());
|
||||
if (output.kind == IR::StageOutputKind::Parameter) {
|
||||
state.builder.AddAnnotation(
|
||||
{spv::OpDecorate, output.variable_id, spv::DecorationLocation, output.location});
|
||||
state.builder.AddAnnotation(spv::OpDecorate, output.variable_id,
|
||||
spv::DecorationLocation, output.location);
|
||||
} else {
|
||||
state.builder.AddAnnotation(
|
||||
{spv::OpDecorate, output.variable_id, spv::DecorationBuiltIn,
|
||||
output.kind == IR::StageOutputKind::Layer ? spv::BuiltInLayer
|
||||
: spv::BuiltInPosition});
|
||||
state.builder.AddAnnotation(spv::OpDecorate, output.variable_id, spv::DecorationBuiltIn,
|
||||
output.kind == IR::StageOutputKind::Layer
|
||||
? spv::BuiltInLayer
|
||||
: spv::BuiltInPosition);
|
||||
}
|
||||
if (output.kind == IR::StageOutputKind::Layer) {
|
||||
state.builder.AddAnnotation({spv::OpDecorate, output.variable_id,
|
||||
spv::DecorationPerPrimitiveEXT}); // PerPrimitiveEXT
|
||||
state.builder.AddAnnotation(spv::OpDecorate, output.variable_id,
|
||||
spv::DecorationPerPrimitiveEXT); // PerPrimitiveEXT
|
||||
}
|
||||
}
|
||||
state.mesh_allocation = MeshArray(state, spv::StorageClassWorkgroup, TypeU32(state), 2);
|
||||
@@ -73,11 +73,11 @@ void DefineMeshOutputs(EmitterState& state) {
|
||||
state.interface_variables.push_back(state.mesh_primitives);
|
||||
state.interface_variables.push_back(state.mesh_cull);
|
||||
state.builder.AddAnnotation(
|
||||
{spv::OpDecorate, state.mesh_primitives, spv::DecorationBuiltIn,
|
||||
spv::BuiltInPrimitiveTriangleIndicesEXT}); // PrimitiveTriangleIndicesEXT
|
||||
state.builder.AddAnnotation({spv::OpDecorate, state.mesh_cull, spv::DecorationBuiltIn,
|
||||
spv::BuiltInCullPrimitiveEXT}); // CullPrimitiveEXT
|
||||
state.builder.AddAnnotation({spv::OpDecorate, state.mesh_cull, spv::DecorationPerPrimitiveEXT});
|
||||
spv::OpDecorate, state.mesh_primitives, spv::DecorationBuiltIn,
|
||||
spv::BuiltInPrimitiveTriangleIndicesEXT); // PrimitiveTriangleIndicesEXT
|
||||
state.builder.AddAnnotation(spv::OpDecorate, state.mesh_cull, spv::DecorationBuiltIn,
|
||||
spv::BuiltInCullPrimitiveEXT); // CullPrimitiveEXT
|
||||
state.builder.AddAnnotation(spv::OpDecorate, state.mesh_cull, spv::DecorationPerPrimitiveEXT);
|
||||
}
|
||||
|
||||
uint32_t MeshOutputPointer(EmitterState& state, IR::StageOutputKind kind, uint32_t index) {
|
||||
@@ -103,45 +103,42 @@ uint32_t MeshPrimitivePointer(EmitterState& state) {
|
||||
void EmitMeshAllocate(ValueEmitContext& ctx, const IR::Inst& inst) {
|
||||
auto& state = ctx.state;
|
||||
const auto first = state.builder.AllocateId();
|
||||
state.builder.AddFunction({spv::OpIEqual, TypeBool(state), first,
|
||||
EmitLocalInvocationIndex(state), ConstantU32(state, 0)});
|
||||
state.builder.AddFunction(spv::OpIEqual, TypeBool(state), first,
|
||||
EmitLocalInvocationIndex(state), ConstantU32(state, 0));
|
||||
EmitIfCondition(state, first, [&] {
|
||||
const auto allocation = ctx.Arg(inst, 0);
|
||||
for (uint32_t field = 0; field < 2; field++) {
|
||||
const auto value = state.builder.AllocateId();
|
||||
state.builder.AddFunction({spv::OpBitFieldUExtract, TypeU32(state), value, allocation,
|
||||
ConstantU32(state, field * 12u),
|
||||
ConstantU32(state, field == 0 ? 10u : 11u)});
|
||||
state.builder.AddFunction(spv::OpBitFieldUExtract, TypeU32(state), value, allocation,
|
||||
ConstantU32(state, field * 12u),
|
||||
ConstantU32(state, field == 0 ? 10u : 11u));
|
||||
const auto pointer =
|
||||
MeshElement(state, state.mesh_allocation, spv::StorageClassWorkgroup,
|
||||
TypeU32(state), ConstantU32(state, field));
|
||||
state.builder.AddFunction({spv::OpStore, pointer, value});
|
||||
state.builder.AddFunction(spv::OpStore, pointer, value);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
void EmitMeshEntryPoint(EmitterState& state) {
|
||||
state.builder.AddFunction({spv::OpFunction, TypeVoid(state), state.main_func,
|
||||
spv::FunctionControlMaskNone, TypeFunction(state)});
|
||||
state.builder.AddFunction(spv::OpFunction, TypeVoid(state), state.main_func,
|
||||
spv::FunctionControlMaskNone, TypeFunction(state));
|
||||
EmitLabel(state, state.builder.AllocateId());
|
||||
state.builder.AddFunction(
|
||||
{spv::OpFunctionCall, TypeVoid(state), state.builder.AllocateId(), state.mesh_guest_func});
|
||||
state.builder.AddFunction(spv::OpFunctionCall, TypeVoid(state), state.builder.AllocateId(),
|
||||
state.mesh_guest_func);
|
||||
// All guest waves finish before the uniform Vulkan allocation and output stores.
|
||||
state.builder.AddFunction({spv::OpControlBarrier, ConstantU32(state, spv::ScopeWorkgroup),
|
||||
ConstantU32(state, spv::ScopeWorkgroup),
|
||||
ConstantU32(state, spv::MemorySemanticsAcquireReleaseMask |
|
||||
spv::MemorySemanticsWorkgroupMemoryMask)});
|
||||
EmitBarrier(state);
|
||||
const auto vertices = MeshLoad(state, state.mesh_allocation, spv::StorageClassWorkgroup,
|
||||
TypeU32(state), ConstantU32(state, 0));
|
||||
const auto primitives = MeshLoad(state, state.mesh_allocation, spv::StorageClassWorkgroup,
|
||||
TypeU32(state), ConstantU32(state, 1));
|
||||
state.builder.AddFunction(
|
||||
{spv::OpSetMeshOutputsEXT, vertices, primitives}); // OpSetMeshOutputsEXT
|
||||
state.builder.AddFunction(spv::OpSetMeshOutputsEXT, vertices,
|
||||
primitives); // OpSetMeshOutputsEXT
|
||||
for (uint32_t half = 0; half < state.lane_count; half++) {
|
||||
state.lane_half = half;
|
||||
const auto index = EmitLocalInvocationIndex(state);
|
||||
const auto is_vertex = state.builder.AllocateId();
|
||||
state.builder.AddFunction({spv::OpULessThan, TypeBool(state), is_vertex, index, vertices});
|
||||
state.builder.AddFunction(spv::OpULessThan, TypeBool(state), is_vertex, index, vertices);
|
||||
EmitIfCondition(state, is_vertex, [&] {
|
||||
for (const auto& output: state.outputs) {
|
||||
if (output.kind == IR::StageOutputKind::Layer) {
|
||||
@@ -153,12 +150,12 @@ void EmitMeshEntryPoint(EmitterState& state) {
|
||||
ConstantU32(state, half));
|
||||
const auto pointer =
|
||||
MeshElement(state, output.variable_id, spv::StorageClassOutput, type, index);
|
||||
state.builder.AddFunction({spv::OpStore, pointer, value});
|
||||
state.builder.AddFunction(spv::OpStore, pointer, value);
|
||||
}
|
||||
});
|
||||
const auto is_primitive = state.builder.AllocateId();
|
||||
state.builder.AddFunction(
|
||||
{spv::OpULessThan, TypeBool(state), is_primitive, index, primitives});
|
||||
state.builder.AddFunction(spv::OpULessThan, TypeBool(state), is_primitive, index,
|
||||
primitives);
|
||||
EmitIfCondition(state, is_primitive, [&] {
|
||||
const auto packed = MeshLoad(state, state.mesh_primitive_data, spv::StorageClassPrivate,
|
||||
TypeU32(state), ConstantU32(state, half));
|
||||
@@ -166,25 +163,25 @@ void EmitMeshEntryPoint(EmitterState& state) {
|
||||
for (uint32_t component = 0; component < 3; component++) {
|
||||
vertex[component] = state.builder.AllocateId();
|
||||
state.builder.AddFunction(
|
||||
{spv::OpBitFieldUExtract, TypeU32(state), vertex[component], packed,
|
||||
ConstantU32(state, component * 10u), ConstantU32(state, 10)});
|
||||
spv::OpBitFieldUExtract, TypeU32(state), vertex[component], packed,
|
||||
ConstantU32(state, component * 10u), ConstantU32(state, 10));
|
||||
}
|
||||
const auto triangle = state.builder.AllocateId();
|
||||
state.builder.AddFunction({spv::OpCompositeConstruct, TypeU32Vector(state, 3), triangle,
|
||||
vertex[0], vertex[1], vertex[2]});
|
||||
state.builder.AddFunction(spv::OpCompositeConstruct, TypeU32Vector(state, 3), triangle,
|
||||
vertex[0], vertex[1], vertex[2]);
|
||||
const auto triangle_pointer =
|
||||
MeshElement(state, state.mesh_primitives, spv::StorageClassOutput,
|
||||
TypeU32Vector(state, 3), index);
|
||||
state.builder.AddFunction({spv::OpStore, triangle_pointer, triangle});
|
||||
state.builder.AddFunction(spv::OpStore, triangle_pointer, triangle);
|
||||
const auto null_bit =
|
||||
EmitBinaryU32(state, spv::OpBitwiseAnd, packed, ConstantU32(state, 0x80000000u));
|
||||
const auto culled = state.builder.AllocateId();
|
||||
state.builder.AddFunction(
|
||||
{spv::OpINotEqual, TypeBool(state), culled, null_bit, ConstantU32(state, 0)});
|
||||
state.builder.AddFunction({spv::OpStore,
|
||||
MeshElement(state, state.mesh_cull, spv::StorageClassOutput,
|
||||
TypeBool(state), index),
|
||||
culled});
|
||||
state.builder.AddFunction(spv::OpINotEqual, TypeBool(state), culled, null_bit,
|
||||
ConstantU32(state, 0));
|
||||
state.builder.AddFunction(spv::OpStore,
|
||||
MeshElement(state, state.mesh_cull, spv::StorageClassOutput,
|
||||
TypeBool(state), index),
|
||||
culled);
|
||||
for (const auto& output: state.outputs) {
|
||||
if (output.kind != IR::StageOutputKind::Layer) {
|
||||
continue;
|
||||
@@ -194,13 +191,13 @@ void EmitMeshEntryPoint(EmitterState& state) {
|
||||
vertex[state.input_info.vertex->mesh.provoking_vertex]);
|
||||
const auto pointer = MeshElement(state, output.variable_id, spv::StorageClassOutput,
|
||||
TypeU32(state), index);
|
||||
state.builder.AddFunction({spv::OpStore, pointer, layer});
|
||||
state.builder.AddFunction(spv::OpStore, pointer, layer);
|
||||
}
|
||||
});
|
||||
}
|
||||
state.lane_half = 0;
|
||||
state.builder.AddFunction({spv::OpReturn});
|
||||
state.builder.AddFunction({spv::OpFunctionEnd});
|
||||
state.builder.AddFunction(spv::OpReturn);
|
||||
state.builder.AddFunction(spv::OpFunctionEnd);
|
||||
}
|
||||
|
||||
} // namespace Libs::Graphics::ShaderRecompiler::Spirv::Emitter
|
||||
Loaded 100 of 230 files, more files were not shown because too many files have changed in this diff.
Show more
Reference in new issue
Block a user