mirror of
https://github.com/KytyPS5/KytyPS5.git
synced 2026-08-03 11:23:49 +00:00
Compare commits
61
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
2e9e2ae566 | ||
|
|
53feb6cb19 | ||
|
|
05d14f5421 | ||
|
|
1a164c3628 | ||
|
|
86a586025f | ||
|
|
06cbd602c1 | ||
|
|
055d0920d7 | ||
|
|
f2ee98fe31 | ||
|
|
207cef9602 | ||
|
|
c4134a95e2 | ||
|
|
fb5ecec455 | ||
|
|
650d9c91a1 | ||
|
|
fc8d2a3b83 | ||
|
|
59b8fad341 | ||
|
|
b877b4be9c | ||
|
|
9da7fc5dd6 | ||
|
|
da0d33224d | ||
|
|
f831e60412 | ||
|
|
84236d1f87 | ||
|
|
302b579779 | ||
|
|
0b6bf01b36 | ||
|
|
66f640527d | ||
|
|
4631b96178 | ||
|
|
43f64e4ab4 | ||
|
|
e63f5b7d5c | ||
|
|
fa7c3c01bf | ||
|
|
89651f6f59 | ||
|
|
44d7f2a3e8 | ||
|
|
2dcb90066c | ||
|
|
51a33cc363 | ||
|
|
ed84370786 | ||
|
|
43f30d3ab2 | ||
|
|
0838142abd | ||
|
|
0f550d1fd0 | ||
|
|
c1a5927036 | ||
|
|
bc436548a9 | ||
|
|
a65d17a5d6 | ||
|
|
c690aeea62 | ||
|
|
f830d6b2e4 | ||
|
|
d68a477276 | ||
|
|
8977d4d2f0 | ||
|
|
4b4e3bf3cf | ||
|
|
a0bb129f02 | ||
|
|
846002c5eb | ||
|
|
d8a4c83cc7 | ||
|
|
68be13345a | ||
|
|
167da0abe0 | ||
|
|
48c31d61ee | ||
|
|
212282d693 | ||
|
|
6bca35d1f5 | ||
|
|
e4ad5fc988 | ||
|
|
c0d3d261ea | ||
|
|
3b75a5659a | ||
|
|
d475387171 | ||
|
|
2f5396c6a5 | ||
|
|
ecb48f90bb | ||
|
|
77aa28b27c | ||
|
|
d04938c88c | ||
|
|
85622befb8 | ||
|
|
3965d41d36 | ||
|
|
c508c4a9c0 |
@@ -73,7 +73,7 @@ jobs:
|
||||
- name: Configure
|
||||
shell: cmd
|
||||
run: |
|
||||
cmake -S src -B _Build/windows ^
|
||||
cmake -S . -B _Build/windows ^
|
||||
-G Ninja ^
|
||||
-DCMAKE_BUILD_TYPE=Release ^
|
||||
-DCMAKE_C_COMPILER=clang-cl ^
|
||||
@@ -83,7 +83,12 @@ jobs:
|
||||
- name: Build
|
||||
shell: cmd
|
||||
run: |
|
||||
cmake --build _Build/windows --target launcher --parallel
|
||||
cmake --build _Build/windows --target launcher audio_out2_port_tests virtual_memory_allocation_tests --parallel
|
||||
|
||||
- name: Test
|
||||
shell: cmd
|
||||
run: |
|
||||
ctest --test-dir _Build/windows --output-on-failure -R "^(audio_out2_port|virtual_memory_allocation)$"
|
||||
|
||||
- name: Install
|
||||
shell: cmd
|
||||
@@ -142,7 +147,7 @@ jobs:
|
||||
- name: Configure
|
||||
shell: bash
|
||||
run: |
|
||||
cmake -S src -B _Build/macos \
|
||||
cmake -S . -B _Build/macos \
|
||||
-G Ninja \
|
||||
-DCMAKE_BUILD_TYPE=Release \
|
||||
-DCMAKE_OSX_ARCHITECTURES=x86_64 \
|
||||
@@ -153,7 +158,15 @@ jobs:
|
||||
- name: Build
|
||||
shell: bash
|
||||
run: |
|
||||
cmake --build _Build/macos --target launcher --parallel
|
||||
cmake --build _Build/macos \
|
||||
--target launcher audio_out2_port_tests virtual_memory_allocation_tests \
|
||||
--parallel
|
||||
|
||||
- name: Test
|
||||
shell: bash
|
||||
run: |
|
||||
ctest --test-dir _Build/macos --output-on-failure \
|
||||
-R '^(audio_out2_port|virtual_memory_allocation)$'
|
||||
|
||||
- name: Install
|
||||
shell: bash
|
||||
@@ -257,7 +270,7 @@ jobs:
|
||||
shell: bash
|
||||
run: |
|
||||
mkdir -p _Build
|
||||
cmake -S src -B _Build/linux \
|
||||
cmake -S . -B _Build/linux \
|
||||
-G Ninja \
|
||||
-DCMAKE_BUILD_TYPE=Release \
|
||||
-DCMAKE_C_COMPILER=clang \
|
||||
@@ -284,13 +297,14 @@ jobs:
|
||||
run: |
|
||||
cmake --build _Build/linux \
|
||||
--target launcher page_manager_tests memory_tracker_tests \
|
||||
audio_out2_port_tests virtual_memory_allocation_tests \
|
||||
--parallel
|
||||
|
||||
- name: Test
|
||||
shell: bash
|
||||
run: |
|
||||
ctest --test-dir _Build/linux --output-on-failure \
|
||||
-R '^(page_manager|memory_tracker)$'
|
||||
-R '^(audio_out2_port|page_manager|memory_tracker|virtual_memory_allocation)$'
|
||||
|
||||
- name: Install
|
||||
shell: bash
|
||||
|
||||
@@ -4,3 +4,4 @@
|
||||
build/
|
||||
_Build/vscode-clang/
|
||||
_Build/
|
||||
cmake-build-debug/
|
||||
|
||||
Vendored
+1
-1
@@ -1,5 +1,5 @@
|
||||
{
|
||||
"cmake.sourceDirectory": "${workspaceFolder}/src",
|
||||
"cmake.sourceDirectory": "${workspaceFolder}",
|
||||
"cmake.buildDirectory": "${workspaceFolder}/_Build/vscode-clang",
|
||||
"cmake.generator": "Ninja",
|
||||
"cmake.environment": {
|
||||
|
||||
@@ -6,6 +6,10 @@ endif()
|
||||
|
||||
project(Kyty)
|
||||
|
||||
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")
|
||||
|
||||
if(CMAKE_SYSTEM_NAME MATCHES ".*Linux")
|
||||
set(LINUX TRUE)
|
||||
endif()
|
||||
@@ -19,13 +23,11 @@ set(CMAKE_CXX_STANDARD_REQUIRED ON)
|
||||
set(CMAKE_CXX_EXTENSIONS OFF)
|
||||
set(CMAKE_CXX_SCAN_FOR_MODULES OFF)
|
||||
|
||||
include(utils.cmake)
|
||||
include("${KYTY_SOURCE_DIR}/utils.cmake")
|
||||
include(CTest)
|
||||
|
||||
option(KYTY_ENABLE_CLANG_TIDY "Run clang-tidy checks during builds" OFF)
|
||||
|
||||
set(KYTY_THIRD_PARTY_DIR "${CMAKE_CURRENT_SOURCE_DIR}/../3rdparty")
|
||||
|
||||
set(CMAKE_WINDOWS_EXPORT_ALL_SYMBOLS ON)
|
||||
|
||||
include(TestBigEndian)
|
||||
@@ -103,13 +105,13 @@ include_directories(
|
||||
${KYTY_THIRD_PARTY_DIR}/fmt/include
|
||||
${KYTY_THIRD_PARTY_DIR}/magic_enum/include/magic_enum
|
||||
${PROJECT_BINARY_DIR}
|
||||
${CMAKE_CURRENT_SOURCE_DIR}
|
||||
${KYTY_SOURCE_DIR}
|
||||
)
|
||||
|
||||
set(KYTY_VERSION "${PROJECT_VERSION}")
|
||||
|
||||
configure_file(
|
||||
${PROJECT_SOURCE_DIR}/cmake_config.h.in
|
||||
${KYTY_SOURCE_DIR}/cmake_config.h.in
|
||||
${PROJECT_BINARY_DIR}/cmake_config.h
|
||||
)
|
||||
|
||||
@@ -117,11 +119,11 @@ find_package(Git)
|
||||
|
||||
add_custom_target( KytyGitVersion
|
||||
COMMAND ${CMAKE_COMMAND}
|
||||
-D INPUT_FILE=${CMAKE_CURRENT_SOURCE_DIR}/kytyGitVersion.h.in
|
||||
-D OUTPUT_FILE=${CMAKE_CURRENT_BINARY_DIR}/kytyGitVersion.h
|
||||
-D INPUT_FILE=${KYTY_SOURCE_DIR}/kytyGitVersion.h.in
|
||||
-D OUTPUT_FILE=${PROJECT_BINARY_DIR}/kytyGitVersion.h
|
||||
-D GIT_EXECUTABLE=${GIT_EXECUTABLE}
|
||||
-D GIT_WORKING_DIRECTORY=${CMAKE_CURRENT_SOURCE_DIR}
|
||||
-P ${CMAKE_CURRENT_SOURCE_DIR}/generate_version.cmake
|
||||
-P ${KYTY_SOURCE_DIR}/generate_version.cmake
|
||||
COMMENT "Generate kytyGitVersion.h"
|
||||
)
|
||||
|
||||
@@ -143,51 +145,51 @@ option(KYTY_BUILD_LAUNCHER "Build Qt launcher" ON)
|
||||
config_compiler_and_linker()
|
||||
|
||||
add_subdirectory("${KYTY_THIRD_PARTY_DIR}" "${CMAKE_CURRENT_BINARY_DIR}/3rdparty")
|
||||
add_subdirectory(common)
|
||||
add_subdirectory("${KYTY_SOURCE_DIR}/common" "${CMAKE_CURRENT_BINARY_DIR}/common")
|
||||
|
||||
file(GLOB kyty_emulator_src CONFIGURE_DEPENDS
|
||||
libs/*.cpp
|
||||
libs/*.h
|
||||
graphics/*.cpp
|
||||
graphics/*.h
|
||||
graphics/guest_gpu/*.cpp
|
||||
graphics/guest_gpu/*.h
|
||||
graphics/guest_gpu/command_processor/*.cpp
|
||||
graphics/guest_gpu/command_processor/*.h
|
||||
graphics/host_gpu/*.cpp
|
||||
graphics/host_gpu/*.h
|
||||
graphics/host_gpu/renderer/*.cpp
|
||||
graphics/host_gpu/renderer/*.h
|
||||
graphics/host_gpu/renderer/cache/*.cpp
|
||||
graphics/host_gpu/renderer/cache/*.h
|
||||
graphics/host_gpu/renderer/image/*.cpp
|
||||
graphics/host_gpu/renderer/image/*.h
|
||||
graphics/host_gpu/renderer/pipeline/*.cpp
|
||||
graphics/host_gpu/renderer/pipeline/*.h
|
||||
graphics/shader/*.cpp
|
||||
graphics/shader/*.h
|
||||
graphics/shader/recompiler/*.cpp
|
||||
graphics/shader/recompiler/*.h
|
||||
graphics/shader/recompiler/cfg/*.cpp
|
||||
graphics/shader/recompiler/cfg/*.h
|
||||
graphics/shader/recompiler/decompiler/*.cpp
|
||||
graphics/shader/recompiler/decompiler/*.h
|
||||
graphics/shader/recompiler/emitter/*.cpp
|
||||
graphics/shader/recompiler/emitter/*.h
|
||||
graphics/shader/recompiler/ir/*.cpp
|
||||
graphics/shader/recompiler/ir/*.h
|
||||
graphics/presentation/*.cpp
|
||||
graphics/presentation/*.h
|
||||
graphics/presentation/window/*.cpp
|
||||
graphics/presentation/window/*.h
|
||||
kernel/*.cpp
|
||||
kernel/*.h
|
||||
loader/*.cpp
|
||||
loader/*.h
|
||||
"${KYTY_SOURCE_DIR}/libs/*.cpp"
|
||||
"${KYTY_SOURCE_DIR}/libs/*.h"
|
||||
"${KYTY_SOURCE_DIR}/graphics/*.cpp"
|
||||
"${KYTY_SOURCE_DIR}/graphics/*.h"
|
||||
"${KYTY_SOURCE_DIR}/graphics/guest_gpu/*.cpp"
|
||||
"${KYTY_SOURCE_DIR}/graphics/guest_gpu/*.h"
|
||||
"${KYTY_SOURCE_DIR}/graphics/guest_gpu/command_processor/*.cpp"
|
||||
"${KYTY_SOURCE_DIR}/graphics/guest_gpu/command_processor/*.h"
|
||||
"${KYTY_SOURCE_DIR}/graphics/host_gpu/*.cpp"
|
||||
"${KYTY_SOURCE_DIR}/graphics/host_gpu/*.h"
|
||||
"${KYTY_SOURCE_DIR}/graphics/host_gpu/renderer/*.cpp"
|
||||
"${KYTY_SOURCE_DIR}/graphics/host_gpu/renderer/*.h"
|
||||
"${KYTY_SOURCE_DIR}/graphics/host_gpu/renderer/cache/*.cpp"
|
||||
"${KYTY_SOURCE_DIR}/graphics/host_gpu/renderer/cache/*.h"
|
||||
"${KYTY_SOURCE_DIR}/graphics/host_gpu/renderer/image/*.cpp"
|
||||
"${KYTY_SOURCE_DIR}/graphics/host_gpu/renderer/image/*.h"
|
||||
"${KYTY_SOURCE_DIR}/graphics/host_gpu/renderer/pipeline/*.cpp"
|
||||
"${KYTY_SOURCE_DIR}/graphics/host_gpu/renderer/pipeline/*.h"
|
||||
"${KYTY_SOURCE_DIR}/graphics/shader/*.cpp"
|
||||
"${KYTY_SOURCE_DIR}/graphics/shader/*.h"
|
||||
"${KYTY_SOURCE_DIR}/graphics/shader/recompiler/*.cpp"
|
||||
"${KYTY_SOURCE_DIR}/graphics/shader/recompiler/*.h"
|
||||
"${KYTY_SOURCE_DIR}/graphics/shader/recompiler/cfg/*.cpp"
|
||||
"${KYTY_SOURCE_DIR}/graphics/shader/recompiler/cfg/*.h"
|
||||
"${KYTY_SOURCE_DIR}/graphics/shader/recompiler/decompiler/*.cpp"
|
||||
"${KYTY_SOURCE_DIR}/graphics/shader/recompiler/decompiler/*.h"
|
||||
"${KYTY_SOURCE_DIR}/graphics/shader/recompiler/emitter/*.cpp"
|
||||
"${KYTY_SOURCE_DIR}/graphics/shader/recompiler/emitter/*.h"
|
||||
"${KYTY_SOURCE_DIR}/graphics/shader/recompiler/ir/*.cpp"
|
||||
"${KYTY_SOURCE_DIR}/graphics/shader/recompiler/ir/*.h"
|
||||
"${KYTY_SOURCE_DIR}/graphics/presentation/*.cpp"
|
||||
"${KYTY_SOURCE_DIR}/graphics/presentation/*.h"
|
||||
"${KYTY_SOURCE_DIR}/graphics/presentation/window/*.cpp"
|
||||
"${KYTY_SOURCE_DIR}/graphics/presentation/window/*.h"
|
||||
"${KYTY_SOURCE_DIR}/kernel/*.cpp"
|
||||
"${KYTY_SOURCE_DIR}/kernel/*.h"
|
||||
"${KYTY_SOURCE_DIR}/loader/*.cpp"
|
||||
"${KYTY_SOURCE_DIR}/loader/*.h"
|
||||
)
|
||||
|
||||
find_program(KYTY_GLSLANG_VALIDATOR glslangValidator REQUIRED)
|
||||
set(gpu_tiler_shader_dir "${CMAKE_CURRENT_SOURCE_DIR}/graphics/host_gpu/shaders")
|
||||
set(gpu_tiler_shader_dir "${KYTY_SOURCE_DIR}/graphics/host_gpu/shaders")
|
||||
set(gpu_tiler_generated_dir "${PROJECT_BINARY_DIR}/gpu_tiler_shaders")
|
||||
set(gpu_tiler_shader_names
|
||||
standard256
|
||||
@@ -215,7 +217,7 @@ foreach(shader_name IN LISTS gpu_tiler_shader_names)
|
||||
COMMAND "${KYTY_GLSLANG_VALIDATOR}" -V --target-env vulkan1.0 -Os
|
||||
"-I${gpu_tiler_shader_dir}" -o "${shader_spv}" "${shader_source}"
|
||||
COMMAND ${CMAKE_COMMAND} -DINPUT=${shader_spv} -DOUTPUT=${shader_header}
|
||||
-DSYMBOL=${shader_symbol} -P "${CMAKE_CURRENT_SOURCE_DIR}/embed_spirv.cmake"
|
||||
-DSYMBOL=${shader_symbol} -P "${KYTY_SOURCE_DIR}/embed_spirv.cmake"
|
||||
DEPENDS "${shader_source}" ${gpu_tiler_shader_includes}
|
||||
VERBATIM
|
||||
)
|
||||
@@ -239,7 +241,7 @@ foreach(shader_source IN LISTS gpu_blit_shader_sources)
|
||||
COMMAND "${KYTY_GLSLANG_VALIDATOR}" -V --target-env vulkan1.0 -Os
|
||||
"-I${gpu_tiler_shader_dir}" -o "${shader_spv}" "${shader_source}"
|
||||
COMMAND ${CMAKE_COMMAND} -DINPUT=${shader_spv} -DOUTPUT=${shader_header}
|
||||
-DSYMBOL=${shader_symbol} -P "${CMAKE_CURRENT_SOURCE_DIR}/embed_spirv.cmake"
|
||||
-DSYMBOL=${shader_symbol} -P "${KYTY_SOURCE_DIR}/embed_spirv.cmake"
|
||||
DEPENDS "${shader_source}"
|
||||
VERBATIM
|
||||
)
|
||||
@@ -248,8 +250,8 @@ endforeach()
|
||||
list(APPEND kyty_emulator_src ${gpu_blit_shader_headers})
|
||||
|
||||
list(APPEND kyty_emulator_src
|
||||
emulator.h
|
||||
emulator.cpp
|
||||
"${KYTY_SOURCE_DIR}/emulator.h"
|
||||
"${KYTY_SOURCE_DIR}/emulator.cpp"
|
||||
)
|
||||
list(REMOVE_DUPLICATES kyty_emulator_src)
|
||||
|
||||
@@ -280,7 +282,7 @@ if(LINUX)
|
||||
endif()
|
||||
|
||||
set(inc_headers
|
||||
${CMAKE_CURRENT_SOURCE_DIR}
|
||||
${KYTY_SOURCE_DIR}
|
||||
${KYTY_THIRD_PARTY_DIR}/SDL2/include
|
||||
${KYTY_THIRD_PARTY_DIR}/VulkanMemoryAllocator/include
|
||||
${KYTY_THIRD_PARTY_DIR}/SPIRV-Tools/include
|
||||
@@ -298,7 +300,7 @@ if (KYTY_CLANG_CL)
|
||||
endif()
|
||||
|
||||
list(APPEND check_headers
|
||||
${CMAKE_CURRENT_SOURCE_DIR}
|
||||
${KYTY_SOURCE_DIR}
|
||||
)
|
||||
|
||||
function(add_kyty_full_emulator_test target source)
|
||||
@@ -312,90 +314,116 @@ function(add_kyty_full_emulator_test target source)
|
||||
target_link_libraries(${target} onecore)
|
||||
add_custom_command(TARGET ${target} POST_BUILD COMMAND ${CMAKE_COMMAND} -E copy_if_different "${KYTY_THIRD_PARTY_DIR}/winpthread/bin/libwinpthread-1.dll" $<TARGET_FILE_DIR:${target}>/libwinpthread-1.dll)
|
||||
endif()
|
||||
# The macOS x86_64 guest address space needs its .zerofill segments anchored
|
||||
# by linker flags, or the kernel kills the binary on load (posix_spawn EIO).
|
||||
configure_macos_guest_address_space(${target})
|
||||
endfunction()
|
||||
|
||||
add_kyty_full_emulator_test(shader_cfg_tests ../tests/shaderCfgTests.cpp)
|
||||
function(configure_macos_guest_address_space target)
|
||||
if(APPLE AND (CMAKE_OSX_ARCHITECTURES STREQUAL "x86_64" OR
|
||||
(NOT CMAKE_OSX_ARCHITECTURES AND CMAKE_SYSTEM_PROCESSOR MATCHES "^(x86_64|AMD64)$")))
|
||||
target_sources(${target} PRIVATE "${KYTY_SOURCE_DIR}/kernel/macosGuestAddressSpace.cpp")
|
||||
target_compile_definitions(${target} PRIVATE KYTY_LINKED_GUEST_ADDRESS_SPACE=1)
|
||||
target_link_options(${target} PRIVATE
|
||||
-Wl,-ld_classic,-no_pie,-no_fixup_chains,-no_huge,-pagezero_size,0x40000,-segaddr,SYSTEM_MANAGED,0x40000,-segaddr,SYSTEM_RESERVED,0x7ffffc000,-segaddr,USER_AREA,0x7000000000,-image_base,0x700000000000)
|
||||
endif()
|
||||
endfunction()
|
||||
|
||||
add_kyty_full_emulator_test(shader_cfg_tests "${KYTY_TESTS_DIR}/shaderCfgTests.cpp")
|
||||
|
||||
add_executable(scalar_provenance_tests EXCLUDE_FROM_ALL
|
||||
../tests/ScalarProvenanceTests.cpp
|
||||
graphics/host_gpu/hostMemory.cpp
|
||||
graphics/shader/recompiler/ir/ScalarProvenance.cpp
|
||||
graphics/shader/recompiler/ir/SrtWalker.cpp
|
||||
"${KYTY_TESTS_DIR}/ScalarProvenanceTests.cpp"
|
||||
"${KYTY_SOURCE_DIR}/graphics/host_gpu/hostMemory.cpp"
|
||||
"${KYTY_SOURCE_DIR}/graphics/shader/recompiler/ir/ReadLaneElimination.cpp"
|
||||
"${KYTY_SOURCE_DIR}/graphics/shader/recompiler/ir/ScalarProvenance.cpp"
|
||||
"${KYTY_SOURCE_DIR}/graphics/shader/recompiler/ir/SrtWalker.cpp"
|
||||
)
|
||||
target_link_libraries(scalar_provenance_tests fmt::fmt)
|
||||
target_include_directories(scalar_provenance_tests PRIVATE ${inc_headers})
|
||||
|
||||
add_executable(page_manager_tests EXCLUDE_FROM_ALL
|
||||
../tests/PageManagerTests.cpp
|
||||
graphics/host_gpu/pageManager.cpp
|
||||
"${KYTY_TESTS_DIR}/PageManagerTests.cpp"
|
||||
"${KYTY_SOURCE_DIR}/graphics/host_gpu/pageManager.cpp"
|
||||
)
|
||||
target_include_directories(page_manager_tests PRIVATE ${inc_headers})
|
||||
|
||||
add_executable(bit_array_tests EXCLUDE_FROM_ALL
|
||||
"${KYTY_TESTS_DIR}/BitArrayTests.cpp"
|
||||
)
|
||||
target_include_directories(bit_array_tests PRIVATE ${inc_headers})
|
||||
|
||||
add_executable(memory_tracker_tests EXCLUDE_FROM_ALL
|
||||
../tests/MemoryTrackerTests.cpp
|
||||
graphics/host_gpu/pageManager.cpp
|
||||
graphics/host_gpu/memoryTracker.cpp
|
||||
"${KYTY_TESTS_DIR}/MemoryTrackerTests.cpp"
|
||||
"${KYTY_SOURCE_DIR}/graphics/host_gpu/pageManager.cpp"
|
||||
"${KYTY_SOURCE_DIR}/graphics/host_gpu/memoryTracker.cpp"
|
||||
)
|
||||
target_link_libraries(memory_tracker_tests fmt::fmt common)
|
||||
target_include_directories(memory_tracker_tests PRIVATE ${inc_headers})
|
||||
target_compile_definitions(memory_tracker_tests PRIVATE KYTY_MEMORY_TRACKER_TESTS=1)
|
||||
|
||||
add_executable(shader_vertex_metadata_tests EXCLUDE_FROM_ALL
|
||||
../tests/ShaderVertexMetadataTests.cpp
|
||||
graphics/host_gpu/hostMemory.cpp
|
||||
graphics/shader/shaderVertexMetadata.cpp
|
||||
"${KYTY_TESTS_DIR}/ShaderVertexMetadataTests.cpp"
|
||||
"${KYTY_SOURCE_DIR}/graphics/host_gpu/hostMemory.cpp"
|
||||
"${KYTY_SOURCE_DIR}/graphics/shader/shaderVertexMetadata.cpp"
|
||||
)
|
||||
target_include_directories(shader_vertex_metadata_tests PRIVATE ${inc_headers})
|
||||
|
||||
add_executable(shader_stage_runtime_tests EXCLUDE_FROM_ALL
|
||||
../tests/ShaderStageRuntimeTests.cpp
|
||||
graphics/guest_gpu/gpu_format.cpp
|
||||
graphics/host_gpu/hostMemory.cpp
|
||||
graphics/shader/shaderStageRuntime.cpp
|
||||
graphics/shader/recompiler/ir/ResourceMaterialization.cpp
|
||||
graphics/shader/recompiler/ir/ScalarProvenance.cpp
|
||||
graphics/shader/recompiler/ir/SrtWalker.cpp
|
||||
"${KYTY_TESTS_DIR}/ShaderStageRuntimeTests.cpp"
|
||||
"${KYTY_SOURCE_DIR}/graphics/guest_gpu/gpu_format.cpp"
|
||||
"${KYTY_SOURCE_DIR}/graphics/host_gpu/hostMemory.cpp"
|
||||
"${KYTY_SOURCE_DIR}/graphics/shader/shaderStageRuntime.cpp"
|
||||
"${KYTY_SOURCE_DIR}/graphics/shader/recompiler/ir/ResourceMaterialization.cpp"
|
||||
"${KYTY_SOURCE_DIR}/graphics/shader/recompiler/ir/ScalarProvenance.cpp"
|
||||
"${KYTY_SOURCE_DIR}/graphics/shader/recompiler/ir/SrtWalker.cpp"
|
||||
)
|
||||
target_link_libraries(shader_stage_runtime_tests fmt::fmt)
|
||||
target_include_directories(shader_stage_runtime_tests PRIVATE ${inc_headers})
|
||||
|
||||
add_executable(resource_tracking_tests EXCLUDE_FROM_ALL
|
||||
../tests/ResourceTrackingTests.cpp
|
||||
graphics/guest_gpu/gpu_format.cpp
|
||||
graphics/host_gpu/hostMemory.cpp
|
||||
graphics/shader/recompiler/ir/ScalarProvenance.cpp
|
||||
graphics/shader/recompiler/ir/SrtWalker.cpp
|
||||
graphics/shader/recompiler/ir/SrtPatcher.cpp
|
||||
graphics/shader/recompiler/ir/ResourceTracking.cpp
|
||||
graphics/shader/recompiler/ir/ResourceMaterialization.cpp
|
||||
graphics/shader/recompiler/ir/ShaderInfoCollection.cpp
|
||||
graphics/shader/recompiler/ir/BindingLayout.cpp
|
||||
"${KYTY_TESTS_DIR}/ResourceTrackingTests.cpp"
|
||||
"${KYTY_SOURCE_DIR}/graphics/guest_gpu/gpu_format.cpp"
|
||||
"${KYTY_SOURCE_DIR}/graphics/host_gpu/hostMemory.cpp"
|
||||
"${KYTY_SOURCE_DIR}/graphics/shader/recompiler/ir/ScalarProvenance.cpp"
|
||||
"${KYTY_SOURCE_DIR}/graphics/shader/recompiler/ir/SrtWalker.cpp"
|
||||
"${KYTY_SOURCE_DIR}/graphics/shader/recompiler/ir/SrtPatcher.cpp"
|
||||
"${KYTY_SOURCE_DIR}/graphics/shader/recompiler/ir/ResourceTracking.cpp"
|
||||
"${KYTY_SOURCE_DIR}/graphics/shader/recompiler/ir/ResourceMaterialization.cpp"
|
||||
"${KYTY_SOURCE_DIR}/graphics/shader/recompiler/ir/ShaderInfoCollection.cpp"
|
||||
"${KYTY_SOURCE_DIR}/graphics/shader/recompiler/ir/BindingLayout.cpp"
|
||||
)
|
||||
target_link_libraries(resource_tracking_tests fmt::fmt)
|
||||
target_include_directories(resource_tracking_tests PRIVATE ${inc_headers})
|
||||
|
||||
add_executable(resource_mutex_tests EXCLUDE_FROM_ALL
|
||||
../tests/ResourceMutexTests.cpp
|
||||
graphics/host_gpu/renderer/cache/resourceMutex.cpp
|
||||
"${KYTY_TESTS_DIR}/ResourceMutexTests.cpp"
|
||||
"${KYTY_SOURCE_DIR}/graphics/host_gpu/renderer/cache/resourceMutex.cpp"
|
||||
)
|
||||
target_link_libraries(resource_mutex_tests common)
|
||||
target_include_directories(resource_mutex_tests PRIVATE ${inc_headers})
|
||||
|
||||
add_executable(audio_out2_port_tests EXCLUDE_FROM_ALL
|
||||
"${KYTY_TESTS_DIR}/AudioOut2PortTests.cpp"
|
||||
"${KYTY_SOURCE_DIR}/libs/libAudio2.cpp"
|
||||
"${KYTY_SOURCE_DIR}/loader/timer.cpp"
|
||||
)
|
||||
target_link_libraries(audio_out2_port_tests common fmt::fmt)
|
||||
target_include_directories(audio_out2_port_tests PRIVATE ${inc_headers})
|
||||
|
||||
add_executable(event_queue_lifetime_tests EXCLUDE_FROM_ALL
|
||||
../tests/EventQueueLifetimeTests.cpp
|
||||
kernel/eventQueue.cpp
|
||||
loader/timer.cpp
|
||||
"${KYTY_TESTS_DIR}/EventQueueLifetimeTests.cpp"
|
||||
"${KYTY_SOURCE_DIR}/kernel/eventQueue.cpp"
|
||||
"${KYTY_SOURCE_DIR}/loader/timer.cpp"
|
||||
)
|
||||
target_link_libraries(event_queue_lifetime_tests common fmt::fmt)
|
||||
target_include_directories(event_queue_lifetime_tests PRIVATE ${inc_headers})
|
||||
|
||||
add_executable(image_page_table_tests EXCLUDE_FROM_ALL
|
||||
../tests/ImagePageTableTests.cpp
|
||||
"${KYTY_TESTS_DIR}/ImagePageTableTests.cpp"
|
||||
)
|
||||
target_link_libraries(image_page_table_tests common fmt::fmt)
|
||||
target_include_directories(image_page_table_tests PRIVATE ${inc_headers})
|
||||
|
||||
add_kyty_full_emulator_test(shader_recompiler_compute_tests ../tests/ShaderRecompilerComputeTests.cpp)
|
||||
add_kyty_full_emulator_test(shader_recompiler_compute_tests "${KYTY_TESTS_DIR}/ShaderRecompilerComputeTests.cpp")
|
||||
|
||||
set(gpu_test_generated_dir "${PROJECT_BINARY_DIR}/gpu_test_shaders")
|
||||
set(gpu_test_ms_depth_source
|
||||
@@ -411,14 +439,14 @@ add_custom_command(
|
||||
-o "${gpu_test_ms_depth_spv}" "${gpu_test_ms_depth_source}"
|
||||
COMMAND ${CMAKE_COMMAND} -DINPUT=${gpu_test_ms_depth_spv}
|
||||
-DOUTPUT=${gpu_test_ms_depth_header} -DSYMBOL=GPU_TEST_MS_DEPTH_SPV
|
||||
-P "${CMAKE_CURRENT_SOURCE_DIR}/embed_spirv.cmake"
|
||||
-P "${KYTY_SOURCE_DIR}/embed_spirv.cmake"
|
||||
DEPENDS "${gpu_test_ms_depth_source}"
|
||||
VERBATIM
|
||||
)
|
||||
target_sources(shader_recompiler_compute_tests PRIVATE
|
||||
"${gpu_test_ms_depth_header}")
|
||||
|
||||
add_kyty_full_emulator_test(virtual_memory_allocation_tests ../tests/VirtualMemoryAllocationTests.cpp)
|
||||
add_kyty_full_emulator_test(virtual_memory_allocation_tests "${KYTY_TESTS_DIR}/VirtualMemoryAllocationTests.cpp")
|
||||
target_compile_definitions(virtual_memory_allocation_tests PRIVATE
|
||||
KYTY_VIRTUAL_MEMORY_ALLOCATION_TESTS=1)
|
||||
|
||||
@@ -431,12 +459,21 @@ if(NOT KYTY_CLANG_CL)
|
||||
endif()
|
||||
|
||||
if(BUILD_TESTING)
|
||||
add_test(NAME shader_cfg COMMAND $<TARGET_FILE:shader_cfg_tests>)
|
||||
add_test(NAME scalar_provenance COMMAND $<TARGET_FILE:scalar_provenance_tests>)
|
||||
add_test(NAME image_page_table COMMAND $<TARGET_FILE:image_page_table_tests>)
|
||||
add_test(NAME memory_tracker COMMAND $<TARGET_FILE:memory_tracker_tests>)
|
||||
add_test(NAME page_manager COMMAND $<TARGET_FILE:page_manager_tests>)
|
||||
add_test(NAME bit_array COMMAND $<TARGET_FILE:bit_array_tests>)
|
||||
add_test(NAME shader_vertex_metadata COMMAND $<TARGET_FILE:shader_vertex_metadata_tests>)
|
||||
add_test(NAME shader_stage_runtime COMMAND $<TARGET_FILE:shader_stage_runtime_tests>)
|
||||
add_test(NAME resource_tracking COMMAND $<TARGET_FILE:resource_tracking_tests>)
|
||||
add_test(NAME resource_mutex COMMAND $<TARGET_FILE:resource_mutex_tests>)
|
||||
add_test(NAME event_queue_lifetime COMMAND $<TARGET_FILE:event_queue_lifetime_tests>)
|
||||
add_test(NAME audio_out2_port COMMAND $<TARGET_FILE:audio_out2_port_tests>)
|
||||
add_test(NAME shader_recompiler_compute COMMAND $<TARGET_FILE:shader_recompiler_compute_tests>)
|
||||
add_test(NAME virtual_memory_allocation
|
||||
COMMAND $<TARGET_FILE:virtual_memory_allocation_tests>)
|
||||
add_test(NAME command_scheduler_timeline
|
||||
COMMAND $<TARGET_FILE:shader_recompiler_compute_tests> --scheduler-only)
|
||||
add_test(NAME stream_buffer_ring
|
||||
@@ -466,10 +503,27 @@ if(BUILD_TESTING)
|
||||
add_test(NAME buffer_cache_ranges
|
||||
COMMAND $<TARGET_FILE:shader_recompiler_compute_tests> --buffer-cache-range-only)
|
||||
endif()
|
||||
|
||||
add_custom_target(kyty_tests DEPENDS
|
||||
shader_cfg_tests
|
||||
scalar_provenance_tests
|
||||
image_page_table_tests
|
||||
memory_tracker_tests
|
||||
page_manager_tests
|
||||
bit_array_tests
|
||||
shader_vertex_metadata_tests
|
||||
shader_stage_runtime_tests
|
||||
resource_tracking_tests
|
||||
resource_mutex_tests
|
||||
event_queue_lifetime_tests
|
||||
shader_recompiler_compute_tests
|
||||
virtual_memory_allocation_tests
|
||||
)
|
||||
endif()
|
||||
|
||||
|
||||
add_executable(kyty_emulator main.cpp ${kyty_emulator_src})
|
||||
add_executable(kyty_emulator "${KYTY_SOURCE_DIR}/main.cpp" ${kyty_emulator_src})
|
||||
configure_macos_guest_address_space(kyty_emulator)
|
||||
|
||||
target_link_libraries(kyty_emulator ${kyty_emulator_link_libraries})
|
||||
if (WIN32)
|
||||
@@ -498,7 +552,12 @@ set(KYTY_EMULATOR_MAP_LINK_PATH "${CMAKE_CURRENT_BINARY_DIR}/${KYTY_EMULATOR_MAP
|
||||
set(KYTY_EMULATOR_PDB_LINK_PATH "${CMAKE_CURRENT_BINARY_DIR}/kyty_emulator.pdb")
|
||||
|
||||
if(KYTY_CLANG_CL)
|
||||
set_target_properties(kyty_emulator PROPERTIES LINK_FLAGS "/DYNAMICBASE:NO /DEBUG:FULL /PDB:${KYTY_EMULATOR_PDB_LINK_PATH} /lldmap:${KYTY_EMULATOR_MAP_LINK_PATH}")
|
||||
target_link_options(kyty_emulator PRIVATE
|
||||
"/DYNAMICBASE:NO"
|
||||
"/DEBUG:FULL"
|
||||
"/PDB:${KYTY_EMULATOR_PDB_LINK_PATH}"
|
||||
"/lldmap:${KYTY_EMULATOR_MAP_LINK_PATH}"
|
||||
)
|
||||
add_custom_command(TARGET kyty_emulator POST_BUILD COMMAND ${CMAKE_COMMAND} -E copy_if_different "${KYTY_THIRD_PARTY_DIR}/winpthread/bin/libwinpthread-1.dll" $<TARGET_FILE_DIR:kyty_emulator>/libwinpthread-1.dll)
|
||||
elseif(WIN32 OR LINUX)
|
||||
set_target_properties(kyty_emulator PROPERTIES LINK_FLAGS "${KYTY_LD_OPTIONS} -Wl,-Map=${KYTY_EMULATOR_MAP_LINK_PATH}")
|
||||
@@ -513,14 +572,14 @@ if(APPLE)
|
||||
# reverts to the hardened defaults and aborts when it first executes written code).
|
||||
add_custom_command(TARGET kyty_emulator POST_BUILD
|
||||
COMMAND codesign -s - --force --options runtime
|
||||
--entitlements "${CMAKE_CURRENT_SOURCE_DIR}/macos_jit.entitlements"
|
||||
--entitlements "${KYTY_SOURCE_DIR}/macos_jit.entitlements"
|
||||
$<TARGET_FILE:kyty_emulator>
|
||||
COMMENT "Codesign kyty_emulator with JIT entitlements (macOS)")
|
||||
endif()
|
||||
|
||||
install(TARGETS kyty_emulator DESTINATION .)
|
||||
if(KYTY_BUILD_LAUNCHER)
|
||||
add_subdirectory(launcher)
|
||||
add_subdirectory("${KYTY_SOURCE_DIR}/launcher" "${CMAKE_CURRENT_BINARY_DIR}/launcher")
|
||||
endif()
|
||||
if(KYTY_CLANG_CL)
|
||||
install(FILES "${KYTY_THIRD_PARTY_DIR}/winpthread/bin/libwinpthread-1.dll" DESTINATION .)
|
||||
@@ -27,8 +27,9 @@ Development is focused on compatibility and boot reliability.
|
||||
Windows is the primary platform and receives the most testing. Linux builds and runs; see
|
||||
[Building on Linux](#building-on-linux).
|
||||
|
||||
macOS support is experimental. Compatibility with the same games on Windows and macOS has not yet
|
||||
been tested.
|
||||
macOS support is experimental. The emulator is built for x86-64 and runs on Apple Silicon under
|
||||
Rosetta 2, with Vulkan provided by MoltenVK. A small number of titles have been verified in-game
|
||||
on Apple Silicon hardware; see [Building on macOS](#building-on-macos).
|
||||
|
||||
## Bugs and Issues
|
||||
|
||||
@@ -50,7 +51,7 @@ graphical glitches, low compatibility, and poor performance.
|
||||
</tr>
|
||||
<tr>
|
||||
<td align="center">
|
||||
<strong>Minecraft Legends</strong><br>
|
||||
<strong>Neptunia ReVerse</strong><br>
|
||||
<img src="docs/screenshots/ps5-04.png" width="300" alt="Minecraft Legends running in KytyPS5">
|
||||
</td>
|
||||
<td align="center">
|
||||
@@ -58,8 +59,20 @@ graphical glitches, low compatibility, and poor performance.
|
||||
<img src="docs/screenshots/ps5-05.png" width="300" alt="SILENT HILL: The Short Message running in KytyPS5">
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td align="center">
|
||||
<strong>Hellboy</strong><br>
|
||||
<img src="docs/screenshots/ps5-02.png" width="300" alt="Disgaea 6 running in KytyPS5">
|
||||
</td>
|
||||
<td align="center">
|
||||
<strong>Paleo Pines</strong><br>
|
||||
<img src="docs/screenshots/ps5-06.png" width="300" alt="Dreaming Sarah running in KytyPS5">
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
|
||||
<p align="center"><em>And many more...</em></p>
|
||||
|
||||
## Contributing
|
||||
|
||||
Testing games and submitting detailed bug reports are useful ways to contribute. Search existing
|
||||
@@ -102,9 +115,10 @@ the Vulkan/SPIR-V validation rules.
|
||||
|
||||
### System requirements
|
||||
|
||||
- Windows 10 version 1803, or a current Linux distribution
|
||||
- A 64-bit x86 processor
|
||||
- A Vulkan 1.3-capable GPU with current drivers
|
||||
- Windows 10 version 1803, a current Linux distribution, or macOS on Apple Silicon
|
||||
- A 64-bit x86 processor (on macOS, an Apple Silicon processor with Rosetta 2)
|
||||
- A Vulkan 1.3-capable GPU with current drivers (on macOS, Vulkan is provided by the bundled
|
||||
MoltenVK)
|
||||
|
||||
### Build requirements (Windows)
|
||||
|
||||
@@ -127,7 +141,7 @@ git submodule update --init --recursive
|
||||
Configure the project. Replace the Qt path with the version installed on your system:
|
||||
|
||||
```powershell
|
||||
cmake -S src -B _Build/windows -G Ninja -DCMAKE_BUILD_TYPE=Release -DCMAKE_C_COMPILER=clang-cl -DCMAKE_CXX_COMPILER=clang-cl -DCMAKE_PREFIX_PATH="C:/Qt/6.x.x/msvc2022_64"
|
||||
cmake -S . -B _Build/windows -G Ninja -DCMAKE_BUILD_TYPE=Release -DCMAKE_C_COMPILER=clang-cl -DCMAKE_CXX_COMPILER=clang-cl -DCMAKE_PREFIX_PATH="C:/Qt/6.x.x/msvc2022_64"
|
||||
```
|
||||
|
||||
Build the launcher and stage a runnable installation:
|
||||
@@ -160,7 +174,7 @@ Qt 6 (Concurrent, Network, Widgets) is also required — either the distribution
|
||||
```bash
|
||||
git submodule update --init --recursive
|
||||
|
||||
cmake -S src -B _Build/linux -G Ninja -DCMAKE_BUILD_TYPE=Release \
|
||||
cmake -S . -B _Build/linux -G Ninja -DCMAKE_BUILD_TYPE=Release \
|
||||
-DCMAKE_C_COMPILER=clang -DCMAKE_CXX_COMPILER=clang++ \
|
||||
-DCMAKE_PREFIX_PATH="$Qt6_DIR"
|
||||
|
||||
@@ -174,7 +188,57 @@ The install step copies the Qt libraries and plugins next to the binaries, so
|
||||
As on Windows, the MSVC compiler is not used; Clang is required. `cl.exe` is rejected at configure
|
||||
time.
|
||||
|
||||
Note that the CMake source root is `src`, not the repository root.
|
||||
The CMake source root is the repository root.
|
||||
|
||||
### Building on macOS
|
||||
|
||||
macOS builds target x86-64 and run under Rosetta 2 on Apple Silicon, so the PS5's x86-64 game
|
||||
code executes through the same translation layer as the emulator itself. Prebuilt archives are
|
||||
attached to releases; the steps below are for building from source.
|
||||
|
||||
Requirements:
|
||||
|
||||
- An Apple Silicon Mac with Rosetta 2 installed (`softwareupdate --install-rosetta`)
|
||||
- Xcode (or the Command Line Tools)
|
||||
- Homebrew packages: `brew install cmake ninja glslang`
|
||||
- Qt 6 (Concurrent, Network, Widgets) with x86-64 support. The official Qt installation is
|
||||
universal and works; Homebrew's Qt is arm64-only and will not link
|
||||
|
||||
```bash
|
||||
git submodule update --init --recursive
|
||||
|
||||
cmake -S . -B _Build/macos -G Ninja -DCMAKE_BUILD_TYPE=Release \
|
||||
-DCMAKE_OSX_ARCHITECTURES=x86_64 \
|
||||
-DCMAKE_C_COMPILER=clang -DCMAKE_CXX_COMPILER=clang++ \
|
||||
-DCMAKE_PREFIX_PATH="$Qt6_DIR"
|
||||
|
||||
cmake --build _Build/macos --target launcher --parallel
|
||||
cmake --install _Build/macos --prefix _Build/macos/install
|
||||
```
|
||||
|
||||
The build re-signs `kyty_emulator` with the JIT entitlements it needs to execute translated
|
||||
guest code; no manual signing step is required.
|
||||
|
||||
Vulkan comes from MoltenVK. Download `MoltenVK-macos.tar` from the
|
||||
[MoltenVK releases](https://github.com/KhronosGroup/MoltenVK/releases), then copy
|
||||
`MoltenVK/dynamic/dylib/macOS/libMoltenVK.dylib` next to `kyty_emulator` and ad-hoc sign it:
|
||||
|
||||
```bash
|
||||
codesign --force --sign - _Build/macos/install/libMoltenVK.dylib
|
||||
```
|
||||
|
||||
Release archives already include a signed `libMoltenVK.dylib`.
|
||||
|
||||
### Regression tests
|
||||
|
||||
Build every regression executable and run the registered tests with:
|
||||
|
||||
```powershell
|
||||
cmake --build _Build/windows --target kyty_tests
|
||||
ctest --test-dir _Build/windows --output-on-failure
|
||||
```
|
||||
|
||||
Use `_Build/linux` instead of `_Build/windows` for a Linux build.
|
||||
|
||||
### Visual Studio Code
|
||||
|
||||
@@ -221,6 +285,14 @@ The emulator can also be started directly with a legally obtained game directory
|
||||
./_Build/linux/install/kyty_emulator --game "/games/ExampleGame"
|
||||
```
|
||||
|
||||
On macOS, point SDL at the MoltenVK library explicitly; the hardened runtime prevents it from
|
||||
being picked up from the executable's directory:
|
||||
|
||||
```bash
|
||||
cd _Build/macos/install
|
||||
SDL_VULKAN_LIBRARY="$PWD/libMoltenVK.dylib" ./kyty_emulator --game "/games/ExampleGame"
|
||||
```
|
||||
|
||||
Run `kyty_emulator --help` to see the available graphics, logging, validation, profiling, and
|
||||
debugging options.
|
||||
|
||||
|
||||
Binary file not shown.
|
After Width: | Height: | Size: 1.6 MiB |
Binary file not shown.
|
Before Width: | Height: | Size: 2.7 MiB After Width: | Height: | Size: 2.6 MiB |
Binary file not shown.
|
After Width: | Height: | Size: 2.4 MiB |
@@ -13,7 +13,7 @@ endif()
|
||||
add_library(common STATIC ${common_src} ${common_headers})
|
||||
|
||||
target_include_directories(common
|
||||
PUBLIC ${PROJECT_SOURCE_DIR}
|
||||
PUBLIC ${KYTY_SOURCE_DIR}
|
||||
PRIVATE
|
||||
${KYTY_THIRD_PARTY_DIR}/cpuinfo/include
|
||||
)
|
||||
|
||||
@@ -0,0 +1,260 @@
|
||||
#ifndef EMULATOR_SRC_COMMON_BITARRAY_H_
|
||||
#define EMULATOR_SRC_COMMON_BITARRAY_H_
|
||||
|
||||
#include <array>
|
||||
#include <bit>
|
||||
#include <cstddef>
|
||||
#include <cstdint>
|
||||
#include <iterator>
|
||||
#include <utility>
|
||||
|
||||
namespace Common {
|
||||
|
||||
template <size_t N>
|
||||
class BitArray final {
|
||||
static_assert(N != 0, "BitArray size must be nonzero");
|
||||
static_assert(N % 64 == 0, "BitArray size must be a multiple of 64 bits");
|
||||
|
||||
static constexpr size_t BITS_PER_WORD = 64;
|
||||
static constexpr size_t WORD_COUNT = N / BITS_PER_WORD;
|
||||
|
||||
public:
|
||||
using Range = std::pair<size_t, size_t>;
|
||||
|
||||
class Iterator final {
|
||||
public:
|
||||
using iterator_category = std::forward_iterator_tag;
|
||||
using value_type = Range;
|
||||
using difference_type = std::ptrdiff_t;
|
||||
using pointer = const Range*;
|
||||
using reference = const Range&;
|
||||
|
||||
Iterator(const BitArray& bits, size_t start)
|
||||
: m_bits(bits), m_range(bits.FirstRangeFrom(start)) {}
|
||||
|
||||
Iterator& operator++() {
|
||||
m_range = m_bits.FirstRangeFrom(m_range.second);
|
||||
return *this;
|
||||
}
|
||||
|
||||
[[nodiscard]] bool operator==(const Iterator& other) const {
|
||||
return &m_bits == &other.m_bits && m_range == other.m_range;
|
||||
}
|
||||
|
||||
[[nodiscard]] bool operator!=(const Iterator& other) const { return !(*this == other); }
|
||||
|
||||
[[nodiscard]] reference operator*() const { return m_range; }
|
||||
[[nodiscard]] pointer operator->() const { return &m_range; }
|
||||
|
||||
private:
|
||||
const BitArray& m_bits;
|
||||
Range m_range;
|
||||
};
|
||||
|
||||
using const_iterator = Iterator;
|
||||
|
||||
constexpr BitArray() = default;
|
||||
|
||||
constexpr BitArray(const BitArray& other, size_t start, size_t end) {
|
||||
if (start >= end || end > N) {
|
||||
return;
|
||||
}
|
||||
|
||||
const auto first_word = start / BITS_PER_WORD;
|
||||
const auto last_word = (end - 1) / BITS_PER_WORD;
|
||||
const auto start_bit = start % BITS_PER_WORD;
|
||||
const auto end_bit = (end - 1) % BITS_PER_WORD;
|
||||
const auto start_mask = ~uint64_t {0} << start_bit;
|
||||
const auto end_mask =
|
||||
end_bit == BITS_PER_WORD - 1 ? ~uint64_t {0} : (uint64_t {1} << (end_bit + 1)) - 1;
|
||||
|
||||
if (first_word == last_word) {
|
||||
m_data[first_word] = other.m_data[first_word] & start_mask & end_mask;
|
||||
return;
|
||||
}
|
||||
|
||||
m_data[first_word] = other.m_data[first_word] & start_mask;
|
||||
for (auto word = first_word + 1; word < last_word; word++) {
|
||||
m_data[word] = other.m_data[word];
|
||||
}
|
||||
m_data[last_word] = other.m_data[last_word] & end_mask;
|
||||
}
|
||||
|
||||
[[nodiscard]] constexpr bool Get(size_t index) const {
|
||||
return (m_data[index / BITS_PER_WORD] & (uint64_t {1} << (index % BITS_PER_WORD))) != 0;
|
||||
}
|
||||
|
||||
constexpr void Set(size_t index) {
|
||||
m_data[index / BITS_PER_WORD] |= uint64_t {1} << (index % BITS_PER_WORD);
|
||||
}
|
||||
|
||||
constexpr void Unset(size_t index) {
|
||||
m_data[index / BITS_PER_WORD] &= ~(uint64_t {1} << (index % BITS_PER_WORD));
|
||||
}
|
||||
|
||||
constexpr void SetRange(size_t start, size_t end) {
|
||||
if (start >= end || end > N) {
|
||||
return;
|
||||
}
|
||||
|
||||
const auto first_word = start / BITS_PER_WORD;
|
||||
const auto last_word = (end - 1) / BITS_PER_WORD;
|
||||
const auto start_bit = start % BITS_PER_WORD;
|
||||
const auto end_bit = (end - 1) % BITS_PER_WORD;
|
||||
const auto start_mask = ~uint64_t {0} << start_bit;
|
||||
const auto end_mask =
|
||||
end_bit == BITS_PER_WORD - 1 ? ~uint64_t {0} : (uint64_t {1} << (end_bit + 1)) - 1;
|
||||
|
||||
if (first_word == last_word) {
|
||||
m_data[first_word] |= start_mask & end_mask;
|
||||
return;
|
||||
}
|
||||
|
||||
m_data[first_word] |= start_mask;
|
||||
for (auto word = first_word + 1; word < last_word; word++) {
|
||||
m_data[word] = ~uint64_t {0};
|
||||
}
|
||||
m_data[last_word] |= end_mask;
|
||||
}
|
||||
|
||||
constexpr void UnsetRange(size_t start, size_t end) {
|
||||
if (start >= end || end > N) {
|
||||
return;
|
||||
}
|
||||
|
||||
const auto first_word = start / BITS_PER_WORD;
|
||||
const auto last_word = (end - 1) / BITS_PER_WORD;
|
||||
const auto start_bit = start % BITS_PER_WORD;
|
||||
const auto end_bit = (end - 1) % BITS_PER_WORD;
|
||||
const auto start_mask = (uint64_t {1} << start_bit) - 1;
|
||||
const auto end_mask =
|
||||
end_bit == BITS_PER_WORD - 1 ? uint64_t {0} : ~((uint64_t {1} << (end_bit + 1)) - 1);
|
||||
|
||||
if (first_word == last_word) {
|
||||
m_data[first_word] &= start_mask | end_mask;
|
||||
return;
|
||||
}
|
||||
|
||||
m_data[first_word] &= start_mask;
|
||||
for (auto word = first_word + 1; word < last_word; word++) {
|
||||
m_data[word] = 0;
|
||||
}
|
||||
m_data[last_word] &= end_mask;
|
||||
}
|
||||
|
||||
constexpr void Clear() { m_data.fill(0); }
|
||||
constexpr void Fill() { m_data.fill(~uint64_t {0}); }
|
||||
|
||||
[[nodiscard]] constexpr bool None() const {
|
||||
uint64_t combined = 0;
|
||||
for (const auto word: m_data) {
|
||||
combined |= word;
|
||||
}
|
||||
return combined == 0;
|
||||
}
|
||||
|
||||
[[nodiscard]] constexpr bool Any() const { return !None(); }
|
||||
|
||||
[[nodiscard]] constexpr Range FirstRangeFrom(size_t start) const {
|
||||
if (start >= N) {
|
||||
return {N, N};
|
||||
}
|
||||
|
||||
auto word_index = start / BITS_PER_WORD;
|
||||
auto word = m_data[word_index] & (~uint64_t {0} << (start % BITS_PER_WORD));
|
||||
while (word == 0) {
|
||||
word_index++;
|
||||
if (word_index == WORD_COUNT) {
|
||||
return {N, N};
|
||||
}
|
||||
word = m_data[word_index];
|
||||
}
|
||||
|
||||
const auto first = word_index * BITS_PER_WORD + std::countr_zero(word);
|
||||
const auto first_bit = first % BITS_PER_WORD;
|
||||
const auto first_ones =
|
||||
static_cast<size_t>(std::countr_one(m_data[word_index] >> first_bit));
|
||||
if (first_bit + first_ones < BITS_PER_WORD) {
|
||||
return {first, first + first_ones};
|
||||
}
|
||||
|
||||
for (word_index++; word_index < WORD_COUNT; word_index++) {
|
||||
word = m_data[word_index];
|
||||
if (word != ~uint64_t {0}) {
|
||||
return {first, word_index * BITS_PER_WORD + std::countr_one(word)};
|
||||
}
|
||||
}
|
||||
return {first, N};
|
||||
}
|
||||
|
||||
[[nodiscard]] constexpr Range FirstRange() const { return FirstRangeFrom(0); }
|
||||
|
||||
[[nodiscard]] constexpr Range LastRangeFrom(size_t end) const {
|
||||
if (end == 0) {
|
||||
return {0, 0};
|
||||
}
|
||||
if (end > N) {
|
||||
end = N;
|
||||
}
|
||||
|
||||
auto word_index = (end - 1) / BITS_PER_WORD;
|
||||
const auto end_bit = (end - 1) % BITS_PER_WORD;
|
||||
const auto end_mask =
|
||||
end_bit == BITS_PER_WORD - 1 ? ~uint64_t {0} : (uint64_t {1} << (end_bit + 1)) - 1;
|
||||
auto word = m_data[word_index] & end_mask;
|
||||
while (word == 0) {
|
||||
if (word_index == 0) {
|
||||
return {0, 0};
|
||||
}
|
||||
word = m_data[--word_index];
|
||||
}
|
||||
|
||||
const auto empty_bits = static_cast<size_t>(std::countl_zero(word));
|
||||
const auto ones = static_cast<size_t>(std::countl_one(word << empty_bits));
|
||||
const auto last = (word_index + 1) * BITS_PER_WORD - empty_bits;
|
||||
if (empty_bits + ones < BITS_PER_WORD) {
|
||||
return {last - ones, last};
|
||||
}
|
||||
|
||||
while (word_index != 0) {
|
||||
word = m_data[--word_index];
|
||||
if (word != ~uint64_t {0}) {
|
||||
return {(word_index + 1) * BITS_PER_WORD - std::countl_one(word), last};
|
||||
}
|
||||
}
|
||||
return {0, last};
|
||||
}
|
||||
|
||||
[[nodiscard]] constexpr Range LastRange() const { return LastRangeFrom(N); }
|
||||
|
||||
[[nodiscard]] const_iterator begin() const { return Iterator(*this, 0); }
|
||||
[[nodiscard]] const_iterator end() const { return Iterator(*this, N); }
|
||||
|
||||
constexpr BitArray& operator^=(const BitArray& other) {
|
||||
for (size_t word = 0; word < WORD_COUNT; word++) {
|
||||
m_data[word] ^= other.m_data[word];
|
||||
}
|
||||
return *this;
|
||||
}
|
||||
|
||||
[[nodiscard]] constexpr BitArray operator^(const BitArray& other) const {
|
||||
auto result = *this;
|
||||
result ^= other;
|
||||
return result;
|
||||
}
|
||||
|
||||
[[nodiscard]] constexpr BitArray operator~() const {
|
||||
auto result = *this;
|
||||
for (auto& word: result.m_data) {
|
||||
word = ~word;
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
private:
|
||||
std::array<uint64_t, WORD_COUNT> m_data {};
|
||||
};
|
||||
|
||||
} // namespace Common
|
||||
|
||||
#endif // EMULATOR_SRC_COMMON_BITARRAY_H_
|
||||
@@ -37,6 +37,10 @@ uint32_t GetVblankFrequency() {
|
||||
return std::clamp(g_config->vblank_frequency, 30u, 360u);
|
||||
}
|
||||
|
||||
uint32_t GetConsoleLanguage() {
|
||||
return g_config->console_language;
|
||||
}
|
||||
|
||||
bool VulkanValidationEnabled() {
|
||||
return g_config->vulkan_validation_enabled;
|
||||
}
|
||||
@@ -89,10 +93,6 @@ bool RenderDocEnabled() {
|
||||
return g_config->renderdoc_enabled;
|
||||
}
|
||||
|
||||
bool NggRectlistDrawEnabled() {
|
||||
return g_config->ngg_rectlist_draw_enabled;
|
||||
}
|
||||
|
||||
bool ReadbackLinearImagesEnabled() {
|
||||
return g_config->readback_linear_images;
|
||||
}
|
||||
|
||||
@@ -18,10 +18,14 @@ enum class ProfilerDirection { None, Network };
|
||||
|
||||
enum class OutputDirection { Silent, Console, File };
|
||||
|
||||
constexpr uint32_t DEFAULT_CONSOLE_LANGUAGE = 1;
|
||||
constexpr uint32_t MAX_CONSOLE_LANGUAGE = 29;
|
||||
|
||||
struct ConfigOptions {
|
||||
uint32_t screen_width = 1280;
|
||||
uint32_t screen_height = 720;
|
||||
uint32_t vblank_frequency = 60;
|
||||
uint32_t console_language = DEFAULT_CONSOLE_LANGUAGE;
|
||||
bool vulkan_validation_enabled = false;
|
||||
bool shader_validation_enabled = false;
|
||||
ShaderOptimizationType shader_optimization_type = ShaderOptimizationType::None;
|
||||
@@ -35,7 +39,6 @@ struct ConfigOptions {
|
||||
ProfilerDirection profiler_direction = ProfilerDirection::None;
|
||||
bool spirv_debug_printf_enabled = false;
|
||||
bool renderdoc_enabled = false;
|
||||
bool ngg_rectlist_draw_enabled = true;
|
||||
bool readback_linear_images = false;
|
||||
};
|
||||
|
||||
@@ -44,6 +47,7 @@ void Load(const ConfigOptions& cfg);
|
||||
uint32_t GetScreenWidth();
|
||||
uint32_t GetScreenHeight();
|
||||
uint32_t GetVblankFrequency();
|
||||
uint32_t GetConsoleLanguage();
|
||||
bool VulkanValidationEnabled();
|
||||
|
||||
bool ShaderValidationEnabled();
|
||||
@@ -64,7 +68,6 @@ ProfilerDirection GetProfilerDirection();
|
||||
bool SpirvDebugPrintfEnabled();
|
||||
|
||||
bool RenderDocEnabled();
|
||||
bool NggRectlistDrawEnabled();
|
||||
bool ReadbackLinearImagesEnabled();
|
||||
|
||||
} // namespace Config
|
||||
|
||||
@@ -324,6 +324,10 @@ bool InstallHandler(Handler handler) {
|
||||
sa.sa_sigaction = SignalHandler;
|
||||
sa.sa_flags = SA_SIGINFO;
|
||||
sigemptyset(&sa.sa_mask);
|
||||
// The guest signal-dispatch path (KernelRaiseException) interrupts threads with
|
||||
// SIGUSR1; block it while a fault is being resolved so a stop-the-world request
|
||||
// cannot preempt the handler between the protection fix and the retry.
|
||||
sigaddset(&sa.sa_mask, SIGUSR1);
|
||||
|
||||
// macOS raises SIGBUS for protection faults on some paths and SIGSEGV on others;
|
||||
// SIGILL covers instructions the host cannot execute (routed to the x64 emulator).
|
||||
|
||||
@@ -49,8 +49,7 @@ public:
|
||||
|
||||
template <typename Function>
|
||||
void ForEachItemBelow(Tick tick, Function&& function) {
|
||||
constexpr bool ReturnsBool =
|
||||
std::is_same_v<std::invoke_result_t<Function, Object>, bool>;
|
||||
constexpr bool ReturnsBool = std::is_same_v<std::invoke_result_t<Function, Object>, bool>;
|
||||
for (auto* item = m_first; item != nullptr;) {
|
||||
if (item->tick > tick) {
|
||||
return;
|
||||
|
||||
@@ -33,8 +33,7 @@ static bool OnOwnStack() {
|
||||
}
|
||||
void* base = nullptr;
|
||||
size_t size = 0;
|
||||
const bool ok =
|
||||
pthread_attr_getstack(&attr, &base, &size) == 0 && base != nullptr && size != 0;
|
||||
const bool ok = pthread_attr_getstack(&attr, &base, &size) == 0 && base != nullptr && size != 0;
|
||||
pthread_attr_destroy(&attr);
|
||||
if (!ok) {
|
||||
return false;
|
||||
|
||||
@@ -172,8 +172,7 @@ sys_file_t* SysFileCreate(const std::filesystem::path& file_name) {
|
||||
return ret;
|
||||
}
|
||||
|
||||
sys_file_t* SysFileOpenR(const std::filesystem::path& file_name,
|
||||
sys_file_cache_type_t cache_type) {
|
||||
sys_file_t* SysFileOpenR(const std::filesystem::path& file_name, sys_file_cache_type_t cache_type) {
|
||||
auto* ret = new sys_file_t;
|
||||
|
||||
ret->type = SYS_FILE_FILE;
|
||||
@@ -218,8 +217,7 @@ sys_file_t* SysFileCreate() {
|
||||
return ret;
|
||||
}
|
||||
|
||||
sys_file_t* SysFileOpenW(const std::filesystem::path& file_name,
|
||||
sys_file_cache_type_t cache_type) {
|
||||
sys_file_t* SysFileOpenW(const std::filesystem::path& file_name, sys_file_cache_type_t cache_type) {
|
||||
auto* ret = new sys_file_t;
|
||||
|
||||
auto real_name = get_internal_name(file_name);
|
||||
|
||||
@@ -136,8 +136,8 @@ static void* map_anonymous(uintptr_t addr, size_t size, int protect, int flags)
|
||||
break;
|
||||
}
|
||||
const auto hint = (top - step) & ~(LOW_ARENA_GRAIN - 1);
|
||||
void* ptr = mmap(reinterpret_cast<void*>(hint), size, protect,
|
||||
flags | MAP_FIXED_NOREPLACE, -1, 0); // NOLINT
|
||||
void* ptr = mmap(reinterpret_cast<void*>(hint), size, protect, flags | MAP_FIXED_NOREPLACE,
|
||||
-1, 0); // NOLINT
|
||||
if (ptr != MAP_FAILED) {
|
||||
return ptr;
|
||||
}
|
||||
@@ -194,8 +194,8 @@ uint64_t SysVirtualAllocAligned(uint64_t address, uint64_t size, VirtualMemory::
|
||||
if (ptr != MAP_FAILED && ((ret_addr & (alignment - 1)) != 0)) {
|
||||
munmap(ptr, size);
|
||||
|
||||
ptr = map_anonymous(addr, size + alignment, protect,
|
||||
MAP_PRIVATE | MAP_ANON | MAP_NORESERVE);
|
||||
ptr =
|
||||
map_anonymous(addr, size + alignment, protect, MAP_PRIVATE | MAP_ANON | MAP_NORESERVE);
|
||||
ret_addr = reinterpret_cast<uintptr_t>(ptr);
|
||||
if (ptr != MAP_FAILED) {
|
||||
#if defined(__APPLE__)
|
||||
|
||||
@@ -5,9 +5,9 @@
|
||||
|
||||
#include <algorithm>
|
||||
#include <atomic>
|
||||
#include <cerrno>
|
||||
#include <chrono> // IWYU pragma: keep
|
||||
#include <condition_variable> // IWYU pragma: keep
|
||||
#include <cerrno>
|
||||
#include <mutex>
|
||||
#include <vector>
|
||||
|
||||
|
||||
@@ -20,9 +20,7 @@ class UniqueFunction {
|
||||
public:
|
||||
explicit Callable(Function function): m_function(std::move(function)) {}
|
||||
|
||||
Result Invoke(Args&&... args) override {
|
||||
return m_function(std::forward<Args>(args)...);
|
||||
}
|
||||
Result Invoke(Args&&... args) override { return m_function(std::forward<Args>(args)...); }
|
||||
|
||||
private:
|
||||
Function m_function;
|
||||
|
||||
@@ -58,25 +58,6 @@ bool FlushInstructionCache(uint64_t address, uint64_t size) {
|
||||
return SysVirtualFlushInstructionCache(address, size);
|
||||
}
|
||||
|
||||
bool PatchReplace(uint64_t vaddr, uint64_t value) {
|
||||
Mode old_mode {};
|
||||
Protect(vaddr, 8, Mode::ReadWrite, &old_mode);
|
||||
|
||||
auto* ptr = reinterpret_cast<uint64_t*>(vaddr);
|
||||
|
||||
bool ret = (*ptr != value);
|
||||
|
||||
*ptr = value;
|
||||
|
||||
Protect(vaddr, 8, old_mode);
|
||||
|
||||
if (IsExecute(old_mode)) {
|
||||
FlushInstructionCache(vaddr, 8);
|
||||
}
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
} // namespace VirtualMemory
|
||||
|
||||
} // namespace Common
|
||||
|
||||
@@ -37,7 +37,6 @@ bool Free(uint64_t address);
|
||||
bool FreeRange(uint64_t address, uint64_t size);
|
||||
bool Protect(uint64_t address, uint64_t size, Mode mode, Mode* old_mode = nullptr);
|
||||
bool FlushInstructionCache(uint64_t address, uint64_t size);
|
||||
bool PatchReplace(uint64_t vaddr, uint64_t value);
|
||||
|
||||
} // namespace VirtualMemory
|
||||
|
||||
|
||||
+13
-12
@@ -105,7 +105,7 @@ static void ClearDebugTextureFolder() {
|
||||
}
|
||||
}
|
||||
|
||||
static void Init(const Config::ConfigOptions& cfg) {
|
||||
static void Init(const Config::ConfigOptions& cfg, const std::filesystem::path& param_json) {
|
||||
EXIT_IF(!Common::Thread::IsMainThread());
|
||||
|
||||
auto* slist = Common::SubsystemsList::Instance();
|
||||
@@ -127,12 +127,21 @@ static void Init(const Config::ConfigOptions& cfg) {
|
||||
slist->InitAll(true);
|
||||
|
||||
Config::Load(cfg);
|
||||
slist->Add(log, {core, config});
|
||||
slist->InitAll(true);
|
||||
|
||||
if (Common::File::IsFileExisting(param_json)) {
|
||||
Loader::SystemContentLoadParamSfo(param_json);
|
||||
if (const auto flexible_memory_size = Loader::SystemContentGetFlexibleMemorySize();
|
||||
flexible_memory_size != 0) {
|
||||
Libs::LibKernel::Memory::SetFlexibleMemorySize(flexible_memory_size);
|
||||
}
|
||||
}
|
||||
|
||||
slist->Add(audio, {core, log, pthread, memory});
|
||||
slist->Add(controller, {core, log, config});
|
||||
slist->Add(file_system, {core, log, pthread});
|
||||
slist->Add(graphics, {core, log, pthread, memory, config, profiler, controller});
|
||||
slist->Add(log, {core, config});
|
||||
slist->Add(memory, {core, log});
|
||||
slist->Add(network, {core, log, pthread});
|
||||
slist->Add(profiler, {core, config});
|
||||
@@ -180,7 +189,8 @@ void Run(const RunOptions& options) {
|
||||
EXIT("ELF is required\n");
|
||||
}
|
||||
|
||||
Init(options.config);
|
||||
const auto param_json = options.app0_dir / "sce_sys" / "param.json";
|
||||
Init(options.config, param_json);
|
||||
|
||||
ClearDebugTextureFolder();
|
||||
|
||||
@@ -192,15 +202,6 @@ void Run(const RunOptions& options) {
|
||||
Libs::LibKernel::FileSystem::Mount(options.app0_dir, "/app0");
|
||||
Libs::LibKernel::FileSystem::Mount(options.app0_dir, "/hostapp");
|
||||
|
||||
auto param_json = options.app0_dir / "sce_sys" / "param.json";
|
||||
if (Common::File::IsFileExisting(param_json)) {
|
||||
Loader::SystemContentLoadParamSfo(param_json);
|
||||
if (auto flexible_memory_size = Loader::SystemContentGetFlexibleMemorySize();
|
||||
flexible_memory_size != 0) {
|
||||
Libs::LibKernel::Memory::SetFlexibleMemorySize(flexible_memory_size);
|
||||
}
|
||||
}
|
||||
|
||||
MountSandboxDirs();
|
||||
|
||||
auto* rt = Common::Singleton<Loader::RuntimeLinker>::Instance();
|
||||
|
||||
@@ -83,8 +83,7 @@ public:
|
||||
uint32_t first_instance = 0);
|
||||
void DrawIndexOffset(uint32_t index_offset, uint32_t index_count, uint32_t flags);
|
||||
void DrawIndexAuto(uint32_t index_count, uint32_t flags,
|
||||
uint32_t render_target_slice_offset = 0, uint32_t instance_count = 1,
|
||||
uint32_t first_vertex = 0, uint32_t first_instance = 0);
|
||||
uint32_t render_target_slice_offset = 0);
|
||||
void DrawIndirect(uint32_t data_offset, uint32_t draw_initiator, bool indexed);
|
||||
void DrawIndirectMulti(uint32_t data_offset, uint32_t max_count_or_count,
|
||||
const volatile uint32_t* count_addr, uint32_t stride_in_bytes,
|
||||
@@ -152,6 +151,9 @@ private:
|
||||
uint32_t interrupt_context_id);
|
||||
void ProcessPm4(Pm4Execution& execution, size_t stop_depth);
|
||||
void SuspendPm4();
|
||||
void SubmitNonIndexedDraw(uint32_t vertex_count, uint32_t flags,
|
||||
uint32_t render_target_slice_offset, uint32_t first_vertex,
|
||||
uint32_t first_instance);
|
||||
|
||||
CommandScheduler& GetScheduler() const { return m_renderer.GetCommandScheduler(); }
|
||||
RenderCommandBuffer& CurrentBuffer() { return GetScheduler().Current(); }
|
||||
@@ -168,6 +170,7 @@ private:
|
||||
uint64_t m_index_base_addr = 0;
|
||||
uint64_t m_draw_indirect_args_base_addr = 0;
|
||||
uint64_t m_dispatch_indirect_args_base_addr = 0;
|
||||
// Persistent draw state: indirect draws update it for subsequent draws.
|
||||
uint32_t m_num_instances = 1;
|
||||
|
||||
uint32_t m_de_count = 0;
|
||||
|
||||
@@ -1926,8 +1926,14 @@ KYTY_CP_OP_PARSER(CpOpCopyData) {
|
||||
const uint32_t num_bytes = ((control >> 16u) & 0x1u) != 0 ? 8u : 4u;
|
||||
const uint64_t src = buffer[1] | (static_cast<uint64_t>(buffer[2]) << 32u);
|
||||
const uint64_t dst = buffer[3] | (static_cast<uint64_t>(buffer[4]) << 32u);
|
||||
if (src_sel == (9u << 1u)) {
|
||||
if (dst_sel != (2u << 1u) || dst == 0 || (dst & (num_bytes - 1u)) != 0) {
|
||||
uint32_t reference_clock_dst = 0;
|
||||
switch (src_sel) {
|
||||
case 9u: reference_clock_dst = 2u; break;
|
||||
case 18u: reference_clock_dst = 4u; break;
|
||||
default: break;
|
||||
}
|
||||
if (reference_clock_dst != 0) {
|
||||
if (dst_sel != reference_clock_dst || dst == 0 || (dst & (num_bytes - 1u)) != 0) {
|
||||
EXIT("unsupported reference-clock copyData, src_sel=0x%02" PRIx32
|
||||
" dst_sel=0x%02" PRIx32 " dst=0x%016" PRIx64 " size=%u\n",
|
||||
src_sel, dst_sel, dst, num_bytes);
|
||||
@@ -3390,6 +3396,12 @@ void GraphicsInitJmpTablesCxIndirect() {
|
||||
g_hw_ctx_indirect_func[Pm4::DB_COUNT_CONTROL] = [](KYTY_HW_CTX_INDIRECT_ARGS) {
|
||||
HwCtxIgnoreDepthMetadataRegister(cmd_offset, value);
|
||||
};
|
||||
for (auto cmd_offset = Pm4::DB_SRESULTS_COMPARE_STATE0;
|
||||
cmd_offset <= Pm4::DB_SRESULTS_COMPARE_STATE1; cmd_offset++) {
|
||||
g_hw_ctx_indirect_func[cmd_offset] = [](KYTY_HW_CTX_INDIRECT_ARGS) {
|
||||
HwCtxIgnoreDepthMetadataRegister(cmd_offset, value);
|
||||
};
|
||||
}
|
||||
g_hw_ctx_indirect_func[Pm4::DB_RENDER_OVERRIDE] = [](KYTY_HW_CTX_INDIRECT_ARGS) {
|
||||
HwCtxIgnoreDepthMetadataRegister(cmd_offset, value);
|
||||
};
|
||||
|
||||
@@ -72,6 +72,7 @@ enum class ChannelLayout : uint32_t {
|
||||
k32_32 = 11,
|
||||
k16_16_16_16 = 12,
|
||||
k32_32_32_32 = 14,
|
||||
k5_6_5 = 16,
|
||||
k5_5_5_1 = 17,
|
||||
k4_4_4_4 = 19,
|
||||
kBc1 = 35,
|
||||
@@ -374,6 +375,8 @@ enum class BufferFormat : uint32_t {
|
||||
k32_32_32_32UInt = 75,
|
||||
k32_32_32_32SInt = 76,
|
||||
k32_32_32_32Float = 77,
|
||||
k8Srgb = 128,
|
||||
k8_8Srgb = 129,
|
||||
k8_8_8_8Srgb = 130,
|
||||
k9_9_9_5Float = 132,
|
||||
k5_6_5UNorm = 133,
|
||||
|
||||
@@ -39,6 +39,7 @@ constexpr FormatInfo kFormatInfo[] = {
|
||||
{GpuEnumValue(BufferFormat::k16_16Float), 4, 0, 4, true, false},
|
||||
{GpuEnumValue(BufferFormat::k11_11_10Float), 4, 0, 4, true, false},
|
||||
{GpuEnumValue(BufferFormat::k10_10_10_2UNorm), 4, 0, 4, true, false},
|
||||
{GpuEnumValue(BufferFormat::k10_10_10_2UInt), 4, 0, 4, true, true},
|
||||
{GpuEnumValue(BufferFormat::k8_8_8_8UNorm), 4, 0, 4, true, false},
|
||||
{GpuEnumValue(BufferFormat::k8_8_8_8SNorm), 4, 0, 4, true, false},
|
||||
{GpuEnumValue(BufferFormat::k8_8_8_8UInt), 4, 0, 4, true, true},
|
||||
@@ -57,6 +58,8 @@ constexpr FormatInfo kFormatInfo[] = {
|
||||
{GpuEnumValue(BufferFormat::k32_32_32_32UInt), 16, 0, 16, true, true},
|
||||
{GpuEnumValue(BufferFormat::k32_32_32_32SInt), 16, 0, 16, false, false},
|
||||
{GpuEnumValue(BufferFormat::k32_32_32_32Float), 16, 0, 16, true, false},
|
||||
{GpuEnumValue(BufferFormat::k8Srgb), 1, 0, 0, true, false},
|
||||
{GpuEnumValue(BufferFormat::k8_8Srgb), 2, 0, 0, true, false},
|
||||
{GpuEnumValue(BufferFormat::k8_8_8_8Srgb), 4, 0, 4, true, false},
|
||||
{GpuEnumValue(BufferFormat::k9_9_9_5Float), 4, 0, 0, true, false},
|
||||
{GpuEnumValue(BufferFormat::k5_6_5UNorm), 2, 0, 2, true, false},
|
||||
|
||||
@@ -34,7 +34,6 @@ namespace Libs::Graphics {
|
||||
|
||||
static thread_local CommandProcessor* g_current_processor = nullptr;
|
||||
static thread_local Pm4Execution* g_current_execution = nullptr;
|
||||
static thread_local uint32_t g_submission_pause_depth = 0;
|
||||
static thread_local bool g_gpu_mutex_owned = false;
|
||||
static thread_local bool g_gpu_thread = false;
|
||||
|
||||
@@ -98,8 +97,6 @@ public:
|
||||
bool trigger_agc_interrupt_on_done);
|
||||
void SubmitFlipPreparation(uint64_t request_id);
|
||||
void Done();
|
||||
void PauseSubmissions();
|
||||
void ResumeSubmissions();
|
||||
void Shutdown();
|
||||
[[nodiscard]] bool IsStopping();
|
||||
void SendCommand(Common::UniqueFunction<void>&& command);
|
||||
@@ -410,8 +407,13 @@ void CommandProcessor::WriteData(uint32_t* dst, const uint32_t* src, uint32_t dw
|
||||
const uint32_t increment = (write_control >> 16u) & 0x1u;
|
||||
const uint32_t write_confirm = (write_control >> 20u) & 0x1u;
|
||||
|
||||
if (dst_sel != 0 && dst_sel != 2 && dst_sel != 4 && dst_sel != 5) {
|
||||
EXIT("unsupported writeData destination selector 0x%02" PRIx32 "\n", dst_sel);
|
||||
switch (dst_sel) {
|
||||
case 0:
|
||||
case 2:
|
||||
case 4:
|
||||
case 5:
|
||||
case 6: break;
|
||||
default: EXIT("unsupported writeData destination selector 0x%02" PRIx32 "\n", dst_sel);
|
||||
}
|
||||
EXIT_NOT_IMPLEMENTED(increment != 0);
|
||||
|
||||
@@ -691,26 +693,6 @@ bool GpuState::Process(Submission& submission) {
|
||||
return complete;
|
||||
}
|
||||
|
||||
void GpuState::PauseSubmissions() {
|
||||
if (g_gpu_mutex_owned) {
|
||||
EXIT("GPU submissions are already paused by this thread\n");
|
||||
}
|
||||
g_gpu_mutex_owned = true;
|
||||
m_submission_mutex.Lock();
|
||||
if (!IsGpuThread()) {
|
||||
WaitLocked();
|
||||
}
|
||||
m_renderer.GetCommandScheduler().DrainPriorityOperations();
|
||||
}
|
||||
|
||||
void GpuState::ResumeSubmissions() {
|
||||
if (!g_gpu_mutex_owned) {
|
||||
EXIT("GPU submissions resumed without an active pause\n");
|
||||
}
|
||||
m_submission_mutex.Unlock();
|
||||
g_gpu_mutex_owned = false;
|
||||
}
|
||||
|
||||
Pm4ProcessResult CommandProcessor::Process(Pm4Execution& execution, uint32_t* buffer,
|
||||
uint32_t size_dw) {
|
||||
KYTY_PROFILER_BLOCK("CommandProcessor::Process");
|
||||
@@ -962,9 +944,8 @@ void CommandProcessor::DrawIndexOffset(uint32_t index_offset, uint32_t index_cou
|
||||
auto* index_addr = reinterpret_cast<const void*>(
|
||||
m_index_base_addr + static_cast<uint64_t>(index_offset) * index_size);
|
||||
|
||||
m_renderer.GetRenderExecutor().DrawIndex(m_submit_id, CurrentBuffer(),
|
||||
m_index_type_and_size, index_count, index_addr,
|
||||
flags, 1, m_num_instances);
|
||||
m_renderer.GetRenderExecutor().DrawIndex(m_submit_id, CurrentBuffer(), m_index_type_and_size,
|
||||
index_count, index_addr, flags, 1, m_num_instances);
|
||||
}
|
||||
|
||||
void CommandProcessor::DrawIndirect(uint32_t data_offset, uint32_t draw_initiator, bool indexed) {
|
||||
@@ -1002,8 +983,9 @@ void CommandProcessor::DrawIndirect(uint32_t data_offset, uint32_t draw_initiato
|
||||
args.start_vertex_location, args.start_instance_location);
|
||||
}
|
||||
}
|
||||
DrawIndexAuto(args.vertex_count_per_instance, 0, 0, args.instance_count,
|
||||
args.start_vertex_location, args.start_instance_location);
|
||||
m_num_instances = args.instance_count;
|
||||
SubmitNonIndexedDraw(args.vertex_count_per_instance, 0, 0, args.start_vertex_location,
|
||||
args.start_instance_location);
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -1043,6 +1025,7 @@ void CommandProcessor::DrawIndirect(uint32_t data_offset, uint32_t draw_initiato
|
||||
}
|
||||
}
|
||||
|
||||
m_num_instances = args.instance_count;
|
||||
DrawIndex(index_count, index_addr, 0, 1, args.instance_count, nullptr, 0,
|
||||
static_cast<int32_t>(args.base_vertex_location), args.start_instance_location);
|
||||
}
|
||||
@@ -1100,8 +1083,9 @@ void CommandProcessor::DrawIndirectMulti(uint32_t data_offset, uint32_t max_coun
|
||||
args->start_vertex_location, args->start_instance_location);
|
||||
}
|
||||
}
|
||||
DrawIndexAuto(args->vertex_count_per_instance, 0, 0, args->instance_count,
|
||||
args->start_vertex_location, args->start_instance_location);
|
||||
m_num_instances = args->instance_count;
|
||||
SubmitNonIndexedDraw(args->vertex_count_per_instance, 0, 0, args->start_vertex_location,
|
||||
args->start_instance_location);
|
||||
continue;
|
||||
}
|
||||
|
||||
@@ -1142,6 +1126,7 @@ void CommandProcessor::DrawIndirectMulti(uint32_t data_offset, uint32_t max_coun
|
||||
}
|
||||
}
|
||||
|
||||
m_num_instances = args->instance_count;
|
||||
DrawIndex(index_count, index_addr, 0, 1, args->instance_count, nullptr, 0,
|
||||
static_cast<int32_t>(args->base_vertex_location), args->start_instance_location);
|
||||
}
|
||||
@@ -1190,8 +1175,8 @@ void CommandProcessor::DispatchDirect(uint32_t thread_group_x, uint32_t thread_g
|
||||
}
|
||||
}
|
||||
|
||||
m_renderer.GetRenderExecutor().DispatchDirect(
|
||||
m_submit_id, CurrentBuffer(), thread_group_x, thread_group_y, thread_group_z, mode);
|
||||
m_renderer.GetRenderExecutor().DispatchDirect(m_submit_id, CurrentBuffer(), thread_group_x,
|
||||
thread_group_y, thread_group_z, mode);
|
||||
}
|
||||
|
||||
constexpr uint32_t DispatchInitiatorUseThreadDimensions = 1u << 5u;
|
||||
@@ -1233,13 +1218,18 @@ void CommandProcessor::DispatchIndirect(uint32_t data_offset, uint32_t mode) {
|
||||
}
|
||||
|
||||
void CommandProcessor::DrawIndexAuto(uint32_t index_count, uint32_t flags,
|
||||
uint32_t render_target_slice_offset, uint32_t instance_count,
|
||||
uint32_t render_target_slice_offset) {
|
||||
SubmitNonIndexedDraw(index_count, flags, render_target_slice_offset, 0, 0);
|
||||
}
|
||||
|
||||
void CommandProcessor::SubmitNonIndexedDraw(uint32_t vertex_count, uint32_t flags,
|
||||
uint32_t render_target_slice_offset,
|
||||
uint32_t first_vertex, uint32_t first_instance) {
|
||||
CheckBuffer();
|
||||
|
||||
m_renderer.GetRenderExecutor().DrawAuto(
|
||||
m_submit_id, CurrentBuffer(), index_count, flags, render_target_slice_offset,
|
||||
instance_count, first_vertex, first_instance);
|
||||
m_renderer.GetRenderExecutor().DrawAuto(m_submit_id, CurrentBuffer(), vertex_count, flags,
|
||||
render_target_slice_offset, m_num_instances,
|
||||
first_vertex, first_instance);
|
||||
}
|
||||
|
||||
void CommandProcessor::WaitFlipDone(uint32_t video_out_handle, uint32_t display_buffer_index) {
|
||||
@@ -1317,8 +1307,8 @@ void CommandProcessor::WriteAtEndOfPipe(uint32_t cache_policy, uint32_t event_wr
|
||||
if (eop_event_type == 0x2f && cache_action == 0x00 && event_index == 0x06) {
|
||||
auto* dst = static_cast<uint32_t*>(dst_gpu_addr);
|
||||
SynchronizeGpu();
|
||||
Sync::ReadGds(m_renderer.GetBufferCache().GetGdsBuffer(), dst,
|
||||
value & 0xffffu, value >> 16u);
|
||||
Sync::ReadGds(m_renderer.GetBufferCache().GetGdsBuffer(), dst, value & 0xffffu,
|
||||
value >> 16u);
|
||||
Sync::WriteAtEndOfPipeGds32(m_submit_id, CurrentBuffer(), dst, value & 0xffffu,
|
||||
value >> 16u);
|
||||
return;
|
||||
@@ -1486,8 +1476,7 @@ void CommandProcessor::EmitGlobalBarrier() {
|
||||
barrier.srcStageMask = vk::PipelineStageFlagBits2::eAllCommands;
|
||||
barrier.srcAccessMask = vk::AccessFlagBits2::eMemoryWrite;
|
||||
barrier.dstStageMask = vk::PipelineStageFlagBits2::eAllCommands;
|
||||
barrier.dstAccessMask =
|
||||
vk::AccessFlagBits2::eMemoryRead | vk::AccessFlagBits2::eMemoryWrite;
|
||||
barrier.dstAccessMask = vk::AccessFlagBits2::eMemoryRead | vk::AccessFlagBits2::eMemoryWrite;
|
||||
|
||||
vk::DependencyInfo dependency {};
|
||||
dependency.memoryBarrierCount = 1;
|
||||
@@ -1690,32 +1679,6 @@ int Gpu::GetFrameNum() const {
|
||||
return m_state->GetFrameNum();
|
||||
}
|
||||
|
||||
void Gpu::PauseSubmissions() {
|
||||
m_state->PauseSubmissions();
|
||||
}
|
||||
|
||||
void Gpu::ResumeSubmissions() {
|
||||
m_state->ResumeSubmissions();
|
||||
}
|
||||
|
||||
Gpu::SubmissionLock::SubmissionLock(Gpu& gpu): m_gpu(gpu) {
|
||||
if (g_current_processor != nullptr || g_submission_pause_depth == UINT32_MAX) {
|
||||
EXIT("cannot acquire GPU submission lock in the current state\n");
|
||||
}
|
||||
if (g_submission_pause_depth++ == 0) {
|
||||
m_gpu.PauseSubmissions();
|
||||
}
|
||||
}
|
||||
|
||||
Gpu::SubmissionLock::~SubmissionLock() {
|
||||
if (g_submission_pause_depth == 0) {
|
||||
EXIT("GPU submission lock released without ownership\n");
|
||||
}
|
||||
if (--g_submission_pause_depth == 0) {
|
||||
m_gpu.ResumeSubmissions();
|
||||
}
|
||||
}
|
||||
|
||||
bool Gpu::IsCommandProcessorThread() noexcept {
|
||||
return g_current_processor != nullptr;
|
||||
}
|
||||
@@ -1724,12 +1687,4 @@ CommandProcessor* Gpu::CurrentCommandProcessor() noexcept {
|
||||
return g_current_processor;
|
||||
}
|
||||
|
||||
bool Gpu::SubmissionLockHeld() noexcept {
|
||||
return g_submission_pause_depth != 0;
|
||||
}
|
||||
|
||||
bool Gpu::MutexHeld() noexcept {
|
||||
return g_gpu_mutex_owned;
|
||||
}
|
||||
|
||||
} // namespace Libs::Graphics
|
||||
|
||||
@@ -35,25 +35,8 @@ public:
|
||||
|
||||
[[nodiscard]] static bool IsCommandProcessorThread() noexcept;
|
||||
[[nodiscard]] static CommandProcessor* CurrentCommandProcessor() noexcept;
|
||||
[[nodiscard]] static bool SubmissionLockHeld() noexcept;
|
||||
[[nodiscard]] static bool MutexHeld() noexcept;
|
||||
|
||||
class SubmissionLock final {
|
||||
public:
|
||||
explicit SubmissionLock(Gpu& gpu);
|
||||
~SubmissionLock();
|
||||
KYTY_CLASS_NO_COPY(SubmissionLock);
|
||||
|
||||
private:
|
||||
Gpu& m_gpu;
|
||||
};
|
||||
|
||||
private:
|
||||
friend class SubmissionLock;
|
||||
|
||||
void PauseSubmissions();
|
||||
void ResumeSubmissions();
|
||||
|
||||
std::unique_ptr<GpuState> m_state;
|
||||
};
|
||||
} // namespace Libs::Graphics
|
||||
|
||||
@@ -110,7 +110,6 @@ void DumpPm4PacketStream(Common::File* file, uint32_t* cmd_buffer, uint32_t star
|
||||
auto* cmd = cmd_buffer + start_dw;
|
||||
auto dw = num_dw;
|
||||
while (dw != 0) {
|
||||
EXIT_NOT_IMPLEMENTED(dw < 2);
|
||||
EXIT_NOT_IMPLEMENTED(dw > num_dw);
|
||||
|
||||
auto cmd_id = *cmd++;
|
||||
@@ -120,6 +119,9 @@ void DumpPm4PacketStream(Common::File* file, uint32_t* cmd_buffer, uint32_t star
|
||||
uint32_t len = 0;
|
||||
|
||||
const auto packet_type = static_cast<PacketType>(cmd_id >> 30u);
|
||||
// Type-2 packets are header-only padding; every other packet type requires a body.
|
||||
EXIT_NOT_IMPLEMENTED(dw < 2 && packet_type != PacketType::Type2);
|
||||
|
||||
switch (packet_type) {
|
||||
case PacketType::Type3: {
|
||||
const bool sh_gx = (cmd_id & 0x2u) == 0;
|
||||
|
||||
@@ -4,25 +4,9 @@
|
||||
|
||||
namespace Libs::Graphics {
|
||||
|
||||
#if defined(KYTY_MEMORY_TRACKER_TESTS)
|
||||
namespace {
|
||||
std::atomic<MemoryTracker::UnmapContentionHook> g_unmap_contention_hook {nullptr};
|
||||
}
|
||||
|
||||
void MemoryTracker::SetUnmapContentionHook(UnmapContentionHook hook) noexcept {
|
||||
g_unmap_contention_hook.store(hook, std::memory_order_release);
|
||||
}
|
||||
#endif
|
||||
|
||||
static_assert(std::atomic<void*>::is_always_lock_free);
|
||||
|
||||
MemoryTracker::MemoryTracker(PageManager& page_manager, PageWatchMode gpu_watch_mode)
|
||||
: m_page_manager(page_manager), m_gpu_watch_mode(gpu_watch_mode) {
|
||||
switch (m_gpu_watch_mode) {
|
||||
case PageWatchMode::Write:
|
||||
case PageWatchMode::ReadWrite: break;
|
||||
default: EXIT("unsupported memory tracker GPU page-watch mode\n");
|
||||
}
|
||||
MemoryTracker::MemoryTracker(PageManager& page_manager): m_page_manager(page_manager) {
|
||||
m_regions = std::make_unique<std::atomic<RegionManager*>[]>(REGION_COUNT);
|
||||
for (size_t i = 0; i < REGION_COUNT; i++) {
|
||||
m_regions[i].store(nullptr, std::memory_order_relaxed);
|
||||
@@ -31,6 +15,7 @@ MemoryTracker::MemoryTracker(PageManager& page_manager, PageWatchMode gpu_watch_
|
||||
|
||||
MemoryTracker::~MemoryTracker() = default;
|
||||
|
||||
#if KYTY_BUILD == KYTY_BUILD_DEBUG
|
||||
void MemoryTracker::ValidateGpuDirtyPages(const RangeSet& dirty, uint64_t vaddr, uint64_t size,
|
||||
const char* operation) const noexcept {
|
||||
if (vaddr == 0 || size == 0 || size > UINT64_MAX - vaddr ||
|
||||
@@ -68,6 +53,7 @@ void MemoryTracker::ValidateGpuDirtyOwnership(const RangeSet& dirty, uint64_t va
|
||||
}
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
void MemoryTracker::ValidateRange(uint64_t vaddr, uint64_t size) {
|
||||
if (vaddr == 0 || size == 0 || vaddr >= TRACKER_ADDRESS_SIZE ||
|
||||
@@ -94,7 +80,6 @@ RegionManager* MemoryTracker::GetOrCreateRegion(uint64_t index) {
|
||||
bool MemoryTracker::IsRegionCpuModified(uint64_t vaddr, uint64_t size) {
|
||||
CheckNotInUploadCallback();
|
||||
std::lock_guard access(m_access_mutex);
|
||||
RequireMapped(vaddr, size);
|
||||
return Iterate<true>(vaddr, size, [](RegionManager* manager, uint64_t offset, uint64_t bytes) {
|
||||
std::scoped_lock lock(manager->lock);
|
||||
return manager->IsModified<DirtySource::Cpu>(offset, bytes);
|
||||
@@ -104,7 +89,6 @@ bool MemoryTracker::IsRegionCpuModified(uint64_t vaddr, uint64_t size) {
|
||||
bool MemoryTracker::IsRegionGpuModified(uint64_t vaddr, uint64_t size) {
|
||||
CheckNotInUploadCallback();
|
||||
std::lock_guard access(m_access_mutex);
|
||||
RequireMapped(vaddr, size);
|
||||
return Iterate<false>(vaddr, size, [](RegionManager* manager, uint64_t offset, uint64_t bytes) {
|
||||
std::scoped_lock lock(manager->lock);
|
||||
return manager->IsModified<DirtySource::Gpu>(offset, bytes);
|
||||
@@ -114,45 +98,31 @@ bool MemoryTracker::IsRegionGpuModified(uint64_t vaddr, uint64_t size) {
|
||||
void MemoryTracker::MarkRegionAsCpuModified(uint64_t vaddr, uint64_t size) {
|
||||
CheckNotInUploadCallback();
|
||||
std::lock_guard access(m_access_mutex);
|
||||
RequireMapped(vaddr, size);
|
||||
Iterate<true>(vaddr, size, [](RegionManager* manager, uint64_t offset, uint64_t bytes) {
|
||||
std::scoped_lock lock(manager->lock);
|
||||
const auto changed =
|
||||
manager->ChangeState<DirtySource::Cpu, true>(manager->GetCpuAddr() + offset, bytes);
|
||||
manager->ApplyProtection(changed, false);
|
||||
});
|
||||
}
|
||||
|
||||
void MemoryTracker::MarkRegionAsGpuModified(uint64_t vaddr, uint64_t size) {
|
||||
CheckNotInUploadCallback();
|
||||
std::lock_guard access(m_access_mutex);
|
||||
RequireMapped(vaddr, size);
|
||||
Iterate<true>(vaddr, size, [this](RegionManager* manager, uint64_t offset, uint64_t bytes) {
|
||||
Iterate<true>(vaddr, size, [](RegionManager* manager, uint64_t offset, uint64_t bytes) {
|
||||
std::scoped_lock lock(manager->lock);
|
||||
const auto changed =
|
||||
manager->ChangeState<DirtySource::Gpu, true>(manager->GetCpuAddr() + offset, bytes);
|
||||
manager->ApplyGpuProtection(changed, true, m_gpu_watch_mode);
|
||||
});
|
||||
}
|
||||
|
||||
void MemoryTracker::UnmarkRegionAsGpuModified(uint64_t vaddr, uint64_t size) {
|
||||
CheckNotInUploadCallback();
|
||||
std::lock_guard access(m_access_mutex);
|
||||
RequireMapped(vaddr, size);
|
||||
Iterate<true>(vaddr, size, [this](RegionManager* manager, uint64_t offset, uint64_t bytes) {
|
||||
Iterate<false>(vaddr, size, [](RegionManager* manager, uint64_t offset, uint64_t bytes) {
|
||||
std::scoped_lock lock(manager->lock);
|
||||
if (!manager->IsFullyModified<DirtySource::Gpu>(offset, bytes)) {
|
||||
EXIT("cannot clear partially GPU-dirty tracking range\n");
|
||||
}
|
||||
const auto changed =
|
||||
manager->ChangeState<DirtySource::Gpu, false>(manager->GetCpuAddr() + offset, bytes);
|
||||
manager->ApplyGpuProtection(changed, false, m_gpu_watch_mode);
|
||||
});
|
||||
}
|
||||
|
||||
void MemoryTracker::UntrackMemoryLocked(uint64_t vaddr, uint64_t size) {
|
||||
RequireMapped(vaddr, size);
|
||||
|
||||
std::vector<RegionManager*> managers;
|
||||
managers.reserve((vaddr % TRACKER_REGION_SIZE + size + TRACKER_REGION_SIZE - 1) /
|
||||
TRACKER_REGION_SIZE);
|
||||
@@ -171,10 +141,7 @@ void MemoryTracker::UntrackMemoryLocked(uint64_t vaddr, uint64_t size) {
|
||||
EXIT("cannot untrack GPU-dirty memory\n");
|
||||
}
|
||||
Iterate<false>(vaddr, size, [](RegionManager* manager, uint64_t offset, uint64_t bytes) {
|
||||
const auto changed =
|
||||
manager->ChangeState<DirtySource::Cpu, true>(manager->GetCpuAddr() + offset, bytes);
|
||||
manager->ApplyProtection(changed, false);
|
||||
manager->Untrack(manager->GetCpuAddr() + offset, bytes);
|
||||
});
|
||||
locks.clear();
|
||||
}
|
||||
@@ -185,109 +152,4 @@ void MemoryTracker::UntrackMemory(uint64_t vaddr, uint64_t size) {
|
||||
UntrackMemoryLocked(vaddr, size);
|
||||
}
|
||||
|
||||
void MemoryTracker::UnmapMemory(uint64_t vaddr, uint64_t size) {
|
||||
CheckNotInUploadCallback();
|
||||
std::unique_lock access(m_access_mutex, std::try_to_lock);
|
||||
if (!access.owns_lock()) {
|
||||
#if defined(KYTY_MEMORY_TRACKER_TESTS)
|
||||
if (const auto hook = g_unmap_contention_hook.load(std::memory_order_acquire);
|
||||
hook != nullptr) {
|
||||
hook();
|
||||
}
|
||||
#endif
|
||||
access.lock();
|
||||
}
|
||||
UntrackMemoryLocked(vaddr, size);
|
||||
m_page_manager.OnGpuUnmap(vaddr, size);
|
||||
}
|
||||
|
||||
bool MemoryTracker::InvalidateRegion(uint64_t vaddr, uint64_t size, PageFaultPhase phase) noexcept {
|
||||
switch (phase) {
|
||||
case PageFaultPhase::Release: return true;
|
||||
case PageFaultPhase::Invalidate: {
|
||||
const auto action = BeginCpuFault(vaddr, size);
|
||||
switch (action) {
|
||||
case CpuFaultAction::Untracked: return false;
|
||||
case CpuFaultAction::Continue: return true;
|
||||
case CpuFaultAction::Download:
|
||||
EXIT("generic region invalidation cannot download GPU-dirty memory\n");
|
||||
}
|
||||
}
|
||||
case PageFaultPhase::Complete:
|
||||
return CompleteCpuFault(vaddr, size, PageFaultAccess::Write, false);
|
||||
}
|
||||
EXIT("unsupported region invalidation phase\n");
|
||||
}
|
||||
|
||||
bool MemoryTracker::InvalidateVirtualGpuWrite(PageFaultAccess access, uint64_t vaddr, uint64_t size,
|
||||
PageFaultPhase phase) noexcept {
|
||||
switch (phase) {
|
||||
case PageFaultPhase::Release: return true;
|
||||
case PageFaultPhase::Invalidate: {
|
||||
const bool gpu_modified = Iterate<false>(
|
||||
vaddr, size, [](RegionManager* manager, uint64_t offset, uint64_t bytes) {
|
||||
std::scoped_lock lock(manager->lock);
|
||||
return manager->IsModified<DirtySource::Gpu>(offset, bytes);
|
||||
});
|
||||
if (!gpu_modified) {
|
||||
return false;
|
||||
}
|
||||
const auto action = BeginCpuFault(vaddr, size);
|
||||
if (access != PageFaultAccess::Write || action != CpuFaultAction::Download) {
|
||||
EXIT("virtual GPU write fault requires write access to GPU-dirty memory\n");
|
||||
}
|
||||
return true;
|
||||
}
|
||||
case PageFaultPhase::Complete: {
|
||||
if (access != PageFaultAccess::Write) {
|
||||
EXIT("virtual GPU write completion requires write access\n");
|
||||
}
|
||||
bool completed = false;
|
||||
Iterate<false>(
|
||||
vaddr, size, [&completed](RegionManager* manager, uint64_t offset, uint64_t bytes) {
|
||||
std::scoped_lock lock(manager->lock);
|
||||
if (completed) {
|
||||
EXIT("virtual GPU write fault spans multiple tracked regions\n");
|
||||
}
|
||||
completed =
|
||||
manager->CompleteVirtualGpuWrite(manager->GetCpuAddr() + offset, bytes);
|
||||
});
|
||||
return completed;
|
||||
}
|
||||
}
|
||||
EXIT("unsupported virtual GPU write invalidation phase\n");
|
||||
}
|
||||
|
||||
CpuFaultAction MemoryTracker::BeginCpuFault(uint64_t vaddr, uint64_t size,
|
||||
PageFaultAccess access) noexcept {
|
||||
CheckNotInUploadCallback();
|
||||
CpuFaultAction action = CpuFaultAction::Untracked;
|
||||
Iterate<false>(
|
||||
vaddr, size, [&action, access](RegionManager* manager, uint64_t offset, uint64_t bytes) {
|
||||
std::scoped_lock lock(manager->lock);
|
||||
if (action != CpuFaultAction::Untracked) {
|
||||
EXIT("CPU fault spans multiple tracked regions\n");
|
||||
}
|
||||
action = manager->BeginCpuFault(manager->GetCpuAddr() + offset, bytes, access);
|
||||
});
|
||||
return action;
|
||||
}
|
||||
|
||||
bool MemoryTracker::CompleteCpuFault(uint64_t vaddr, uint64_t size, PageFaultAccess access,
|
||||
bool downloaded) noexcept {
|
||||
CheckNotInUploadCallback();
|
||||
bool found = false;
|
||||
Iterate<false>(
|
||||
vaddr, size,
|
||||
[&found, access, downloaded](RegionManager* manager, uint64_t offset, uint64_t bytes) {
|
||||
std::scoped_lock lock(manager->lock);
|
||||
if (found) {
|
||||
EXIT("CPU fault completion spans multiple tracked regions\n");
|
||||
}
|
||||
found = manager->CompleteCpuFault(manager->GetCpuAddr() + offset, bytes, access,
|
||||
downloaded);
|
||||
});
|
||||
return found;
|
||||
}
|
||||
|
||||
} // namespace Libs::Graphics
|
||||
|
||||
@@ -18,8 +18,7 @@ namespace Libs::Graphics {
|
||||
|
||||
class MemoryTracker final {
|
||||
public:
|
||||
explicit MemoryTracker(PageManager& page_manager,
|
||||
PageWatchMode gpu_watch_mode = PageWatchMode::ReadWrite);
|
||||
explicit MemoryTracker(PageManager& page_manager);
|
||||
~MemoryTracker();
|
||||
|
||||
KYTY_CLASS_NO_COPY(MemoryTracker);
|
||||
@@ -30,14 +29,6 @@ public:
|
||||
void MarkRegionAsGpuModified(uint64_t vaddr, uint64_t size);
|
||||
void UnmarkRegionAsGpuModified(uint64_t vaddr, uint64_t size);
|
||||
void UntrackMemory(uint64_t vaddr, uint64_t size);
|
||||
void UnmapMemory(uint64_t vaddr, uint64_t size);
|
||||
[[nodiscard]] CpuFaultAction
|
||||
BeginCpuFault(uint64_t vaddr, uint64_t size,
|
||||
PageFaultAccess access = PageFaultAccess::Write) noexcept;
|
||||
[[nodiscard]] bool CompleteCpuFault(uint64_t vaddr, uint64_t size, PageFaultAccess access,
|
||||
bool downloaded) noexcept;
|
||||
[[nodiscard]] bool InvalidateRegion(uint64_t vaddr, uint64_t size,
|
||||
PageFaultPhase phase) noexcept;
|
||||
template <typename Flush>
|
||||
void InvalidateRegion(uint64_t vaddr, uint64_t size, Flush&& on_flush) {
|
||||
static_assert(std::is_invocable_v<Flush&>);
|
||||
@@ -64,9 +55,8 @@ public:
|
||||
}
|
||||
Iterate<false>(vaddr, size,
|
||||
[](RegionManager* manager, uint64_t offset, uint64_t bytes) {
|
||||
const auto changed = manager->ChangeState<DirtySource::Cpu, true>(
|
||||
manager->ChangeState<DirtySource::Cpu, true>(
|
||||
manager->GetCpuAddr() + offset, bytes);
|
||||
manager->ApplyProtection(changed, false);
|
||||
});
|
||||
return false;
|
||||
};
|
||||
@@ -79,12 +69,15 @@ public:
|
||||
EXIT("memory invalidation retained GPU-owned pages\n");
|
||||
}
|
||||
}
|
||||
[[nodiscard]] bool InvalidateVirtualGpuWrite(PageFaultAccess access, uint64_t vaddr,
|
||||
uint64_t size, PageFaultPhase phase) noexcept;
|
||||
#if KYTY_BUILD == KYTY_BUILD_DEBUG
|
||||
void ValidateGpuDirtyPages(const RangeSet& dirty, uint64_t vaddr, uint64_t size,
|
||||
const char* operation) const noexcept;
|
||||
void ValidateGpuDirtyOwnership(const RangeSet& dirty, uint64_t vaddr, uint64_t size,
|
||||
const char* operation);
|
||||
#else
|
||||
void ValidateGpuDirtyPages(const RangeSet&, uint64_t, uint64_t, const char*) const noexcept {}
|
||||
void ValidateGpuDirtyOwnership(const RangeSet&, uint64_t, uint64_t, const char*) {}
|
||||
#endif
|
||||
|
||||
template <bool clear, typename Preflight, typename Func>
|
||||
void ForEachDownloadRange(uint64_t vaddr, uint64_t size, Preflight&& preflight, Func&& func) {
|
||||
@@ -92,7 +85,6 @@ public:
|
||||
static_assert(std::is_nothrow_invocable_v<Func&, uint64_t, uint64_t>);
|
||||
CheckNotInUploadCallback();
|
||||
std::lock_guard access(m_access_mutex);
|
||||
RequireMapped(vaddr, size);
|
||||
std::vector<RegionManager*> managers;
|
||||
Iterate<false>(vaddr, size, [&](RegionManager* manager, uint64_t, uint64_t) {
|
||||
managers.push_back(manager);
|
||||
@@ -104,9 +96,6 @@ public:
|
||||
}
|
||||
Iterate<false>(vaddr, size, [&](RegionManager* manager, uint64_t offset, uint64_t bytes) {
|
||||
const auto address = manager->GetCpuAddr() + offset;
|
||||
if (manager->HasPendingFault(address, bytes)) {
|
||||
EXIT("GPU download synchronization raced a pending CPU fault\n");
|
||||
}
|
||||
manager->template ForEachModifiedRange<DirtySource::Gpu, false>(address, bytes,
|
||||
preflight);
|
||||
});
|
||||
@@ -118,10 +107,8 @@ public:
|
||||
Iterate<false>(vaddr, size,
|
||||
[&](RegionManager* manager, uint64_t offset, uint64_t bytes) {
|
||||
const auto address = manager->GetCpuAddr() + offset;
|
||||
const auto changed =
|
||||
manager->template ForEachModifiedRange<DirtySource::Gpu, true>(
|
||||
address, bytes, [](uint64_t, uint64_t) noexcept {});
|
||||
manager->ApplyGpuProtection(changed, false, m_gpu_watch_mode);
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -132,11 +119,6 @@ public:
|
||||
vaddr, size, [](uint64_t, uint64_t) noexcept {}, std::forward<Func>(func));
|
||||
}
|
||||
|
||||
#if defined(KYTY_MEMORY_TRACKER_TESTS)
|
||||
using UnmapContentionHook = void (*)() noexcept;
|
||||
static void SetUnmapContentionHook(UnmapContentionHook hook) noexcept;
|
||||
#endif
|
||||
|
||||
template <typename RangeFunc, typename UploadFunc>
|
||||
void ForEachUploadRange(uint64_t vaddr, uint64_t size, bool is_written, RangeFunc&& range_func,
|
||||
UploadFunc&& upload_func) {
|
||||
@@ -144,12 +126,10 @@ public:
|
||||
static_assert(std::is_nothrow_invocable_v<UploadFunc&>);
|
||||
CheckNotInUploadCallback();
|
||||
std::unique_lock access(m_access_mutex);
|
||||
RequireMapped(vaddr, size);
|
||||
Iterate<true>(vaddr, size, [](RegionManager*, uint64_t, uint64_t) {});
|
||||
const auto* previous_upload_owner = std::exchange(s_upload_owner, this);
|
||||
Iterate<false>(vaddr, size, [&](RegionManager* manager, uint64_t offset, uint64_t bytes) {
|
||||
manager->lock.lock();
|
||||
manager->Track(manager->GetCpuAddr() + offset, bytes);
|
||||
manager->ForEachModifiedRange<DirtySource::Cpu, true>(manager->GetCpuAddr() + offset,
|
||||
bytes, range_func);
|
||||
if (!is_written) {
|
||||
@@ -158,11 +138,10 @@ public:
|
||||
});
|
||||
upload_func();
|
||||
if (is_written) {
|
||||
Iterate<false>(
|
||||
vaddr, size, [this](RegionManager* manager, uint64_t offset, uint64_t bytes) {
|
||||
const auto changed = manager->template ChangeState<DirtySource::Gpu, true>(
|
||||
Iterate<false>(vaddr, size,
|
||||
[](RegionManager* manager, uint64_t offset, uint64_t bytes) {
|
||||
manager->template ChangeState<DirtySource::Gpu, true>(
|
||||
manager->GetCpuAddr() + offset, bytes);
|
||||
manager->ApplyGpuProtection(changed, true, m_gpu_watch_mode);
|
||||
manager->lock.unlock();
|
||||
});
|
||||
}
|
||||
@@ -211,14 +190,6 @@ private:
|
||||
|
||||
static void ValidateRange(uint64_t vaddr, uint64_t size);
|
||||
void UntrackMemoryLocked(uint64_t vaddr, uint64_t size);
|
||||
void RequireMapped(uint64_t vaddr, uint64_t size) const {
|
||||
ValidateRange(vaddr, size);
|
||||
if (!m_page_manager.IsMapped(vaddr, size)) {
|
||||
EXIT("memory tracker range [0x%llx, 0x%llx) is not mapped\n",
|
||||
static_cast<unsigned long long>(vaddr),
|
||||
static_cast<unsigned long long>(vaddr + size));
|
||||
}
|
||||
}
|
||||
RegionManager* GetOrCreateRegion(uint64_t index);
|
||||
|
||||
std::unique_ptr<std::atomic<RegionManager*>[]> m_regions;
|
||||
@@ -226,7 +197,6 @@ private:
|
||||
std::mutex m_region_mutex;
|
||||
std::mutex m_access_mutex;
|
||||
PageManager& m_page_manager;
|
||||
PageWatchMode m_gpu_watch_mode = PageWatchMode::ReadWrite;
|
||||
};
|
||||
|
||||
} // namespace Libs::Graphics
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -2,60 +2,32 @@
|
||||
#define EMULATOR_SRC_GRAPHICS_HOST_GPU_PAGEMANAGER_H_
|
||||
|
||||
#include "common/common.h"
|
||||
#include "graphics/host_gpu/rangeSet.h"
|
||||
#include "graphics/host_gpu/regionDefinitions.h"
|
||||
|
||||
#include <memory>
|
||||
#include <span>
|
||||
#include <vector>
|
||||
|
||||
namespace Libs::Graphics {
|
||||
|
||||
enum class PageFaultAccess { Read, Write, Execute, Unknown };
|
||||
enum class PageFaultPhase { Invalidate, Complete, Release };
|
||||
enum class PageWatchMode { Write, ReadWrite };
|
||||
enum class GpuAccess { Read, Write, ReadWrite };
|
||||
|
||||
using PageFaultHandler = bool (*)(void* context, PageFaultAccess access, uint64_t vaddr,
|
||||
uint64_t size, PageFaultPhase phase) noexcept;
|
||||
|
||||
class PageManager final {
|
||||
public:
|
||||
class BackingWrite final {
|
||||
public:
|
||||
BackingWrite(PageManager& manager, uint64_t vaddr, uint64_t size) noexcept;
|
||||
~BackingWrite();
|
||||
KYTY_CLASS_NO_COPY(BackingWrite);
|
||||
|
||||
private:
|
||||
PageManager& m_manager;
|
||||
uint64_t m_vaddr = 0;
|
||||
uint64_t m_size = 0;
|
||||
};
|
||||
|
||||
PageManager(PageFaultHandler fault_handler, void* fault_context);
|
||||
PageManager();
|
||||
// The owner must stop all PageManager callers before destruction.
|
||||
~PageManager();
|
||||
|
||||
KYTY_CLASS_NO_COPY(PageManager);
|
||||
|
||||
[[nodiscard]] uint64_t GetPageSize() const;
|
||||
[[nodiscard]] bool IsTracked(uint64_t vaddr) const noexcept;
|
||||
[[nodiscard]] bool IsMapped(uint64_t vaddr, uint64_t size) const noexcept;
|
||||
[[nodiscard]] bool HasGpuAccess(uint64_t vaddr, uint64_t size, GpuAccess access) const noexcept;
|
||||
|
||||
void UpdatePageWatchers(bool track, uint64_t vaddr, uint64_t size,
|
||||
PageWatchMode mode = PageWatchMode::Write);
|
||||
void OnGpuMap(uint64_t vaddr, uint64_t size, GpuAccess access = GpuAccess::ReadWrite);
|
||||
void OnGpuUnmap(uint64_t vaddr, uint64_t size, GpuAccess access = GpuAccess::ReadWrite);
|
||||
|
||||
[[nodiscard]] bool HandleFault(PageFaultAccess access, uint64_t fault_vaddr) noexcept;
|
||||
[[nodiscard]] std::vector<std::unique_ptr<BackingWrite>>
|
||||
ReserveBackingWrites(std::span<const RangeSet::Range> ranges);
|
||||
template <bool track>
|
||||
void UpdatePageWatchers(uint64_t vaddr, uint64_t size);
|
||||
template <bool track, bool is_read = false>
|
||||
void UpdatePageWatchersForRegion(uint64_t base_addr, RegionBits& mask);
|
||||
void OnGpuMap(uint64_t vaddr, uint64_t size);
|
||||
void OnGpuUnmap(uint64_t vaddr, uint64_t size);
|
||||
|
||||
private:
|
||||
void BeginBackingWrite(uint64_t vaddr, uint64_t size) noexcept;
|
||||
void EndBackingWrite(uint64_t vaddr, uint64_t size) noexcept;
|
||||
|
||||
struct Impl;
|
||||
std::unique_ptr<Impl> m_impl;
|
||||
};
|
||||
|
||||
@@ -1,10 +1,9 @@
|
||||
#ifndef EMULATOR_SRC_GRAPHICS_HOST_GPU_REGIONDEFINITIONS_H_
|
||||
#define EMULATOR_SRC_GRAPHICS_HOST_GPU_REGIONDEFINITIONS_H_
|
||||
|
||||
#include "common/bitArray.h"
|
||||
#include "common/common.h"
|
||||
|
||||
#include <bitset>
|
||||
|
||||
namespace Libs::Graphics {
|
||||
|
||||
constexpr uint64_t TRACKER_PAGE_SIZE = 4ull * 1024ull;
|
||||
@@ -13,7 +12,8 @@ constexpr uint64_t TRACKER_ADDRESS_SIZE = 1ull << 40u;
|
||||
constexpr size_t TRACKER_REGION_PAGES = TRACKER_REGION_SIZE / TRACKER_PAGE_SIZE;
|
||||
|
||||
enum class DirtySource { Cpu, Gpu };
|
||||
using RegionBits = std::bitset<TRACKER_REGION_PAGES>;
|
||||
using RegionBits = Common::BitArray<TRACKER_REGION_PAGES>;
|
||||
static_assert(sizeof(RegionBits) == TRACKER_REGION_PAGES / 8);
|
||||
|
||||
} // namespace Libs::Graphics
|
||||
|
||||
|
||||
@@ -25,8 +25,6 @@
|
||||
|
||||
namespace Libs::Graphics {
|
||||
|
||||
enum class CpuFaultAction { Untracked, Continue, Download };
|
||||
|
||||
class TrackingSpinLock final {
|
||||
public:
|
||||
void lock() noexcept {
|
||||
@@ -78,229 +76,93 @@ public:
|
||||
if (m_cpu_addr % TRACKER_REGION_SIZE != 0) {
|
||||
EXIT("invalid region tracking manager construction\n");
|
||||
}
|
||||
m_cpu_dirty.set();
|
||||
m_writable.set();
|
||||
m_cpu_dirty.Fill();
|
||||
m_writable.Fill();
|
||||
m_readable.Fill();
|
||||
}
|
||||
|
||||
KYTY_CLASS_NO_COPY(RegionManager);
|
||||
|
||||
[[nodiscard]] uint64_t GetCpuAddr() const { return m_cpu_addr; }
|
||||
void Track(uint64_t vaddr, uint64_t size) {
|
||||
const auto [start, end] = GetPageRange(vaddr, size);
|
||||
for (auto page = start; page < end; page++) {
|
||||
m_tracked.set(page);
|
||||
}
|
||||
}
|
||||
void Untrack(uint64_t vaddr, uint64_t size) {
|
||||
const auto [start, end] = GetPageRange(vaddr, size);
|
||||
for (auto page = start; page < end; page++) {
|
||||
m_tracked.reset(page);
|
||||
}
|
||||
}
|
||||
template <DirtySource source>
|
||||
[[nodiscard]] bool IsModified(uint64_t offset, uint64_t size) const {
|
||||
const auto [start, end] = GetPageRange(m_cpu_addr + offset, size);
|
||||
const auto& bits = GetBits<source>();
|
||||
for (auto page = start; page < end; page++) {
|
||||
if (bits.test(page)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
template <DirtySource source>
|
||||
[[nodiscard]] bool IsFullyModified(uint64_t offset, uint64_t size) const {
|
||||
const auto [start, end] = GetPageRange(m_cpu_addr + offset, size);
|
||||
const auto& bits = GetBits<source>();
|
||||
for (auto page = start; page < end; page++) {
|
||||
if (!bits.test(page)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
return RegionBits(bits, start, end).Any();
|
||||
}
|
||||
|
||||
template <DirtySource source, bool enable>
|
||||
RegionBits ChangeState(uint64_t vaddr, uint64_t size) {
|
||||
void ChangeState(uint64_t vaddr, uint64_t size) {
|
||||
const auto [start, end] = GetPageRange(vaddr, size);
|
||||
if constexpr (source == DirtySource::Cpu && enable) {
|
||||
for (auto page = start; page < end; page++) {
|
||||
if (m_gpu_dirty.test(page) || m_fault_pending.test(page)) {
|
||||
EXIT("CPU dirty state conflicts with GPU dirty or pending fault state\n");
|
||||
}
|
||||
if (RegionBits(m_gpu_dirty, start, end).Any()) {
|
||||
EXIT("CPU dirty state conflicts with GPU dirty state\n");
|
||||
}
|
||||
}
|
||||
if constexpr (source == DirtySource::Gpu && enable) {
|
||||
for (auto page = start; page < end; page++) {
|
||||
if (m_cpu_dirty.test(page) || m_fault_pending.test(page)) {
|
||||
EXIT("GPU dirty state conflicts with CPU dirty or pending fault state\n");
|
||||
}
|
||||
if (RegionBits(m_cpu_dirty, start, end).Any()) {
|
||||
EXIT("GPU dirty state conflicts with CPU dirty state\n");
|
||||
}
|
||||
}
|
||||
auto& bits = GetBits<source>();
|
||||
auto changed = bits;
|
||||
for (auto page = start; page < end; page++) {
|
||||
bits.set(page, enable);
|
||||
if constexpr (enable) {
|
||||
bits.SetRange(start, end);
|
||||
} else {
|
||||
bits.UnsetRange(start, end);
|
||||
}
|
||||
changed ^= bits;
|
||||
if constexpr (source == DirtySource::Cpu) {
|
||||
changed = m_cpu_dirty ^ m_writable;
|
||||
m_writable = m_cpu_dirty;
|
||||
UpdateCpuProtection<!enable>();
|
||||
} else {
|
||||
UpdateGpuProtection<enable>();
|
||||
}
|
||||
return changed;
|
||||
}
|
||||
|
||||
[[nodiscard]] CpuFaultAction BeginCpuFault(uint64_t vaddr, uint64_t size,
|
||||
PageFaultAccess access = PageFaultAccess::Write) {
|
||||
if (access != PageFaultAccess::Read && access != PageFaultAccess::Write) {
|
||||
EXIT("unsupported CPU fault access while beginning ownership transfer\n");
|
||||
}
|
||||
const auto [start, end] = GetPageRange(vaddr, size);
|
||||
const bool tracked = m_tracked.test(start);
|
||||
for (auto page = start; page < end; page++) {
|
||||
if (m_tracked.test(page) != tracked) {
|
||||
EXIT("CPU fault spans mixed tracked and untracked pages\n");
|
||||
}
|
||||
if (m_fault_pending.test(page)) {
|
||||
return CpuFaultAction::Untracked;
|
||||
}
|
||||
if (m_cpu_dirty.test(page) != m_writable.test(page) ||
|
||||
(m_gpu_dirty.test(page) && (m_cpu_dirty.test(page) || m_writable.test(page)))) {
|
||||
EXIT("inconsistent CPU fault page state\n");
|
||||
}
|
||||
}
|
||||
if (!tracked) {
|
||||
return CpuFaultAction::Untracked;
|
||||
}
|
||||
bool gpu_dirty = m_gpu_dirty.test(start);
|
||||
bool writable = m_writable.test(start);
|
||||
for (auto page = start + 1; page < end; page++) {
|
||||
if (m_gpu_dirty.test(page) != gpu_dirty || m_writable.test(page) != writable) {
|
||||
EXIT("CPU fault spans pages with incompatible dirty or writable state\n");
|
||||
}
|
||||
}
|
||||
for (auto page = start; page < end; page++) {
|
||||
if (!gpu_dirty && access == PageFaultAccess::Write) {
|
||||
m_cpu_dirty.set(page);
|
||||
m_writable.set(page);
|
||||
}
|
||||
m_fault_pending.set(page);
|
||||
}
|
||||
return gpu_dirty ? CpuFaultAction::Download : CpuFaultAction::Continue;
|
||||
}
|
||||
|
||||
[[nodiscard]] bool CompleteCpuFault(uint64_t vaddr, uint64_t size, PageFaultAccess access,
|
||||
bool downloaded) {
|
||||
const auto [start, end] = GetPageRange(vaddr, size);
|
||||
for (auto page = start; page < end; page++) {
|
||||
if (!m_fault_pending.test(page)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
for (auto page = start; page < end; page++) {
|
||||
const bool gpu_dirty = m_gpu_dirty.test(page);
|
||||
if (gpu_dirty != downloaded) {
|
||||
EXIT("CPU fault download result disagrees with GPU dirty state\n");
|
||||
}
|
||||
if (gpu_dirty) {
|
||||
m_gpu_dirty.reset(page);
|
||||
switch (access) {
|
||||
case PageFaultAccess::Read: break;
|
||||
case PageFaultAccess::Write:
|
||||
m_cpu_dirty.set(page);
|
||||
m_writable.set(page);
|
||||
break;
|
||||
default: EXIT("unsupported CPU fault access after GPU download\n");
|
||||
}
|
||||
}
|
||||
m_fault_pending.reset(page);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
[[nodiscard]] bool HasPendingFault(uint64_t vaddr, uint64_t size) const {
|
||||
const auto [start, end] = GetPageRange(vaddr, size);
|
||||
for (auto page = start; page < end; page++) {
|
||||
if (m_fault_pending.test(page)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
[[nodiscard]] bool CompleteVirtualGpuWrite(uint64_t vaddr, uint64_t size) {
|
||||
const auto [start, end] = GetPageRange(vaddr, size);
|
||||
for (auto page = start; page < end; page++) {
|
||||
if (!m_fault_pending.test(page)) {
|
||||
return false;
|
||||
}
|
||||
if (!m_gpu_dirty.test(page)) {
|
||||
EXIT("virtual GPU write completion found a non-GPU-dirty page\n");
|
||||
}
|
||||
}
|
||||
for (auto page = start; page < end; page++) {
|
||||
m_gpu_dirty.reset(page);
|
||||
m_cpu_dirty.set(page);
|
||||
m_writable.set(page);
|
||||
m_fault_pending.reset(page);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
template <DirtySource source, bool clear, typename Func>
|
||||
RegionBits ForEachModifiedRange(uint64_t vaddr, uint64_t size, Func&& func) {
|
||||
void ForEachModifiedRange(uint64_t vaddr, uint64_t size, Func&& func) {
|
||||
const auto [start, end] = GetPageRange(vaddr, size);
|
||||
auto mask = GetBits<source>();
|
||||
if constexpr (source == DirtySource::Cpu) {
|
||||
mask &= ~m_fault_pending;
|
||||
}
|
||||
for (auto page = 0u; page < start; page++) {
|
||||
mask.reset(page);
|
||||
}
|
||||
for (auto page = end; page < TRACKER_REGION_PAGES; page++) {
|
||||
mask.reset(page);
|
||||
}
|
||||
RegionBits mask(GetBits<source>(), start, end);
|
||||
if constexpr (clear) {
|
||||
auto& bits = GetBits<source>();
|
||||
for (auto page = start; page < end; page++) {
|
||||
if (mask.test(page)) {
|
||||
bits.reset(page);
|
||||
}
|
||||
}
|
||||
GetBits<source>().UnsetRange(start, end);
|
||||
}
|
||||
if constexpr (source == DirtySource::Cpu && clear) {
|
||||
auto changed = m_cpu_dirty ^ m_writable;
|
||||
m_writable = m_cpu_dirty;
|
||||
ApplyProtection(changed, true);
|
||||
UpdateCpuProtection<true>();
|
||||
ForEachRange(mask, std::forward<Func>(func));
|
||||
return changed;
|
||||
return;
|
||||
}
|
||||
if constexpr (source == DirtySource::Gpu && clear) {
|
||||
UpdateGpuProtection<false>();
|
||||
}
|
||||
ForEachRange(mask, std::forward<Func>(func));
|
||||
if constexpr (clear) {
|
||||
return mask;
|
||||
}
|
||||
return {};
|
||||
}
|
||||
|
||||
void ApplyProtection(const RegionBits& changed, bool track) {
|
||||
ForEachRange(changed, [this, track](uint64_t vaddr, uint64_t size) {
|
||||
m_page_manager.UpdatePageWatchers(track, vaddr, size);
|
||||
});
|
||||
}
|
||||
|
||||
void ApplyGpuProtection(const RegionBits& changed, bool track, PageWatchMode mode) {
|
||||
if (mode != PageWatchMode::Write && mode != PageWatchMode::ReadWrite) {
|
||||
EXIT("unsupported GPU page-watch mode\n");
|
||||
}
|
||||
ForEachRange(changed, [this, track, mode](uint64_t vaddr, uint64_t size) {
|
||||
m_page_manager.UpdatePageWatchers(track, vaddr, size, mode);
|
||||
});
|
||||
}
|
||||
|
||||
TrackingSpinLock lock;
|
||||
|
||||
private:
|
||||
template <bool track>
|
||||
void UpdateCpuProtection() {
|
||||
auto mask = m_cpu_dirty ^ m_writable;
|
||||
m_writable = m_cpu_dirty;
|
||||
if (mask.None()) {
|
||||
return;
|
||||
}
|
||||
m_page_manager.UpdatePageWatchersForRegion<track>(m_cpu_addr, mask);
|
||||
}
|
||||
|
||||
template <bool track>
|
||||
void UpdateGpuProtection() {
|
||||
auto readable = ~m_gpu_dirty;
|
||||
auto mask = readable ^ m_readable;
|
||||
m_readable = readable;
|
||||
if (mask.None()) {
|
||||
return;
|
||||
}
|
||||
if constexpr (track) {
|
||||
m_page_manager.UpdatePageWatchersForRegion<true, true>(m_cpu_addr, mask);
|
||||
} else {
|
||||
m_page_manager.UpdatePageWatchersForRegion<false, true>(m_cpu_addr, mask);
|
||||
}
|
||||
}
|
||||
|
||||
template <DirtySource source>
|
||||
RegionBits& GetBits() {
|
||||
if constexpr (source == DirtySource::Cpu) {
|
||||
@@ -331,18 +193,8 @@ private:
|
||||
|
||||
template <typename Func>
|
||||
void ForEachRange(const RegionBits& bits, Func&& func) const {
|
||||
size_t page = 0;
|
||||
while (page < TRACKER_REGION_PAGES) {
|
||||
while (page < TRACKER_REGION_PAGES && !bits.test(page)) {
|
||||
page++;
|
||||
}
|
||||
const auto start = page;
|
||||
while (page < TRACKER_REGION_PAGES && bits.test(page)) {
|
||||
page++;
|
||||
}
|
||||
if (start != page) {
|
||||
func(m_cpu_addr + start * TRACKER_PAGE_SIZE, (page - start) * TRACKER_PAGE_SIZE);
|
||||
}
|
||||
for (const auto [start, end]: bits) {
|
||||
func(m_cpu_addr + start * TRACKER_PAGE_SIZE, (end - start) * TRACKER_PAGE_SIZE);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -351,8 +203,7 @@ private:
|
||||
RegionBits m_cpu_dirty;
|
||||
RegionBits m_gpu_dirty;
|
||||
RegionBits m_writable;
|
||||
RegionBits m_fault_pending;
|
||||
RegionBits m_tracked;
|
||||
RegionBits m_readable;
|
||||
};
|
||||
|
||||
} // namespace Libs::Graphics
|
||||
|
||||
+48
-428
@@ -123,30 +123,6 @@ struct BufferCache::RetiredBuffer {
|
||||
std::shared_ptr<Buffer> owner;
|
||||
};
|
||||
|
||||
struct BufferCache::FaultReadback {
|
||||
PageFaultAccess access = PageFaultAccess::Unknown;
|
||||
uint64_t vaddr = 0;
|
||||
uint64_t size = 0;
|
||||
std::vector<DownloadRange> ranges;
|
||||
bool installed = false;
|
||||
|
||||
[[nodiscard]] bool Active() const noexcept { return !ranges.empty(); }
|
||||
|
||||
void Reset() {
|
||||
access = PageFaultAccess::Unknown;
|
||||
vaddr = 0;
|
||||
size = 0;
|
||||
installed = false;
|
||||
ranges.clear();
|
||||
}
|
||||
};
|
||||
|
||||
struct BufferCache::PendingBackingPublication {
|
||||
uint64_t address = 0;
|
||||
uint64_t size = 0;
|
||||
uint64_t tick = 0;
|
||||
};
|
||||
|
||||
std::pair<uint64_t, uint64_t> BufferCache::DownloadEnvelope(const DownloadCopy& copy) {
|
||||
if (copy.owner == nullptr || copy.size == 0 || copy.source_offset > copy.owner->Size() ||
|
||||
copy.size > copy.owner->Size() - copy.source_offset) {
|
||||
@@ -219,32 +195,24 @@ void BufferCache::QueueGarbageDownload(std::span<const DownloadCopy> copies, Ret
|
||||
return;
|
||||
}
|
||||
auto downloads = RecordDownloads(copies);
|
||||
const auto tick = m_scheduler.CurrentTick();
|
||||
BeginBackingPublication(retire.address, retire.size, tick);
|
||||
m_scheduler.DeferOperation([this, downloads = std::move(downloads), retire = std::move(retire),
|
||||
tick]() mutable {
|
||||
m_scheduler.DeferOperation(
|
||||
[this, downloads = std::move(downloads), retire = std::move(retire)]() mutable {
|
||||
PublishDownloads(downloads);
|
||||
{
|
||||
FaultSafeCacheLock lock(this, m_mutex);
|
||||
if (m_memory_tracker.IsRegionGpuModified(retire.address, retire.size)) {
|
||||
m_memory_tracker.ForEachDownloadRange<true>(
|
||||
retire.address, retire.size,
|
||||
[&](uint64_t address, uint64_t size) noexcept {
|
||||
m_memory_tracker.ValidateGpuDirtyPages(m_gpu_modified_ranges, address, size,
|
||||
"asynchronous garbage retirement");
|
||||
},
|
||||
[](uint64_t, uint64_t) noexcept {});
|
||||
}
|
||||
for (const auto& range: downloads) {
|
||||
m_gpu_modified_ranges.Subtract(range.address, range.size);
|
||||
}
|
||||
// ForEachDownloadRange reports full tracker pages, and every exact GPU-owned
|
||||
// interval on those pages was downloaded and removed. Clearing the original
|
||||
// query therefore cannot orphan a dirty sibling on an edge page.
|
||||
m_memory_tracker.UnmarkRegionAsGpuModified(retire.address, retire.size);
|
||||
if (m_memory_tracker.IsRegionGpuModified(retire.address, retire.size) ||
|
||||
!m_gpu_modified_ranges.Intersections(retire.address, retire.size).empty()) {
|
||||
EXIT("BufferCache: asynchronous garbage collection retained GPU ownership\n");
|
||||
}
|
||||
m_memory_tracker.UntrackMemory(retire.address, retire.size);
|
||||
}
|
||||
CompleteBackingPublication(retire.address, retire.size, tick);
|
||||
});
|
||||
}
|
||||
|
||||
@@ -253,13 +221,12 @@ BufferCache::BufferCache(GraphicContext& graphics, CommandScheduler& scheduler,
|
||||
ResourceMutex& resource_mutex)
|
||||
: m_graphics(graphics), m_scheduler(scheduler),
|
||||
m_gds_buffer(graphics, scheduler, MemoryUsage::Stream, 0, AllFlags, GdsBufferSize),
|
||||
m_fault_readback(std::make_unique<FaultReadback>()), m_memory_tracker(page_manager),
|
||||
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_device_buffer(graphics, scheduler, MemoryUsage::DeviceLocal, 128 * MiB),
|
||||
m_page_manager(page_manager), m_texture_cache(texture_cache),
|
||||
m_resource_mutex(resource_mutex) {
|
||||
m_texture_cache(texture_cache), m_resource_mutex(resource_mutex) {
|
||||
std::memset(m_gds_buffer.Mapped().data(), 0, static_cast<size_t>(m_gds_buffer.Size()));
|
||||
m_gds_buffer.Flush(0, m_gds_buffer.Size());
|
||||
if (!m_graphics.CanReportMemoryUsage()) {
|
||||
@@ -277,15 +244,9 @@ BufferCache::BufferCache(GraphicContext& graphics, CommandScheduler& scheduler,
|
||||
}
|
||||
|
||||
BufferCache::~BufferCache() {
|
||||
if (m_fault_readback->Active()) {
|
||||
EXIT("BufferCache: destroyed with an active fault readback\n");
|
||||
}
|
||||
if (!m_gpu_modified_ranges.Empty()) {
|
||||
EXIT("BufferCache: destroyed with pending GPU-modified ranges\n");
|
||||
}
|
||||
if (!m_pending_backing_publications.empty()) {
|
||||
EXIT("BufferCache: destroyed with pending backing publications\n");
|
||||
}
|
||||
for (const auto& [vaddr, cached]: m_buffers) {
|
||||
(void)vaddr;
|
||||
if (m_memory_tracker.IsRegionGpuModified(cached->vaddr, cached->size)) {
|
||||
@@ -295,68 +256,6 @@ BufferCache::~BufferCache() {
|
||||
m_buffers.clear();
|
||||
}
|
||||
|
||||
bool BufferCache::SynchronizeBacking(uint64_t vaddr, uint64_t size) {
|
||||
bool waited = false;
|
||||
for (;;) {
|
||||
uint64_t tick = 0;
|
||||
const auto page_begin = vaddr & ~(TRACKER_PAGE_SIZE - 1);
|
||||
const auto page_end = (vaddr + size + TRACKER_PAGE_SIZE - 1) & ~(TRACKER_PAGE_SIZE - 1);
|
||||
CacheRange affected {.address = page_begin, .size = page_end - page_begin};
|
||||
{
|
||||
FaultSafeCacheLock lock(this, m_mutex);
|
||||
bool changed = true;
|
||||
while (changed) {
|
||||
changed = false;
|
||||
for (const auto& [address, cached]: m_buffers) {
|
||||
const CacheRange previous = affected;
|
||||
if (ResolveOverlap(affected, {address, cached->size}) &&
|
||||
(previous.address != affected.address || previous.size != affected.size)) {
|
||||
changed = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
{
|
||||
std::lock_guard lock(m_publication_mutex);
|
||||
for (const auto& publication: m_pending_backing_publications) {
|
||||
if (publication.address < affected.address + affected.size &&
|
||||
affected.address < publication.address + publication.size) {
|
||||
tick = std::max(tick, publication.tick);
|
||||
}
|
||||
}
|
||||
}
|
||||
if (tick == 0) {
|
||||
return waited;
|
||||
}
|
||||
waited = true;
|
||||
m_scheduler.Wait(tick);
|
||||
m_scheduler.WaitPriorityOperations(tick);
|
||||
}
|
||||
}
|
||||
|
||||
void BufferCache::RefreshInvalidatedRanges(CommandBuffer& command, CachedBuffer& cached,
|
||||
uint64_t vaddr, uint64_t size, bool upload) {
|
||||
const auto invalidated = m_image_invalidated_ranges.Intersections(vaddr, size);
|
||||
if (upload) {
|
||||
std::array<uint8_t, 64 * 1024> bytes;
|
||||
for (const auto& range: invalidated) {
|
||||
for (uint64_t copied = 0; copied < range.size;) {
|
||||
const auto chunk = std::min<uint64_t>(range.size - copied, bytes.size());
|
||||
if (!Libs::LibKernel::Memory::TryReadBacking(range.address + copied, bytes.data(),
|
||||
chunk)) {
|
||||
EXIT("BufferCache: failed to refresh an invalidated image alias\n");
|
||||
}
|
||||
Upload(command, *cached.buffer, cached.buffer->Offset(range.address + copied),
|
||||
bytes.data(), chunk);
|
||||
copied += chunk;
|
||||
}
|
||||
}
|
||||
}
|
||||
if (!invalidated.empty()) {
|
||||
m_image_invalidated_ranges.Subtract(vaddr, size);
|
||||
}
|
||||
}
|
||||
|
||||
StreamBuffer& BufferCache::GetUtilityBuffer(MemoryUsage usage) noexcept {
|
||||
switch (usage) {
|
||||
case MemoryUsage::Upload: return m_staging_buffer;
|
||||
@@ -385,7 +284,6 @@ void BufferCache::InvalidateMemory(uint64_t vaddr, uint64_t size) {
|
||||
size > TRACKER_ADDRESS_SIZE - vaddr) {
|
||||
EXIT("BufferCache: invalid memory-invalidation range\n");
|
||||
}
|
||||
(void)SynchronizeBacking(vaddr, size);
|
||||
if (!HasPageOverlap(vaddr, size)) {
|
||||
return;
|
||||
}
|
||||
@@ -394,7 +292,6 @@ void BufferCache::InvalidateMemory(uint64_t vaddr, uint64_t size) {
|
||||
}
|
||||
|
||||
void BufferCache::ReadMemory(uint64_t vaddr, uint64_t size) {
|
||||
(void)SynchronizeBacking(vaddr, size);
|
||||
std::vector<DownloadCopy> copies;
|
||||
{
|
||||
FaultSafeCacheLock lock(this, m_mutex);
|
||||
@@ -434,116 +331,20 @@ void BufferCache::ReadMemory(uint64_t vaddr, uint64_t size) {
|
||||
PublishDownloads(downloads);
|
||||
{
|
||||
FaultSafeCacheLock lock(this, m_mutex);
|
||||
m_memory_tracker.ForEachDownloadRange<true>(
|
||||
vaddr, size,
|
||||
[&](uint64_t address, uint64_t bytes) noexcept {
|
||||
m_memory_tracker.ValidateGpuDirtyPages(m_gpu_modified_ranges, address, bytes,
|
||||
"memory invalidation completion");
|
||||
},
|
||||
[](uint64_t, uint64_t) noexcept {});
|
||||
for (const auto& range: downloads) {
|
||||
m_gpu_modified_ranges.Subtract(range.address, range.size);
|
||||
}
|
||||
// The enumeration above covered whole dirty pages and every exact interval on them.
|
||||
m_memory_tracker.UnmarkRegionAsGpuModified(vaddr, size);
|
||||
}
|
||||
}
|
||||
|
||||
bool BufferCache::InvalidateMemory(PageFaultAccess access, uint64_t vaddr, uint64_t size,
|
||||
PageFaultPhase phase) noexcept {
|
||||
const auto page = vaddr & ~(TRACKER_PAGE_SIZE - 1);
|
||||
if (size == 0 || size > page + TRACKER_PAGE_SIZE - vaddr) {
|
||||
EXIT("BufferCache: invalid page-fault range\n");
|
||||
}
|
||||
|
||||
if (phase == PageFaultPhase::Complete) {
|
||||
FaultSafeCacheLock lock(this, m_mutex);
|
||||
auto& fault = *m_fault_readback;
|
||||
if (!fault.Active()) {
|
||||
return m_memory_tracker.CompleteCpuFault(vaddr, size, access, false);
|
||||
}
|
||||
if (fault.access != access || fault.vaddr != vaddr || fault.size != size ||
|
||||
fault.installed) {
|
||||
EXIT("BufferCache: mismatched fault readback completion\n");
|
||||
}
|
||||
PublishDownloads(fault.ranges);
|
||||
if (!m_memory_tracker.CompleteCpuFault(vaddr, size, access, true)) {
|
||||
EXIT("BufferCache: failed to complete downloaded CPU fault\n");
|
||||
}
|
||||
fault.installed = true;
|
||||
return true;
|
||||
}
|
||||
|
||||
if (phase == PageFaultPhase::Release) {
|
||||
FaultSafeCacheLock lock(this, m_mutex);
|
||||
auto& fault = *m_fault_readback;
|
||||
if (fault.Active()) {
|
||||
if (fault.access != access || fault.vaddr != vaddr || fault.size != size ||
|
||||
!fault.installed) {
|
||||
EXIT("BufferCache: mismatched fault readback release\n");
|
||||
}
|
||||
for (const auto& range: fault.ranges) {
|
||||
m_gpu_modified_ranges.Subtract(range.address, range.size);
|
||||
}
|
||||
fault.Reset();
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
if (phase != PageFaultPhase::Invalidate) {
|
||||
EXIT("BufferCache: unsupported page-fault phase\n");
|
||||
}
|
||||
|
||||
const auto action = m_memory_tracker.BeginCpuFault(vaddr, size, access);
|
||||
if (action != CpuFaultAction::Download) {
|
||||
return action == CpuFaultAction::Continue;
|
||||
}
|
||||
|
||||
auto& fault = *m_fault_readback;
|
||||
std::vector<DownloadCopy> copies;
|
||||
{
|
||||
FaultSafeCacheLock lock(this, m_mutex);
|
||||
if (fault.Active()) {
|
||||
EXIT("BufferCache: nested fault readback\n");
|
||||
}
|
||||
fault.access = access;
|
||||
fault.vaddr = vaddr;
|
||||
fault.size = size;
|
||||
|
||||
m_gpu_modified_ranges.ForEachIntersection(
|
||||
page, TRACKER_PAGE_SIZE, [&](RangeSet::Range range) {
|
||||
auto owner = m_buffers.upper_bound(range.address);
|
||||
if (owner == m_buffers.begin()) {
|
||||
EXIT("BufferCache: fault readback has no buffer owner\n");
|
||||
}
|
||||
--owner;
|
||||
auto& cached = *owner->second;
|
||||
if (!cached.buffer->IsInBounds(range.address, range.size)) {
|
||||
EXIT("BufferCache: fault readback is outside its buffer owner\n");
|
||||
}
|
||||
copies.push_back({cached.buffer, cached.buffer->Offset(range.address),
|
||||
range.address, range.size});
|
||||
});
|
||||
if (copies.empty()) {
|
||||
EXIT("BufferCache: GPU-dirty fault page has no dirty byte ranges\n");
|
||||
}
|
||||
}
|
||||
fault.ranges = RecordDownloads(copies);
|
||||
if (!fault.Active()) {
|
||||
EXIT("BufferCache: GPU-dirty fault page has no dirty byte ranges\n");
|
||||
}
|
||||
m_scheduler.FinishCurrent();
|
||||
return true;
|
||||
}
|
||||
|
||||
void BufferCache::UnmapMemory(uint64_t vaddr, uint64_t size) {
|
||||
if (vaddr == 0 || size == 0 || size > UINT64_MAX - vaddr) {
|
||||
EXIT("BufferCache: invalid unmap range\n");
|
||||
}
|
||||
(void)SynchronizeBacking(vaddr, size);
|
||||
|
||||
std::vector<DownloadCopy> copies;
|
||||
std::vector<RangeSet::Range> dirty_ranges;
|
||||
std::vector<std::pair<uint64_t, uint64_t>> modified_buffers;
|
||||
std::vector<std::unique_ptr<PageManager::BackingWrite>> backing_writes;
|
||||
std::vector<std::pair<uint64_t, uint64_t>> retired_buffers;
|
||||
{
|
||||
FaultSafeCacheLock lock(this, m_mutex);
|
||||
@@ -561,12 +362,8 @@ void BufferCache::UnmapMemory(uint64_t vaddr, uint64_t size) {
|
||||
if (dirty.empty()) {
|
||||
EXIT("BufferCache: GPU-modified buffer has no dirty ranges\n");
|
||||
}
|
||||
dirty_ranges.insert(dirty_ranges.end(), dirty.begin(), dirty.end());
|
||||
modified_buffers.emplace_back(begin, cached->size);
|
||||
}
|
||||
if (!dirty_ranges.empty()) {
|
||||
backing_writes = m_page_manager.ReserveBackingWrites(dirty_ranges);
|
||||
}
|
||||
for (const auto& [begin, bytes]: modified_buffers) {
|
||||
auto owner = m_buffers.find(begin);
|
||||
if (owner == m_buffers.end() || owner->second->size != bytes) {
|
||||
@@ -596,24 +393,11 @@ void BufferCache::UnmapMemory(uint64_t vaddr, uint64_t size) {
|
||||
// command stream before removing such backing.
|
||||
m_scheduler.FinishCurrent();
|
||||
}
|
||||
backing_writes.clear();
|
||||
|
||||
{
|
||||
FaultSafeCacheLock lock(this, m_mutex);
|
||||
for (const auto& [begin, bytes]: modified_buffers) {
|
||||
if (!m_memory_tracker.IsRegionGpuModified(begin, bytes)) {
|
||||
continue;
|
||||
}
|
||||
m_memory_tracker.ForEachDownloadRange<true>(
|
||||
begin, bytes,
|
||||
[&](uint64_t address, uint64_t download_size) noexcept {
|
||||
m_memory_tracker.ValidateGpuDirtyPages(m_gpu_modified_ranges, address,
|
||||
download_size, "unmap retirement");
|
||||
},
|
||||
[](uint64_t, uint64_t) noexcept {});
|
||||
}
|
||||
for (const auto& [begin, bytes]: modified_buffers) {
|
||||
m_gpu_modified_ranges.Subtract(begin, bytes);
|
||||
m_memory_tracker.UnmarkRegionAsGpuModified(begin, bytes);
|
||||
}
|
||||
for (const auto& [begin, bytes]: retired_buffers) {
|
||||
m_memory_tracker.MarkRegionAsCpuModified(begin, bytes);
|
||||
@@ -621,7 +405,6 @@ void BufferCache::UnmapMemory(uint64_t vaddr, uint64_t size) {
|
||||
if (!m_gpu_modified_ranges.Intersections(vaddr, size).empty()) {
|
||||
EXIT("BufferCache: unmap retained dirty byte ranges\n");
|
||||
}
|
||||
m_image_invalidated_ranges.Subtract(vaddr, size);
|
||||
m_memory_tracker.UntrackMemory(vaddr, size);
|
||||
for (auto it = m_buffers.begin(); it != m_buffers.end();) {
|
||||
if (vaddr < it->first + it->second->size && it->first < vaddr + size) {
|
||||
@@ -714,22 +497,30 @@ BufferBinding BufferCache::ObtainBuffer(CommandBuffer& command, uint64_t vaddr,
|
||||
if (command.IsInvalid() || command.IsExecute()) {
|
||||
EXIT("BufferCache: buffer request requires a recording command buffer\n");
|
||||
}
|
||||
ValidateGpuAccess(vaddr, size, is_read, is_written);
|
||||
std::lock_guard transaction(m_resource_mutex);
|
||||
(void)SynchronizeBacking(vaddr, size);
|
||||
|
||||
if (is_read && !is_written && size <= CACHING_PAGE_SIZE &&
|
||||
!m_memory_tracker.IsRegionGpuModified(vaddr, size) &&
|
||||
m_memory_tracker.IsRegionCpuModified(vaddr, size)) {
|
||||
std::vector<uint8_t> data(size);
|
||||
if (Libs::LibKernel::Memory::TryReadBacking(vaddr, data.data(), size)) {
|
||||
return UploadTransient(data.data(), size, 16);
|
||||
const auto alignment = std::max<uint64_t>(
|
||||
m_graphics.physical_device_properties.limits.minUniformBufferOffsetAlignment, 1);
|
||||
if (auto [mapped, offset] = m_stream_buffer.Map(size, alignment, false);
|
||||
mapped != nullptr) {
|
||||
if (Libs::LibKernel::Memory::TryReadBacking(vaddr, mapped, size)) {
|
||||
m_stream_buffer.Commit();
|
||||
return {{}, m_stream_buffer.Handle(), offset};
|
||||
}
|
||||
} else {
|
||||
auto owner = std::make_shared<Buffer>(m_graphics, m_scheduler, MemoryUsage::Upload, 0,
|
||||
AllFlags, size);
|
||||
if (Libs::LibKernel::Memory::TryReadBacking(vaddr, owner->Mapped().data(), size)) {
|
||||
owner->Flush(0, size);
|
||||
return {owner, owner->Handle(), 0};
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (is_formatted && is_read && !is_written) {
|
||||
(void)m_texture_cache.SynchronizeImageToBuffer(vaddr, size);
|
||||
} else if (is_formatted && is_written) {
|
||||
if (is_formatted && is_written) {
|
||||
(void)m_texture_cache.InvalidateMemoryFromGPU(vaddr, size, true);
|
||||
}
|
||||
|
||||
@@ -745,10 +536,12 @@ BufferBinding BufferCache::ObtainBuffer(CommandBuffer& command, uint64_t vaddr,
|
||||
reinterpret_cast<const void*>(address), bytes);
|
||||
}
|
||||
});
|
||||
RefreshInvalidatedRanges(command, cached, vaddr, size, is_read);
|
||||
if (is_written) {
|
||||
m_gpu_modified_ranges.Add(vaddr, size);
|
||||
}
|
||||
if (is_formatted && is_read && !is_written) {
|
||||
(void)SynchronizeBufferFromImage(*cached.buffer, vaddr, size);
|
||||
}
|
||||
return {cached.buffer, cached.buffer->Handle(), cached.buffer->Offset(vaddr)};
|
||||
}
|
||||
|
||||
@@ -773,7 +566,6 @@ ImageBufferSource BufferCache::ObtainBufferForImage(uint64_t vaddr, uint64_t siz
|
||||
size > TRACKER_ADDRESS_SIZE - vaddr) {
|
||||
EXIT("BufferCache: invalid image source\n");
|
||||
}
|
||||
(void)SynchronizeBacking(vaddr, size);
|
||||
auto find_owner = [&]() {
|
||||
auto owner = m_buffers.upper_bound(vaddr);
|
||||
if (owner == m_buffers.begin()) {
|
||||
@@ -788,13 +580,12 @@ ImageBufferSource BufferCache::ObtainBufferForImage(uint64_t vaddr, uint64_t siz
|
||||
const bool cpu_modified = m_memory_tracker.IsRegionCpuModified(vaddr, size);
|
||||
const bool gpu_modified = m_memory_tracker.IsRegionGpuModified(vaddr, size);
|
||||
const auto dirty = m_gpu_modified_ranges.Intersections(vaddr, size);
|
||||
const bool invalidated = !m_image_invalidated_ranges.Intersections(vaddr, size).empty();
|
||||
const bool requested_gpu_owned = !dirty.empty();
|
||||
const bool has_dirty_buffer_source = !dirty.empty();
|
||||
m_memory_tracker.ValidateGpuDirtyOwnership(m_gpu_modified_ranges, vaddr, size,
|
||||
"image source");
|
||||
|
||||
auto owner = find_owner();
|
||||
if (requested_gpu_owned && owner == m_buffers.end()) {
|
||||
if (has_dirty_buffer_source && owner == m_buffers.end()) {
|
||||
CacheRange merged {.address = AlignDown(vaddr),
|
||||
.size = AlignUp(vaddr + size) - AlignDown(vaddr)};
|
||||
using Iterator = decltype(m_buffers.begin());
|
||||
@@ -844,42 +635,32 @@ ImageBufferSource BufferCache::ObtainBufferForImage(uint64_t vaddr, uint64_t siz
|
||||
EXIT("BufferCache: merged image source does not contain the requested range\n");
|
||||
}
|
||||
}
|
||||
if (owner != m_buffers.end() && !cpu_modified && !invalidated &&
|
||||
(!gpu_modified || requested_gpu_owned)) {
|
||||
DiscardGpuDirtyBytesLocked(vaddr, size, "image source transfer");
|
||||
if (owner != m_buffers.end() && !cpu_modified &&
|
||||
(!gpu_modified || has_dirty_buffer_source)) {
|
||||
owner->second->tick_accessed_last = m_gc_tick;
|
||||
return {owner->second->buffer.get(), owner->second->buffer->Offset(vaddr),
|
||||
requested_gpu_owned};
|
||||
return {owner->second->buffer.get(), owner->second->buffer->Offset(vaddr)};
|
||||
}
|
||||
if (requested_gpu_owned && owner == m_buffers.end()) {
|
||||
if (has_dirty_buffer_source && owner == m_buffers.end()) {
|
||||
EXIT("BufferCache: GPU-dirty image source could not resolve its native owner\n");
|
||||
}
|
||||
}
|
||||
|
||||
// Direct-memory backing remains readable while PageManager protects the guest mapping. The
|
||||
// fallback exists for plain host mappings used by standalone renderer tests and is deliberately
|
||||
// performed outside the cache lock so a page fault cannot recurse into BufferCache.
|
||||
const auto stage_address = vaddr & ~(TRACKER_PAGE_SIZE - 1);
|
||||
const auto stage_end = (vaddr + size + TRACKER_PAGE_SIZE - 1) & ~(TRACKER_PAGE_SIZE - 1);
|
||||
const auto stage_size = stage_end - stage_address;
|
||||
(void)SynchronizeBacking(stage_address, stage_size);
|
||||
std::vector<uint8_t> bytes(stage_size);
|
||||
if (!Libs::LibKernel::Memory::TryReadBacking(stage_address, bytes.data(), stage_size)) {
|
||||
auto [staging, stage_offset] = m_staging_buffer.Map(size, 16);
|
||||
if (staging == nullptr || !Libs::LibKernel::Memory::TryReadBacking(vaddr, staging, size)) {
|
||||
EXIT("BufferCache: failed to read mapped guest image backing\n");
|
||||
}
|
||||
m_staging_buffer.Commit();
|
||||
|
||||
FaultSafeCacheLock lock(this, m_mutex);
|
||||
const auto dirty = m_gpu_modified_ranges.Intersections(vaddr, size);
|
||||
const bool invalidated = !m_image_invalidated_ranges.Intersections(vaddr, size).empty();
|
||||
const bool requested_gpu_owned = !dirty.empty();
|
||||
const bool has_dirty_buffer_source = !dirty.empty();
|
||||
auto owner = find_owner();
|
||||
if (requested_gpu_owned && owner == m_buffers.end()) {
|
||||
if (has_dirty_buffer_source && owner == m_buffers.end()) {
|
||||
EXIT("BufferCache: GPU-dirty image source lost its native owner\n");
|
||||
}
|
||||
const auto stage_offset = m_staging_buffer.Copy(bytes.data(), stage_size, 16);
|
||||
if (owner == m_buffers.end() || invalidated ||
|
||||
(m_memory_tracker.IsRegionGpuModified(vaddr, size) && !requested_gpu_owned)) {
|
||||
return {&m_staging_buffer, stage_offset + vaddr - stage_address, false};
|
||||
if (owner == m_buffers.end() ||
|
||||
(m_memory_tracker.IsRegionGpuModified(vaddr, size) && !has_dirty_buffer_source)) {
|
||||
return {&m_staging_buffer, stage_offset};
|
||||
}
|
||||
|
||||
auto& cached = *owner->second;
|
||||
@@ -893,42 +674,17 @@ ImageBufferSource BufferCache::ObtainBufferForImage(uint64_t vaddr, uint64_t siz
|
||||
[&]() noexcept {
|
||||
for (const auto& [address, upload_size]: uploads) {
|
||||
cached.buffer->CopyFrom(
|
||||
m_scheduler.Current(), m_staging_buffer, stage_offset + address - stage_address,
|
||||
m_scheduler.Current(), m_staging_buffer, stage_offset + address - vaddr,
|
||||
cached.buffer->Offset(address), upload_size, vk::AccessFlagBits::eHostWrite);
|
||||
}
|
||||
});
|
||||
DiscardGpuDirtyBytesLocked(vaddr, size, "staged image source transfer");
|
||||
return {cached.buffer.get(), cached.buffer->Offset(vaddr), requested_gpu_owned};
|
||||
}
|
||||
|
||||
void BufferCache::DiscardGpuDirtyBytesLocked(uint64_t vaddr, uint64_t size, const char* operation) {
|
||||
m_memory_tracker.ValidateGpuDirtyOwnership(m_gpu_modified_ranges, vaddr, size, operation);
|
||||
m_gpu_modified_ranges.Subtract(vaddr, size);
|
||||
const auto page_begin = vaddr & ~(TRACKER_PAGE_SIZE - 1);
|
||||
const auto page_end = (vaddr + size + TRACKER_PAGE_SIZE - 1) & ~(TRACKER_PAGE_SIZE - 1);
|
||||
for (auto page = page_begin; page < page_end; page += TRACKER_PAGE_SIZE) {
|
||||
if (m_gpu_modified_ranges.Intersections(page, TRACKER_PAGE_SIZE).empty() &&
|
||||
m_memory_tracker.IsRegionGpuModified(page, TRACKER_PAGE_SIZE)) {
|
||||
m_memory_tracker.UnmarkRegionAsGpuModified(page, TRACKER_PAGE_SIZE);
|
||||
}
|
||||
}
|
||||
m_memory_tracker.ValidateGpuDirtyOwnership(m_gpu_modified_ranges, vaddr, size, operation);
|
||||
}
|
||||
|
||||
void BufferCache::DiscardGpuDirtyBytes(uint64_t vaddr, uint64_t size) {
|
||||
if (vaddr == 0 || size == 0 || vaddr >= TRACKER_ADDRESS_SIZE ||
|
||||
size > TRACKER_ADDRESS_SIZE - vaddr) {
|
||||
EXIT("BufferCache: invalid dirty-byte discard range\n");
|
||||
}
|
||||
FaultSafeCacheLock lock(this, m_mutex);
|
||||
DiscardGpuDirtyBytesLocked(vaddr, size, "image output supersession");
|
||||
return {cached.buffer.get(), cached.buffer->Offset(vaddr)};
|
||||
}
|
||||
|
||||
void BufferCache::WriteHostMemory(uint64_t vaddr, std::span<const uint8_t> data) {
|
||||
if (vaddr == 0 || data.empty() || data.size() > UINT64_MAX - vaddr) {
|
||||
EXIT("BufferCache: invalid host DMA write\n");
|
||||
}
|
||||
(void)SynchronizeBacking(vaddr, data.size());
|
||||
Libs::LibKernel::Memory::WriteBacking(vaddr, data.data(), data.size());
|
||||
|
||||
FaultSafeCacheLock lock(this, m_mutex);
|
||||
@@ -944,45 +700,6 @@ void BufferCache::WriteHostMemory(uint64_t vaddr, std::span<const uint8_t> data)
|
||||
data.data() + begin - vaddr, range_end - begin);
|
||||
cached->tick_accessed_last = m_gc_tick;
|
||||
}
|
||||
m_image_invalidated_ranges.Subtract(vaddr, data.size());
|
||||
}
|
||||
|
||||
std::pair<std::shared_ptr<Buffer>, uint64_t> BufferCache::ObtainBufferForImageWrite(uint64_t vaddr,
|
||||
uint64_t size) {
|
||||
if (vaddr == 0 || size == 0 || vaddr >= TRACKER_ADDRESS_SIZE ||
|
||||
size > TRACKER_ADDRESS_SIZE - vaddr) {
|
||||
EXIT("BufferCache: invalid image destination\n");
|
||||
}
|
||||
const auto stage_address = vaddr & ~(TRACKER_PAGE_SIZE - 1);
|
||||
const auto stage_end = (vaddr + size + TRACKER_PAGE_SIZE - 1) & ~(TRACKER_PAGE_SIZE - 1);
|
||||
const auto stage_size = stage_end - stage_address;
|
||||
(void)SynchronizeBacking(stage_address, stage_size);
|
||||
std::vector<uint8_t> bytes(stage_size);
|
||||
if (!Libs::LibKernel::Memory::TryReadBacking(stage_address, bytes.data(), stage_size)) {
|
||||
EXIT("BufferCache: failed to preserve guest bytes around an image mirror\n");
|
||||
}
|
||||
FaultSafeCacheLock lock(this, m_mutex);
|
||||
auto& cached = GetOrCreateBuffer(m_scheduler.Current(), vaddr, size);
|
||||
m_memory_tracker.ValidateGpuDirtyOwnership(m_gpu_modified_ranges, vaddr, size,
|
||||
"image destination");
|
||||
if (!m_gpu_modified_ranges.Intersections(vaddr, size).empty()) {
|
||||
EXIT("BufferCache: image destination aliases GPU-owned buffer bytes\n");
|
||||
}
|
||||
const auto stage_offset = m_staging_buffer.Copy(bytes.data(), stage_size, 16);
|
||||
std::vector<std::pair<uint64_t, uint64_t>> uploads;
|
||||
m_memory_tracker.ForEachUploadRange(
|
||||
vaddr, size, false,
|
||||
[&](uint64_t address, uint64_t upload_size) noexcept {
|
||||
uploads.emplace_back(address, upload_size);
|
||||
},
|
||||
[&]() noexcept {
|
||||
for (const auto& [address, upload_size]: uploads) {
|
||||
cached.buffer->CopyFrom(
|
||||
m_scheduler.Current(), m_staging_buffer, stage_offset + address - stage_address,
|
||||
cached.buffer->Offset(address), upload_size, vk::AccessFlagBits::eHostWrite);
|
||||
}
|
||||
});
|
||||
return {cached.buffer, cached.buffer->Offset(vaddr)};
|
||||
}
|
||||
|
||||
void BufferCache::FillBuffer(uint64_t vaddr, uint64_t size, uint32_t value, bool is_gds) {
|
||||
@@ -999,7 +716,6 @@ void BufferCache::FillBuffer(uint64_t vaddr, uint64_t size, uint32_t value, bool
|
||||
if (vaddr == 0) {
|
||||
EXIT("BufferCache: invalid fill memory address\n");
|
||||
}
|
||||
ValidateGpuAccess(vaddr, size, false, true);
|
||||
(void)m_texture_cache.ClearMeta(vaddr);
|
||||
{
|
||||
std::lock_guard transaction(m_resource_mutex);
|
||||
@@ -1041,25 +757,12 @@ void BufferCache::CopyBuffer(uint64_t dst_vaddr, uint64_t src_vaddr, uint64_t si
|
||||
(src_gds && (src_vaddr > m_gds_buffer.Size() || size > m_gds_buffer.Size() - src_vaddr))) {
|
||||
EXIT("BufferCache: invalid or overlapping copy range\n");
|
||||
}
|
||||
if (src_memory) {
|
||||
ValidateGpuAccess(src_vaddr, size, true, false);
|
||||
}
|
||||
if (dst_memory) {
|
||||
ValidateGpuAccess(dst_vaddr, size, false, true);
|
||||
}
|
||||
if (src_memory || dst_memory) {
|
||||
std::lock_guard transaction(m_resource_mutex);
|
||||
if (src_memory) {
|
||||
(void)SynchronizeBacking(src_vaddr, size);
|
||||
}
|
||||
const auto src_region =
|
||||
src_memory ? m_texture_cache.QueryRegion(src_vaddr, size) : TextureCache::RegionInfo {};
|
||||
const auto dst_region =
|
||||
dst_memory ? m_texture_cache.QueryRegion(dst_vaddr, size) : TextureCache::RegionInfo {};
|
||||
if (src_memory && src_region.gpu_image_bytes &&
|
||||
!m_texture_cache.SynchronizeImageToBuffer(src_vaddr, size)) {
|
||||
EXIT("BufferCache: GPU copy source image could not be synchronized\n");
|
||||
}
|
||||
if (src_memory && dst_memory && !HasGpuDirtyBytes(src_vaddr, size) &&
|
||||
!HasGpuDirtyBytes(dst_vaddr, size) && !src_region.gpu_image_bytes &&
|
||||
!dst_region.gpu_image_bytes) {
|
||||
@@ -1081,7 +784,7 @@ void BufferCache::CopyBuffer(uint64_t dst_vaddr, uint64_t src_vaddr, uint64_t si
|
||||
}
|
||||
|
||||
auto& command = m_scheduler.Current();
|
||||
auto src = src_memory ? ObtainBuffer(command, src_vaddr, size, false, true)
|
||||
auto src = src_memory ? ObtainBuffer(command, src_vaddr, size, false, true, true)
|
||||
: BufferBinding {.buffer = m_gds_buffer.Handle(), .offset = src_vaddr};
|
||||
auto dst = dst_memory ? ObtainBuffer(command, dst_vaddr, size, true, false, true)
|
||||
: BufferBinding {.buffer = m_gds_buffer.Handle(), .offset = dst_vaddr};
|
||||
@@ -1134,95 +837,13 @@ bool BufferCache::IsRegionCpuModified(uint64_t vaddr, uint64_t size) {
|
||||
return m_memory_tracker.IsRegionCpuModified(vaddr, size);
|
||||
}
|
||||
|
||||
void BufferCache::InvalidateImageAliases(uint64_t vaddr, uint64_t size) {
|
||||
if (vaddr == 0 || size == 0 || vaddr >= TRACKER_ADDRESS_SIZE ||
|
||||
size > TRACKER_ADDRESS_SIZE - vaddr) {
|
||||
EXIT("BufferCache: invalid image-alias invalidation\n");
|
||||
}
|
||||
FaultSafeCacheLock lock(this, m_mutex);
|
||||
const auto end = vaddr + size;
|
||||
for (const auto& [address, cached]: m_buffers) {
|
||||
const auto cached_end = address + cached->size;
|
||||
const auto begin = std::max(vaddr, address);
|
||||
const auto range_end = std::min(end, cached_end);
|
||||
if (begin >= range_end) {
|
||||
continue;
|
||||
}
|
||||
const auto bytes = range_end - begin;
|
||||
if (!m_gpu_modified_ranges.Intersections(begin, bytes).empty()) {
|
||||
EXIT("BufferCache: image ownership overlaps exact dirty buffer bytes\n");
|
||||
}
|
||||
m_image_invalidated_ranges.Add(begin, bytes);
|
||||
}
|
||||
}
|
||||
|
||||
void BufferCache::BeginBackingPublication(uint64_t vaddr, uint64_t size, uint64_t tick) {
|
||||
if (vaddr == 0 || size == 0 || tick == 0 || vaddr >= TRACKER_ADDRESS_SIZE ||
|
||||
size > TRACKER_ADDRESS_SIZE - vaddr) {
|
||||
EXIT("BufferCache: invalid pending backing publication\n");
|
||||
}
|
||||
std::lock_guard lock(m_publication_mutex);
|
||||
m_pending_backing_publications.push_back({vaddr, size, tick});
|
||||
}
|
||||
|
||||
void BufferCache::CompleteBackingPublication(uint64_t vaddr, uint64_t size, uint64_t tick) {
|
||||
std::lock_guard lock(m_publication_mutex);
|
||||
const auto publication =
|
||||
std::ranges::find_if(m_pending_backing_publications, [&](const auto& pending) {
|
||||
return pending.address == vaddr && pending.size == size && pending.tick == tick;
|
||||
});
|
||||
if (publication == m_pending_backing_publications.end()) {
|
||||
EXIT("BufferCache: completed an unknown backing publication\n");
|
||||
}
|
||||
m_pending_backing_publications.erase(publication);
|
||||
}
|
||||
|
||||
void BufferCache::PublishImageBuffer(uint64_t vaddr, uint64_t size) {
|
||||
FaultSafeCacheLock lock(this, m_mutex);
|
||||
auto owner = m_buffers.end();
|
||||
for (auto it = m_buffers.begin(); it != m_buffers.end(); ++it) {
|
||||
if (!PageOverlaps(vaddr, size, it->second->vaddr, it->second->size)) {
|
||||
continue;
|
||||
}
|
||||
if (owner != m_buffers.end() || !it->second->buffer->IsInBounds(vaddr, size)) {
|
||||
EXIT("BufferCache: image destination aliases a non-containing cached buffer\n");
|
||||
}
|
||||
owner = it;
|
||||
}
|
||||
m_memory_tracker.ValidateGpuDirtyOwnership(m_gpu_modified_ranges, vaddr, size,
|
||||
"image destination publication");
|
||||
if (owner == m_buffers.end() || m_memory_tracker.IsRegionCpuModified(vaddr, size) ||
|
||||
!m_gpu_modified_ranges.Intersections(vaddr, size).empty()) {
|
||||
EXIT("BufferCache: image destination requires clean buffer ownership\n");
|
||||
}
|
||||
m_memory_tracker.MarkRegionAsGpuModified(vaddr, size);
|
||||
m_gpu_modified_ranges.Add(vaddr, size);
|
||||
m_image_invalidated_ranges.Subtract(vaddr, size);
|
||||
m_memory_tracker.ValidateGpuDirtyOwnership(m_gpu_modified_ranges, vaddr, size,
|
||||
"published image destination");
|
||||
owner->second->tick_accessed_last = m_gc_tick;
|
||||
}
|
||||
|
||||
void BufferCache::ValidateGpuAccess(uint64_t vaddr, uint64_t size, bool is_read,
|
||||
bool is_written) const {
|
||||
if ((!is_read && !is_written) || vaddr == 0 || size == 0 || size > UINT64_MAX - vaddr) {
|
||||
EXIT("BufferCache: invalid GPU access request\n");
|
||||
}
|
||||
if (is_read && !m_page_manager.HasGpuAccess(vaddr, size, GpuAccess::Read)) {
|
||||
EXIT("BufferCache: GPU-read access denied\n");
|
||||
}
|
||||
if (is_written && !m_page_manager.HasGpuAccess(vaddr, size, GpuAccess::Write)) {
|
||||
EXIT("BufferCache: GPU-write access denied\n");
|
||||
}
|
||||
}
|
||||
|
||||
void BufferCache::RunGarbageCollector() {
|
||||
std::lock_guard transaction(m_resource_mutex);
|
||||
const auto tick = m_gc_tick++;
|
||||
if (m_graphics.CanReportMemoryUsage()) {
|
||||
m_total_used_memory = m_graphics.GetDeviceMemoryUsage();
|
||||
}
|
||||
if (m_total_used_memory < m_trigger_gc_memory || m_fault_readback->Active()) {
|
||||
if (m_total_used_memory < m_trigger_gc_memory) {
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -1291,7 +912,6 @@ void BufferCache::RunGarbageCollector() {
|
||||
if (!m_memory_tracker.IsRegionGpuModified(retire.address, retire.size)) {
|
||||
m_memory_tracker.UntrackMemory(retire.address, retire.size);
|
||||
}
|
||||
m_image_invalidated_ranges.Subtract(retire.address, retire.size);
|
||||
if (retire.size > m_total_used_memory) {
|
||||
EXIT("BufferCache: allocation accounting underflow\n");
|
||||
}
|
||||
|
||||
+1
-23
@@ -10,7 +10,6 @@
|
||||
|
||||
#include <map>
|
||||
#include <memory>
|
||||
#include <mutex>
|
||||
#include <span>
|
||||
#include <utility>
|
||||
#include <vector>
|
||||
@@ -32,7 +31,6 @@ struct BufferBinding {
|
||||
struct ImageBufferSource {
|
||||
Buffer* buffer = nullptr;
|
||||
uint64_t offset = 0;
|
||||
bool gpu_owned = false;
|
||||
};
|
||||
|
||||
class BufferCache {
|
||||
@@ -47,8 +45,6 @@ public:
|
||||
~BufferCache();
|
||||
KYTY_CLASS_NO_COPY(BufferCache);
|
||||
|
||||
[[nodiscard]] bool InvalidateMemory(PageFaultAccess access, uint64_t vaddr, uint64_t size,
|
||||
PageFaultPhase phase) noexcept;
|
||||
void InvalidateMemory(uint64_t vaddr, uint64_t size);
|
||||
void ReadMemory(uint64_t vaddr, uint64_t size);
|
||||
void UnmapMemory(uint64_t vaddr, uint64_t size);
|
||||
@@ -62,9 +58,6 @@ public:
|
||||
uint64_t alignment);
|
||||
[[nodiscard]] std::shared_ptr<Buffer> ObtainNullBuffer();
|
||||
[[nodiscard]] ImageBufferSource ObtainBufferForImage(uint64_t vaddr, uint64_t size);
|
||||
[[nodiscard]] std::pair<std::shared_ptr<Buffer>, uint64_t>
|
||||
ObtainBufferForImageWrite(uint64_t vaddr, uint64_t size);
|
||||
void DiscardGpuDirtyBytes(uint64_t vaddr, uint64_t size);
|
||||
void FillBuffer(uint64_t vaddr, uint64_t size, uint32_t value, bool is_gds = false);
|
||||
void CopyBuffer(uint64_t dst_vaddr, uint64_t src_vaddr, uint64_t size, bool dst_gds = false,
|
||||
bool src_gds = false);
|
||||
@@ -72,12 +65,6 @@ public:
|
||||
[[nodiscard]] bool HasGpuDirtyBytes(uint64_t vaddr, uint64_t size);
|
||||
[[nodiscard]] bool IsRegionCpuModified(uint64_t vaddr, uint64_t size);
|
||||
[[nodiscard]] bool IsRegionGpuModified(uint64_t vaddr, uint64_t size);
|
||||
void InvalidateImageAliases(uint64_t vaddr, uint64_t size);
|
||||
void BeginBackingPublication(uint64_t vaddr, uint64_t size, uint64_t tick);
|
||||
void CompleteBackingPublication(uint64_t vaddr, uint64_t size, uint64_t tick);
|
||||
[[nodiscard]] bool SynchronizeBacking(uint64_t vaddr, uint64_t size);
|
||||
void PublishImageBuffer(uint64_t vaddr, uint64_t size);
|
||||
void ValidateGpuAccess(uint64_t vaddr, uint64_t size, bool is_read, bool is_written) const;
|
||||
void RunGarbageCollector();
|
||||
|
||||
private:
|
||||
@@ -91,8 +78,6 @@ private:
|
||||
struct DownloadCopy;
|
||||
struct DownloadRange;
|
||||
struct RetiredBuffer;
|
||||
struct FaultReadback;
|
||||
struct PendingBackingPublication;
|
||||
static constexpr uint64_t DOWNLOAD_ALIGNMENT = 64;
|
||||
[[nodiscard]] static uint64_t AlignDown(uint64_t value) noexcept;
|
||||
[[nodiscard]] static uint64_t AlignUp(uint64_t value);
|
||||
@@ -107,12 +92,10 @@ private:
|
||||
const void* source, uint64_t size);
|
||||
[[nodiscard]] CachedBuffer& GetOrCreateBuffer(CommandBuffer& command, uint64_t vaddr,
|
||||
uint64_t size);
|
||||
[[nodiscard]] bool SynchronizeBufferFromImage(Buffer& buffer, uint64_t vaddr, uint64_t size);
|
||||
[[nodiscard]] std::vector<DownloadRange> RecordDownloads(std::span<const DownloadCopy> copies);
|
||||
void PublishDownloads(std::span<const DownloadRange> downloads);
|
||||
void QueueGarbageDownload(std::span<const DownloadCopy> copies, RetiredBuffer retire);
|
||||
void RefreshInvalidatedRanges(CommandBuffer& command, CachedBuffer& cached, uint64_t vaddr,
|
||||
uint64_t size, bool upload);
|
||||
void DiscardGpuDirtyBytesLocked(uint64_t vaddr, uint64_t size, const char* operation);
|
||||
void WriteHostMemory(uint64_t vaddr, std::span<const uint8_t> data);
|
||||
|
||||
GraphicContext& m_graphics;
|
||||
@@ -121,17 +104,12 @@ private:
|
||||
Common::Mutex m_mutex;
|
||||
std::shared_ptr<Buffer> m_null_buffer;
|
||||
std::map<uint64_t, std::unique_ptr<CachedBuffer>> m_buffers;
|
||||
std::unique_ptr<FaultReadback> m_fault_readback;
|
||||
RangeSet m_gpu_modified_ranges;
|
||||
RangeSet m_image_invalidated_ranges;
|
||||
std::mutex m_publication_mutex;
|
||||
std::vector<PendingBackingPublication> m_pending_backing_publications;
|
||||
MemoryTracker m_memory_tracker;
|
||||
StreamBuffer m_staging_buffer;
|
||||
StreamBuffer m_stream_buffer;
|
||||
StreamBuffer m_download_buffer;
|
||||
StreamBuffer m_device_buffer;
|
||||
PageManager& m_page_manager;
|
||||
TextureCache& m_texture_cache;
|
||||
ResourceMutex& m_resource_mutex;
|
||||
uint64_t m_total_used_memory = 0;
|
||||
|
||||
+21
-35
@@ -4,37 +4,15 @@
|
||||
#include "graphics/guest_gpu/command_processor/commandProcessor.h"
|
||||
#include "graphics/guest_gpu/graphicsRun.h"
|
||||
#include "graphics/host_gpu/renderer/commandScheduler.h"
|
||||
|
||||
namespace Libs::Graphics {
|
||||
|
||||
GpuResourceManager::GpuResourceManager(GraphicContext& graphics, CommandScheduler& scheduler)
|
||||
: m_page_manager(FaultThunk, this),
|
||||
: m_scheduler(scheduler),
|
||||
m_buffer_cache(graphics, scheduler, m_page_manager, m_texture_cache, m_resource_mutex),
|
||||
m_texture_cache(graphics, scheduler, m_page_manager, m_buffer_cache, m_resource_mutex) {}
|
||||
|
||||
GpuResourceManager::~GpuResourceManager() = default;
|
||||
|
||||
bool GpuResourceManager::FaultThunk(void* context, PageFaultAccess access, uint64_t vaddr,
|
||||
uint64_t size, PageFaultPhase phase) noexcept {
|
||||
return static_cast<GpuResourceManager*>(context)->InvalidateMemory(access, vaddr, size, phase);
|
||||
}
|
||||
|
||||
bool GpuResourceManager::InvalidateMemory(PageFaultAccess access, uint64_t vaddr, uint64_t size,
|
||||
PageFaultPhase phase) noexcept {
|
||||
// Let the authoritative image materialize first. A clean overlapping buffer marks a write
|
||||
// fault CPU-dirty when it begins ownership transfer; doing that before image preflight would
|
||||
// make the image appear to race a real CPU write. Completion and release retain buffer-first
|
||||
// ordering so its pending fault is gone before TextureCache publishes the downloaded backing.
|
||||
if (phase == PageFaultPhase::Invalidate) {
|
||||
const bool image_handled = m_texture_cache.InvalidateMemory(access, vaddr, size, phase);
|
||||
const bool buffer_handled = m_buffer_cache.InvalidateMemory(access, vaddr, size, phase);
|
||||
return buffer_handled || image_handled;
|
||||
}
|
||||
const bool buffer_handled = m_buffer_cache.InvalidateMemory(access, vaddr, size, phase);
|
||||
const bool image_handled = m_texture_cache.InvalidateMemory(access, vaddr, size, phase);
|
||||
return buffer_handled || image_handled;
|
||||
}
|
||||
|
||||
bool GpuResourceManager::HandleFault(PageFaultAccess access, uint64_t fault_vaddr) noexcept {
|
||||
constexpr uint64_t fault_size = 8;
|
||||
if (!IsMapped(fault_vaddr, fault_size)) {
|
||||
@@ -115,33 +93,41 @@ bool GpuResourceManager::IsMapped(uint64_t vaddr, uint64_t size) const noexcept
|
||||
return m_mapped_ranges.Contains(vaddr, size);
|
||||
}
|
||||
|
||||
void GpuResourceManager::MapMemory(uint64_t vaddr, uint64_t size, GpuAccess access) {
|
||||
void GpuResourceManager::MapMemory(uint64_t vaddr, uint64_t size) {
|
||||
{
|
||||
std::lock_guard lock(m_mapped_ranges_mutex);
|
||||
m_mapped_ranges.Add(vaddr, size);
|
||||
}
|
||||
m_page_manager.OnGpuMap(vaddr, size, access);
|
||||
m_page_manager.OnGpuMap(vaddr, size);
|
||||
}
|
||||
|
||||
void GpuResourceManager::UnmapMemory(uint64_t vaddr, uint64_t size, GpuAccess access) {
|
||||
if (!IsMapped(vaddr, size)) {
|
||||
EXIT("cannot unmap an unmapped GPU resource range\n");
|
||||
void GpuResourceManager::UnmapMemory(uint64_t vaddr, uint64_t size) {
|
||||
if (CommandScheduler::InDeferredOperation()) {
|
||||
EXIT("unsupported memory unmap from an asynchronous GPU completion, "
|
||||
"addr=0x%016" PRIx64 " size=0x%016" PRIx64 "\n",
|
||||
vaddr, size);
|
||||
}
|
||||
if (m_resource_mutex.IsOwnedByCurrentThread()) {
|
||||
EXIT("unsupported memory unmap from a pre-owned resource transaction, "
|
||||
"addr=0x%016" PRIx64 " size=0x%016" PRIx64 "\n",
|
||||
vaddr, size);
|
||||
}
|
||||
const auto unmap = [this, vaddr, size] {
|
||||
if (m_scheduler.Active()) {
|
||||
const auto tick = m_scheduler.CurrentTick();
|
||||
m_scheduler.FinishCurrent();
|
||||
m_scheduler.WaitPriorityOperations(tick);
|
||||
}
|
||||
const auto unmap = [this, vaddr, size, access] {
|
||||
m_texture_cache.UnmapMemory(vaddr, size);
|
||||
m_buffer_cache.UnmapMemory(vaddr, size);
|
||||
m_page_manager.OnGpuUnmap(vaddr, size, access);
|
||||
m_texture_cache.UnmapMemory(vaddr, size);
|
||||
m_page_manager.OnGpuUnmap(vaddr, size);
|
||||
std::lock_guard lock(m_mapped_ranges_mutex);
|
||||
m_mapped_ranges.Subtract(vaddr, size);
|
||||
};
|
||||
if (m_gpu == nullptr) {
|
||||
if (m_resource_mutex.IsOwnedByCurrentThread()) {
|
||||
EXIT("cannot synchronously unmap from a resource transaction\n");
|
||||
}
|
||||
unmap();
|
||||
return;
|
||||
}
|
||||
Gpu::SubmissionLock submissions(*m_gpu);
|
||||
m_gpu->SendCommandSync(unmap);
|
||||
}
|
||||
|
||||
|
||||
@@ -29,18 +29,14 @@ public:
|
||||
[[nodiscard]] bool HandleFault(PageFaultAccess access, uint64_t fault_vaddr) noexcept;
|
||||
[[nodiscard]] bool InvalidateMemory(uint64_t vaddr, uint64_t size);
|
||||
[[nodiscard]] bool IsMapped(uint64_t vaddr, uint64_t size) const noexcept;
|
||||
void MapMemory(uint64_t vaddr, uint64_t size, GpuAccess access);
|
||||
void UnmapMemory(uint64_t vaddr, uint64_t size, GpuAccess access);
|
||||
void MapMemory(uint64_t vaddr, uint64_t size);
|
||||
void UnmapMemory(uint64_t vaddr, uint64_t size);
|
||||
void RunGarbageCollector();
|
||||
|
||||
private:
|
||||
static bool FaultThunk(void* context, PageFaultAccess access, uint64_t vaddr, uint64_t size,
|
||||
PageFaultPhase phase) noexcept;
|
||||
[[nodiscard]] bool InvalidateMemory(PageFaultAccess access, uint64_t vaddr, uint64_t size,
|
||||
PageFaultPhase phase) noexcept;
|
||||
|
||||
PageManager m_page_manager;
|
||||
ResourceMutex m_resource_mutex;
|
||||
CommandScheduler& m_scheduler;
|
||||
BufferCache m_buffer_cache;
|
||||
TextureCache m_texture_cache;
|
||||
mutable std::shared_mutex m_mapped_ranges_mutex;
|
||||
|
||||
@@ -219,7 +219,7 @@ private:
|
||||
typename CoarseTable::PageRange coarse_range {};
|
||||
typename TrackingTable::PageRange tracking_range {};
|
||||
if (!CoarseTable::TryGetPageRange(address, size, coarse_range) ||
|
||||
!TrackingTable::TryGetPageRange(address, size, tracking_range)) {
|
||||
(!strict_bytes && !TrackingTable::TryGetPageRange(address, size, tracking_range))) {
|
||||
return {};
|
||||
}
|
||||
MembershipList candidates;
|
||||
@@ -230,8 +230,8 @@ private:
|
||||
}
|
||||
std::vector<OwnerT> result;
|
||||
for (const Registration* registration: candidates) {
|
||||
if ((!strict_bytes || Overlaps(registration->ranges, address, size)) &&
|
||||
HasTrackingMembership(registration, tracking_range) &&
|
||||
if ((strict_bytes ? Overlaps(registration->ranges, address, size)
|
||||
: HasTrackingMembership(registration, tracking_range)) &&
|
||||
predicate(registration->owner)) {
|
||||
result.push_back(registration->owner);
|
||||
}
|
||||
|
||||
+4
-1
@@ -56,7 +56,10 @@ vk::Sampler SamplerCache::GetSampler(const ShaderSamplerResource& r) {
|
||||
case Prospero::SamplerAnisoRatio::kFour: aniso_ratio = 4.0f; break;
|
||||
case Prospero::SamplerAnisoRatio::kEight: aniso_ratio = 8.0f; break;
|
||||
case Prospero::SamplerAnisoRatio::kSixteen: aniso_ratio = 16.0f; break;
|
||||
default: EXIT("unknown ratio: %d\n", static_cast<int>(r.MaxAnisoRatio()));
|
||||
default:
|
||||
EXIT("unknown ratio: %d dwords=%08x,%08x,%08x,%08x\n",
|
||||
static_cast<int>(r.MaxAnisoRatio()), r.fields[0], r.fields[1], r.fields[2],
|
||||
r.fields[3]);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
+6
-8
@@ -135,8 +135,8 @@ void Buffer::Write(uint64_t offset, const void* source, uint64_t size) {
|
||||
void Buffer::Flush(uint64_t offset, uint64_t size) {
|
||||
EXIT_IF(m_mapped.empty() || offset > m_size || size > m_size - offset);
|
||||
if (!m_is_coherent && size != 0) {
|
||||
const auto result = vmaFlushAllocation(m_graphics->allocator, m_buffer->memory.allocation,
|
||||
offset, size);
|
||||
const auto result =
|
||||
vmaFlushAllocation(m_graphics->allocator, m_buffer->memory.allocation, offset, size);
|
||||
EXIT_NOT_IMPLEMENTED(static_cast<vk::Result>(result) != vk::Result::eSuccess);
|
||||
}
|
||||
}
|
||||
@@ -144,8 +144,8 @@ void Buffer::Flush(uint64_t offset, uint64_t size) {
|
||||
vk::BufferMemoryBarrier Buffer::Barrier(uint64_t offset, uint64_t size, vk::AccessFlags source,
|
||||
vk::AccessFlags destination) const {
|
||||
if (Handle() == nullptr || size == 0 || offset > m_size || size > m_size - offset) {
|
||||
EXIT("Buffer: invalid DMA barrier, handle=%p offset=0x%016" PRIx64
|
||||
" size=0x%016" PRIx64 " capacity=0x%016" PRIx64 "\n",
|
||||
EXIT("Buffer: invalid DMA barrier, handle=%p offset=0x%016" PRIx64 " size=0x%016" PRIx64
|
||||
" capacity=0x%016" PRIx64 "\n",
|
||||
static_cast<const void*>(Handle()), offset, size, m_size);
|
||||
}
|
||||
vk::BufferMemoryBarrier barrier {};
|
||||
@@ -175,8 +175,7 @@ void Buffer::CopyFrom(CommandBuffer& command, const Buffer& source, uint64_t sou
|
||||
command.EndRendering();
|
||||
const vk::BufferMemoryBarrier before[] = {
|
||||
source.Barrier(source_offset, size, source_before, vk::AccessFlagBits::eTransferRead),
|
||||
Barrier(destination_offset, size, destination_before,
|
||||
vk::AccessFlagBits::eTransferWrite),
|
||||
Barrier(destination_offset, size, destination_before, vk::AccessFlagBits::eTransferWrite),
|
||||
};
|
||||
const auto host_access = vk::AccessFlagBits::eHostRead | vk::AccessFlagBits::eHostWrite;
|
||||
auto before_stage = vk::PipelineStageFlags {vk::PipelineStageFlagBits::eAllCommands};
|
||||
@@ -214,8 +213,7 @@ void Buffer::Fill(uint64_t offset, uint64_t size, uint32_t value) {
|
||||
vk::PipelineStageFlagBits::eTransfer, vk::DependencyFlagBits::eByRegion,
|
||||
0, nullptr, 1, &before, 0, nullptr);
|
||||
native.fillBuffer(Handle(), offset, size, value);
|
||||
const auto after =
|
||||
Barrier(offset, size, vk::AccessFlagBits::eTransferWrite,
|
||||
const auto after = Barrier(offset, size, vk::AccessFlagBits::eTransferWrite,
|
||||
vk::AccessFlagBits::eMemoryRead | vk::AccessFlagBits::eMemoryWrite);
|
||||
native.pipelineBarrier(vk::PipelineStageFlagBits::eTransfer,
|
||||
vk::PipelineStageFlagBits::eAllCommands,
|
||||
|
||||
+7
-8
@@ -54,16 +54,15 @@ public:
|
||||
[[nodiscard]] bool IsInBounds(uint64_t address, uint64_t size) const noexcept;
|
||||
void Write(uint64_t offset, const void* source, uint64_t size);
|
||||
void Flush(uint64_t offset, uint64_t size);
|
||||
void CopyFrom(
|
||||
CommandBuffer& command, const Buffer& source, uint64_t source_offset,
|
||||
void CopyFrom(CommandBuffer& command, const Buffer& source, uint64_t source_offset,
|
||||
uint64_t destination_offset, uint64_t size,
|
||||
vk::AccessFlags source_before = vk::AccessFlagBits::eMemoryWrite,
|
||||
vk::AccessFlags destination_before =
|
||||
vk::AccessFlagBits::eMemoryRead | vk::AccessFlagBits::eMemoryWrite,
|
||||
vk::AccessFlags source_after =
|
||||
vk::AccessFlagBits::eMemoryRead | vk::AccessFlagBits::eMemoryWrite,
|
||||
vk::AccessFlags destination_after =
|
||||
vk::AccessFlagBits::eMemoryRead | vk::AccessFlagBits::eMemoryWrite);
|
||||
vk::AccessFlags destination_before = vk::AccessFlagBits::eMemoryRead |
|
||||
vk::AccessFlagBits::eMemoryWrite,
|
||||
vk::AccessFlags source_after = vk::AccessFlagBits::eMemoryRead |
|
||||
vk::AccessFlagBits::eMemoryWrite,
|
||||
vk::AccessFlags destination_after = vk::AccessFlagBits::eMemoryRead |
|
||||
vk::AccessFlagBits::eMemoryWrite);
|
||||
void Fill(uint64_t offset, uint64_t size, uint32_t value);
|
||||
|
||||
protected:
|
||||
|
||||
+185
-155
@@ -88,15 +88,34 @@ TextureCache::~TextureCache() {
|
||||
|
||||
bool TextureCache::SameBacking(const ImageInfo& cached, const ImageInfo& requested,
|
||||
bool exact_format) {
|
||||
const bool unit_extent =
|
||||
requested.extent.width == 1 && requested.extent.height == 1 && requested.extent.depth == 1;
|
||||
return cached.data == requested.data && cached.extent == requested.extent &&
|
||||
cached.samples == requested.samples &&
|
||||
cached.bytes_per_block == requested.bytes_per_block &&
|
||||
(cached.type == requested.type || unit_extent) &&
|
||||
(exact_format
|
||||
? cached.pixel_format == requested.pixel_format
|
||||
: ImageViewOps::FormatsCompatible(cached.pixel_format, requested.pixel_format));
|
||||
if (cached.data.address != requested.data.address) {
|
||||
return false;
|
||||
}
|
||||
if (cached.data.size != requested.data.size) {
|
||||
return false;
|
||||
}
|
||||
if (cached.extent != requested.extent) {
|
||||
return false;
|
||||
}
|
||||
if (cached.samples != requested.samples) {
|
||||
return false;
|
||||
}
|
||||
if (cached.bytes_per_block != requested.bytes_per_block) {
|
||||
return false;
|
||||
}
|
||||
if (cached.tile_mode != requested.tile_mode) {
|
||||
return false;
|
||||
}
|
||||
if (!ImageViewOps::FormatsCompatible(cached.pixel_format, requested.pixel_format)) {
|
||||
return false;
|
||||
}
|
||||
if (cached.type != requested.type && requested.extent != vk::Extent3D {1, 1, 1}) {
|
||||
return false;
|
||||
}
|
||||
if (exact_format && cached.pixel_format != requested.pixel_format) {
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
TextureCache::BindingType TextureCache::UploadBinding(const Image& image) {
|
||||
@@ -304,7 +323,7 @@ void TextureCache::TrackImage(ImageId id) {
|
||||
if (!image.IsTracked()) {
|
||||
image.track_addr = image_begin;
|
||||
image.track_addr_end = image_end;
|
||||
m_page_manager.UpdatePageWatchers(true, image_begin, image.info.data.size);
|
||||
m_page_manager.UpdatePageWatchers<true>(image_begin, image.info.data.size);
|
||||
return;
|
||||
}
|
||||
if (image_begin < image.track_addr) {
|
||||
@@ -329,7 +348,7 @@ void TextureCache::TrackImageHead(ImageId id) {
|
||||
}
|
||||
const auto size = image.track_addr - image_begin;
|
||||
image.track_addr = image_begin;
|
||||
m_page_manager.UpdatePageWatchers(true, image_begin, size);
|
||||
m_page_manager.UpdatePageWatchers<true>(image_begin, size);
|
||||
}
|
||||
|
||||
void TextureCache::TrackImageTail(ImageId id) {
|
||||
@@ -347,7 +366,7 @@ void TextureCache::TrackImageTail(ImageId id) {
|
||||
const auto address = image.track_addr_end;
|
||||
const auto size = image_end - address;
|
||||
image.track_addr_end = image_end;
|
||||
m_page_manager.UpdatePageWatchers(true, address, size);
|
||||
m_page_manager.UpdatePageWatchers<true>(address, size);
|
||||
}
|
||||
|
||||
void TextureCache::UntrackImage(ImageId id) {
|
||||
@@ -360,7 +379,7 @@ void TextureCache::UntrackImage(ImageId id) {
|
||||
image.track_addr = 0;
|
||||
image.track_addr_end = 0;
|
||||
if (size != 0) {
|
||||
m_page_manager.UpdatePageWatchers(false, address, size);
|
||||
m_page_manager.UpdatePageWatchers<false>(address, size);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -381,7 +400,7 @@ void TextureCache::UntrackImageHead(ImageId id) {
|
||||
UntrackImage(id);
|
||||
}
|
||||
if (size != 0) {
|
||||
m_page_manager.UpdatePageWatchers(false, begin, size);
|
||||
m_page_manager.UpdatePageWatchers<false>(begin, size);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -402,7 +421,7 @@ void TextureCache::UntrackImageTail(ImageId id) {
|
||||
UntrackImage(id);
|
||||
}
|
||||
if (size != 0) {
|
||||
m_page_manager.UpdatePageWatchers(false, address, size);
|
||||
m_page_manager.UpdatePageWatchers<false>(address, size);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -735,6 +754,12 @@ TextureCache::OverlapResult TextureCache::ResolveOverlap(const ImageInfo& reques
|
||||
(requested.IsVolume() || cached.info.IsVolume())) {
|
||||
return {ExpandImage(requested, cached_id)};
|
||||
}
|
||||
if (requested.tile_mode != cached.info.tile_mode) {
|
||||
if (safe_to_delete) {
|
||||
DeleteImages(std::array {cached_id}, cached_id);
|
||||
}
|
||||
return {merged_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;
|
||||
@@ -747,12 +772,6 @@ TextureCache::OverlapResult TextureCache::ResolveOverlap(const ImageInfo& reques
|
||||
if (requested.type == cached.info.type && requested.resources > cached.info.resources) {
|
||||
return {ExpandImage(requested, cached_id)};
|
||||
}
|
||||
if (requested.tile_mode != cached.info.tile_mode) {
|
||||
if (safe_to_delete) {
|
||||
DeleteImages(std::array {cached_id}, cached_id);
|
||||
}
|
||||
return {merged_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
|
||||
@@ -819,11 +838,20 @@ struct TextureCache::ColorTransferPlan {
|
||||
TextureUploadLayout layout;
|
||||
std::vector<vk::BufferImageCopy> regions;
|
||||
std::vector<GpuTileInfo> tiles;
|
||||
uint64_t linear_size = 0;
|
||||
bool tiled = false;
|
||||
bool swap_bgra16 = false;
|
||||
bool valid = false;
|
||||
};
|
||||
|
||||
static uint64_t GetLinearSize(std::span<const GpuTileInfo> tiles) {
|
||||
uint64_t size = 0;
|
||||
for (const auto& tile: tiles) {
|
||||
size = std::max(size, tile.linear_offset + tile.linear_size);
|
||||
}
|
||||
return size;
|
||||
}
|
||||
|
||||
struct TextureCache::DownloadPlan {
|
||||
ColorTransferPlan color;
|
||||
bool depth = false;
|
||||
@@ -848,22 +876,28 @@ TextureCache::BuildColorTransfer(const Image& image, BindingType binding,
|
||||
case BindingType::Texture: break;
|
||||
case BindingType::Storage: owner = "StorageTextureCache"; break;
|
||||
case BindingType::RenderTarget:
|
||||
if (info.resources.layers == 0 || info.data.size % info.resources.layers != 0 ||
|
||||
info.samples != 1 || image.backing.samples != 1) {
|
||||
EXIT("TextureCache: invalid color-attachment upload\n");
|
||||
}
|
||||
format = ImageOps::RenderTargetTransferFormat(info.bytes_per_block);
|
||||
allow_depth_tile = false;
|
||||
plan.swap_bgra16 = info.bgra16;
|
||||
owner = "RenderTarget";
|
||||
break;
|
||||
case BindingType::VideoOut:
|
||||
if (info.resources.layers == 0 || info.data.size % info.resources.layers != 0 ||
|
||||
info.samples != 1 || image.backing.samples != 1 ||
|
||||
(binding == BindingType::VideoOut &&
|
||||
info.metadata.compression != VideoOutCompression::Uncompressed)) {
|
||||
info.metadata.compression != VideoOutCompression::Uncompressed) {
|
||||
EXIT("TextureCache: invalid color-attachment upload\n");
|
||||
}
|
||||
format = binding == BindingType::RenderTarget
|
||||
? ImageOps::RenderTargetTransferFormat(info.bytes_per_block)
|
||||
: info.guest_format;
|
||||
format = info.guest_format;
|
||||
layers = info.resources.layers;
|
||||
volume = false;
|
||||
layered = layers > 1;
|
||||
allow_depth_tile = false;
|
||||
plan.swap_bgra16 = info.bgra16;
|
||||
owner = binding == BindingType::RenderTarget ? "RenderTarget" : "VideoOut";
|
||||
owner = "VideoOut";
|
||||
break;
|
||||
case BindingType::DepthTarget: return plan;
|
||||
}
|
||||
@@ -894,6 +928,7 @@ TextureCache::BuildColorTransfer(const Image& image, BindingType binding,
|
||||
info.resources.levels, plan.tiles)) {
|
||||
return plan;
|
||||
}
|
||||
plan.linear_size = GetLinearSize(plan.tiles);
|
||||
}
|
||||
plan.valid = true;
|
||||
return plan;
|
||||
@@ -931,11 +966,19 @@ void TextureCache::UploadImage(Image& image, const ImageDesc& desc, Buffer& sour
|
||||
|
||||
if (desc.type != BindingType::DepthTarget) {
|
||||
auto plan = BuildColorTransfer(image, desc.type, TransferDirection::Upload);
|
||||
EXIT_NOT_IMPLEMENTED(!plan.valid);
|
||||
if (!plan.valid) {
|
||||
EXIT("TextureCache: invalid color upload: binding=%u addr=0x%016" PRIx64
|
||||
" size=0x%016" PRIx64 " format=%u tile=%u family=%u extent=%ux%ux%u "
|
||||
"pitch=%u levels=%u layers=%u samples=%u\n",
|
||||
static_cast<uint32_t>(desc.type), info.data.address, info.data.size,
|
||||
info.guest_format, info.tile_mode, static_cast<uint32_t>(plan.layout.tile_family),
|
||||
info.extent.width, info.extent.height, info.extent.depth, info.pitch,
|
||||
info.resources.levels, info.resources.layers, info.samples);
|
||||
}
|
||||
TileManager::Result linear {source.Handle(), source_offset, info.data.size};
|
||||
if (plan.tiled) {
|
||||
linear = m_tiler->Detile(source.Handle(), source_offset, info.data.size, info.data.size,
|
||||
plan.tiles);
|
||||
linear = m_tiler->Detile(source.Handle(), source_offset, info.data.size,
|
||||
plan.linear_size, plan.tiles);
|
||||
}
|
||||
if (plan.swap_bgra16) {
|
||||
linear = m_tiler->SwapBgra16(linear);
|
||||
@@ -1019,7 +1062,6 @@ void TextureCache::InitializeImage(ImageId id, const ImageDesc& desc) {
|
||||
if (image.info.samples > 1) {
|
||||
return;
|
||||
}
|
||||
bool data_gpu_owned = false;
|
||||
bool data_imported = false;
|
||||
const bool upload = image.IsBufferModified() || image.IsCpuDirty();
|
||||
if (upload) {
|
||||
@@ -1028,16 +1070,12 @@ void TextureCache::InitializeImage(ImageId id, const ImageDesc& desc) {
|
||||
if (source.buffer == nullptr) {
|
||||
EXIT("TextureCache: failed to obtain image upload source\n");
|
||||
}
|
||||
data_gpu_owned |= source.gpu_owned;
|
||||
data_imported = true;
|
||||
UploadImage(image, desc, *source.buffer, source.offset);
|
||||
}
|
||||
if (data_imported) {
|
||||
image.ClearBufferModified();
|
||||
}
|
||||
if (data_gpu_owned) {
|
||||
image.MarkGpuModified();
|
||||
}
|
||||
if (image.IsCpuDirty()) {
|
||||
image.RefreshComplete();
|
||||
}
|
||||
@@ -1112,7 +1150,6 @@ ImageId TextureCache::FindImage(ImageDesc& desc, bool exact_format) {
|
||||
}
|
||||
|
||||
ImageId result {};
|
||||
bool replacement_buffer = false;
|
||||
bool inserted_new = false;
|
||||
{
|
||||
std::lock_guard transaction(m_resource_mutex);
|
||||
@@ -1122,7 +1159,7 @@ ImageId TextureCache::FindImage(ImageDesc& desc, bool exact_format) {
|
||||
|
||||
for (const auto id: candidates) {
|
||||
const auto owner = ResolveOwner(id);
|
||||
if (owner == nullptr || owner->info.data != desc.info.data) {
|
||||
if (owner == nullptr) {
|
||||
continue;
|
||||
}
|
||||
if (SameBacking(owner->info, desc.info, exact_format)) {
|
||||
@@ -1140,7 +1177,7 @@ ImageId TextureCache::FindImage(ImageDesc& desc, bool exact_format) {
|
||||
if (owner == nullptr) {
|
||||
continue;
|
||||
}
|
||||
const auto merged_info = result ? ResolveImage(result).info : desc.info;
|
||||
const auto& merged_info = result ? ResolveImage(result).info : desc.info;
|
||||
const auto overlap = ResolveOverlap(merged_info, desc.type, candidate, result);
|
||||
if (overlap.image) {
|
||||
result = overlap.image;
|
||||
@@ -1155,22 +1192,14 @@ ImageId TextureCache::FindImage(ImageDesc& desc, bool exact_format) {
|
||||
if (exact_format && resolved.info.pixel_format != desc.info.pixel_format) {
|
||||
result = {};
|
||||
} else if (resolved.info.resources < desc.info.resources) {
|
||||
ImageDesc refresh {
|
||||
.info = resolved.info, .view_info = {}, .type = UploadBinding(resolved)};
|
||||
RefreshImage(result, refresh);
|
||||
if (resolved.IsGpuModified() && !SynchronizeImageToBuffer(result)) {
|
||||
EXIT("TextureCache: cannot preserve an unsupported replacement image\n");
|
||||
}
|
||||
replacement_buffer = resolved.IsBufferModified();
|
||||
DeleteImage(result);
|
||||
result = {};
|
||||
result = ExpandImage(desc.info, result);
|
||||
}
|
||||
}
|
||||
if (!result) {
|
||||
result = InsertImage(desc.info);
|
||||
inserted_new = true;
|
||||
auto& inserted = ResolveImage(result);
|
||||
if (replacement_buffer || m_buffer_cache.HasGpuDirtyBytes(inserted.info.data.address,
|
||||
if (m_buffer_cache.HasGpuDirtyBytes(inserted.info.data.address,
|
||||
inserted.info.data.size)) {
|
||||
inserted.MarkBufferModified();
|
||||
}
|
||||
@@ -1341,11 +1370,6 @@ void TextureCache::CommitGpuWrite(Image& image) {
|
||||
if (image.depth_id || image.backing.image == nullptr) {
|
||||
EXIT("TextureCache: stencil association cannot own image contents\n");
|
||||
}
|
||||
const auto range = image.info.data;
|
||||
if (m_buffer_cache.HasGpuDirtyBytes(range.address, range.size)) {
|
||||
m_buffer_cache.DiscardGpuDirtyBytes(range.address, range.size);
|
||||
}
|
||||
m_buffer_cache.InvalidateImageAliases(range.address, range.size);
|
||||
image.ClearBufferModified();
|
||||
if (image.IsCpuDirty()) {
|
||||
image.RefreshComplete();
|
||||
@@ -1358,7 +1382,6 @@ bool TextureCache::ClearImageFromBuffer(CommandBuffer& command, uint64_t address
|
||||
if (command.IsInvalid() || !GuestRange {address, size}.Valid()) {
|
||||
EXIT("TextureCache: invalid image clear\n");
|
||||
}
|
||||
m_buffer_cache.ValidateGpuAccess(address, size, false, true);
|
||||
std::lock_guard transaction(m_resource_mutex);
|
||||
CacheLock lock(*this, m_lock);
|
||||
ImageId selected {};
|
||||
@@ -1411,9 +1434,6 @@ bool TextureCache::ClearImageFromBuffer(CommandBuffer& command, uint64_t address
|
||||
return false;
|
||||
}
|
||||
}
|
||||
if (m_buffer_cache.HasGpuDirtyBytes(address, size)) {
|
||||
m_buffer_cache.DiscardGpuDirtyBytes(address, size);
|
||||
}
|
||||
if (image.IsBufferModified() || image.IsCpuDirty()) {
|
||||
ImageDesc refresh {.info = image.info, .view_info = {}, .type = UploadBinding(image)};
|
||||
InitializeImage(selected, refresh);
|
||||
@@ -1533,11 +1553,14 @@ void TextureCache::DownloadDepth(Image& image, Buffer& destination, uint64_t des
|
||||
}
|
||||
|
||||
void TextureCache::DownloadImageData(Image& image, Buffer& destination, uint64_t destination_offset,
|
||||
DownloadPlan plan) {
|
||||
uint64_t destination_size, DownloadPlan plan) {
|
||||
if (!plan.valid) {
|
||||
EXIT("TextureCache: invalid image download plan\n");
|
||||
}
|
||||
if (plan.depth) {
|
||||
if (destination_size != image.info.data.size) {
|
||||
EXIT("TextureCache: partial depth image download is unsupported\n");
|
||||
}
|
||||
DownloadDepth(image, destination, destination_offset);
|
||||
return;
|
||||
}
|
||||
@@ -1547,22 +1570,119 @@ void TextureCache::DownloadImageData(Image& image, Buffer& destination, uint64_t
|
||||
: TileManager::ColorTransform::None;
|
||||
if (!color.tiled) {
|
||||
if (transform == TileManager::ColorTransform::SwapBgra16) {
|
||||
auto linear = m_tiler->GetScratchBuffer(image.info.data.size);
|
||||
auto linear = m_tiler->GetScratchBuffer(destination_size);
|
||||
image.Download(color.regions, linear.buffer, 0, linear.size);
|
||||
m_tiler->SwapBgra16(linear,
|
||||
{destination.Handle(), destination_offset, image.info.data.size});
|
||||
{destination.Handle(), destination_offset, destination_size});
|
||||
return;
|
||||
}
|
||||
for (auto& copy: color.regions) {
|
||||
copy.bufferOffset += destination_offset;
|
||||
}
|
||||
image.Download(color.regions, destination.Handle(), destination_offset,
|
||||
image.info.data.size);
|
||||
image.Download(color.regions, destination.Handle(), destination_offset, destination_size);
|
||||
return;
|
||||
}
|
||||
|
||||
m_tiler->TileImage(image, color.regions, destination.Handle(), destination_offset,
|
||||
image.info.data.size, image.info.data.size, color.tiles, transform);
|
||||
destination_size, color.linear_size, color.tiles, transform);
|
||||
}
|
||||
|
||||
bool BufferCache::SynchronizeBufferFromImage(Buffer& buffer, uint64_t vaddr, uint64_t size) {
|
||||
CacheLock lock(m_texture_cache, m_texture_cache.m_lock);
|
||||
std::vector<ImageId> matches;
|
||||
for (const auto id: m_texture_cache.FindImagesInRegion(vaddr, size, false)) {
|
||||
auto owner = m_texture_cache.ResolveOwner(id);
|
||||
if (owner == nullptr || owner->info.data.address != vaddr) {
|
||||
continue;
|
||||
}
|
||||
if (owner->depth_id) {
|
||||
owner = m_texture_cache.ResolveOwner(owner->depth_id);
|
||||
}
|
||||
if (owner != nullptr && owner->SafeToDownload()) {
|
||||
matches.push_back(id);
|
||||
}
|
||||
}
|
||||
|
||||
ImageId selected {};
|
||||
if (matches.size() == 1) {
|
||||
selected = matches.front();
|
||||
} else {
|
||||
for (const auto id: matches) {
|
||||
const auto& image = m_texture_cache.ResolveImage(id);
|
||||
if (image.info.data.size == size) {
|
||||
selected = id;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
if (!selected) {
|
||||
return false;
|
||||
}
|
||||
if (const auto owner = m_texture_cache.ResolveOwner(selected);
|
||||
owner != nullptr && owner->depth_id) {
|
||||
selected = owner->depth_id;
|
||||
}
|
||||
|
||||
auto& image = m_texture_cache.ResolveImage(selected);
|
||||
if (!buffer.IsInBounds(image.info.data.address, 1)) {
|
||||
return false;
|
||||
}
|
||||
const auto buf_offset = buffer.Offset(image.info.data.address);
|
||||
const auto available = buffer.Size() - buf_offset;
|
||||
uint32_t levels = 0;
|
||||
uint64_t copy_size = 0;
|
||||
if (image.info.IsVolume()) {
|
||||
// Volume mips contain strided block slices, so a mip's linear span cannot prove that
|
||||
// every retained slice fits. Keep volume synchronization whole-image only.
|
||||
if (!buffer.IsInBounds(image.info.data.address, image.info.data.size)) {
|
||||
return false;
|
||||
}
|
||||
levels = image.info.resources.levels;
|
||||
copy_size = image.info.data.size;
|
||||
} else {
|
||||
for (; levels < image.info.resources.levels; ++levels) {
|
||||
const auto& mip = image.info.mip_layout[levels];
|
||||
if (mip.size == 0 || mip.offset > available || mip.size > available - mip.offset) {
|
||||
break;
|
||||
}
|
||||
copy_size = std::max(copy_size, mip.offset + mip.size);
|
||||
}
|
||||
}
|
||||
if (copy_size == 0) {
|
||||
return false;
|
||||
}
|
||||
auto plan = m_texture_cache.BuildDownload(image);
|
||||
if (!plan.valid) {
|
||||
return false;
|
||||
}
|
||||
if (plan.depth && copy_size != image.info.data.size) {
|
||||
return false;
|
||||
}
|
||||
if (!plan.depth && levels < image.info.resources.levels) {
|
||||
auto& color = plan.color;
|
||||
std::erase_if(color.regions, [levels](const vk::BufferImageCopy& region) {
|
||||
return region.imageSubresource.mipLevel >= levels;
|
||||
});
|
||||
if (color.regions.empty()) {
|
||||
return false;
|
||||
}
|
||||
if (color.tiled) {
|
||||
const auto binding = m_texture_cache.UploadBinding(image);
|
||||
const auto format =
|
||||
binding == TextureCache::BindingType::RenderTarget
|
||||
? ImageOps::RenderTargetTransferFormat(image.info.bytes_per_block)
|
||||
: image.info.guest_format;
|
||||
color.tiles.clear();
|
||||
if (!TextureBuildGpuTileInfos(copy_size, color.regions, color.layout, format,
|
||||
image.info.TransferLayers(), levels, color.tiles)) {
|
||||
return false;
|
||||
}
|
||||
color.linear_size = GetLinearSize(color.tiles);
|
||||
}
|
||||
}
|
||||
m_texture_cache.DownloadImageData(image, buffer, buf_offset, copy_size, std::move(plan));
|
||||
m_texture_cache.RetainImage(m_scheduler.Current(), selected);
|
||||
return true;
|
||||
}
|
||||
|
||||
std::pair<uint8_t*, uint64_t> TextureCache::MapDownload(uint64_t size, uint64_t alignment) {
|
||||
@@ -1594,12 +1714,9 @@ void TextureCache::QueueDownload(GuestRange range, StreamBuffer& download, uint8
|
||||
m_scheduler.Current().Handle().pipelineBarrier(vk::PipelineStageFlagBits::eAllCommands,
|
||||
vk::PipelineStageFlagBits::eHost, {}, 0, nullptr,
|
||||
1, &barrier, 0, nullptr);
|
||||
const auto tick = m_scheduler.CurrentTick();
|
||||
m_buffer_cache.BeginBackingPublication(range.address, range.size, tick);
|
||||
m_scheduler.DeferPriorityOperation([this, &download, range, mapped, offset, tick] {
|
||||
m_scheduler.DeferPriorityOperation([&download, range, mapped, offset] {
|
||||
download.Invalidate(offset, range.size);
|
||||
LibKernel::Memory::WriteBacking(range.address, mapped, range.size);
|
||||
m_buffer_cache.CompleteBackingPublication(range.address, range.size, tick);
|
||||
});
|
||||
}
|
||||
|
||||
@@ -1620,7 +1737,7 @@ bool TextureCache::TryDownloadImage(ImageId id) {
|
||||
}
|
||||
download.Flush(offset, range.size);
|
||||
|
||||
DownloadImageData(image, download, offset, std::move(plan));
|
||||
DownloadImageData(image, download, offset, range.size, std::move(plan));
|
||||
|
||||
QueueDownload(range, download, mapped, offset);
|
||||
return true;
|
||||
@@ -1634,62 +1751,6 @@ void TextureCache::DownloadImage(ImageId id) {
|
||||
m_scheduler.DrainPriorityOperations();
|
||||
}
|
||||
|
||||
bool TextureCache::SynchronizeImageToBuffer(ImageId id) {
|
||||
auto& image = ResolveImage(id);
|
||||
if (image.depth_id) {
|
||||
return true;
|
||||
}
|
||||
auto plan = BuildDownload(image);
|
||||
if (!plan.valid) {
|
||||
return false;
|
||||
}
|
||||
const auto range = image.info.data;
|
||||
if (image.IsCpuDirty()) {
|
||||
RefreshImage(id,
|
||||
ImageDesc {.info = image.info, .view_info = {}, .type = UploadBinding(image)});
|
||||
}
|
||||
if (!image.IsGpuModified()) {
|
||||
return true;
|
||||
}
|
||||
if (image.IsDefinitelyCpuDirty() || image.IsBufferModified()) {
|
||||
EXIT("TextureCache: image mirror source is not native-current\n");
|
||||
}
|
||||
auto [destination, offset] =
|
||||
m_buffer_cache.ObtainBufferForImageWrite(range.address, range.size);
|
||||
if (destination == nullptr) {
|
||||
EXIT("TextureCache: failed to allocate image mirror\n");
|
||||
}
|
||||
DownloadImageData(image, *destination, offset, std::move(plan));
|
||||
m_scheduler.Current().RetainResourceUntilFence(destination);
|
||||
m_buffer_cache.PublishImageBuffer(range.address, range.size);
|
||||
image.MarkBufferModified();
|
||||
RetainImage(m_scheduler.Current(), id);
|
||||
ClearGpuModified(id);
|
||||
return true;
|
||||
}
|
||||
|
||||
bool TextureCache::SynchronizeImageToBuffer(uint64_t address, uint64_t size) {
|
||||
if (!GuestRange {address, size}.Valid()) {
|
||||
return false;
|
||||
}
|
||||
CacheLock lock(*this, m_lock);
|
||||
ImageId selected {};
|
||||
for (const auto id: FindImagesInRegion(address, size, true)) {
|
||||
auto owner = ResolveOwner(id);
|
||||
if (owner == nullptr || !owner->GpuOverlaps(address, size)) {
|
||||
continue;
|
||||
}
|
||||
if (selected) {
|
||||
EXIT("TextureCache: ambiguous image-to-buffer synchronization\n");
|
||||
}
|
||||
selected = id;
|
||||
}
|
||||
if (!selected) {
|
||||
return false;
|
||||
}
|
||||
return SynchronizeImageToBuffer(selected);
|
||||
}
|
||||
|
||||
bool TextureCache::InvalidateMemoryFromGPU(uint64_t address, uint64_t size,
|
||||
bool formatted_buffer_write) {
|
||||
if (!GuestRange {address, size}.Valid()) {
|
||||
@@ -1808,37 +1869,6 @@ bool TextureCache::TouchMeta(uint64_t address, uint32_t slice, bool is_clear) {
|
||||
return true;
|
||||
}
|
||||
|
||||
bool TextureCache::InvalidateMemory(PageFaultAccess access, uint64_t address, uint64_t size,
|
||||
PageFaultPhase phase) noexcept {
|
||||
if ((access != PageFaultAccess::Read && access != PageFaultAccess::Write) ||
|
||||
!GuestRange {address, size}.Valid()) {
|
||||
return false;
|
||||
}
|
||||
if (access == PageFaultAccess::Read) {
|
||||
return false;
|
||||
}
|
||||
if (phase == PageFaultPhase::Invalidate) {
|
||||
CacheLock lock(*this, m_lock);
|
||||
const bool tracked =
|
||||
std::ranges::any_of(FindImagesInRegion(address, size, true), [&](ImageId id) {
|
||||
const auto owner = ResolveOwner(id);
|
||||
return owner != nullptr && !owner->depth_id && owner->IsTracked();
|
||||
});
|
||||
if (tracked) {
|
||||
InvalidateCpuAliases(address, size);
|
||||
}
|
||||
return tracked;
|
||||
}
|
||||
if (phase != PageFaultPhase::Complete && phase != PageFaultPhase::Release) {
|
||||
return false;
|
||||
}
|
||||
CacheLock lock(*this, m_lock);
|
||||
return std::ranges::any_of(FindImagesInRegion(address, size, true), [&](ImageId id) {
|
||||
const auto owner = ResolveOwner(id);
|
||||
return owner != nullptr && !owner->depth_id;
|
||||
});
|
||||
}
|
||||
|
||||
void TextureCache::UnmapMemory(uint64_t address, uint64_t size) {
|
||||
if (!GuestRange {address, size}.Valid()) {
|
||||
EXIT("TextureCache: invalid unmap range\n");
|
||||
|
||||
+2
-5
@@ -66,7 +66,6 @@ public:
|
||||
[[nodiscard]] bool ClearImageFromBuffer(CommandBuffer& command, uint64_t address, uint64_t size,
|
||||
uint32_t packed_clear);
|
||||
void InvalidateMemory(uint64_t address, uint64_t size);
|
||||
[[nodiscard]] bool SynchronizeImageToBuffer(uint64_t address, uint64_t size);
|
||||
[[nodiscard]] bool InvalidateMemoryFromGPU(uint64_t address, uint64_t size,
|
||||
bool formatted_buffer_write = false);
|
||||
[[nodiscard]] RegionInfo QueryRegion(uint64_t address, uint64_t size);
|
||||
@@ -76,8 +75,6 @@ public:
|
||||
[[nodiscard]] bool ClearMeta(uint64_t address);
|
||||
[[nodiscard]] bool TouchMeta(uint64_t address, uint32_t slice, bool is_clear);
|
||||
|
||||
[[nodiscard]] bool InvalidateMemory(PageFaultAccess access, uint64_t address, uint64_t size,
|
||||
PageFaultPhase phase) noexcept;
|
||||
void UnmapMemory(uint64_t address, uint64_t size);
|
||||
void ProcessDownloadImages();
|
||||
void RunGarbageCollector();
|
||||
@@ -142,7 +139,7 @@ private:
|
||||
[[nodiscard]] DownloadPlan BuildDownload(const Image& image) const;
|
||||
void UploadImage(Image& image, const ImageDesc& desc, Buffer& source, uint64_t source_offset);
|
||||
void DownloadImageData(Image& image, Buffer& destination, uint64_t destination_offset,
|
||||
DownloadPlan plan);
|
||||
uint64_t destination_size, DownloadPlan plan);
|
||||
void DownloadDepth(Image& image, Buffer& destination, uint64_t destination_offset);
|
||||
void CommitGpuWrite(Image& image);
|
||||
void PrepareImageCopy(Image& image);
|
||||
@@ -157,7 +154,6 @@ private:
|
||||
void InvalidateCpuAliases(uint64_t address, uint64_t size);
|
||||
void ClearGpuModified(ImageId id);
|
||||
|
||||
[[nodiscard]] bool SynchronizeImageToBuffer(ImageId id);
|
||||
void DownloadImage(ImageId id);
|
||||
[[nodiscard]] bool TryDownloadImage(ImageId id);
|
||||
[[nodiscard]] std::pair<uint8_t*, uint64_t> MapDownload(uint64_t size, uint64_t alignment);
|
||||
@@ -186,6 +182,7 @@ private:
|
||||
bool m_readback_linear_images = false;
|
||||
|
||||
friend struct TextureCacheTestAccess;
|
||||
friend class BufferCache;
|
||||
friend class RenderExecutor;
|
||||
};
|
||||
|
||||
|
||||
@@ -7,8 +7,8 @@
|
||||
#include "graphics/guest_gpu/hardwareContext.h"
|
||||
#include "graphics/guest_gpu/tile.h"
|
||||
#include "graphics/host_gpu/graphicContext.h"
|
||||
#include "graphics/host_gpu/renderer/image/textureCommon.h"
|
||||
#include "graphics/host_gpu/renderer/debug.h"
|
||||
#include "graphics/host_gpu/renderer/image/textureCommon.h"
|
||||
#include "graphics/host_gpu/renderer/pipeline/descriptorCache.h"
|
||||
#include "graphics/host_gpu/renderer/render.h"
|
||||
#include "graphics/host_gpu/renderer/renderContext.h"
|
||||
@@ -79,10 +79,8 @@ void RenderExecutor::ResolveRenderColorTarget(uint64_t submit_id, RenderCommandB
|
||||
const auto view = ResolveTargetViewInfo(
|
||||
rt.view.base_array_slice_index, rt.view.last_array_slice_index, render_target_slice_offset);
|
||||
switch (view.type) {
|
||||
case TargetViewType::Image2D: break;
|
||||
case TargetViewType::Image2DArray:
|
||||
EXIT("layered render-target views are unsupported: base=%u count=%u\n", view.base_layer,
|
||||
view.layer_count);
|
||||
case TargetViewType::Image2D:
|
||||
case TargetViewType::Image2DArray: break;
|
||||
case TargetViewType::Unsupported:
|
||||
EXIT("invalid render-target view: base=%u last=%u draw_offset=%u\n",
|
||||
rt.view.base_array_slice_index, rt.view.last_array_slice_index,
|
||||
@@ -121,6 +119,17 @@ void RenderExecutor::ResolveRenderColorTarget(uint64_t submit_id, RenderCommandB
|
||||
uint32_t pitch = 0;
|
||||
uint64_t size = 0;
|
||||
bool tile = false;
|
||||
const bool volume = rt.attrib3.dimension == 2;
|
||||
if (rt.attrib3.dimension != 1 && !volume) {
|
||||
EXIT("unsupported render-target dimension: %u\n", rt.attrib3.dimension);
|
||||
}
|
||||
if (!volume && rt.attrib3.depth != 0) {
|
||||
EXIT("2D render target has nonzero depth: %u\n", rt.attrib3.depth);
|
||||
}
|
||||
if (volume && samples != 1) {
|
||||
EXIT("multisampled 3D render targets are unsupported\n");
|
||||
}
|
||||
const uint32_t depth = volume ? rt.attrib3.depth + 1u : 1u;
|
||||
const bool standard64 =
|
||||
rt.attrib3.tile_mode == Prospero::GpuEnumValue(Prospero::TileMode::kStandard64KB);
|
||||
|
||||
@@ -147,6 +156,7 @@ void RenderExecutor::ResolveRenderColorTarget(uint64_t submit_id, RenderCommandB
|
||||
if (bytes_per_element == 0) {
|
||||
EXIT("render-target format has no valid element size\n");
|
||||
}
|
||||
const auto transfer_format = ImageOps::RenderTargetTransferFormat(bytes_per_element);
|
||||
if (standard64 &&
|
||||
(rt.attrib3.dimension != 1 || rt.attrib3.depth != 0 || levels != 1 ||
|
||||
rt.view.current_mip_level != 0 || view.base_layer != 0 || view.image_layers != 1 ||
|
||||
@@ -167,10 +177,14 @@ void RenderExecutor::ResolveRenderColorTarget(uint64_t submit_id, RenderCommandB
|
||||
if (rt.pitch.pitch_div8_minus1 != 0) {
|
||||
pitch = (rt.pitch.pitch_div8_minus1 + 1u) << 3u;
|
||||
} else if (tile) {
|
||||
pitch = standard64
|
||||
? TileGetTexturePitch(Prospero::GpuEnumValue(Prospero::BufferFormat::k32Float),
|
||||
width, levels, rt.attrib3.tile_mode)
|
||||
: TileGetRenderTargetPitch(width, bytes_per_element, rt.attrib.num_fragments);
|
||||
if (volume) {
|
||||
pitch = TileGetTexturePitch(transfer_format, width, levels, rt.attrib3.tile_mode);
|
||||
} else if (standard64) {
|
||||
pitch = TileGetTexturePitch(Prospero::GpuEnumValue(Prospero::BufferFormat::k32Float),
|
||||
width, levels, rt.attrib3.tile_mode);
|
||||
} else {
|
||||
pitch = TileGetRenderTargetPitch(width, bytes_per_element, rt.attrib.num_fragments);
|
||||
}
|
||||
if (pitch == 0) {
|
||||
EXIT("unsupported render-target pitch: width=%u bytes=%u\n", width, bytes_per_element);
|
||||
}
|
||||
@@ -180,7 +194,17 @@ void RenderExecutor::ResolveRenderColorTarget(uint64_t submit_id, RenderCommandB
|
||||
|
||||
TileSizeOffset mip_sizes[16] {};
|
||||
TilePaddedSize mip_padded[16] {};
|
||||
if (tile) {
|
||||
TileVolumeLayout volume_layout {};
|
||||
uint64_t backing_size = 0;
|
||||
if (volume) {
|
||||
if (!tile || !TileGetTextureVolumeLayout(transfer_format, width, height, depth, levels,
|
||||
rt.attrib3.tile_mode, volume_layout)) {
|
||||
EXIT("unsupported 3D render-target layout: %ux%ux%u levels=%u tile=%u\n", width, height,
|
||||
depth, levels, rt.attrib3.tile_mode);
|
||||
}
|
||||
size = volume_layout.block_slice_size;
|
||||
backing_size = volume_layout.total_size;
|
||||
} else if (tile) {
|
||||
TileSizeAlign layout {};
|
||||
bool valid_layout = false;
|
||||
if (standard64) {
|
||||
@@ -205,12 +229,6 @@ void RenderExecutor::ResolveRenderColorTarget(uint64_t submit_id, RenderCommandB
|
||||
mip_sizes[0] = {static_cast<uint32_t>(size), 0, 0, 0, 0, 0};
|
||||
mip_padded[0] = {pitch, height};
|
||||
}
|
||||
if (rt.slice.slice_div64_minus1 != 0 &&
|
||||
(static_cast<uint64_t>(rt.slice.slice_div64_minus1) + 1u) * 64u != size) {
|
||||
EXIT("render-target slice span mismatch: encoded=0x%016" PRIx64 " derived=0x%016" PRIx64
|
||||
"\n",
|
||||
(static_cast<uint64_t>(rt.slice.slice_div64_minus1) + 1u) * 64u, size);
|
||||
}
|
||||
} else {
|
||||
size = static_cast<uint64_t>(pitch) * height * bytes_per_element * samples;
|
||||
if (size > UINT32_MAX) {
|
||||
@@ -219,23 +237,40 @@ void RenderExecutor::ResolveRenderColorTarget(uint64_t submit_id, RenderCommandB
|
||||
mip_sizes[0] = {static_cast<uint32_t>(size), 0, 0, 0, 0, 0};
|
||||
mip_padded[0] = {pitch, height};
|
||||
}
|
||||
if (size == 0 || size > UINT64_MAX / view.image_layers) {
|
||||
if (rt.slice.slice_div64_minus1 != 0 &&
|
||||
(static_cast<uint64_t>(rt.slice.slice_div64_minus1) + 1u) * 64u != size) {
|
||||
EXIT("render-target slice span mismatch: encoded=0x%016" PRIx64 " derived=0x%016" PRIx64
|
||||
"\n",
|
||||
(static_cast<uint64_t>(rt.slice.slice_div64_minus1) + 1u) * 64u, size);
|
||||
}
|
||||
if (size == 0 || (!volume && size > UINT64_MAX / view.image_layers)) {
|
||||
EXIT("render-target memory footprint is invalid\n");
|
||||
}
|
||||
const auto backing_size = size * view.image_layers;
|
||||
if (!volume) {
|
||||
backing_size = size * view.image_layers;
|
||||
}
|
||||
if (backing_size == 0) {
|
||||
EXIT("render-target backing is empty\n");
|
||||
}
|
||||
if (backing_size > TRACKER_ADDRESS_SIZE - rt.base.addr) {
|
||||
EXIT("render-target backing range is invalid\n");
|
||||
}
|
||||
|
||||
const vk::Extent2D view_extent = {std::max(width >> rt.view.current_mip_level, 1u),
|
||||
std::max(height >> rt.view.current_mip_level, 1u)};
|
||||
const uint32_t view_depth = std::max(depth >> rt.view.current_mip_level, 1u);
|
||||
if (volume &&
|
||||
(view.base_layer >= view_depth || view.layer_count > view_depth - view.base_layer)) {
|
||||
EXIT("3D render-target view exceeds mip depth: base=%u count=%u depth=%u mip=%u\n",
|
||||
view.base_layer, view.layer_count, view_depth, rt.view.current_mip_level);
|
||||
}
|
||||
|
||||
auto decision_log_id = g_render_color_log_count.fetch_add(1);
|
||||
if (decision_log_id < 128) {
|
||||
LOGF("RenderColorTarget: slot=%" PRIu32 " addr=0x%010" PRIx64 " size=0x%016" PRIx64
|
||||
" extent=%ux%u view_mip=%u view_extent=%ux%u levels=%u pitch=%u"
|
||||
" extent=%ux%ux%u view_mip=%u view_extent=%ux%u levels=%u pitch=%u"
|
||||
" fmt=0x%08" PRIx32 " nfmt=0x%08" PRIx32 " order=0x%08" PRIx32 " samples=%u tile=%s\n",
|
||||
rt_slot, rt.base.addr, backing_size, width, height, rt.view.current_mip_level,
|
||||
rt_slot, rt.base.addr, backing_size, width, height, depth, rt.view.current_mip_level,
|
||||
view_extent.width, view_extent.height, levels, pitch, rt.info.format,
|
||||
rt.info.channel_type, rt.info.channel_order, samples, tile ? "tiled" : "linear");
|
||||
}
|
||||
@@ -244,15 +279,24 @@ void RenderExecutor::ResolveRenderColorTarget(uint64_t submit_id, RenderCommandB
|
||||
desc.type = TextureCache::BindingType::RenderTarget;
|
||||
desc.info.data = {rt.base.addr, backing_size};
|
||||
desc.info.pixel_format = target_format.format;
|
||||
desc.info.guest_format = ImageOps::RenderTargetTransferFormat(bytes_per_element);
|
||||
desc.info.type = Prospero::ImageType::kColor2D;
|
||||
desc.info.extent = {width, height, 1};
|
||||
desc.info.resources = {levels, view.image_layers};
|
||||
desc.info.guest_format = transfer_format;
|
||||
desc.info.type = volume ? Prospero::ImageType::kColor3D : Prospero::ImageType::kColor2D;
|
||||
desc.info.extent = {width, height, depth};
|
||||
desc.info.resources = {levels, volume ? 1u : view.image_layers};
|
||||
desc.info.pitch = pitch;
|
||||
desc.info.bytes_per_block = bytes_per_element;
|
||||
desc.info.samples = samples;
|
||||
desc.info.tile_mode = rt.attrib3.tile_mode;
|
||||
for (uint32_t level = 0; level < levels; level++) {
|
||||
if (volume) {
|
||||
desc.info.mip_layout[level] = {
|
||||
volume_layout.level_offsets[level],
|
||||
volume_layout.level_sizes[level],
|
||||
volume_layout.level_widths[level],
|
||||
volume_layout.level_heights[level],
|
||||
};
|
||||
continue;
|
||||
}
|
||||
const auto level_offset =
|
||||
mip_sizes[level].src_size != 0 ? mip_sizes[level].src_offset : mip_sizes[level].offset;
|
||||
const auto level_size =
|
||||
|
||||
@@ -2,8 +2,8 @@
|
||||
#define EMULATOR_SRC_GRAPHICS_HOST_GPU_RENDERER_COLORRENDERTARGET_H_
|
||||
|
||||
#include "graphics/guest_gpu/gpu_defs.h"
|
||||
#include "graphics/host_gpu/renderer/renderTarget.h"
|
||||
#include "graphics/host_gpu/renderer/cache/textureCache.h"
|
||||
#include "graphics/host_gpu/renderer/renderTarget.h"
|
||||
#include "graphics/host_gpu/vulkanCommon.h"
|
||||
|
||||
#include <cstdint>
|
||||
|
||||
@@ -331,8 +331,7 @@ void CommandScheduler::WaitPriorityOperations(uint64_t tick) {
|
||||
EXIT_IF(g_deferred_callback_scheduler == this);
|
||||
std::unique_lock lock(m_operation_mutex);
|
||||
m_operation_available.wait(lock, [this, tick] {
|
||||
const bool active_before_or_at =
|
||||
m_priority_active && m_priority_active_tick <= tick;
|
||||
const bool active_before_or_at = m_priority_active && m_priority_active_tick <= tick;
|
||||
const bool queued_before_or_at =
|
||||
!m_priority_operations.empty() && m_priority_operations.front().tick <= tick;
|
||||
return !active_before_or_at && !queued_before_or_at;
|
||||
|
||||
@@ -8,8 +8,8 @@
|
||||
#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/pipeline/descriptorCache.h"
|
||||
#include "graphics/host_gpu/renderer/image/imageView.h"
|
||||
#include "graphics/host_gpu/renderer/pipeline/descriptorCache.h"
|
||||
#include "graphics/host_gpu/renderer/render.h"
|
||||
#include "graphics/host_gpu/renderer/renderContext.h"
|
||||
#include "graphics/host_gpu/vma.h"
|
||||
@@ -270,8 +270,8 @@ void CommandBuffer::BeginRendering(const RenderState& state) const {
|
||||
colors[i].sType = vk::StructureType::eRenderingAttachmentInfo;
|
||||
colors[i].imageView = attachment.image_view;
|
||||
colors[i].imageLayout = attachment.image_layout;
|
||||
colors[i].loadOp = attachment.is_clear ? vk::AttachmentLoadOp::eClear
|
||||
: vk::AttachmentLoadOp::eLoad;
|
||||
colors[i].loadOp =
|
||||
attachment.is_clear ? vk::AttachmentLoadOp::eClear : vk::AttachmentLoadOp::eLoad;
|
||||
colors[i].storeOp = vk::AttachmentStoreOp::eStore;
|
||||
colors[i].clearValue.color.uint32 = attachment.clear_value;
|
||||
}
|
||||
@@ -281,8 +281,8 @@ void CommandBuffer::BeginRendering(const RenderState& state) const {
|
||||
depth.sType = vk::StructureType::eRenderingAttachmentInfo;
|
||||
depth.imageView = depth_stencil.image_view;
|
||||
depth.imageLayout = depth_stencil.image_layout;
|
||||
depth.loadOp = depth_stencil.depth_clear ? vk::AttachmentLoadOp::eClear
|
||||
: vk::AttachmentLoadOp::eLoad;
|
||||
depth.loadOp =
|
||||
depth_stencil.depth_clear ? vk::AttachmentLoadOp::eClear : vk::AttachmentLoadOp::eLoad;
|
||||
depth.storeOp = vk::AttachmentStoreOp::eStore;
|
||||
depth.clearValue.depthStencil.depth = std::bit_cast<float>(depth_stencil.clear_value[0]);
|
||||
|
||||
@@ -290,8 +290,8 @@ void CommandBuffer::BeginRendering(const RenderState& state) const {
|
||||
stencil.sType = vk::StructureType::eRenderingAttachmentInfo;
|
||||
stencil.imageView = depth_stencil.image_view;
|
||||
stencil.imageLayout = depth_stencil.image_layout;
|
||||
stencil.loadOp = depth_stencil.stencil_clear ? vk::AttachmentLoadOp::eClear
|
||||
: vk::AttachmentLoadOp::eLoad;
|
||||
stencil.loadOp =
|
||||
depth_stencil.stencil_clear ? vk::AttachmentLoadOp::eClear : vk::AttachmentLoadOp::eLoad;
|
||||
stencil.storeOp = vk::AttachmentStoreOp::eStore;
|
||||
stencil.clearValue.depthStencil.stencil = depth_stencil.clear_value[1];
|
||||
|
||||
|
||||
@@ -359,30 +359,12 @@ static void RtCheck(const HW::RenderTarget& rt) {
|
||||
logged = true;
|
||||
}
|
||||
}
|
||||
if (rt.attrib3.depth != 0x00000000) {
|
||||
static bool logged = false;
|
||||
if (!logged) {
|
||||
LOGF("RenderTarget: temporary: ignoring PS5 color target depth_minus1=0x%08" PRIx32
|
||||
"\n",
|
||||
rt.attrib3.depth);
|
||||
logged = true;
|
||||
}
|
||||
}
|
||||
if (!RenderIsColorTileMode(rt.attrib3.tile_mode)) {
|
||||
EXIT("unknown PS5 render-target tile mode: 0x%08" PRIx32 "\n", rt.attrib3.tile_mode);
|
||||
}
|
||||
if (!RenderIsColorDimension(rt.attrib3.dimension)) {
|
||||
EXIT("unknown PS5 render-target dimension: 0x%08" PRIx32 "\n", rt.attrib3.dimension);
|
||||
}
|
||||
if (rt.attrib3.dimension != 0x00000001) {
|
||||
static bool logged = false;
|
||||
if (!logged) {
|
||||
LOGF("RenderTarget: temporary: using 2D fallback for PS5 color "
|
||||
"dimension=0x%08" PRIx32 "\n",
|
||||
rt.attrib3.dimension);
|
||||
logged = true;
|
||||
}
|
||||
}
|
||||
if (!rt.attrib3.cmask_pipe_aligned) {
|
||||
static bool logged = false;
|
||||
if (!logged) {
|
||||
@@ -497,7 +479,15 @@ static void ZPrint(const char* func, const HW::DepthRenderTarget& z) {
|
||||
}
|
||||
|
||||
// NOLINTNEXTLINE(readability-function-cognitive-complexity)
|
||||
static void ZCheck(const HW::DepthRenderTarget& z) {
|
||||
static void ZCheck(const HW::DepthRenderTarget& z, const HW::DepthControl& dc,
|
||||
const HW::RenderControl& rc) {
|
||||
const bool depth_active =
|
||||
dc.z_enable || dc.z_write_enable || dc.depth_bounds_enable || rc.depth_clear_enable;
|
||||
const bool stencil_active = dc.stencil_enable || rc.stencil_clear_enable;
|
||||
if (!depth_active && !stencil_active) {
|
||||
return;
|
||||
}
|
||||
|
||||
EXIT_NOT_IMPLEMENTED(!z.z_info.HasValidTextureCompatibility());
|
||||
EXIT_NOT_IMPLEMENTED(!z.stencil_info.HasValidTextureCompatibility());
|
||||
if (z.z_info.format == 0) {
|
||||
@@ -548,14 +538,6 @@ static void ZCheck(const HW::DepthRenderTarget& z) {
|
||||
EXIT_NOT_IMPLEMENTED(z.htile_surface.prefetch_height != 0x00000000);
|
||||
EXIT_NOT_IMPLEMENTED(z.htile_surface.dst_outside_zero_to_one != 0x00000000);
|
||||
|
||||
if (z.depth_view.slice_start != 0x00000000 || z.depth_view.slice_max != 0x00000000) {
|
||||
static std::atomic<uint32_t> log_count {0};
|
||||
if (log_count.fetch_add(1, std::memory_order_relaxed) < 16) {
|
||||
LOGF("DepthTarget: temporary: ignoring PS5 array slice view start=0x%08" PRIx32
|
||||
", max=0x%08" PRIx32 "\n",
|
||||
z.depth_view.slice_start, z.depth_view.slice_max);
|
||||
}
|
||||
}
|
||||
if (z.depth_view.current_mip_level != 0x00000000) {
|
||||
static std::atomic<uint32_t> log_count {0};
|
||||
if (log_count.fetch_add(1, std::memory_order_relaxed) < 16) {
|
||||
@@ -1214,7 +1196,7 @@ void hw_check(const RenderCommandBuffer& buffer) {
|
||||
log_phase("vp");
|
||||
VpCheck(vp, smc);
|
||||
log_phase("z");
|
||||
ZCheck(z);
|
||||
ZCheck(z, d, rc);
|
||||
log_phase("clip");
|
||||
ClipCheck(c);
|
||||
log_phase("rc");
|
||||
|
||||
@@ -10,10 +10,10 @@
|
||||
#include "graphics/guest_gpu/hardwareContext.h"
|
||||
#include "graphics/guest_gpu/tile.h"
|
||||
#include "graphics/host_gpu/graphicContext.h"
|
||||
#include "graphics/host_gpu/renderer/image/textureCommon.h"
|
||||
#include "graphics/host_gpu/renderer/debug.h"
|
||||
#include "graphics/host_gpu/renderer/pipeline/descriptorCache.h"
|
||||
#include "graphics/host_gpu/renderer/image/imageView.h"
|
||||
#include "graphics/host_gpu/renderer/image/textureCommon.h"
|
||||
#include "graphics/host_gpu/renderer/pipeline/descriptorCache.h"
|
||||
#include "graphics/host_gpu/renderer/render.h"
|
||||
#include "graphics/host_gpu/renderer/renderContext.h"
|
||||
#include "graphics/host_gpu/vulkanCommon.h"
|
||||
@@ -96,9 +96,11 @@ void RenderExecutor::ResolveRenderDepthTarget(uint64_t submit_id, RenderCommandB
|
||||
const auto& dc = hw.GetDepthControl();
|
||||
const auto& sc = hw.GetStencilControl();
|
||||
const auto& sm = hw.GetStencilMask();
|
||||
const bool has_stencil =
|
||||
z.stencil_info.format != Prospero::GpuEnumValue(Prospero::StencilFormat::kInvalid);
|
||||
const bool depth_active =
|
||||
dc.z_enable || dc.z_write_enable || dc.depth_bounds_enable || rc.depth_clear_enable;
|
||||
const bool stencil_active = dc.stencil_enable || rc.stencil_clear_enable;
|
||||
const bool stencil_active = has_stencil && (dc.stencil_enable || rc.stencil_clear_enable);
|
||||
if (!depth_active && !stencil_active) {
|
||||
return;
|
||||
}
|
||||
@@ -139,8 +141,6 @@ void RenderExecutor::ResolveRenderDepthTarget(uint64_t submit_id, RenderCommandB
|
||||
}
|
||||
return;
|
||||
}
|
||||
const bool has_stencil =
|
||||
z.stencil_info.format != Prospero::GpuEnumValue(Prospero::StencilFormat::kInvalid);
|
||||
const bool has_htile = z.z_info.htile_acceleration;
|
||||
const auto samples = render_sample_count(z.z_info.num_samples);
|
||||
if (samples == 0) {
|
||||
@@ -150,16 +150,14 @@ void RenderExecutor::ResolveRenderDepthTarget(uint64_t submit_id, RenderCommandB
|
||||
has_stencil, has_htile, z.stencil_info.htile_stencil_disabled);
|
||||
const auto view = ResolveTargetViewInfo(z.depth_view.slice_start, z.depth_view.slice_max);
|
||||
switch (view.type) {
|
||||
case TargetViewType::Image2D: break;
|
||||
case TargetViewType::Image2DArray:
|
||||
DepthFatal("layered depth views are unsupported: base=%u count=%u", view.base_layer,
|
||||
view.layer_count);
|
||||
case TargetViewType::Image2D:
|
||||
case TargetViewType::Image2DArray: break;
|
||||
case TargetViewType::Unsupported:
|
||||
DepthFatal("invalid depth view: base=%u last=%u", z.depth_view.slice_start,
|
||||
z.depth_view.slice_max);
|
||||
}
|
||||
if ((stencil_active && !has_stencil) || rc.resummarize_enable || rc.copy_centroid ||
|
||||
rc.copy_sample != 0 || z.z_info.expclear_enabled || z.stencil_info.expclear_enabled ||
|
||||
if (rc.resummarize_enable || rc.copy_centroid || rc.copy_sample != 0 ||
|
||||
z.z_info.expclear_enabled || z.stencil_info.expclear_enabled ||
|
||||
z.z_info.partially_resident || z.stencil_info.partially_resident ||
|
||||
z.z_info.max_mip_level != 0 || z.depth_view.current_mip_level != 0 ||
|
||||
z.depth_info.addr5_swizzle_mask != 0 || z.depth_info.array_mode != 0 ||
|
||||
@@ -281,10 +279,10 @@ void RenderExecutor::ResolveRenderDepthTarget(uint64_t submit_id, RenderCommandB
|
||||
r.depth_min_bounds = hw.GetDepthBoundsMin();
|
||||
r.depth_max_bounds = hw.GetDepthBoundsMax();
|
||||
|
||||
r.stencil_clear_enable = rc.stencil_clear_enable;
|
||||
r.stencil_clear_enable = has_stencil && rc.stencil_clear_enable;
|
||||
r.stencil_clear_value = hw.GetStencilClearValue();
|
||||
r.stencil_test_enable = dc.stencil_enable;
|
||||
if (dc.stencil_enable) {
|
||||
r.stencil_test_enable = has_stencil && dc.stencil_enable;
|
||||
if (r.stencil_test_enable) {
|
||||
if (dc.stencilfunc > static_cast<uint8_t>(vk::CompareOp::eAlways) ||
|
||||
(dc.backface_enable &&
|
||||
dc.stencilfunc_bf > static_cast<uint8_t>(vk::CompareOp::eAlways)) ||
|
||||
|
||||
@@ -2,9 +2,9 @@
|
||||
#define EMULATOR_SRC_GRAPHICS_HOST_GPU_RENDERER_DEPTHRENDERTARGET_H_
|
||||
|
||||
#include "common/assert.h"
|
||||
#include "graphics/host_gpu/renderer/cache/textureCache.h"
|
||||
#include "graphics/host_gpu/renderer/image/imageView.h"
|
||||
#include "graphics/host_gpu/renderer/renderTarget.h"
|
||||
#include "graphics/host_gpu/renderer/cache/textureCache.h"
|
||||
#include "graphics/host_gpu/vulkanCommon.h"
|
||||
|
||||
#include <cstdint>
|
||||
|
||||
@@ -182,8 +182,8 @@ void BlitHelper::ReinterpretColorAsMsDepth(Image& source, Image& destination) {
|
||||
auto command = command_buffer.Handle();
|
||||
source.Transit(vk::ImageLayout::eShaderReadOnlyOptimal, vk::AccessFlagBits2::eShaderRead, {},
|
||||
command);
|
||||
destination.Transit(ColorToMsDepthLayout,
|
||||
vk::AccessFlagBits2::eDepthStencilAttachmentWrite, {}, command);
|
||||
destination.Transit(ColorToMsDepthLayout, vk::AccessFlagBits2::eDepthStencilAttachmentWrite, {},
|
||||
command);
|
||||
|
||||
vk::RenderingAttachmentInfo depth_attachment {};
|
||||
depth_attachment.sType = vk::StructureType::eRenderingAttachmentInfo;
|
||||
|
||||
@@ -105,6 +105,10 @@ Image::Barriers Image::GetBarriers(vk::ImageLayout destinat
|
||||
std::optional<ImageSubresourceRange> range) {
|
||||
auto& state = backing.state;
|
||||
auto& subresource_states = backing.subresource_states;
|
||||
if (range && info.IsVolume()) {
|
||||
range->base_layer = 0;
|
||||
range->layer_count = 1;
|
||||
}
|
||||
|
||||
const bool partial =
|
||||
range && (range->base_level != 0 || range->level_count != info.resources.levels ||
|
||||
|
||||
@@ -21,8 +21,7 @@ struct GuestRange {
|
||||
|
||||
[[nodiscard]] constexpr bool Empty() const noexcept { return address == 0 || size == 0; }
|
||||
[[nodiscard]] constexpr bool Valid() const noexcept {
|
||||
return !Empty() && address < TRACKER_ADDRESS_SIZE &&
|
||||
size <= TRACKER_ADDRESS_SIZE - address;
|
||||
return !Empty() && address < TRACKER_ADDRESS_SIZE && size <= TRACKER_ADDRESS_SIZE - address;
|
||||
}
|
||||
[[nodiscard]] constexpr uint64_t End() const noexcept { return address + size; }
|
||||
auto operator<=>(const GuestRange&) const = default;
|
||||
@@ -352,8 +351,7 @@ inline bool ImageInfo::IsDepth() const noexcept {
|
||||
}
|
||||
const auto transfer_bytes = DepthAspectTransferBytes(info.pixel_format);
|
||||
return transfer_bytes == info.bytes_per_block ||
|
||||
(info.bytes_per_block == sizeof(uint16_t) &&
|
||||
transfer_bytes == sizeof(uint32_t));
|
||||
(info.bytes_per_block == sizeof(uint16_t) && transfer_bytes == sizeof(uint32_t));
|
||||
}
|
||||
|
||||
[[nodiscard]] inline VideoOutCompression
|
||||
@@ -472,16 +470,11 @@ IsSupportedDisplayRenderTargetTileMode(uint32_t tile_mode) noexcept {
|
||||
const auto unorm8 = [](uint32_t value) { return static_cast<float>(value & 0xffu) / 255.0f; };
|
||||
const auto srgb8 = [](uint32_t value) {
|
||||
const auto encoded = static_cast<float>(value & 0xffu) / 255.0f;
|
||||
return encoded <= 0.04045f ? encoded / 12.92f
|
||||
: std::pow((encoded + 0.055f) / 1.055f, 2.4f);
|
||||
return encoded <= 0.04045f ? encoded / 12.92f : std::pow((encoded + 0.055f) / 1.055f, 2.4f);
|
||||
};
|
||||
switch (format) {
|
||||
case vk::Format::eR32Uint:
|
||||
next.uint32[0] = packed;
|
||||
break;
|
||||
case vk::Format::eR32Sint:
|
||||
next.int32[0] = static_cast<int32_t>(packed);
|
||||
break;
|
||||
case vk::Format::eR32Uint: next.uint32[0] = packed; break;
|
||||
case vk::Format::eR32Sint: next.int32[0] = static_cast<int32_t>(packed); break;
|
||||
case vk::Format::eR8G8B8A8Srgb:
|
||||
next.float32[0] = srgb8(packed);
|
||||
next.float32[1] = srgb8(packed >> 8u);
|
||||
|
||||
@@ -70,15 +70,14 @@ namespace {
|
||||
}
|
||||
case vk::ImageType::e3D:
|
||||
switch (info.type) {
|
||||
case vk::ImageViewType::e3D:
|
||||
return info.base_layer == 0 && info.layer_count == 1;
|
||||
case vk::ImageViewType::e3D: return info.base_layer == 0 && info.layer_count == 1;
|
||||
case vk::ImageViewType::e2D:
|
||||
return static_cast<bool>(
|
||||
image.flags & vk::ImageCreateFlagBits::e2DArrayCompatible) &&
|
||||
return static_cast<bool>(image.flags &
|
||||
vk::ImageCreateFlagBits::e2DArrayCompatible) &&
|
||||
info.level_count == 1 && info.layer_count == 1;
|
||||
case vk::ImageViewType::e2DArray:
|
||||
return static_cast<bool>(
|
||||
image.flags & vk::ImageCreateFlagBits::e2DArrayCompatible) &&
|
||||
return static_cast<bool>(image.flags &
|
||||
vk::ImageCreateFlagBits::e2DArrayCompatible) &&
|
||||
info.level_count == 1;
|
||||
default: return false;
|
||||
}
|
||||
@@ -327,8 +326,7 @@ bool FormatsCompatible(vk::Format base, vk::Format view) noexcept {
|
||||
vk::ImageView Image::FindView(const ImageViewInfo& view_info) {
|
||||
const auto& image = backing;
|
||||
auto normalized = view_info;
|
||||
const bool is_storage =
|
||||
static_cast<bool>(normalized.usage & vk::ImageUsageFlagBits::eStorage);
|
||||
const bool is_storage = static_cast<bool>(normalized.usage & vk::ImageUsageFlagBits::eStorage);
|
||||
normalized.aspect = FullAspectMask(image.format);
|
||||
if (normalized.aspect & vk::ImageAspectFlagBits::eDepth &&
|
||||
IsDepthViewFormat(normalized.format)) {
|
||||
@@ -340,12 +338,11 @@ vk::ImageView Image::FindView(const ImageViewInfo& view_info) {
|
||||
normalized.format = image.format;
|
||||
normalized.aspect = vk::ImageAspectFlagBits::eStencil;
|
||||
}
|
||||
normalized.usage =
|
||||
is_storage ? vk::ImageUsageFlagBits::eStorage : vk::ImageUsageFlags {};
|
||||
normalized.usage = is_storage ? vk::ImageUsageFlagBits::eStorage : vk::ImageUsageFlags {};
|
||||
const bool format_compatible = normalized.format != vk::Format::eUndefined &&
|
||||
IsCompatibleViewFormat(image.format, normalized.format);
|
||||
const bool slice_view = image.image_type == vk::ImageType::e3D &&
|
||||
(normalized.type == vk::ImageViewType::e2D ||
|
||||
const bool slice_view =
|
||||
image.image_type == vk::ImageType::e3D && (normalized.type == vk::ImageViewType::e2D ||
|
||||
normalized.type == vk::ImageViewType::e2DArray);
|
||||
const bool levels_valid = normalized.level_count != 0 &&
|
||||
normalized.base_level < image.mip_levels &&
|
||||
@@ -353,15 +350,14 @@ vk::ImageView Image::FindView(const ImageViewInfo& view_info) {
|
||||
const auto view_layers = slice_view && levels_valid
|
||||
? std::max(image.extent.depth >> normalized.base_level, 1u)
|
||||
: image.layers;
|
||||
const bool ranges_valid = levels_valid &&
|
||||
normalized.layer_count != 0 && normalized.base_layer < view_layers &&
|
||||
const bool ranges_valid = levels_valid && normalized.layer_count != 0 &&
|
||||
normalized.base_layer < view_layers &&
|
||||
normalized.layer_count <= view_layers - normalized.base_layer;
|
||||
const bool mapping_valid =
|
||||
IsComponentSwizzle(normalized.mapping.r) && IsComponentSwizzle(normalized.mapping.g) &&
|
||||
IsComponentSwizzle(normalized.mapping.b) && IsComponentSwizzle(normalized.mapping.a);
|
||||
if (image.image == nullptr || !format_compatible || !ranges_valid || !mapping_valid ||
|
||||
!IsValidViewType(image, normalized) ||
|
||||
!IsValidAspect(image, normalized.aspect)) {
|
||||
!IsValidViewType(image, normalized) || !IsValidAspect(image, normalized.aspect)) {
|
||||
EXIT("invalid image view: image_format=%d view_format=%d type=%d aspect=0x%x "
|
||||
"mip=%u+%u layer=%u+%u usage=0x%x image_levels=%u image_layers=%u\n",
|
||||
static_cast<int>(image.format), static_cast<int>(normalized.format),
|
||||
|
||||
@@ -88,7 +88,9 @@ SelectSampledDepthView(vk::Format image_format, vk::Format view_format, uint32_t
|
||||
IsSupportedSampledDepthResource(const ShaderRecompiler::IR::ImageResource& resource) noexcept {
|
||||
return resource.kind == ShaderRecompiler::IR::ResourceKind::Image &&
|
||||
(resource.dimension == ShaderRecompiler::Decoder::ImageDimension::Dim2D ||
|
||||
resource.dimension == ShaderRecompiler::Decoder::ImageDimension::Dim2DArray) &&
|
||||
resource.dimension == ShaderRecompiler::Decoder::ImageDimension::Dim2DArray ||
|
||||
resource.dimension == ShaderRecompiler::Decoder::ImageDimension::Dim2DMsaa ||
|
||||
resource.dimension == ShaderRecompiler::Decoder::ImageDimension::Dim2DMsaaArray) &&
|
||||
resource.mip_mode == ShaderRecompiler::IR::ImageMipMode::None && resource.read &&
|
||||
!resource.written && !resource.atomic;
|
||||
}
|
||||
|
||||
@@ -70,6 +70,10 @@ constexpr RenderTargetFormatMapping kRenderTargetFormats[] = {
|
||||
Prospero::ChannelType::kFloat,
|
||||
Prospero::ChannelOrder::kStandard,
|
||||
{vk::Format::eB10G11R11UfloatPack32, 4}},
|
||||
{Prospero::ChannelLayout::k5_6_5,
|
||||
Prospero::ChannelType::kUNorm,
|
||||
Prospero::ChannelOrder::kStandard,
|
||||
{vk::Format::eB5G6R5UnormPack16, 2}},
|
||||
{Prospero::ChannelLayout::k16,
|
||||
Prospero::ChannelType::kUNorm,
|
||||
Prospero::ChannelOrder::kStandard,
|
||||
@@ -397,10 +401,10 @@ TextureUploadLayout TextureCalcUploadLayout(uint32_t fmt, uint64_t width, uint64
|
||||
return layout;
|
||||
}
|
||||
|
||||
std::vector<vk::BufferImageCopy>
|
||||
TextureBuildImageCopies(const TextureUploadLayout& layout, uint32_t width, uint32_t height,
|
||||
uint32_t depth, uint64_t levels, bool array_texture,
|
||||
bool volume_texture) {
|
||||
std::vector<vk::BufferImageCopy> TextureBuildImageCopies(const TextureUploadLayout& layout,
|
||||
uint32_t width, uint32_t height,
|
||||
uint32_t depth, uint64_t levels,
|
||||
bool array_texture, bool volume_texture) {
|
||||
uint32_t mip_width = width;
|
||||
uint32_t mip_height = height;
|
||||
uint32_t mip_pitch = volume_texture && static_cast<Prospero::TileMode>(layout.tile) !=
|
||||
@@ -418,10 +422,9 @@ TextureBuildImageCopies(const TextureUploadLayout& layout, uint32_t width, uint3
|
||||
for (uint32_t z = 0; z < mip_depth; z++) {
|
||||
const auto slice_offset = z * layout.slice_stride;
|
||||
vk::BufferImageCopy region {};
|
||||
region.bufferOffset =
|
||||
layout.level_sizes[i].offset + slice_offset;
|
||||
region.imageSubresource = {vk::ImageAspectFlagBits::eColor, i,
|
||||
array_texture ? z : 0, 1};
|
||||
region.bufferOffset = layout.level_sizes[i].offset + slice_offset;
|
||||
region.imageSubresource = {vk::ImageAspectFlagBits::eColor, i, array_texture ? z : 0,
|
||||
1};
|
||||
region.imageOffset.z = volume_texture ? static_cast<int>(z) : 0;
|
||||
region.imageExtent = {mip_width, mip_height, 1};
|
||||
const bool linear =
|
||||
@@ -434,8 +437,7 @@ TextureBuildImageCopies(const TextureUploadLayout& layout, uint32_t width, uint3
|
||||
return ((value + block - 1u) / block) * block;
|
||||
};
|
||||
const auto pitch = align(mip_pitch, layout.texel_block);
|
||||
region.bufferRowLength =
|
||||
pitch > align(mip_width, layout.texel_block) ? pitch : 0;
|
||||
region.bufferRowLength = pitch > align(mip_width, layout.texel_block) ? pitch : 0;
|
||||
}
|
||||
regions.push_back(region);
|
||||
}
|
||||
@@ -480,11 +482,10 @@ static bool SetGpuTileSize(uint64_t offset, uint64_t length, uint64_t capacity,
|
||||
return true;
|
||||
}
|
||||
|
||||
bool TextureBuildGpuTileInfos(uint64_t size,
|
||||
const std::vector<vk::BufferImageCopy>& regions,
|
||||
bool TextureBuildGpuTileInfos(uint64_t tiled_size, const std::vector<vk::BufferImageCopy>& regions,
|
||||
const TextureUploadLayout& layout, uint32_t fmt, uint32_t depth,
|
||||
uint64_t levels, std::vector<GpuTileInfo>& out_infos) {
|
||||
if (size == 0 || levels == 0 || levels > 16 || depth == 0 ||
|
||||
if (tiled_size == 0 || levels == 0 || levels > 16 || depth == 0 ||
|
||||
regions.size() != GetTextureRegionCount(depth, levels, layout.volume_texture) ||
|
||||
Prospero::IsFmaskTextureFormat(fmt)) {
|
||||
return false;
|
||||
@@ -522,9 +523,8 @@ bool TextureBuildGpuTileInfos(uint64_t size,
|
||||
for (uint32_t z = 0; z < mip_depth; z += block.block_depth) {
|
||||
const uint32_t copy_depth = std::min(block.block_depth, mip_depth - z);
|
||||
const auto& region = regions[region_base + z];
|
||||
const auto pitch = region.bufferRowLength != 0
|
||||
? region.bufferRowLength
|
||||
: region.imageExtent.width;
|
||||
const auto pitch =
|
||||
region.bufferRowLength != 0 ? region.bufferRowLength : region.imageExtent.width;
|
||||
const auto logical_height = region.bufferImageHeight != 0
|
||||
? region.bufferImageHeight
|
||||
: region.imageExtent.height;
|
||||
@@ -538,22 +538,20 @@ bool TextureBuildGpuTileInfos(uint64_t size,
|
||||
const uint64_t linear_span =
|
||||
static_cast<uint64_t>(copy_depth - 1u) * linear_stride +
|
||||
layout.level_sizes[level].size;
|
||||
if (!SetGpuTileSize(info.linear_offset, linear_span, size, info.linear_size) ||
|
||||
!SetGpuTileSize(info.tiled_offset, volume.level_sizes[level], size,
|
||||
if (!SetGpuTileSize(info.linear_offset, linear_span, UINT64_MAX,
|
||||
info.linear_size) ||
|
||||
!SetGpuTileSize(info.tiled_offset, volume.level_sizes[level], tiled_size,
|
||||
info.tiled_size)) {
|
||||
return false;
|
||||
}
|
||||
info.linear_slice_stride = linear_stride;
|
||||
info.width = std::max(
|
||||
(region.imageExtent.width + element.wide - 1u) / element.wide, 1u);
|
||||
info.height = std::max(
|
||||
(logical_height + element.tall - 1u) / element.tall, 1u);
|
||||
info.width =
|
||||
std::max((region.imageExtent.width + element.wide - 1u) / element.wide, 1u);
|
||||
info.height = std::max((logical_height + element.tall - 1u) / element.tall, 1u);
|
||||
info.depth = copy_depth;
|
||||
info.surface_z = block.block_depth == 1
|
||||
? static_cast<uint32_t>(region.imageOffset.z)
|
||||
: 0;
|
||||
info.pitch =
|
||||
std::max((pitch + element.wide - 1u) / element.wide, 1u);
|
||||
info.surface_z =
|
||||
block.block_depth == 1 ? static_cast<uint32_t>(region.imageOffset.z) : 0;
|
||||
info.pitch = std::max((pitch + element.wide - 1u) / element.wide, 1u);
|
||||
info.tail_x = tail ? volume.tail_x[level] : 0;
|
||||
info.tail_y = tail ? volume.tail_y[level] : 0;
|
||||
info.tail = tail;
|
||||
@@ -581,9 +579,8 @@ bool TextureBuildGpuTileInfos(uint64_t size,
|
||||
const auto level_depth = GetTextureLevelDepth(depth, level, layout.volume_texture);
|
||||
for (uint32_t z = 0; z < level_depth; z++) {
|
||||
const auto& region = regions[region_index++];
|
||||
const auto pitch = region.bufferRowLength != 0
|
||||
? region.bufferRowLength
|
||||
: region.imageExtent.width;
|
||||
const auto pitch =
|
||||
region.bufferRowLength != 0 ? region.bufferRowLength : region.imageExtent.width;
|
||||
const auto logical_height = region.bufferImageHeight != 0
|
||||
? region.bufferImageHeight
|
||||
: region.imageExtent.height;
|
||||
@@ -592,21 +589,20 @@ bool TextureBuildGpuTileInfos(uint64_t size,
|
||||
info.bytes_per_element = block.bytes_per_element;
|
||||
info.linear_offset = region.bufferOffset;
|
||||
info.tiled_offset = TextureUploadSliceSourceOffset(layout, level, z);
|
||||
if (!SetGpuTileSize(info.linear_offset, level_size.size, size, info.linear_size) ||
|
||||
!SetGpuTileSize(info.tiled_offset, GetLevelSrcSize(level_size), size,
|
||||
if (!SetGpuTileSize(info.linear_offset, level_size.size, UINT64_MAX,
|
||||
info.linear_size) ||
|
||||
!SetGpuTileSize(info.tiled_offset, GetLevelSrcSize(level_size), tiled_size,
|
||||
info.tiled_size)) {
|
||||
return false;
|
||||
}
|
||||
info.width = std::max(
|
||||
(region.imageExtent.width + element.wide - 1u) / element.wide, 1u);
|
||||
info.height = std::max(
|
||||
(logical_height + element.tall - 1u) / element.tall, 1u);
|
||||
info.width =
|
||||
std::max((region.imageExtent.width + element.wide - 1u) / element.wide, 1u);
|
||||
info.height = std::max((logical_height + element.tall - 1u) / element.tall, 1u);
|
||||
info.surface_z = base_family == TileBlockFamily::RenderTarget64KB ||
|
||||
base_family == TileBlockFamily::Depth64KB
|
||||
? region.imageSubresource.baseArrayLayer
|
||||
: 0;
|
||||
info.pitch =
|
||||
std::max((pitch + element.wide - 1u) / element.wide, 1u);
|
||||
info.pitch = std::max((pitch + element.wide - 1u) / element.wide, 1u);
|
||||
info.tail = tail;
|
||||
info.tail_x = tail ? level_size.x : 0;
|
||||
info.tail_y = tail ? level_size.y : 0;
|
||||
|
||||
@@ -40,12 +40,11 @@ TextureUploadLayout TextureCalcUploadLayout(uint32_t fmt, uint64_t width, uint64
|
||||
uint64_t tile, uint64_t upload_size,
|
||||
bool allow_depth_tile, bool volume_texture,
|
||||
const char* owner);
|
||||
std::vector<vk::BufferImageCopy>
|
||||
TextureBuildImageCopies(const TextureUploadLayout& layout, uint32_t width, uint32_t height,
|
||||
uint32_t depth, uint64_t levels, bool array_texture,
|
||||
bool volume_texture);
|
||||
bool TextureBuildGpuTileInfos(uint64_t size,
|
||||
const std::vector<vk::BufferImageCopy>& regions,
|
||||
std::vector<vk::BufferImageCopy> TextureBuildImageCopies(const TextureUploadLayout& layout,
|
||||
uint32_t width, uint32_t height,
|
||||
uint32_t depth, uint64_t levels,
|
||||
bool array_texture, bool volume_texture);
|
||||
bool TextureBuildGpuTileInfos(uint64_t tiled_size, const std::vector<vk::BufferImageCopy>& regions,
|
||||
const TextureUploadLayout& layout, uint32_t fmt, uint32_t depth,
|
||||
uint64_t levels, std::vector<GpuTileInfo>& infos);
|
||||
|
||||
|
||||
@@ -14,9 +14,9 @@
|
||||
#include "gpu_tiler_shaders/gpu_tiler_standard64_spv.h"
|
||||
#include "gpu_tiler_shaders/gpu_tiler_swap_bgra16_spv.h"
|
||||
#include "graphics/host_gpu/graphicContext.h"
|
||||
#include "graphics/host_gpu/renderer/cache/streamBuffer.h"
|
||||
#include "graphics/host_gpu/renderer/commandScheduler.h"
|
||||
#include "graphics/host_gpu/renderer/image/image.h"
|
||||
#include "graphics/host_gpu/renderer/cache/streamBuffer.h"
|
||||
|
||||
#include <algorithm>
|
||||
#include <array>
|
||||
|
||||
@@ -26,11 +26,15 @@ bool IsSampledImage(BindingKind kind) {
|
||||
case BindingKind::Sampled1DArray:
|
||||
case BindingKind::Sampled2D:
|
||||
case BindingKind::Sampled2DArray:
|
||||
case BindingKind::Sampled2DMsaa:
|
||||
case BindingKind::Sampled2DMsaaArray:
|
||||
case BindingKind::Sampled3D:
|
||||
case BindingKind::SampledUint1D:
|
||||
case BindingKind::SampledUint1DArray:
|
||||
case BindingKind::SampledUint2D:
|
||||
case BindingKind::SampledUint2DArray:
|
||||
case BindingKind::SampledUint2DMsaa:
|
||||
case BindingKind::SampledUint2DMsaaArray:
|
||||
case BindingKind::SampledUint3D: return true;
|
||||
default: return false;
|
||||
}
|
||||
|
||||
@@ -73,6 +73,11 @@ static Prospero::ImageType TextureBaseType(Prospero::ImageType type) {
|
||||
}
|
||||
}
|
||||
|
||||
static bool IsMultisampledTexture(Prospero::ImageType type) {
|
||||
return type == Prospero::ImageType::kColor2DMsaa ||
|
||||
type == Prospero::ImageType::kColor2DMsaaArray;
|
||||
}
|
||||
|
||||
static BufferView NativeStorageBuffer(RenderContext& context, CommandBuffer& command_buffer,
|
||||
const ShaderBufferResource& descriptor,
|
||||
const ShaderRecompiler::IR::BufferResource& resource,
|
||||
@@ -159,6 +164,8 @@ static bool IsSupportedSampledColorResource(const ShaderRecompiler::IR::ImageRes
|
||||
case ShaderRecompiler::Decoder::ImageDimension::Dim1DArray:
|
||||
case ShaderRecompiler::Decoder::ImageDimension::Dim2D:
|
||||
case ShaderRecompiler::Decoder::ImageDimension::Dim2DArray:
|
||||
case ShaderRecompiler::Decoder::ImageDimension::Dim2DMsaa:
|
||||
case ShaderRecompiler::Decoder::ImageDimension::Dim2DMsaaArray:
|
||||
supported_dimension = true;
|
||||
break;
|
||||
default: break;
|
||||
@@ -195,6 +202,22 @@ TargetTextureViewInfo ResolveTargetTextureView(const ShaderRecompiler::IR::Image
|
||||
? TargetTextureViewInfo {vk::ImageViewType::e2DArray, base_layer,
|
||||
image_layers - base_layer}
|
||||
: TargetTextureViewInfo {};
|
||||
case Prospero::ImageType::kColor2DMsaa:
|
||||
return resource.dimension == ShaderRecompiler::Decoder::ImageDimension::Dim2DMsaa &&
|
||||
base_layer == 0 && image_layers == 1
|
||||
? TargetTextureViewInfo {vk::ImageViewType::e2D, 0, 1}
|
||||
: TargetTextureViewInfo {};
|
||||
case Prospero::ImageType::kColor2DMsaaArray:
|
||||
if (resource.dimension == ShaderRecompiler::Decoder::ImageDimension::Dim2DMsaa &&
|
||||
base_layer == 0 && image_layers == 1) {
|
||||
return {vk::ImageViewType::e2D, 0, 1};
|
||||
}
|
||||
return resource.dimension ==
|
||||
ShaderRecompiler::Decoder::ImageDimension::Dim2DMsaaArray &&
|
||||
base_layer < image_layers
|
||||
? TargetTextureViewInfo {vk::ImageViewType::e2DArray, base_layer,
|
||||
image_layers - base_layer}
|
||||
: TargetTextureViewInfo {};
|
||||
default: return {};
|
||||
}
|
||||
}
|
||||
@@ -211,34 +234,57 @@ bool IsSupportedSampledVideoOutView(const ShaderRecompiler::IR::ImageResource& r
|
||||
bool IsSupportedDepthTargetDescriptor(const ShaderTextureResource& descriptor, const Image& image) {
|
||||
const auto width = static_cast<uint32_t>(descriptor.Width5()) + 1u;
|
||||
const auto height = static_cast<uint32_t>(descriptor.Height5()) + 1u;
|
||||
const auto pitch = TileGetTexturePitch(descriptor.Format(), width, 1, descriptor.TileMode());
|
||||
const auto type = static_cast<Prospero::ImageType>(descriptor.Type());
|
||||
const bool supported_single_layer =
|
||||
const bool multisampled = IsMultisampledTexture(type);
|
||||
const auto samples = multisampled ? 1u << descriptor.LastLevel() : 1u;
|
||||
const auto pitch =
|
||||
multisampled ? TileGetDepthPitch(width, image.info.bytes_per_block, descriptor.LastLevel())
|
||||
: TileGetTexturePitch(descriptor.Format(), width, 1, descriptor.TileMode());
|
||||
const bool supported_2d = type == Prospero::ImageType::kColor2D &&
|
||||
image.info.resources.layers == 1 && descriptor.Depth() == 0 &&
|
||||
descriptor.BaseArray5() == 0 &&
|
||||
(type == Prospero::ImageType::kColor2D || type == Prospero::ImageType::kColor2DArray);
|
||||
descriptor.BaseArray5() == 0;
|
||||
const bool supported_array = type == Prospero::ImageType::kColor2DArray &&
|
||||
descriptor.BaseArray5() <= descriptor.Depth() &&
|
||||
descriptor.Depth() < image.info.resources.layers;
|
||||
const bool supported_cube =
|
||||
type == Prospero::ImageType::kCube && width == height && image.info.resources.layers >= 6 &&
|
||||
image.info.resources.layers % 6u == 0 &&
|
||||
static_cast<uint32_t>(descriptor.Depth()) + 1u == image.info.resources.layers &&
|
||||
descriptor.BaseArray5() == 0;
|
||||
const bool supported_msaa_2d = type == Prospero::ImageType::kColor2DMsaa &&
|
||||
image.info.resources.layers == 1 && descriptor.Depth() == 0 &&
|
||||
descriptor.BaseArray5() == 0;
|
||||
const bool supported_msaa_array = type == Prospero::ImageType::kColor2DMsaaArray &&
|
||||
descriptor.BaseArray5() <= descriptor.Depth() &&
|
||||
descriptor.Depth() < image.info.resources.layers;
|
||||
const bool levels_ok =
|
||||
multisampled
|
||||
? descriptor.BaseLevel() == 0 && descriptor.LastLevel() >= 1 &&
|
||||
descriptor.LastLevel() <= 3 && descriptor.MaxMip() == descriptor.LastLevel() &&
|
||||
image.info.resources.levels == 1 && image.info.samples == samples
|
||||
: descriptor.BaseLevel() == 0 && descriptor.LastLevel() == 0 &&
|
||||
descriptor.MaxMip() == 0 && image.info.samples == 1;
|
||||
return image.info.IsDepth() && width == image.info.extent.width &&
|
||||
height == image.info.extent.height && (supported_single_layer || supported_cube) &&
|
||||
descriptor.BaseLevel() == 0 && descriptor.LastLevel() == 0 && descriptor.MaxMip() == 0 &&
|
||||
descriptor.MinLod() == 0 && descriptor.BaseArray5() == 0 &&
|
||||
height == image.info.extent.height &&
|
||||
(supported_2d || supported_array || supported_cube || supported_msaa_2d ||
|
||||
supported_msaa_array) &&
|
||||
levels_ok && descriptor.MinLod() == 0 &&
|
||||
descriptor.TileMode() == Prospero::GpuEnumValue(Prospero::TileMode::kDepth) &&
|
||||
descriptor.BCSwizzle() == 0 && !descriptor.MsaaDepth() && pitch >= width &&
|
||||
pitch == image.info.pitch;
|
||||
descriptor.BCSwizzle() == 0 && (!descriptor.MsaaDepth() || multisampled) &&
|
||||
pitch >= width && pitch == image.info.pitch;
|
||||
}
|
||||
|
||||
bool IsSupportedDepthTextureEncoding(const ShaderTextureResource& descriptor, const Image& image) {
|
||||
constexpr uint32_t field1_reserved_mask = 0x200fff00u;
|
||||
constexpr uint32_t field2_reserved_mask = 0xf0003000u;
|
||||
constexpr uint32_t field3_common = 0x01800000u;
|
||||
constexpr uint32_t field5_expected = 0x00700000u;
|
||||
const uint32_t field3_expected =
|
||||
(descriptor.Type() << 28u) | field3_common | descriptor.DstSelXYZW();
|
||||
const uint32_t field3_expected = descriptor.DstSelXYZW() |
|
||||
(static_cast<uint32_t>(descriptor.BaseLevel()) << 12u) |
|
||||
(static_cast<uint32_t>(descriptor.LastLevel()) << 16u) |
|
||||
(static_cast<uint32_t>(descriptor.TileMode()) << 20u) |
|
||||
(static_cast<uint32_t>(descriptor.Type()) << 28u);
|
||||
const uint32_t field4_expected = descriptor.Depth() | (descriptor.BaseArray5() << 16u);
|
||||
const uint32_t field5_expected =
|
||||
0x00700000u | (static_cast<uint32_t>(descriptor.MaxMip()) << 4u);
|
||||
const bool common = (descriptor.fields[1] & field1_reserved_mask) == 0 &&
|
||||
(descriptor.fields[2] & field2_reserved_mask) == 0 &&
|
||||
descriptor.fields[3] == field3_expected &&
|
||||
@@ -251,8 +297,9 @@ bool IsSupportedDepthTextureEncoding(const ShaderTextureResource& descriptor, co
|
||||
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) == htile_control && metadata_addr != 0 &&
|
||||
return (descriptor.fields[6] & 0x00ffffffu) == expected_control && metadata_addr != 0 &&
|
||||
descriptor.TileMode() == Prospero::GpuEnumValue(Prospero::TileMode::kDepth) &&
|
||||
image.info.tile_mode == Prospero::GpuEnumValue(Prospero::TileMode::kDepth) &&
|
||||
image.info.metadata.kind == ImageMetadataKind::Htile &&
|
||||
@@ -344,9 +391,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 bool supported_mip_view = descriptor.BaseLevel() == 0 || is_1d || is_2d;
|
||||
return (is_1d || is_1d_array || is_2d || is_2d_array || is_3d) && supported_tile &&
|
||||
supported_mip_view && descriptor.BaseLevel() == descriptor.LastLevel() &&
|
||||
descriptor.BaseLevel() == descriptor.LastLevel() &&
|
||||
descriptor.LastLevel() <= descriptor.MaxMip() && descriptor.MinLod() == 0 &&
|
||||
supported_swizzle && descriptor.BCSwizzle() == 0 && !descriptor.MsaaDepth();
|
||||
}
|
||||
@@ -377,10 +423,14 @@ void ValidateStorageTexture(const ShaderRecompiler::IR::ImageResource& resource,
|
||||
const bool encoding_ok = IsSupportedStorageTextureEncoding(descriptor);
|
||||
const bool uint_resource =
|
||||
resource.kind == ShaderRecompiler::IR::ResourceKind::StorageImageUint;
|
||||
const bool raw_sint_storage =
|
||||
format == Prospero::GpuEnumValue(Prospero::BufferFormat::k32SInt) && uint_resource &&
|
||||
resource.written && !resource.read && !resource.atomic;
|
||||
const bool format_ok =
|
||||
Prospero::IsSupportedTextureFormat(format) &&
|
||||
raw_sint_storage ||
|
||||
(Prospero::IsSupportedTextureFormat(format) &&
|
||||
uint_resource == Prospero::IsUintTextureFormat(format) &&
|
||||
(!resource.atomic || format == Prospero::GpuEnumValue(Prospero::BufferFormat::k32UInt));
|
||||
(!resource.atomic || format == Prospero::GpuEnumValue(Prospero::BufferFormat::k32UInt)));
|
||||
if (resource_ok && descriptor_ok && encoding_ok && format_ok && size != 0) {
|
||||
return;
|
||||
}
|
||||
@@ -518,6 +568,7 @@ static ImageViewInfo TextureViewInfo(const ShaderRecompiler::IR::ImageResource&
|
||||
view.layer_count = 1;
|
||||
break;
|
||||
case ShaderRecompiler::Decoder::ImageDimension::Dim2DArray:
|
||||
case ShaderRecompiler::Decoder::ImageDimension::Dim2DMsaaArray:
|
||||
view.type = vk::ImageViewType::e2DArray;
|
||||
view.base_layer = descriptor.BaseArray5();
|
||||
if (view.base_layer >= image_layers) {
|
||||
@@ -526,6 +577,7 @@ static ImageViewInfo TextureViewInfo(const ShaderRecompiler::IR::ImageResource&
|
||||
view.layer_count = image_layers - view.base_layer;
|
||||
break;
|
||||
case ShaderRecompiler::Decoder::ImageDimension::Dim2D:
|
||||
case ShaderRecompiler::Decoder::ImageDimension::Dim2DMsaa:
|
||||
view.type = vk::ImageViewType::e2D;
|
||||
view.base_layer = descriptor.BaseArray5();
|
||||
if (view.base_layer >= image_layers) {
|
||||
@@ -562,19 +614,28 @@ RenderExecutor::ResolveTexture(const ShaderRecompiler::IR::ImageResource& reso
|
||||
const auto base_level = descriptor.BaseLevel();
|
||||
const auto last_level = descriptor.LastLevel();
|
||||
const auto type = TextureType(descriptor);
|
||||
const bool multisampled =
|
||||
type == Prospero::ImageType::kColor2DMsaa || type == Prospero::ImageType::kColor2DMsaaArray;
|
||||
const bool multisampled = IsMultisampledTexture(type);
|
||||
const auto levels = multisampled ? 1u : static_cast<uint32_t>(descriptor.MaxMip()) + 1u;
|
||||
const auto tile = descriptor.TileMode();
|
||||
const bool msaa_tile = tile == Prospero::GpuEnumValue(Prospero::TileMode::kRenderTarget);
|
||||
const bool depth_tile = tile == Prospero::GpuEnumValue(Prospero::TileMode::kDepth);
|
||||
const bool msaa_tile =
|
||||
depth_tile || tile == Prospero::GpuEnumValue(Prospero::TileMode::kRenderTarget);
|
||||
const bool msaa_array = type == Prospero::ImageType::kColor2DMsaaArray;
|
||||
if ((!multisampled && (base_level > last_level || last_level >= levels)) ||
|
||||
(multisampled &&
|
||||
(base_level != 0 || last_level == 0 || last_level > 3 ||
|
||||
descriptor.MaxMip() != last_level || !msaa_tile || descriptor.MsaaDepth() ||
|
||||
descriptor.MaxMip() != last_level || !msaa_tile ||
|
||||
(descriptor.MsaaDepth() && !depth_tile) ||
|
||||
(!msaa_array && (descriptor.Depth() != 0 || descriptor.BaseArray5() != 0))))) {
|
||||
EXIT("unsupported texture mip view: base=%u last=%u levels=%u\n", base_level, last_level,
|
||||
levels);
|
||||
EXIT("unsupported texture mip view: base=%u last=%u levels=%u max=%u type=%u tile=%u "
|
||||
"kind=%u dimension=%u mip_mode=%u read=%d written=%d "
|
||||
"dwords=%08x,%08x,%08x,%08x,%08x,%08x,%08x,%08x\n",
|
||||
base_level, last_level, levels, descriptor.MaxMip(), descriptor.Type(), tile,
|
||||
static_cast<uint32_t>(resource.kind), static_cast<uint32_t>(resource.dimension),
|
||||
static_cast<uint32_t>(resource.mip_mode), resource.read, resource.written,
|
||||
descriptor.fields[0], descriptor.fields[1], descriptor.fields[2], descriptor.fields[3],
|
||||
descriptor.fields[4], descriptor.fields[5], descriptor.fields[6],
|
||||
descriptor.fields[7]);
|
||||
}
|
||||
const auto samples = multisampled ? 1u << last_level : 1u;
|
||||
const auto view_levels =
|
||||
@@ -601,7 +662,8 @@ RenderExecutor::ResolveTexture(const ShaderRecompiler::IR::ImageResource& reso
|
||||
TileSizeAlign size {};
|
||||
if (multisampled) {
|
||||
const auto bytes = Prospero::NumBytesPerElement(format);
|
||||
pitch = TileGetRenderTargetPitch(width, bytes, last_level);
|
||||
pitch = depth_tile ? TileGetDepthPitch(width, bytes, last_level)
|
||||
: TileGetRenderTargetPitch(width, bytes, last_level);
|
||||
if (pitch == 0 || !TileGetRenderTargetSize(width, height, pitch, bytes, size, last_level) ||
|
||||
size.size > UINT32_MAX / image_layers) {
|
||||
EXIT("unsupported multisample texture layout\n");
|
||||
@@ -616,12 +678,13 @@ RenderExecutor::ResolveTexture(const ShaderRecompiler::IR::ImageResource& reso
|
||||
(address & (static_cast<uint64_t>(size.align) - 1u)) != 0);
|
||||
if (storage) {
|
||||
ValidateStorageTexture(resource, descriptor, size.size);
|
||||
m_context.GetBufferCache().ValidateGpuAccess(address, size.size, resource.read,
|
||||
resource.written);
|
||||
}
|
||||
|
||||
const auto pixel_format = TextureGetFormat(format);
|
||||
const auto storage_view_format = SrgbStorageViewFormat(pixel_format);
|
||||
const auto storage_view_format =
|
||||
storage && format == Prospero::GpuEnumValue(Prospero::BufferFormat::k32SInt)
|
||||
? vk::Format::eR32Uint
|
||||
: SrgbStorageViewFormat(pixel_format);
|
||||
const auto view_format = storage && storage_view_format != vk::Format::eUndefined
|
||||
? storage_view_format
|
||||
: pixel_format;
|
||||
|
||||
@@ -116,8 +116,8 @@ PipelineCache::GraphicsPipeline& PipelineCache::CreateGraphicsPipeline(
|
||||
if (attachment_samples == 0) {
|
||||
attachment_samples = depth.samples;
|
||||
} else if (attachment_samples != depth.samples) {
|
||||
EXIT("mixed color/depth sample counts are unsupported: %u and %u\n",
|
||||
attachment_samples, depth.samples);
|
||||
EXIT("mixed color/depth sample counts are unsupported: %u and %u\n", attachment_samples,
|
||||
depth.samples);
|
||||
}
|
||||
}
|
||||
EXIT_IF(attachment_samples == 0 ||
|
||||
@@ -154,8 +154,9 @@ PipelineCache::GraphicsPipeline& PipelineCache::CreateGraphicsPipeline(
|
||||
for (uint32_t i = 0; i < RENDER_COLOR_ATTACHMENTS_MAX; i++) {
|
||||
static_params.color_mask[i] = color_mask[i];
|
||||
}
|
||||
static_params.cull_back = mc.cull_back;
|
||||
static_params.cull_front = mc.cull_front;
|
||||
const bool rect_list = topology == vk::PrimitiveTopology::ePatchList;
|
||||
static_params.cull_back = !rect_list && mc.cull_back;
|
||||
static_params.cull_front = !rect_list && mc.cull_front;
|
||||
static_params.face = mc.face;
|
||||
|
||||
for (uint32_t i = 0; i < color_count; i++) {
|
||||
@@ -203,9 +204,8 @@ PipelineCache::GraphicsPipeline& PipelineCache::CreateGraphicsPipeline(
|
||||
LogPipelineTrace("CreatePipelineInternal begin", vs_id.hash0, vs_id.crc32, ps_id.hash0,
|
||||
ps_id.crc32);
|
||||
CreatePipelineInternal(m_graphics, m_descriptor_cache, *cached, rendering, vs_input_info,
|
||||
vs_spirv, ps_input_info,
|
||||
ps_spirv, static_params, vs_id.hash0, vs_id.crc32, ps_id.hash0,
|
||||
ps_id.crc32, ps_active);
|
||||
vs_spirv, ps_input_info, ps_spirv, static_params, vs_id.hash0,
|
||||
vs_id.crc32, ps_id.hash0, ps_id.crc32, ps_active);
|
||||
LogPipelineTrace("CreatePipelineInternal done", vs_id.hash0, vs_id.crc32, ps_id.hash0,
|
||||
ps_id.crc32);
|
||||
|
||||
|
||||
@@ -118,11 +118,12 @@ public:
|
||||
ShaderId cs_shader_id;
|
||||
};
|
||||
|
||||
GraphicsPipeline& CreateGraphicsPipeline(
|
||||
RenderColorInfo* colors, uint32_t color_count, RenderDepthInfo& depth,
|
||||
GraphicsPipeline&
|
||||
CreateGraphicsPipeline(RenderColorInfo* colors, uint32_t color_count, RenderDepthInfo& depth,
|
||||
ShaderVertexInputInfo& vs_input_info, RenderCommandBuffer& command,
|
||||
ShaderPixelInputInfo* ps_input_info, vk::PrimitiveTopology topology, bool ps_active,
|
||||
std::span<const uint32_t> vs_spirv, std::span<const uint32_t> ps_spirv);
|
||||
ShaderPixelInputInfo* ps_input_info, vk::PrimitiveTopology topology,
|
||||
bool ps_active, std::span<const uint32_t> vs_spirv,
|
||||
std::span<const uint32_t> ps_spirv);
|
||||
ComputePipeline& CreateComputePipeline(ShaderComputeInputInfo& input_info,
|
||||
const HW::ComputeShaderInfo& cs_regs,
|
||||
std::span<const uint32_t> cs_spirv);
|
||||
@@ -211,16 +212,13 @@ private:
|
||||
|
||||
void LogPipelineTrace(const char* phase, uint32_t vs_hash0, uint32_t vs_crc32, uint32_t ps_hash0,
|
||||
uint32_t ps_crc32);
|
||||
void CreatePipelineInternal(GraphicContext& graphics, DescriptorCache& descriptor_cache,
|
||||
PipelineCache::GraphicsPipeline& pipeline,
|
||||
const PipelineRenderingState& rendering,
|
||||
const ShaderVertexInputInfo& vs_input_info,
|
||||
std::span<const uint32_t> vs_shader,
|
||||
const ShaderPixelInputInfo* ps_input_info,
|
||||
std::span<const uint32_t> ps_shader,
|
||||
const PipelineStaticParameters& static_params, uint32_t vs_hash0,
|
||||
uint32_t vs_crc32, uint32_t ps_hash0, uint32_t ps_crc32,
|
||||
bool ps_active);
|
||||
void CreatePipelineInternal(
|
||||
GraphicContext& graphics, DescriptorCache& descriptor_cache,
|
||||
PipelineCache::GraphicsPipeline& pipeline, const PipelineRenderingState& rendering,
|
||||
const ShaderVertexInputInfo& vs_input_info, std::span<const uint32_t> vs_shader,
|
||||
const ShaderPixelInputInfo* ps_input_info, std::span<const uint32_t> ps_shader,
|
||||
const PipelineStaticParameters& static_params, uint32_t vs_hash0, uint32_t vs_crc32,
|
||||
uint32_t ps_hash0, uint32_t ps_crc32, bool ps_active);
|
||||
void CreatePipelineInternal(GraphicContext& graphics, DescriptorCache& descriptor_cache,
|
||||
PipelineCache::ComputePipeline& pipeline,
|
||||
const ShaderComputeInputInfo& input_info,
|
||||
|
||||
@@ -8,12 +8,13 @@
|
||||
#include "graphics/host_gpu/renderer/debug.h"
|
||||
#include "graphics/host_gpu/renderer/pipeline/descriptorCache.h"
|
||||
#include "graphics/host_gpu/renderer/pipeline/pipelineCache.h"
|
||||
#include "graphics/host_gpu/renderer/pipeline/shaderSubgroup.h"
|
||||
#include "graphics/host_gpu/renderer/render.h"
|
||||
#include "graphics/host_gpu/renderer/renderContext.h"
|
||||
#include "graphics/host_gpu/renderer/renderTarget.h"
|
||||
#include "graphics/host_gpu/renderer/pipeline/shaderSubgroup.h"
|
||||
#include "graphics/host_gpu/vulkanCommon.h"
|
||||
#include "graphics/shader/recompiler/ir/ShaderIR.h"
|
||||
#include "graphics/shader/rectListShader.h"
|
||||
#include "graphics/shader/shader.h"
|
||||
|
||||
#include <algorithm>
|
||||
@@ -386,8 +387,7 @@ static vk::BlendOp GetBlendOp(uint32_t op) {
|
||||
}
|
||||
|
||||
static void CreateLayout(DescriptorCache& descriptor_cache,
|
||||
std::span<vk::DescriptorSetLayout> set_layouts,
|
||||
uint32_t& set_layouts_num,
|
||||
std::span<vk::DescriptorSetLayout> set_layouts, uint32_t& set_layouts_num,
|
||||
std::span<vk::PushConstantRange> push_constant_info,
|
||||
uint32_t& push_constant_info_num,
|
||||
const ShaderRecompiler::IR::Program& program,
|
||||
@@ -412,8 +412,7 @@ static void CreateLayout(DescriptorCache& descriptor_cache,
|
||||
}
|
||||
}
|
||||
|
||||
static void ConfigureSubgroupSize(const GraphicContext& graphics,
|
||||
vk::ShaderStageFlagBits vk_stage,
|
||||
static void ConfigureSubgroupSize(const GraphicContext& graphics, vk::ShaderStageFlagBits vk_stage,
|
||||
const ShaderRecompiler::IR::Program& program,
|
||||
vk::PipelineShaderStageRequiredSubgroupSizeCreateInfo& required,
|
||||
vk::PipelineShaderStageCreateInfo& stage) {
|
||||
@@ -456,19 +455,20 @@ static void ConfigureSubgroupSize(const GraphicContext&
|
||||
}
|
||||
|
||||
// NOLINTNEXTLINE(readability-function-cognitive-complexity)
|
||||
void CreatePipelineInternal(GraphicContext& graphics, DescriptorCache& descriptor_cache,
|
||||
PipelineCache::GraphicsPipeline& pipeline,
|
||||
const PipelineRenderingState& rendering,
|
||||
const ShaderVertexInputInfo& vs_input_info,
|
||||
std::span<const uint32_t> vs_shader,
|
||||
const ShaderPixelInputInfo* ps_input_info,
|
||||
std::span<const uint32_t> ps_shader,
|
||||
const PipelineStaticParameters& static_params, uint32_t vs_hash0,
|
||||
uint32_t vs_crc32, uint32_t ps_hash0, uint32_t ps_crc32,
|
||||
bool ps_active) {
|
||||
void CreatePipelineInternal(
|
||||
GraphicContext& graphics, DescriptorCache& descriptor_cache,
|
||||
PipelineCache::GraphicsPipeline& pipeline, const PipelineRenderingState& rendering,
|
||||
const ShaderVertexInputInfo& vs_input_info, std::span<const uint32_t> vs_shader,
|
||||
const ShaderPixelInputInfo* ps_input_info, std::span<const uint32_t> ps_shader,
|
||||
const PipelineStaticParameters& static_params, uint32_t vs_hash0, uint32_t vs_crc32,
|
||||
uint32_t ps_hash0, uint32_t ps_crc32, bool ps_active) {
|
||||
EXIT_IF(ps_active && ps_input_info == nullptr);
|
||||
|
||||
const bool rect_list = static_params.topology == vk::PrimitiveTopology::ePatchList;
|
||||
|
||||
vk::ShaderModule vert_shader_module = nullptr;
|
||||
vk::ShaderModule tess_control_shader_module = nullptr;
|
||||
vk::ShaderModule tess_eval_shader_module = nullptr;
|
||||
vk::ShaderModule frag_shader_module = nullptr;
|
||||
|
||||
vk::ShaderModuleCreateInfo create_info {};
|
||||
@@ -496,8 +496,33 @@ void CreatePipelineInternal(GraphicContext& graphics, DescriptorCache& descripto
|
||||
}
|
||||
EXIT_NOT_IMPLEMENTED(result != vk::Result::eSuccess);
|
||||
}
|
||||
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);
|
||||
if (graphics_debug_dump_enabled()) {
|
||||
LOGF("PipelineTrace: vkCreateShaderModule RectList TCS done result=%s module=%p\n",
|
||||
VulkanToString(result).c_str(), 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);
|
||||
if (graphics_debug_dump_enabled()) {
|
||||
LOGF("PipelineTrace: vkCreateShaderModule RectList TES done result=%s module=%p\n",
|
||||
VulkanToString(result).c_str(), static_cast<void*>(tess_eval_shader_module));
|
||||
}
|
||||
EXIT_NOT_IMPLEMENTED(result != vk::Result::eSuccess);
|
||||
}
|
||||
|
||||
EXIT_NOT_IMPLEMENTED(vert_shader_module == nullptr);
|
||||
EXIT_NOT_IMPLEMENTED(
|
||||
rect_list && (tess_control_shader_module == nullptr || tess_eval_shader_module == nullptr));
|
||||
EXIT_NOT_IMPLEMENTED(ps_active && frag_shader_module == nullptr);
|
||||
|
||||
vk::PipelineShaderStageCreateInfo vert_shader_stage_info {};
|
||||
@@ -511,8 +536,7 @@ void CreatePipelineInternal(GraphicContext& graphics, DescriptorCache& descripto
|
||||
vert_shader_stage_info.pName = "main";
|
||||
vert_shader_stage_info.pSpecializationInfo = nullptr;
|
||||
EXIT_IF(!vs_input_info.stage);
|
||||
ConfigureSubgroupSize(graphics, vk::ShaderStageFlagBits::eVertex,
|
||||
*vs_input_info.stage.program,
|
||||
ConfigureSubgroupSize(graphics, vk::ShaderStageFlagBits::eVertex, *vs_input_info.stage.program,
|
||||
vert_subgroup_size, vert_shader_stage_info);
|
||||
|
||||
vk::PipelineShaderStageCreateInfo frag_shader_stage_info {};
|
||||
@@ -527,13 +551,32 @@ void CreatePipelineInternal(GraphicContext& graphics, DescriptorCache& descripto
|
||||
if (ps_active) {
|
||||
EXIT_IF(!ps_input_info->stage);
|
||||
ConfigureSubgroupSize(graphics, vk::ShaderStageFlagBits::eFragment,
|
||||
*ps_input_info->stage.program,
|
||||
frag_subgroup_size, frag_shader_stage_info);
|
||||
*ps_input_info->stage.program, frag_subgroup_size,
|
||||
frag_shader_stage_info);
|
||||
}
|
||||
|
||||
vk::PipelineShaderStageCreateInfo shader_stages[] = {vert_shader_stage_info,
|
||||
frag_shader_stage_info};
|
||||
const uint32_t shader_stage_count = ps_active ? 2u : 1u;
|
||||
vk::PipelineShaderStageCreateInfo tess_control_shader_stage_info {};
|
||||
tess_control_shader_stage_info.sType = vk::StructureType::ePipelineShaderStageCreateInfo;
|
||||
tess_control_shader_stage_info.stage = vk::ShaderStageFlagBits::eTessellationControl;
|
||||
tess_control_shader_stage_info.module = tess_control_shader_module;
|
||||
tess_control_shader_stage_info.pName = "main";
|
||||
|
||||
vk::PipelineShaderStageCreateInfo tess_eval_shader_stage_info {};
|
||||
tess_eval_shader_stage_info.sType = vk::StructureType::ePipelineShaderStageCreateInfo;
|
||||
tess_eval_shader_stage_info.stage = vk::ShaderStageFlagBits::eTessellationEvaluation;
|
||||
tess_eval_shader_stage_info.module = tess_eval_shader_module;
|
||||
tess_eval_shader_stage_info.pName = "main";
|
||||
|
||||
vk::PipelineShaderStageCreateInfo shader_stages[4] = {};
|
||||
uint32_t shader_stage_count = 0;
|
||||
shader_stages[shader_stage_count++] = vert_shader_stage_info;
|
||||
if (rect_list) {
|
||||
shader_stages[shader_stage_count++] = tess_control_shader_stage_info;
|
||||
shader_stages[shader_stage_count++] = tess_eval_shader_stage_info;
|
||||
}
|
||||
if (ps_active) {
|
||||
shader_stages[shader_stage_count++] = frag_shader_stage_info;
|
||||
}
|
||||
|
||||
vk::VertexInputAttributeDescription input_attr[ShaderVertexInputInfo::RES_MAX];
|
||||
vk::VertexInputBindingDescription input_desc[ShaderVertexInputInfo::RES_MAX];
|
||||
@@ -838,15 +881,13 @@ void CreatePipelineInternal(GraphicContext& graphics, DescriptorCache& descripto
|
||||
|
||||
EXIT_IF(!vs_input_info.stage);
|
||||
CreateLayout(descriptor_cache, set_layouts, set_layouts_num, push_constant_info,
|
||||
push_constant_info_num,
|
||||
*vs_input_info.stage.program, vk::ShaderStageFlagBits::eVertex,
|
||||
DescriptorCache::Stage::Vertex);
|
||||
push_constant_info_num, *vs_input_info.stage.program,
|
||||
vk::ShaderStageFlagBits::eVertex, DescriptorCache::Stage::Vertex);
|
||||
if (ps_active) {
|
||||
EXIT_IF(!ps_input_info->stage);
|
||||
CreateLayout(descriptor_cache, set_layouts, set_layouts_num, push_constant_info,
|
||||
push_constant_info_num,
|
||||
*ps_input_info->stage.program, vk::ShaderStageFlagBits::eFragment,
|
||||
DescriptorCache::Stage::Pixel);
|
||||
push_constant_info_num, *ps_input_info->stage.program,
|
||||
vk::ShaderStageFlagBits::eFragment, DescriptorCache::Stage::Pixel);
|
||||
}
|
||||
|
||||
vk::PipelineLayoutCreateInfo pipeline_layout_info {};
|
||||
@@ -937,7 +978,10 @@ void CreatePipelineInternal(GraphicContext& graphics, DescriptorCache& descripto
|
||||
pipeline_info.pStages = shader_stages;
|
||||
pipeline_info.pVertexInputState = &vertex_input_info;
|
||||
pipeline_info.pInputAssemblyState = &input_assembly;
|
||||
pipeline_info.pTessellationState = nullptr;
|
||||
vk::PipelineTessellationStateCreateInfo tessellation_state {};
|
||||
tessellation_state.sType = vk::StructureType::ePipelineTessellationStateCreateInfo;
|
||||
tessellation_state.patchControlPoints = 3;
|
||||
pipeline_info.pTessellationState = (rect_list ? &tessellation_state : nullptr);
|
||||
pipeline_info.pViewportState = &viewport_state;
|
||||
pipeline_info.pRasterizationState = &rasterizer;
|
||||
pipeline_info.pMultisampleState = &multisampling;
|
||||
@@ -976,6 +1020,12 @@ void CreatePipelineInternal(GraphicContext& graphics, DescriptorCache& descripto
|
||||
if (frag_shader_module != nullptr) {
|
||||
graphics.device.destroyShaderModule(frag_shader_module, nullptr);
|
||||
}
|
||||
if (tess_control_shader_module != nullptr) {
|
||||
graphics.device.destroyShaderModule(tess_control_shader_module, nullptr);
|
||||
}
|
||||
if (tess_eval_shader_module != nullptr) {
|
||||
graphics.device.destroyShaderModule(tess_eval_shader_module, nullptr);
|
||||
}
|
||||
graphics.device.destroyShaderModule(vert_shader_module, nullptr);
|
||||
}
|
||||
|
||||
@@ -1012,8 +1062,7 @@ void CreatePipelineInternal(GraphicContext& graphics, DescriptorCache& descripto
|
||||
comp_shader_stage_info.pName = "main";
|
||||
comp_shader_stage_info.pSpecializationInfo = nullptr;
|
||||
EXIT_IF(!input_info.stage);
|
||||
ConfigureSubgroupSize(graphics, vk::ShaderStageFlagBits::eCompute,
|
||||
*input_info.stage.program,
|
||||
ConfigureSubgroupSize(graphics, vk::ShaderStageFlagBits::eCompute, *input_info.stage.program,
|
||||
comp_subgroup_size, comp_shader_stage_info);
|
||||
|
||||
vk::DescriptorSetLayout set_layouts[1] = {};
|
||||
@@ -1024,9 +1073,8 @@ void CreatePipelineInternal(GraphicContext& graphics, DescriptorCache& descripto
|
||||
|
||||
EXIT_IF(!input_info.stage);
|
||||
CreateLayout(descriptor_cache, set_layouts, set_layouts_num, push_constant_info,
|
||||
push_constant_info_num,
|
||||
*input_info.stage.program, vk::ShaderStageFlagBits::eCompute,
|
||||
DescriptorCache::Stage::Compute);
|
||||
push_constant_info_num, *input_info.stage.program,
|
||||
vk::ShaderStageFlagBits::eCompute, DescriptorCache::Stage::Compute);
|
||||
|
||||
vk::PipelineLayoutCreateInfo pipeline_layout_info {};
|
||||
pipeline_layout_info.sType = vk::StructureType::ePipelineLayoutCreateInfo;
|
||||
|
||||
@@ -10,14 +10,14 @@
|
||||
#include "graphics/guest_gpu/graphicsRun.h"
|
||||
#include "graphics/guest_gpu/hardwareContext.h"
|
||||
#include "graphics/host_gpu/graphicContext.h"
|
||||
#include "graphics/host_gpu/renderer/image/imageInfo.h"
|
||||
#include "graphics/host_gpu/renderer/pipeline/descriptorCache.h"
|
||||
#include "graphics/host_gpu/renderer/pipeline/descriptors.h"
|
||||
#include "graphics/host_gpu/renderer/image/imageInfo.h"
|
||||
#include "graphics/host_gpu/renderer/pipeline/pipelineCache.h"
|
||||
#include "graphics/host_gpu/renderer/render.h"
|
||||
#include "graphics/host_gpu/renderer/renderContext.h"
|
||||
#include "graphics/host_gpu/renderer/pipeline/shaderResourceBarrier.h"
|
||||
#include "graphics/host_gpu/renderer/pipeline/shaderSubgroup.h"
|
||||
#include "graphics/host_gpu/renderer/render.h"
|
||||
#include "graphics/host_gpu/renderer/renderContext.h"
|
||||
#include "graphics/host_gpu/vulkanCommon.h"
|
||||
#include "graphics/shader/recompiler/ir/ResourceMaterialization.h"
|
||||
#include "graphics/shader/recompiler/ir/ShaderIR.h"
|
||||
|
||||
@@ -14,8 +14,7 @@ namespace Libs::Graphics {
|
||||
RenderContext::RenderContext(GraphicContext& graphics)
|
||||
: m_graphics(graphics), m_render_executor(*this), m_command_scheduler(*this, graphics),
|
||||
m_descriptor_cache(graphics), m_pipeline_cache(graphics, m_descriptor_cache),
|
||||
m_sampler_cache(graphics),
|
||||
m_gpu_resources(graphics, m_command_scheduler) {
|
||||
m_sampler_cache(graphics), m_gpu_resources(graphics, m_command_scheduler) {
|
||||
EXIT_NOT_IMPLEMENTED(!Common::Thread::IsMainThread());
|
||||
}
|
||||
|
||||
@@ -99,8 +98,7 @@ void RenderContext::TriggerEopEvent(uint32_t context_id) {
|
||||
registration.eq, static_cast<uintptr_t>(registration.id),
|
||||
LibKernel::EventQueue::KERNEL_EVFILT_GRAPHICS,
|
||||
reinterpret_cast<void*>(static_cast<uintptr_t>(context_id)));
|
||||
if (result == LibKernel::KERNEL_ERROR_EBADF ||
|
||||
result == LibKernel::KERNEL_ERROR_ENOENT) {
|
||||
if (result == LibKernel::KERNEL_ERROR_EBADF || result == LibKernel::KERNEL_ERROR_ENOENT) {
|
||||
DeleteEopEq(registration.eq, registration.id);
|
||||
continue;
|
||||
}
|
||||
|
||||
@@ -6,12 +6,12 @@
|
||||
#include "common/common.h"
|
||||
#include "common/threads.h"
|
||||
#include "graphics/host_gpu/renderer/cache/bufferCache.h"
|
||||
#include "graphics/host_gpu/renderer/commandScheduler.h"
|
||||
#include "graphics/host_gpu/renderer/pipeline/descriptorCache.h"
|
||||
#include "graphics/host_gpu/renderer/cache/gpuResourceManager.h"
|
||||
#include "graphics/host_gpu/renderer/pipeline/pipelineCache.h"
|
||||
#include "graphics/host_gpu/renderer/cache/samplerCache.h"
|
||||
#include "graphics/host_gpu/renderer/cache/textureCache.h"
|
||||
#include "graphics/host_gpu/renderer/commandScheduler.h"
|
||||
#include "graphics/host_gpu/renderer/pipeline/descriptorCache.h"
|
||||
#include "graphics/host_gpu/renderer/pipeline/pipelineCache.h"
|
||||
#include "kernel/eventQueue.h"
|
||||
|
||||
#include <memory>
|
||||
|
||||
@@ -2,7 +2,6 @@
|
||||
|
||||
#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"
|
||||
@@ -651,8 +650,6 @@ static bool ConsumeMetadataColorOperation(const RenderCommandBuffer& buffer) {
|
||||
|
||||
struct DrawEmitInfo {
|
||||
bool indexed = false;
|
||||
bool draw_prim7_as_ngg = false;
|
||||
uint32_t draw_vertex_count = 0;
|
||||
int32_t vertex_offset = 0;
|
||||
uint32_t first_vertex = 0;
|
||||
};
|
||||
@@ -767,7 +764,7 @@ static void SetDrawDebugPhase(RenderCommandBuffer& buffer, uint64_t submit_id,
|
||||
draw.flags, draw.instance_count, draw.first_instance);
|
||||
}
|
||||
|
||||
static bool GetDrawTopology(const HW::UserConfig& ucfg, bool auto_draw, bool use_ngg_rectlist_draw,
|
||||
static bool GetDrawTopology(const HW::UserConfig& ucfg, bool auto_draw,
|
||||
vk::PrimitiveTopology& topology) {
|
||||
|
||||
topology = vk::PrimitiveTopology::ePointList;
|
||||
@@ -791,8 +788,7 @@ static bool GetDrawTopology(const HW::UserConfig& ucfg, bool auto_draw, bool use
|
||||
topology = vk::PrimitiveTopology::eTriangleStrip;
|
||||
break;
|
||||
case Prospero::PrimitiveType::kRectList:
|
||||
topology = (auto_draw && use_ngg_rectlist_draw ? vk::PrimitiveTopology::eTriangleStrip
|
||||
: vk::PrimitiveTopology::eTriangleList);
|
||||
topology = vk::PrimitiveTopology::ePatchList;
|
||||
break;
|
||||
case Prospero::PrimitiveType::kRectListLegacy:
|
||||
if (!auto_draw) {
|
||||
@@ -991,20 +987,6 @@ static void LogDrawStateIfNeeded(const RenderCommandBuffer& buffer, const DrawCa
|
||||
// LogDrawTextureState(draw.name, state.color_info[0], state.ps_input_info);
|
||||
}
|
||||
|
||||
static bool IsHostExpandedRectListDrawSupported(const ShaderVertexInputInfo& vs_input_info,
|
||||
const DrawCallInfo& draw,
|
||||
const DrawEmitInfo& emit) {
|
||||
if (!emit.draw_prim7_as_ngg) {
|
||||
return true;
|
||||
}
|
||||
|
||||
if (vs_input_info.buffers_num != 0) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return draw.index_count == 3 || draw.index_count == emit.draw_vertex_count;
|
||||
}
|
||||
|
||||
static void EmitDrawPrimitives(const HW::UserConfig& ucfg, vk::CommandBuffer vk_buffer,
|
||||
const ShaderVertexInputInfo& vs_input_info, const DrawCallInfo& draw,
|
||||
const DrawEmitInfo& emit) {
|
||||
@@ -1017,22 +999,12 @@ static void EmitDrawPrimitives(const HW::UserConfig& ucfg, vk::CommandBuffer vk_
|
||||
case Prospero::PrimitiveType::kTriList:
|
||||
case Prospero::PrimitiveType::kTriFan:
|
||||
case Prospero::PrimitiveType::kTriStrip:
|
||||
if (emit.indexed) {
|
||||
vk_buffer.drawIndexed(draw.index_count, draw.instance_count, 0, emit.vertex_offset,
|
||||
draw.first_instance);
|
||||
} else {
|
||||
vk_buffer.draw(draw.index_count, draw.instance_count, emit.first_vertex,
|
||||
draw.first_instance);
|
||||
}
|
||||
break;
|
||||
case Prospero::PrimitiveType::kRectList:
|
||||
if (emit.indexed) {
|
||||
vk_buffer.drawIndexed(draw.index_count, draw.instance_count, 0, emit.vertex_offset,
|
||||
draw.first_instance);
|
||||
} else {
|
||||
EXIT_NOT_IMPLEMENTED(
|
||||
!IsHostExpandedRectListDrawSupported(vs_input_info, draw, emit));
|
||||
vk_buffer.draw(emit.draw_vertex_count, draw.instance_count, emit.first_vertex,
|
||||
vk_buffer.draw(draw.index_count, draw.instance_count, emit.first_vertex,
|
||||
draw.first_instance);
|
||||
}
|
||||
break;
|
||||
@@ -1159,7 +1131,7 @@ void RenderExecutor::DrawIndex(uint64_t submit_id, RenderCommandBuffer& buffer,
|
||||
reinterpret_cast<uint64_t>(index_addr));
|
||||
|
||||
Common::LockGuard lock(m_context.GetMutex());
|
||||
if (index_count == 0) {
|
||||
if (index_count == 0 || instance_count == 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -1201,7 +1173,7 @@ void RenderExecutor::DrawIndex(uint64_t submit_id, RenderCommandBuffer& buffer,
|
||||
hw_check(buffer);
|
||||
|
||||
vk::PrimitiveTopology topology = vk::PrimitiveTopology::ePointList;
|
||||
if (!GetDrawTopology(ucfg, false, false, topology)) {
|
||||
if (!GetDrawTopology(ucfg, false, topology)) {
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -1229,10 +1201,6 @@ void RenderExecutor::DrawIndex(uint64_t submit_id, RenderCommandBuffer& buffer,
|
||||
|
||||
EXIT_NOT_IMPLEMENTED(flags != 0);
|
||||
EXIT_NOT_IMPLEMENTED(type != 1);
|
||||
if (instance_count == 0) {
|
||||
instance_count = 1;
|
||||
}
|
||||
|
||||
const DrawCallInfo draw {"DrawIndex", CommandBufferDebugOp::DrawIndex,
|
||||
index_count, flags,
|
||||
instance_count, first_instance};
|
||||
@@ -1292,7 +1260,7 @@ void RenderExecutor::DrawAuto(uint64_t submit_id, RenderCommandBuffer& buffer, u
|
||||
index_count, flags, first_vertex, instance_count, first_instance);
|
||||
|
||||
Common::LockGuard lock(m_context.GetMutex());
|
||||
if (index_count == 0) {
|
||||
if (index_count == 0 || instance_count == 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -1330,10 +1298,6 @@ void RenderExecutor::DrawAuto(uint64_t submit_id, RenderCommandBuffer& buffer, u
|
||||
hw_check(buffer);
|
||||
|
||||
EXIT_NOT_IMPLEMENTED(flags != 0);
|
||||
if (instance_count == 0) {
|
||||
instance_count = 1;
|
||||
}
|
||||
|
||||
const DrawCallInfo draw {"DrawIndexAuto", CommandBufferDebugOp::DrawIndexAuto,
|
||||
index_count, flags,
|
||||
instance_count, first_instance};
|
||||
@@ -1346,19 +1310,14 @@ void RenderExecutor::DrawAuto(uint64_t submit_id, RenderCommandBuffer& buffer, u
|
||||
}
|
||||
|
||||
vk::PrimitiveTopology topology = vk::PrimitiveTopology::ePointList;
|
||||
const bool use_ngg_rectlist_draw = Config::NggRectlistDrawEnabled();
|
||||
|
||||
if (!GetDrawTopology(ucfg, true, use_ngg_rectlist_draw, topology)) {
|
||||
if (!GetDrawTopology(ucfg, true, topology)) {
|
||||
ResetBindings();
|
||||
return;
|
||||
}
|
||||
const bool draw_prim7_as_ngg =
|
||||
(use_ngg_rectlist_draw &&
|
||||
ucfg.GetPrimType() == Prospero::GpuEnumValue(Prospero::PrimitiveType::kRectList));
|
||||
|
||||
RefreshShaders(buffer, draw, false, state);
|
||||
|
||||
if (draw_prim7_as_ngg && state.vs_input_info.buffers_num == 0 &&
|
||||
const bool rect_list = topology == vk::PrimitiveTopology::ePatchList;
|
||||
if (rect_list && state.vs_input_info.buffers_num == 0 &&
|
||||
state.vs_input_info.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: "
|
||||
@@ -1375,12 +1334,9 @@ void RenderExecutor::DrawAuto(uint64_t submit_id, RenderCommandBuffer& buffer, u
|
||||
Prospero::GpuEnumValue(Prospero::PrimitiveType::kRectListLegacy),
|
||||
0, nullptr);
|
||||
|
||||
const uint32_t draw_vertex_count = (draw_prim7_as_ngg ? 4u : index_count);
|
||||
const auto vertex_offset = ResolveVertexOffset(ucfg.GetIndexOffset(), state.vs_input_info) +
|
||||
static_cast<int32_t>(first_vertex);
|
||||
DrawEmitInfo emit {};
|
||||
emit.draw_prim7_as_ngg = draw_prim7_as_ngg;
|
||||
emit.draw_vertex_count = draw_vertex_count;
|
||||
emit.first_vertex = static_cast<uint32_t>(vertex_offset);
|
||||
|
||||
DrawIndexBufferSource index_source {};
|
||||
|
||||
@@ -252,8 +252,8 @@ uint64_t PrepareVideoOutFlip(CommandBuffer& buffer, int handle, int index, int f
|
||||
for (;;) {
|
||||
uint64_t request_id = 0;
|
||||
auto& video_out = buffer.GetContext().GetVideoOut();
|
||||
const auto result = video_out.SubmitFlipFromGpu(
|
||||
buffer, handle, index, flip_mode, flip_arg, request_id);
|
||||
const auto result =
|
||||
video_out.SubmitFlipFromGpu(buffer, handle, index, flip_mode, flip_arg, request_id);
|
||||
if (result == OK) {
|
||||
EXIT_IF(request_id == 0);
|
||||
return request_id;
|
||||
|
||||
@@ -122,8 +122,8 @@ uint64_t GraphicContext::GetDeviceMemoryUsage() const {
|
||||
physical_device_properties.deviceType == vk::PhysicalDeviceType::eDiscreteGpu;
|
||||
uint64_t usage = 0;
|
||||
for (uint32_t heap = 0; heap < physical_device_memory_properties.memoryHeapCount; heap++) {
|
||||
const bool device_local = static_cast<bool>(
|
||||
physical_device_memory_properties.memoryHeaps[heap].flags &
|
||||
const bool device_local =
|
||||
static_cast<bool>(physical_device_memory_properties.memoryHeaps[heap].flags &
|
||||
vk::MemoryHeapFlagBits::eDeviceLocal);
|
||||
if (!discrete || device_local) {
|
||||
usage += budgets[heap].usage;
|
||||
@@ -160,8 +160,7 @@ uint64_t GraphicContext::GetTotalMemoryBudget() const {
|
||||
}
|
||||
constexpr uint64_t system_reserve = 8ull * 1024 * 1024 * 1024;
|
||||
const auto available = budget > usage ? budget - usage : uint64_t {0};
|
||||
return std::max(local,
|
||||
available > system_reserve ? available - system_reserve : uint64_t {0});
|
||||
return std::max(local, available > system_reserve ? available - system_reserve : uint64_t {0});
|
||||
}
|
||||
|
||||
void GraphicContext::CreateBuffer(uint64_t size, VulkanBuffer& buffer) {
|
||||
|
||||
@@ -37,6 +37,7 @@ constexpr FormatMapping kFormatMappings[] = {
|
||||
{Prospero::BufferFormat::k16_16Float, vk::Format::eR16G16Sfloat},
|
||||
{Prospero::BufferFormat::k11_11_10Float, vk::Format::eB10G11R11UfloatPack32},
|
||||
{Prospero::BufferFormat::k10_10_10_2UNorm, vk::Format::eA2B10G10R10UnormPack32},
|
||||
{Prospero::BufferFormat::k10_10_10_2UInt, vk::Format::eA2B10G10R10UintPack32},
|
||||
{Prospero::BufferFormat::k8_8_8_8UNorm, vk::Format::eR8G8B8A8Unorm},
|
||||
{Prospero::BufferFormat::k8_8_8_8SNorm, vk::Format::eR8G8B8A8Snorm},
|
||||
{Prospero::BufferFormat::k8_8_8_8UInt, vk::Format::eR8G8B8A8Uint},
|
||||
@@ -55,6 +56,10 @@ constexpr FormatMapping kFormatMappings[] = {
|
||||
{Prospero::BufferFormat::k32_32_32_32UInt, vk::Format::eR32G32B32A32Uint},
|
||||
{Prospero::BufferFormat::k32_32_32_32SInt, vk::Format::eR32G32B32A32Sint},
|
||||
{Prospero::BufferFormat::k32_32_32_32Float, vk::Format::eR32G32B32A32Sfloat},
|
||||
// Narrow-channel sRGB formats are optional in Vulkan. Keep a same-width fallback until
|
||||
// sampler-aware sRGB emulation is available.
|
||||
{Prospero::BufferFormat::k8Srgb, vk::Format::eR8Unorm},
|
||||
{Prospero::BufferFormat::k8_8Srgb, vk::Format::eR8G8Unorm},
|
||||
{Prospero::BufferFormat::k8_8_8_8Srgb, vk::Format::eR8G8B8A8Srgb},
|
||||
{Prospero::BufferFormat::k9_9_9_5Float, vk::Format::eE5B9G9R9UfloatPack32},
|
||||
{Prospero::BufferFormat::k5_6_5UNorm, vk::Format::eB5G6R5UnormPack16},
|
||||
|
||||
@@ -359,9 +359,9 @@ static void TriggerVideoOutEvents(VideoOutConfig& video_out, VideoOutEventKind k
|
||||
if (!registration || registration->generation != video_out.generation) {
|
||||
continue;
|
||||
}
|
||||
const auto result = EventQueue::KernelTriggerEvent(
|
||||
registration->handle, VideoOutEventId(kind), EventQueue::KERNEL_EVFILT_VIDEO_OUT,
|
||||
trigger_data);
|
||||
const auto result =
|
||||
EventQueue::KernelTriggerEvent(registration->handle, VideoOutEventId(kind),
|
||||
EventQueue::KERNEL_EVFILT_VIDEO_OUT, trigger_data);
|
||||
EXIT_NOT_IMPLEMENTED(result != OK && result != LibKernel::KERNEL_ERROR_EBADF &&
|
||||
result != LibKernel::KERNEL_ERROR_ENOENT);
|
||||
}
|
||||
@@ -372,9 +372,8 @@ static void DeleteVideoOutEvents(const VideoOutEventQueues& queues, VideoOutEven
|
||||
if (!registration) {
|
||||
continue;
|
||||
}
|
||||
const auto result =
|
||||
EventQueue::KernelDeleteEvent(registration->handle, VideoOutEventId(kind),
|
||||
EventQueue::KERNEL_EVFILT_VIDEO_OUT);
|
||||
const auto result = EventQueue::KernelDeleteEvent(
|
||||
registration->handle, VideoOutEventId(kind), EventQueue::KERNEL_EVFILT_VIDEO_OUT);
|
||||
EXIT_NOT_IMPLEMENTED(result != OK && result != LibKernel::KERNEL_ERROR_EBADF &&
|
||||
result != LibKernel::KERNEL_ERROR_ENOENT);
|
||||
}
|
||||
@@ -425,17 +424,15 @@ static int RegisterVideoOutEvent(int handle, EventQueue::KernelEqueue eq, VideoO
|
||||
bool add_queue = false;
|
||||
{
|
||||
Common::LockGuard event_lock(event_state->mutex);
|
||||
const auto existing = std::find_if(queues.begin(), queues.end(), [&](const auto& candidate) {
|
||||
const auto existing =
|
||||
std::find_if(queues.begin(), queues.end(), [&](const auto& candidate) {
|
||||
return candidate->handle == eq && candidate->generation == generation;
|
||||
});
|
||||
if (existing != queues.end()) {
|
||||
registration = *existing;
|
||||
} else {
|
||||
registration = std::make_shared<VideoOutEventRegistration>(
|
||||
VideoOutEventRegistration {.handle = eq,
|
||||
.state = event_state,
|
||||
.generation = generation,
|
||||
.kind = kind});
|
||||
registration = std::make_shared<VideoOutEventRegistration>(VideoOutEventRegistration {
|
||||
.handle = eq, .state = event_state, .generation = generation, .kind = kind});
|
||||
queues.push_back(registration);
|
||||
add_queue = true;
|
||||
}
|
||||
@@ -814,8 +811,8 @@ void VideoOutDriver::Impl::PresentThread(std::stop_token token) {
|
||||
m_presenter.Present(*frame, true);
|
||||
}
|
||||
const auto frame_end = Common::Timer::QueryPerformanceCounter();
|
||||
total_wait += static_cast<int64_t>(period) -
|
||||
static_cast<int64_t>(frame_end - frame_begin);
|
||||
total_wait +=
|
||||
static_cast<int64_t>(period) - static_cast<int64_t>(frame_end - frame_begin);
|
||||
continue;
|
||||
}
|
||||
|
||||
@@ -841,8 +838,7 @@ void VideoOutDriver::Impl::PresentThread(std::stop_token token) {
|
||||
VblankEnd();
|
||||
|
||||
const auto frame_end = Common::Timer::QueryPerformanceCounter();
|
||||
total_wait += static_cast<int64_t>(period) -
|
||||
static_cast<int64_t>(frame_end - frame_begin);
|
||||
total_wait += static_cast<int64_t>(period) - static_cast<int64_t>(frame_end - frame_begin);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1000,8 +996,8 @@ void FlipQueue::Prepare(uint64_t request_id, Graphics::CommandBuffer& buffer) {
|
||||
}
|
||||
Graphics::Presenter::Frame* frame = nullptr;
|
||||
if (special) {
|
||||
frame = &m_presenter.PrepareBlankFrame(width, height,
|
||||
index == VIDEO_OUT_BUFFER_INDEX_BLACK, &buffer);
|
||||
frame = &m_presenter.PrepareBlankFrame(width, height, index == VIDEO_OUT_BUFFER_INDEX_BLACK,
|
||||
&buffer);
|
||||
} else {
|
||||
frame = &m_presenter.PrepareFrame(buffer, source_info);
|
||||
}
|
||||
|
||||
@@ -206,21 +206,18 @@ private:
|
||||
vk::Format m_format = vk::Format::eUndefined;
|
||||
};
|
||||
|
||||
void Presenter::Frame::Configure(GraphicContext& graphics, vk::Extent2D extent,
|
||||
vk::Format format) {
|
||||
void Presenter::Frame::Configure(GraphicContext& graphics, vk::Extent2D extent, vk::Format format) {
|
||||
if (extent.width == 0 || extent.height == 0 || format == vk::Format::eUndefined) {
|
||||
EXIT("unsupported prepared frame, extent=%ux%u format=%d\n", extent.width, extent.height,
|
||||
static_cast<int>(format));
|
||||
}
|
||||
const auto features = graphics.GetFormatProperties(format).optimalTilingFeatures;
|
||||
const auto required = vk::FormatFeatureFlagBits::eBlitSrc |
|
||||
vk::FormatFeatureFlagBits::eSampledImageFilterLinear |
|
||||
vk::FormatFeatureFlagBits::eTransferSrc |
|
||||
vk::FormatFeatureFlagBits::eTransferDst;
|
||||
const auto required =
|
||||
vk::FormatFeatureFlagBits::eBlitSrc | vk::FormatFeatureFlagBits::eSampledImageFilterLinear |
|
||||
vk::FormatFeatureFlagBits::eTransferSrc | vk::FormatFeatureFlagBits::eTransferDst;
|
||||
if ((features & required) != required) {
|
||||
EXIT("prepared presentation format lacks optimal blit support: format=%d features=0x%x\n",
|
||||
static_cast<int>(format),
|
||||
static_cast<vk::FormatFeatureFlags::MaskType>(features));
|
||||
static_cast<int>(format), static_cast<vk::FormatFeatureFlags::MaskType>(features));
|
||||
}
|
||||
|
||||
auto& dst = image;
|
||||
@@ -267,8 +264,7 @@ void Presenter::Frame::Transit(vk::CommandBuffer command, vk::ImageLayout layout
|
||||
? vk::PipelineStageFlagBits2::eTransfer
|
||||
: vk::PipelineStageFlagBits2::eAllCommands;
|
||||
constexpr auto writes = vk::AccessFlagBits2::eTransferWrite |
|
||||
vk::AccessFlagBits2::eShaderWrite |
|
||||
vk::AccessFlagBits2::eMemoryWrite;
|
||||
vk::AccessFlagBits2::eShaderWrite | vk::AccessFlagBits2::eMemoryWrite;
|
||||
if (image.state.layout == layout && image.state.access_mask == access &&
|
||||
!static_cast<bool>(image.state.access_mask & writes)) {
|
||||
return;
|
||||
@@ -299,35 +295,27 @@ void Presenter::Frame::Transit(vk::CommandBuffer command, vk::ImageLayout layout
|
||||
void Presenter::Frame::CopyFrom(CommandBuffer& command_buffer, Image& source) {
|
||||
command_buffer.EndRendering();
|
||||
auto command = command_buffer.Handle();
|
||||
source.Transit(vk::ImageLayout::eTransferSrcOptimal,
|
||||
vk::AccessFlagBits2::eTransferRead, {}, command);
|
||||
Transit(command, vk::ImageLayout::eTransferDstOptimal,
|
||||
vk::AccessFlagBits2::eTransferWrite);
|
||||
source.Transit(vk::ImageLayout::eTransferSrcOptimal, vk::AccessFlagBits2::eTransferRead, {},
|
||||
command);
|
||||
Transit(command, vk::ImageLayout::eTransferDstOptimal, vk::AccessFlagBits2::eTransferWrite);
|
||||
vk::ImageCopy copy {};
|
||||
copy.srcSubresource = {vk::ImageAspectFlagBits::eColor, 0, 0,
|
||||
source.backing.layers};
|
||||
copy.srcSubresource = {vk::ImageAspectFlagBits::eColor, 0, 0, source.backing.layers};
|
||||
copy.dstSubresource = {vk::ImageAspectFlagBits::eColor, 0, 0, image.layers};
|
||||
copy.extent = {std::min(source.backing.extent.width, image.extent.width),
|
||||
std::min(source.backing.extent.height, image.extent.height), 1};
|
||||
EXIT_IF(copy.srcSubresource.layerCount != copy.dstSubresource.layerCount);
|
||||
command.copyImage(source.backing.image, vk::ImageLayout::eTransferSrcOptimal,
|
||||
image.image, vk::ImageLayout::eTransferDstOptimal, copy);
|
||||
Transit(command, vk::ImageLayout::eTransferSrcOptimal,
|
||||
vk::AccessFlagBits2::eTransferRead);
|
||||
command.copyImage(source.backing.image, vk::ImageLayout::eTransferSrcOptimal, image.image,
|
||||
vk::ImageLayout::eTransferDstOptimal, copy);
|
||||
Transit(command, vk::ImageLayout::eTransferSrcOptimal, vk::AccessFlagBits2::eTransferRead);
|
||||
}
|
||||
|
||||
void Presenter::Frame::Clear(CommandBuffer& command_buffer,
|
||||
const vk::ClearColorValue& color) {
|
||||
void Presenter::Frame::Clear(CommandBuffer& command_buffer, const vk::ClearColorValue& color) {
|
||||
command_buffer.EndRendering();
|
||||
auto command = command_buffer.Handle();
|
||||
Transit(command, vk::ImageLayout::eTransferDstOptimal,
|
||||
vk::AccessFlagBits2::eTransferWrite);
|
||||
const vk::ImageSubresourceRange range {
|
||||
vk::ImageAspectFlagBits::eColor, 0, 1, 0, 1};
|
||||
command.clearColorImage(image.image, vk::ImageLayout::eTransferDstOptimal, &color, 1,
|
||||
&range);
|
||||
Transit(command, vk::ImageLayout::eTransferSrcOptimal,
|
||||
vk::AccessFlagBits2::eTransferRead);
|
||||
Transit(command, vk::ImageLayout::eTransferDstOptimal, vk::AccessFlagBits2::eTransferWrite);
|
||||
const vk::ImageSubresourceRange range {vk::ImageAspectFlagBits::eColor, 0, 1, 0, 1};
|
||||
command.clearColorImage(image.image, vk::ImageLayout::eTransferDstOptimal, &color, 1, &range);
|
||||
Transit(command, vk::ImageLayout::eTransferSrcOptimal, vk::AccessFlagBits2::eTransferRead);
|
||||
}
|
||||
|
||||
class Swapchain final {
|
||||
@@ -441,16 +429,12 @@ void Swapchain::Create() {
|
||||
? vk::CompositeAlphaFlagBitsKHR::eOpaque
|
||||
: vk::CompositeAlphaFlagBitsKHR::eInherit;
|
||||
|
||||
vk::SurfaceFormatKHR format {vk::Format::eR8G8B8A8Unorm,
|
||||
vk::ColorSpaceKHR::eSrgbNonlinear};
|
||||
if (surface.formats.size() != 1 ||
|
||||
surface.formats.front().format != vk::Format::eUndefined) {
|
||||
vk::SurfaceFormatKHR format {vk::Format::eR8G8B8A8Unorm, vk::ColorSpaceKHR::eSrgbNonlinear};
|
||||
if (surface.formats.size() != 1 || surface.formats.front().format != vk::Format::eUndefined) {
|
||||
const auto it = std::find_if(surface.formats.begin(), surface.formats.end(),
|
||||
[](const vk::SurfaceFormatKHR& candidate) {
|
||||
return candidate.format ==
|
||||
vk::Format::eB8G8R8A8Unorm ||
|
||||
candidate.format ==
|
||||
vk::Format::eR8G8B8A8Unorm;
|
||||
return candidate.format == vk::Format::eB8G8R8A8Unorm ||
|
||||
candidate.format == vk::Format::eR8G8B8A8Unorm;
|
||||
});
|
||||
if (it == surface.formats.end()) {
|
||||
EXIT("no supported UNORM swapchain format\n");
|
||||
@@ -458,8 +442,7 @@ void Swapchain::Create() {
|
||||
format = *it;
|
||||
}
|
||||
m_format = format.format;
|
||||
const auto swapchain_features =
|
||||
graphics.GetFormatProperties(m_format).optimalTilingFeatures;
|
||||
const auto swapchain_features = graphics.GetFormatProperties(m_format).optimalTilingFeatures;
|
||||
if (!static_cast<bool>(swapchain_features & vk::FormatFeatureFlagBits::eBlitDst)) {
|
||||
EXIT("swapchain format cannot be a blit destination: format=%d\n",
|
||||
static_cast<int>(m_format));
|
||||
@@ -503,8 +486,7 @@ void Swapchain::Create() {
|
||||
view.subresourceRange.baseMipLevel = 0;
|
||||
view.subresourceRange.layerCount = 1;
|
||||
view.subresourceRange.levelCount = 1;
|
||||
RequireVulkanSuccess(
|
||||
graphics.device.createImageView(&view, nullptr, &m_image_views[i]),
|
||||
RequireVulkanSuccess(graphics.device.createImageView(&view, nullptr, &m_image_views[i]),
|
||||
"vkCreateImageView");
|
||||
EXIT_IF(m_image_views[i] == nullptr);
|
||||
}
|
||||
@@ -683,10 +665,9 @@ void Swapchain::RecordPresentCommands(CommandBuffer& command, VulkanImage& sourc
|
||||
to_present.subresourceRange.levelCount = 1;
|
||||
to_present.subresourceRange.baseArrayLayer = 0;
|
||||
to_present.subresourceRange.layerCount = 1;
|
||||
vk_command.pipelineBarrier(vk::PipelineStageFlagBits::eAllCommands,
|
||||
vk::PipelineStageFlagBits::eAllCommands,
|
||||
vk::DependencyFlagBits::eByRegion, 0,
|
||||
nullptr, 0, nullptr, 1, &to_present);
|
||||
vk_command.pipelineBarrier(
|
||||
vk::PipelineStageFlagBits::eAllCommands, vk::PipelineStageFlagBits::eAllCommands,
|
||||
vk::DependencyFlagBits::eByRegion, 0, nullptr, 0, nullptr, 1, &to_present);
|
||||
command.End();
|
||||
}
|
||||
|
||||
@@ -752,8 +733,7 @@ Presenter::Frame& Presenter::PrepareFrame(CommandBuffer& buffer, const ImageInfo
|
||||
default: break;
|
||||
}
|
||||
frame->Configure(m_impl->window.graphic_ctx,
|
||||
{image.backing.extent.width, image.backing.extent.height},
|
||||
frame_format);
|
||||
{image.backing.extent.width, image.backing.extent.height}, frame_format);
|
||||
frame->CopyFrom(buffer, image);
|
||||
return *frame;
|
||||
}
|
||||
@@ -772,8 +752,7 @@ Presenter::Frame& Presenter::PrepareBlankFrame(uint32_t width, uint32_t height,
|
||||
frame->Clear(*producer, clear);
|
||||
} else {
|
||||
if (frame->present_commands == nullptr) {
|
||||
frame->present_commands =
|
||||
std::make_unique<CommandBuffer>(m_impl->present_scheduler);
|
||||
frame->present_commands = std::make_unique<CommandBuffer>(m_impl->present_scheduler);
|
||||
}
|
||||
auto& command = *frame->present_commands;
|
||||
command.WaitForFenceAndReset();
|
||||
@@ -830,8 +809,7 @@ void Presenter::Present(Frame& frame, bool reuse) {
|
||||
continue;
|
||||
}
|
||||
if (frame.present_commands == nullptr) {
|
||||
frame.present_commands =
|
||||
std::make_unique<CommandBuffer>(m_impl->present_scheduler);
|
||||
frame.present_commands = std::make_unique<CommandBuffer>(m_impl->present_scheduler);
|
||||
}
|
||||
{
|
||||
Common::LockGuard render_lock(m_impl->renderer.GetMutex());
|
||||
|
||||
@@ -32,11 +32,11 @@
|
||||
#include "graphics/host_gpu/vma.h"
|
||||
#include "graphics/host_gpu/vulkanCommon.h"
|
||||
#include "graphics/presentation/presenter.h"
|
||||
#include "kernel/memory.h"
|
||||
#include "graphics/presentation/renderDoc.h"
|
||||
#include "graphics/presentation/videoOut.h"
|
||||
#include "graphics/presentation/window.h"
|
||||
#include "graphics/presentation/window/windowInternal.h"
|
||||
#include "kernel/memory.h"
|
||||
#include "libs/controller.h"
|
||||
#include "loader/systemContent.h"
|
||||
|
||||
@@ -909,10 +909,9 @@ void WindowContext::CreateVulkan() {
|
||||
}
|
||||
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"};
|
||||
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"};
|
||||
|
||||
#if defined(__APPLE__)
|
||||
// MoltenVK lacks VK_EXT_depth_clip_enable and VK_EXT_color_write_enable; the renderer
|
||||
@@ -932,8 +931,8 @@ void WindowContext::CreateVulkan() {
|
||||
|
||||
uint32_t queue_family = static_cast<uint32_t>(-1);
|
||||
|
||||
VulkanFindPhysicalDevice(graphic_ctx.instance, surface, device_extensions,
|
||||
surface_capabilities, graphic_ctx.physical_device, queue_family);
|
||||
VulkanFindPhysicalDevice(graphic_ctx.instance, surface, device_extensions, surface_capabilities,
|
||||
graphic_ctx.physical_device, queue_family);
|
||||
|
||||
if (graphic_ctx.physical_device == nullptr) {
|
||||
EXIT("Could not find suitable device");
|
||||
@@ -949,9 +948,8 @@ void WindowContext::CreateVulkan() {
|
||||
auto available_extensions = EnumerateVulkan<vk::ExtensionProperties>(
|
||||
"vkEnumerateDeviceExtensionProperties",
|
||||
[&](uint32_t* count, vk::ExtensionProperties* values) {
|
||||
return graphic_ctx.physical_device.enumerateDeviceExtensionProperties(nullptr,
|
||||
count,
|
||||
values);
|
||||
return graphic_ctx.physical_device.enumerateDeviceExtensionProperties(
|
||||
nullptr, count, values);
|
||||
});
|
||||
|
||||
if (HasExtension(available_extensions, VK_EXT_MEMORY_BUDGET_EXTENSION_NAME)) {
|
||||
|
||||
@@ -1,7 +1,5 @@
|
||||
#include "graphics/presentation/window.h"
|
||||
|
||||
#include <cstdlib>
|
||||
|
||||
#include "SDL.h"
|
||||
#include "SDL_error.h"
|
||||
#include "SDL_events.h"
|
||||
@@ -40,6 +38,7 @@
|
||||
|
||||
#include <algorithm>
|
||||
#include <cstdio>
|
||||
#include <cstdlib>
|
||||
#include <cstring>
|
||||
#include <memory>
|
||||
#include <string>
|
||||
@@ -251,9 +250,7 @@ static void GameEventKeyboard(WindowLoopState& game, const EventKeyboard& key) {
|
||||
if (key.down) {
|
||||
switch (key.key_code) {
|
||||
case SDLK_ESCAPE: game.need_exit = true; break;
|
||||
case SDLK_SPACE:
|
||||
SetPause(game, !game.paused.load(std::memory_order_acquire));
|
||||
break;
|
||||
case SDLK_SPACE: SetPause(game, !game.paused.load(std::memory_order_acquire)); break;
|
||||
case SDLK_F1:
|
||||
if (!key.repeat) {
|
||||
RenderDocRequestCapture();
|
||||
@@ -390,7 +387,9 @@ 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: LOGF("Window %" PRIu32 " shown\n", window_event.windowID); break;
|
||||
case SDL_WINDOWEVENT_SHOWN:
|
||||
LOGF("Window %" PRIu32 " shown\n", window_event.windowID);
|
||||
break;
|
||||
|
||||
case SDL_WINDOWEVENT_HIDDEN:
|
||||
LOGF("Window %" PRIu32 " hidden\n", window_event.windowID);
|
||||
@@ -401,13 +400,13 @@ void WindowContext::ProcessWindowEvent(const SDL_WindowEvent& event) {
|
||||
break;
|
||||
|
||||
case SDL_WINDOWEVENT_MOVED:
|
||||
LOGF("Window %" PRIu32 " moved to %" PRId32 ",%" PRId32 "\n",
|
||||
window_event.windowID, window_event.data1, window_event.data2);
|
||||
LOGF("Window %" PRIu32 " moved to %" PRId32 ",%" PRId32 "\n", window_event.windowID,
|
||||
window_event.data1, window_event.data2);
|
||||
break;
|
||||
|
||||
case SDL_WINDOWEVENT_RESIZED:
|
||||
LOGF("Window %" PRIu32 " resized to %" PRId32 "x%" PRId32 "\n",
|
||||
window_event.windowID, window_event.data1, window_event.data2);
|
||||
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);
|
||||
@@ -807,8 +806,7 @@ 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,
|
||||
context.window = SDL_CreateWindow(KYTY_SDL_WINDOW_CAPTION, KYTY_SDL_WINDOWPOS_CENTERED,
|
||||
KYTY_SDL_WINDOWPOS_CENTERED, width, height, window_flags);
|
||||
|
||||
context.window_hidden = true;
|
||||
@@ -950,11 +948,11 @@ void WindowContext::UpdateTitle() {
|
||||
fps_frames = 0;
|
||||
}
|
||||
|
||||
auto fps = fmt::format("{}{}{}{}{}{}[{}] [{}], frame: {}, fps: {:f}", (has_title ? title : ""),
|
||||
auto fps =
|
||||
fmt::format("{}{}{}{}{}{}[{}] [{}], frame: {}, fps: {:f}", (has_title ? title : ""),
|
||||
(has_title ? ", " : ""), (has_title_id ? title_id : ""),
|
||||
(has_title_id ? ", " : ""), (has_app_ver ? app_ver : ""),
|
||||
(has_app_ver ? " " : ""), device_name, processor_name,
|
||||
frame_num, current_fps);
|
||||
(has_app_ver ? " " : ""), device_name, processor_name, frame_num, current_fps);
|
||||
|
||||
#if defined(__APPLE__)
|
||||
// AppKit traps on title changes off the main thread; fire-and-forget keeps present pacing.
|
||||
|
||||
@@ -38,8 +38,7 @@ struct WindowContext {
|
||||
~WindowContext();
|
||||
KYTY_CLASS_NO_COPY(WindowContext);
|
||||
|
||||
[[nodiscard]] static vk::PhysicalDeviceVulkan13Features
|
||||
RequiredVulkan13Features() noexcept;
|
||||
[[nodiscard]] static vk::PhysicalDeviceVulkan13Features RequiredVulkan13Features() noexcept;
|
||||
void CreateVulkan();
|
||||
void RecreateSurface();
|
||||
void RefreshSurfaceCapabilities();
|
||||
|
||||
@@ -2,15 +2,16 @@
|
||||
|
||||
#include "common/assert.h"
|
||||
#include "common/logging/log.h"
|
||||
#include "graphics/shader/recompiler/cfg/ShaderCFG.h"
|
||||
#include "graphics/shader/recompiler/decompiler/ShaderDecoder.h"
|
||||
#include "graphics/shader/recompiler/emitter/SpirvEmitter.h"
|
||||
#include "graphics/shader/recompiler/ir/BindingLayout.h"
|
||||
#include "graphics/shader/recompiler/ir/ReadLaneElimination.h"
|
||||
#include "graphics/shader/recompiler/ir/ResourceMaterialization.h"
|
||||
#include "graphics/shader/recompiler/ir/ResourceTracking.h"
|
||||
#include "graphics/shader/recompiler/ir/ScalarProvenance.h"
|
||||
#include "graphics/shader/recompiler/cfg/ShaderCFG.h"
|
||||
#include "graphics/shader/recompiler/decompiler/ShaderDecoder.h"
|
||||
#include "graphics/shader/recompiler/ir/ShaderIR.h"
|
||||
#include "graphics/shader/recompiler/ir/ShaderInfoCollection.h"
|
||||
#include "graphics/shader/recompiler/emitter/SpirvEmitter.h"
|
||||
#include "graphics/shader/recompiler/ir/SrtPatcher.h"
|
||||
#include "graphics/shader/recompiler/ir/SrtWalker.h"
|
||||
|
||||
@@ -838,6 +839,11 @@ bool TryRecompile(std::span<const uint32_t> code, const CompileOptions& options,
|
||||
if (!IR::AllocateBindings(ir, layout_options, error)) {
|
||||
return false;
|
||||
}
|
||||
const auto read_lane_stats = IR::EliminateReadLane(ir);
|
||||
if (read_lane_stats.rewritten_reads != 0) {
|
||||
LOGF("%s read-lane elimination: reads=%" PRIu32 " shadow_writes=%" PRIu32 "\n",
|
||||
GetDumpLabel(options), read_lane_stats.rewritten_reads, read_lane_stats.shadow_writes);
|
||||
}
|
||||
std::string ir_dump;
|
||||
if (options.dump_ir) {
|
||||
ir_dump = MakeIrDump(cfg, ir);
|
||||
|
||||
@@ -871,19 +871,19 @@ std::vector<uint32_t> DominatedBlocks(const Graph& graph, uint32_t header,
|
||||
return blocks;
|
||||
}
|
||||
|
||||
uint32_t AppendSyntheticMergeBlock(Graph& graph, uint32_t old_merge) {
|
||||
const auto* merge = graph.FindBlock(old_merge);
|
||||
uint32_t AppendSyntheticBranchBlock(Graph& graph, uint32_t target) {
|
||||
const auto* target_block = graph.FindBlock(target);
|
||||
|
||||
BasicBlock block;
|
||||
block.id = static_cast<uint32_t>(graph.blocks.size());
|
||||
block.start_pc = merge != nullptr ? merge->start_pc : 0u;
|
||||
block.start_pc = target_block != nullptr ? target_block->start_pc : 0u;
|
||||
block.end_pc = block.start_pc;
|
||||
block.inst_begin = merge != nullptr ? merge->inst_begin : 0u;
|
||||
block.inst_begin = target_block != nullptr ? target_block->inst_begin : 0u;
|
||||
block.inst_end = block.inst_begin;
|
||||
block.successors = {old_merge};
|
||||
block.successors = {target};
|
||||
block.terminator.kind = TerminatorKind::Branch;
|
||||
block.terminator.condition = BranchCondition::Always;
|
||||
block.terminator.true_block = old_merge;
|
||||
block.terminator.true_block = target;
|
||||
graph.blocks.push_back(std::move(block));
|
||||
return graph.blocks.back().id;
|
||||
}
|
||||
@@ -897,15 +897,50 @@ bool IsSyntheticMergeForwarder(const Graph& graph, uint32_t block_id, uint32_t m
|
||||
block->terminator.true_block == merge;
|
||||
}
|
||||
|
||||
bool IsInsideLoopConstruct(const Graph& graph, const NaturalLoop& loop, uint32_t block_id) {
|
||||
return block_id != UINT32_MAX && block_id != loop.merge && block_id != loop.continue_block &&
|
||||
graph.Dominates(loop.header, block_id) &&
|
||||
(loop.merge == UINT32_MAX || !graph.Dominates(loop.merge, block_id));
|
||||
const NaturalLoop* FindInnermostContainingLoop(const Graph& graph, uint32_t block_id) {
|
||||
const NaturalLoop* innermost = nullptr;
|
||||
for (const auto& loop: graph.natural_loops) {
|
||||
if (Contains(loop.body_blocks, block_id) &&
|
||||
(innermost == nullptr || loop.body_blocks.size() < innermost->body_blocks.size())) {
|
||||
innermost = &loop;
|
||||
}
|
||||
}
|
||||
return innermost;
|
||||
}
|
||||
|
||||
bool SelectionMergeLeavesContainingLoop(const Graph& graph, uint32_t header, uint32_t merge) {
|
||||
bool IsInsideLoopConstruct(const Graph& graph, const NaturalLoop& loop, uint32_t block_id) {
|
||||
return block_id != UINT32_MAX && block_id != loop.merge && block_id != loop.continue_block &&
|
||||
graph.Dominates(loop.header, block_id) && !graph.Dominates(loop.merge, block_id);
|
||||
}
|
||||
|
||||
bool IsInnermostLoopControlConditional(const Graph& graph, const BasicBlock& block) {
|
||||
if (block.terminator.kind != TerminatorKind::ConditionalBranch) {
|
||||
return false;
|
||||
}
|
||||
const auto* loop = FindInnermostContainingLoop(graph, block.id);
|
||||
if (loop == nullptr || loop->merge == UINT32_MAX || loop->continue_block == UINT32_MAX) {
|
||||
return false;
|
||||
}
|
||||
const auto true_target = block.terminator.true_block;
|
||||
const auto false_target = block.terminator.false_block;
|
||||
if (block.id == loop->continue_block) {
|
||||
const auto is_repeat_target = [&](uint32_t target) {
|
||||
return target == loop->header || target == loop->merge;
|
||||
};
|
||||
return is_repeat_target(true_target) && is_repeat_target(false_target);
|
||||
}
|
||||
const auto is_control_target = [&](uint32_t target) {
|
||||
return target == loop->merge || target == loop->continue_block;
|
||||
};
|
||||
return (is_control_target(true_target) &&
|
||||
(is_control_target(false_target) ||
|
||||
IsInsideLoopConstruct(graph, *loop, false_target))) ||
|
||||
(is_control_target(false_target) && IsInsideLoopConstruct(graph, *loop, true_target));
|
||||
}
|
||||
|
||||
bool MergeLeavesContainingLoop(const Graph& graph, uint32_t header, uint32_t merge) {
|
||||
for (const auto& loop: graph.natural_loops) {
|
||||
if (IsInsideLoopConstruct(graph, loop, header) &&
|
||||
if (loop.header != header && IsInsideLoopConstruct(graph, loop, header) &&
|
||||
!IsInsideLoopConstruct(graph, loop, merge)) {
|
||||
return true;
|
||||
}
|
||||
@@ -913,6 +948,80 @@ bool SelectionMergeLeavesContainingLoop(const Graph& graph, uint32_t header, uin
|
||||
return false;
|
||||
}
|
||||
|
||||
bool CanonicalizeNaturalLoops(Graph& graph, std::string* error) {
|
||||
const auto rewrite_budget = graph.blocks.size() * 2u + 16u;
|
||||
for (size_t rewrite = 0; rewrite < rewrite_budget; rewrite++) {
|
||||
bool changed = false;
|
||||
for (const auto& loop: graph.natural_loops) {
|
||||
std::vector<uint32_t> latches;
|
||||
for (const auto& edge: graph.back_edges) {
|
||||
if (edge.to == loop.header) {
|
||||
AddUnique(latches, edge.from);
|
||||
}
|
||||
}
|
||||
if (latches.size() <= 1u) {
|
||||
continue;
|
||||
}
|
||||
|
||||
const auto continue_block = AppendSyntheticBranchBlock(graph, loop.header);
|
||||
for (auto latch: latches) {
|
||||
auto* block = graph.FindBlock(latch);
|
||||
if (block != nullptr) {
|
||||
ReplaceValue(block->successors, loop.header, continue_block);
|
||||
ReplaceTerminatorTarget(block->terminator, loop.header, continue_block);
|
||||
}
|
||||
}
|
||||
RebuildPredecessors(graph);
|
||||
RecomputeAnalyses(graph);
|
||||
changed = true;
|
||||
break;
|
||||
}
|
||||
if (changed) {
|
||||
continue;
|
||||
}
|
||||
|
||||
for (const auto& loop: graph.natural_loops) {
|
||||
const auto* header = graph.FindBlock(loop.header);
|
||||
const auto is_loop_control_target = [&](uint32_t target) {
|
||||
return target == loop.merge || target == loop.continue_block;
|
||||
};
|
||||
if (header == nullptr || header->terminator.kind != TerminatorKind::ConditionalBranch ||
|
||||
is_loop_control_target(header->terminator.true_block) ||
|
||||
is_loop_control_target(header->terminator.false_block) ||
|
||||
!Contains(loop.body_blocks, header->terminator.true_block) ||
|
||||
!Contains(loop.body_blocks, header->terminator.false_block)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
const auto old_header = loop.header;
|
||||
const auto predecessors = header->predecessors;
|
||||
const auto new_header = AppendSyntheticBranchBlock(graph, old_header);
|
||||
for (auto pred: predecessors) {
|
||||
auto* block = graph.FindBlock(pred);
|
||||
if (block != nullptr) {
|
||||
ReplaceValue(block->successors, old_header, new_header);
|
||||
ReplaceTerminatorTarget(block->terminator, old_header, new_header);
|
||||
}
|
||||
}
|
||||
if (graph.entry_block == old_header) {
|
||||
graph.entry_block = new_header;
|
||||
}
|
||||
MoveBlockBefore(graph, new_header, old_header);
|
||||
RebuildPredecessors(graph);
|
||||
RecomputeAnalyses(graph);
|
||||
changed = true;
|
||||
break;
|
||||
}
|
||||
if (!changed) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
SetFailure(graph, FailureKind::StructuredControlFlow, graph.entry_block,
|
||||
"CFG loop canonicalization exceeded rewrite budget", error);
|
||||
return false;
|
||||
}
|
||||
|
||||
bool SplitSharedMergeBlock(Graph& graph, uint32_t merge,
|
||||
const std::vector<uint32_t>& construct_blocks,
|
||||
bool force_split = false) {
|
||||
@@ -948,7 +1057,7 @@ bool SplitSharedMergeBlock(Graph& graph, uint32_t merge,
|
||||
return false;
|
||||
}
|
||||
|
||||
const auto synthetic_merge = AppendSyntheticMergeBlock(graph, merge);
|
||||
const auto synthetic_merge = AppendSyntheticBranchBlock(graph, merge);
|
||||
auto* synthetic_block = graph.FindBlock(synthetic_merge);
|
||||
if (synthetic_block != nullptr) {
|
||||
synthetic_block->predecessors = predecessors_to_split;
|
||||
@@ -980,14 +1089,111 @@ bool SplitSharedMergeBlock(Graph& graph, uint32_t merge,
|
||||
bool SplitOneLoopMerge(Graph& graph) {
|
||||
const auto& loops = graph.natural_loops;
|
||||
for (const auto& loop: loops) {
|
||||
if (SplitSharedMergeBlock(graph, loop.merge, loop.body_blocks)) {
|
||||
const auto construct_blocks = DominatedBlocks(graph, loop.header, loop.merge);
|
||||
const auto force_split = MergeLeavesContainingLoop(graph, loop.header, loop.merge);
|
||||
if (SplitSharedMergeBlock(graph, loop.merge, construct_blocks, force_split)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
bool SplitOneSelectionMerge(Graph& graph) {
|
||||
std::vector<uint32_t> SelectionRegion(const Graph& graph, const BasicBlock& header,
|
||||
uint32_t merge) {
|
||||
std::vector<uint32_t> region;
|
||||
std::vector<uint32_t> pending = {header.terminator.true_block,
|
||||
header.terminator.false_block};
|
||||
while (!pending.empty()) {
|
||||
const auto block_id = pending.back();
|
||||
pending.pop_back();
|
||||
if (block_id == merge || Contains(region, block_id)) {
|
||||
continue;
|
||||
}
|
||||
const auto* block = graph.FindBlock(block_id);
|
||||
if (block == nullptr) {
|
||||
continue;
|
||||
}
|
||||
AddUnique(region, block_id);
|
||||
pending.insert(pending.end(), block->successors.begin(), block->successors.end());
|
||||
}
|
||||
SortUnique(region);
|
||||
return region;
|
||||
}
|
||||
|
||||
bool DuplicateSelectionRegion(Graph& graph, uint32_t header_id, uint32_t merge,
|
||||
const std::vector<uint32_t>& region, uint32_t block_budget) {
|
||||
std::vector<uint32_t> cloned_blocks;
|
||||
for (auto block_id: region) {
|
||||
if (!graph.Dominates(header_id, block_id)) {
|
||||
cloned_blocks.push_back(block_id);
|
||||
}
|
||||
}
|
||||
if (cloned_blocks.empty() || graph.FindBlock(header_id) == nullptr || header_id >= merge ||
|
||||
graph.blocks.size() + cloned_blocks.size() + 1u > block_budget) {
|
||||
return false;
|
||||
}
|
||||
|
||||
const auto first_clone = static_cast<uint32_t>(graph.blocks.size());
|
||||
std::map<uint32_t, uint32_t> clones;
|
||||
for (uint32_t i = 0; i < cloned_blocks.size(); i++) {
|
||||
clones.emplace(cloned_blocks[i], first_clone + i);
|
||||
}
|
||||
|
||||
for (auto block_id: cloned_blocks) {
|
||||
BasicBlock clone = *graph.FindBlock(block_id);
|
||||
clone.id = clones.at(block_id);
|
||||
clone.predecessors.clear();
|
||||
clone.dominators.clear();
|
||||
clone.post_dominators.clear();
|
||||
graph.blocks.push_back(std::move(clone));
|
||||
}
|
||||
|
||||
const auto remap_block = [&](BasicBlock& block) {
|
||||
const auto remap_target = [&](uint32_t& target) {
|
||||
if (const auto it = clones.find(target); it != clones.end()) {
|
||||
target = it->second;
|
||||
}
|
||||
};
|
||||
for (auto& successor: block.successors) {
|
||||
remap_target(successor);
|
||||
}
|
||||
remap_target(block.terminator.true_block);
|
||||
remap_target(block.terminator.false_block);
|
||||
remap_target(block.terminator.merge_block);
|
||||
remap_target(block.terminator.continue_block);
|
||||
for (auto& target: block.terminator.indirect_targets) {
|
||||
remap_target(target);
|
||||
}
|
||||
};
|
||||
for (auto block_id: region) {
|
||||
const auto owned_id = clones.contains(block_id) ? clones.at(block_id) : block_id;
|
||||
remap_block(*graph.FindBlock(owned_id));
|
||||
}
|
||||
|
||||
const auto private_merge = AppendSyntheticBranchBlock(graph, merge);
|
||||
auto& header = *graph.FindBlock(header_id);
|
||||
remap_block(header);
|
||||
|
||||
for (auto block_id: region) {
|
||||
const auto owned_id = clones.contains(block_id) ? clones.at(block_id) : block_id;
|
||||
auto* block = graph.FindBlock(owned_id);
|
||||
if (block != nullptr) {
|
||||
ReplaceValue(block->successors, merge, private_merge);
|
||||
ReplaceTerminatorTarget(block->terminator, merge, private_merge);
|
||||
}
|
||||
}
|
||||
ReplaceValue(header.successors, merge, private_merge);
|
||||
ReplaceTerminatorTarget(header.terminator, merge, private_merge);
|
||||
|
||||
for (uint32_t i = 0; i <= cloned_blocks.size(); i++) {
|
||||
MoveBlockBefore(graph, first_clone + i, merge + i);
|
||||
}
|
||||
RebuildPredecessors(graph);
|
||||
RecomputeAnalyses(graph);
|
||||
return true;
|
||||
}
|
||||
|
||||
bool SplitOneSelectionMerge(Graph& graph, uint32_t block_budget) {
|
||||
std::vector<uint32_t> loop_headers;
|
||||
loop_headers.reserve(graph.natural_loops.size());
|
||||
for (const auto& loop: graph.natural_loops) {
|
||||
@@ -1001,11 +1207,26 @@ bool SplitOneSelectionMerge(Graph& graph) {
|
||||
Contains(loop_headers, block_id)) {
|
||||
continue;
|
||||
}
|
||||
if (IsInnermostLoopControlConditional(graph, *block)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
const auto merge = graph.FindNearestCommonPostDominator(block->terminator.true_block,
|
||||
block->terminator.false_block);
|
||||
if (merge == UINT32_MAX || graph.FindBlock(merge) == nullptr) {
|
||||
continue;
|
||||
}
|
||||
const auto region = SelectionRegion(graph, *block, merge);
|
||||
if (std::any_of(region.begin(), region.end(),
|
||||
[&](uint32_t member) { return !graph.Dominates(block_id, member); })) {
|
||||
if (graph.natural_loops.empty() &&
|
||||
DuplicateSelectionRegion(graph, block_id, merge, region, block_budget)) {
|
||||
return true;
|
||||
}
|
||||
continue;
|
||||
}
|
||||
const auto construct_blocks = DominatedBlocks(graph, block_id, merge);
|
||||
const auto force_split = SelectionMergeLeavesContainingLoop(graph, block_id, merge);
|
||||
const auto force_split = MergeLeavesContainingLoop(graph, block_id, merge);
|
||||
if (SplitSharedMergeBlock(graph, merge, construct_blocks, force_split)) {
|
||||
return true;
|
||||
}
|
||||
@@ -1015,10 +1236,12 @@ bool SplitOneSelectionMerge(Graph& graph) {
|
||||
|
||||
bool SplitSharedMergeBlocks(Graph& graph, std::string* error) {
|
||||
const auto original_block_count = static_cast<uint32_t>(graph.blocks.size());
|
||||
const auto split_budget =
|
||||
std::max<uint32_t>(16u, std::min<uint32_t>(128u, original_block_count));
|
||||
const auto split_budget = std::max<uint32_t>(
|
||||
16u, std::min<uint32_t>(128u, original_block_count * 4u));
|
||||
const auto block_budget = std::max<uint32_t>(
|
||||
32u, std::min<uint32_t>(512u, original_block_count * 8u));
|
||||
for (uint32_t splits = 0; splits < split_budget; splits++) {
|
||||
if (!SplitOneLoopMerge(graph) && !SplitOneSelectionMerge(graph)) {
|
||||
if (!SplitOneLoopMerge(graph) && !SplitOneSelectionMerge(graph, block_budget)) {
|
||||
return true;
|
||||
}
|
||||
RebuildPredecessors(graph);
|
||||
@@ -1353,6 +1576,9 @@ bool Structurize(Graph& graph, std::string* error) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!CanonicalizeNaturalLoops(graph, error)) {
|
||||
return false;
|
||||
}
|
||||
if (!SplitSharedMergeBlocks(graph, error)) {
|
||||
return false;
|
||||
}
|
||||
@@ -1395,6 +1621,9 @@ bool Structurize(Graph& graph, std::string* error) {
|
||||
block.terminator.loop_header) {
|
||||
continue;
|
||||
}
|
||||
if (IsInnermostLoopControlConditional(graph, block)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
const auto merge = graph.FindNearestCommonPostDominator(block.terminator.true_block,
|
||||
block.terminator.false_block);
|
||||
|
||||
@@ -35,9 +35,9 @@ constexpr ImageDimension DecodeImageDimension(uint32_t dim) {
|
||||
case 2u: return ImageDimension::Dim3D;
|
||||
case 3u: return ImageDimension::Dim2DArray;
|
||||
case 4u: return ImageDimension::Dim1DArray;
|
||||
case 5u:
|
||||
case 7u: return ImageDimension::Dim2DArray;
|
||||
case 6u: return ImageDimension::Dim2D;
|
||||
case 5u: return ImageDimension::Dim2DArray;
|
||||
case 6u: return ImageDimension::Dim2DMsaa;
|
||||
case 7u: return ImageDimension::Dim2DMsaaArray;
|
||||
default: return ImageDimension::Unknown;
|
||||
}
|
||||
}
|
||||
@@ -46,8 +46,10 @@ constexpr uint32_t ImageCoordComponents(ImageDimension dimension) {
|
||||
switch (dimension) {
|
||||
case ImageDimension::Dim1D: return 1u;
|
||||
case ImageDimension::Dim1DArray: return 2u;
|
||||
case ImageDimension::Dim2DMsaa:
|
||||
case ImageDimension::Dim3D:
|
||||
case ImageDimension::Dim2DArray: return 3u;
|
||||
case ImageDimension::Dim2DMsaaArray: return 4u;
|
||||
default: return 2u;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -442,11 +442,6 @@ bool DecodeDs(uint32_t pc, std::span<const uint32_t> code, uint32_t word_index,
|
||||
inst.opcode == Opcode::DsReadAddtidB32)) {
|
||||
SetUnsupported(inst, Family::DS, opcode, "DS swizzle/addtid is available only for LDS");
|
||||
}
|
||||
if (inst.gds && (inst.opcode == Opcode::DsAppend || inst.opcode == Opcode::DsConsume) &&
|
||||
inst.offset != 0u) {
|
||||
SetUnsupported(inst, Family::DS, opcode,
|
||||
"GDS append/consume requires a zero instruction offset");
|
||||
}
|
||||
if (inst.opcode == Opcode::DsWriteAddtidB32 && data1 != 0u) {
|
||||
SetUnsupported(inst, Family::DS, opcode,
|
||||
"DS write addtid data1 operand is not implemented");
|
||||
|
||||
@@ -194,6 +194,8 @@ const char* ImageDimensionToString(ImageDimension dimension) {
|
||||
case ImageDimension::Dim2D: return "2d";
|
||||
case ImageDimension::Dim3D: return "3d";
|
||||
case ImageDimension::Dim2DArray: return "2d_array";
|
||||
case ImageDimension::Dim2DMsaa: return "2d_msaa";
|
||||
case ImageDimension::Dim2DMsaaArray: return "2d_msaa_array";
|
||||
default: return "unknown";
|
||||
}
|
||||
}
|
||||
|
||||
@@ -575,6 +575,8 @@ enum class ImageDimension : uint32_t {
|
||||
Dim2D,
|
||||
Dim3D,
|
||||
Dim2DArray,
|
||||
Dim2DMsaa,
|
||||
Dim2DMsaaArray,
|
||||
};
|
||||
|
||||
constexpr uint32_t MaxInstructionRawWords = 5u;
|
||||
|
||||
@@ -19,7 +19,7 @@ static void AppendInstructionWords(std::vector<uint32_t>& section, const uint32_
|
||||
section.insert(section.end(), words + 1, words + words_num);
|
||||
}
|
||||
|
||||
Builder::Builder() {
|
||||
Builder::Builder(uint32_t version): m_version(version) {
|
||||
m_debug.reserve(InitialSpirvSectionReserve);
|
||||
m_annotations.reserve(InitialSpirvSectionReserve);
|
||||
m_types.reserve(InitialSpirvSectionReserve);
|
||||
@@ -138,7 +138,7 @@ std::vector<uint32_t> Builder::Build() const {
|
||||
m_debug.size() + m_annotations.size() + m_types.size() + m_functions.size());
|
||||
|
||||
module.push_back(0x07230203u);
|
||||
module.push_back(0x00010300u);
|
||||
module.push_back(m_version);
|
||||
module.push_back(0u);
|
||||
module.push_back(m_next_id);
|
||||
module.push_back(0u);
|
||||
|
||||
@@ -10,7 +10,7 @@ namespace Libs::Graphics::ShaderRecompiler::Spirv {
|
||||
|
||||
class Builder {
|
||||
public:
|
||||
Builder();
|
||||
explicit Builder(uint32_t version = 0x00010300u);
|
||||
~Builder() = default;
|
||||
KYTY_CLASS_DEFAULT_COPY(Builder);
|
||||
|
||||
@@ -39,6 +39,7 @@ private:
|
||||
static void AppendString(std::vector<uint32_t>& words, const char* text);
|
||||
|
||||
uint32_t m_next_id = 1;
|
||||
uint32_t m_version = 0;
|
||||
std::vector<uint32_t> m_capabilities;
|
||||
std::vector<uint32_t> m_extensions;
|
||||
std::vector<uint32_t> m_ext_inst_imports;
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
#include "graphics/shader/recompiler/emitter/SpirvEmitter.h"
|
||||
|
||||
#include "graphics/shader/recompiler/ir/SrtWalker.h"
|
||||
#include "graphics/shader/recompiler/emitter/spirvEmitterInternal.h"
|
||||
#include "graphics/shader/recompiler/ir/SrtWalker.h"
|
||||
|
||||
#include <algorithm>
|
||||
#include <array>
|
||||
@@ -30,10 +30,16 @@ bool ImageBinding(const IR::ImageResource& image, IR::DescriptorBindingKind& kin
|
||||
kind = integer ? Kind::SampledUint1DArray : Kind::Sampled1DArray;
|
||||
return true;
|
||||
case Dim::Dim2D: kind = integer ? Kind::SampledUint2D : Kind::Sampled2D; return true;
|
||||
case Dim::Dim2DMsaa:
|
||||
kind = integer ? Kind::SampledUint2DMsaa : Kind::Sampled2DMsaa;
|
||||
return true;
|
||||
case Dim::Dim3D: kind = integer ? Kind::SampledUint3D : Kind::Sampled3D; return true;
|
||||
case Dim::Dim2DArray:
|
||||
kind = integer ? Kind::SampledUint2DArray : Kind::Sampled2DArray;
|
||||
return true;
|
||||
case Dim::Dim2DMsaaArray:
|
||||
kind = integer ? Kind::SampledUint2DMsaaArray : Kind::Sampled2DMsaaArray;
|
||||
return true;
|
||||
case Dim::Unknown: return false;
|
||||
}
|
||||
}
|
||||
@@ -51,6 +57,8 @@ bool ImageBinding(const IR::ImageResource& image, IR::DescriptorBindingKind& kin
|
||||
case Dim::Dim2DArray:
|
||||
kind = uint_image ? Kind::StorageUint2DArray : Kind::Storage2DArray;
|
||||
return true;
|
||||
case Dim::Dim2DMsaa:
|
||||
case Dim::Dim2DMsaaArray: return false;
|
||||
case Dim::Unknown: return false;
|
||||
}
|
||||
return false;
|
||||
@@ -160,8 +168,7 @@ bool ValidateInstructionContract(const IR::Instruction& inst, std::string* error
|
||||
inst.dst.kind != IR::OperandKind::Null)) ||
|
||||
((inst.op == IR::Opcode::DsAppend || inst.op == IR::Opcode::DsConsume) &&
|
||||
(!ds_kind || !ds_resource || inst.src_count != 1 ||
|
||||
inst.dst.kind != IR::OperandKind::Register ||
|
||||
(kind == IR::ResourceKind::Gds && inst.memory.offset != 0))) ||
|
||||
inst.dst.kind != IR::OperandKind::Register)) ||
|
||||
((inst.op == IR::Opcode::DsMinF32 || inst.op == IR::Opcode::DsMaxF32) &&
|
||||
(!ds_kind || !ds_resource || inst.src_count != 3 ||
|
||||
inst.dst.kind != IR::OperandKind::Null)) ||
|
||||
|
||||
@@ -7,51 +7,30 @@ namespace Libs::Graphics::ShaderRecompiler::Spirv::Emitter {
|
||||
|
||||
uint32_t PixelParameterMappedLocation(const EmitterState& state, uint32_t attr) {
|
||||
const auto* ps = state.pixel_input_info;
|
||||
if (state.stage != ShaderType::Pixel || ps == nullptr || attr >= ps->input_num) {
|
||||
if (state.stage != ShaderType::Pixel || ps == nullptr) {
|
||||
return attr;
|
||||
}
|
||||
// VINTRP ATTR selects the PS input slot. SPI_PS_INPUT_CNTL maps that slot to a
|
||||
// VS parameter export, which is the SPIR-V location we must link against.
|
||||
return ps->interpolator_settings[attr] & PsInputOffsetMask;
|
||||
return ShaderPixelParameterMappedLocation(*ps, attr);
|
||||
}
|
||||
|
||||
uint32_t PixelParameterLocation(const EmitterState& state, uint32_t attr) {
|
||||
bool used_locations[32] = {};
|
||||
|
||||
std::array<uint32_t, 32> active_inputs {};
|
||||
uint32_t active_count = 0;
|
||||
for (const auto& input: state.inputs) {
|
||||
if (input.kind != IR::StageInputKind::Parameter) {
|
||||
continue;
|
||||
}
|
||||
|
||||
auto location = PixelParameterMappedLocation(state, input.location);
|
||||
if (location < std::size(used_locations) && used_locations[location]) {
|
||||
auto fallback_location = input.location;
|
||||
while (fallback_location < std::size(used_locations) &&
|
||||
used_locations[fallback_location]) {
|
||||
fallback_location++;
|
||||
}
|
||||
EXIT_NOT_IMPLEMENTED(fallback_location >= std::size(used_locations));
|
||||
location = fallback_location;
|
||||
}
|
||||
|
||||
if (input.location == attr) {
|
||||
return location;
|
||||
}
|
||||
|
||||
if (location < std::size(used_locations)) {
|
||||
used_locations[location] = true;
|
||||
if (input.kind == IR::StageInputKind::Parameter) {
|
||||
active_inputs[active_count++] = input.location;
|
||||
}
|
||||
}
|
||||
|
||||
return PixelParameterMappedLocation(state, attr);
|
||||
return state.stage == ShaderType::Pixel && state.pixel_input_info != nullptr
|
||||
? ShaderPixelParameterLocation(*state.pixel_input_info,
|
||||
{active_inputs.data(), active_count}, attr)
|
||||
: attr;
|
||||
}
|
||||
|
||||
bool PixelParameterIsFlat(const EmitterState& state, uint32_t attr) {
|
||||
const auto* ps = state.pixel_input_info;
|
||||
if (state.stage != ShaderType::Pixel || ps == nullptr || attr >= ps->input_num) {
|
||||
return false;
|
||||
}
|
||||
return (ps->interpolator_settings[attr] & PsInputFlatShade) != 0;
|
||||
return state.stage == ShaderType::Pixel && ps != nullptr &&
|
||||
ShaderPixelParameterIsFlat(*ps, attr);
|
||||
}
|
||||
|
||||
void SetError(std::string* error, const char* message) {
|
||||
@@ -276,8 +255,7 @@ void CopyProgramInputsAndOutputs(EmitterState& state, const IR::Program& program
|
||||
if (HasOutput(state.outputs, output.kind, output.index)) {
|
||||
continue;
|
||||
}
|
||||
state.outputs.push_back(
|
||||
{output.kind, output.index, output.location, 0, output.debug_name});
|
||||
state.outputs.push_back({output.kind, output.index, output.location, 0, output.debug_name});
|
||||
}
|
||||
}
|
||||
|
||||
@@ -576,6 +554,8 @@ ImageViewKind ImageViewKindFromDimension(Decoder::ImageDimension dimension) {
|
||||
case Decoder::ImageDimension::Dim1DArray: return ImageViewKind::Dim1DArray;
|
||||
case Decoder::ImageDimension::Dim2DArray: return ImageViewKind::Dim2DArray;
|
||||
case Decoder::ImageDimension::Dim3D: return ImageViewKind::Dim3D;
|
||||
case Decoder::ImageDimension::Dim2DMsaa: return ImageViewKind::Dim2DMsaa;
|
||||
case Decoder::ImageDimension::Dim2DMsaaArray: return ImageViewKind::Dim2DMsaaArray;
|
||||
default: return ImageViewKind::Dim2D;
|
||||
}
|
||||
}
|
||||
@@ -601,7 +581,9 @@ uint32_t ImageViewCoordinateComponents(ImageViewKind view) {
|
||||
case ImageViewKind::Dim1DArray:
|
||||
case ImageViewKind::Dim2D: return 2u;
|
||||
case ImageViewKind::Dim2DArray:
|
||||
case ImageViewKind::Dim2DMsaaArray:
|
||||
case ImageViewKind::Dim3D: return 3u;
|
||||
case ImageViewKind::Dim2DMsaa: return 2u;
|
||||
default: return 0u;
|
||||
}
|
||||
}
|
||||
@@ -611,7 +593,9 @@ uint32_t ImageViewSpatialComponents(ImageViewKind view) {
|
||||
case ImageViewKind::Dim1D:
|
||||
case ImageViewKind::Dim1DArray: return 1u;
|
||||
case ImageViewKind::Dim2D:
|
||||
case ImageViewKind::Dim2DArray: return 2u;
|
||||
case ImageViewKind::Dim2DArray:
|
||||
case ImageViewKind::Dim2DMsaa:
|
||||
case ImageViewKind::Dim2DMsaaArray: return 2u;
|
||||
case ImageViewKind::Dim3D: return 3u;
|
||||
default: return 0u;
|
||||
}
|
||||
@@ -663,8 +647,7 @@ uint32_t LoadSampledImageDescriptor(EmitterState& state, const IR::MemoryInfo& m
|
||||
|
||||
uint32_t LoadSamplerDescriptor(EmitterState& state, uint32_t sampler, uint32_t use_pc) {
|
||||
(void)use_pc;
|
||||
const auto binding =
|
||||
ResourceForDescriptor(state, IR::DescriptorBindingKind::Samplers, sampler);
|
||||
const auto binding = ResourceForDescriptor(state, IR::DescriptorBindingKind::Samplers, sampler);
|
||||
const auto pointer = DescriptorElementPointer(
|
||||
state, state.ptr_uniform_sampler, state.sampler_variable, binding.array_index,
|
||||
IR::DescriptorBindingKind::Samplers, sampler, "sampler descriptor array was not emitted");
|
||||
|
||||
@@ -1025,15 +1025,47 @@ void EmitDispatcherSwitch(EmitterState& state, const IR::Program& program) {
|
||||
EmitDispatcherExit(state);
|
||||
}
|
||||
|
||||
size_t BufferLoadGroupSize(const IR::BasicBlock& block, size_t first_index) {
|
||||
const auto& first = block.instructions[first_index];
|
||||
if (first.op != IR::Opcode::BufferLoadDword || first.memory.component_index != 0u ||
|
||||
first.memory.component_count <= 1u) {
|
||||
return 1u;
|
||||
}
|
||||
|
||||
size_t count = 1u;
|
||||
while (first_index + count < block.instructions.size() &&
|
||||
count < first.memory.component_count) {
|
||||
const auto& next = block.instructions[first_index + count];
|
||||
if (next.op != IR::Opcode::BufferLoadDword || next.pc != first.pc ||
|
||||
next.memory.component_index != count ||
|
||||
next.memory.component_count != first.memory.component_count) {
|
||||
break;
|
||||
}
|
||||
count++;
|
||||
}
|
||||
return count;
|
||||
}
|
||||
|
||||
void EmitBlockInstructions(EmitterState& state, const IR::BasicBlock& block) {
|
||||
for (size_t i = 0; i < block.instructions.size();) {
|
||||
const auto count = BufferLoadGroupSize(block, i);
|
||||
if (count > 1u) {
|
||||
EmitBufferLoadDwordGroup(state, block.instructions.data() + i,
|
||||
static_cast<uint32_t>(count));
|
||||
} else {
|
||||
EmitInstruction(state, block.instructions[i]);
|
||||
}
|
||||
i += count;
|
||||
}
|
||||
}
|
||||
|
||||
void EmitDispatcherBlocks(EmitterState& state, const IR::Program& program) {
|
||||
for (const auto& block: program.blocks) {
|
||||
if (block.id >= state.reachable_blocks.size() || !state.reachable_blocks[block.id]) {
|
||||
continue;
|
||||
}
|
||||
state.builder.AddFunction({OpLabel, BlockLabel(state, block.id)});
|
||||
for (const auto& inst: block.instructions) {
|
||||
EmitInstruction(state, inst);
|
||||
}
|
||||
EmitBlockInstructions(state, block);
|
||||
EmitDispatcherTerminator(state, block.terminator);
|
||||
}
|
||||
}
|
||||
@@ -1083,9 +1115,7 @@ void EmitFunction(EmitterState& state, const IR::Program& program) {
|
||||
continue;
|
||||
}
|
||||
state.builder.AddFunction({OpLabel, BlockLabel(state, block.id)});
|
||||
for (const auto& inst: block.instructions) {
|
||||
EmitInstruction(state, inst);
|
||||
}
|
||||
EmitBlockInstructions(state, block);
|
||||
EmitTerminator(state, block.terminator);
|
||||
}
|
||||
|
||||
|
||||
@@ -36,8 +36,8 @@ uint32_t EmitExportVec4F32(EmitterState& state, const IR::Instruction& inst) {
|
||||
}
|
||||
}
|
||||
const auto vec = state.builder.AllocateId();
|
||||
state.builder.AddFunction({OpCompositeConstruct, state.vec4_float_type, vec,
|
||||
components[0], components[1], components[2], components[3]});
|
||||
state.builder.AddFunction({OpCompositeConstruct, state.vec4_float_type, vec, components[0],
|
||||
components[1], components[2], components[3]});
|
||||
return vec;
|
||||
}
|
||||
|
||||
@@ -50,7 +50,59 @@ uint32_t EmitExportVec4F32(EmitterState& state, const IR::Instruction& inst) {
|
||||
return vec;
|
||||
}
|
||||
|
||||
uint32_t ApplyMrtExportMapping(EmitterState& state, const IR::Instruction& inst, uint32_t value) {
|
||||
uint32_t EmitExportComponentU32(EmitterState& state, const IR::Instruction& inst,
|
||||
uint32_t component) {
|
||||
const bool enabled = ((inst.export_info.en >> component) & 1u) != 0;
|
||||
if (!enabled || component >= inst.src_count || component >= 4u) {
|
||||
return ConstantU32(state, component == 3u ? 1u : 0u);
|
||||
}
|
||||
return EmitValueLoad(state, inst.src[component]);
|
||||
}
|
||||
|
||||
uint32_t EmitExportVec4U32(EmitterState& state, const IR::Instruction& inst) {
|
||||
uint32_t components[4] = {
|
||||
ConstantU32(state, 0u),
|
||||
ConstantU32(state, 0u),
|
||||
ConstantU32(state, 0u),
|
||||
ConstantU32(state, 1u),
|
||||
};
|
||||
|
||||
if (inst.export_info.compr) {
|
||||
for (uint32_t pair_index = 0; pair_index < 2u && pair_index < inst.src_count;
|
||||
pair_index++) {
|
||||
const auto raw = EmitValueLoad(state, inst.src[pair_index]);
|
||||
for (uint32_t lane = 0; lane < 2u; lane++) {
|
||||
const auto component = pair_index * 2u + lane;
|
||||
if (((inst.export_info.en >> component) & 1u) == 0) {
|
||||
continue;
|
||||
}
|
||||
components[component] = state.builder.AllocateId();
|
||||
state.builder.AddFunction(
|
||||
{OpBitFieldUExtract, state.uint_type, components[component], raw,
|
||||
ConstantU32(state, lane * 16u), ConstantU32(state, 16u)});
|
||||
}
|
||||
}
|
||||
} else {
|
||||
for (uint32_t component = 0; component < 4u; component++) {
|
||||
components[component] = EmitExportComponentU32(state, inst, component);
|
||||
}
|
||||
}
|
||||
|
||||
const auto vec = state.builder.AllocateId();
|
||||
state.builder.AddFunction({OpCompositeConstruct, state.vec4_uint_type, vec, components[0],
|
||||
components[1], components[2], components[3]});
|
||||
return vec;
|
||||
}
|
||||
|
||||
static bool MrtUsesUintOutput(const EmitterState& state, const IR::Instruction& inst) {
|
||||
return inst.export_info.kind == IR::ExportTargetKind::Mrt &&
|
||||
state.pixel_input_info != nullptr &&
|
||||
inst.export_info.index < std::size(state.pixel_input_info->target_output_mode) &&
|
||||
state.pixel_input_info->target_output_mode[inst.export_info.index] == 7u;
|
||||
}
|
||||
|
||||
uint32_t ApplyMrtExportMapping(EmitterState& state, const IR::Instruction& inst, uint32_t value,
|
||||
uint32_t vector_type) {
|
||||
if (inst.export_info.kind != IR::ExportTargetKind::Mrt || state.pixel_input_info == nullptr ||
|
||||
inst.export_info.index >= state.pixel_input_info->target_export_mapping.size()) {
|
||||
return value;
|
||||
@@ -62,8 +114,8 @@ uint32_t ApplyMrtExportMapping(EmitterState& state, const IR::Instruction& inst,
|
||||
}
|
||||
|
||||
const auto mapped = state.builder.AllocateId();
|
||||
state.builder.AddFunction({OpVectorShuffle, state.vec4_float_type, mapped, value, value,
|
||||
mapping.Map(0), mapping.Map(1), mapping.Map(2), mapping.Map(3)});
|
||||
state.builder.AddFunction({OpVectorShuffle, vector_type, mapped, value, value, mapping.Map(0),
|
||||
mapping.Map(1), mapping.Map(2), mapping.Map(3)});
|
||||
return mapped;
|
||||
}
|
||||
|
||||
@@ -114,11 +166,15 @@ void EmitExport(EmitterState& state, const IR::Instruction& inst) {
|
||||
return;
|
||||
}
|
||||
|
||||
const auto value = ApplyMrtExportMapping(state, inst, EmitExportVec4F32(state, inst));
|
||||
const auto uint_output = MrtUsesUintOutput(state, inst);
|
||||
const auto vector_type = uint_output ? state.vec4_uint_type : state.vec4_float_type;
|
||||
const auto value = ApplyMrtExportMapping(
|
||||
state, inst, uint_output ? EmitExportVec4U32(state, inst) : EmitExportVec4F32(state, inst),
|
||||
vector_type);
|
||||
if (inst.export_info.kind == IR::ExportTargetKind::Position) {
|
||||
const auto pointer = state.builder.AllocateId();
|
||||
state.builder.AddFunction({OpAccessChain, state.ptr_output_vec4_float, pointer, variable,
|
||||
ConstantU32(state, 0)});
|
||||
state.builder.AddFunction(
|
||||
{OpAccessChain, state.ptr_output_vec4_float, pointer, variable, ConstantU32(state, 0)});
|
||||
state.builder.AddFunction({OpStore, pointer, value});
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -150,8 +150,8 @@ void EmitWqmB64(EmitterState& state, const IR::Instruction& inst) {
|
||||
EmitPerInvocationMask(state, inst.dst, active);
|
||||
} else {
|
||||
const auto result = state.builder.AllocateId();
|
||||
state.builder.AddFunction({OpSelect, state.uint_type, result, active,
|
||||
ConstantU32(state, 1), ConstantU32(state, 0)});
|
||||
state.builder.AddFunction({OpSelect, state.uint_type, result, active, ConstantU32(state, 1),
|
||||
ConstantU32(state, 0)});
|
||||
EmitStoreU32(state, inst.dst, result);
|
||||
EmitStoreU32(state, OffsetRegisterOperand(inst.dst, 1), ConstantU32(state, 0));
|
||||
}
|
||||
@@ -205,8 +205,7 @@ void EmitSaveexecB32(EmitterState& state, const IR::Instruction& inst) {
|
||||
|
||||
const auto cond = state.builder.AllocateId();
|
||||
const auto scc = state.builder.AllocateId();
|
||||
state.builder.AddFunction(
|
||||
{OpINotEqual, state.bool_type, cond, new_low, ConstantU32(state, 0)});
|
||||
state.builder.AddFunction({OpINotEqual, state.bool_type, cond, new_low, ConstantU32(state, 0)});
|
||||
state.builder.AddFunction(
|
||||
{OpSelect, state.uint_type, scc, cond, ConstantU32(state, 1), ConstantU32(state, 0)});
|
||||
EmitStoreU32(state, SccOperand(), scc);
|
||||
@@ -279,8 +278,9 @@ void EmitReadFirstLaneU32(EmitterState& state, const IR::Instruction& inst) {
|
||||
|
||||
uint32_t EmitLaneIndex(EmitterState& state, const IR::Operand& operand) {
|
||||
const auto lane = state.builder.AllocateId();
|
||||
const auto mask = state.wave_size == 32u ? 31u : 63u;
|
||||
state.builder.AddFunction({OpBitwiseAnd, state.uint_type, lane, EmitValueLoad(state, operand),
|
||||
ConstantU32(state, 63)});
|
||||
ConstantU32(state, mask)});
|
||||
return lane;
|
||||
}
|
||||
|
||||
@@ -336,10 +336,8 @@ void EmitPermlaneB32(EmitterState& state, const IR::Instruction& inst, bool x16)
|
||||
state.builder.AddFunction(
|
||||
{OpBitwiseXor, state.uint_type, row_value, row, ConstantU32(state, 16)});
|
||||
}
|
||||
state.builder.AddFunction(
|
||||
{OpBitwiseAnd, state.uint_type, lane, subid, ConstantU32(state, 15)});
|
||||
state.builder.AddFunction(
|
||||
{OpBitwiseAnd, state.uint_type, lane8, lane, ConstantU32(state, 7)});
|
||||
state.builder.AddFunction({OpBitwiseAnd, state.uint_type, lane, subid, ConstantU32(state, 15)});
|
||||
state.builder.AddFunction({OpBitwiseAnd, state.uint_type, lane8, lane, ConstantU32(state, 7)});
|
||||
state.builder.AddFunction(
|
||||
{OpShiftLeftLogical, state.uint_type, shift, lane8, ConstantU32(state, 2)});
|
||||
state.builder.AddFunction(
|
||||
|
||||
@@ -2,6 +2,66 @@
|
||||
#include "graphics/shader/recompiler/emitter/spirvEmitterInternal.h"
|
||||
|
||||
namespace Libs::Graphics::ShaderRecompiler::Spirv::Emitter {
|
||||
namespace {
|
||||
|
||||
uint32_t EmitCubeAxisF32(EmitterState& state, uint32_t value) {
|
||||
const auto normalized = state.builder.AllocateId();
|
||||
state.builder.AddFunction(
|
||||
{OpFSub, state.float_type, normalized, value, ConstantF32(state, 0x3f800000u)});
|
||||
return normalized;
|
||||
}
|
||||
|
||||
uint32_t EmitCubeLayerF32(EmitterState& state, uint32_t face_id) {
|
||||
// Sampled RDNA2 cubemaps encode face_id as slice * 8 + face. The native
|
||||
// 2D-array view stores six contiguous faces per slice, so remove the two
|
||||
// reserved face IDs from every preceding slice.
|
||||
const auto guest_layer = state.builder.AllocateId();
|
||||
const auto slice = state.builder.AllocateId();
|
||||
const auto padding = state.builder.AllocateId();
|
||||
const auto host_layer = state.builder.AllocateId();
|
||||
const auto result = state.builder.AllocateId();
|
||||
state.builder.AddFunction({OpConvertFToU, state.uint_type, guest_layer, face_id});
|
||||
state.builder.AddFunction(
|
||||
{OpShiftRightLogical, state.uint_type, slice, guest_layer, ConstantU32(state, 3)});
|
||||
state.builder.AddFunction(
|
||||
{OpShiftLeftLogical, state.uint_type, padding, slice, ConstantU32(state, 1)});
|
||||
state.builder.AddFunction({OpISub, state.uint_type, host_layer, guest_layer, padding});
|
||||
state.builder.AddFunction({OpConvertUToF, state.float_type, result, host_layer});
|
||||
return result;
|
||||
}
|
||||
|
||||
uint32_t EmitImageCoordF32Impl(EmitterState& state, const IR::Instruction& inst,
|
||||
const IR::Operand& address, uint32_t first_component,
|
||||
uint32_t components) {
|
||||
auto x = EmitImageAddressFloatLoad(state, inst, address, first_component);
|
||||
if (components == 1u) {
|
||||
return x;
|
||||
}
|
||||
auto y = inst.memory.image_address_components > first_component + 1u
|
||||
? EmitImageAddressFloatLoad(state, inst, address, first_component + 1u)
|
||||
: EmitZeroF32(state);
|
||||
if (inst.memory.image_cube) {
|
||||
// RDNA2 sampled cubemap S/T coordinates are biased by +1 relative to
|
||||
// normalized 2D-array coordinates.
|
||||
x = EmitCubeAxisF32(state, x);
|
||||
y = EmitCubeAxisF32(state, y);
|
||||
}
|
||||
const auto coord = state.builder.AllocateId();
|
||||
if (components == 3u) {
|
||||
auto z = inst.memory.image_address_components > first_component + 2u
|
||||
? EmitImageAddressFloatLoad(state, inst, address, first_component + 2u)
|
||||
: EmitZeroF32(state);
|
||||
if (inst.memory.image_cube) {
|
||||
z = EmitCubeLayerF32(state, z);
|
||||
}
|
||||
state.builder.AddFunction({OpCompositeConstruct, state.vec3_float_type, coord, x, y, z});
|
||||
} else {
|
||||
state.builder.AddFunction({OpCompositeConstruct, state.vec2_float_type, coord, x, y});
|
||||
}
|
||||
return coord;
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
bool HasImageSampleFlag(const IR::Instruction& inst, uint32_t flag) {
|
||||
return (inst.memory.image_sample_flags & flag) != 0;
|
||||
@@ -36,24 +96,8 @@ ImageSampleLayout MakeImageSampleLayout(const IR::Instruction& inst, ImageViewKi
|
||||
|
||||
uint32_t EmitImageCoordF32(EmitterState& state, const IR::Instruction& inst,
|
||||
const ImageSampleLayout& layout, ImageViewKind view) {
|
||||
const auto x = EmitImageAddressFloatLoad(state, inst, inst.src[0], layout.coord);
|
||||
const auto components = ImageViewCoordinateComponents(view);
|
||||
if (components == 1u) {
|
||||
return x;
|
||||
}
|
||||
const auto y = inst.memory.image_address_components > layout.coord + 1u
|
||||
? EmitImageAddressFloatLoad(state, inst, inst.src[0], layout.coord + 1u)
|
||||
: EmitZeroF32(state);
|
||||
const auto coord = state.builder.AllocateId();
|
||||
if (components == 3u) {
|
||||
const auto z = inst.memory.image_address_components > layout.coord + 2u
|
||||
? EmitImageAddressFloatLoad(state, inst, inst.src[0], layout.coord + 2u)
|
||||
: EmitZeroF32(state);
|
||||
state.builder.AddFunction({OpCompositeConstruct, state.vec3_float_type, coord, x, y, z});
|
||||
} else {
|
||||
state.builder.AddFunction({OpCompositeConstruct, state.vec2_float_type, coord, x, y});
|
||||
}
|
||||
return coord;
|
||||
return EmitImageCoordF32Impl(state, inst, inst.src[0], layout.coord,
|
||||
ImageViewCoordinateComponents(view));
|
||||
}
|
||||
|
||||
uint32_t EmitImageLodF32(EmitterState& state, const IR::Instruction& inst,
|
||||
@@ -95,9 +139,9 @@ uint32_t EmitImageGradientF32(EmitterState& state, const IR::Instruction& inst,
|
||||
: EmitZeroF32(state);
|
||||
const auto grad = state.builder.AllocateId();
|
||||
if (components == 3u) {
|
||||
const auto z = inst.memory.image_address_components > first_component + 2u
|
||||
? EmitImageAddressFloatLoad(state, inst, inst.src[0],
|
||||
first_component + 2u)
|
||||
const auto z =
|
||||
inst.memory.image_address_components > first_component + 2u
|
||||
? EmitImageAddressFloatLoad(state, inst, inst.src[0], first_component + 2u)
|
||||
: EmitZeroF32(state);
|
||||
state.builder.AddFunction({OpCompositeConstruct, state.vec3_float_type, grad, x, y, z});
|
||||
} else {
|
||||
@@ -120,8 +164,7 @@ uint32_t EmitImagePackedOffsetI32(EmitterState& state, const IR::Instruction& in
|
||||
state.builder.AddFunction(
|
||||
{OpCompositeConstruct, state.vec3_int_type, ret, zero, zero, zero});
|
||||
} else {
|
||||
state.builder.AddFunction(
|
||||
{OpCompositeConstruct, state.vec2_int_type, ret, zero, zero});
|
||||
state.builder.AddFunction({OpCompositeConstruct, state.vec2_int_type, ret, zero, zero});
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
@@ -209,24 +252,8 @@ uint32_t EmitImageMipLodU32(EmitterState& state, const IR::Instruction& inst,
|
||||
|
||||
uint32_t EmitImageQueryCoordF32(EmitterState& state, const IR::Instruction& inst,
|
||||
ImageViewKind view) {
|
||||
const auto x = EmitImageAddressFloatLoad(state, inst, inst.src[0], 0);
|
||||
const auto components = ImageViewCoordinateComponents(view);
|
||||
if (components == 1u) {
|
||||
return x;
|
||||
}
|
||||
const auto y = inst.memory.image_address_components > 1u
|
||||
? EmitImageAddressFloatLoad(state, inst, inst.src[0], 1)
|
||||
: EmitZeroF32(state);
|
||||
const auto coord = state.builder.AllocateId();
|
||||
if (components == 3u) {
|
||||
const auto z = inst.memory.image_address_components > 2u
|
||||
? EmitImageAddressFloatLoad(state, inst, inst.src[0], 2)
|
||||
: EmitZeroF32(state);
|
||||
state.builder.AddFunction({OpCompositeConstruct, state.vec3_float_type, coord, x, y, z});
|
||||
} else {
|
||||
state.builder.AddFunction({OpCompositeConstruct, state.vec2_float_type, coord, x, y});
|
||||
}
|
||||
return coord;
|
||||
// OpImageQueryLod takes only the spatial coordinates, even for arrayed images.
|
||||
return EmitImageCoordF32Impl(state, inst, inst.src[0], 0, ImageViewSpatialComponents(view));
|
||||
}
|
||||
|
||||
uint32_t DmaskComponentIndex(uint32_t dmask, uint32_t component) {
|
||||
|
||||
@@ -133,10 +133,18 @@ void EmitImageLoad(EmitterState& state, const IR::Instruction& inst) {
|
||||
const bool integer = inst.memory.kind == IR::ResourceKind::ImageUint;
|
||||
|
||||
const auto color = state.builder.AllocateId();
|
||||
state.builder.AddFunction({OpImageFetch, integer ? state.vec4_uint_type : state.vec4_float_type,
|
||||
color, image, EmitImageLoadCoordU32(state, inst, view),
|
||||
ImageOperandsLodMask,
|
||||
EmitImageMipLodU32(state, inst, inst.src[0], view)});
|
||||
const auto coord = EmitImageLoadCoordU32(state, inst, view);
|
||||
if (ImageSpirvMultisampled(view) != 0) {
|
||||
const auto sample = EmitImageAddressValueLoad(state, inst, inst.src[0],
|
||||
ImageViewCoordinateComponents(view));
|
||||
state.builder.AddFunction({OpImageFetch,
|
||||
integer ? state.vec4_uint_type : state.vec4_float_type, color,
|
||||
image, coord, ImageOperandsSampleMask, sample});
|
||||
} else {
|
||||
state.builder.AddFunction(
|
||||
{OpImageFetch, integer ? state.vec4_uint_type : state.vec4_float_type, color, image,
|
||||
coord, ImageOperandsLodMask, EmitImageMipLodU32(state, inst, inst.src[0], view)});
|
||||
}
|
||||
|
||||
const auto dmask = inst.memory.dmask != 0 ? inst.memory.dmask : 1u;
|
||||
uint32_t dst_index = 0;
|
||||
@@ -158,8 +166,8 @@ void EmitImageLoad(EmitterState& state, const IR::Instruction& inst) {
|
||||
void EmitImageStore(EmitterState& state, const IR::Instruction& inst) {
|
||||
const auto uint_image = inst.memory.kind == IR::ResourceKind::StorageImageUint;
|
||||
const auto view = StorageImageViewKind(state, inst.memory, uint_image, inst.pc);
|
||||
const auto binding = ResourceForDescriptor(state, StorageBindingKind(uint_image, view),
|
||||
inst.memory.resource);
|
||||
const auto binding =
|
||||
ResourceForDescriptor(state, StorageBindingKind(uint_image, view), inst.memory.resource);
|
||||
const auto image = LoadStorageImageDescriptorAtIndex(state, inst.memory.resource,
|
||||
binding.array_index, uint_image, view);
|
||||
|
||||
|
||||
@@ -3,11 +3,11 @@
|
||||
|
||||
#include "common/common.h"
|
||||
#include "common/stringUtils.h"
|
||||
#include "graphics/shader/recompiler/ir/BindingLayout.h"
|
||||
#include "graphics/shader/recompiler/BufferFormat.h"
|
||||
#include "graphics/shader/recompiler/emitter/SpirvBuilder.h"
|
||||
#include "graphics/shader/recompiler/ir/BindingLayout.h"
|
||||
#include "graphics/shader/recompiler/ir/ResourceMaterialization.h"
|
||||
#include "graphics/shader/recompiler/ir/ShaderIR.h"
|
||||
#include "graphics/shader/recompiler/emitter/SpirvBuilder.h"
|
||||
|
||||
#include <algorithm>
|
||||
#include <array>
|
||||
@@ -99,6 +99,7 @@ enum : uint32_t {
|
||||
ImageOperandsGradMask = 0x00000004u,
|
||||
ImageOperandsOffsetMask = 0x00000010u,
|
||||
ImageOperandsConstOffsetsMask = 0x00000020u,
|
||||
ImageOperandsSampleMask = 0x00000040u,
|
||||
};
|
||||
|
||||
enum : uint32_t {
|
||||
@@ -150,7 +151,6 @@ enum : uint32_t {
|
||||
OpImageGather = 96,
|
||||
OpImageDrefGather = 97,
|
||||
OpImageWrite = 99,
|
||||
OpImage = 100,
|
||||
OpImageQuerySizeLod = 103,
|
||||
OpImageQueryLod = 105,
|
||||
OpImageQueryLevels = 106,
|
||||
@@ -382,7 +382,7 @@ struct EmitterState {
|
||||
uint32_t ptr_workgroup_array = 0;
|
||||
uint32_t ptr_workgroup_uint = 0;
|
||||
uint32_t lds_variable = 0;
|
||||
std::array<SampledImageDescriptors, 10> sampled_images;
|
||||
std::array<SampledImageDescriptors, 14> sampled_images;
|
||||
std::array<StorageImageDescriptors, 10> storage_images;
|
||||
uint32_t sampler_type = 0;
|
||||
uint32_t sampler_array_type = 0;
|
||||
@@ -425,9 +425,6 @@ struct EmitterState {
|
||||
std::map<uint32_t, uint32_t> float_constants;
|
||||
};
|
||||
|
||||
constexpr uint32_t PsInputOffsetMask = 0x0000001fu;
|
||||
constexpr uint32_t PsInputFlatShade = 0x00000400u;
|
||||
|
||||
enum class VertexInputScalarKind { Float, Sint, Uint };
|
||||
|
||||
constexpr uint32_t NoImageComponent = 0xffffffffu;
|
||||
@@ -453,17 +450,20 @@ enum class ImageViewKind {
|
||||
Dim2D,
|
||||
Dim2DArray,
|
||||
Dim3D,
|
||||
Dim2DMsaa,
|
||||
Dim2DMsaaArray,
|
||||
Count,
|
||||
};
|
||||
|
||||
constexpr uint32_t ImageViewKindCount = static_cast<uint32_t>(ImageViewKind::Count);
|
||||
constexpr uint32_t SampledImageViewKindCount = static_cast<uint32_t>(ImageViewKind::Count);
|
||||
constexpr uint32_t StorageImageViewKindCount = static_cast<uint32_t>(ImageViewKind::Dim2DMsaa);
|
||||
|
||||
constexpr uint32_t SampledImageIndex(bool integer, ImageViewKind view) {
|
||||
return static_cast<uint32_t>(view) + (integer ? ImageViewKindCount : 0u);
|
||||
return static_cast<uint32_t>(view) + (integer ? SampledImageViewKindCount : 0u);
|
||||
}
|
||||
|
||||
constexpr uint32_t StorageImageIndex(bool integer, ImageViewKind view) {
|
||||
return static_cast<uint32_t>(view) + (integer ? ImageViewKindCount : 0u);
|
||||
return static_cast<uint32_t>(view) + (integer ? StorageImageViewKindCount : 0u);
|
||||
}
|
||||
|
||||
constexpr IR::DescriptorBindingKind SampledBindingKind(bool integer, ImageViewKind view) {
|
||||
@@ -474,6 +474,9 @@ constexpr IR::DescriptorBindingKind SampledBindingKind(bool integer, ImageViewKi
|
||||
case ImageViewKind::Dim2D: return IR::DescriptorBindingKind::SampledUint2D;
|
||||
case ImageViewKind::Dim2DArray: return IR::DescriptorBindingKind::SampledUint2DArray;
|
||||
case ImageViewKind::Dim3D: return IR::DescriptorBindingKind::SampledUint3D;
|
||||
case ImageViewKind::Dim2DMsaa: return IR::DescriptorBindingKind::SampledUint2DMsaa;
|
||||
case ImageViewKind::Dim2DMsaaArray:
|
||||
return IR::DescriptorBindingKind::SampledUint2DMsaaArray;
|
||||
default: break;
|
||||
}
|
||||
}
|
||||
@@ -483,6 +486,8 @@ constexpr IR::DescriptorBindingKind SampledBindingKind(bool integer, ImageViewKi
|
||||
case ImageViewKind::Dim2D: return IR::DescriptorBindingKind::Sampled2D;
|
||||
case ImageViewKind::Dim2DArray: return IR::DescriptorBindingKind::Sampled2DArray;
|
||||
case ImageViewKind::Dim3D: return IR::DescriptorBindingKind::Sampled3D;
|
||||
case ImageViewKind::Dim2DMsaa: return IR::DescriptorBindingKind::Sampled2DMsaa;
|
||||
case ImageViewKind::Dim2DMsaaArray: return IR::DescriptorBindingKind::Sampled2DMsaaArray;
|
||||
default: break;
|
||||
}
|
||||
return IR::DescriptorBindingKind::Count;
|
||||
@@ -516,6 +521,8 @@ constexpr uint32_t ImageSpirvDimension(ImageViewKind view) {
|
||||
case ImageViewKind::Dim1DArray: return Dim1D;
|
||||
case ImageViewKind::Dim2D:
|
||||
case ImageViewKind::Dim2DArray:
|
||||
case ImageViewKind::Dim2DMsaa:
|
||||
case ImageViewKind::Dim2DMsaaArray:
|
||||
case ImageViewKind::Count: return Dim2D;
|
||||
case ImageViewKind::Dim3D: return Dim3D;
|
||||
}
|
||||
@@ -523,7 +530,14 @@ constexpr uint32_t ImageSpirvDimension(ImageViewKind view) {
|
||||
}
|
||||
|
||||
constexpr uint32_t ImageSpirvArrayed(ImageViewKind view) {
|
||||
return view == ImageViewKind::Dim1DArray || view == ImageViewKind::Dim2DArray ? 1u : 0u;
|
||||
return view == ImageViewKind::Dim1DArray || view == ImageViewKind::Dim2DArray ||
|
||||
view == ImageViewKind::Dim2DMsaaArray
|
||||
? 1u
|
||||
: 0u;
|
||||
}
|
||||
|
||||
constexpr uint32_t ImageSpirvMultisampled(ImageViewKind view) {
|
||||
return view == ImageViewKind::Dim2DMsaa || view == ImageViewKind::Dim2DMsaaArray ? 1u : 0u;
|
||||
}
|
||||
|
||||
struct AddCarryResult {
|
||||
@@ -990,11 +1004,6 @@ uint32_t NormalizeFormatComponent(EmitterState& state, const Format::BufferForma
|
||||
uint32_t UnpackTBufferFormat(EmitterState& state, const IR::Instruction& inst,
|
||||
const Format::BufferFormatInfo& info);
|
||||
|
||||
bool EmitTypedTBufferLoad(EmitterState& state, const IR::Instruction& inst,
|
||||
const Format::BufferFormatInfo& info);
|
||||
|
||||
bool EmitFormattedBufferLoad(EmitterState& state, const IR::Instruction& inst);
|
||||
|
||||
uint32_t FormattedBufferDwordStoreComponentCount(Prospero::BufferFormat format,
|
||||
uint32_t opcode_components);
|
||||
|
||||
@@ -1020,6 +1029,9 @@ void EmitBufferLoadSshort(EmitterState& state, const IR::Instruction& inst);
|
||||
|
||||
void EmitBufferLoadDword(EmitterState& state, const IR::Instruction& inst);
|
||||
|
||||
void EmitBufferLoadDwordGroup(EmitterState& state, const IR::Instruction* instructions,
|
||||
uint32_t count);
|
||||
|
||||
void EmitBufferStoreDword(EmitterState& state, const IR::Instruction& inst);
|
||||
|
||||
void EmitFlatLoadUbyte(EmitterState& state, const IR::Instruction& inst);
|
||||
|
||||
@@ -34,16 +34,15 @@ uint32_t EmitDppWriteActiveBool(EmitterState& state, const IR::Operand& dst) {
|
||||
{OpShiftLeftLogical, state.uint_type, bank_bit, ConstantU32(state, 1), bank});
|
||||
state.builder.AddFunction(
|
||||
{OpShiftLeftLogical, state.uint_type, row_bit, ConstantU32(state, 1), row});
|
||||
state.builder.AddFunction({OpBitwiseAnd, state.uint_type, bank_hit,
|
||||
ConstantU32(state, dst.dpp_bank_mask), bank_bit});
|
||||
state.builder.AddFunction(
|
||||
{OpBitwiseAnd, state.uint_type, bank_hit, ConstantU32(state, dst.dpp_bank_mask), bank_bit});
|
||||
state.builder.AddFunction(
|
||||
{OpBitwiseAnd, state.uint_type, row_hit, ConstantU32(state, dst.dpp_row_mask), row_bit});
|
||||
state.builder.AddFunction(
|
||||
{OpINotEqual, state.bool_type, bank_active, bank_hit, ConstantU32(state, 0)});
|
||||
state.builder.AddFunction(
|
||||
{OpINotEqual, state.bool_type, row_active, row_hit, ConstantU32(state, 0)});
|
||||
state.builder.AddFunction(
|
||||
{OpLogicalAnd, state.bool_type, dpp_active, bank_active, row_active});
|
||||
state.builder.AddFunction({OpLogicalAnd, state.bool_type, dpp_active, bank_active, row_active});
|
||||
uint32_t write_active = dpp_active;
|
||||
if (!dst.dpp_bound_ctrl) {
|
||||
const auto target = EmitDppTargetLane(state, dst.dpp_ctrl);
|
||||
@@ -131,8 +130,7 @@ uint32_t EmitNotEqualZeroBool(EmitterState& state, uint32_t value) {
|
||||
uint32_t EmitSelectU32Value(EmitterState& state, uint32_t condition, uint32_t true_value,
|
||||
uint32_t false_value) {
|
||||
const auto ret = state.builder.AllocateId();
|
||||
state.builder.AddFunction(
|
||||
{OpSelect, state.uint_type, ret, condition, true_value, false_value});
|
||||
state.builder.AddFunction({OpSelect, state.uint_type, ret, condition, true_value, false_value});
|
||||
return ret;
|
||||
}
|
||||
|
||||
@@ -212,12 +210,12 @@ bool IsStorageBufferMemoryKind(IR::ResourceKind kind) {
|
||||
|
||||
void EmitStorageBufferOffsets(EmitterState& state) {
|
||||
for (uint32_t i = 0; i < state.program.bindings.buffer_offset_count; i++) {
|
||||
const auto word = EmitShaderDataDwordLoad(
|
||||
state, state.program.bindings.buffer_offset_dword + i / 4u);
|
||||
const auto word =
|
||||
EmitShaderDataDwordLoad(state, state.program.bindings.buffer_offset_dword + i / 4u);
|
||||
const auto shift = ConstantU32(state, (i % 4u) * 8u + 2u);
|
||||
state.storage_buffer_offsets[i] = EmitBinaryU32(
|
||||
state, OpBitwiseAnd,
|
||||
EmitBinaryU32(state, OpShiftRightLogical, word, shift), ConstantU32(state, 0x3fu));
|
||||
state, OpBitwiseAnd, EmitBinaryU32(state, OpShiftRightLogical, word, shift),
|
||||
ConstantU32(state, 0x3fu));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -329,8 +327,7 @@ uint32_t EmitRelativeAddress(EmitterState& state, const IR::Instruction& inst, u
|
||||
|
||||
uint32_t EmitFlatVirtualAddress(EmitterState& state, const IR::Instruction& inst,
|
||||
uint32_t first_src, uint32_t src_count) {
|
||||
if (inst.memory.resource >= state.resources.addresses.size() ||
|
||||
src_count < 2) {
|
||||
if (inst.memory.resource >= state.resources.addresses.size() || src_count < 2) {
|
||||
ExitDescriptorBindingFailure(state, IR::DescriptorBindingKind::AddressMemory,
|
||||
inst.memory.resource, "flat address snapshot is missing");
|
||||
}
|
||||
@@ -659,8 +656,7 @@ void EmitAtomicUpdateU32(EmitterState& state, uint32_t pointer, IR::ResourceKind
|
||||
state.builder.AddFunction({OpBranch, preheader});
|
||||
state.builder.AddFunction({OpLabel, preheader});
|
||||
state.builder.AddFunction({OpAtomicLoad, state.uint_type, initial, pointer,
|
||||
ConstantU32(state, scope),
|
||||
ConstantU32(state, MemorySemanticsNone)});
|
||||
ConstantU32(state, scope), ConstantU32(state, MemorySemanticsNone)});
|
||||
state.builder.AddFunction({OpBranch, header});
|
||||
state.builder.AddFunction({OpLabel, header});
|
||||
state.builder.AddFunction(
|
||||
@@ -793,8 +789,7 @@ uint32_t EmitTBufferBitcastU32ToI32(EmitterState& state, uint32_t value) {
|
||||
uint32_t EmitTBufferCompareU32Constant(EmitterState& state, uint32_t opcode, uint32_t value,
|
||||
uint32_t constant) {
|
||||
const auto ret = state.builder.AllocateId();
|
||||
state.builder.AddFunction(
|
||||
{opcode, state.bool_type, ret, value, ConstantU32(state, constant)});
|
||||
state.builder.AddFunction({opcode, state.bool_type, ret, value, ConstantU32(state, constant)});
|
||||
return ret;
|
||||
}
|
||||
|
||||
@@ -961,18 +956,8 @@ uint32_t UnpackTBufferFormat(EmitterState& state, const IR::Instruction& inst,
|
||||
return NormalizeFormatComponent(state, info, inst.memory.component_index, raw);
|
||||
}
|
||||
|
||||
bool EmitTypedTBufferLoad(EmitterState& state, const IR::Instruction& inst,
|
||||
const Format::BufferFormatInfo& info) {
|
||||
if (!Format::CanUseTypedBufferLoad(info.format)) {
|
||||
return false;
|
||||
}
|
||||
const auto value = EmitMemoryLoadDwordValueU32(state, inst, IR::ResourceKind::Buffer, 0,
|
||||
AddressSourceCount(inst, 0));
|
||||
EmitStoreU32(state, inst.dst, value);
|
||||
return true;
|
||||
}
|
||||
|
||||
bool EmitFormattedBufferLoad(EmitterState& state, const IR::Instruction& inst) {
|
||||
bool EmitFormattedBufferLoadValueU32(EmitterState& state, const IR::Instruction& inst,
|
||||
uint32_t& value) {
|
||||
if (!IsFormattedBufferComponent(inst)) {
|
||||
return false;
|
||||
}
|
||||
@@ -984,18 +969,29 @@ bool EmitFormattedBufferLoad(EmitterState& state, const IR::Instruction& inst) {
|
||||
|
||||
const auto info = Format::GetFormatInfo(format);
|
||||
if (inst.memory.component_index >= info.component_count) {
|
||||
EmitStoreU32(state, inst.dst, ConstantU32(state, 0));
|
||||
value = ConstantU32(state, 0);
|
||||
return true;
|
||||
}
|
||||
|
||||
if (EmitTypedTBufferLoad(state, inst, info)) {
|
||||
if (Format::CanUseTypedBufferLoad(info.format)) {
|
||||
value = EmitMemoryLoadDwordValueU32(state, inst, IR::ResourceKind::Buffer, 0,
|
||||
AddressSourceCount(inst, 0));
|
||||
return true;
|
||||
}
|
||||
|
||||
EmitStoreU32(state, inst.dst, UnpackTBufferFormat(state, inst, info));
|
||||
value = UnpackTBufferFormat(state, inst, info);
|
||||
return true;
|
||||
}
|
||||
|
||||
uint32_t EmitBufferLoadDwordValueU32(EmitterState& state, const IR::Instruction& inst) {
|
||||
uint32_t value = 0;
|
||||
if (EmitFormattedBufferLoadValueU32(state, inst, value)) {
|
||||
return value;
|
||||
}
|
||||
return EmitMemoryLoadDwordValueU32(state, inst, IR::ResourceKind::Buffer, 0,
|
||||
AddressSourceCount(inst, 0));
|
||||
}
|
||||
|
||||
uint32_t FormattedBufferDwordStoreComponentCount(Prospero::BufferFormat format,
|
||||
uint32_t opcode_components) {
|
||||
switch (format) {
|
||||
@@ -1262,11 +1258,27 @@ void EmitBufferLoadSshort(EmitterState& state, const IR::Instruction& inst) {
|
||||
}
|
||||
|
||||
void EmitBufferLoadDword(EmitterState& state, const IR::Instruction& inst) {
|
||||
EmitGuardedByExec(state, [&]() {
|
||||
if (EmitFormattedBufferLoad(state, inst)) {
|
||||
EmitGuardedByExec(
|
||||
state, [&]() { EmitStoreU32(state, inst.dst, EmitBufferLoadDwordValueU32(state, inst)); });
|
||||
}
|
||||
|
||||
void EmitBufferLoadDwordGroup(EmitterState& state, const IR::Instruction* instructions,
|
||||
uint32_t count) {
|
||||
if (instructions == nullptr || count == 0u) {
|
||||
return;
|
||||
}
|
||||
EmitMemoryLoadU32(state, inst, IR::ResourceKind::Buffer, 0, AddressSourceCount(inst, 0));
|
||||
|
||||
EmitGuardedByExec(state, [&]() {
|
||||
// RDNA VMEM captures every VADDR component before making overlapping VDATA writes
|
||||
// visible. Keep the split IR components instruction-atomic by deferring all stores.
|
||||
std::vector<uint32_t> values;
|
||||
values.reserve(count);
|
||||
for (uint32_t i = 0; i < count; i++) {
|
||||
values.push_back(EmitBufferLoadDwordValueU32(state, instructions[i]));
|
||||
}
|
||||
for (uint32_t i = 0; i < count; i++) {
|
||||
EmitStoreU32(state, instructions[i].dst, values[i]);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@@ -1482,8 +1494,7 @@ void EmitDsAppendConsume(EmitterState& state, const IR::Instruction& inst, uint3
|
||||
const auto do_atomic = state.builder.AllocateId();
|
||||
state.builder.AddFunction({OpIEqual, state.bool_type, first_lane, subid, exec.first_lane});
|
||||
const auto first_active = EmitLogicalAndBool(state, first_lane, exec.any_active);
|
||||
state.builder.AddFunction(
|
||||
{OpLogicalAnd, state.bool_type, do_atomic, first_active, in_bounds});
|
||||
state.builder.AddFunction({OpLogicalAnd, state.bool_type, do_atomic, first_active, in_bounds});
|
||||
|
||||
const auto atomic_value = EmitValueOrZeroIfCondition(state, do_atomic, [&]() {
|
||||
const auto pointer = gds ? EmitGdsElementPointer(state, address.index)
|
||||
@@ -1575,14 +1586,13 @@ 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(
|
||||
{OpBitwiseAnd, state.uint_type, lane, subid, ConstantU32(state, 31)});
|
||||
state.builder.AddFunction({OpBitwiseAnd, state.uint_type, lane, subid, ConstantU32(state, 31)});
|
||||
state.builder.AddFunction(
|
||||
{OpBitwiseAnd, state.uint_type, masked, lane, ConstantU32(state, control & 0x1fu)});
|
||||
state.builder.AddFunction(
|
||||
{OpBitwiseOr, state.uint_type, ored, masked, ConstantU32(state, (control >> 5u) & 0x1fu)});
|
||||
state.builder.AddFunction({OpBitwiseXor, state.uint_type, xored, ored,
|
||||
ConstantU32(state, (control >> 10u) & 0x1fu)});
|
||||
state.builder.AddFunction(
|
||||
{OpBitwiseXor, state.uint_type, xored, ored, ConstantU32(state, (control >> 10u) & 0x1fu)});
|
||||
state.builder.AddFunction(
|
||||
{OpBitwiseAnd, state.uint_type, base, subid, ConstantU32(state, 0xffffffe0u)});
|
||||
state.builder.AddFunction({OpBitwiseOr, state.uint_type, target, base, xored});
|
||||
|
||||
@@ -174,6 +174,12 @@ uint32_t VertexParameterInputPointerType(const EmitterState& state, VertexInputS
|
||||
}
|
||||
}
|
||||
|
||||
static bool MrtUsesUintOutput(const EmitterState& state, uint32_t index) {
|
||||
return state.stage == ShaderType::Pixel && state.pixel_input_info != nullptr &&
|
||||
index < std::size(state.pixel_input_info->target_output_mode) &&
|
||||
state.pixel_input_info->target_output_mode[index] == 7u;
|
||||
}
|
||||
|
||||
void AllocateInputVariables(EmitterState& state) {
|
||||
for (auto& binding: state.inputs) {
|
||||
binding.variable_id = state.builder.AllocateId();
|
||||
@@ -323,23 +329,39 @@ void AddDescriptorAnnotationsAndNames(EmitterState& state) {
|
||||
Decorate(state.address_memory_variable, "address_memory",
|
||||
IR::DescriptorBindingKind::AddressMemory);
|
||||
}
|
||||
constexpr const char* SampledNames[] = {
|
||||
"sampled_1d", "sampled_1d_array", "sampled_2d", "sampled_2d_array",
|
||||
"sampled_3d", "sampled_uint_1d", "sampled_uint_1d_array",
|
||||
"sampled_uint_2d", "sampled_uint_2d_array", "sampled_uint_3d"};
|
||||
constexpr const char* SampledNames[] = {"sampled_1d",
|
||||
"sampled_1d_array",
|
||||
"sampled_2d",
|
||||
"sampled_2d_array",
|
||||
"sampled_3d",
|
||||
"sampled_2d_msaa",
|
||||
"sampled_2d_msaa_array",
|
||||
"sampled_uint_1d",
|
||||
"sampled_uint_1d_array",
|
||||
"sampled_uint_2d",
|
||||
"sampled_uint_2d_array",
|
||||
"sampled_uint_3d",
|
||||
"sampled_uint_2d_msaa",
|
||||
"sampled_uint_2d_msaa_array"};
|
||||
for (uint32_t i = 0; i < state.sampled_images.size(); i++) {
|
||||
const auto view = static_cast<ImageViewKind>(i % ImageViewKindCount);
|
||||
const auto view = static_cast<ImageViewKind>(i % SampledImageViewKindCount);
|
||||
Decorate(state.sampled_images[i].variable, SampledNames[i],
|
||||
SampledBindingKind(i >= ImageViewKindCount, view));
|
||||
SampledBindingKind(i >= SampledImageViewKindCount, view));
|
||||
}
|
||||
constexpr const char* StorageNames[] = {
|
||||
"storage_1d", "storage_1d_array", "storage_2d", "storage_2d_array",
|
||||
"storage_3d", "storage_uint_1d", "storage_uint_1d_array",
|
||||
"storage_uint_2d", "storage_uint_2d_array", "storage_uint_3d"};
|
||||
constexpr const char* StorageNames[] = {"storage_1d",
|
||||
"storage_1d_array",
|
||||
"storage_2d",
|
||||
"storage_2d_array",
|
||||
"storage_3d",
|
||||
"storage_uint_1d",
|
||||
"storage_uint_1d_array",
|
||||
"storage_uint_2d",
|
||||
"storage_uint_2d_array",
|
||||
"storage_uint_3d"};
|
||||
for (uint32_t i = 0; i < state.storage_images.size(); i++) {
|
||||
const auto view = static_cast<ImageViewKind>(i % ImageViewKindCount);
|
||||
const auto view = static_cast<ImageViewKind>(i % StorageImageViewKindCount);
|
||||
Decorate(state.storage_images[i].variable, StorageNames[i],
|
||||
StorageBindingKind(i >= ImageViewKindCount, view));
|
||||
StorageBindingKind(i >= StorageImageViewKindCount, view));
|
||||
}
|
||||
if (state.sampler_variable != 0) {
|
||||
Decorate(state.sampler_variable, "samplers", IR::DescriptorBindingKind::Samplers);
|
||||
@@ -409,6 +431,7 @@ void EmitHeaderAndTypes(EmitterState& state) {
|
||||
state.ptr_output_sample_mask_array = state.builder.AllocateId();
|
||||
state.ptr_output_float = state.builder.AllocateId();
|
||||
state.ptr_output_vec4_float = state.builder.AllocateId();
|
||||
const auto ptr_output_vec4_uint = state.builder.AllocateId();
|
||||
state.per_vertex_type = state.builder.AllocateId();
|
||||
state.ptr_output_per_vertex = state.builder.AllocateId();
|
||||
state.storage_runtime_array_type = state.builder.AllocateId();
|
||||
@@ -462,7 +485,7 @@ void EmitHeaderAndTypes(EmitterState& state) {
|
||||
state.builder.AddCapability({CapabilityImageGatherExtended});
|
||||
}
|
||||
if (std::any_of(state.storage_images.begin(),
|
||||
state.storage_images.begin() + ImageViewKindCount,
|
||||
state.storage_images.begin() + StorageImageViewKindCount,
|
||||
[](const auto& image) { return image.variable != 0; })) {
|
||||
state.builder.AddCapability({CapabilityStorageImageReadWithoutFormat});
|
||||
state.builder.AddCapability({CapabilityStorageImageWriteWithoutFormat});
|
||||
@@ -605,6 +628,8 @@ void EmitHeaderAndTypes(EmitterState& state) {
|
||||
{OpTypePointer, state.ptr_output_int, StorageClassOutput, state.int_type});
|
||||
state.builder.AddType(
|
||||
{OpTypePointer, state.ptr_output_vec4_float, StorageClassOutput, state.vec4_float_type});
|
||||
state.builder.AddType(
|
||||
{OpTypePointer, ptr_output_vec4_uint, StorageClassOutput, state.vec4_uint_type});
|
||||
if (state.per_vertex_variable != 0) {
|
||||
state.builder.AddType({OpTypeStruct, state.per_vertex_type, state.vec4_float_type});
|
||||
state.builder.AddType({OpTypePointer, state.ptr_output_per_vertex, StorageClassOutput,
|
||||
@@ -615,8 +640,12 @@ void EmitHeaderAndTypes(EmitterState& state) {
|
||||
for (const auto& binding: state.outputs) {
|
||||
if (binding.kind == IR::StageOutputKind::Parameter ||
|
||||
binding.kind == IR::StageOutputKind::Mrt) {
|
||||
const auto pointer_type =
|
||||
binding.kind == IR::StageOutputKind::Mrt && MrtUsesUintOutput(state, binding.index)
|
||||
? ptr_output_vec4_uint
|
||||
: state.ptr_output_vec4_float;
|
||||
state.builder.AddType(
|
||||
{OpVariable, state.ptr_output_vec4_float, binding.variable_id, StorageClassOutput});
|
||||
{OpVariable, pointer_type, binding.variable_id, StorageClassOutput});
|
||||
}
|
||||
}
|
||||
if (state.depth_variable != 0) {
|
||||
@@ -700,11 +729,11 @@ void EmitHeaderAndTypes(EmitterState& state) {
|
||||
}
|
||||
for (uint32_t i = 0; i < state.sampled_images.size(); i++) {
|
||||
auto& image = state.sampled_images[i];
|
||||
const auto view = static_cast<ImageViewKind>(i % ImageViewKindCount);
|
||||
const bool integer = i >= ImageViewKindCount;
|
||||
const auto view = static_cast<ImageViewKind>(i % SampledImageViewKindCount);
|
||||
const bool integer = i >= SampledImageViewKindCount;
|
||||
const auto component = integer ? state.uint_type : state.float_type;
|
||||
state.builder.AddType({OpTypeImage, image.image_type, component,
|
||||
ImageSpirvDimension(view), 0, ImageSpirvArrayed(view), 0, 1,
|
||||
state.builder.AddType({OpTypeImage, image.image_type, component, ImageSpirvDimension(view),
|
||||
0, ImageSpirvArrayed(view), ImageSpirvMultisampled(view), 1,
|
||||
ImageFormatUnknown});
|
||||
state.builder.AddType({OpTypeSampledImage, image.sampled_image_type, image.image_type});
|
||||
state.builder.AddType(
|
||||
@@ -733,13 +762,12 @@ void EmitHeaderAndTypes(EmitterState& state) {
|
||||
}
|
||||
for (uint32_t i = 0; i < state.storage_images.size(); i++) {
|
||||
auto& image = state.storage_images[i];
|
||||
const auto view = static_cast<ImageViewKind>(i % ImageViewKindCount);
|
||||
const bool integer = i >= ImageViewKindCount;
|
||||
const auto view = static_cast<ImageViewKind>(i % StorageImageViewKindCount);
|
||||
const bool integer = i >= StorageImageViewKindCount;
|
||||
const auto component = integer ? state.uint_type : state.float_type;
|
||||
const auto format = integer ? ImageFormatR32ui : ImageFormatUnknown;
|
||||
state.builder.AddType({OpTypeImage, image.image_type, component,
|
||||
ImageSpirvDimension(view), 0, ImageSpirvArrayed(view), 0, 2,
|
||||
format});
|
||||
state.builder.AddType({OpTypeImage, image.image_type, component, ImageSpirvDimension(view),
|
||||
0, ImageSpirvArrayed(view), 0, 2, format});
|
||||
state.builder.AddType(
|
||||
{OpTypePointer, image.pointer_type, StorageClassUniformConstant, image.image_type});
|
||||
if (image.variable != 0) {
|
||||
@@ -786,15 +814,15 @@ void AllocateDescriptorVariables(EmitterState& state) {
|
||||
state.flattened_srt_variable = state.builder.AllocateId();
|
||||
}
|
||||
for (uint32_t i = 0; i < state.sampled_images.size(); i++) {
|
||||
const auto view = static_cast<ImageViewKind>(i % ImageViewKindCount);
|
||||
if (DescriptorBinding(state, SampledBindingKind(i >= ImageViewKindCount, view)) !=
|
||||
const auto view = static_cast<ImageViewKind>(i % SampledImageViewKindCount);
|
||||
if (DescriptorBinding(state, SampledBindingKind(i >= SampledImageViewKindCount, view)) !=
|
||||
nullptr) {
|
||||
state.sampled_images[i].variable = state.builder.AllocateId();
|
||||
}
|
||||
}
|
||||
for (uint32_t i = 0; i < state.storage_images.size(); i++) {
|
||||
const auto view = static_cast<ImageViewKind>(i % ImageViewKindCount);
|
||||
if (DescriptorBinding(state, StorageBindingKind(i >= ImageViewKindCount, view)) !=
|
||||
const auto view = static_cast<ImageViewKind>(i % StorageImageViewKindCount);
|
||||
if (DescriptorBinding(state, StorageBindingKind(i >= StorageImageViewKindCount, view)) !=
|
||||
nullptr) {
|
||||
state.storage_images[i].variable = state.builder.AllocateId();
|
||||
}
|
||||
|
||||
@@ -13,16 +13,30 @@ namespace {
|
||||
constexpr uint32_t MaxPushConstantBytes = 128;
|
||||
|
||||
constexpr std::array ImageBindingKinds = {
|
||||
DescriptorBindingKind::Sampled1D, DescriptorBindingKind::Sampled1DArray,
|
||||
DescriptorBindingKind::Sampled2D, DescriptorBindingKind::Sampled2DArray,
|
||||
DescriptorBindingKind::Sampled3D, DescriptorBindingKind::SampledUint1D,
|
||||
DescriptorBindingKind::SampledUint1DArray, DescriptorBindingKind::SampledUint2D,
|
||||
DescriptorBindingKind::SampledUint2DArray, DescriptorBindingKind::SampledUint3D,
|
||||
DescriptorBindingKind::Storage1D, DescriptorBindingKind::Storage1DArray,
|
||||
DescriptorBindingKind::Storage2D, DescriptorBindingKind::Storage2DArray,
|
||||
DescriptorBindingKind::Storage3D, DescriptorBindingKind::StorageUint1D,
|
||||
DescriptorBindingKind::StorageUint1DArray, DescriptorBindingKind::StorageUint2D,
|
||||
DescriptorBindingKind::StorageUint2DArray, DescriptorBindingKind::StorageUint3D,
|
||||
DescriptorBindingKind::Sampled1D,
|
||||
DescriptorBindingKind::Sampled1DArray,
|
||||
DescriptorBindingKind::Sampled2D,
|
||||
DescriptorBindingKind::Sampled2DArray,
|
||||
DescriptorBindingKind::Sampled2DMsaa,
|
||||
DescriptorBindingKind::Sampled2DMsaaArray,
|
||||
DescriptorBindingKind::Sampled3D,
|
||||
DescriptorBindingKind::SampledUint1D,
|
||||
DescriptorBindingKind::SampledUint1DArray,
|
||||
DescriptorBindingKind::SampledUint2D,
|
||||
DescriptorBindingKind::SampledUint2DArray,
|
||||
DescriptorBindingKind::SampledUint2DMsaa,
|
||||
DescriptorBindingKind::SampledUint2DMsaaArray,
|
||||
DescriptorBindingKind::SampledUint3D,
|
||||
DescriptorBindingKind::Storage1D,
|
||||
DescriptorBindingKind::Storage1DArray,
|
||||
DescriptorBindingKind::Storage2D,
|
||||
DescriptorBindingKind::Storage2DArray,
|
||||
DescriptorBindingKind::Storage3D,
|
||||
DescriptorBindingKind::StorageUint1D,
|
||||
DescriptorBindingKind::StorageUint1DArray,
|
||||
DescriptorBindingKind::StorageUint2D,
|
||||
DescriptorBindingKind::StorageUint2DArray,
|
||||
DescriptorBindingKind::StorageUint3D,
|
||||
};
|
||||
|
||||
bool ImageBinding(const ImageResource& image, DescriptorBindingKind& result) {
|
||||
@@ -36,6 +50,8 @@ bool ImageBinding(const ImageResource& image, DescriptorBindingKind& result) {
|
||||
case Dimension::Dim1DArray: result = Kind::Sampled1DArray; return true;
|
||||
case Dimension::Dim2D: result = Kind::Sampled2D; return true;
|
||||
case Dimension::Dim2DArray: result = Kind::Sampled2DArray; return true;
|
||||
case Dimension::Dim2DMsaa: result = Kind::Sampled2DMsaa; return true;
|
||||
case Dimension::Dim2DMsaaArray: result = Kind::Sampled2DMsaaArray; return true;
|
||||
case Dimension::Dim3D: result = Kind::Sampled3D; return true;
|
||||
default: return false;
|
||||
}
|
||||
@@ -45,6 +61,8 @@ bool ImageBinding(const ImageResource& image, DescriptorBindingKind& result) {
|
||||
case Dimension::Dim1DArray: result = Kind::SampledUint1DArray; return true;
|
||||
case Dimension::Dim2D: result = Kind::SampledUint2D; return true;
|
||||
case Dimension::Dim2DArray: result = Kind::SampledUint2DArray; return true;
|
||||
case Dimension::Dim2DMsaa: result = Kind::SampledUint2DMsaa; return true;
|
||||
case Dimension::Dim2DMsaaArray: result = Kind::SampledUint2DMsaaArray; return true;
|
||||
case Dimension::Dim3D: result = Kind::SampledUint3D; return true;
|
||||
default: return false;
|
||||
}
|
||||
@@ -165,8 +183,7 @@ bool CollectUserData(const Program& program, std::vector<uint32_t>& result) {
|
||||
return false;
|
||||
}
|
||||
for (uint32_t i = 0; i < inst.src_count; i++) {
|
||||
if (!CollectValue(program.provenance, inst.scalar_sources[i], visited,
|
||||
registers)) {
|
||||
if (!CollectValue(program.provenance, inst.scalar_sources[i], visited, registers)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,323 @@
|
||||
#include "graphics/shader/recompiler/ir/ReadLaneElimination.h"
|
||||
|
||||
#include "graphics/shader/recompiler/ir/SrtWalker.h"
|
||||
|
||||
#include <algorithm>
|
||||
#include <iterator>
|
||||
#include <map>
|
||||
#include <set>
|
||||
#include <utility>
|
||||
|
||||
namespace Libs::Graphics::ShaderRecompiler::IR {
|
||||
|
||||
namespace {
|
||||
|
||||
constexpr uint32_t FirstTemporaryScalarRegister = 128;
|
||||
|
||||
struct LaneKey {
|
||||
uint32_t reg = 0;
|
||||
uint32_t lane = 0;
|
||||
|
||||
auto operator<=>(const LaneKey&) const = default;
|
||||
};
|
||||
|
||||
using LaneSet = std::set<LaneKey>;
|
||||
|
||||
bool PairDwordOpcode(Opcode op) {
|
||||
switch (op) {
|
||||
case Opcode::MoveU64:
|
||||
case Opcode::WqmB64:
|
||||
case Opcode::SaveexecB64:
|
||||
case Opcode::BitwiseAndU64:
|
||||
case Opcode::BitwiseAndNotU64:
|
||||
case Opcode::BitwiseOrU64:
|
||||
case Opcode::BitwiseOrNotU64:
|
||||
case Opcode::BitwiseXorU64:
|
||||
case Opcode::BitwiseNandU64:
|
||||
case Opcode::BitwiseNorU64:
|
||||
case Opcode::BitwiseXnorU64:
|
||||
case Opcode::BitwiseNotU64:
|
||||
case Opcode::BitFieldMaskU64:
|
||||
case Opcode::BitFieldExtractU64:
|
||||
case Opcode::BitReplicateB64B32:
|
||||
case Opcode::ShiftLeftLogicalU64:
|
||||
case Opcode::ShiftRightLogicalU64:
|
||||
case Opcode::SelectU64: return true;
|
||||
default: return false;
|
||||
}
|
||||
}
|
||||
|
||||
bool ResolveLane(const Program& program, const Instruction& inst, uint32_t source_index,
|
||||
uint32_t& lane) {
|
||||
if (source_index >= inst.src_count || (program.wave_size != 32 && program.wave_size != 64)) {
|
||||
return false;
|
||||
}
|
||||
const auto& selector = inst.src[source_index];
|
||||
if (selector.kind == OperandKind::ImmediateU32) {
|
||||
lane = selector.imm % program.wave_size;
|
||||
return true;
|
||||
}
|
||||
uint32_t folded = 0;
|
||||
if (!FoldScalarConstant(program.provenance, inst.scalar_sources[source_index], folded)) {
|
||||
return false;
|
||||
}
|
||||
lane = folded % program.wave_size;
|
||||
return true;
|
||||
}
|
||||
|
||||
bool UniformWriteSource(const Instruction& inst) {
|
||||
if (inst.src_count == 0) {
|
||||
return false;
|
||||
}
|
||||
const auto& source = inst.src[0];
|
||||
if (source.kind == OperandKind::ImmediateU32 || source.kind == OperandKind::PcRelativeU32) {
|
||||
return true;
|
||||
}
|
||||
return source.kind == OperandKind::Register &&
|
||||
(source.reg.file == RegisterFile::Scalar || source.reg.file == RegisterFile::Scc ||
|
||||
source.reg.file == RegisterFile::M0);
|
||||
}
|
||||
|
||||
bool WriteLaneKey(const Program& program, const Instruction& inst, LaneKey& key) {
|
||||
if (inst.op != Opcode::WriteLaneU32 || inst.dst.kind != OperandKind::Register ||
|
||||
inst.dst.reg.file != RegisterFile::Vector || !UniformWriteSource(inst)) {
|
||||
return false;
|
||||
}
|
||||
uint32_t lane = 0;
|
||||
if (!ResolveLane(program, inst, 1, lane)) {
|
||||
return false;
|
||||
}
|
||||
key = {inst.dst.reg.index, lane};
|
||||
return true;
|
||||
}
|
||||
|
||||
bool ReadLaneKey(const Program& program, const Instruction& inst, LaneKey& key) {
|
||||
if (inst.op != Opcode::ReadLaneU32 || inst.src_count < 2 ||
|
||||
inst.src[0].kind != OperandKind::Register || inst.src[0].reg.file != RegisterFile::Vector) {
|
||||
return false;
|
||||
}
|
||||
uint32_t lane = 0;
|
||||
if (!ResolveLane(program, inst, 1, lane)) {
|
||||
return false;
|
||||
}
|
||||
key = {inst.src[0].reg.index, lane};
|
||||
return true;
|
||||
}
|
||||
|
||||
void InvalidateRegister(LaneSet& valid, uint32_t reg) {
|
||||
const auto first = valid.lower_bound({reg, 0});
|
||||
const auto last = valid.lower_bound({reg + 1u, 0});
|
||||
valid.erase(first, last);
|
||||
}
|
||||
|
||||
void ApplyInstruction(const Program& program, const Instruction& inst, LaneSet& valid) {
|
||||
if (inst.op == Opcode::WriteLaneU32 && inst.dst.kind == OperandKind::Register &&
|
||||
inst.dst.reg.file == RegisterFile::Vector) {
|
||||
LaneKey key;
|
||||
if (WriteLaneKey(program, inst, key)) {
|
||||
valid.insert(key);
|
||||
return;
|
||||
}
|
||||
uint32_t lane = 0;
|
||||
if (ResolveLane(program, inst, 1, lane)) {
|
||||
valid.erase({inst.dst.reg.index, lane});
|
||||
} else {
|
||||
InvalidateRegister(valid, inst.dst.reg.index);
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
if (inst.op == Opcode::MoveRelDestU32 && inst.dst.kind == OperandKind::Register &&
|
||||
inst.dst.reg.file == RegisterFile::Vector) {
|
||||
valid.clear();
|
||||
return;
|
||||
}
|
||||
|
||||
if (inst.dst.kind == OperandKind::Register && inst.dst.reg.file == RegisterFile::Vector) {
|
||||
uint32_t dwords = std::max(inst.memory.data_dwords, 1u);
|
||||
if (PairDwordOpcode(inst.op) || inst.op == Opcode::UMadU64U32) {
|
||||
dwords = std::max(dwords, 2u);
|
||||
}
|
||||
for (uint32_t i = 0; i < dwords && inst.dst.reg.index <= UINT32_MAX - i; i++) {
|
||||
InvalidateRegister(valid, inst.dst.reg.index + i);
|
||||
}
|
||||
}
|
||||
if (inst.dst2.kind == OperandKind::Register && inst.dst2.reg.file == RegisterFile::Vector) {
|
||||
InvalidateRegister(valid, inst.dst2.reg.index);
|
||||
}
|
||||
}
|
||||
|
||||
LaneSet TransferBlock(const Program& program, const BasicBlock& block, LaneSet state) {
|
||||
for (const auto& inst: block.instructions) {
|
||||
ApplyInstruction(program, inst, state);
|
||||
}
|
||||
return state;
|
||||
}
|
||||
|
||||
LaneSet Intersect(const LaneSet& left, const LaneSet& right) {
|
||||
LaneSet result;
|
||||
std::set_intersection(left.begin(), left.end(), right.begin(), right.end(),
|
||||
std::inserter(result, result.end()));
|
||||
return result;
|
||||
}
|
||||
|
||||
uint32_t NextTemporaryScalarRegister(const Program& program) {
|
||||
uint32_t next = FirstTemporaryScalarRegister;
|
||||
const auto consider = [&next](const Operand& operand) {
|
||||
if (operand.kind == OperandKind::Register && operand.reg.file == RegisterFile::Scalar &&
|
||||
operand.reg.index >= next && operand.reg.index != UINT32_MAX) {
|
||||
next = operand.reg.index + 1u;
|
||||
}
|
||||
};
|
||||
for (const auto& block: program.blocks) {
|
||||
for (const auto& inst: block.instructions) {
|
||||
consider(inst.dst);
|
||||
consider(inst.dst2);
|
||||
for (uint32_t i = 0; i < inst.src_count; i++) {
|
||||
consider(inst.src[i]);
|
||||
}
|
||||
}
|
||||
}
|
||||
return next;
|
||||
}
|
||||
|
||||
Operand ScalarRegisterOperand(uint32_t reg) {
|
||||
Operand operand;
|
||||
operand.kind = OperandKind::Register;
|
||||
operand.reg.file = RegisterFile::Scalar;
|
||||
operand.reg.index = reg;
|
||||
return operand;
|
||||
}
|
||||
|
||||
Instruction ShadowWrite(const Instruction& write, uint32_t temporary) {
|
||||
Instruction shadow;
|
||||
shadow.pc = write.pc;
|
||||
shadow.op = Opcode::MoveU32;
|
||||
shadow.dst = ScalarRegisterOperand(temporary);
|
||||
shadow.src[0] = write.src[0];
|
||||
shadow.src_count = 1;
|
||||
return shadow;
|
||||
}
|
||||
|
||||
Instruction ShadowRead(const Instruction& read, uint32_t temporary) {
|
||||
Instruction rewritten;
|
||||
rewritten.pc = read.pc;
|
||||
rewritten.op = Opcode::MoveU32;
|
||||
rewritten.dst = read.dst;
|
||||
rewritten.src[0] = ScalarRegisterOperand(temporary);
|
||||
rewritten.src_count = 1;
|
||||
return rewritten;
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
ReadLaneEliminationStats EliminateReadLane(Program& program) {
|
||||
ReadLaneEliminationStats stats;
|
||||
if (program.blocks.empty() || (program.wave_size != 32 && program.wave_size != 64)) {
|
||||
return stats;
|
||||
}
|
||||
|
||||
LaneSet universe;
|
||||
for (const auto& block: program.blocks) {
|
||||
for (const auto& inst: block.instructions) {
|
||||
LaneKey key;
|
||||
if (WriteLaneKey(program, inst, key)) {
|
||||
universe.insert(key);
|
||||
}
|
||||
}
|
||||
}
|
||||
if (universe.empty()) {
|
||||
return stats;
|
||||
}
|
||||
|
||||
const size_t block_count = program.blocks.size();
|
||||
std::vector<LaneSet> entry(block_count, universe);
|
||||
std::vector<LaneSet> exit(block_count, universe);
|
||||
entry[0].clear();
|
||||
for (size_t block = 0; block < block_count; block++) {
|
||||
exit[block] = TransferBlock(program, program.blocks[block], entry[block]);
|
||||
}
|
||||
|
||||
bool changed = true;
|
||||
while (changed) {
|
||||
changed = false;
|
||||
for (size_t block_index = 0; block_index < block_count; block_index++) {
|
||||
LaneSet next_entry;
|
||||
const auto& block = program.blocks[block_index];
|
||||
if (block_index != 0 && !block.predecessors.empty()) {
|
||||
next_entry = universe;
|
||||
for (const auto predecessor: block.predecessors) {
|
||||
if (predecessor >= block_count) {
|
||||
next_entry.clear();
|
||||
break;
|
||||
}
|
||||
next_entry = Intersect(next_entry, exit[predecessor]);
|
||||
}
|
||||
}
|
||||
auto next_exit = TransferBlock(program, block, next_entry);
|
||||
if (next_entry != entry[block_index] || next_exit != exit[block_index]) {
|
||||
entry[block_index] = std::move(next_entry);
|
||||
exit[block_index] = std::move(next_exit);
|
||||
changed = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
LaneSet forwarded;
|
||||
for (size_t block_index = 0; block_index < block_count; block_index++) {
|
||||
auto state = entry[block_index];
|
||||
for (const auto& inst: program.blocks[block_index].instructions) {
|
||||
LaneKey key;
|
||||
if (ReadLaneKey(program, inst, key) && state.contains(key)) {
|
||||
forwarded.insert(key);
|
||||
}
|
||||
ApplyInstruction(program, inst, state);
|
||||
}
|
||||
}
|
||||
if (forwarded.empty()) {
|
||||
return stats;
|
||||
}
|
||||
|
||||
std::map<LaneKey, uint32_t> temporaries;
|
||||
auto next_temporary = NextTemporaryScalarRegister(program);
|
||||
for (const auto& key: forwarded) {
|
||||
if (next_temporary == UINT32_MAX) {
|
||||
return {};
|
||||
}
|
||||
temporaries.emplace(key, next_temporary++);
|
||||
}
|
||||
|
||||
for (size_t block_index = 0; block_index < block_count; block_index++) {
|
||||
const auto original = std::move(program.blocks[block_index].instructions);
|
||||
auto& rewritten = program.blocks[block_index].instructions;
|
||||
rewritten.clear();
|
||||
rewritten.reserve(original.size() + temporaries.size());
|
||||
auto state = entry[block_index];
|
||||
for (const auto& inst: original) {
|
||||
LaneKey read_key;
|
||||
if (ReadLaneKey(program, inst, read_key) && state.contains(read_key)) {
|
||||
const auto temporary = temporaries.find(read_key);
|
||||
if (temporary != temporaries.end()) {
|
||||
rewritten.push_back(ShadowRead(inst, temporary->second));
|
||||
stats.rewritten_reads++;
|
||||
ApplyInstruction(program, inst, state);
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
rewritten.push_back(inst);
|
||||
LaneKey write_key;
|
||||
if (WriteLaneKey(program, inst, write_key)) {
|
||||
const auto temporary = temporaries.find(write_key);
|
||||
if (temporary != temporaries.end()) {
|
||||
rewritten.push_back(ShadowWrite(inst, temporary->second));
|
||||
stats.shadow_writes++;
|
||||
}
|
||||
}
|
||||
ApplyInstruction(program, inst, state);
|
||||
}
|
||||
}
|
||||
return stats;
|
||||
}
|
||||
|
||||
} // namespace Libs::Graphics::ShaderRecompiler::IR
|
||||
@@ -0,0 +1,20 @@
|
||||
#ifndef EMULATOR_INCLUDE_EMULATOR_GRAPHICS_SHADER_RECOMPILER_READLANEELIMINATION_H_
|
||||
#define EMULATOR_INCLUDE_EMULATOR_GRAPHICS_SHADER_RECOMPILER_READLANEELIMINATION_H_
|
||||
|
||||
#include "graphics/shader/recompiler/ir/ShaderIR.h"
|
||||
|
||||
namespace Libs::Graphics::ShaderRecompiler::IR {
|
||||
|
||||
struct ReadLaneEliminationStats {
|
||||
uint32_t rewritten_reads = 0;
|
||||
uint32_t shadow_writes = 0;
|
||||
};
|
||||
|
||||
// Replaces fixed-lane ReadLane operations that are reached by a matching WriteLane on every
|
||||
// control-flow path. A synthetic scalar register snapshots the value at WriteLane execution time,
|
||||
// so the rewrite remains valid when the source SGPR is subsequently overwritten.
|
||||
[[nodiscard]] ReadLaneEliminationStats EliminateReadLane(Program& program);
|
||||
|
||||
} // namespace Libs::Graphics::ShaderRecompiler::IR
|
||||
|
||||
#endif /* EMULATOR_INCLUDE_EMULATOR_GRAPHICS_SHADER_RECOMPILER_READLANEELIMINATION_H_ */
|
||||
@@ -15,7 +15,8 @@ constexpr uint64_t AddressMask = 0x0000ffffffffffffull;
|
||||
Decoder::ImageDimension DescriptorDimension(const DescriptorValue& descriptor,
|
||||
Decoder::ImageDimension requested) {
|
||||
const bool is_array = requested == Decoder::ImageDimension::Dim1DArray ||
|
||||
requested == Decoder::ImageDimension::Dim2DArray;
|
||||
requested == Decoder::ImageDimension::Dim2DArray ||
|
||||
requested == Decoder::ImageDimension::Dim2DMsaaArray;
|
||||
switch (static_cast<Prospero::ImageType>((descriptor.dwords[3] >> 28u) & 0xfu)) {
|
||||
case Prospero::ImageType::kColor1D: return Decoder::ImageDimension::Dim1D;
|
||||
case Prospero::ImageType::kColor1DArray:
|
||||
@@ -26,13 +27,17 @@ Decoder::ImageDimension DescriptorDimension(const DescriptorValue& descrip
|
||||
case Prospero::ImageType::kColor3D: return Decoder::ImageDimension::Dim3D;
|
||||
case Prospero::ImageType::kCube: return Decoder::ImageDimension::Dim2DArray;
|
||||
case Prospero::ImageType::kColor2DArray:
|
||||
case Prospero::ImageType::kColor2DMsaaArray:
|
||||
if (is_array) {
|
||||
return Decoder::ImageDimension::Dim2DArray;
|
||||
}
|
||||
return Decoder::ImageDimension::Dim2D;
|
||||
case Prospero::ImageType::kColor2D:
|
||||
case Prospero::ImageType::kColor2DMsaa: return Decoder::ImageDimension::Dim2D;
|
||||
case Prospero::ImageType::kColor2DMsaaArray:
|
||||
if (is_array) {
|
||||
return Decoder::ImageDimension::Dim2DMsaaArray;
|
||||
}
|
||||
return Decoder::ImageDimension::Dim2DMsaa;
|
||||
case Prospero::ImageType::kColor2D: return Decoder::ImageDimension::Dim2D;
|
||||
case Prospero::ImageType::kColor2DMsaa: return Decoder::ImageDimension::Dim2DMsaa;
|
||||
default: return Decoder::ImageDimension::Unknown;
|
||||
}
|
||||
}
|
||||
@@ -43,7 +48,8 @@ bool NullImageDescriptor(const DescriptorValue& descriptor) {
|
||||
|
||||
bool ValidImageDescriptor(const DescriptorValue& descriptor) {
|
||||
const auto type = static_cast<Prospero::ImageType>((descriptor.dwords[3] >> 28u) & 0xfu);
|
||||
if (type < Prospero::ImageType::kColor1D) {
|
||||
const auto format = static_cast<Prospero::BufferFormat>((descriptor.dwords[1] >> 20u) & 0x1ffu);
|
||||
if (type < Prospero::ImageType::kColor1D || format == Prospero::BufferFormat::kInvalid) {
|
||||
return false;
|
||||
}
|
||||
if (type == Prospero::ImageType::kColor2DMsaa ||
|
||||
@@ -51,8 +57,7 @@ bool ValidImageDescriptor(const DescriptorValue& descriptor) {
|
||||
const auto base_level = (descriptor.dwords[3] >> 12u) & 0xfu;
|
||||
const auto fragments = (descriptor.dwords[3] >> 16u) & 0xfu;
|
||||
const auto max_mip = (descriptor.dwords[5] >> 4u) & 0xfu;
|
||||
return base_level == 0 && fragments >= 1 && fragments <= 3 &&
|
||||
max_mip == fragments;
|
||||
return base_level == 0 && fragments >= 1 && fragments <= 3 && max_mip == fragments;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
@@ -61,6 +66,11 @@ uint32_t DescriptorImageSwizzle(const DescriptorValue& descriptor) {
|
||||
return descriptor.dwords[3] & 0xfffu;
|
||||
}
|
||||
|
||||
bool DescriptorIsCube(const DescriptorValue& descriptor) {
|
||||
return static_cast<Prospero::ImageType>((descriptor.dwords[3] >> 28u) & 0xfu) ==
|
||||
Prospero::ImageType::kCube;
|
||||
}
|
||||
|
||||
bool DecodeBufferDescriptor(const DescriptorValue& descriptor, ShaderBufferResource& result) {
|
||||
if (descriptor.dword_count != std::size(result.fields)) {
|
||||
return false;
|
||||
@@ -171,12 +181,13 @@ bool ValidateResourceSpecialization(const Program& program, const ResourceSnapsh
|
||||
const auto& image = program.info.images[i];
|
||||
const auto& descriptor = snapshot.images[i];
|
||||
if (NullImageDescriptor(descriptor)) {
|
||||
bool canonical_kind = image.kind == ResourceKind::Image ||
|
||||
image.kind == ResourceKind::StorageImage;
|
||||
bool canonical_kind =
|
||||
image.kind == ResourceKind::Image || image.kind == ResourceKind::StorageImage;
|
||||
if (image.atomic) {
|
||||
canonical_kind = image.kind == ResourceKind::StorageImageUint;
|
||||
}
|
||||
if (image.dimension != Decoder::ImageDimension::Dim2D || !canonical_kind) {
|
||||
if (image.dimension != Decoder::ImageDimension::Dim2D || image.cube ||
|
||||
!canonical_kind) {
|
||||
if (error != nullptr) {
|
||||
*error = fmt::format(
|
||||
"image descriptor {} no longer matches canonical null specialization", i);
|
||||
@@ -186,10 +197,15 @@ bool ValidateResourceSpecialization(const Program& program, const ResourceSnapsh
|
||||
continue;
|
||||
}
|
||||
const auto dimension = DescriptorDimension(descriptor, image.dimension);
|
||||
if (dimension == Decoder::ImageDimension::Unknown || dimension != image.dimension) {
|
||||
if (dimension == Decoder::ImageDimension::Unknown || dimension != image.dimension ||
|
||||
DescriptorIsCube(descriptor) != image.cube) {
|
||||
if (error != nullptr) {
|
||||
*error =
|
||||
fmt::format("image descriptor {} no longer matches specialized dimension", i);
|
||||
fmt::format("image descriptor {} no longer matches specialized dimension: "
|
||||
"{:08x},{:08x},{:08x},{:08x},{:08x},{:08x},{:08x},{:08x}",
|
||||
i, descriptor.dwords[0], descriptor.dwords[1], descriptor.dwords[2],
|
||||
descriptor.dwords[3], descriptor.dwords[4], descriptor.dwords[5],
|
||||
descriptor.dwords[6], descriptor.dwords[7]);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
@@ -204,14 +220,21 @@ bool ValidateResourceSpecialization(const Program& program, const ResourceSnapsh
|
||||
}
|
||||
return false;
|
||||
}
|
||||
const auto uint_descriptor =
|
||||
Prospero::IsUintTextureFormat((descriptor.dwords[1] >> 20u) & 0x1ffu);
|
||||
const auto format = (descriptor.dwords[1] >> 20u) & 0x1ffu;
|
||||
const bool raw_sint_storage =
|
||||
storage && format == Prospero::GpuEnumValue(Prospero::BufferFormat::k32SInt) &&
|
||||
!image.read && !image.atomic;
|
||||
const bool uint_descriptor = Prospero::IsUintTextureFormat(format) || raw_sint_storage;
|
||||
const auto uint_program = image.kind == ResourceKind::ImageUint ||
|
||||
image.kind == ResourceKind::StorageImageUint;
|
||||
if (uint_descriptor != uint_program && !(image.atomic && uint_program)) {
|
||||
if (error != nullptr) {
|
||||
*error =
|
||||
fmt::format("image descriptor {} no longer matches specialized format", i);
|
||||
*error = fmt::format(
|
||||
"image descriptor {} no longer matches specialized format: "
|
||||
"{:08x},{:08x},{:08x},{:08x},{:08x},{:08x},{:08x},{:08x}",
|
||||
i, descriptor.dwords[0], descriptor.dwords[1], descriptor.dwords[2],
|
||||
descriptor.dwords[3], descriptor.dwords[4], descriptor.dwords[5],
|
||||
descriptor.dwords[6], descriptor.dwords[7]);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
@@ -361,6 +384,7 @@ bool SpecializeResources(Program& program, const ResourceSnapshot& snapshot, std
|
||||
auto& image = next.images[i];
|
||||
if (NullImageDescriptor(descriptor)) {
|
||||
image.dimension = Decoder::ImageDimension::Dim2D;
|
||||
image.cube = false;
|
||||
switch (image.kind) {
|
||||
case ResourceKind::ImageUint: image.kind = ResourceKind::Image; break;
|
||||
case ResourceKind::StorageImageUint:
|
||||
@@ -386,11 +410,19 @@ bool SpecializeResources(Program& program, const ResourceSnapshot& snapshot, std
|
||||
return false;
|
||||
}
|
||||
image.dimension = descriptor_dimension;
|
||||
image.cube = DescriptorIsCube(descriptor);
|
||||
if (image.kind == ResourceKind::StorageImage ||
|
||||
image.kind == ResourceKind::StorageImageUint) {
|
||||
image.storage_swizzle = DescriptorImageSwizzle(descriptor);
|
||||
}
|
||||
if (Prospero::IsUintTextureFormat((descriptor.dwords[1] >> 20u) & 0x1ffu)) {
|
||||
const auto format = (descriptor.dwords[1] >> 20u) & 0x1ffu;
|
||||
const bool storage = image.kind == ResourceKind::StorageImage ||
|
||||
image.kind == ResourceKind::StorageImageUint;
|
||||
const bool raw_sint_storage =
|
||||
storage && format == Prospero::GpuEnumValue(Prospero::BufferFormat::k32SInt) &&
|
||||
!image.read && !image.atomic;
|
||||
const bool uint_image = Prospero::IsUintTextureFormat(format) || raw_sint_storage;
|
||||
if (uint_image) {
|
||||
switch (image.kind) {
|
||||
case ResourceKind::Image: image.kind = ResourceKind::ImageUint; break;
|
||||
case ResourceKind::StorageImage: image.kind = ResourceKind::StorageImageUint; break;
|
||||
@@ -402,6 +434,7 @@ bool SpecializeResources(Program& program, const ResourceSnapshot& snapshot, std
|
||||
std::reference_wrapper<Instruction> inst;
|
||||
ResourceKind kind;
|
||||
Decoder::ImageDimension dimension;
|
||||
bool cube;
|
||||
};
|
||||
std::vector<ImagePatch> patches;
|
||||
for (auto& block: program.blocks) {
|
||||
@@ -420,13 +453,14 @@ bool SpecializeResources(Program& program, const ResourceSnapshot& snapshot, std
|
||||
return false;
|
||||
}
|
||||
const auto& image = next.images[inst.memory.resource];
|
||||
patches.push_back({std::ref(inst), image.kind, image.dimension});
|
||||
patches.push_back({std::ref(inst), image.kind, image.dimension, image.cube});
|
||||
}
|
||||
}
|
||||
program.info = std::move(next);
|
||||
for (const auto& patch: patches) {
|
||||
patch.inst.get().memory.kind = patch.kind;
|
||||
patch.inst.get().memory.image_dimension = patch.dimension;
|
||||
patch.inst.get().memory.image_cube = patch.cube;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -45,6 +45,8 @@ namespace {
|
||||
|
||||
constexpr uint32_t ScalarRegisters = 128;
|
||||
constexpr uint32_t VectorRegisters = 256;
|
||||
// Clamp X/Y/Z are consecutive three-bit fields; the high bit of each selects a border mode.
|
||||
constexpr uint32_t SamplerBorderClampMask = (1u << 2u) | (1u << 5u) | (1u << 8u);
|
||||
|
||||
struct ScalarState {
|
||||
std::array<uint32_t, ScalarRegisters> regs = {};
|
||||
@@ -647,6 +649,24 @@ private:
|
||||
return AddDescriptor(descriptor);
|
||||
}
|
||||
|
||||
uint32_t AddSamplerDescriptor(const ScalarState& state, uint32_t base) {
|
||||
if (base >= ScalarRegisters || 4u > ScalarRegisters - base) {
|
||||
return ScalarProvenance::Unknown;
|
||||
}
|
||||
DescriptorValue descriptor;
|
||||
descriptor.dword_count = 4;
|
||||
for (uint32_t i = 0; i < 4; i++) {
|
||||
descriptor.dwords[i] = state.regs[base + i];
|
||||
}
|
||||
if (auto d0 = descriptor.dwords[0];
|
||||
d0 < m_graph.values.size() && m_graph.values[d0].op == ScalarValueOp::Constant &&
|
||||
(m_graph.values[d0].imm & SamplerBorderClampMask) == 0) {
|
||||
// Without a border clamp, the border color and table index in dword 3 are unused.
|
||||
descriptor.dwords[3] = Constant(0);
|
||||
}
|
||||
return AddDescriptor(descriptor);
|
||||
}
|
||||
|
||||
uint32_t AddFlatAddressDescriptor(const Instruction& inst, const ScalarState& state) {
|
||||
const uint32_t first = FlatStore(inst.op) ? 1u : 0u;
|
||||
if (inst.src_count < first + 2u) {
|
||||
@@ -704,7 +724,7 @@ private:
|
||||
inst.memory.resource_source = AddDescriptor(state, inst.memory.resource * 4u, 8);
|
||||
if (inst.op == Opcode::ImageSample || inst.op == Opcode::ImageGather4 ||
|
||||
inst.op == Opcode::ImageGetLod) {
|
||||
inst.memory.sampler_source = AddDescriptor(state, inst.memory.sampler * 4u, 4);
|
||||
inst.memory.sampler_source = AddSamplerDescriptor(state, inst.memory.sampler * 4u);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user