mirror of
https://github.com/KytyPS5/KytyPS5.git
synced 2026-08-03 11:23:49 +00:00
Compare commits
12
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
d09c81d1c6 | ||
|
|
26f0c7b0bf | ||
|
|
2e9e2ae566 | ||
|
|
53feb6cb19 | ||
|
|
05d14f5421 | ||
|
|
1a164c3628 | ||
|
|
86a586025f | ||
|
|
06cbd602c1 | ||
|
|
055d0920d7 | ||
|
|
f2ee98fe31 | ||
|
|
207cef9602 | ||
|
|
c4134a95e2 |
@@ -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 ^
|
||||
@@ -147,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 \
|
||||
@@ -270,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 \
|
||||
|
||||
@@ -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": {
|
||||
|
||||
+104
-102
@@ -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)
|
||||
@@ -320,108 +322,108 @@ endfunction()
|
||||
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 kernel/macosGuestAddressSpace.cpp)
|
||||
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 ../tests/shaderCfgTests.cpp)
|
||||
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/ReadLaneElimination.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
|
||||
../tests/BitArrayTests.cpp
|
||||
"${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})
|
||||
|
||||
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
|
||||
../tests/AudioOut2PortTests.cpp
|
||||
libs/libAudio2.cpp
|
||||
loader/timer.cpp
|
||||
"${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
|
||||
@@ -437,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)
|
||||
|
||||
@@ -520,7 +522,7 @@ if(BUILD_TESTING)
|
||||
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})
|
||||
@@ -570,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 .)
|
||||
@@ -141,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:
|
||||
@@ -174,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"
|
||||
|
||||
@@ -188,7 +188,7 @@ 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
|
||||
|
||||
@@ -207,7 +207,7 @@ Requirements:
|
||||
```bash
|
||||
git submodule update --init --recursive
|
||||
|
||||
cmake -S src -B _Build/macos -G Ninja -DCMAKE_BUILD_TYPE=Release \
|
||||
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"
|
||||
|
||||
@@ -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
|
||||
)
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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,7 +170,8 @@ 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;
|
||||
uint32_t m_num_instances = 1;
|
||||
// Persistent draw state: indirect draws update it for subsequent draws.
|
||||
uint32_t m_num_instances = 1;
|
||||
|
||||
uint32_t m_de_count = 0;
|
||||
uint32_t m_ce_count = 0;
|
||||
|
||||
@@ -983,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;
|
||||
}
|
||||
|
||||
@@ -1024,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);
|
||||
}
|
||||
@@ -1081,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;
|
||||
}
|
||||
|
||||
@@ -1123,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);
|
||||
}
|
||||
@@ -1214,12 +1218,17 @@ 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 first_vertex, uint32_t first_instance) {
|
||||
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,
|
||||
m_renderer.GetRenderExecutor().DrawAuto(m_submit_id, CurrentBuffer(), vertex_count, flags,
|
||||
render_target_slice_offset, m_num_instances,
|
||||
first_vertex, first_instance);
|
||||
}
|
||||
|
||||
|
||||
+154
-242
@@ -7,8 +7,9 @@
|
||||
#include <array>
|
||||
#include <cstddef>
|
||||
#include <cstdint>
|
||||
#include <list>
|
||||
#include <cstring>
|
||||
#include <memory>
|
||||
#include <type_traits>
|
||||
#include <utility>
|
||||
#include <vector>
|
||||
|
||||
@@ -103,6 +104,158 @@ private:
|
||||
size_t m_allocated_buckets = 0;
|
||||
};
|
||||
|
||||
// Inline owner storage for page-table entries. Texture pages normally have only a
|
||||
// handful of owners, so avoid a heap allocation on the first 16 registrations.
|
||||
//
|
||||
// Owners are packed into live uint64_t objects instead of being constructed in
|
||||
// raw storage. This keeps sparse bucket construction cheap without relying on
|
||||
// subtle implicit-lifetime or pointer-provenance rules.
|
||||
template <typename OwnerT, size_t InlineCapacity = 16>
|
||||
class InlinePageOwnerList final {
|
||||
public:
|
||||
using value_type = OwnerT;
|
||||
using size_type = size_t;
|
||||
|
||||
class const_iterator final {
|
||||
public:
|
||||
[[nodiscard]] OwnerT operator*() const noexcept { return m_owners->At(m_index); }
|
||||
const_iterator& operator++() noexcept {
|
||||
++m_index;
|
||||
return *this;
|
||||
}
|
||||
bool operator==(const const_iterator&) const = default;
|
||||
|
||||
private:
|
||||
friend class InlinePageOwnerList;
|
||||
const_iterator(const InlinePageOwnerList* owners, size_type index) noexcept
|
||||
: m_owners(owners), m_index(index) {}
|
||||
|
||||
const InlinePageOwnerList* m_owners = nullptr;
|
||||
size_type m_index = 0;
|
||||
};
|
||||
|
||||
static_assert(InlineCapacity > 0);
|
||||
static_assert(std::is_default_constructible_v<OwnerT>);
|
||||
static_assert(std::is_trivially_copyable_v<OwnerT>);
|
||||
static_assert(std::has_unique_object_representations_v<OwnerT>);
|
||||
static_assert(sizeof(OwnerT) <= sizeof(uint64_t));
|
||||
|
||||
InlinePageOwnerList() noexcept {}
|
||||
InlinePageOwnerList(const InlinePageOwnerList&) = delete;
|
||||
InlinePageOwnerList& operator=(const InlinePageOwnerList&) = delete;
|
||||
InlinePageOwnerList(InlinePageOwnerList&& other) noexcept
|
||||
: m_overflow(std::move(other.m_overflow)),
|
||||
m_inline_size(std::exchange(other.m_inline_size, 0)) {
|
||||
if (m_overflow == nullptr) {
|
||||
std::copy_n(other.m_inline.begin(), m_inline_size, m_inline.begin());
|
||||
}
|
||||
}
|
||||
InlinePageOwnerList& operator=(InlinePageOwnerList&& other) noexcept {
|
||||
if (this != &other) {
|
||||
m_overflow = std::move(other.m_overflow);
|
||||
m_inline_size = std::exchange(other.m_inline_size, 0);
|
||||
if (m_overflow == nullptr) {
|
||||
std::copy_n(other.m_inline.begin(), m_inline_size, m_inline.begin());
|
||||
}
|
||||
}
|
||||
return *this;
|
||||
}
|
||||
|
||||
[[nodiscard]] const_iterator begin() const noexcept { return {this, 0}; }
|
||||
[[nodiscard]] const_iterator end() const noexcept { return {this, size()}; }
|
||||
|
||||
[[nodiscard]] bool empty() const noexcept { return size() == 0; }
|
||||
[[nodiscard]] size_type size() const noexcept {
|
||||
return m_overflow == nullptr ? m_inline_size : m_overflow->size();
|
||||
}
|
||||
[[nodiscard]] OwnerT front() const noexcept { return At(0); }
|
||||
[[nodiscard]] OwnerT operator[](size_type index) const noexcept { return At(index); }
|
||||
|
||||
void push_back(const OwnerT& owner) {
|
||||
const uint64_t packed = Pack(owner);
|
||||
if (m_overflow != nullptr) {
|
||||
m_overflow->push_back(packed);
|
||||
return;
|
||||
}
|
||||
if (m_inline_size < InlineCapacity) {
|
||||
m_inline[m_inline_size] = packed;
|
||||
++m_inline_size;
|
||||
return;
|
||||
}
|
||||
auto overflow = std::make_unique<std::vector<uint64_t>>();
|
||||
overflow->reserve(InlineCapacity * 2);
|
||||
overflow->assign(m_inline.begin(), m_inline.end());
|
||||
overflow->push_back(packed);
|
||||
m_overflow = std::move(overflow);
|
||||
}
|
||||
|
||||
[[nodiscard]] bool Contains(const OwnerT& owner) const noexcept {
|
||||
const uint64_t packed = Pack(owner);
|
||||
if (m_overflow != nullptr) {
|
||||
return std::find(m_overflow->begin(), m_overflow->end(), packed) != m_overflow->end();
|
||||
}
|
||||
return std::find(m_inline.begin(), m_inline.begin() + static_cast<ptrdiff_t>(m_inline_size),
|
||||
packed) != m_inline.begin() + static_cast<ptrdiff_t>(m_inline_size);
|
||||
}
|
||||
|
||||
[[nodiscard]] bool Erase(const OwnerT& owner) noexcept {
|
||||
const uint64_t packed = Pack(owner);
|
||||
if (m_overflow != nullptr) {
|
||||
const auto found = std::find(m_overflow->begin(), m_overflow->end(), packed);
|
||||
if (found == m_overflow->end()) {
|
||||
return false;
|
||||
}
|
||||
m_overflow->erase(found);
|
||||
if (m_overflow->size() == InlineCapacity) {
|
||||
std::copy(m_overflow->begin(), m_overflow->end(), m_inline.begin());
|
||||
m_inline_size = InlineCapacity;
|
||||
m_overflow.reset();
|
||||
}
|
||||
return true;
|
||||
}
|
||||
const auto end = m_inline.begin() + static_cast<ptrdiff_t>(m_inline_size);
|
||||
const auto found = std::find(m_inline.begin(), end, packed);
|
||||
if (found == end) {
|
||||
return false;
|
||||
}
|
||||
std::move(found + 1, end, found);
|
||||
--m_inline_size;
|
||||
return true;
|
||||
}
|
||||
|
||||
template <typename Func>
|
||||
void ForEach(Func&& func) const {
|
||||
if (m_overflow != nullptr) {
|
||||
for (const uint64_t owner: *m_overflow) {
|
||||
func(Unpack(owner));
|
||||
}
|
||||
return;
|
||||
}
|
||||
for (size_type index = 0; index < m_inline_size; ++index) {
|
||||
func(Unpack(m_inline[index]));
|
||||
}
|
||||
}
|
||||
|
||||
private:
|
||||
[[nodiscard]] OwnerT At(size_type index) const noexcept {
|
||||
return Unpack(m_overflow == nullptr ? m_inline[index] : (*m_overflow)[index]);
|
||||
}
|
||||
[[nodiscard]] static uint64_t Pack(const OwnerT& owner) noexcept {
|
||||
uint64_t packed = 0;
|
||||
std::memcpy(&packed, &owner, sizeof(owner));
|
||||
return packed;
|
||||
}
|
||||
[[nodiscard]] static OwnerT Unpack(uint64_t packed) noexcept {
|
||||
OwnerT owner {};
|
||||
std::memcpy(&owner, &packed, sizeof(owner));
|
||||
return owner;
|
||||
}
|
||||
|
||||
std::array<uint64_t, InlineCapacity> m_inline;
|
||||
std::unique_ptr<std::vector<uint64_t>> m_overflow;
|
||||
size_type m_inline_size = 0;
|
||||
};
|
||||
|
||||
// Removes only the requested owner. Other owners in the same page entry remain intact.
|
||||
template <typename Container, typename Value>
|
||||
[[nodiscard]] bool EraseExact(Container& owners, const Value& owner) {
|
||||
@@ -114,247 +267,6 @@ template <typename Container, typename Value>
|
||||
return true;
|
||||
}
|
||||
|
||||
// Multi-range ownership with 1 MiB candidate buckets and precise 4 KiB
|
||||
// lifetime accounting. OwnerT only needs equality.
|
||||
template <typename OwnerT>
|
||||
class MultiRangePageOwnerIndex final {
|
||||
private:
|
||||
struct Registration;
|
||||
using MembershipList = std::vector<const Registration*>;
|
||||
|
||||
public:
|
||||
struct ByteRange final {
|
||||
uint64_t address = 0;
|
||||
uint64_t size = 0;
|
||||
};
|
||||
|
||||
using CoarseTable = MultiLevelPageTable<MembershipList, 20, 40, 10>;
|
||||
using TrackingTable = MultiLevelPageTable<MembershipList, 12, 40, 18>;
|
||||
|
||||
[[nodiscard]] bool Register(const OwnerT& owner, const std::vector<ByteRange>& ranges) {
|
||||
if (FindRegistration(owner) != m_registrations.end()) {
|
||||
return false;
|
||||
}
|
||||
auto normalized = Normalize(ranges);
|
||||
if (normalized.empty()) {
|
||||
return false;
|
||||
}
|
||||
m_registrations.push_back({owner, std::move(normalized)});
|
||||
const Registration* registration = &m_registrations.back();
|
||||
for (const size_t page: CollectPages<20>(registration->ranges)) {
|
||||
m_coarse_pages[page].push_back(registration);
|
||||
}
|
||||
for (const size_t page: CollectPages<12>(registration->ranges)) {
|
||||
m_tracking_pages[page].push_back(registration);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
// No state changes if an expected membership is absent. Final releases are
|
||||
// sorted and coalesced 4 KiB spans whose final owner disappeared.
|
||||
[[nodiscard]] bool Unregister(const OwnerT& owner, std::vector<ByteRange>& final_releases) {
|
||||
final_releases.clear();
|
||||
auto registration = FindRegistration(owner);
|
||||
if (registration == m_registrations.end()) {
|
||||
return false;
|
||||
}
|
||||
const auto coarse_pages = CollectPages<20>(registration->ranges);
|
||||
const auto tracking_pages = CollectPages<12>(registration->ranges);
|
||||
const Registration* registration_ptr = &*registration;
|
||||
if (!HasAllMemberships(m_coarse_pages, coarse_pages, registration_ptr) ||
|
||||
!HasAllMemberships(m_tracking_pages, tracking_pages, registration_ptr)) {
|
||||
return false;
|
||||
}
|
||||
std::vector<size_t> final_pages;
|
||||
for (const size_t page: coarse_pages) {
|
||||
(void)EraseExact(*m_coarse_pages.Find(page), registration_ptr);
|
||||
}
|
||||
for (const size_t page: tracking_pages) {
|
||||
auto& owners = *m_tracking_pages.Find(page);
|
||||
if (owners.size() == 1) {
|
||||
final_pages.push_back(page);
|
||||
}
|
||||
(void)EraseExact(owners, registration_ptr);
|
||||
}
|
||||
m_registrations.erase(registration);
|
||||
final_releases = CoalesceTrackingPages(final_pages);
|
||||
return true;
|
||||
}
|
||||
|
||||
[[nodiscard]] std::vector<OwnerT> Query(uint64_t address, uint64_t size) const {
|
||||
return Query(address, size, [](const OwnerT&) { return true; });
|
||||
}
|
||||
|
||||
template <typename Predicate>
|
||||
[[nodiscard]] std::vector<OwnerT> Query(uint64_t address, uint64_t size,
|
||||
Predicate&& predicate) const {
|
||||
return QueryImpl(address, size, true, std::forward<Predicate>(predicate));
|
||||
}
|
||||
|
||||
// Fault paths use page candidates: exact byte-disjoint owners sharing a
|
||||
// touched 4 KiB page are intentionally retained.
|
||||
[[nodiscard]] std::vector<OwnerT> QueryCandidates(uint64_t address, uint64_t size) const {
|
||||
return QueryCandidates(address, size, [](const OwnerT&) { return true; });
|
||||
}
|
||||
|
||||
template <typename Predicate>
|
||||
[[nodiscard]] std::vector<OwnerT> QueryCandidates(uint64_t address, uint64_t size,
|
||||
Predicate&& predicate) const {
|
||||
return QueryImpl(address, size, false, std::forward<Predicate>(predicate));
|
||||
}
|
||||
|
||||
[[nodiscard]] size_t CoarseMembershipCount(size_t page) const {
|
||||
const auto* owners = m_coarse_pages.Find(page);
|
||||
return owners == nullptr ? 0 : owners->size();
|
||||
}
|
||||
[[nodiscard]] size_t TrackingMembershipCount(size_t page) const {
|
||||
const auto* owners = m_tracking_pages.Find(page);
|
||||
return owners == nullptr ? 0 : owners->size();
|
||||
}
|
||||
|
||||
private:
|
||||
template <typename Predicate>
|
||||
[[nodiscard]] std::vector<OwnerT> QueryImpl(uint64_t address, uint64_t size, bool strict_bytes,
|
||||
Predicate&& predicate) const {
|
||||
typename CoarseTable::PageRange coarse_range {};
|
||||
typename TrackingTable::PageRange tracking_range {};
|
||||
if (!CoarseTable::TryGetPageRange(address, size, coarse_range) ||
|
||||
(!strict_bytes && !TrackingTable::TryGetPageRange(address, size, tracking_range))) {
|
||||
return {};
|
||||
}
|
||||
MembershipList candidates;
|
||||
for (size_t page = coarse_range.first; page < coarse_range.last_exclusive; ++page) {
|
||||
if (const auto* owners = m_coarse_pages.Find(page); owners != nullptr) {
|
||||
AppendUnique(candidates, *owners);
|
||||
}
|
||||
}
|
||||
std::vector<OwnerT> result;
|
||||
for (const Registration* registration: candidates) {
|
||||
if ((strict_bytes ? Overlaps(registration->ranges, address, size)
|
||||
: HasTrackingMembership(registration, tracking_range)) &&
|
||||
predicate(registration->owner)) {
|
||||
result.push_back(registration->owner);
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
struct Registration final {
|
||||
OwnerT owner;
|
||||
std::vector<ByteRange> ranges;
|
||||
};
|
||||
using RegistrationIterator = typename std::list<Registration>::iterator;
|
||||
using ConstRegistrationIterator = typename std::list<Registration>::const_iterator;
|
||||
|
||||
[[nodiscard]] RegistrationIterator FindRegistration(const OwnerT& owner) {
|
||||
return std::find_if(m_registrations.begin(), m_registrations.end(),
|
||||
[&](const Registration& item) { return item.owner == owner; });
|
||||
}
|
||||
[[nodiscard]] ConstRegistrationIterator FindRegistration(const OwnerT& owner) const {
|
||||
return std::find_if(m_registrations.begin(), m_registrations.end(),
|
||||
[&](const Registration& item) { return item.owner == owner; });
|
||||
}
|
||||
|
||||
[[nodiscard]] static std::vector<ByteRange> Normalize(const std::vector<ByteRange>& ranges) {
|
||||
std::vector<ByteRange> sorted;
|
||||
for (const auto& range: ranges) {
|
||||
typename CoarseTable::PageRange ignored {};
|
||||
if (!CoarseTable::TryGetPageRange(range.address, range.size, ignored)) {
|
||||
return {};
|
||||
}
|
||||
sorted.push_back(range);
|
||||
}
|
||||
std::sort(sorted.begin(), sorted.end(), [](const ByteRange& lhs, const ByteRange& rhs) {
|
||||
return lhs.address < rhs.address;
|
||||
});
|
||||
std::vector<ByteRange> merged;
|
||||
for (const auto& range: sorted) {
|
||||
if (merged.empty() || range.address > merged.back().address + merged.back().size) {
|
||||
merged.push_back(range);
|
||||
} else {
|
||||
const uint64_t end = std::max(merged.back().address + merged.back().size,
|
||||
range.address + range.size);
|
||||
merged.back().size = end - merged.back().address;
|
||||
}
|
||||
}
|
||||
return merged;
|
||||
}
|
||||
|
||||
template <size_t Bits>
|
||||
[[nodiscard]] static std::vector<size_t> CollectPages(const std::vector<ByteRange>& ranges) {
|
||||
std::vector<size_t> pages;
|
||||
for (const auto& range: ranges) {
|
||||
const size_t first = static_cast<size_t>(range.address >> Bits);
|
||||
const size_t last = static_cast<size_t>((range.address + range.size - 1) >> Bits);
|
||||
for (size_t page = first; page <= last; ++page) {
|
||||
pages.push_back(page);
|
||||
}
|
||||
}
|
||||
std::sort(pages.begin(), pages.end());
|
||||
pages.erase(std::unique(pages.begin(), pages.end()), pages.end());
|
||||
return pages;
|
||||
}
|
||||
|
||||
template <typename Table>
|
||||
[[nodiscard]] static bool HasAllMemberships(const Table& table,
|
||||
const std::vector<size_t>& pages,
|
||||
const Registration* registration) {
|
||||
for (const size_t page: pages) {
|
||||
const auto* owners = table.Find(page);
|
||||
if (owners == nullptr ||
|
||||
std::find(owners->begin(), owners->end(), registration) == owners->end()) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
[[nodiscard]] bool HasTrackingMembership(const Registration* registration,
|
||||
const typename TrackingTable::PageRange& range) const {
|
||||
for (size_t page = range.first; page < range.last_exclusive; ++page) {
|
||||
const auto* owners = m_tracking_pages.Find(page);
|
||||
if (owners != nullptr &&
|
||||
std::find(owners->begin(), owners->end(), registration) != owners->end()) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
[[nodiscard]] static bool Overlaps(const std::vector<ByteRange>& ranges, uint64_t address,
|
||||
uint64_t size) {
|
||||
const uint64_t end = address + size;
|
||||
return std::any_of(ranges.begin(), ranges.end(), [&](const ByteRange& range) {
|
||||
return range.address < end && address < range.address + range.size;
|
||||
});
|
||||
}
|
||||
static void AppendUnique(MembershipList& destination, const MembershipList& source) {
|
||||
for (const Registration* registration: source) {
|
||||
if (std::find(destination.begin(), destination.end(), registration) ==
|
||||
destination.end()) {
|
||||
destination.push_back(registration);
|
||||
}
|
||||
}
|
||||
}
|
||||
[[nodiscard]] static std::vector<ByteRange>
|
||||
CoalesceTrackingPages(const std::vector<size_t>& pages) {
|
||||
std::vector<ByteRange> result;
|
||||
for (const size_t page: pages) {
|
||||
const uint64_t address = static_cast<uint64_t>(page) << 12;
|
||||
if (!result.empty() && result.back().address + result.back().size == address) {
|
||||
result.back().size += uint64_t {1} << 12;
|
||||
} else {
|
||||
result.push_back({address, uint64_t {1} << 12});
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
CoarseTable m_coarse_pages;
|
||||
TrackingTable m_tracking_pages;
|
||||
std::list<Registration> m_registrations;
|
||||
};
|
||||
|
||||
} // namespace Libs::Graphics
|
||||
|
||||
#endif // EMULATOR_SRC_GRAPHICS_HOST_GPU_RENDERER_MULTILEVELPAGETABLE_H_
|
||||
|
||||
+83
-18
@@ -195,10 +195,12 @@ void TextureCache::RegisterImage(ImageId id) {
|
||||
if (image.registered || image.info.data.Empty()) {
|
||||
EXIT("TextureCache: invalid image registration\n");
|
||||
}
|
||||
std::vector<ImageOwnerIndex::ByteRange> ranges;
|
||||
ranges.push_back({image.info.data.address, image.info.data.size});
|
||||
if (!m_image_owner_index.Register(id, ranges)) {
|
||||
EXIT("TextureCache: duplicate or invalid image registration\n");
|
||||
ImagePageTable::PageRange pages {};
|
||||
if (!ImagePageTable::TryGetPageRange(image.info.data.address, image.info.data.size, pages)) {
|
||||
EXIT("TextureCache: image registration is outside the guest address space\n");
|
||||
}
|
||||
for (size_t page = pages.first; page < pages.last_exclusive; ++page) {
|
||||
m_image_page_table[page].push_back(id);
|
||||
}
|
||||
image.registered = true;
|
||||
image.lru_id = m_lru_cache.Insert(id, m_gc_tick);
|
||||
@@ -211,9 +213,15 @@ void TextureCache::UnregisterImage(ImageId id) {
|
||||
return;
|
||||
}
|
||||
UntrackImage(id);
|
||||
std::vector<ImageOwnerIndex::ByteRange> releases;
|
||||
if (!m_image_owner_index.Unregister(id, releases)) {
|
||||
EXIT("TextureCache: image missing from owner index\n");
|
||||
ImagePageTable::PageRange pages {};
|
||||
if (!ImagePageTable::TryGetPageRange(image.info.data.address, image.info.data.size, pages)) {
|
||||
EXIT("TextureCache: registered image is outside the guest address space\n");
|
||||
}
|
||||
for (size_t page = pages.first; page < pages.last_exclusive; ++page) {
|
||||
auto* owners = m_image_page_table.Find(page);
|
||||
if (owners == nullptr || !owners->Erase(id)) {
|
||||
EXIT("TextureCache: image missing from page owner index\n");
|
||||
}
|
||||
}
|
||||
m_lru_cache.Free(image.lru_id);
|
||||
const auto accounted = image.AccountedSize();
|
||||
@@ -451,10 +459,48 @@ const Image& TextureCache::GetImage(ImageId id) const {
|
||||
return ResolveImage(id);
|
||||
}
|
||||
|
||||
std::vector<ImageId> TextureCache::FindImagesInRegion(uint64_t address, uint64_t size,
|
||||
bool page_overlap) const {
|
||||
return page_overlap ? m_image_owner_index.QueryCandidates(address, size)
|
||||
: m_image_owner_index.Query(address, size);
|
||||
TextureCache::ImageIds TextureCache::FindImagesInRegion(uint64_t address, uint64_t size,
|
||||
bool page_overlap) const {
|
||||
ImagePageTable::PageRange pages {};
|
||||
if (!ImagePageTable::TryGetPageRange(address, size, pages)) {
|
||||
return {};
|
||||
}
|
||||
|
||||
uint32_t query_epoch = ++m_image_query_epoch;
|
||||
if (query_epoch == 0) {
|
||||
for (const auto& slot: m_slots) {
|
||||
if (slot.image != nullptr) {
|
||||
slot.image->query_epoch = 0;
|
||||
}
|
||||
}
|
||||
query_epoch = ++m_image_query_epoch;
|
||||
}
|
||||
|
||||
ImageIds result;
|
||||
for (size_t page = pages.first; page < pages.last_exclusive; ++page) {
|
||||
const auto* owners = m_image_page_table.Find(page);
|
||||
if (owners == nullptr) {
|
||||
continue;
|
||||
}
|
||||
owners->ForEach([&](ImageId id) {
|
||||
if (!id || id.index >= m_slots.size()) {
|
||||
return;
|
||||
}
|
||||
const auto& slot = m_slots[id.index];
|
||||
if (slot.generation != id.generation || slot.image == nullptr) {
|
||||
return;
|
||||
}
|
||||
auto& image = *slot.image;
|
||||
if (image.query_epoch == query_epoch) {
|
||||
return;
|
||||
}
|
||||
image.query_epoch = query_epoch;
|
||||
if (image.Overlaps(address, size, page_overlap)) {
|
||||
result.push_back(id);
|
||||
}
|
||||
});
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
ImageId TextureCache::GetNullImage(const ImageDesc& desc) {
|
||||
@@ -838,11 +884,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;
|
||||
@@ -919,6 +974,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;
|
||||
@@ -956,11 +1012,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);
|
||||
@@ -1134,9 +1198,9 @@ ImageId TextureCache::FindImage(ImageDesc& desc, bool exact_format) {
|
||||
ImageId result {};
|
||||
bool inserted_new = false;
|
||||
{
|
||||
std::lock_guard transaction(m_resource_mutex);
|
||||
CacheLock lock(*this, m_lock);
|
||||
const std::vector<ImageId> candidates =
|
||||
std::lock_guard transaction(m_resource_mutex);
|
||||
CacheLock lock(*this, m_lock);
|
||||
const auto candidates =
|
||||
FindImagesInRegion(desc.info.data.address, desc.info.data.size, false);
|
||||
|
||||
for (const auto id: candidates) {
|
||||
@@ -1566,7 +1630,7 @@ void TextureCache::DownloadImageData(Image& image, Buffer& destination, uint64_t
|
||||
}
|
||||
|
||||
m_tiler->TileImage(image, color.regions, destination.Handle(), destination_offset,
|
||||
destination_size, destination_size, color.tiles, transform);
|
||||
destination_size, color.linear_size, color.tiles, transform);
|
||||
}
|
||||
|
||||
bool BufferCache::SynchronizeBufferFromImage(Buffer& buffer, uint64_t vaddr, uint64_t size) {
|
||||
@@ -1659,6 +1723,7 @@ bool BufferCache::SynchronizeBufferFromImage(Buffer& buffer, uint64_t vaddr, uin
|
||||
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));
|
||||
|
||||
+10
-7
@@ -99,7 +99,8 @@ private:
|
||||
int32_t layer = -1;
|
||||
};
|
||||
|
||||
using ImageOwnerIndex = MultiRangePageOwnerIndex<ImageId>;
|
||||
using ImageIds = InlinePageOwnerList<ImageId, 16>;
|
||||
using ImagePageTable = MultiLevelPageTable<ImageIds, 20, 40, 10>;
|
||||
|
||||
[[nodiscard]] Image& ResolveImage(ImageId id);
|
||||
[[nodiscard]] const Image& ResolveImage(ImageId id) const;
|
||||
@@ -125,8 +126,9 @@ private:
|
||||
[[nodiscard]] static BindingType UploadBinding(const Image& image);
|
||||
[[nodiscard]] bool SafeToDownload(const Image& image);
|
||||
|
||||
[[nodiscard]] std::vector<ImageId> FindImagesInRegion(uint64_t address, uint64_t size,
|
||||
bool page_overlap) const;
|
||||
// Caller holds m_lock; it also serializes the per-image query epoch.
|
||||
[[nodiscard]] ImageIds FindImagesInRegion(uint64_t address, uint64_t size,
|
||||
bool page_overlap) const;
|
||||
[[nodiscard]] OverlapResult ResolveOverlap(const ImageInfo& requested, BindingType binding,
|
||||
ImageId cached, ImageId merged);
|
||||
[[nodiscard]] ImageId ResolveDepthOverlap(const ImageInfo& requested, BindingType binding,
|
||||
@@ -169,7 +171,7 @@ private:
|
||||
ResourceMutex& m_resource_mutex;
|
||||
std::vector<Slot> m_slots;
|
||||
std::vector<uint32_t> m_free_slots;
|
||||
ImageOwnerIndex m_image_owner_index;
|
||||
ImagePageTable m_image_page_table;
|
||||
std::map<vk::Format, ImageId> m_null_images;
|
||||
Common::LeastRecentlyUsedCache<ImageId, uint64_t> m_lru_cache;
|
||||
std::set<ImageId> m_download_images;
|
||||
@@ -177,9 +179,10 @@ private:
|
||||
uint64_t m_total_used_memory = 0;
|
||||
uint64_t m_trigger_gc_memory = 0;
|
||||
uint64_t m_pressure_gc_memory = 1536ull * 1024 * 1024;
|
||||
uint64_t m_critical_gc_memory = 3ull * 1024 * 1024 * 1024;
|
||||
uint64_t m_gc_tick = 0;
|
||||
bool m_readback_linear_images = false;
|
||||
uint64_t m_critical_gc_memory = 3ull * 1024 * 1024 * 1024;
|
||||
uint64_t m_gc_tick = 0;
|
||||
mutable uint32_t m_image_query_epoch = 0;
|
||||
bool m_readback_linear_images = false;
|
||||
|
||||
friend struct TextureCacheTestAccess;
|
||||
friend class BufferCache;
|
||||
|
||||
@@ -154,17 +154,18 @@ public:
|
||||
}
|
||||
[[nodiscard]] uint64_t HashGuestEdges() const;
|
||||
|
||||
ImageInfo info;
|
||||
VulkanImage backing;
|
||||
ImageViewCache views;
|
||||
ImageUsage usage;
|
||||
ImageBinding binding;
|
||||
bool registered = false;
|
||||
uint64_t track_addr = 0;
|
||||
uint64_t track_addr_end = 0;
|
||||
ImageId depth_id {};
|
||||
uint64_t tick_accessed_last = 0;
|
||||
size_t lru_id = 0;
|
||||
ImageInfo info;
|
||||
VulkanImage backing;
|
||||
ImageViewCache views;
|
||||
ImageUsage usage;
|
||||
ImageBinding binding;
|
||||
bool registered = false;
|
||||
mutable uint32_t query_epoch = 0;
|
||||
uint64_t track_addr = 0;
|
||||
uint64_t track_addr_end = 0;
|
||||
ImageId depth_id {};
|
||||
uint64_t tick_accessed_last = 0;
|
||||
size_t lru_id = 0;
|
||||
|
||||
private:
|
||||
friend struct ImageTestAccess;
|
||||
|
||||
@@ -482,10 +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;
|
||||
@@ -538,8 +538,9 @@ bool TextureBuildGpuTileInfos(uint64_t size, const std::vector<vk::BufferImageCo
|
||||
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;
|
||||
}
|
||||
@@ -588,8 +589,9 @@ bool TextureBuildGpuTileInfos(uint64_t size, const std::vector<vk::BufferImageCo
|
||||
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;
|
||||
}
|
||||
|
||||
@@ -44,7 +44,7 @@ std::vector<vk::BufferImageCopy> TextureBuildImageCopies(const TextureUploadLayo
|
||||
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,
|
||||
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);
|
||||
|
||||
|
||||
@@ -391,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();
|
||||
}
|
||||
@@ -618,14 +617,15 @@ RenderExecutor::ResolveTexture(const ShaderRecompiler::IR::ImageResource& reso
|
||||
const bool multisampled = IsMultisampledTexture(type);
|
||||
const auto levels = multisampled ? 1u : static_cast<uint32_t>(descriptor.MaxMip()) + 1u;
|
||||
const auto tile = descriptor.TileMode();
|
||||
const bool depth_tile = tile == Prospero::GpuEnumValue(Prospero::TileMode::kDepth);
|
||||
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() && !depth_tile) ||
|
||||
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 max=%u type=%u tile=%u "
|
||||
"kind=%u dimension=%u mip_mode=%u read=%d written=%d "
|
||||
@@ -634,7 +634,8 @@ RenderExecutor::ResolveTexture(const ShaderRecompiler::IR::ImageResource& reso
|
||||
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]);
|
||||
descriptor.fields[4], descriptor.fields[5], descriptor.fields[6],
|
||||
descriptor.fields[7]);
|
||||
}
|
||||
const auto samples = multisampled ? 1u << last_level : 1u;
|
||||
const auto view_levels =
|
||||
@@ -661,8 +662,8 @@ RenderExecutor::ResolveTexture(const ShaderRecompiler::IR::ImageResource& reso
|
||||
TileSizeAlign size {};
|
||||
if (multisampled) {
|
||||
const auto bytes = Prospero::NumBytesPerElement(format);
|
||||
pitch = depth_tile ? TileGetDepthPitch(width, bytes, last_level)
|
||||
: 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");
|
||||
@@ -679,8 +680,8 @@ RenderExecutor::ResolveTexture(const ShaderRecompiler::IR::ImageResource& reso
|
||||
ValidateStorageTexture(resource, descriptor, size.size);
|
||||
}
|
||||
|
||||
const auto pixel_format = TextureGetFormat(format);
|
||||
const auto storage_view_format =
|
||||
const auto pixel_format = TextureGetFormat(format);
|
||||
const auto storage_view_format =
|
||||
storage && format == Prospero::GpuEnumValue(Prospero::BufferFormat::k32SInt)
|
||||
? vk::Format::eR32Uint
|
||||
: SrgbStorageViewFormat(pixel_format);
|
||||
|
||||
@@ -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++) {
|
||||
|
||||
@@ -14,6 +14,7 @@
|
||||
#include "graphics/host_gpu/renderer/renderTarget.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>
|
||||
@@ -463,8 +464,12 @@ void CreatePipelineInternal(
|
||||
uint32_t ps_hash0, uint32_t ps_crc32, bool ps_active) {
|
||||
EXIT_IF(ps_active && ps_input_info == nullptr);
|
||||
|
||||
vk::ShaderModule vert_shader_module = nullptr;
|
||||
vk::ShaderModule frag_shader_module = 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 {};
|
||||
|
||||
@@ -491,8 +496,33 @@ void CreatePipelineInternal(
|
||||
}
|
||||
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 {};
|
||||
@@ -525,9 +555,28 @@ void CreatePipelineInternal(
|
||||
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];
|
||||
@@ -929,10 +978,13 @@ void CreatePipelineInternal(
|
||||
pipeline_info.pStages = shader_stages;
|
||||
pipeline_info.pVertexInputState = &vertex_input_info;
|
||||
pipeline_info.pInputAssemblyState = &input_assembly;
|
||||
pipeline_info.pTessellationState = nullptr;
|
||||
pipeline_info.pViewportState = &viewport_state;
|
||||
pipeline_info.pRasterizationState = &rasterizer;
|
||||
pipeline_info.pMultisampleState = &multisampling;
|
||||
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;
|
||||
pipeline_info.pDepthStencilState = (static_params.with_depth ? &depth_stencil_info : nullptr);
|
||||
pipeline_info.pColorBlendState = &color_blending;
|
||||
pipeline_info.pDynamicState = &dynamic_state;
|
||||
@@ -968,6 +1020,12 @@ void CreatePipelineInternal(
|
||||
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);
|
||||
}
|
||||
|
||||
|
||||
@@ -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"
|
||||
@@ -650,11 +649,9 @@ 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;
|
||||
bool indexed = false;
|
||||
int32_t vertex_offset = 0;
|
||||
uint32_t first_vertex = 0;
|
||||
};
|
||||
|
||||
struct DrawIndexBufferSource {
|
||||
@@ -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};
|
||||
@@ -1345,20 +1309,15 @@ void RenderExecutor::DrawAuto(uint64_t submit_id, RenderCommandBuffer& buffer, u
|
||||
return;
|
||||
}
|
||||
|
||||
vk::PrimitiveTopology topology = vk::PrimitiveTopology::ePointList;
|
||||
const bool use_ngg_rectlist_draw = Config::NggRectlistDrawEnabled();
|
||||
|
||||
if (!GetDrawTopology(ucfg, true, use_ngg_rectlist_draw, topology)) {
|
||||
vk::PrimitiveTopology topology = vk::PrimitiveTopology::ePointList;
|
||||
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,13 +1334,10 @@ 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);
|
||||
const auto vertex_offset = ResolveVertexOffset(ucfg.GetIndexOffset(), state.vs_input_info) +
|
||||
static_cast<int32_t>(first_vertex);
|
||||
DrawEmitInfo emit {};
|
||||
emit.first_vertex = static_cast<uint32_t>(vertex_offset);
|
||||
|
||||
DrawIndexBufferSource index_source {};
|
||||
ExecutePreparedDraw(submit_id, buffer, draw, state, topology, emit, index_source, false, false,
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -200,12 +200,12 @@ bool ValidateResourceSpecialization(const Program& program, const ResourceSnapsh
|
||||
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: "
|
||||
"{: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]);
|
||||
*error =
|
||||
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;
|
||||
}
|
||||
@@ -224,14 +224,17 @@ bool ValidateResourceSpecialization(const Program& program, const ResourceSnapsh
|
||||
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;
|
||||
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;
|
||||
}
|
||||
|
||||
@@ -0,0 +1,397 @@
|
||||
#include "graphics/shader/rectListShader.h"
|
||||
|
||||
#include "common/assert.h"
|
||||
#include "graphics/shader/recompiler/emitter/SpirvBuilder.h"
|
||||
#include "graphics/shader/recompiler/ir/ShaderIR.h"
|
||||
#include "graphics/shader/shader.h"
|
||||
#include "spirv/unified1/spirv.hpp11"
|
||||
|
||||
#include <array>
|
||||
#include <bit>
|
||||
#include <cstdint>
|
||||
#include <utility>
|
||||
|
||||
namespace Libs::Graphics {
|
||||
|
||||
namespace {
|
||||
|
||||
using ShaderRecompiler::Spirv::Builder;
|
||||
|
||||
constexpr uint32_t SpirvVersion15 = 0x00010500u;
|
||||
|
||||
template <typename T>
|
||||
constexpr uint32_t Word(T value) {
|
||||
return static_cast<uint32_t>(value);
|
||||
}
|
||||
|
||||
struct Parameter {
|
||||
uint32_t input_location = 0;
|
||||
uint32_t output_location = 0;
|
||||
bool flat = false;
|
||||
};
|
||||
|
||||
std::vector<Parameter> GetParameters(const ShaderVertexInputInfo& vertex_info,
|
||||
const ShaderPixelInputInfo* pixel_info) {
|
||||
if (pixel_info == nullptr) {
|
||||
return {};
|
||||
}
|
||||
EXIT_IF(pixel_info->input_num > ShaderVertexInputInfo::RES_MAX);
|
||||
EXIT_IF(pixel_info->stage.program == nullptr);
|
||||
|
||||
std::vector<uint32_t> active_inputs;
|
||||
for (const auto& input: pixel_info->stage.program->info.inputs) {
|
||||
if (input.kind == ShaderRecompiler::IR::StageInputKind::Parameter) {
|
||||
active_inputs.push_back(input.location);
|
||||
}
|
||||
}
|
||||
|
||||
std::vector<Parameter> parameters;
|
||||
for (const auto input: active_inputs) {
|
||||
const auto input_location = ShaderPixelParameterMappedLocation(*pixel_info, input);
|
||||
if ((vertex_info.param_export_mask & (1u << input_location)) != 0) {
|
||||
parameters.push_back({input_location,
|
||||
ShaderPixelParameterLocation(*pixel_info, active_inputs, input),
|
||||
ShaderPixelParameterIsFlat(*pixel_info, input)});
|
||||
}
|
||||
}
|
||||
return parameters;
|
||||
}
|
||||
|
||||
class RectListEmitter {
|
||||
public:
|
||||
RectListEmitter(const std::vector<Parameter>& parameters_, spv::ExecutionModel model)
|
||||
: parameters(parameters_) {
|
||||
builder.AddMemoryModel(
|
||||
{Word(spv::AddressingModel::Logical), Word(spv::MemoryModel::GLSL450)});
|
||||
|
||||
void_type = Type(spv::Op::OpTypeVoid);
|
||||
uint_type = Type(spv::Op::OpTypeInt, 32u, 0u);
|
||||
int_type = Type(spv::Op::OpTypeInt, 32u, 1u);
|
||||
float_type = Type(spv::Op::OpTypeFloat, 32u);
|
||||
vec4_float_type = Type(spv::Op::OpTypeVector, float_type, 4u);
|
||||
function_type = Type(spv::Op::OpTypeFunction, void_type);
|
||||
|
||||
per_vertex_type = Type(spv::Op::OpTypeStruct, vec4_float_type);
|
||||
builder.AddAnnotation({Word(spv::Op::OpMemberDecorate), per_vertex_type, 0u,
|
||||
Word(spv::Decoration::BuiltIn), Word(spv::BuiltIn::Position)});
|
||||
builder.AddAnnotation(
|
||||
{Word(spv::Op::OpDecorate), per_vertex_type, Word(spv::Decoration::Block)});
|
||||
|
||||
ptr_input_vec4_float = Pointer(spv::StorageClass::Input, vec4_float_type);
|
||||
ptr_output_vec4_float = Pointer(spv::StorageClass::Output, vec4_float_type);
|
||||
if (model == spv::ExecutionModel::TessellationControl) {
|
||||
bool_type = Type(spv::Op::OpTypeBool);
|
||||
vec2_bool_type = Type(spv::Op::OpTypeVector, bool_type, 2u);
|
||||
vec2_float_type = Type(spv::Op::OpTypeVector, float_type, 2u);
|
||||
ptr_output_float = Pointer(spv::StorageClass::Output, float_type);
|
||||
} else {
|
||||
vec3_float_type = Type(spv::Op::OpTypeVector, float_type, 3u);
|
||||
ptr_input_float = Pointer(spv::StorageClass::Input, float_type);
|
||||
}
|
||||
}
|
||||
|
||||
std::vector<uint32_t> EmitControl() {
|
||||
DefineEntry(spv::ExecutionModel::TessellationControl);
|
||||
const auto float_one = Constant(float_type, std::bit_cast<uint32_t>(1.0f));
|
||||
|
||||
for (uint32_t i = 0; i < 4; i++) {
|
||||
Store(Access(ptr_output_float, tess_outer, Int(i)), float_one);
|
||||
}
|
||||
for (uint32_t i = 0; i < 2; i++) {
|
||||
Store(Access(ptr_output_float, tess_inner, Int(i)), float_one);
|
||||
}
|
||||
|
||||
std::array<uint32_t, 3> positions {};
|
||||
for (uint32_t i = 0; i < positions.size(); i++) {
|
||||
positions[i] =
|
||||
Load(vec4_float_type, Access(ptr_input_vec4_float, gl_in, Int(i), Int(0)));
|
||||
}
|
||||
|
||||
std::array<uint32_t, 3> coordinate_equal {};
|
||||
for (uint32_t i = 0; i < coordinate_equal.size(); i++) {
|
||||
const auto left = Result(spv::Op::OpVectorShuffle, vec2_float_type, positions[i],
|
||||
positions[i], 0u, 1u);
|
||||
const auto right = Result(spv::Op::OpVectorShuffle, vec2_float_type,
|
||||
positions[(i + 1u) % 3u], positions[(i + 1u) % 3u], 0u, 1u);
|
||||
coordinate_equal[i] = Result(spv::Op::OpFOrdEqual, vec2_bool_type, left, right);
|
||||
}
|
||||
|
||||
std::array<uint32_t, 3> barycentric {};
|
||||
std::array<uint32_t, 3> edge_vertex {};
|
||||
const auto float_minus_one = Constant(float_type, std::bit_cast<uint32_t>(-1.0f));
|
||||
for (uint32_t i = 0; i < edge_vertex.size(); i++) {
|
||||
const auto previous = (i + 2u) % 3u;
|
||||
const auto xy = Result(
|
||||
spv::Op::OpLogicalAnd, bool_type,
|
||||
Result(spv::Op::OpCompositeExtract, bool_type, coordinate_equal[i], 0u),
|
||||
Result(spv::Op::OpCompositeExtract, bool_type, coordinate_equal[previous], 1u));
|
||||
const auto yx = Result(
|
||||
spv::Op::OpLogicalAnd, bool_type,
|
||||
Result(spv::Op::OpCompositeExtract, bool_type, coordinate_equal[i], 1u),
|
||||
Result(spv::Op::OpCompositeExtract, bool_type, coordinate_equal[previous], 0u));
|
||||
edge_vertex[i] = Result(spv::Op::OpLogicalOr, bool_type, xy, yx);
|
||||
barycentric[i] =
|
||||
Result(spv::Op::OpSelect, float_type, edge_vertex[i], float_minus_one, float_one);
|
||||
}
|
||||
|
||||
auto vertex_index = Result(spv::Op::OpSelect, int_type, edge_vertex[2], Int(2), Int(0));
|
||||
vertex_index = Result(spv::Op::OpSelect, int_type, edge_vertex[1], Int(1), vertex_index);
|
||||
const auto invocation = Load(int_type, invocation_id);
|
||||
const auto is_fourth = Result(spv::Op::OpIEqual, bool_type, invocation, Int(3));
|
||||
const auto index =
|
||||
Result(spv::Op::OpSMod, int_type,
|
||||
Result(spv::Op::OpIAdd, int_type, vertex_index, invocation), Int(3));
|
||||
|
||||
const auto position3 = Interpolate(positions[0], positions[1], positions[2], barycentric);
|
||||
const auto position =
|
||||
Result(spv::Op::OpSelect, vec4_float_type, is_fourth, position3,
|
||||
Load(vec4_float_type, Access(ptr_input_vec4_float, gl_in, index, Int(0))));
|
||||
Store(Access(ptr_output_vec4_float, gl_out, invocation, Int(0)), position);
|
||||
|
||||
for (uint32_t i = 0; i < parameters.size(); i++) {
|
||||
const auto input0 =
|
||||
Load(vec4_float_type, Access(ptr_input_vec4_float, inputs[i], Int(0)));
|
||||
if (parameters[i].flat) {
|
||||
Store(Access(ptr_output_vec4_float, outputs[i], invocation), input0);
|
||||
continue;
|
||||
}
|
||||
const auto input1 =
|
||||
Load(vec4_float_type, Access(ptr_input_vec4_float, inputs[i], Int(1)));
|
||||
const auto input2 =
|
||||
Load(vec4_float_type, Access(ptr_input_vec4_float, inputs[i], Int(2)));
|
||||
const auto input3 = Interpolate(input0, input1, input2, barycentric);
|
||||
const auto value =
|
||||
Result(spv::Op::OpSelect, vec4_float_type, is_fourth, input3,
|
||||
Load(vec4_float_type, Access(ptr_input_vec4_float, inputs[i], index)));
|
||||
Store(Access(ptr_output_vec4_float, outputs[i], invocation), value);
|
||||
}
|
||||
|
||||
Emit(spv::Op::OpReturn);
|
||||
Emit(spv::Op::OpFunctionEnd);
|
||||
return builder.Build();
|
||||
}
|
||||
|
||||
std::vector<uint32_t> EmitEvaluation() {
|
||||
DefineEntry(spv::ExecutionModel::TessellationEvaluation);
|
||||
|
||||
const auto x = Load(float_type, Access(ptr_input_float, tess_coord, Int(0)));
|
||||
const auto y = Load(float_type, Access(ptr_input_float, tess_coord, Int(1)));
|
||||
const auto index = Result(
|
||||
spv::Op::OpIAdd, int_type,
|
||||
Result(spv::Op::OpIMul, int_type, Result(spv::Op::OpConvertFToS, int_type, y), Int(2)),
|
||||
Result(spv::Op::OpConvertFToS, int_type, x));
|
||||
|
||||
const auto position =
|
||||
Load(vec4_float_type, Access(ptr_input_vec4_float, gl_in, index, Int(0)));
|
||||
Store(Access(ptr_output_vec4_float, gl_out, Int(0)), position);
|
||||
for (uint32_t i = 0; i < parameters.size(); i++) {
|
||||
Store(outputs[i],
|
||||
Load(vec4_float_type, Access(ptr_input_vec4_float, inputs[i], index)));
|
||||
}
|
||||
|
||||
Emit(spv::Op::OpReturn);
|
||||
Emit(spv::Op::OpFunctionEnd);
|
||||
return builder.Build();
|
||||
}
|
||||
|
||||
private:
|
||||
template <typename... Args>
|
||||
uint32_t Type(spv::Op opcode, Args... operands) {
|
||||
const auto id = builder.AllocateId();
|
||||
builder.AddType({Word(opcode), id, Word(operands)...});
|
||||
return id;
|
||||
}
|
||||
|
||||
uint32_t Constant(uint32_t type, uint32_t value) {
|
||||
const auto id = builder.AllocateId();
|
||||
builder.AddType({Word(spv::Op::OpConstant), type, id, value});
|
||||
return id;
|
||||
}
|
||||
|
||||
uint32_t Pointer(spv::StorageClass storage, uint32_t type) {
|
||||
return Type(spv::Op::OpTypePointer, storage, type);
|
||||
}
|
||||
|
||||
uint32_t Array(uint32_t type, uint32_t size) {
|
||||
return Type(spv::Op::OpTypeArray, type, Uint(size));
|
||||
}
|
||||
|
||||
template <typename... Args>
|
||||
uint32_t Result(spv::Op opcode, uint32_t type, Args... operands) {
|
||||
const auto id = builder.AllocateId();
|
||||
builder.AddFunction({Word(opcode), type, id, Word(operands)...});
|
||||
return id;
|
||||
}
|
||||
|
||||
template <typename... Args>
|
||||
uint32_t ResultWithoutType(spv::Op opcode, Args... operands) {
|
||||
const auto id = builder.AllocateId();
|
||||
builder.AddFunction({Word(opcode), id, Word(operands)...});
|
||||
return id;
|
||||
}
|
||||
|
||||
template <typename... Args>
|
||||
void Emit(spv::Op opcode, Args... operands) {
|
||||
builder.AddFunction({Word(opcode), Word(operands)...});
|
||||
}
|
||||
|
||||
template <typename... Args>
|
||||
uint32_t Access(uint32_t pointer_type, uint32_t base, Args... indices) {
|
||||
return Result(spv::Op::OpAccessChain, pointer_type, base, Word(indices)...);
|
||||
}
|
||||
|
||||
uint32_t Load(uint32_t type, uint32_t pointer) {
|
||||
return Result(spv::Op::OpLoad, type, pointer);
|
||||
}
|
||||
|
||||
void Store(uint32_t pointer, uint32_t value) { Emit(spv::Op::OpStore, pointer, value); }
|
||||
|
||||
uint32_t Int(uint32_t value) {
|
||||
auto& id = int_constants[value];
|
||||
if (id == 0) {
|
||||
id = Constant(int_type, value);
|
||||
}
|
||||
return id;
|
||||
}
|
||||
|
||||
uint32_t Uint(uint32_t value) {
|
||||
auto& id = uint_constants[value];
|
||||
if (id == 0) {
|
||||
id = Constant(uint_type, value);
|
||||
}
|
||||
return id;
|
||||
}
|
||||
|
||||
uint32_t AddInterface(spv::StorageClass storage, uint32_t type) {
|
||||
const auto variable = builder.AllocateId();
|
||||
builder.AddType(
|
||||
{Word(spv::Op::OpVariable), Pointer(storage, type), variable, Word(storage)});
|
||||
interfaces.push_back(variable);
|
||||
return variable;
|
||||
}
|
||||
|
||||
void Decorate(uint32_t target, spv::Decoration decoration, uint32_t value) {
|
||||
builder.AddAnnotation({Word(spv::Op::OpDecorate), target, Word(decoration), value});
|
||||
}
|
||||
|
||||
void DefineEntry(spv::ExecutionModel model) {
|
||||
builder.AddCapability({Word(spv::Capability::Shader)});
|
||||
builder.AddCapability({Word(spv::Capability::Tessellation)});
|
||||
main = Result(spv::Op::OpFunction, void_type, spv::FunctionControlMask::MaskNone,
|
||||
function_type);
|
||||
if (model == spv::ExecutionModel::TessellationControl) {
|
||||
builder.AddExecutionMode({main, Word(spv::ExecutionMode::OutputVertices), 4u});
|
||||
} else {
|
||||
builder.AddExecutionMode({main, Word(spv::ExecutionMode::Quads)});
|
||||
builder.AddExecutionMode({main, Word(spv::ExecutionMode::SpacingEqual)});
|
||||
builder.AddExecutionMode({main, Word(spv::ExecutionMode::VertexOrderCw)});
|
||||
}
|
||||
DefineInputs(model);
|
||||
DefineOutputs(model);
|
||||
builder.AddEntryPoint(Word(model), main, "main", interfaces);
|
||||
ResultWithoutType(spv::Op::OpLabel);
|
||||
}
|
||||
|
||||
void DefineInputs(spv::ExecutionModel model) {
|
||||
const auto tess_control = model == spv::ExecutionModel::TessellationControl;
|
||||
if (tess_control) {
|
||||
invocation_id = AddInterface(spv::StorageClass::Input, int_type);
|
||||
Decorate(invocation_id, spv::Decoration::BuiltIn, Word(spv::BuiltIn::InvocationId));
|
||||
} else {
|
||||
tess_coord = AddInterface(spv::StorageClass::Input, vec3_float_type);
|
||||
Decorate(tess_coord, spv::Decoration::BuiltIn, Word(spv::BuiltIn::TessCoord));
|
||||
}
|
||||
gl_in =
|
||||
AddInterface(spv::StorageClass::Input, Array(per_vertex_type, tess_control ? 3u : 4u));
|
||||
|
||||
inputs.resize(parameters.size());
|
||||
std::array<uint32_t, ShaderVertexInputInfo::RES_MAX> locations {};
|
||||
for (uint32_t i = 0; i < parameters.size(); i++) {
|
||||
const auto location =
|
||||
tess_control ? parameters[i].input_location : parameters[i].output_location;
|
||||
if (tess_control && locations[location] != 0) {
|
||||
inputs[i] = locations[location];
|
||||
continue;
|
||||
}
|
||||
inputs[i] = AddInterface(spv::StorageClass::Input,
|
||||
Array(vec4_float_type, tess_control ? 3u : 4u));
|
||||
Decorate(inputs[i], spv::Decoration::Location, location);
|
||||
locations[location] = inputs[i];
|
||||
}
|
||||
}
|
||||
|
||||
void DefineOutputs(spv::ExecutionModel model) {
|
||||
const auto tess_control = model == spv::ExecutionModel::TessellationControl;
|
||||
if (tess_control) {
|
||||
gl_out = AddInterface(spv::StorageClass::Output, Array(per_vertex_type, 4u));
|
||||
tess_inner = AddInterface(spv::StorageClass::Output, Array(float_type, 2u));
|
||||
Decorate(tess_inner, spv::Decoration::BuiltIn, Word(spv::BuiltIn::TessLevelInner));
|
||||
builder.AddAnnotation(
|
||||
{Word(spv::Op::OpDecorate), tess_inner, Word(spv::Decoration::Patch)});
|
||||
tess_outer = AddInterface(spv::StorageClass::Output, Array(float_type, 4u));
|
||||
Decorate(tess_outer, spv::Decoration::BuiltIn, Word(spv::BuiltIn::TessLevelOuter));
|
||||
builder.AddAnnotation(
|
||||
{Word(spv::Op::OpDecorate), tess_outer, Word(spv::Decoration::Patch)});
|
||||
} else {
|
||||
gl_out = AddInterface(spv::StorageClass::Output, per_vertex_type);
|
||||
}
|
||||
|
||||
outputs.resize(parameters.size());
|
||||
for (uint32_t i = 0; i < parameters.size(); i++) {
|
||||
outputs[i] = AddInterface(spv::StorageClass::Output,
|
||||
tess_control ? Array(vec4_float_type, 4u) : vec4_float_type);
|
||||
Decorate(outputs[i], spv::Decoration::Location, parameters[i].output_location);
|
||||
}
|
||||
}
|
||||
|
||||
uint32_t Interpolate(uint32_t v0, uint32_t v1, uint32_t v2,
|
||||
const std::array<uint32_t, 3>& barycentric) {
|
||||
const auto p0 = Result(spv::Op::OpVectorTimesScalar, vec4_float_type, v0, barycentric[0]);
|
||||
const auto p1 = Result(spv::Op::OpVectorTimesScalar, vec4_float_type, v1, barycentric[1]);
|
||||
const auto p2 = Result(spv::Op::OpVectorTimesScalar, vec4_float_type, v2, barycentric[2]);
|
||||
return Result(spv::Op::OpFAdd, vec4_float_type, p0,
|
||||
Result(spv::Op::OpFAdd, vec4_float_type, p1, p2));
|
||||
}
|
||||
|
||||
Builder builder {SpirvVersion15};
|
||||
const std::vector<Parameter>& parameters;
|
||||
std::vector<uint32_t> interfaces;
|
||||
std::vector<uint32_t> inputs;
|
||||
std::vector<uint32_t> outputs;
|
||||
std::array<uint32_t, 5> int_constants {};
|
||||
std::array<uint32_t, 5> uint_constants {};
|
||||
uint32_t main = 0;
|
||||
uint32_t void_type = 0;
|
||||
uint32_t bool_type = 0;
|
||||
uint32_t uint_type = 0;
|
||||
uint32_t int_type = 0;
|
||||
uint32_t float_type = 0;
|
||||
uint32_t vec2_bool_type = 0;
|
||||
uint32_t vec2_float_type = 0;
|
||||
uint32_t vec3_float_type = 0;
|
||||
uint32_t vec4_float_type = 0;
|
||||
uint32_t function_type = 0;
|
||||
uint32_t per_vertex_type = 0;
|
||||
uint32_t ptr_input_float = 0;
|
||||
uint32_t ptr_input_vec4_float = 0;
|
||||
uint32_t ptr_output_float = 0;
|
||||
uint32_t ptr_output_vec4_float = 0;
|
||||
uint32_t gl_in = 0;
|
||||
uint32_t gl_out = 0;
|
||||
uint32_t tess_inner = 0;
|
||||
uint32_t tess_outer = 0;
|
||||
uint32_t tess_coord = 0;
|
||||
uint32_t invocation_id = 0;
|
||||
};
|
||||
|
||||
} // namespace
|
||||
|
||||
RectListShaders BuildRectListShaders(const ShaderVertexInputInfo& vertex_info,
|
||||
const ShaderPixelInputInfo* pixel_info) {
|
||||
const auto parameters = GetParameters(vertex_info, pixel_info);
|
||||
RectListEmitter control(parameters, spv::ExecutionModel::TessellationControl);
|
||||
RectListEmitter evaluation(parameters, spv::ExecutionModel::TessellationEvaluation);
|
||||
return {control.EmitControl(), evaluation.EmitEvaluation()};
|
||||
}
|
||||
|
||||
} // namespace Libs::Graphics
|
||||
@@ -0,0 +1,22 @@
|
||||
#ifndef EMULATOR_SRC_GRAPHICS_SHADER_RECTLISTSHADER_H_
|
||||
#define EMULATOR_SRC_GRAPHICS_SHADER_RECTLISTSHADER_H_
|
||||
|
||||
#include <cstdint>
|
||||
#include <vector>
|
||||
|
||||
namespace Libs::Graphics {
|
||||
|
||||
struct ShaderPixelInputInfo;
|
||||
struct ShaderVertexInputInfo;
|
||||
|
||||
struct RectListShaders {
|
||||
std::vector<uint32_t> control;
|
||||
std::vector<uint32_t> evaluation;
|
||||
};
|
||||
|
||||
RectListShaders BuildRectListShaders(const ShaderVertexInputInfo& vertex_info,
|
||||
const ShaderPixelInputInfo* pixel_info);
|
||||
|
||||
} // namespace Libs::Graphics
|
||||
|
||||
#endif // EMULATOR_SRC_GRAPHICS_SHADER_RECTLISTSHADER_H_
|
||||
@@ -45,6 +45,42 @@
|
||||
|
||||
namespace Libs::Graphics {
|
||||
|
||||
namespace {
|
||||
|
||||
constexpr uint32_t PsInputOffsetMask = 0x0000001fu;
|
||||
constexpr uint32_t PsInputFlatShade = 0x00000400u;
|
||||
|
||||
} // namespace
|
||||
|
||||
uint32_t ShaderPixelParameterMappedLocation(const ShaderPixelInputInfo& info, uint32_t input) {
|
||||
return input < info.input_num ? info.interpolator_settings[input] & PsInputOffsetMask : input;
|
||||
}
|
||||
|
||||
uint32_t ShaderPixelParameterLocation(const ShaderPixelInputInfo& info,
|
||||
std::span<const uint32_t> active_inputs, uint32_t input) {
|
||||
std::array<bool, 32> used_locations {};
|
||||
for (const auto active_input: active_inputs) {
|
||||
auto location = ShaderPixelParameterMappedLocation(info, active_input);
|
||||
if (location < used_locations.size() && used_locations[location]) {
|
||||
location = active_input;
|
||||
while (location < used_locations.size() && used_locations[location]) {
|
||||
location++;
|
||||
}
|
||||
EXIT_NOT_IMPLEMENTED(location >= used_locations.size());
|
||||
}
|
||||
|
||||
if (active_input == input) {
|
||||
return location;
|
||||
}
|
||||
used_locations[location] = true;
|
||||
}
|
||||
return ShaderPixelParameterMappedLocation(info, input);
|
||||
}
|
||||
|
||||
bool ShaderPixelParameterIsFlat(const ShaderPixelInputInfo& info, uint32_t input) {
|
||||
return input < info.input_num && (info.interpolator_settings[input] & PsInputFlatShade) != 0;
|
||||
}
|
||||
|
||||
struct ShaderBinaryInfo {
|
||||
uint8_t signature[7];
|
||||
uint8_t version;
|
||||
@@ -1606,6 +1642,7 @@ ShaderId ShaderGetIdPS(const HW::PixelShaderInfo& regs, const ShaderPixelInputIn
|
||||
ret.ids.push_back(static_cast<uint32_t>(input_info.ps_pos_z));
|
||||
ret.ids.push_back(static_cast<uint32_t>(input_info.ps_pos_w));
|
||||
ret.ids.push_back(static_cast<uint32_t>(input_info.ps_front_face));
|
||||
ret.ids.push_back(static_cast<uint32_t>(input_info.ps_no_perspective));
|
||||
ret.ids.push_back(static_cast<uint32_t>(input_info.ps_pixel_kill_enable));
|
||||
ret.ids.push_back(static_cast<uint32_t>(input_info.ps_sample_mask_export_enable));
|
||||
ret.ids.push_back(static_cast<uint32_t>(input_info.ps_early_z));
|
||||
|
||||
@@ -122,6 +122,11 @@ struct ShaderPixelInputInfo {
|
||||
bool HasPositionInput() const { return ps_pos_x || ps_pos_y || ps_pos_z || ps_pos_w; }
|
||||
};
|
||||
|
||||
uint32_t ShaderPixelParameterMappedLocation(const ShaderPixelInputInfo& info, uint32_t input);
|
||||
uint32_t ShaderPixelParameterLocation(const ShaderPixelInputInfo& info,
|
||||
std::span<const uint32_t> active_inputs, uint32_t input);
|
||||
bool ShaderPixelParameterIsFlat(const ShaderPixelInputInfo& info, uint32_t input);
|
||||
|
||||
struct ShaderSharp {
|
||||
uint16_t offset_dw : 15;
|
||||
uint16_t size : 1;
|
||||
|
||||
@@ -64,6 +64,7 @@ struct File {
|
||||
std::filesystem::path real_name;
|
||||
std::atomic_bool opened;
|
||||
std::atomic_bool directory;
|
||||
std::atomic_bool writable;
|
||||
std::atomic_bool append;
|
||||
std::atomic_bool sync_writes;
|
||||
SpecialFile special;
|
||||
@@ -129,6 +130,7 @@ int FileDescriptors::CreateDescriptor() {
|
||||
auto* file = new File {};
|
||||
file->opened = false;
|
||||
file->directory = false;
|
||||
file->writable = false;
|
||||
file->append = false;
|
||||
file->sync_writes = false;
|
||||
file->special = SpecialFile::None;
|
||||
@@ -408,6 +410,7 @@ int KYTY_SYSV_ABI KernelOpen(const char* path, int flags, uint16_t mode) {
|
||||
EXIT_IF(file == nullptr || file->opened || file->directory);
|
||||
|
||||
file->name = path;
|
||||
file->writable = rw_mode != Common::File::Mode::Read;
|
||||
file->append = append;
|
||||
file->sync_writes = fsync || sync || dsync;
|
||||
|
||||
@@ -953,6 +956,47 @@ int KYTY_SYSV_ABI KernelFstat(int d, FileStat* sb) {
|
||||
return OK;
|
||||
}
|
||||
|
||||
int KYTY_SYSV_ABI KernelFtruncate(int d, int64_t length) {
|
||||
PRINT_NAME();
|
||||
|
||||
if (d < DESCRIPTOR_MIN) {
|
||||
return KERNEL_ERROR_EBADF;
|
||||
}
|
||||
|
||||
if (length < 0) {
|
||||
return KERNEL_ERROR_EINVAL;
|
||||
}
|
||||
|
||||
if (::Libs::Network::Net::IsSocket(d)) {
|
||||
return KERNEL_ERROR_EINVAL;
|
||||
}
|
||||
|
||||
auto* file = g_files->GetFile(d);
|
||||
|
||||
if (file == nullptr || !file->opened) {
|
||||
return KERNEL_ERROR_EBADF;
|
||||
}
|
||||
|
||||
if (!file->writable) {
|
||||
return KERNEL_ERROR_EBADF;
|
||||
}
|
||||
|
||||
if (file->directory || file->special != SpecialFile::None) {
|
||||
return KERNEL_ERROR_EINVAL;
|
||||
}
|
||||
|
||||
Common::LockGuard lock(file->mutex);
|
||||
|
||||
if (file->f.IsInvalid() || !file->f.Truncate(static_cast<uint64_t>(length))) {
|
||||
return KERNEL_ERROR_EIO;
|
||||
}
|
||||
|
||||
LOGF("\tFtruncate (size = %" PRId64 ") file: %s\n", length,
|
||||
Common::PathToString(file->real_name).c_str());
|
||||
|
||||
return OK;
|
||||
}
|
||||
|
||||
int KYTY_SYSV_ABI KernelUnlink(const char* path) {
|
||||
PRINT_NAME();
|
||||
|
||||
|
||||
@@ -48,6 +48,7 @@ int64_t KYTY_SYSV_ABI KernelPwrite(int d, const void* buf, size_t nbytes, int64_
|
||||
int64_t KYTY_SYSV_ABI KernelLseek(int d, int64_t offset, int whence);
|
||||
int KYTY_SYSV_ABI KernelStat(const char* path, FileStat* sb);
|
||||
int KYTY_SYSV_ABI KernelFstat(int d, FileStat* sb);
|
||||
int KYTY_SYSV_ABI KernelFtruncate(int d, int64_t length);
|
||||
int KYTY_SYSV_ABI KernelUnlink(const char* path);
|
||||
int KYTY_SYSV_ABI KernelRename(const char* from, const char* to);
|
||||
int KYTY_SYSV_ABI KernelGetdirentries(int fd, char* buf, int nbytes, int64_t* basep);
|
||||
|
||||
+41
-50
@@ -365,6 +365,7 @@ struct PthreadAttrPrivate {
|
||||
uint64_t stack_map_addr;
|
||||
size_t stack_map_size;
|
||||
int policy;
|
||||
int guest_priority;
|
||||
int inherit_sched;
|
||||
int solosched;
|
||||
bool detached;
|
||||
@@ -875,9 +876,11 @@ bool TestGuestStackOwnerLifecycle(uint64_t* first_address, uint64_t* second_addr
|
||||
|
||||
static KYTY_SYSV_ABI void* RunOnGuestStack(void* arg, pthread_entry_func_t func, void* stack_top) {
|
||||
#if defined(__x86_64__) || defined(_M_X64)
|
||||
void* ret = nullptr;
|
||||
const auto guest_rsp = reinterpret_cast<uintptr_t>(stack_top) & ~static_cast<uintptr_t>(0x0f);
|
||||
const auto guest_rbp = guest_rsp - 4u * sizeof(uint64_t);
|
||||
void* ret = nullptr;
|
||||
const auto aligned_stack_top =
|
||||
reinterpret_cast<uintptr_t>(stack_top) & ~static_cast<uintptr_t>(0x0f);
|
||||
const auto guest_rsp = aligned_stack_top - 2u * sizeof(uintptr_t);
|
||||
const auto guest_rbp = guest_rsp;
|
||||
|
||||
auto* guest_root_frame = reinterpret_cast<uintptr_t*>(guest_rbp);
|
||||
guest_root_frame[0] = 0;
|
||||
@@ -2128,13 +2131,8 @@ int KYTY_SYSV_ABI PthreadAttrGetschedparam(const PthreadAttr* attr, KernelSchedP
|
||||
|
||||
int result = pthread_attr_getschedparam(&(*attr)->p, param);
|
||||
|
||||
if (param->sched_priority <= -2) {
|
||||
param->sched_priority = 767;
|
||||
} else if (param->sched_priority >= +2) {
|
||||
param->sched_priority = 256;
|
||||
} else {
|
||||
param->sched_priority = 700;
|
||||
}
|
||||
// Host priority mapping is lossy; return the exact guest value.
|
||||
param->sched_priority = (*attr)->guest_priority;
|
||||
|
||||
if (result == 0) {
|
||||
return OK;
|
||||
@@ -2298,6 +2296,7 @@ int KYTY_SYSV_ABI PthreadAttrSetschedparam(PthreadAttr* attr, const KernelSchedP
|
||||
return KERNEL_ERROR_EINVAL;
|
||||
}
|
||||
|
||||
#if KYTY_PLATFORM == KYTY_PLATFORM_WINDOWS
|
||||
KernelSchedParam pparam {};
|
||||
if (param->sched_priority <= 478) {
|
||||
pparam.sched_priority = +2;
|
||||
@@ -2307,12 +2306,13 @@ int KYTY_SYSV_ABI PthreadAttrSetschedparam(PthreadAttr* attr, const KernelSchedP
|
||||
pparam.sched_priority = 0;
|
||||
}
|
||||
|
||||
int result = pthread_attr_setschedparam(&attr_value->p, &pparam);
|
||||
|
||||
if (result == 0) {
|
||||
return OK;
|
||||
if (pthread_attr_setschedparam(&attr_value->p, &pparam) != 0) {
|
||||
return KERNEL_ERROR_EINVAL;
|
||||
}
|
||||
return KERNEL_ERROR_EINVAL;
|
||||
#endif
|
||||
|
||||
attr_value->guest_priority = param->sched_priority;
|
||||
return OK;
|
||||
}
|
||||
|
||||
int KYTY_SYSV_ABI PthreadAttrSetschedpolicy(PthreadAttr* attr, int policy) {
|
||||
@@ -3639,26 +3639,15 @@ int KYTY_SYSV_ABI PthreadGetprio(Pthread thread, int* prio) {
|
||||
|
||||
EXIT_NOT_IMPLEMENTED(prio == nullptr);
|
||||
|
||||
sched_param param {};
|
||||
int pol = 0;
|
||||
|
||||
int result = pthread_getschedparam(thread->p, &pol, ¶m);
|
||||
|
||||
if (result == 0) {
|
||||
if (param.sched_priority <= -2) {
|
||||
*prio = 767;
|
||||
} else if (param.sched_priority >= +2) {
|
||||
*prio = 256;
|
||||
} else {
|
||||
*prio = 700;
|
||||
}
|
||||
|
||||
LOGF("\t PthreadGetprio: %d, %d\n", thread->unique_id, *prio);
|
||||
|
||||
return OK;
|
||||
sched_param native_param {};
|
||||
int native_policy = 0;
|
||||
if (pthread_getschedparam(thread->p, &native_policy, &native_param) != 0) {
|
||||
return KERNEL_ERROR_EINVAL;
|
||||
}
|
||||
|
||||
return KERNEL_ERROR_EINVAL;
|
||||
*prio = thread->attr->guest_priority;
|
||||
LOGF("\t PthreadGetprio: %d, %d\n", thread->unique_id, *prio);
|
||||
return OK;
|
||||
}
|
||||
|
||||
int KYTY_SYSV_ABI PthreadSetprio(Pthread thread, int prio) {
|
||||
@@ -3673,25 +3662,27 @@ int KYTY_SYSV_ABI PthreadSetprio(Pthread thread, int prio) {
|
||||
|
||||
int result = pthread_getschedparam(thread->p, &pol, ¶m);
|
||||
|
||||
if (result == 0) {
|
||||
if (prio <= 478) {
|
||||
param.sched_priority = +2;
|
||||
} else if (prio >= 733) {
|
||||
param.sched_priority = -2;
|
||||
} else {
|
||||
param.sched_priority = 0;
|
||||
}
|
||||
|
||||
result = pthread_setschedparam(thread->p, pol, ¶m);
|
||||
|
||||
if (result == 0) {
|
||||
LOGF("\t PthreadSetprio: %d, %d\n", thread->unique_id, prio);
|
||||
|
||||
return OK;
|
||||
}
|
||||
if (result != 0) {
|
||||
return KERNEL_ERROR_EINVAL;
|
||||
}
|
||||
|
||||
return KERNEL_ERROR_EINVAL;
|
||||
#if KYTY_PLATFORM == KYTY_PLATFORM_WINDOWS
|
||||
if (prio <= 478) {
|
||||
param.sched_priority = +2;
|
||||
} else if (prio >= 733) {
|
||||
param.sched_priority = -2;
|
||||
} else {
|
||||
param.sched_priority = 0;
|
||||
}
|
||||
|
||||
if (pthread_setschedparam(thread->p, pol, ¶m) != 0) {
|
||||
return KERNEL_ERROR_EINVAL;
|
||||
}
|
||||
#endif
|
||||
|
||||
thread->attr->guest_priority = prio;
|
||||
LOGF("\t PthreadSetprio: %d, %d\n", thread->unique_id, prio);
|
||||
return OK;
|
||||
}
|
||||
|
||||
void KYTY_SYSV_ABI PthreadTestcancel() {
|
||||
|
||||
@@ -141,14 +141,14 @@ elseif(LINUX)
|
||||
endif()
|
||||
|
||||
|
||||
set(tidy_dirs "${CMAKE_SOURCE_DIR}/launcher/include")
|
||||
set(iwyu_maps "${CMAKE_SOURCE_DIR}/launcher/utils/qt6_16.imp")
|
||||
set(tidy_dirs "${CMAKE_CURRENT_SOURCE_DIR}/include")
|
||||
set(iwyu_maps "${CMAKE_CURRENT_SOURCE_DIR}/utils/qt6_16.imp")
|
||||
|
||||
get_property(inc_headers TARGET launcher PROPERTY INCLUDE_DIRECTORIES)
|
||||
|
||||
list(APPEND inc_headers
|
||||
${CMAKE_SOURCE_DIR}/launcher
|
||||
${CMAKE_BINARY_DIR}/launcher/launcher_autogen/include
|
||||
${CMAKE_CURRENT_SOURCE_DIR}
|
||||
${CMAKE_CURRENT_BINARY_DIR}/launcher_autogen/include
|
||||
${Qt6Widgets_INCLUDE_DIRS}
|
||||
${Qt6Core_INCLUDE_DIRS}
|
||||
${Qt6Gui_INCLUDE_DIRS}
|
||||
|
||||
@@ -7,7 +7,7 @@
|
||||
<x>0</x>
|
||||
<y>0</y>
|
||||
<width>560</width>
|
||||
<height>420</height>
|
||||
<height>450</height>
|
||||
</rect>
|
||||
</property>
|
||||
<property name="windowTitle">
|
||||
@@ -78,19 +78,6 @@
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="2" column="0" colspan="2">
|
||||
<widget class="QCheckBox" name="checkBox_ngg_rectlist_draw">
|
||||
<property name="toolTip">
|
||||
<string>Use the NGG 4-vertex path for rect-list DrawIndexAuto primitive 7</string>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>Use NGG rect-list draw</string>
|
||||
</property>
|
||||
<property name="checked">
|
||||
<bool>true</bool>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
</widget>
|
||||
</item>
|
||||
@@ -132,41 +119,55 @@
|
||||
</widget>
|
||||
</item>
|
||||
<item row="3" column="0">
|
||||
<widget class="QLabel" name="label_console_language">
|
||||
<property name="text">
|
||||
<string>Console language:</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="3" column="1">
|
||||
<widget class="QComboBox" name="comboBox_console_language">
|
||||
<property name="toolTip">
|
||||
<string>Language</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="4" column="0">
|
||||
<widget class="QLabel" name="label_20">
|
||||
<property name="text">
|
||||
<string>Shader optimization type:</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="3" column="1">
|
||||
<item row="4" column="1">
|
||||
<widget class="QComboBox" name="comboBox_shader_optimization_type">
|
||||
<property name="toolTip">
|
||||
<string>Optimize shaders for code size or performance</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="4" column="0">
|
||||
<item row="5" column="0">
|
||||
<widget class="QLabel" name="label_21">
|
||||
<property name="text">
|
||||
<string>Shader log direction:</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="4" column="1">
|
||||
<item row="5" column="1">
|
||||
<widget class="QComboBox" name="comboBox_shader_log_direction">
|
||||
<property name="toolTip">
|
||||
<string>Dump shaders to file or console window. If enabled may decrease emulator performance</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="5" column="0">
|
||||
<item row="6" column="0">
|
||||
<widget class="QLabel" name="label_22">
|
||||
<property name="text">
|
||||
<string>Shader log folder:</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="5" column="1">
|
||||
<item row="6" column="1">
|
||||
<widget class="MandatoryLineEdit" name="lineEdit_shader_log_folder">
|
||||
<property name="toolTip">
|
||||
<string>Specify directory to dump shaders</string>
|
||||
@@ -176,14 +177,14 @@
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="6" column="0">
|
||||
<item row="7" column="0">
|
||||
<widget class="QLabel" name="label_24">
|
||||
<property name="text">
|
||||
<string>Command buffer dump folder:</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="6" column="1">
|
||||
<item row="7" column="1">
|
||||
<widget class="MandatoryLineEdit" name="lineEdit_cmd_dump_folder">
|
||||
<property name="toolTip">
|
||||
<string>Specify directory to dump command buffers</string>
|
||||
@@ -193,28 +194,28 @@
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="7" column="0">
|
||||
<item row="8" column="0">
|
||||
<widget class="QLabel" name="label_25">
|
||||
<property name="text">
|
||||
<string>Printf direction:</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="7" column="1">
|
||||
<item row="8" column="1">
|
||||
<widget class="QComboBox" name="comboBox_printf_direction">
|
||||
<property name="toolTip">
|
||||
<string>Print logs to file or console window. If enabled may decrease emulator performance</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="8" column="0">
|
||||
<item row="9" column="0">
|
||||
<widget class="QLabel" name="label_26">
|
||||
<property name="text">
|
||||
<string>Printf output file:</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="8" column="1">
|
||||
<item row="9" column="1">
|
||||
<widget class="MandatoryLineEdit" name="lineEdit_printf_file">
|
||||
<property name="toolTip">
|
||||
<string>Specify file to dump logs</string>
|
||||
@@ -224,14 +225,14 @@
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="9" column="0">
|
||||
<item row="10" column="0">
|
||||
<widget class="QLabel" name="label_27">
|
||||
<property name="text">
|
||||
<string>Profiler direction:</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="9" column="1">
|
||||
<item row="10" column="1">
|
||||
<widget class="QComboBox" name="comboBox_profiler_direction">
|
||||
<property name="toolTip">
|
||||
<string>Enable or disable profiler. If enabled may decrease emulator performance</string>
|
||||
|
||||
@@ -48,6 +48,9 @@ class Configuration: public QObject {
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
static constexpr int DEFAULT_CONSOLE_LANGUAGE = 1;
|
||||
static constexpr int MAX_CONSOLE_LANGUAGE = 29;
|
||||
|
||||
enum class Resolution {
|
||||
R1280X720,
|
||||
R1920X1080,
|
||||
@@ -83,6 +86,7 @@ public:
|
||||
|
||||
Resolution screen_resolution = Resolution::R1280X720;
|
||||
int vblank_frequency = 60;
|
||||
int console_language = DEFAULT_CONSOLE_LANGUAGE;
|
||||
bool vulkan_validation_enabled = true;
|
||||
bool shader_validation_enabled = true;
|
||||
ShaderOptimizationType shader_optimization_type = ShaderOptimizationType::Performance;
|
||||
@@ -94,13 +98,13 @@ public:
|
||||
QString printf_output_file = "_kyty.txt";
|
||||
ProfilerDirection profiler_direction = ProfilerDirection::None;
|
||||
bool renderdoc_enabled = false;
|
||||
bool ngg_rectlist_draw_enabled = true;
|
||||
|
||||
QString elf = QStringLiteral("eboot.bin");
|
||||
|
||||
void CopyEmulatorSettingsFrom(const Configuration& other) {
|
||||
screen_resolution = other.screen_resolution;
|
||||
vblank_frequency = other.vblank_frequency;
|
||||
console_language = other.console_language;
|
||||
vulkan_validation_enabled = other.vulkan_validation_enabled;
|
||||
shader_validation_enabled = other.shader_validation_enabled;
|
||||
shader_optimization_type = other.shader_optimization_type;
|
||||
@@ -112,7 +116,6 @@ public:
|
||||
printf_output_file = other.printf_output_file;
|
||||
profiler_direction = other.profiler_direction;
|
||||
renderdoc_enabled = other.renderdoc_enabled;
|
||||
ngg_rectlist_draw_enabled = other.ngg_rectlist_draw_enabled;
|
||||
}
|
||||
|
||||
void CopyFrom(const Configuration& other) {
|
||||
@@ -136,6 +139,7 @@ public:
|
||||
KYTY_CFG_SET(custom_settings);
|
||||
KYTY_CFG_SET(screen_resolution);
|
||||
KYTY_CFG_SET(vblank_frequency);
|
||||
KYTY_CFG_SET(console_language);
|
||||
KYTY_CFG_SET(vulkan_validation_enabled);
|
||||
KYTY_CFG_SET(shader_validation_enabled);
|
||||
KYTY_CFG_SET(shader_optimization_type);
|
||||
@@ -147,7 +151,6 @@ public:
|
||||
KYTY_CFG_SET(printf_output_file);
|
||||
KYTY_CFG_SET(profiler_direction);
|
||||
KYTY_CFG_SET(renderdoc_enabled);
|
||||
KYTY_CFG_SET(ngg_rectlist_draw_enabled);
|
||||
KYTY_CFG_SET(elf);
|
||||
}
|
||||
|
||||
@@ -158,6 +161,10 @@ public:
|
||||
KYTY_CFG_GET(custom_settings);
|
||||
KYTY_CFG_GET(screen_resolution);
|
||||
vblank_frequency = s->value("vblank_frequency", vblank_frequency).toInt();
|
||||
console_language = s->value("console_language", console_language).toInt();
|
||||
if (console_language < 0 || console_language > MAX_CONSOLE_LANGUAGE) {
|
||||
console_language = DEFAULT_CONSOLE_LANGUAGE;
|
||||
}
|
||||
KYTY_CFG_GET(vulkan_validation_enabled);
|
||||
KYTY_CFG_GET(shader_validation_enabled);
|
||||
KYTY_CFG_GET(shader_optimization_type);
|
||||
@@ -169,8 +176,6 @@ public:
|
||||
KYTY_CFG_GET(printf_output_file);
|
||||
KYTY_CFG_GET(profiler_direction);
|
||||
KYTY_CFG_GET(renderdoc_enabled);
|
||||
ngg_rectlist_draw_enabled =
|
||||
s->value("ngg_rectlist_draw_enabled", ngg_rectlist_draw_enabled).toBool();
|
||||
elf = s->value("elf", elf).toString();
|
||||
}
|
||||
};
|
||||
|
||||
@@ -30,6 +30,39 @@ constexpr char SETTINGS_CFG_DIALOG[] = "ConfigurationEditDialog";
|
||||
constexpr char SETTINGS_CFG_LAST_GEOMETRY[] = "geometry";
|
||||
constexpr int GLOBAL_SETTINGS_GAME_DIRS_MIN_WIDTH = 560;
|
||||
|
||||
const QStringList CONSOLE_LANGUAGE_NAMES = {
|
||||
"Japanese",
|
||||
"English (United States)",
|
||||
"French (France)",
|
||||
"Spanish (Spain)",
|
||||
"German",
|
||||
"Italian",
|
||||
"Dutch",
|
||||
"Portuguese (Portugal)",
|
||||
"Russian",
|
||||
"Korean",
|
||||
"Chinese (Traditional)",
|
||||
"Chinese (Simplified)",
|
||||
"Finnish",
|
||||
"Swedish",
|
||||
"Danish",
|
||||
"Norwegian",
|
||||
"Polish",
|
||||
"Portuguese (Brazil)",
|
||||
"English (United Kingdom)",
|
||||
"Turkish",
|
||||
"Spanish (Latin America)",
|
||||
"Arabic",
|
||||
"French (Canada)",
|
||||
"Czech",
|
||||
"Hungarian",
|
||||
"Greek",
|
||||
"Romanian",
|
||||
"Thai",
|
||||
"Vietnamese",
|
||||
"Indonesian",
|
||||
};
|
||||
|
||||
static QString NormalizeGameDirectory(const QString& dir) {
|
||||
const auto trimmed = dir.trimmed();
|
||||
if (trimmed.isEmpty()) {
|
||||
@@ -121,10 +154,15 @@ static void ListInit(QComboBox* combo, T value) {
|
||||
void ConfigurationEditDialog::Init(const Configuration& info) {
|
||||
ListInit(m_ui->comboBox_screen_resolution, info.screen_resolution);
|
||||
m_ui->spinBox_vblank_frequency->setValue(info.vblank_frequency);
|
||||
m_ui->comboBox_console_language->clear();
|
||||
m_ui->comboBox_console_language->addItems(CONSOLE_LANGUAGE_NAMES);
|
||||
m_ui->comboBox_console_language->setCurrentIndex(
|
||||
info.console_language >= 0 && info.console_language < CONSOLE_LANGUAGE_NAMES.size()
|
||||
? info.console_language
|
||||
: Configuration::DEFAULT_CONSOLE_LANGUAGE);
|
||||
m_ui->checkBox_shader_validation->setChecked(info.shader_validation_enabled);
|
||||
m_ui->checkBox_vulkan_validation->setChecked(info.vulkan_validation_enabled);
|
||||
m_ui->checkBox_renderdoc_capture->setChecked(info.renderdoc_enabled);
|
||||
m_ui->checkBox_ngg_rectlist_draw->setChecked(info.ngg_rectlist_draw_enabled);
|
||||
ListInit(m_ui->comboBox_shader_optimization_type, info.shader_optimization_type);
|
||||
ListInit(m_ui->comboBox_shader_log_direction, info.shader_log_direction);
|
||||
m_ui->lineEdit_shader_log_folder->setText(info.shader_log_folder);
|
||||
@@ -242,10 +280,10 @@ static void UpdateInfo(Configuration& info, Ui::ConfigurationEditDialog& ui) {
|
||||
info.screen_resolution =
|
||||
TextToEnum<Configuration::Resolution>(ui.comboBox_screen_resolution->currentText());
|
||||
info.vblank_frequency = ui.spinBox_vblank_frequency->value();
|
||||
info.console_language = ui.comboBox_console_language->currentIndex();
|
||||
info.vulkan_validation_enabled = ui.checkBox_vulkan_validation->isChecked();
|
||||
info.shader_validation_enabled = ui.checkBox_shader_validation->isChecked();
|
||||
info.renderdoc_enabled = ui.checkBox_renderdoc_capture->isChecked();
|
||||
info.ngg_rectlist_draw_enabled = ui.checkBox_ngg_rectlist_draw->isChecked();
|
||||
info.shader_optimization_type = TextToEnum<Configuration::ShaderOptimizationType>(
|
||||
ui.comboBox_shader_optimization_type->currentText());
|
||||
info.shader_log_direction = TextToEnum<Configuration::ShaderLogDirection>(
|
||||
|
||||
@@ -205,6 +205,7 @@ static QStringList CreateEmulatorArgs(const Configuration& info) {
|
||||
args << "--screen-width" << r.at(0);
|
||||
args << "--screen-height" << r.at(1);
|
||||
args << "--vblank-frequency" << QString::number(info.vblank_frequency);
|
||||
args << "--console-language" << QString::number(info.console_language);
|
||||
args << "--vulkan-validation" << BoolArg(info.vulkan_validation_enabled);
|
||||
args << "--shader-validation" << BoolArg(info.shader_validation_enabled);
|
||||
args << "--shader-optimization-type" << EnumToText(info.shader_optimization_type);
|
||||
@@ -216,7 +217,6 @@ static QStringList CreateEmulatorArgs(const Configuration& info) {
|
||||
args << "--printf-output-file" << info.printf_output_file;
|
||||
args << "--profiler-direction" << EnumToText(info.profiler_direction);
|
||||
args << "--spirv-debug-printf" << "false";
|
||||
args << "--ngg-rectlist-draw" << BoolArg(info.ngg_rectlist_draw_enabled);
|
||||
if (info.renderdoc_enabled) {
|
||||
args << "--rd";
|
||||
}
|
||||
|
||||
@@ -2021,6 +2021,12 @@ int64_t KYTY_SYSV_ABI fstat(int d, LibKernel::FileSystem::FileStat* sb) {
|
||||
return POSIX_N_CALL(LibKernel::FileSystem::KernelFstat(d, sb));
|
||||
}
|
||||
|
||||
int KYTY_SYSV_ABI ftruncate(int d, int64_t length) {
|
||||
PRINT_NAME();
|
||||
|
||||
return POSIX_CALL(LibKernel::FileSystem::KernelFtruncate(d, length));
|
||||
}
|
||||
|
||||
int KYTY_SYSV_ABI socket(int family, int type, int protocol) {
|
||||
PRINT_NAME();
|
||||
return Network::Net::Socket(family, type, protocol);
|
||||
@@ -2159,6 +2165,7 @@ LIB_DEFINE(InitLibKernel_1_Posix) {
|
||||
LIB_FUNC("yS8U2TGCe1A", nanosleep);
|
||||
LIB_FUNC("E6ao34wPw+U", stat);
|
||||
LIB_FUNC("JGMio+21L4c", mkdir);
|
||||
LIB_FUNC("ih4CD9-gghM", Posix::ftruncate);
|
||||
LIB_FUNC("pDuPEf3m4fI", Posix::sem_init);
|
||||
LIB_FUNC("cDW233RAwWo", Posix::sem_destroy);
|
||||
LIB_FUNC("YCV5dGGBcCo", Posix::sem_wait);
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
#include "common/abi.h"
|
||||
#include "common/assert.h"
|
||||
#include "common/common.h"
|
||||
#include "common/emulatorConfig.h"
|
||||
#include "common/logging/log.h"
|
||||
#include "common/stringUtils.h"
|
||||
#include "libs/errno.h"
|
||||
@@ -25,37 +26,6 @@ namespace SystemService {
|
||||
[[maybe_unused]] constexpr int PARAM_ID_SCREEN_READER = 208;
|
||||
[[maybe_unused]] constexpr int PARAM_ID_ENTER_BUTTON_ASSIGN = 1000;
|
||||
|
||||
[[maybe_unused]] constexpr int PARAM_LANG_JAPANESE = 0;
|
||||
[[maybe_unused]] constexpr int PARAM_LANG_ENGLISH_US = 1;
|
||||
[[maybe_unused]] constexpr int PARAM_LANG_FRENCH = 2;
|
||||
[[maybe_unused]] constexpr int PARAM_LANG_SPANISH = 3;
|
||||
[[maybe_unused]] constexpr int PARAM_LANG_GERMAN = 4;
|
||||
[[maybe_unused]] constexpr int PARAM_LANG_ITALIAN = 5;
|
||||
[[maybe_unused]] constexpr int PARAM_LANG_DUTCH = 6;
|
||||
[[maybe_unused]] constexpr int PARAM_LANG_PORTUGUESE_PT = 7;
|
||||
[[maybe_unused]] constexpr int PARAM_LANG_RUSSIAN = 8;
|
||||
[[maybe_unused]] constexpr int PARAM_LANG_KOREAN = 9;
|
||||
[[maybe_unused]] constexpr int PARAM_LANG_CHINESE_T = 10;
|
||||
[[maybe_unused]] constexpr int PARAM_LANG_CHINESE_S = 11;
|
||||
[[maybe_unused]] constexpr int PARAM_LANG_FINNISH = 12;
|
||||
[[maybe_unused]] constexpr int PARAM_LANG_SWEDISH = 13;
|
||||
[[maybe_unused]] constexpr int PARAM_LANG_DANISH = 14;
|
||||
[[maybe_unused]] constexpr int PARAM_LANG_NORWEGIAN = 15;
|
||||
[[maybe_unused]] constexpr int PARAM_LANG_POLISH = 16;
|
||||
[[maybe_unused]] constexpr int PARAM_LANG_PORTUGUESE_BR = 17;
|
||||
[[maybe_unused]] constexpr int PARAM_LANG_ENGLISH_GB = 18;
|
||||
[[maybe_unused]] constexpr int PARAM_LANG_TURKISH = 19;
|
||||
[[maybe_unused]] constexpr int PARAM_LANG_SPANISH_LA = 20;
|
||||
[[maybe_unused]] constexpr int PARAM_LANG_ARABIC = 21;
|
||||
[[maybe_unused]] constexpr int PARAM_LANG_FRENCH_CA = 22;
|
||||
[[maybe_unused]] constexpr int PARAM_LANG_CZECH = 23;
|
||||
[[maybe_unused]] constexpr int PARAM_LANG_HUNGARIAN = 24;
|
||||
[[maybe_unused]] constexpr int PARAM_LANG_GREEK = 25;
|
||||
[[maybe_unused]] constexpr int PARAM_LANG_ROMANIAN = 26;
|
||||
[[maybe_unused]] constexpr int PARAM_LANG_THAI = 27;
|
||||
[[maybe_unused]] constexpr int PARAM_LANG_VIETNAMESE = 28;
|
||||
[[maybe_unused]] constexpr int PARAM_LANG_INDONESIAN = 29;
|
||||
|
||||
[[maybe_unused]] constexpr int PARAM_DATE_FORMAT_YYYYMMDD = 0;
|
||||
[[maybe_unused]] constexpr int PARAM_DATE_FORMAT_DDMMYYYY = 1;
|
||||
[[maybe_unused]] constexpr int PARAM_DATE_FORMAT_MMDDYYYY = 2;
|
||||
@@ -121,7 +91,7 @@ static int KYTY_SYSV_ABI SystemServiceParamGetInt(int param_id, int* value) {
|
||||
int v = 0;
|
||||
|
||||
switch (param_id) {
|
||||
case PARAM_ID_LANG: v = PARAM_LANG_ENGLISH_US; break;
|
||||
case PARAM_ID_LANG: v = static_cast<int>(Config::GetConsoleLanguage()); break;
|
||||
case PARAM_ID_DATE_FORMAT: v = PARAM_DATE_FORMAT_DDMMYYYY; break;
|
||||
case PARAM_ID_TIME_FORMAT: v = PARAM_TIME_FORMAT_24HOUR; break;
|
||||
case PARAM_ID_TIME_ZONE: v = +180; break;
|
||||
|
||||
@@ -360,9 +360,10 @@ static KYTY_SYSV_ABI void RunEntry(uint64_t addr, EntryParams* params, atexit_fu
|
||||
auto* func = reinterpret_cast<entry_func_t>(addr);
|
||||
|
||||
if (stack_top != nullptr) {
|
||||
const auto guest_rsp =
|
||||
const auto aligned_stack_top =
|
||||
reinterpret_cast<uintptr_t>(stack_top) & ~static_cast<uintptr_t>(0x0f);
|
||||
const auto guest_rbp = guest_rsp - 4u * sizeof(uint64_t);
|
||||
const auto guest_rsp = aligned_stack_top - 2u * sizeof(uintptr_t);
|
||||
const auto guest_rbp = guest_rsp;
|
||||
|
||||
auto* guest_root_frame = reinterpret_cast<uintptr_t*>(guest_rbp);
|
||||
guest_root_frame[0] = 0;
|
||||
@@ -507,6 +508,13 @@ struct MainEntryStackTestState {
|
||||
static KYTY_SYSV_ABI void TestMainEntryStackCallback(EntryParams* params,
|
||||
atexit_func_t /*atexit_func*/) {
|
||||
auto* state = reinterpret_cast<MainEntryStackTestState*>(const_cast<char*>(params->argv[0]));
|
||||
asm volatile("pushq %%r15\n\t"
|
||||
"pushq %%r14\n\t"
|
||||
"popq %%r14\n\t"
|
||||
"popq %%r15\n\t"
|
||||
:
|
||||
:
|
||||
: "memory");
|
||||
asm volatile("movq %%rsp, %0" : "=r"(state->rsp) : : "memory");
|
||||
#if KYTY_PLATFORM == KYTY_PLATFORM_WINDOWS
|
||||
asm volatile("movq %%gs:0x08, %0\n\t"
|
||||
@@ -529,6 +537,8 @@ bool TestMainEntryUsesGuestStack() {
|
||||
MainEntryStackTestState state {};
|
||||
EntryParams params {};
|
||||
params.argv[0] = reinterpret_cast<const char*>(&state);
|
||||
std::memset(reinterpret_cast<void*>(stack_base), 0xcd, stack_size);
|
||||
auto* root_frame = reinterpret_cast<const uintptr_t*>(stack_base + stack_size) - 2;
|
||||
|
||||
#if KYTY_PLATFORM == KYTY_PLATFORM_WINDOWS
|
||||
uintptr_t original_teb_stack_base = 0;
|
||||
@@ -558,9 +568,10 @@ bool TestMainEntryUsesGuestStack() {
|
||||
constexpr bool teb_ok = true;
|
||||
#endif
|
||||
|
||||
const bool rsp_ok = state.rsp >= stack_base && state.rsp < stack_base + stack_size;
|
||||
const bool freed = Libs::LibKernel::Memory::FreeGuestMemory(stack_base, stack_size);
|
||||
return state.called && rsp_ok && teb_ok && freed;
|
||||
const bool rsp_ok = state.rsp >= stack_base && state.rsp < stack_base + stack_size;
|
||||
const bool root_ok = root_frame[0] == 0 && root_frame[1] == 0;
|
||||
const bool freed = Libs::LibKernel::Memory::FreeGuestMemory(stack_base, stack_size);
|
||||
return state.called && rsp_ok && root_ok && teb_ok && freed;
|
||||
}
|
||||
|
||||
bool TestModuleRelocationUsesWritableHostMapping() {
|
||||
|
||||
+18
-7
@@ -10,6 +10,7 @@
|
||||
#include "emulator.h"
|
||||
#include "kytyGitVersion.h"
|
||||
|
||||
#include <charconv>
|
||||
#include <cstdio>
|
||||
#include <fmt/format.h>
|
||||
|
||||
@@ -47,6 +48,7 @@ static void PrintUsage() {
|
||||
::printf(" --screen-width <num> Window width. Default: 1280.\n");
|
||||
::printf(" --screen-height <num> Window height. Default: 720.\n");
|
||||
::printf(" --vblank-frequency <num> Virtual vblank frequency. Default: 60.\n");
|
||||
::printf(" --console-language <0-29> Console language. Default: 1 (English US).\n");
|
||||
::printf(" --vulkan-validation <true|false> Enable Vulkan validation.\n");
|
||||
::printf(" --shader-validation <true|false> Enable shader validation.\n");
|
||||
::printf(" --shader-optimization-type <value> None, Size, or Performance.\n");
|
||||
@@ -59,8 +61,6 @@ static void PrintUsage() {
|
||||
::printf(" --printf-output-file <path> Guest printf output file.\n");
|
||||
::printf(" --profiler-direction <value> None or Network.\n");
|
||||
::printf(" --spirv-debug-printf <true|false> Enable SPIR-V debug printf.\n");
|
||||
::printf(" --ngg-rectlist-draw <true|false> Draw rect-list auto draws using the NGG "
|
||||
"4-vertex path.\n");
|
||||
::printf(
|
||||
" --readback-linear-images <true|false> Read back writable linear images on submit.\n");
|
||||
::printf(" --rd Enable RenderDoc capture.\n");
|
||||
@@ -103,6 +103,17 @@ static bool ParseEnum(const std::string& value, E& out) {
|
||||
return true;
|
||||
}
|
||||
|
||||
static bool ParseConsoleLanguage(const std::string& value, uint32_t& out) {
|
||||
uint32_t language = 0;
|
||||
auto [end, error] = std::from_chars(value.data(), value.data() + value.size(), language);
|
||||
if (error != std::errc {} || end != value.data() + value.size() ||
|
||||
language > Config::MAX_CONSOLE_LANGUAGE) {
|
||||
return false;
|
||||
}
|
||||
out = language;
|
||||
return true;
|
||||
}
|
||||
|
||||
static bool ParseArgs(int argc, char* argv[], RunOptions& options, bool& show_help) {
|
||||
show_help = false;
|
||||
|
||||
@@ -169,6 +180,11 @@ static bool ParseArgs(int argc, char* argv[], RunOptions& options, bool& show_he
|
||||
const int32_t vblank_frequency = Common::ToInt32(value);
|
||||
options.config.vblank_frequency =
|
||||
static_cast<uint32_t>(vblank_frequency < 0 ? 0 : vblank_frequency);
|
||||
} else if (arg == "--console-language") {
|
||||
if (!ParseConsoleLanguage(value, options.config.console_language)) {
|
||||
::printf("invalid console language: %s\n", value.c_str());
|
||||
return false;
|
||||
}
|
||||
} else if (arg == "--vulkan-validation") {
|
||||
if (!ParseBool(value, options.config.vulkan_validation_enabled)) {
|
||||
::printf("invalid boolean for %s: %s\n", arg.c_str(), value.c_str());
|
||||
@@ -220,11 +236,6 @@ static bool ParseArgs(int argc, char* argv[], RunOptions& options, bool& show_he
|
||||
::printf("invalid boolean for %s: %s\n", arg.c_str(), value.c_str());
|
||||
return false;
|
||||
}
|
||||
} else if (arg == "--ngg-rectlist-draw") {
|
||||
if (!ParseBool(value, options.config.ngg_rectlist_draw_enabled)) {
|
||||
::printf("invalid boolean for %s: %s\n", arg.c_str(), value.c_str());
|
||||
return false;
|
||||
}
|
||||
} else if (arg == "--readback-linear-images") {
|
||||
if (!ParseBool(value, options.config.readback_linear_images)) {
|
||||
::printf("invalid boolean for %s: %s\n", arg.c_str(), value.c_str());
|
||||
|
||||
@@ -3,13 +3,15 @@
|
||||
#include <cstdint>
|
||||
#include <cstdio>
|
||||
#include <cstdlib>
|
||||
#include <utility>
|
||||
#include <vector>
|
||||
|
||||
namespace {
|
||||
|
||||
using Owners = std::vector<uint32_t>;
|
||||
using Table = Libs::Graphics::MultiLevelPageTable<Owners>;
|
||||
using OwnerIndex = Libs::Graphics::MultiRangePageOwnerIndex<uint32_t>;
|
||||
using PageOwners = Libs::Graphics::InlinePageOwnerList<uint32_t, 16>;
|
||||
using OwnerTable = Libs::Graphics::MultiLevelPageTable<PageOwners, 20, 40, 10>;
|
||||
|
||||
void Check(bool value, const char* text) {
|
||||
if (!value) {
|
||||
@@ -77,76 +79,93 @@ void TestAddressSpaceBoundaries() {
|
||||
"final page supports allocating and nonallocating access");
|
||||
}
|
||||
|
||||
void TestMultiRangeRegistrationDeduplicatesPages() {
|
||||
OwnerIndex index;
|
||||
// Depth and stencil-like planes overlap tracking pages and share one 1 MiB bucket.
|
||||
Check(index.Register(7, {{0x101000, 0x2800}, {0x102000, 0x3000}}),
|
||||
"multi-range owner registers");
|
||||
Check(index.CoarseMembershipCount(1) == 1,
|
||||
"one owner is inserted once in a shared 1 MiB bucket");
|
||||
Check(index.TrackingMembershipCount(0x102) == 1,
|
||||
"overlapping planes insert one 4 KiB membership");
|
||||
Check(!index.Register(7, {{0x101000, 0x1000}}), "duplicate owner registration hard-fails");
|
||||
void TestInlineOwnerStorageAndOverflow() {
|
||||
PageOwners owners;
|
||||
for (uint32_t owner = 1; owner <= 18; ++owner) {
|
||||
owners.push_back(owner);
|
||||
}
|
||||
Check(owners.size() == 18 && owners.front() == 1,
|
||||
"inline owner storage grows past its 16-owner capacity");
|
||||
uint32_t expected = 1;
|
||||
for (const uint32_t owner: owners) {
|
||||
Check(owner == expected++, "overflow preserves registration order");
|
||||
}
|
||||
Check(owners.Erase(5) && owners.size() == 17 && !owners.Contains(5),
|
||||
"overflow erase removes only the requested owner");
|
||||
Check(owners.Erase(18) && owners.size() == 16,
|
||||
"overflow storage shrinks back to inline capacity");
|
||||
const std::vector<uint32_t> remaining {1, 2, 3, 4, 6, 7, 8, 9,
|
||||
10, 11, 12, 13, 14, 15, 16, 17};
|
||||
size_t remaining_index = 0;
|
||||
for (const uint32_t owner: owners) {
|
||||
Check(owner == remaining[remaining_index++],
|
||||
"overflow-to-inline shrink preserves every owner");
|
||||
}
|
||||
Check(!owners.Erase(99), "missing inline owner is reported without mutation");
|
||||
|
||||
const auto owners = index.Query(0x100000, 0x10000);
|
||||
Check(owners.size() == 1 && owners.front() == 7, "multi-page query returns an owner once");
|
||||
PageOwners moved = std::move(owners);
|
||||
Check(owners.empty() && moved.size() == remaining.size() && moved.front() == 1,
|
||||
"moving a full inline owner list leaves the source empty");
|
||||
PageOwners partial;
|
||||
partial.push_back(41);
|
||||
partial.push_back(42);
|
||||
PageOwners partial_moved = std::move(partial);
|
||||
Check(partial.empty() && partial_moved.size() == 2 && partial_moved[1] == 42,
|
||||
"moving a partially populated list copies only live owners");
|
||||
PageOwners assigned;
|
||||
assigned.push_back(99);
|
||||
assigned = std::move(partial_moved);
|
||||
Check(partial_moved.empty() && assigned.size() == 2 && assigned.front() == 41,
|
||||
"move assignment replaces an inline list without reading inactive slots");
|
||||
PageOwners empty;
|
||||
PageOwners empty_moved = std::move(empty);
|
||||
Check(empty.empty() && empty_moved.empty(), "moving an empty owner list is safe");
|
||||
}
|
||||
|
||||
void TestSharedPageUnregisterLifecycle() {
|
||||
OwnerIndex index;
|
||||
const std::vector<OwnerIndex::ByteRange> ranges {{0x202000, 0x2000}};
|
||||
Check(index.Register(11, ranges) && index.Register(22, ranges),
|
||||
"two owners register on identical pages");
|
||||
Check(index.CoarseMembershipCount(2) == 2 && index.TrackingMembershipCount(0x202) == 2,
|
||||
"coarse and tracking pages retain both owners");
|
||||
|
||||
std::vector<OwnerIndex::ByteRange> releases;
|
||||
Check(index.Unregister(11, releases), "first owner unregisters");
|
||||
Check(releases.empty(), "shared tracking pages are not released with one owner remaining");
|
||||
Check(index.Query(0x202000, 1).size() == 1 && index.Query(0x202000, 1).front() == 22,
|
||||
"unregistering one owner preserves the other");
|
||||
Check(!index.Unregister(11, releases), "missing membership hard-fails without mutation");
|
||||
|
||||
Check(index.Unregister(22, releases), "final owner unregisters");
|
||||
Check(releases.size() == 1 && releases.front().address == 0x202000 &&
|
||||
releases.front().size == 0x2000,
|
||||
"adjacent final-owner tracking pages return one contiguous release");
|
||||
void TestOneMiBRegistrationGranularity() {
|
||||
static_assert(OwnerTable::kPageBits == 20);
|
||||
OwnerTable table;
|
||||
OwnerTable::PageRange pages {};
|
||||
constexpr uint64_t range_size = 64ull * 1024 * 1024;
|
||||
Check(OwnerTable::TryGetPageRange(0, range_size, pages), "large owner range is valid");
|
||||
Check(pages.first == 0 && pages.last_exclusive == 64,
|
||||
"64 MiB registration touches exactly 64 one-MiB entries");
|
||||
for (size_t page = pages.first; page < pages.last_exclusive; ++page) {
|
||||
table[page].push_back(7);
|
||||
}
|
||||
Check(table.AllocatedBucketCount() == 1,
|
||||
"large registration uses one sparse second-level bucket");
|
||||
for (size_t page = pages.first; page < pages.last_exclusive; ++page) {
|
||||
auto* owners = table.Find(page);
|
||||
Check(owners != nullptr && owners->size() == 1 && owners->front() == 7,
|
||||
"each touched one-MiB entry retains the owner once");
|
||||
Check(owners->Erase(7), "large owner unregisters by coarse page");
|
||||
}
|
||||
}
|
||||
|
||||
void TestStrictByteFilteringAndPredicate() {
|
||||
OwnerIndex index;
|
||||
Check(index.Register(31, {{0x300100, 0x100}}), "first byte-disjoint owner registers");
|
||||
Check(index.Register(32, {{0x300800, 0x100}}), "second byte-disjoint owner registers");
|
||||
Check(index.Register(33, {{0x30f000, 0x100}}), "coarse-only owner registers");
|
||||
Check(index.TrackingMembershipCount(0x300) == 2,
|
||||
"byte-disjoint owners share one tracking page");
|
||||
Check(index.CoarseMembershipCount(3) == 3,
|
||||
"all owners share one coarse candidate bucket");
|
||||
Check(index.Query(0x300400, 0x40).empty(), "page hit without byte overlap is filtered out");
|
||||
const auto page_candidates = index.QueryCandidates(0x300400, 0x40);
|
||||
Check(page_candidates.size() == 2,
|
||||
"fault candidate query retains touched-page owners and rejects coarse-only owners");
|
||||
const auto first = index.Query(0x300180, 0x10);
|
||||
Check(first.size() == 1 && first.front() == 31,
|
||||
"strict byte overlap selects only the matching owner");
|
||||
const auto predicate_filtered =
|
||||
index.Query(0x300000, 0x1000, [](uint32_t owner) { return owner == 32; });
|
||||
Check(predicate_filtered.size() == 1 && predicate_filtered.front() == 32,
|
||||
"supplied predicate filters query owners");
|
||||
void TestSharedCoarsePageLifecycle() {
|
||||
OwnerTable table;
|
||||
auto& owners = table[2];
|
||||
owners.push_back(11);
|
||||
owners.push_back(22);
|
||||
Check(owners.size() == 2, "two images share one coarse page");
|
||||
Check(owners.Erase(11) && owners.size() == 1 && owners.front() == 22,
|
||||
"unregistering one image preserves its coarse-page neighbor");
|
||||
Check(!owners.Erase(11), "double unregister is rejected without mutation");
|
||||
Check(owners.Erase(22) && owners.empty(), "final coarse-page owner unregisters");
|
||||
}
|
||||
|
||||
void TestOwnerIndexAddressSpaceBoundary() {
|
||||
OwnerIndex index;
|
||||
constexpr uint64_t last_byte = OwnerIndex::CoarseTable::kAddressSpaceSize - 1;
|
||||
Check(index.Register(41, {{last_byte, 1}}), "final guest byte registers");
|
||||
const auto exact = index.Query(last_byte, 1);
|
||||
Check(exact.size() == 1 && exact.front() == 41,
|
||||
"strict query finds an exact overlap at the final guest byte");
|
||||
Check(index.Query(last_byte - 1, 1).empty(),
|
||||
"strict query preserves half-open overlap boundaries");
|
||||
const auto page_candidates = index.QueryCandidates(last_byte - 1, 1);
|
||||
Check(page_candidates.size() == 1 && page_candidates.front() == 41,
|
||||
"page candidate query retains a byte-disjoint owner on the final tracking page");
|
||||
void TestOneMiBBoundaries() {
|
||||
OwnerTable::PageRange range {};
|
||||
Check(OwnerTable::TryGetPageRange(0x0fffff, 2, range),
|
||||
"range crossing a one-MiB boundary is valid");
|
||||
Check(range.first == 0 && range.last_exclusive == 2,
|
||||
"cross-boundary registration touches both coarse pages");
|
||||
Check(OwnerTable::TryGetPageRange(OwnerTable::kAddressSpaceSize - 1, 1, range),
|
||||
"final guest byte maps to a coarse owner page");
|
||||
Check(range.first == OwnerTable::kPageCount - 1 &&
|
||||
range.last_exclusive == OwnerTable::kPageCount,
|
||||
"final guest byte uses the final one-MiB page");
|
||||
}
|
||||
|
||||
} // namespace
|
||||
@@ -156,10 +175,10 @@ int main() {
|
||||
TestCrossBucketRange();
|
||||
TestQueriesDoNotAllocate();
|
||||
TestAddressSpaceBoundaries();
|
||||
TestMultiRangeRegistrationDeduplicatesPages();
|
||||
TestSharedPageUnregisterLifecycle();
|
||||
TestStrictByteFilteringAndPredicate();
|
||||
TestOwnerIndexAddressSpaceBoundary();
|
||||
TestInlineOwnerStorageAndOverflow();
|
||||
TestOneMiBRegistrationGranularity();
|
||||
TestSharedCoarsePageLifecycle();
|
||||
TestOneMiBBoundaries();
|
||||
std::printf("ImagePageTableTests: all cases passed\n");
|
||||
return 0;
|
||||
}
|
||||
|
||||
@@ -39,6 +39,7 @@
|
||||
#include "graphics/shader/recompiler/emitter/SpirvBuilder.h"
|
||||
#include "graphics/shader/recompiler/emitter/SpirvEmitter.h"
|
||||
#include "graphics/shader/recompiler/ir/BindingLayout.h"
|
||||
#include "graphics/shader/rectListShader.h"
|
||||
#include "graphics/shader/shader.h"
|
||||
#include "kernel/memory.h"
|
||||
#include "spirv-tools/libspirv.hpp"
|
||||
@@ -162,6 +163,10 @@ struct TileManagerTestAccess {
|
||||
};
|
||||
|
||||
struct TextureCacheTestAccess {
|
||||
static_assert(TextureCache::ImagePageTable::kPageBits == 20);
|
||||
static_assert(TextureCache::ImagePageTable::kAddressSpaceBits == 40);
|
||||
static_assert(TextureCache::ImagePageTable::kFirstLevelBits == 10);
|
||||
|
||||
static void ConfigureGarbageCollection(TextureCache& cache, std::span<const ImageId> oldest,
|
||||
uint64_t tick, uint64_t pressure) {
|
||||
cache.m_trigger_gc_memory = 0;
|
||||
@@ -196,6 +201,75 @@ struct TextureCacheTestAccess {
|
||||
return owner != nullptr && owner->registered;
|
||||
}
|
||||
|
||||
static std::vector<ImageId> FindImages(TextureCache& cache, uint64_t address, uint64_t size,
|
||||
bool page_overlap) {
|
||||
std::lock_guard lock(cache.m_lock);
|
||||
const auto found = cache.FindImagesInRegion(address, size, page_overlap);
|
||||
std::vector<ImageId> result;
|
||||
result.reserve(found.size());
|
||||
for (const auto id: found) {
|
||||
result.push_back(id);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
static size_t PageOwnerCount(TextureCache& cache, uint64_t address) {
|
||||
std::lock_guard lock(cache.m_lock);
|
||||
const auto* owners = cache.m_image_page_table.Find(
|
||||
static_cast<size_t>(address >> TextureCache::ImagePageTable::kPageBits));
|
||||
return owners == nullptr ? 0 : owners->size();
|
||||
}
|
||||
|
||||
static size_t OwnedPageCount(TextureCache& cache, uint64_t address, uint64_t size, ImageId id) {
|
||||
std::lock_guard lock(cache.m_lock);
|
||||
TextureCache::ImagePageTable::PageRange pages {};
|
||||
if (!TextureCache::ImagePageTable::TryGetPageRange(address, size, pages)) {
|
||||
return 0;
|
||||
}
|
||||
size_t count = 0;
|
||||
for (size_t page = pages.first; page < pages.last_exclusive; ++page) {
|
||||
const auto* owners = cache.m_image_page_table.Find(page);
|
||||
count += owners != nullptr && owners->Contains(id) ? 1 : 0;
|
||||
}
|
||||
return count;
|
||||
}
|
||||
|
||||
static void AddPageOwner(TextureCache& cache, uint64_t address, ImageId id) {
|
||||
std::lock_guard lock(cache.m_lock);
|
||||
cache.m_image_page_table[static_cast<size_t>(
|
||||
address >> TextureCache::ImagePageTable::kPageBits)]
|
||||
.push_back(id);
|
||||
}
|
||||
|
||||
static bool RemovePageOwner(TextureCache& cache, uint64_t address, ImageId id) {
|
||||
std::lock_guard lock(cache.m_lock);
|
||||
auto* owners = cache.m_image_page_table.Find(
|
||||
static_cast<size_t>(address >> TextureCache::ImagePageTable::kPageBits));
|
||||
return owners != nullptr && owners->Erase(id);
|
||||
}
|
||||
|
||||
static void SetQueryEpoch(TextureCache& cache, uint32_t epoch) {
|
||||
std::lock_guard lock(cache.m_lock);
|
||||
cache.m_image_query_epoch = epoch;
|
||||
}
|
||||
|
||||
static uint32_t QueryEpoch(TextureCache& cache) {
|
||||
std::lock_guard lock(cache.m_lock);
|
||||
return cache.m_image_query_epoch;
|
||||
}
|
||||
|
||||
static ImageId InsertImage(TextureCache& cache, const ImageInfo& info) {
|
||||
std::lock_guard transaction(cache.m_resource_mutex);
|
||||
std::lock_guard lock(cache.m_lock);
|
||||
return cache.InsertImage(info);
|
||||
}
|
||||
|
||||
static void DeleteImage(TextureCache& cache, ImageId id) {
|
||||
std::lock_guard transaction(cache.m_resource_mutex);
|
||||
std::lock_guard lock(cache.m_lock);
|
||||
cache.DeleteImage(id);
|
||||
}
|
||||
|
||||
static std::shared_ptr<Image> Owner(const TextureCache& cache, ImageId id) {
|
||||
return cache.ResolveOwner(id);
|
||||
}
|
||||
@@ -771,6 +845,91 @@ void ValidateSpirv(const char* shader_name, const std::vector<u32>& spirv) {
|
||||
}
|
||||
}
|
||||
|
||||
size_t CountText(const std::string& text, const std::string& needle) {
|
||||
size_t count = 0;
|
||||
for (size_t offset = 0; (offset = text.find(needle, offset)) != std::string::npos;
|
||||
offset += needle.size()) {
|
||||
count++;
|
||||
}
|
||||
return count;
|
||||
}
|
||||
|
||||
void CheckRectListShaders() {
|
||||
constexpr const char* name = "RectListShaders";
|
||||
|
||||
auto program = std::make_shared<ShaderRecompiler::IR::Program>();
|
||||
program->info.inputs.push_back(
|
||||
{ShaderRecompiler::IR::StageInputKind::Parameter, 0, 4, "in_param_0"});
|
||||
program->info.inputs.push_back(
|
||||
{ShaderRecompiler::IR::StageInputKind::Parameter, 1, 4, "in_param_1"});
|
||||
|
||||
ShaderVertexInputInfo vertex {};
|
||||
vertex.param_export_mask = 1u;
|
||||
ShaderPixelInputInfo pixel {};
|
||||
pixel.input_num = 2;
|
||||
pixel.interpolator_settings[0] = 0x400u;
|
||||
pixel.interpolator_settings[1] = 0;
|
||||
pixel.stage.program = program;
|
||||
HW::PixelShaderInfo ps_regs {};
|
||||
const auto perspective_id = ShaderGetIdPS(ps_regs, pixel, false);
|
||||
pixel.ps_no_perspective = true;
|
||||
const auto no_perspective_id = ShaderGetIdPS(ps_regs, pixel, false);
|
||||
pixel.ps_no_perspective = false;
|
||||
Require(name, "pipeline identity", perspective_id != no_perspective_id,
|
||||
"pixel interpolation mode must participate in the shader and pipeline key");
|
||||
|
||||
const std::array<uint32_t, 2> active_inputs = {0, 1};
|
||||
Require(name, "duplicate mapping",
|
||||
ShaderPixelParameterLocation(pixel, active_inputs, 0) == 0 &&
|
||||
ShaderPixelParameterLocation(pixel, active_inputs, 1) == 1,
|
||||
"duplicate pixel mappings must receive distinct effective output locations");
|
||||
|
||||
const auto shaders = BuildRectListShaders(vertex, &pixel);
|
||||
Require(name, "SPIR-V version",
|
||||
shaders.control.size() > 1 && shaders.control[1] == 0x00010500u &&
|
||||
shaders.evaluation.size() > 1 && shaders.evaluation[1] == 0x00010500u,
|
||||
"shadPS4-compatible vector selection requires SPIR-V 1.5");
|
||||
ValidateSpirv(name, shaders.control);
|
||||
ValidateSpirv(name, shaders.evaluation);
|
||||
|
||||
spvtools::SpirvTools tools(SPV_ENV_VULKAN_1_2);
|
||||
std::string control_text;
|
||||
std::string evaluation_text;
|
||||
Require(name, "control disassembly", tools.Disassemble(shaders.control, &control_text),
|
||||
"failed to disassemble rectangle-list tessellation control shader");
|
||||
Require(name, "evaluation disassembly",
|
||||
tools.Disassemble(shaders.evaluation, &evaluation_text),
|
||||
"failed to disassemble rectangle-list tessellation evaluation shader");
|
||||
Require(name, "control execution mode",
|
||||
control_text.find("TessellationControl") != std::string::npos &&
|
||||
control_text.find("OutputVertices 4") != std::string::npos,
|
||||
"rectangle-list control shader must produce four control points");
|
||||
Require(name, "evaluation execution modes",
|
||||
evaluation_text.find("TessellationEvaluation") != std::string::npos &&
|
||||
evaluation_text.find("Quads") != std::string::npos &&
|
||||
evaluation_text.find("SpacingEqual") != std::string::npos &&
|
||||
evaluation_text.find("VertexOrderCw") != std::string::npos,
|
||||
"rectangle-list evaluation shader has the wrong patch modes");
|
||||
Require(name, "no geometry stage",
|
||||
control_text.find("Geometry") == std::string::npos &&
|
||||
evaluation_text.find("Geometry") == std::string::npos,
|
||||
"rectangle-list expansion must not use geometry shaders");
|
||||
Require(name, "flat broadcast",
|
||||
CountText(control_text, "OpVectorTimesScalar") == 6 &&
|
||||
CountText(control_text, "OpSelect %v4float") == 2,
|
||||
"flat parameters must use guest vertex zero instead of reconstructed values");
|
||||
Require(name, "remapped interface",
|
||||
CountText(control_text, " Location 0") == 2 &&
|
||||
CountText(control_text, " Location 1") == 1 &&
|
||||
CountText(evaluation_text, " Location 0") == 2 &&
|
||||
CountText(evaluation_text, " Location 1") == 2,
|
||||
"duplicate pixel mappings must share one vertex input and keep distinct patch outputs");
|
||||
|
||||
const auto position_only = BuildRectListShaders(vertex, nullptr);
|
||||
ValidateSpirv(name, position_only.control);
|
||||
ValidateSpirv(name, position_only.evaluation);
|
||||
}
|
||||
|
||||
void CheckSpirvText(const TestCase& test, const std::vector<u32>& spirv) {
|
||||
if (test.required_spirv.empty() && test.forbidden_spirv.empty()) {
|
||||
return;
|
||||
@@ -2402,6 +2561,110 @@ public:
|
||||
vk::Format::eR8G8B8A8Srgb,
|
||||
"registered compatible backing did not reuse one ImageId");
|
||||
|
||||
const auto IsOnlyImage = [](const std::vector<ImageId>& ids, ImageId expected) {
|
||||
return ids.size() == 1 && ids.front() == expected;
|
||||
};
|
||||
const auto exact_byte_miss =
|
||||
TextureCacheTestAccess::FindImages(texture_cache, base + 8, 1, false);
|
||||
const auto touched_page_hit =
|
||||
TextureCacheTestAccess::FindImages(texture_cache, base + 8, 1, true);
|
||||
const auto next_page_miss =
|
||||
TextureCacheTestAccess::FindImages(texture_cache, base + 0x1000, 1, true);
|
||||
Require(name, "exact and 4-KiB page filtering",
|
||||
exact_byte_miss.empty() && IsOnlyImage(touched_page_hit, first) &&
|
||||
next_page_miss.empty(),
|
||||
"coarse candidates did not preserve exact-byte and touched-page semantics");
|
||||
|
||||
const auto MakeOwnershipInfo = [](uint64_t address, uint64_t size) {
|
||||
ImageInfo info {};
|
||||
info.data = {address, size};
|
||||
info.extent = {1, 1, 1};
|
||||
info.resources = {1, 1};
|
||||
info.samples = 1;
|
||||
return info;
|
||||
};
|
||||
const auto spanning_info = MakeOwnershipInfo(base + 0x1ff000, 0x2000);
|
||||
const auto spanning = TextureCacheTestAccess::InsertImage(texture_cache, spanning_info);
|
||||
const auto spanning_results = TextureCacheTestAccess::FindImages(
|
||||
texture_cache, spanning_info.data.address, spanning_info.data.size, false);
|
||||
Require(name, "one-MiB cross-page deduplication",
|
||||
spanning && IsOnlyImage(spanning_results, spanning) &&
|
||||
TextureCacheTestAccess::PageOwnerCount(texture_cache,
|
||||
spanning_info.data.address) == 1 &&
|
||||
TextureCacheTestAccess::PageOwnerCount(
|
||||
texture_cache, spanning_info.data.End() - 1) == 1,
|
||||
"an image spanning two coarse pages was missing or returned more than once");
|
||||
TextureCacheTestAccess::DeleteImage(texture_cache, spanning);
|
||||
Require(name, "cross-page owner cleanup",
|
||||
TextureCacheTestAccess::PageOwnerCount(texture_cache,
|
||||
spanning_info.data.address) == 0 &&
|
||||
TextureCacheTestAccess::PageOwnerCount(
|
||||
texture_cache, spanning_info.data.End() - 1) == 0 &&
|
||||
TextureCacheTestAccess::FindImages(texture_cache,
|
||||
spanning_info.data.address,
|
||||
spanning_info.data.size, false)
|
||||
.empty(),
|
||||
"cross-page unregister left a stale coarse-page membership");
|
||||
|
||||
const auto shared_info = MakeOwnershipInfo(base + 0x500100, 0x100);
|
||||
const auto shared_first =
|
||||
TextureCacheTestAccess::InsertImage(texture_cache, shared_info);
|
||||
const auto shared_second =
|
||||
TextureCacheTestAccess::InsertImage(texture_cache, shared_info);
|
||||
const auto shared_results = TextureCacheTestAccess::FindImages(
|
||||
texture_cache, shared_info.data.address, shared_info.data.size, false);
|
||||
Require(name, "shared coarse-page registration",
|
||||
shared_results.size() == 2 &&
|
||||
std::find(shared_results.begin(), shared_results.end(), shared_first) !=
|
||||
shared_results.end() &&
|
||||
std::find(shared_results.begin(), shared_results.end(), shared_second) !=
|
||||
shared_results.end(),
|
||||
"two registered owners were not retained in one coarse page");
|
||||
TextureCacheTestAccess::DeleteImage(texture_cache, shared_first);
|
||||
const auto shared_survivor = TextureCacheTestAccess::FindImages(
|
||||
texture_cache, shared_info.data.address, shared_info.data.size, false);
|
||||
Require(name, "shared coarse-page unregister",
|
||||
IsOnlyImage(shared_survivor, shared_second) &&
|
||||
TextureCacheTestAccess::PageOwnerCount(texture_cache,
|
||||
shared_info.data.address) == 1,
|
||||
"unregistering one owner removed its coarse-page neighbor");
|
||||
TextureCacheTestAccess::DeleteImage(texture_cache, shared_second);
|
||||
Require(name, "final shared coarse-page unregister",
|
||||
TextureCacheTestAccess::PageOwnerCount(texture_cache,
|
||||
shared_info.data.address) == 0,
|
||||
"the final shared owner remained registered");
|
||||
|
||||
constexpr uint64_t large_owner_size = 64ull * 1024 * 1024;
|
||||
const auto large_info = MakeOwnershipInfo(base + 0x800000, large_owner_size);
|
||||
const auto large_owner =
|
||||
TextureCacheTestAccess::InsertImage(texture_cache, large_info);
|
||||
Require(name, "production one-MiB registration granularity",
|
||||
TextureCacheTestAccess::OwnedPageCount(
|
||||
texture_cache, large_info.data.address, large_info.data.size, large_owner) == 64,
|
||||
"64 MiB image registration did not create exactly 64 coarse memberships");
|
||||
TextureCacheTestAccess::DeleteImage(texture_cache, large_owner);
|
||||
Require(name, "production one-MiB unregister granularity",
|
||||
TextureCacheTestAccess::OwnedPageCount(
|
||||
texture_cache, large_info.data.address, large_info.data.size, large_owner) == 0,
|
||||
"large image unregister left coarse memberships behind");
|
||||
|
||||
const ImageId stale {first.index, first.generation + 1};
|
||||
TextureCacheTestAccess::AddPageOwner(texture_cache, base, stale);
|
||||
const auto stale_filtered =
|
||||
TextureCacheTestAccess::FindImages(texture_cache, base, sizeof(initial), false);
|
||||
Require(name, "stale page owner filtering",
|
||||
IsOnlyImage(stale_filtered, first) &&
|
||||
TextureCacheTestAccess::RemovePageOwner(texture_cache, base, stale),
|
||||
"a stale generation escaped the direct page-owner lookup");
|
||||
|
||||
TextureCacheTestAccess::SetQueryEpoch(texture_cache, UINT32_MAX);
|
||||
const auto wrap_results =
|
||||
TextureCacheTestAccess::FindImages(texture_cache, base, sizeof(initial), false);
|
||||
Require(name, "page-owner query epoch wrap",
|
||||
IsOnlyImage(wrap_results, first) &&
|
||||
TextureCacheTestAccess::QueryEpoch(texture_cache) == 1,
|
||||
"query deduplication failed while wrapping its epoch");
|
||||
|
||||
auto& image = texture_cache.GetImage(first);
|
||||
constexpr uint32_t final_sampled_value = 0x88776655u;
|
||||
Require(name, "sampled write between discovery and acquisition",
|
||||
@@ -7279,26 +7542,30 @@ public:
|
||||
}
|
||||
|
||||
u32 case_index = 0;
|
||||
auto check_round_trip = [&](const char* stage, uint64_t size,
|
||||
auto check_round_trip = [&](const char* stage, uint64_t tiled_size,
|
||||
std::span<const GpuTileInfo> infos) {
|
||||
std::vector<uint8_t> tiled(size);
|
||||
std::vector<uint8_t> cpu(size, 0);
|
||||
std::vector<uint8_t> gpu(size, 0xab);
|
||||
uint64_t linear_size = 0;
|
||||
for (const auto& info: infos) {
|
||||
linear_size = std::max(linear_size, info.linear_offset + info.linear_size);
|
||||
}
|
||||
std::vector<uint8_t> tiled(tiled_size);
|
||||
std::vector<uint8_t> cpu(linear_size, 0);
|
||||
std::vector<uint8_t> gpu(linear_size, 0xab);
|
||||
fill(&tiled, ++case_index);
|
||||
for (const auto& info: infos) {
|
||||
convert_reference(false, &cpu, tiled, info);
|
||||
}
|
||||
gpu_detile(tiled, &gpu, size, size, infos);
|
||||
gpu_detile(tiled, &gpu, tiled_size, linear_size, infos);
|
||||
compare((std::string(stage) + " detile bytes").c_str(), cpu, gpu);
|
||||
|
||||
std::vector<uint8_t> linear(size);
|
||||
std::vector<uint8_t> cpu_tiled(size, 0xab);
|
||||
std::vector<uint8_t> gpu_tiled(size, 0xab);
|
||||
std::vector<uint8_t> linear(linear_size);
|
||||
std::vector<uint8_t> cpu_tiled(tiled_size, 0xab);
|
||||
std::vector<uint8_t> gpu_tiled(tiled_size, 0xab);
|
||||
fill(&linear, 0x280u + case_index);
|
||||
for (const auto& info: infos) {
|
||||
convert_reference(true, &cpu_tiled, linear, info);
|
||||
}
|
||||
gpu_tile(linear, &gpu_tiled, size, size, infos);
|
||||
gpu_tile(linear, &gpu_tiled, tiled_size, linear_size, infos);
|
||||
compare((std::string(stage) + " tile bytes").c_str(), cpu_tiled, gpu_tiled);
|
||||
};
|
||||
for (const auto family: families) {
|
||||
@@ -7436,6 +7703,32 @@ public:
|
||||
Require(name, "format coverage", format_cases != 0,
|
||||
"no CPU-supported standard formats were tested");
|
||||
|
||||
{
|
||||
constexpr u32 format = Prospero::GpuEnumValue(Prospero::BufferFormat::kBc1UNorm);
|
||||
constexpr u32 tile = Prospero::GpuEnumValue(Prospero::TileMode::kStandard64KB);
|
||||
constexpr u32 width = 256, height = 256, levels = 9;
|
||||
const u32 pitch = TileGetTexturePitch(format, width, levels, tile);
|
||||
TileSizeAlign total {};
|
||||
TileGetTextureSize(format, width, height, pitch, levels, tile, &total, nullptr,
|
||||
nullptr);
|
||||
const auto layout = TextureCalcUploadLayout(format, width, height, levels, 1, pitch,
|
||||
tile, total.size, false, false, name);
|
||||
const auto regions =
|
||||
TextureBuildImageCopies(layout, width, height, 1, levels, false, false);
|
||||
std::vector<GpuTileInfo> infos;
|
||||
const bool built =
|
||||
TextureBuildGpuTileInfos(total.size, regions, layout, format, 1, levels, infos);
|
||||
uint64_t linear_size = 0;
|
||||
for (const auto& info: infos) {
|
||||
linear_size = std::max(linear_size, info.linear_offset + info.linear_size);
|
||||
}
|
||||
Require(name, "BC1 mip-tail capacities",
|
||||
built && total.size == 0x10000 && layout.first_tail_level == 0 &&
|
||||
linear_size == 0x15560 && linear_size > total.size,
|
||||
"BC1 mip tail conflated tiled and linear capacities");
|
||||
check_round_trip("BC1 mip tail", total.size, infos);
|
||||
}
|
||||
|
||||
{
|
||||
constexpr u32 format = Prospero::GpuEnumValue(Prospero::BufferFormat::k32Float);
|
||||
constexpr u32 tile = Prospero::GpuEnumValue(Prospero::TileMode::kRenderTarget);
|
||||
@@ -15959,11 +16252,6 @@ ShaderTextureResource AtomicStorageTextureDescriptor() {
|
||||
resource = BasicArrayStorageTextureResource();
|
||||
descriptor = BasicArrayStorageTextureDescriptor();
|
||||
descriptor.fields[4] |= 1u << 16u;
|
||||
} else if (std::strcmp(kind, "array-mip-view") == 0) {
|
||||
resource = BasicArrayStorageTextureResource();
|
||||
descriptor = BasicArrayStorageTextureDescriptor();
|
||||
descriptor.fields[3] |= (1u << 12u) | (1u << 16u);
|
||||
descriptor.fields[5] |= 1u << 4u;
|
||||
} else if (std::strcmp(kind, "reserved") == 0) {
|
||||
descriptor.fields[1] |= 1u << 29u;
|
||||
} else if (std::strcmp(kind, "uint-format") == 0) {
|
||||
@@ -16139,6 +16427,16 @@ void CheckBasicStorageTextureDescriptor() {
|
||||
array.DstSelXYZW() == DstSel(6, 5, 4, 7),
|
||||
"PPSA21268 2D-array storage descriptor fixture is malformed");
|
||||
ValidateStorageTexture(BasicArrayStorageTextureResource(), array, 0x10000);
|
||||
const ShaderTextureResource mip_array {{0x20268d00u, 0xc4700000u, 0x001fc01fu,
|
||||
0xd1b11facu, 0x00000000u, 0x00700070u,
|
||||
0x00000000u, 0x00000000u}};
|
||||
Require("BasicStorageTexture", "PPSA14457 mip-one 2D-array descriptor",
|
||||
mip_array.BaseLevel() == 1 && mip_array.LastLevel() == 1 &&
|
||||
mip_array.MaxMip() == 7 &&
|
||||
mip_array.Type() == Prospero::GpuEnumValue(Prospero::ImageType::kColor2DArray) &&
|
||||
mip_array.Depth() == 0 && mip_array.BaseArray5() == 0,
|
||||
"PPSA14457 mip-one 2D-array storage descriptor fixture is malformed");
|
||||
ValidateStorageTexture(BasicArrayStorageTextureResource(), mip_array, 0x30000);
|
||||
|
||||
const auto uint_array = BasicUintArrayStorageTextureDescriptor();
|
||||
Require("BasicStorageTexture", "uint 2D-array descriptor",
|
||||
@@ -16299,7 +16597,6 @@ void CheckBasicStorageTextureDescriptor() {
|
||||
"yzwx-read",
|
||||
"reserved-swizzle",
|
||||
"array-base-out-of-range",
|
||||
"array-mip-view",
|
||||
"reserved",
|
||||
"uint-format",
|
||||
"uint-resource-float-format",
|
||||
@@ -17468,6 +17765,7 @@ int main(int argc, char** argv) {
|
||||
CheckPm4CeCompletion(vulkan.RuntimeRenderer());
|
||||
CheckEmbeddedFetchVertexOffset();
|
||||
CheckEmbeddedFetchLaneSpill();
|
||||
CheckRectListShaders();
|
||||
CheckPs5GameExampleImageClearRuntimeShape();
|
||||
vulkan.CheckSchedulerTimeline();
|
||||
vulkan.CheckGpuMappedRangeLifecycle();
|
||||
|
||||
Reference in New Issue
Block a user