Compare commits

..

2 Commits

Author SHA1 Message Date
nmzik 83152c8cad guest_gpu: remove memory-unmap submission deadlock + remove legacy agc buffering 2026-08-02 13:17:53 +02:00
nmzik 35a89d616a implement dynamic 2026-08-02 10:50:28 +02:00
97 changed files with 1727 additions and 3926 deletions
+9 -38
View File
@@ -6,27 +6,9 @@ on:
branches: [ main, master ]
pull_request:
env:
KYTY_BUILD_ORIGIN: ${{ github.repository != 'KytyPS5/KytyPS5' && 'Fork' || github.event_name == 'push' && 'Official' || 'Source' }}
KYTY_BUILD_REPOSITORY: ${{ github.repository }}
jobs:
metadata:
name: Prepare build metadata
runs-on: ubuntu-latest
outputs:
release_tag: ${{ steps.release_tag.outputs.value }}
steps:
- name: Generate release tag
id: release_tag
shell: bash
run: |
echo "value=KytyPS5-$(date -u +'%Y-%m-%d')-${GITHUB_SHA::7}" >> "$GITHUB_OUTPUT"
windows:
name: Build KytyPS5 (Windows)
needs: metadata
runs-on: windows-2022
steps:
@@ -91,13 +73,9 @@ jobs:
- name: Configure
shell: cmd
run: |
cmake -S . -B _Build/windows ^
cmake -S src -B _Build/windows ^
-G Ninja ^
-DCMAKE_BUILD_TYPE=Release ^
-DCMAKE_INTERPROCEDURAL_OPTIMIZATION=ON ^
-DKYTY_BUILD_ORIGIN=%KYTY_BUILD_ORIGIN% ^
-DKYTY_BUILD_REPOSITORY=%KYTY_BUILD_REPOSITORY% ^
-DKYTY_RELEASE_TAG=${{ needs.metadata.outputs.release_tag }} ^
-DCMAKE_C_COMPILER=clang-cl ^
-DCMAKE_CXX_COMPILER=clang-cl ^
-DCMAKE_PREFIX_PATH="%Qt6_DIR%"
@@ -126,7 +104,6 @@ jobs:
macos:
name: Build KytyPS5 (macOS)
needs: metadata
runs-on: macos-15
steps:
@@ -170,13 +147,9 @@ jobs:
- name: Configure
shell: bash
run: |
cmake -S . -B _Build/macos \
cmake -S src -B _Build/macos \
-G Ninja \
-DCMAKE_BUILD_TYPE=Release \
-DCMAKE_INTERPROCEDURAL_OPTIMIZATION=ON \
-DKYTY_BUILD_ORIGIN="$KYTY_BUILD_ORIGIN" \
-DKYTY_BUILD_REPOSITORY="$KYTY_BUILD_REPOSITORY" \
-DKYTY_RELEASE_TAG="${{ needs.metadata.outputs.release_tag }}" \
-DCMAKE_OSX_ARCHITECTURES=x86_64 \
-DCMAKE_C_COMPILER=clang \
-DCMAKE_CXX_COMPILER=clang++ \
@@ -240,7 +213,6 @@ jobs:
linux:
name: Build KytyPS5 (Linux)
needs: metadata
runs-on: ubuntu-24.04
steps:
@@ -298,13 +270,9 @@ jobs:
shell: bash
run: |
mkdir -p _Build
cmake -S . -B _Build/linux \
cmake -S src -B _Build/linux \
-G Ninja \
-DCMAKE_BUILD_TYPE=Release \
-DCMAKE_INTERPROCEDURAL_OPTIMIZATION=ON \
-DKYTY_BUILD_ORIGIN="$KYTY_BUILD_ORIGIN" \
-DKYTY_BUILD_REPOSITORY="$KYTY_BUILD_REPOSITORY" \
-DKYTY_RELEASE_TAG="${{ needs.metadata.outputs.release_tag }}" \
-DCMAKE_C_COMPILER=clang \
-DCMAKE_CXX_COMPILER=clang++ \
-DCMAKE_PREFIX_PATH="$Qt6_DIR" 2>&1 | tee _Build/configure.log
@@ -374,12 +342,10 @@ jobs:
release:
name: Release KytyPS5
if: github.event_name == 'push' && github.repository == 'KytyPS5/KytyPS5'
needs: [metadata, windows, macos, linux]
needs: [windows, macos, linux]
runs-on: ubuntu-latest
permissions:
contents: write
env:
RELEASE_NAME: ${{ needs.metadata.outputs.release_tag }}
steps:
- name: Download builds
@@ -387,6 +353,11 @@ jobs:
with:
path: artifacts
- name: Set release name
shell: bash
run: |
echo "RELEASE_NAME=KytyPS5-$(date -u +'%Y-%m-%d')-${GITHUB_SHA::7}" >> "$GITHUB_ENV"
- name: Package builds
shell: bash
run: |
+1 -2
View File
@@ -3,5 +3,4 @@
.idea/
build/
_Build/vscode-clang/
_Build/
cmake-build-debug/
_Build/
+1 -1
View File
@@ -1,5 +1,5 @@
{
"cmake.sourceDirectory": "${workspaceFolder}",
"cmake.sourceDirectory": "${workspaceFolder}/src",
"cmake.buildDirectory": "${workspaceFolder}/_Build/vscode-clang",
"cmake.generator": "Ninja",
"cmake.environment": {
+1 -1
+4 -4
View File
@@ -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 . -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 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"
```
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 . -B _Build/linux -G Ninja -DCMAKE_BUILD_TYPE=Release \
cmake -S src -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.
The CMake source root is the repository root.
Note that the CMake source root is `src`, not the repository root.
### Building on macOS
@@ -207,7 +207,7 @@ Requirements:
```bash
git submodule update --init --recursive
cmake -S . -B _Build/macos -G Ninja -DCMAKE_BUILD_TYPE=Release \
cmake -S src -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"
+115 -159
View File
@@ -4,18 +4,8 @@ if(CMAKE_VERSION VERSION_GREATER_EQUAL 4.0)
set(CMAKE_POLICY_VERSION_MINIMUM 3.5 CACHE STRING "Minimum policy version for bundled dependencies")
endif()
get_property(KYTY_GENERATOR_IS_MULTI_CONFIG GLOBAL PROPERTY GENERATOR_IS_MULTI_CONFIG)
if(NOT KYTY_GENERATOR_IS_MULTI_CONFIG AND NOT CMAKE_BUILD_TYPE)
set(CMAKE_BUILD_TYPE Release CACHE STRING "Choose the type of build" FORCE)
set_property(CACHE CMAKE_BUILD_TYPE PROPERTY STRINGS Debug Release RelWithDebInfo MinSizeRel)
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()
@@ -29,33 +19,12 @@ set(CMAKE_CXX_STANDARD_REQUIRED ON)
set(CMAKE_CXX_EXTENSIONS OFF)
set(CMAKE_CXX_SCAN_FOR_MODULES OFF)
include("${KYTY_SOURCE_DIR}/utils.cmake")
include(utils.cmake)
include(CTest)
option(KYTY_ENABLE_CLANG_TIDY "Run clang-tidy checks during builds" OFF)
set(KYTY_BUILD_ORIGIN "Source" CACHE STRING "Build provenance shown in diagnostics")
set_property(CACHE KYTY_BUILD_ORIGIN PROPERTY STRINGS Source Official Fork)
if(NOT KYTY_BUILD_ORIGIN STREQUAL "Source" AND NOT KYTY_BUILD_ORIGIN STREQUAL "Official" AND
NOT KYTY_BUILD_ORIGIN STREQUAL "Fork")
message(FATAL_ERROR "KYTY_BUILD_ORIGIN must be Source, Official, or Fork")
endif()
set(KYTY_RELEASE_TAG "" CACHE STRING "Release tag embedded in official builds")
set(KYTY_BUILD_REPOSITORY "" CACHE STRING "GitHub repository embedded in fork builds")
set(KYTY_OFFICIAL_BUILD OFF)
set(KYTY_FORK_BUILD OFF)
if(KYTY_BUILD_ORIGIN STREQUAL "Official")
if(KYTY_RELEASE_TAG STREQUAL "")
message(FATAL_ERROR "Official builds require KYTY_RELEASE_TAG")
endif()
set(KYTY_OFFICIAL_BUILD ON)
elseif(KYTY_BUILD_ORIGIN STREQUAL "Fork")
if(KYTY_BUILD_REPOSITORY STREQUAL "")
message(FATAL_ERROR "Fork builds require KYTY_BUILD_REPOSITORY")
endif()
set(KYTY_FORK_BUILD ON)
endif()
set(KYTY_THIRD_PARTY_DIR "${CMAKE_CURRENT_SOURCE_DIR}/../3rdparty")
set(CMAKE_WINDOWS_EXPORT_ALL_SYMBOLS ON)
@@ -128,40 +97,36 @@ endif()
project(Kyty${KYTY_PROJECT_NAME}${CMAKE_BUILD_TYPE}${KYTY_COMPILER} VERSION 0.2.2)
include_directories(
include_directories(
${KYTY_THIRD_PARTY_DIR}/gtest/include
${KYTY_THIRD_PARTY_DIR}/gtest
${KYTY_THIRD_PARTY_DIR}/fmt/include
${KYTY_THIRD_PARTY_DIR}/magic_enum/include/magic_enum
${PROJECT_BINARY_DIR}
${KYTY_SOURCE_DIR}
${CMAKE_CURRENT_SOURCE_DIR}
)
set(KYTY_VERSION "${PROJECT_VERSION}")
configure_file(
${KYTY_SOURCE_DIR}/cmake_config.h.in
${PROJECT_SOURCE_DIR}/cmake_config.h.in
${PROJECT_BINARY_DIR}/cmake_config.h
)
find_package(Git)
add_custom_target(KytyGitVersion
COMMAND ${CMAKE_COMMAND}
-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 ${KYTY_SOURCE_DIR}/generate_version.cmake
BYPRODUCTS ${PROJECT_BINARY_DIR}/kytyGitVersion.h
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 GIT_EXECUTABLE=${GIT_EXECUTABLE}
-D GIT_WORKING_DIRECTORY=${CMAKE_CURRENT_SOURCE_DIR}
-P ${CMAKE_CURRENT_SOURCE_DIR}/generate_version.cmake
COMMENT "Generate kytyGitVersion.h"
)
add_library(kyty_git_version INTERFACE)
add_dependencies(kyty_git_version KytyGitVersion)
if (CMAKE_CXX_COMPILER_VERSION VERSION_GREATER_EQUAL 12.0.0)
list(APPEND KYTY_IWYU
list(APPEND KYTY_IWYU
kyty_emulator
common
#launcher
@@ -174,55 +139,55 @@ if (CMAKE_CXX_COMPILER_VERSION VERSION_GREATER_EQUAL 12.0.0)
endif()
option(KYTY_BUILD_LAUNCHER "Build Qt launcher" ON)
config_compiler_and_linker()
config_compiler_and_linker()
add_subdirectory("${KYTY_THIRD_PARTY_DIR}" "${CMAKE_CURRENT_BINARY_DIR}/3rdparty")
add_subdirectory("${KYTY_SOURCE_DIR}/common" "${CMAKE_CURRENT_BINARY_DIR}/common")
add_subdirectory(common)
file(GLOB kyty_emulator_src CONFIGURE_DEPENDS
"${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"
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
)
find_program(KYTY_GLSLANG_VALIDATOR glslangValidator REQUIRED)
set(gpu_tiler_shader_dir "${KYTY_SOURCE_DIR}/graphics/host_gpu/shaders")
set(gpu_tiler_shader_dir "${CMAKE_CURRENT_SOURCE_DIR}/graphics/host_gpu/shaders")
set(gpu_tiler_generated_dir "${PROJECT_BINARY_DIR}/gpu_tiler_shaders")
set(gpu_tiler_shader_names
standard256
@@ -250,7 +215,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 "${KYTY_SOURCE_DIR}/embed_spirv.cmake"
-DSYMBOL=${shader_symbol} -P "${CMAKE_CURRENT_SOURCE_DIR}/embed_spirv.cmake"
DEPENDS "${shader_source}" ${gpu_tiler_shader_includes}
VERBATIM
)
@@ -274,7 +239,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 "${KYTY_SOURCE_DIR}/embed_spirv.cmake"
-DSYMBOL=${shader_symbol} -P "${CMAKE_CURRENT_SOURCE_DIR}/embed_spirv.cmake"
DEPENDS "${shader_source}"
VERBATIM
)
@@ -283,8 +248,8 @@ endforeach()
list(APPEND kyty_emulator_src ${gpu_blit_shader_headers})
list(APPEND kyty_emulator_src
"${KYTY_SOURCE_DIR}/emulator.h"
"${KYTY_SOURCE_DIR}/emulator.cpp"
emulator.h
emulator.cpp
)
list(REMOVE_DUPLICATES kyty_emulator_src)
@@ -292,7 +257,7 @@ if (KYTY_CLANG_CL)
set_source_files_properties(${kyty_emulator_src} PROPERTIES COMPILE_FLAGS "-Wno-pragma-pack -Wno-deprecated-declarations -D_TIMESPEC_DEFINED")
endif()
set(kyty_emulator_link_libraries common Vulkan::Headers spirv-tools-opt spirv-tools SDL2-static xxhash FFmpeg::ffmpeg fmt::fmt nlohmann_json::nlohmann_json LibAtrac9 kyty_git_version)
set(kyty_emulator_link_libraries common Vulkan::Headers spirv-tools-opt spirv-tools SDL2-static xxhash FFmpeg::ffmpeg fmt::fmt nlohmann_json::nlohmann_json LibAtrac9)
# Linux system libraries required by the static FFmpeg archive.
if(LINUX)
@@ -315,7 +280,7 @@ if(LINUX)
endif()
set(inc_headers
${KYTY_SOURCE_DIR}
${CMAKE_CURRENT_SOURCE_DIR}
${KYTY_THIRD_PARTY_DIR}/SDL2/include
${KYTY_THIRD_PARTY_DIR}/VulkanMemoryAllocator/include
${KYTY_THIRD_PARTY_DIR}/SPIRV-Tools/include
@@ -327,13 +292,13 @@ set(inc_headers
if (KYTY_CLANG_CL)
list(APPEND kyty_emulator_link_libraries winpthread)
list(APPEND inc_headers
list(APPEND inc_headers
${KYTY_THIRD_PARTY_DIR}/winpthread/include
)
endif()
list(APPEND check_headers
${KYTY_SOURCE_DIR}
${CMAKE_CURRENT_SOURCE_DIR}
)
function(add_kyty_full_emulator_test target source)
@@ -355,115 +320,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 "${KYTY_SOURCE_DIR}/kernel/macosGuestAddressSpace.cpp")
target_sources(${target} PRIVATE kernel/macosGuestAddressSpace.cpp)
target_compile_definitions(${target} PRIVATE KYTY_LINKED_GUEST_ADDRESS_SPACE=1)
target_link_options(${target} PRIVATE
-Wl,-ld_classic,-no_pie,-no_fixup_chains,-no_huge,-pagezero_size,0x40000,-segaddr,SYSTEM_MANAGED,0x40000,-segaddr,SYSTEM_RESERVED,0x7ffffc000,-segaddr,USER_AREA,0x7000000000,-image_base,0x700000000000)
endif()
endfunction()
add_kyty_full_emulator_test(shader_cfg_tests "${KYTY_TESTS_DIR}/shaderCfgTests.cpp")
add_kyty_full_emulator_test(shader_cfg_tests ../tests/shaderCfgTests.cpp)
add_executable(scalar_provenance_tests EXCLUDE_FROM_ALL
"${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"
../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
)
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
"${KYTY_TESTS_DIR}/PageManagerTests.cpp"
"${KYTY_SOURCE_DIR}/graphics/host_gpu/pageManager.cpp"
../tests/PageManagerTests.cpp
graphics/host_gpu/pageManager.cpp
)
target_include_directories(page_manager_tests PRIVATE ${inc_headers})
add_executable(bit_array_tests EXCLUDE_FROM_ALL
"${KYTY_TESTS_DIR}/BitArrayTests.cpp"
../tests/BitArrayTests.cpp
)
target_include_directories(bit_array_tests PRIVATE ${inc_headers})
add_executable(memory_tracker_tests EXCLUDE_FROM_ALL
"${KYTY_TESTS_DIR}/MemoryTrackerTests.cpp"
"${KYTY_SOURCE_DIR}/graphics/host_gpu/pageManager.cpp"
"${KYTY_SOURCE_DIR}/graphics/host_gpu/memoryTracker.cpp"
../tests/MemoryTrackerTests.cpp
graphics/host_gpu/pageManager.cpp
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
"${KYTY_TESTS_DIR}/ShaderVertexMetadataTests.cpp"
"${KYTY_SOURCE_DIR}/graphics/host_gpu/hostMemory.cpp"
"${KYTY_SOURCE_DIR}/graphics/shader/shaderVertexMetadata.cpp"
../tests/ShaderVertexMetadataTests.cpp
graphics/host_gpu/hostMemory.cpp
graphics/shader/shaderVertexMetadata.cpp
)
target_include_directories(shader_vertex_metadata_tests PRIVATE ${inc_headers})
add_executable(shader_stage_runtime_tests EXCLUDE_FROM_ALL
"${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"
../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
)
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
"${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"
../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
)
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
"${KYTY_TESTS_DIR}/ResourceMutexTests.cpp"
"${KYTY_SOURCE_DIR}/graphics/host_gpu/renderer/cache/resourceMutex.cpp"
../tests/ResourceMutexTests.cpp
graphics/host_gpu/renderer/cache/resourceMutex.cpp
)
target_link_libraries(resource_mutex_tests common)
target_include_directories(resource_mutex_tests PRIVATE ${inc_headers})
add_executable(audio_out2_port_tests EXCLUDE_FROM_ALL
"${KYTY_TESTS_DIR}/AudioOut2PortTests.cpp"
"${KYTY_SOURCE_DIR}/libs/libAudio2.cpp"
"${KYTY_SOURCE_DIR}/loader/timer.cpp"
../tests/AudioOut2PortTests.cpp
libs/libAudio2.cpp
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
"${KYTY_TESTS_DIR}/EventQueueLifetimeTests.cpp"
"${KYTY_SOURCE_DIR}/kernel/eventQueue.cpp"
"${KYTY_SOURCE_DIR}/loader/timer.cpp"
../tests/EventQueueLifetimeTests.cpp
kernel/eventQueue.cpp
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(sync_on_address_tests EXCLUDE_FROM_ALL
"${KYTY_TESTS_DIR}/SyncOnAddressTests.cpp"
"${KYTY_SOURCE_DIR}/kernel/syncOnAddress.cpp"
)
target_link_libraries(sync_on_address_tests common)
target_include_directories(sync_on_address_tests PRIVATE ${inc_headers})
add_executable(image_page_table_tests EXCLUDE_FROM_ALL
"${KYTY_TESTS_DIR}/ImagePageTableTests.cpp"
../tests/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 "${KYTY_TESTS_DIR}/ShaderRecompilerComputeTests.cpp")
add_kyty_full_emulator_test(shader_recompiler_compute_tests ../tests/ShaderRecompilerComputeTests.cpp)
set(gpu_test_generated_dir "${PROJECT_BINARY_DIR}/gpu_test_shaders")
set(gpu_test_ms_depth_source
@@ -479,14 +437,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 "${KYTY_SOURCE_DIR}/embed_spirv.cmake"
-P "${CMAKE_CURRENT_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 "${KYTY_TESTS_DIR}/VirtualMemoryAllocationTests.cpp")
add_kyty_full_emulator_test(virtual_memory_allocation_tests ../tests/VirtualMemoryAllocationTests.cpp)
target_compile_definitions(virtual_memory_allocation_tests PRIVATE
KYTY_VIRTUAL_MEMORY_ALLOCATION_TESTS=1)
@@ -510,7 +468,6 @@ if(BUILD_TESTING)
add_test(NAME resource_tracking COMMAND $<TARGET_FILE:resource_tracking_tests>)
add_test(NAME resource_mutex COMMAND $<TARGET_FILE:resource_mutex_tests>)
add_test(NAME event_queue_lifetime COMMAND $<TARGET_FILE:event_queue_lifetime_tests>)
add_test(NAME sync_on_address COMMAND $<TARGET_FILE:sync_on_address_tests>)
add_test(NAME audio_out2_port COMMAND $<TARGET_FILE:audio_out2_port_tests>)
add_test(NAME shader_recompiler_compute COMMAND $<TARGET_FILE:shader_recompiler_compute_tests>)
add_test(NAME virtual_memory_allocation
@@ -521,8 +478,6 @@ if(BUILD_TESTING)
COMMAND $<TARGET_FILE:shader_recompiler_compute_tests> --stream-buffer-only)
add_test(NAME gpu_command_lane
COMMAND $<TARGET_FILE:shader_recompiler_compute_tests> --gpu-command-lane-only)
add_test(NAME pm4_context_state
COMMAND $<TARGET_FILE:shader_recompiler_compute_tests> --context-state-only)
add_test(NAME gpu_tiler
COMMAND $<TARGET_FILE:shader_recompiler_compute_tests> --gpu-tiler-only)
@@ -559,14 +514,13 @@ if(BUILD_TESTING)
resource_tracking_tests
resource_mutex_tests
event_queue_lifetime_tests
sync_on_address_tests
shader_recompiler_compute_tests
virtual_memory_allocation_tests
)
endif()
add_executable(kyty_emulator "${KYTY_SOURCE_DIR}/main.cpp" ${kyty_emulator_src})
add_executable(kyty_emulator main.cpp ${kyty_emulator_src})
configure_macos_guest_address_space(kyty_emulator)
target_link_libraries(kyty_emulator ${kyty_emulator_link_libraries})
@@ -607,6 +561,8 @@ elseif(WIN32 OR LINUX)
set_target_properties(kyty_emulator PROPERTIES LINK_FLAGS "${KYTY_LD_OPTIONS} -Wl,-Map=${KYTY_EMULATOR_MAP_LINK_PATH}")
endif()
add_dependencies(kyty_emulator KytyGitVersion)
if(APPLE)
# The emulator writes x86-64 trampolines/PLT stubs into memory and executes them, and
# runs guest code under Rosetta. That requires the JIT / unsigned-executable-memory
@@ -614,14 +570,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 "${KYTY_SOURCE_DIR}/macos_jit.entitlements"
--entitlements "${CMAKE_CURRENT_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("${KYTY_SOURCE_DIR}/launcher" "${CMAKE_CURRENT_BINARY_DIR}/launcher")
add_subdirectory(launcher)
endif()
if(KYTY_CLANG_CL)
install(FILES "${KYTY_THIRD_PARTY_DIR}/winpthread/bin/libwinpthread-1.dll" DESTINATION .)
-4
View File
@@ -1,8 +1,4 @@
#define KYTY_VERSION "@KYTY_VERSION@"
#define KYTY_BUILD_REPOSITORY "@KYTY_BUILD_REPOSITORY@"
#define KYTY_RELEASE_TAG "@KYTY_RELEASE_TAG@"
#cmakedefine KYTY_OFFICIAL_BUILD
#cmakedefine KYTY_FORK_BUILD
#define KYTY_ENDIAN @KYTY_ENDIAN@
#define KYTY_PLATFORM @KYTY_PLATFORM@
#define KYTY_COMPILER KYTY_COMPILER_@KYTY_COMPILER@
+1 -1
View File
@@ -13,7 +13,7 @@ endif()
add_library(common STATIC ${common_src} ${common_headers})
target_include_directories(common
PUBLIC ${KYTY_SOURCE_DIR}
PUBLIC ${PROJECT_SOURCE_DIR}
PRIVATE
${KYTY_THIRD_PARTY_DIR}/cpuinfo/include
)
+4 -12
View File
@@ -33,18 +33,10 @@ uint32_t GetScreenHeight() {
return g_config->screen_height;
}
bool FullscreenEnabled() {
return g_config->fullscreen_enabled;
}
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;
}
@@ -97,12 +89,12 @@ bool RenderDocEnabled() {
return g_config->renderdoc_enabled;
}
bool NggRectlistDrawEnabled() {
return g_config->ngg_rectlist_draw_enabled;
}
bool ReadbackLinearImagesEnabled() {
return g_config->readback_linear_images;
}
const Keymap& GetKeymap() {
return g_config->keymap;
}
} // namespace Config
+2 -14
View File
@@ -5,8 +5,6 @@
#include "common/subsystems.h"
#include <filesystem>
#include <string>
#include <vector>
namespace Config {
@@ -20,17 +18,10 @@ enum class ProfilerDirection { None, Network };
enum class OutputDirection { Silent, Console, File };
using Keymap = std::vector<std::string>;
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;
bool fullscreen_enabled = false;
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;
@@ -44,17 +35,15 @@ 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;
Keymap keymap;
};
void Load(const ConfigOptions& cfg);
uint32_t GetScreenWidth();
uint32_t GetScreenHeight();
bool FullscreenEnabled();
uint32_t GetVblankFrequency();
uint32_t GetConsoleLanguage();
bool VulkanValidationEnabled();
bool ShaderValidationEnabled();
@@ -75,10 +64,9 @@ ProfilerDirection GetProfilerDirection();
bool SpirvDebugPrintfEnabled();
bool RenderDocEnabled();
bool NggRectlistDrawEnabled();
bool ReadbackLinearImagesEnabled();
const Keymap& GetKeymap();
} // namespace Config
#endif /* KYTY_COMMON_EMULATOR_CONFIG_H_ */
+1 -24
View File
@@ -1,8 +1,7 @@
set(KYTY_GIT_VERSION "unknown")
set(KYTY_GIT_HASH "unknown")
if(GIT_EXECUTABLE)
execute_process(
COMMAND "${GIT_EXECUTABLE}" describe --tags --always --dirty
COMMAND "${GIT_EXECUTABLE}" describe --tags --always
WORKING_DIRECTORY "${GIT_WORKING_DIRECTORY}"
OUTPUT_VARIABLE KYTY_GIT_VERSION
OUTPUT_STRIP_TRAILING_WHITESPACE
@@ -12,27 +11,5 @@ if(GIT_EXECUTABLE)
if(NOT GIT_RESULT EQUAL 0)
set(KYTY_GIT_VERSION "unknown")
endif()
execute_process(
COMMAND "${GIT_EXECUTABLE}" rev-parse --short=7 HEAD
WORKING_DIRECTORY "${GIT_WORKING_DIRECTORY}"
OUTPUT_VARIABLE KYTY_GIT_HASH
OUTPUT_STRIP_TRAILING_WHITESPACE
RESULT_VARIABLE GIT_HASH_RESULT
ERROR_QUIET
)
if(NOT GIT_HASH_RESULT EQUAL 0)
set(KYTY_GIT_HASH "unknown")
else()
execute_process(
COMMAND "${GIT_EXECUTABLE}" diff-index --quiet HEAD --
WORKING_DIRECTORY "${GIT_WORKING_DIRECTORY}"
RESULT_VARIABLE GIT_DIRTY_RESULT
ERROR_QUIET
)
if(NOT GIT_DIRTY_RESULT EQUAL 0)
string(APPEND KYTY_GIT_HASH "-dirty")
endif()
endif()
endif()
configure_file("${INPUT_FILE}" "${OUTPUT_FILE}")
@@ -15,13 +15,6 @@ bool TestWaitRegMemValue(uint64_t value, uint64_t ref, uint64_t mask, uint32_t f
enum class Pm4ProcessResult { Complete, Blocked };
enum class ContextStateOperation : uint32_t {
Clear = 0,
Push = 1,
Pop = 2,
PushClear = 3,
};
class Pm4Execution {
public:
[[nodiscard]] bool MadeProgress() const noexcept { return m_made_progress; }
@@ -56,12 +49,24 @@ public:
KYTY_CLASS_NO_COPY(CommandProcessor);
void Reset();
void ApplyContextStateOperation(ContextStateOperation operation);
void BufferInit();
void BufferFlush();
void BufferFlushAndWait();
void BufferWait();
void BufferInit();
void BufferFlush();
void BufferFlushAndWait();
void BufferWait();
void BeginReadbackTransaction() {
if (m_readback_active) {
EXIT("nested command-processor readback transaction\n");
}
m_readback_active = true;
}
void EndReadbackTransaction() {
if (!m_readback_active) {
EXIT("command-processor readback transaction is not active\n");
}
m_readback_active = false;
}
HW::Context& GetCtx() { return m_ctx; }
HW::UserConfig& GetUcfg() { return m_ucfg; }
HW::Shader& GetShCtx() { return m_sh_ctx; }
@@ -78,7 +83,8 @@ 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 render_target_slice_offset = 0, uint32_t instance_count = 1,
uint32_t first_vertex = 0, uint32_t first_instance = 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,
@@ -146,9 +152,6 @@ 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(); }
@@ -157,8 +160,6 @@ private:
RenderContext& m_renderer;
HW::Context m_ctx;
HW::Context m_saved_ctx;
bool m_context_state_pushed = false;
HW::UserConfig m_ucfg;
HW::Shader m_sh_ctx;
HW::UserSgprType m_user_data_marker = HW::UserSgprType::Unknown;
@@ -167,12 +168,12 @@ private:
uint64_t m_index_base_addr = 0;
uint64_t m_draw_indirect_args_base_addr = 0;
uint64_t m_dispatch_indirect_args_base_addr = 0;
// Persistent draw state: indirect draws update it for subsequent draws.
uint32_t m_num_instances = 1;
uint32_t m_num_instances = 1;
uint32_t m_de_count = 0;
uint32_t m_ce_count = 0;
bool m_ce_complete = false;
uint32_t m_de_count = 0;
uint32_t m_ce_count = 0;
bool m_ce_complete = false;
bool m_readback_active = false;
uint32_t m_const_ram[0x3000] = {0};
@@ -254,7 +254,6 @@ constexpr auto MakeCustomOpcodeDispatchTable() {
g_cp_op_custom_func[Pm4::R_WAIT_MEM_64] = CpOpWaitRegMem64;
g_cp_op_custom_func[Pm4::R_FLIP] = CpOpFlip;
g_cp_op_custom_func[Pm4::R_RELEASE_MEM] = CpOpReleaseMem;
g_cp_op_custom_func[Pm4::R_CONTEXT_STATE] = CpOpContextState;
return g_cp_op_custom_func;
}
@@ -121,7 +121,6 @@ uint32_t HwUcSetGeIndexOffset(CommandProcessor&, uint32_t, uint32_t, const uint3
uint32_t HwUcSetGdsOaRegisters(CommandProcessor&, uint32_t, uint32_t, const uint32_t*, uint32_t);
uint32_t CpOpNop(CommandProcessor&, uint32_t, const uint32_t*, uint32_t, uint32_t);
uint32_t CpOpContextState(CommandProcessor&, uint32_t, const uint32_t*, uint32_t, uint32_t);
uint32_t CpOpSetBase(CommandProcessor&, uint32_t, const uint32_t*, uint32_t, uint32_t);
uint32_t CpOpClearState(CommandProcessor&, uint32_t, const uint32_t*, uint32_t, uint32_t);
uint32_t CpOpIndexBase(CommandProcessor&, uint32_t, const uint32_t*, uint32_t, uint32_t);
@@ -2107,19 +2107,6 @@ KYTY_CP_OP_PARSER(CpOpClearState) {
return 1;
}
KYTY_CP_OP_PARSER(CpOpContextState) {
KYTY_PROFILER_FUNCTION();
const auto packet_size_dw = KYTY_PM4_LEN(cmd_id);
EXIT_NOT_IMPLEMENTED(KYTY_PM4_R(cmd_id) != Pm4::R_CONTEXT_STATE);
EXIT_NOT_IMPLEMENTED(packet_size_dw != 3u && packet_size_dw != 5u);
EXIT_NOT_IMPLEMENTED(buffer[0] > static_cast<uint32_t>(ContextStateOperation::PushClear));
cp.ApplyContextStateOperation(static_cast<ContextStateOperation>(buffer[0]));
return packet_size_dw - 1u;
}
KYTY_CP_OP_PARSER(CpOpDumpConstRam) {
KYTY_PROFILER_FUNCTION();
+34 -76
View File
@@ -36,7 +36,6 @@ static thread_local CommandProcessor* g_current_processor = nullptr;
static thread_local Pm4Execution* g_current_execution = nullptr;
static thread_local bool g_gpu_mutex_owned = false;
static thread_local bool g_gpu_thread = false;
static thread_local GpuState* g_gpu_state = nullptr;
class GpuMutexLock final {
public:
@@ -99,10 +98,11 @@ public:
void SubmitFlipPreparation(uint64_t request_id);
void Done();
void Shutdown();
[[nodiscard]] bool IsStopping();
void SendCommand(Common::UniqueFunction<void>&& command);
void SendCommandSync(Common::UniqueFunction<void>&& command);
int GetFrameNum();
[[nodiscard]] bool IsStopping();
void SendCommand(Common::UniqueFunction<void>&& command);
void SendCommandSync(Common::UniqueFunction<void>&& command);
void SendCommandSyncWithProcessor(Common::UniqueFunction<void, CommandProcessor&>&& command);
int GetFrameNum();
[[nodiscard]] static bool IsGpuThread() noexcept { return g_gpu_thread; }
private:
@@ -127,7 +127,6 @@ private:
void WaitLocked();
void Enqueue(Submission submission);
void WaitForIdle();
void ProcessCommands();
bool Process(Submission& submission);
static void ThreadRun(void* data);
CommandProcessor& GetProcessor(uint32_t queue_id);
@@ -140,7 +139,6 @@ private:
Common::CondVar m_idle;
std::array<std::deque<Submission>, QueueCount> m_queues;
std::deque<Common::UniqueFunction<void>> m_commands;
std::atomic_uint32_t m_pending_commands {0};
uint32_t m_next_queue = 0;
uint32_t m_submission_count = 0;
bool m_processing = false;
@@ -155,8 +153,6 @@ private:
uint64_t m_submit_id = 0;
std::atomic_int m_done_num = 0;
std::jthread m_thread;
friend class CommandProcessor;
};
static bool GraphicsRunDebugDumpEnabled() {
@@ -199,25 +195,9 @@ void GpuState::SendCommand(Common::UniqueFunction<void>&& command) {
Common::LockGuard lock(m_queue_mutex);
EXIT_IF(!m_accepting);
m_commands.push_back(std::move(command));
m_pending_commands.fetch_add(1, std::memory_order_release);
m_work_available.Signal();
}
void GpuState::ProcessCommands() {
EXIT_IF(!IsGpuThread());
while (m_pending_commands.load(std::memory_order_acquire) != 0) {
Common::UniqueFunction<void> command;
{
Common::LockGuard lock(m_queue_mutex);
EXIT_IF(m_commands.empty());
command = std::move(m_commands.front());
m_commands.pop_front();
EXIT_IF(m_pending_commands.fetch_sub(1, std::memory_order_acq_rel) == 0);
}
command();
}
}
void GpuState::SendCommandSync(Common::UniqueFunction<void>&& command) {
EXIT_IF(!command);
if (IsGpuThread()) {
@@ -232,6 +212,17 @@ void GpuState::SendCommandSync(Common::UniqueFunction<void>&& command) {
done.acquire();
}
void GpuState::SendCommandSyncWithProcessor(
Common::UniqueFunction<void, CommandProcessor&>&& command) {
EXIT_IF(!command);
SendCommandSync([this, operation = std::move(command)]() mutable {
EXIT_IF(g_current_processor != nullptr);
g_current_processor = m_gfx_cp.get();
operation(*m_gfx_cp);
g_current_processor = nullptr;
});
}
void GpuState::Submit(uint32_t* cmd_draw_buffer, uint32_t num_draw_dw, uint32_t* cmd_const_buffer,
uint32_t num_const_dw, bool trigger_agc_interrupt_on_done) {
GpuMutexLock lock(m_submission_mutex);
@@ -312,8 +303,6 @@ void CommandProcessor::Reset() {
m_sh_ctx.Reset();
m_ucfg.Reset();
m_ctx.Reset();
m_saved_ctx.Reset();
m_context_state_pushed = false;
m_index_type_and_size = 0;
m_index_buffer_size = 0;
m_user_data_marker = HW::UserSgprType::Unknown;
@@ -323,30 +312,6 @@ void CommandProcessor::Reset() {
std::memset(m_const_ram, 0, sizeof(m_const_ram));
}
void CommandProcessor::ApplyContextStateOperation(ContextStateOperation operation) {
switch (operation) {
case ContextStateOperation::Clear: m_ctx.Reset(); break;
case ContextStateOperation::Push:
EXIT_IF(m_context_state_pushed);
m_saved_ctx = m_ctx;
m_context_state_pushed = true;
break;
case ContextStateOperation::Pop:
EXIT_IF(!m_context_state_pushed);
m_ctx = m_saved_ctx;
m_saved_ctx = {};
m_context_state_pushed = false;
break;
case ContextStateOperation::PushClear:
EXIT_IF(m_context_state_pushed);
m_saved_ctx = m_ctx;
m_context_state_pushed = true;
m_ctx.Reset();
break;
default: EXIT("unknown context state operation: %u\n", static_cast<uint32_t>(operation));
}
}
void CommandProcessor::BufferInit() {
GetScheduler().Begin(m_ctx, m_ucfg, m_sh_ctx);
}
@@ -544,7 +509,6 @@ void GpuState::ThreadRun(void* data) {
EXIT_IF(gpu == nullptr);
KYTY_PROFILER_THREAD("Thread_Gpu");
g_gpu_thread = true;
g_gpu_state = gpu;
for (;;) {
Submission submission;
@@ -565,7 +529,6 @@ void GpuState::ThreadRun(void* data) {
} else if (!gpu->m_commands.empty()) {
command = std::move(gpu->m_commands.front());
gpu->m_commands.pop_front();
EXIT_IF(gpu->m_pending_commands.fetch_sub(1, std::memory_order_acq_rel) == 0);
gpu->m_processing = true;
} else {
int selected_queue = -1;
@@ -597,7 +560,6 @@ void GpuState::ThreadRun(void* data) {
}
if (should_stop) {
gpu->m_gfx_cp->BufferWait();
g_gpu_state = nullptr;
g_gpu_thread = false;
return;
}
@@ -779,9 +741,6 @@ void CommandProcessor::SuspendPm4() {
void CommandProcessor::ProcessPm4(Pm4Execution& execution, size_t stop_depth) {
while (execution.m_buffer_stack.size() > stop_depth) {
if (g_gpu_state != nullptr) {
g_gpu_state->ProcessCommands();
}
const auto buffer_index = execution.m_buffer_stack.size() - 1;
auto& cursor = execution.m_buffer_stack[buffer_index];
if (cursor.deferred_advance_dw != 0) {
@@ -1024,9 +983,8 @@ void CommandProcessor::DrawIndirect(uint32_t data_offset, uint32_t draw_initiato
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);
DrawIndexAuto(args.vertex_count_per_instance, 0, 0, args.instance_count,
args.start_vertex_location, args.start_instance_location);
return;
}
@@ -1066,7 +1024,6 @@ 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);
}
@@ -1124,9 +1081,8 @@ void CommandProcessor::DrawIndirectMulti(uint32_t data_offset, uint32_t max_coun
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);
DrawIndexAuto(args->vertex_count_per_instance, 0, 0, args->instance_count,
args->start_vertex_location, args->start_instance_location);
continue;
}
@@ -1167,7 +1123,6 @@ 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);
}
@@ -1259,17 +1214,12 @@ 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) {
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) {
uint32_t render_target_slice_offset, uint32_t instance_count,
uint32_t first_vertex, uint32_t first_instance) {
CheckBuffer();
m_renderer.GetRenderExecutor().DrawAuto(m_submit_id, CurrentBuffer(), vertex_count, flags,
render_target_slice_offset, m_num_instances,
m_renderer.GetRenderExecutor().DrawAuto(m_submit_id, CurrentBuffer(), index_count, flags,
render_target_slice_offset, instance_count,
first_vertex, first_instance);
}
@@ -1691,6 +1641,10 @@ void Gpu::SendCommandSync(Common::UniqueFunction<void>&& command) {
m_state->SendCommandSync(std::move(command));
}
void Gpu::SendCommandSyncWithProcessor(Common::UniqueFunction<void, CommandProcessor&>&& command) {
m_state->SendCommandSyncWithProcessor(std::move(command));
}
void Gpu::Submit(uint32_t* draw_commands, uint32_t draw_size_dw, uint32_t* constant_commands,
uint32_t constant_size_dw, bool trigger_agc_interrupt_on_done) {
EXIT_IF(draw_commands == nullptr || draw_size_dw == 0);
@@ -1716,8 +1670,12 @@ int Gpu::GetFrameNum() const {
return m_state->GetFrameNum();
}
bool Gpu::IsGpuThread() noexcept {
return GpuState::IsGpuThread();
bool Gpu::IsCommandProcessorThread() noexcept {
return g_current_processor != nullptr;
}
CommandProcessor* Gpu::CurrentCommandProcessor() noexcept {
return g_current_processor;
}
} // namespace Libs::Graphics
+4 -1
View File
@@ -9,6 +9,7 @@
namespace Libs::Graphics {
class CommandProcessor;
class GpuState;
class RenderContext;
@@ -22,6 +23,7 @@ public:
[[nodiscard]] bool IsStopping();
void SendCommand(Common::UniqueFunction<void>&& command);
void SendCommandSync(Common::UniqueFunction<void>&& command);
void SendCommandSyncWithProcessor(Common::UniqueFunction<void, CommandProcessor&>&& command);
void Submit(uint32_t* draw_commands, uint32_t draw_size_dw, uint32_t* constant_commands,
uint32_t constant_size_dw, bool trigger_agc_interrupt_on_done = false);
@@ -31,7 +33,8 @@ public:
void Done();
[[nodiscard]] int GetFrameNum() const;
[[nodiscard]] static bool IsGpuThread() noexcept;
[[nodiscard]] static bool IsCommandProcessorThread() noexcept;
[[nodiscard]] static CommandProcessor* CurrentCommandProcessor() noexcept;
private:
std::unique_ptr<GpuState> m_state;
-1
View File
@@ -30,7 +30,6 @@ constexpr auto MakeRegisterNames() {
names[R_FLIP] = "R_FLIP";
names[R_RELEASE_MEM] = "R_RELEASE_MEM";
names[R_DMA_DATA] = "R_DMA_DATA";
names[R_CONTEXT_STATE] = "R_CONTEXT_STATE";
return names;
}
-1
View File
@@ -87,7 +87,6 @@ constexpr uint32_t R_WAIT_MEM_64 = 0x16;
constexpr uint32_t R_FLIP = 0x17;
constexpr uint32_t R_RELEASE_MEM = 0x18;
constexpr uint32_t R_DMA_DATA = 0x19;
constexpr uint32_t R_CONTEXT_STATE = 0x1A;
constexpr uint32_t R_NUM = 0x3F + 1;
+33 -17
View File
@@ -29,29 +29,45 @@ public:
void MarkRegionAsGpuModified(uint64_t vaddr, uint64_t size);
void UnmarkRegionAsGpuModified(uint64_t vaddr, uint64_t size);
void UntrackMemory(uint64_t vaddr, uint64_t size);
// Removes protection from a range and flushes GPU-owned data when required.
template <typename Flush>
void InvalidateRegion(uint64_t vaddr, uint64_t size, Flush&& on_flush) noexcept {
void InvalidateRegion(uint64_t vaddr, uint64_t size, Flush&& on_flush) {
static_assert(std::is_invocable_v<Flush&>);
CheckNotInUploadCallback();
ValidateRange(vaddr, size);
Iterate<false>(vaddr, size, [&](RegionManager* manager, uint64_t offset, uint64_t bytes) {
const bool should_flush = [&] {
// Perform both the GPU modification check and CPU state change with the lock in
// case the GPU thread is racing to mark the page modified. If a flush is needed,
// on_flush performs the CPU state change.
std::scoped_lock lock(manager->lock);
if (manager->IsModified<DirtySource::Gpu>(offset, bytes)) {
return true;
}
manager->ChangeState<DirtySource::Cpu, true>(manager->GetCpuAddr() + offset, bytes);
return false;
}();
if (should_flush) {
on_flush();
const auto update_cpu_state = [this, vaddr, size] {
std::lock_guard access(m_access_mutex);
std::vector<RegionManager*> managers;
Iterate<false>(vaddr, size, [&](RegionManager* manager, uint64_t, uint64_t) {
managers.push_back(manager);
});
std::vector<std::unique_lock<TrackingSpinLock>> locks;
locks.reserve(managers.size());
for (auto* manager: managers) {
locks.emplace_back(manager->lock);
}
});
const bool gpu_modified = Iterate<false>(
vaddr, size, [](RegionManager* manager, uint64_t offset, uint64_t bytes) {
return manager->IsModified<DirtySource::Gpu>(offset, bytes);
});
if (gpu_modified) {
return true;
}
Iterate<false>(vaddr, size,
[](RegionManager* manager, uint64_t offset, uint64_t bytes) {
manager->ChangeState<DirtySource::Cpu, true>(
manager->GetCpuAddr() + offset, bytes);
});
return false;
};
if (!update_cpu_state()) {
return;
}
std::forward<Flush>(on_flush)();
if (update_cpu_state()) {
EXIT("memory invalidation retained GPU-owned pages\n");
}
}
#if KYTY_BUILD == KYTY_BUILD_DEBUG
void ValidateGpuDirtyPages(const RangeSet& dirty, uint64_t vaddr, uint64_t size,
+5 -37
View File
@@ -3,18 +3,15 @@
#include "common/assert.h"
#include "common/logging/log.h"
#include "common/profiler.h"
#include "graphics/guest_gpu/graphicsRun.h"
#include "graphics/host_gpu/graphicContext.h"
#include "graphics/host_gpu/renderer/cache/resourceMutex.h"
#include "graphics/host_gpu/renderer/cache/textureCache.h"
#include "graphics/host_gpu/renderer/commandScheduler.h"
#include "graphics/host_gpu/renderer/render.h"
#include "graphics/host_gpu/renderer/renderContext.h"
#include "kernel/memory.h"
#include <algorithm>
#include <array>
#include <cinttypes>
#include <cstring>
#include <utility>
#include <vector>
@@ -291,29 +288,10 @@ void BufferCache::InvalidateMemory(uint64_t vaddr, uint64_t size) {
return;
}
m_memory_tracker.InvalidateRegion(vaddr, size,
[this, vaddr, size] { ReadMemory(vaddr, size, true); });
[this, vaddr, size] { ReadMemory(vaddr, size); });
}
void BufferCache::ReadMemory(uint64_t vaddr, uint64_t size, bool is_write) {
if (Gpu::IsGpuThread()) {
ReadMemoryOnGpu(vaddr, size, is_write);
return;
}
if (CommandScheduler::InDeferredOperation()) {
EXIT("unsupported buffer readback from an asynchronous GPU completion, "
"addr=0x%016" PRIx64 " size=0x%016" PRIx64 "\n",
vaddr, size);
}
if (m_resource_mutex.IsOwnedByCurrentThread()) {
EXIT("unsupported buffer readback from a pre-owned resource transaction, "
"addr=0x%016" PRIx64 " size=0x%016" PRIx64 "\n",
vaddr, size);
}
m_scheduler.Context().GetGpu().SendCommandSync(
[this, vaddr, size, is_write] { ReadMemoryOnGpu(vaddr, size, is_write); });
}
void BufferCache::ReadMemoryOnGpu(uint64_t vaddr, uint64_t size, bool is_write) {
void BufferCache::ReadMemory(uint64_t vaddr, uint64_t size) {
std::vector<DownloadCopy> copies;
{
FaultSafeCacheLock lock(this, m_mutex);
@@ -344,16 +322,9 @@ void BufferCache::ReadMemoryOnGpu(uint64_t vaddr, uint64_t size, bool is_write)
}
}
});
if (copies.empty()) {
if (!is_write) {
return;
}
// A preceding read fault can consume the last GPU-owned copy after this write
// invalidation has already chosen to flush. Complete the CPU ownership handoff even
// though this callback no longer has bytes to download.
m_memory_tracker.MarkRegionAsCpuModified(vaddr, size);
return;
}
}
if (copies.empty()) {
return;
}
auto downloads = RecordDownloads(copies);
m_scheduler.FinishCurrent();
@@ -365,9 +336,6 @@ void BufferCache::ReadMemoryOnGpu(uint64_t vaddr, uint64_t size, bool is_write)
}
// The enumeration above covered whole dirty pages and every exact interval on them.
m_memory_tracker.UnmarkRegionAsGpuModified(vaddr, size);
if (is_write) {
m_memory_tracker.MarkRegionAsCpuModified(vaddr, size);
}
}
}
+1 -2
View File
@@ -46,7 +46,7 @@ public:
KYTY_CLASS_NO_COPY(BufferCache);
void InvalidateMemory(uint64_t vaddr, uint64_t size);
void ReadMemory(uint64_t vaddr, uint64_t size, bool is_write = false);
void ReadMemory(uint64_t vaddr, uint64_t size);
void UnmapMemory(uint64_t vaddr, uint64_t size);
[[nodiscard]] BufferBinding ObtainBuffer(CommandBuffer& command, uint64_t vaddr, uint64_t size,
bool is_written = false, bool is_read = true,
@@ -97,7 +97,6 @@ private:
void PublishDownloads(std::span<const DownloadRange> downloads);
void QueueGarbageDownload(std::span<const DownloadCopy> copies, RetiredBuffer retire);
void WriteHostMemory(uint64_t vaddr, std::span<const uint8_t> data);
void ReadMemoryOnGpu(uint64_t vaddr, uint64_t size, bool is_write);
GraphicContext& m_graphics;
CommandScheduler& m_scheduler;
+57 -8
View File
@@ -1,6 +1,7 @@
#include "graphics/host_gpu/renderer/cache/gpuResourceManager.h"
#include "common/assert.h"
#include "graphics/guest_gpu/command_processor/commandProcessor.h"
#include "graphics/guest_gpu/graphicsRun.h"
#include "graphics/host_gpu/renderer/commandScheduler.h"
namespace Libs::Graphics {
@@ -17,21 +18,69 @@ bool GpuResourceManager::HandleFault(PageFaultAccess access, uint64_t fault_vadd
if (!IsMapped(fault_vaddr, fault_size)) {
return false;
}
if (access == PageFaultAccess::Write) {
m_buffer_cache.InvalidateMemory(fault_vaddr, fault_size);
m_texture_cache.InvalidateMemory(fault_vaddr, fault_size);
} else {
m_buffer_cache.ReadMemory(fault_vaddr, fault_size);
if (CommandScheduler::InDeferredOperation()) {
EXIT("unsupported guest-memory fault from an asynchronous GPU completion, "
"addr=0x%016" PRIx64 " access=%u\n",
fault_vaddr, static_cast<uint32_t>(access));
}
return true;
bool handled = false;
const auto resolve = [this, access, fault_vaddr, &handled](CommandProcessor& cp) {
cp.BeginReadbackTransaction();
{
ResourceMutex::FaultScope fault(m_resource_mutex);
if (access == PageFaultAccess::Write) {
m_buffer_cache.InvalidateMemory(fault_vaddr, fault_size);
m_texture_cache.InvalidateMemory(fault_vaddr, fault_size);
} else {
m_buffer_cache.ReadMemory(fault_vaddr, fault_size);
}
handled = true;
}
cp.EndReadbackTransaction();
};
if (auto* cp = Gpu::CurrentCommandProcessor(); cp != nullptr) {
resolve(*cp);
return handled;
}
if (m_resource_mutex.IsOwnedByCurrentThread()) {
EXIT("unsupported page fault from a pre-owned resource transaction, addr=0x%016" PRIx64
" access=%u\n",
fault_vaddr, static_cast<uint32_t>(access));
}
EXIT_IF(m_gpu == nullptr);
m_gpu->SendCommandSyncWithProcessor(resolve);
return handled;
}
bool GpuResourceManager::InvalidateMemory(uint64_t vaddr, uint64_t size) {
if (!IsMapped(vaddr, size)) {
return false;
}
m_buffer_cache.InvalidateMemory(vaddr, size);
m_texture_cache.InvalidateMemory(vaddr, size);
if (CommandScheduler::InDeferredOperation()) {
EXIT("unsupported memory invalidation from an asynchronous GPU completion, "
"addr=0x%016" PRIx64 " size=0x%016" PRIx64 "\n",
vaddr, size);
}
const auto resolve = [this, vaddr, size](CommandProcessor& cp) {
cp.BeginReadbackTransaction();
{
ResourceMutex::FaultScope fault(m_resource_mutex);
m_buffer_cache.InvalidateMemory(vaddr, size);
m_texture_cache.InvalidateMemory(vaddr, size);
}
cp.EndReadbackTransaction();
};
if (auto* cp = Gpu::CurrentCommandProcessor(); cp != nullptr) {
resolve(*cp);
return true;
}
if (m_resource_mutex.IsOwnedByCurrentThread()) {
EXIT("unsupported memory invalidation from a pre-owned resource transaction, "
"addr=0x%016" PRIx64 " size=0x%016" PRIx64 "\n",
vaddr, size);
}
EXIT_IF(m_gpu == nullptr);
m_gpu->SendCommandSyncWithProcessor(resolve);
return true;
}
+242 -154
View File
@@ -7,9 +7,8 @@
#include <array>
#include <cstddef>
#include <cstdint>
#include <cstring>
#include <list>
#include <memory>
#include <type_traits>
#include <utility>
#include <vector>
@@ -104,158 +103,6 @@ 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) {
@@ -267,6 +114,247 @@ 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_
+48 -2
View File
@@ -4,10 +4,18 @@
namespace Libs::Graphics {
ResourceMutex::FaultScope::FaultScope(ResourceMutex& mutex): m_mutex(mutex) {
m_resource_preowned = m_mutex.BeginFault();
}
ResourceMutex::FaultScope::~FaultScope() {
m_mutex.EndFault(m_resource_preowned);
}
ResourceMutex::~ResourceMutex() {
std::lock_guard state(m_state);
if (m_resource_owner != std::thread::id {}) {
EXIT("ResourceMutex destroyed with an active owner\n");
if (m_resource_owner != std::thread::id {} || m_fault_owner != std::thread::id {}) {
EXIT("ResourceMutex destroyed with an active owner or fault transaction\n");
}
}
@@ -18,6 +26,9 @@ void ResourceMutex::lock() {
if (m_resource_owner == current) {
EXIT("recursive resource transaction\n");
}
if (m_fault_owner == current) {
EXIT("resource transaction re-entered from a page-fault callback\n");
}
}
m_resource.lock();
std::lock_guard state(m_state);
@@ -44,4 +55,39 @@ bool ResourceMutex::IsOwnedByCurrentThread() {
return m_resource_owner == std::this_thread::get_id();
}
bool ResourceMutex::BeginFault() {
const auto current = std::this_thread::get_id();
{
std::lock_guard state(m_state);
if (m_fault_owner == current) {
EXIT("nested resource page-fault transaction\n");
}
if (m_resource_owner == current) {
m_fault_owner = current;
return true;
}
}
lock();
std::lock_guard state(m_state);
if (m_resource_owner != current || m_fault_owner != std::thread::id {}) {
EXIT("resource fault transaction acquired inconsistent ownership\n");
}
m_fault_owner = current;
return false;
}
void ResourceMutex::EndFault(bool resource_preowned) {
const auto current = std::this_thread::get_id();
{
std::lock_guard state(m_state);
if (m_fault_owner != current || m_resource_owner != current) {
EXIT("resource page-fault transaction ended with inconsistent ownership\n");
}
m_fault_owner = {};
}
if (!resource_preowned) {
unlock();
}
}
} // namespace Libs::Graphics
+19 -1
View File
@@ -8,9 +8,21 @@
namespace Libs::Graphics {
// Owner-tracked shared buffer/image transaction.
// Owner-tracked shared buffer/image transaction. External faults pause GPU submissions first;
// command-processor faults drain pending guest processors before entering this transaction.
class ResourceMutex final {
public:
class FaultScope final {
public:
explicit FaultScope(ResourceMutex& mutex);
~FaultScope();
KYTY_CLASS_NO_COPY(FaultScope);
private:
ResourceMutex& m_mutex;
bool m_resource_preowned = false;
};
ResourceMutex() = default;
~ResourceMutex();
KYTY_CLASS_NO_COPY(ResourceMutex);
@@ -20,9 +32,15 @@ public:
[[nodiscard]] bool IsOwnedByCurrentThread();
private:
friend class FaultScope;
[[nodiscard]] bool BeginFault();
void EndFault(bool resource_preowned);
std::mutex m_resource;
std::mutex m_state;
std::thread::id m_resource_owner;
std::thread::id m_fault_owner;
};
} // namespace Libs::Graphics
+18 -83
View File
@@ -195,12 +195,10 @@ void TextureCache::RegisterImage(ImageId id) {
if (image.registered || image.info.data.Empty()) {
EXIT("TextureCache: 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);
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");
}
image.registered = true;
image.lru_id = m_lru_cache.Insert(id, m_gc_tick);
@@ -213,15 +211,9 @@ void TextureCache::UnregisterImage(ImageId id) {
return;
}
UntrackImage(id);
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");
}
std::vector<ImageOwnerIndex::ByteRange> releases;
if (!m_image_owner_index.Unregister(id, releases)) {
EXIT("TextureCache: image missing from owner index\n");
}
m_lru_cache.Free(image.lru_id);
const auto accounted = image.AccountedSize();
@@ -459,48 +451,10 @@ const Image& TextureCache::GetImage(ImageId id) const {
return ResolveImage(id);
}
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;
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);
}
ImageId TextureCache::GetNullImage(const ImageDesc& desc) {
@@ -884,20 +838,11 @@ 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;
@@ -974,7 +919,6 @@ 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;
@@ -1012,19 +956,11 @@ void TextureCache::UploadImage(Image& image, const ImageDesc& desc, Buffer& sour
if (desc.type != BindingType::DepthTarget) {
auto plan = BuildColorTransfer(image, desc.type, TransferDirection::Upload);
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);
}
EXIT_NOT_IMPLEMENTED(!plan.valid);
TileManager::Result linear {source.Handle(), source_offset, info.data.size};
if (plan.tiled) {
linear = m_tiler->Detile(source.Handle(), source_offset, info.data.size,
plan.linear_size, plan.tiles);
linear = m_tiler->Detile(source.Handle(), source_offset, info.data.size, info.data.size,
plan.tiles);
}
if (plan.swap_bgra16) {
linear = m_tiler->SwapBgra16(linear);
@@ -1198,9 +1134,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 auto candidates =
std::lock_guard transaction(m_resource_mutex);
CacheLock lock(*this, m_lock);
const std::vector<ImageId> candidates =
FindImagesInRegion(desc.info.data.address, desc.info.data.size, false);
for (const auto id: candidates) {
@@ -1630,7 +1566,7 @@ void TextureCache::DownloadImageData(Image& image, Buffer& destination, uint64_t
}
m_tiler->TileImage(image, color.regions, destination.Handle(), destination_offset,
destination_size, color.linear_size, color.tiles, transform);
destination_size, destination_size, color.tiles, transform);
}
bool BufferCache::SynchronizeBufferFromImage(Buffer& buffer, uint64_t vaddr, uint64_t size) {
@@ -1723,7 +1659,6 @@ 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));
+7 -10
View File
@@ -99,8 +99,7 @@ private:
int32_t layer = -1;
};
using ImageIds = InlinePageOwnerList<ImageId, 16>;
using ImagePageTable = MultiLevelPageTable<ImageIds, 20, 40, 10>;
using ImageOwnerIndex = MultiRangePageOwnerIndex<ImageId>;
[[nodiscard]] Image& ResolveImage(ImageId id);
[[nodiscard]] const Image& ResolveImage(ImageId id) const;
@@ -126,9 +125,8 @@ private:
[[nodiscard]] static BindingType UploadBinding(const Image& image);
[[nodiscard]] bool SafeToDownload(const Image& image);
// 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]] std::vector<ImageId> 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,
@@ -171,7 +169,7 @@ private:
ResourceMutex& m_resource_mutex;
std::vector<Slot> m_slots;
std::vector<uint32_t> m_free_slots;
ImagePageTable m_image_page_table;
ImageOwnerIndex m_image_owner_index;
std::map<vk::Format, ImageId> m_null_images;
Common::LeastRecentlyUsedCache<ImageId, uint64_t> m_lru_cache;
std::set<ImageId> m_download_images;
@@ -179,10 +177,9 @@ 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;
mutable uint32_t m_image_query_epoch = 0;
bool m_readback_linear_images = false;
uint64_t m_critical_gc_memory = 3ull * 1024 * 1024 * 1024;
uint64_t m_gc_tick = 0;
bool m_readback_linear_images = false;
friend struct TextureCacheTestAccess;
friend class BufferCache;
@@ -96,11 +96,9 @@ void RenderExecutor::ResolveRenderDepthTarget(uint64_t submit_id, RenderCommandB
const auto& dc = hw.GetDepthControl();
const auto& sc = hw.GetStencilControl();
const auto& sm = hw.GetStencilMask();
const bool has_stencil =
z.stencil_info.format != Prospero::GpuEnumValue(Prospero::StencilFormat::kInvalid);
const bool depth_active =
const bool depth_active =
dc.z_enable || dc.z_write_enable || dc.depth_bounds_enable || rc.depth_clear_enable;
const bool stencil_active = has_stencil && (dc.stencil_enable || rc.stencil_clear_enable);
const bool stencil_active = dc.stencil_enable || rc.stencil_clear_enable;
if (!depth_active && !stencil_active) {
return;
}
@@ -141,6 +139,8 @@ void RenderExecutor::ResolveRenderDepthTarget(uint64_t submit_id, RenderCommandB
}
return;
}
const bool has_stencil =
z.stencil_info.format != Prospero::GpuEnumValue(Prospero::StencilFormat::kInvalid);
const bool has_htile = z.z_info.htile_acceleration;
const auto samples = render_sample_count(z.z_info.num_samples);
if (samples == 0) {
@@ -156,8 +156,8 @@ void RenderExecutor::ResolveRenderDepthTarget(uint64_t submit_id, RenderCommandB
DepthFatal("invalid depth view: base=%u last=%u", z.depth_view.slice_start,
z.depth_view.slice_max);
}
if (rc.resummarize_enable || rc.copy_centroid || rc.copy_sample != 0 ||
z.z_info.expclear_enabled || z.stencil_info.expclear_enabled ||
if ((stencil_active && !has_stencil) || rc.resummarize_enable || rc.copy_centroid ||
rc.copy_sample != 0 || z.z_info.expclear_enabled || z.stencil_info.expclear_enabled ||
z.z_info.partially_resident || z.stencil_info.partially_resident ||
z.z_info.max_mip_level != 0 || z.depth_view.current_mip_level != 0 ||
z.depth_info.addr5_swizzle_mask != 0 || z.depth_info.array_mode != 0 ||
@@ -279,10 +279,10 @@ void RenderExecutor::ResolveRenderDepthTarget(uint64_t submit_id, RenderCommandB
r.depth_min_bounds = hw.GetDepthBoundsMin();
r.depth_max_bounds = hw.GetDepthBoundsMax();
r.stencil_clear_enable = has_stencil && rc.stencil_clear_enable;
r.stencil_clear_enable = rc.stencil_clear_enable;
r.stencil_clear_value = hw.GetStencilClearValue();
r.stencil_test_enable = has_stencil && dc.stencil_enable;
if (r.stencil_test_enable) {
r.stencil_test_enable = dc.stencil_enable;
if (dc.stencil_enable) {
if (dc.stencilfunc > static_cast<uint8_t>(vk::CompareOp::eAlways) ||
(dc.backface_enable &&
dc.stencilfunc_bf > static_cast<uint8_t>(vk::CompareOp::eAlways)) ||
+11 -12
View File
@@ -154,18 +154,17 @@ public:
}
[[nodiscard]] uint64_t HashGuestEdges() const;
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;
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;
private:
friend struct ImageTestAccess;
@@ -113,6 +113,13 @@ inline void ValidateStorageColorView(vk::Format image_format, vk::Format view_fo
[[nodiscard]] inline bool
IsSupportedStorageImageResource(const ShaderRecompiler::IR::ImageResource& resource) noexcept {
const bool supported_mip =
(resource.mip_mode == ShaderRecompiler::IR::ImageMipMode::None &&
resource.mip_levels == 1u) ||
(resource.mip_mode == ShaderRecompiler::IR::ImageMipMode::DynamicStorage &&
resource.mip_levels > 0u &&
resource.mip_levels <= ShaderRecompiler::IR::ImageResource::MaxMipLevels &&
!resource.read && !resource.atomic);
return (resource.kind == ShaderRecompiler::IR::ResourceKind::StorageImage ||
resource.kind == ShaderRecompiler::IR::ResourceKind::StorageImageUint) &&
(resource.dimension == ShaderRecompiler::Decoder::ImageDimension::Dim1D ||
@@ -120,7 +127,7 @@ IsSupportedStorageImageResource(const ShaderRecompiler::IR::ImageResource& resou
resource.dimension == ShaderRecompiler::Decoder::ImageDimension::Dim2D ||
resource.dimension == ShaderRecompiler::Decoder::ImageDimension::Dim3D ||
resource.dimension == ShaderRecompiler::Decoder::ImageDimension::Dim2DArray) &&
resource.mip_mode == ShaderRecompiler::IR::ImageMipMode::None && resource.written &&
supported_mip && resource.written &&
(!resource.atomic ||
(resource.kind == ShaderRecompiler::IR::ResourceKind::StorageImageUint &&
resource.read)) &&
@@ -482,10 +482,10 @@ static bool SetGpuTileSize(uint64_t offset, uint64_t length, uint64_t capacity,
return true;
}
bool TextureBuildGpuTileInfos(uint64_t tiled_size, const std::vector<vk::BufferImageCopy>& regions,
bool TextureBuildGpuTileInfos(uint64_t 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 (tiled_size == 0 || levels == 0 || levels > 16 || depth == 0 ||
if (size == 0 || levels == 0 || levels > 16 || depth == 0 ||
regions.size() != GetTextureRegionCount(depth, levels, layout.volume_texture) ||
Prospero::IsFmaskTextureFormat(fmt)) {
return false;
@@ -538,9 +538,8 @@ bool TextureBuildGpuTileInfos(uint64_t tiled_size, const std::vector<vk::BufferI
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, UINT64_MAX,
info.linear_size) ||
!SetGpuTileSize(info.tiled_offset, volume.level_sizes[level], tiled_size,
if (!SetGpuTileSize(info.linear_offset, linear_span, size, info.linear_size) ||
!SetGpuTileSize(info.tiled_offset, volume.level_sizes[level], size,
info.tiled_size)) {
return false;
}
@@ -589,9 +588,8 @@ bool TextureBuildGpuTileInfos(uint64_t tiled_size, const std::vector<vk::BufferI
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, UINT64_MAX,
info.linear_size) ||
!SetGpuTileSize(info.tiled_offset, GetLevelSrcSize(level_size), tiled_size,
if (!SetGpuTileSize(info.linear_offset, level_size.size, size, info.linear_size) ||
!SetGpuTileSize(info.tiled_offset, GetLevelSrcSize(level_size), 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 tiled_size, const std::vector<vk::BufferImageCopy>& regions,
bool TextureBuildGpuTileInfos(uint64_t size, const std::vector<vk::BufferImageCopy>& regions,
const TextureUploadLayout& layout, uint32_t fmt, uint32_t depth,
uint64_t levels, std::vector<GpuTileInfo>& infos);
@@ -94,10 +94,11 @@ vk::DescriptorBufferInfo BufferInfo(const BufferView& view) {
} // namespace
vk::DescriptorImageInfo DescriptorCache::MakeImageInfo(const TextureBinding& texture) {
EXIT_IF(!texture.image_id || texture.image_view == nullptr ||
texture.layout == vk::ImageLayout::eUndefined);
return {nullptr, texture.image_view, texture.layout};
vk::DescriptorImageInfo DescriptorCache::MakeImageInfo(const TextureBinding& texture,
uint32_t mip) {
const auto view = texture.mip_views.empty() ? texture.image_view : texture.mip_views.at(mip);
EXIT_IF(!texture.image_id || view == nullptr || texture.layout == vk::ImageLayout::eUndefined);
return {nullptr, view, texture.layout};
}
DescriptorCache::~DescriptorCache() {
@@ -154,7 +155,8 @@ void DescriptorCache::CreatePool() {
MaxSets * (ShaderRecompiler::IR::ShaderInfo::MaxBuffers +
ShaderRecompiler::IR::ShaderInfo::MaxAddresses + 3u)},
{vk::DescriptorType::eSampledImage, MaxSets * ShaderRecompiler::IR::ShaderInfo::MaxImages},
{vk::DescriptorType::eStorageImage, MaxSets * ShaderRecompiler::IR::ShaderInfo::MaxImages},
{vk::DescriptorType::eStorageImage, MaxSets * ShaderRecompiler::IR::ShaderInfo::MaxImages *
ShaderRecompiler::IR::ImageResource::MaxMipLevels},
{vk::DescriptorType::eSampler, MaxSets * ShaderRecompiler::IR::ShaderInfo::MaxSamplers},
};
vk::DescriptorPoolCreateInfo info {};
@@ -229,8 +231,10 @@ VulkanDescriptorSet& DescriptorCache::GetDescriptor(Stage
auto* set = Allocate(stage, program);
EXIT_NOT_IMPLEMENTED(set == nullptr);
const auto descriptor_count = program.info.buffers.size() + program.info.images.size() +
program.info.samplers.size() + program.info.addresses.size() + 3u;
uint32_t descriptor_count = 0;
for (const auto& binding: program.bindings.descriptors) {
descriptor_count += DescriptorCount(binding);
}
std::vector<vk::DescriptorBufferInfo> buffer_infos;
std::vector<vk::DescriptorImageInfo> image_infos;
std::vector<vk::WriteDescriptorSet> writes;
@@ -238,6 +242,7 @@ VulkanDescriptorSet& DescriptorCache::GetDescriptor(Stage
image_infos.reserve(descriptor_count);
writes.reserve(program.bindings.descriptors.size());
std::vector<uint32_t> image_mips(program.info.images.size());
for (const auto& binding: program.bindings.descriptors) {
vk::WriteDescriptorSet write {};
write.sType = vk::StructureType::eWriteDescriptorSet;
@@ -273,7 +278,11 @@ VulkanDescriptorSet& DescriptorCache::GetDescriptor(Stage
default: {
for (const auto resource: binding.resources) {
const auto& texture = data.images.at(resource);
image_infos.push_back(MakeImageInfo(texture));
const auto mip = program.info.images.at(resource).mip_mode ==
ShaderRecompiler::IR::ImageMipMode::DynamicStorage
? image_mips.at(resource)++
: 0u;
image_infos.push_back(MakeImageInfo(texture, mip));
}
break;
}
@@ -47,10 +47,11 @@ public:
enum class Stage { Unknown, Vertex, Pixel, Compute };
struct TextureBinding {
ImageId image_id;
vk::ImageView image_view = nullptr;
TextureCache::ImageDesc desc;
vk::ImageLayout layout = vk::ImageLayout::eUndefined;
ImageId image_id;
vk::ImageView image_view = nullptr;
TextureCache::ImageDesc desc;
vk::ImageLayout layout = vk::ImageLayout::eUndefined;
std::vector<vk::ImageView> mip_views;
};
struct NativeDescriptors {
@@ -94,7 +95,7 @@ private:
int next_free_pool = -1;
};
static vk::DescriptorImageInfo MakeImageInfo(const TextureBinding& texture);
static vk::DescriptorImageInfo MakeImageInfo(const TextureBinding& texture, uint32_t mip = 0);
void CreatePool();
VulkanDescriptorSet* Allocate(Stage stage, const ShaderRecompiler::IR::Program& program);
vk::DescriptorSetLayout
@@ -391,8 +391,16 @@ static bool IsSupportedStorageTextureDescriptor(const ShaderRecompiler::IR::Imag
const bool supported_swizzle =
IsValidImageSwizzle(swizzle) &&
(swizzle == DstSel(4, 5, 6, 7) || !resource.read || resource.atomic);
const auto base_level = static_cast<uint32_t>(descriptor.BaseLevel());
const auto last_level = static_cast<uint32_t>(descriptor.LastLevel());
const auto mip_levels = last_level >= base_level ? last_level - base_level + 1u : 0u;
const bool dynamic_mip =
resource.mip_mode == ShaderRecompiler::IR::ImageMipMode::DynamicStorage;
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 &&
descriptor.BaseLevel() == descriptor.LastLevel() &&
supported_mip_view && mip_levels != 0u &&
((dynamic_mip && mip_levels == resource.mip_levels) ||
(!dynamic_mip && descriptor.BaseLevel() == descriptor.LastLevel())) &&
descriptor.LastLevel() <= descriptor.MaxMip() && descriptor.MinLod() == 0 &&
supported_swizzle && descriptor.BCSwizzle() == 0 && !descriptor.MsaaDepth();
}
@@ -639,7 +647,10 @@ RenderExecutor::ResolveTexture(const ShaderRecompiler::IR::ImageResource& reso
}
const auto samples = multisampled ? 1u << last_level : 1u;
const auto view_levels =
multisampled ? 1u : static_cast<uint32_t>(last_level - base_level) + 1u;
multisampled ||
(storage && resource.mip_mode == ShaderRecompiler::IR::ImageMipMode::DynamicStorage)
? 1u
: static_cast<uint32_t>(last_level - base_level) + 1u;
const auto depth = static_cast<uint32_t>(descriptor.Depth()) + 1u;
const auto format = descriptor.Format();
const bool sampled_numeric_class =
@@ -894,7 +905,17 @@ void RenderExecutor::RebindImages(CommandBuffer& buffer,
}
auto& binding = images[i];
binding.image_view = texture_cache.FindTexture(binding.image_id, binding.desc);
auto& image = texture_cache.GetImage(binding.image_id);
auto& image = texture_cache.GetImage(binding.image_id);
binding.mip_views.clear();
if (program.info.images[i].mip_mode == ShaderRecompiler::IR::ImageMipMode::DynamicStorage) {
binding.mip_views.reserve(program.info.images[i].mip_levels);
for (uint32_t mip = 0; mip < program.info.images[i].mip_levels; mip++) {
auto view = binding.desc.view_info;
view.base_level += mip;
view.level_count = 1;
binding.mip_views.push_back(mip == 0 ? binding.image_view : image.FindView(view));
}
}
const bool storage = binding.desc.type == TextureCache::BindingType::Storage;
image.usage.storage |= storage;
image.usage.texture |= !storage;
@@ -972,7 +993,11 @@ void RenderExecutor::CommitBindings(CommandBuffer& buffer,
auto& image = m_context.GetTextureCache().GetImage(descriptors.images[i].image_id);
auto& binding = descriptors.images[i];
const auto& view = binding.desc.view_info;
const ImageSubresourceRange range {view.base_level, view.level_count, view.base_layer,
const auto level_count =
program.info.images[i].mip_mode == ShaderRecompiler::IR::ImageMipMode::DynamicStorage
? program.info.images[i].mip_levels
: view.level_count;
const ImageSubresourceRange range {view.base_level, level_count, view.base_layer,
view.layer_count};
const bool storage = binding.desc.type == TextureCache::BindingType::Storage;
if (image.info.data.Empty()) {
@@ -154,9 +154,8 @@ PipelineCache::GraphicsPipeline& PipelineCache::CreateGraphicsPipeline(
for (uint32_t i = 0; i < RENDER_COLOR_ATTACHMENTS_MAX; i++) {
static_params.color_mask[i] = color_mask[i];
}
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.cull_back = mc.cull_back;
static_params.cull_front = mc.cull_front;
static_params.face = mc.face;
for (uint32_t i = 0; i < color_count; i++) {
@@ -14,7 +14,6 @@
#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>
@@ -464,12 +463,8 @@ void CreatePipelineInternal(
uint32_t ps_hash0, uint32_t ps_crc32, bool ps_active) {
EXIT_IF(ps_active && ps_input_info == nullptr);
const bool rect_list = static_params.topology == vk::PrimitiveTopology::ePatchList;
vk::ShaderModule vert_shader_module = nullptr;
vk::ShaderModule tess_control_shader_module = nullptr;
vk::ShaderModule tess_eval_shader_module = nullptr;
vk::ShaderModule frag_shader_module = nullptr;
vk::ShaderModule vert_shader_module = nullptr;
vk::ShaderModule frag_shader_module = nullptr;
vk::ShaderModuleCreateInfo create_info {};
@@ -496,33 +491,8 @@ 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 {};
@@ -555,28 +525,9 @@ void CreatePipelineInternal(
frag_shader_stage_info);
}
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::PipelineShaderStageCreateInfo shader_stages[] = {vert_shader_stage_info,
frag_shader_stage_info};
const uint32_t shader_stage_count = ps_active ? 2u : 1u;
vk::VertexInputAttributeDescription input_attr[ShaderVertexInputInfo::RES_MAX];
vk::VertexInputBindingDescription input_desc[ShaderVertexInputInfo::RES_MAX];
@@ -978,13 +929,10 @@ void CreatePipelineInternal(
pipeline_info.pStages = shader_stages;
pipeline_info.pVertexInputState = &vertex_input_info;
pipeline_info.pInputAssemblyState = &input_assembly;
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.pTessellationState = 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;
@@ -1020,12 +968,6 @@ 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);
}
+93 -26
View File
@@ -2,6 +2,7 @@
#include "common/assert.h"
#include "common/common.h"
#include "common/emulatorConfig.h"
#include "common/file.h"
#include "common/logging/log.h"
#include "common/profiler.h"
@@ -649,9 +650,11 @@ static bool ConsumeMetadataColorOperation(const RenderCommandBuffer& buffer) {
}
struct DrawEmitInfo {
bool indexed = false;
int32_t vertex_offset = 0;
uint32_t first_vertex = 0;
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;
};
struct DrawIndexBufferSource {
@@ -664,10 +667,12 @@ struct DrawIndexBufferSource {
struct PreparedIndexBuffer {
std::shared_ptr<void> owner;
vk::Buffer buffer = nullptr;
uint64_t size = 0;
vk::DeviceSize offset = 0;
vk::IndexType type = vk::IndexType::eUint16;
vk::Buffer buffer = nullptr;
uint64_t address = 0;
uint64_t size = 0;
vk::DeviceSize offset = 0;
vk::IndexType type = vk::IndexType::eUint16;
bool streamed = false;
};
static uint64_t VertexBufferDescriptorSize(const ShaderVertexInputBuffer& buffer) {
@@ -762,7 +767,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,
static bool GetDrawTopology(const HW::UserConfig& ucfg, bool auto_draw, bool use_ngg_rectlist_draw,
vk::PrimitiveTopology& topology) {
topology = vk::PrimitiveTopology::ePointList;
@@ -786,7 +791,8 @@ static bool GetDrawTopology(const HW::UserConfig& ucfg, bool auto_draw,
topology = vk::PrimitiveTopology::eTriangleStrip;
break;
case Prospero::PrimitiveType::kRectList:
topology = vk::PrimitiveTopology::ePatchList;
topology = (auto_draw && use_ngg_rectlist_draw ? vk::PrimitiveTopology::eTriangleStrip
: vk::PrimitiveTopology::eTriangleList);
break;
case Prospero::PrimitiveType::kRectListLegacy:
if (!auto_draw) {
@@ -894,6 +900,12 @@ static std::vector<BufferBinding> PrepareVertexBuffers(uint64_t
return AcquireVertexBuffers(buffer, vs_input_info);
}
static void RebindVertexBuffers(RenderCommandBuffer& buffer,
const ShaderVertexInputInfo& vs_input_info,
std::vector<BufferBinding>& bindings) {
bindings = AcquireVertexBuffers(buffer, vs_input_info);
}
static PreparedIndexBuffer PrepareIndexBuffer(RenderCommandBuffer& buffer,
const DrawIndexBufferSource& source) {
PreparedIndexBuffer prepared;
@@ -901,14 +913,16 @@ static PreparedIndexBuffer PrepareIndexBuffer(RenderCommandBuffer& buffe
return prepared;
}
EXIT_IF(source.size == 0);
prepared.size = source.size;
prepared.type = source.type;
prepared.address = source.address;
prepared.size = source.size;
prepared.type = source.type;
if (source.host_data != nullptr) {
auto binding =
buffer.GetContext().GetBufferCache().UploadTransient(source.host_data, source.size, 16);
prepared.owner = std::move(binding.owner);
prepared.buffer = binding.buffer;
prepared.offset = binding.offset;
prepared.owner = std::move(binding.owner);
prepared.buffer = binding.buffer;
prepared.offset = binding.offset;
prepared.streamed = true;
} else {
auto binding =
buffer.GetContext().GetBufferCache().ObtainBuffer(buffer, source.address, source.size);
@@ -919,6 +933,17 @@ static PreparedIndexBuffer PrepareIndexBuffer(RenderCommandBuffer& buffe
return prepared;
}
static void RebindIndexBuffer(RenderCommandBuffer& buffer, PreparedIndexBuffer& prepared) {
if (prepared.size == 0 || prepared.streamed) {
return;
}
auto binding =
buffer.GetContext().GetBufferCache().ObtainBuffer(buffer, prepared.address, prepared.size);
prepared.owner = std::move(binding.owner);
prepared.buffer = binding.buffer;
prepared.offset = binding.offset;
}
static void CommitVertexBuffers(RenderCommandBuffer& buffer, vk::CommandBuffer vk_buffer,
std::vector<BufferBinding>& bindings) {
for (uint32_t slot = 0; slot < bindings.size(); slot++) {
@@ -966,6 +991,20 @@ 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) {
@@ -978,7 +1017,6 @@ static void EmitDrawPrimitives(const HW::UserConfig& ucfg, vk::CommandBuffer vk_
case Prospero::PrimitiveType::kTriList:
case Prospero::PrimitiveType::kTriFan:
case Prospero::PrimitiveType::kTriStrip:
case Prospero::PrimitiveType::kRectList:
if (emit.indexed) {
vk_buffer.drawIndexed(draw.index_count, draw.instance_count, 0, emit.vertex_offset,
draw.first_instance);
@@ -987,6 +1025,17 @@ static void EmitDrawPrimitives(const HW::UserConfig& ucfg, vk::CommandBuffer vk_
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,
draw.first_instance);
}
break;
case Prospero::PrimitiveType::kRectListLegacy:
if (emit.indexed) {
EXIT("unknown primitive type: %u\n", ucfg.GetPrimType());
@@ -1025,6 +1074,8 @@ void RenderExecutor::ExecutePreparedDraw(uint64_t submit_id, RenderCommandBuffer
state.ps_input_info.stage, state.ps_active);
auto vertex_bindings = PrepareVertexBuffers(submit_id, buffer, draw, state.vs_input_info);
auto index_binding = PrepareIndexBuffer(buffer, index_source);
RebindVertexBuffers(buffer, state.vs_input_info, vertex_bindings);
RebindIndexBuffer(buffer, index_binding);
state.rendering =
AcquireRenderTargets(buffer, state.color_info, state.color_count, state.depth_info);
@@ -1108,7 +1159,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 || instance_count == 0) {
if (index_count == 0) {
return;
}
@@ -1150,7 +1201,7 @@ void RenderExecutor::DrawIndex(uint64_t submit_id, RenderCommandBuffer& buffer,
hw_check(buffer);
vk::PrimitiveTopology topology = vk::PrimitiveTopology::ePointList;
if (!GetDrawTopology(ucfg, false, topology)) {
if (!GetDrawTopology(ucfg, false, false, topology)) {
return;
}
@@ -1178,6 +1229,10 @@ 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};
@@ -1237,7 +1292,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 || instance_count == 0) {
if (index_count == 0) {
return;
}
@@ -1275,6 +1330,10 @@ 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};
@@ -1286,15 +1345,20 @@ void RenderExecutor::DrawAuto(uint64_t submit_id, RenderCommandBuffer& buffer, u
return;
}
vk::PrimitiveTopology topology = vk::PrimitiveTopology::ePointList;
if (!GetDrawTopology(ucfg, true, topology)) {
vk::PrimitiveTopology topology = vk::PrimitiveTopology::ePointList;
const bool use_ngg_rectlist_draw = Config::NggRectlistDrawEnabled();
if (!GetDrawTopology(ucfg, true, use_ngg_rectlist_draw, 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);
const bool rect_list = topology == vk::PrimitiveTopology::ePatchList;
if (rect_list && state.vs_input_info.buffers_num == 0 &&
if (draw_prim7_as_ngg && 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: "
@@ -1311,10 +1375,13 @@ void RenderExecutor::DrawAuto(uint64_t submit_id, RenderCommandBuffer& buffer, u
Prospero::GpuEnumValue(Prospero::PrimitiveType::kRectListLegacy),
0, nullptr);
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);
const uint32_t draw_vertex_count = (draw_prim7_as_ngg ? 4u : index_count);
const auto vertex_offset = ResolveVertexOffset(ucfg.GetIndexOffset(), state.vs_input_info) +
static_cast<int32_t>(first_vertex);
DrawEmitInfo emit {};
emit.draw_prim7_as_ngg = draw_prim7_as_ngg;
emit.draw_vertex_count = draw_vertex_count;
emit.first_vertex = static_cast<uint32_t>(vertex_offset);
DrawIndexBufferSource index_source {};
ExecutePreparedDraw(submit_id, buffer, draw, state, topology, emit, index_source, false, false,
@@ -1,256 +0,0 @@
#include "graphics/presentation/window/hostInput.h"
#include "SDL_keyboard.h"
#include "SDL_keycode.h"
#include "SDL_mouse.h"
#include "common/assert.h"
#include "common/emulatorConfig.h"
#include "libs/controller.h"
#include <algorithm>
#include <array>
#include <cstddef>
#include <string>
#include <string_view>
#include <utility>
namespace Libs::Graphics {
namespace {
struct Binding {
SDL_Keycode key = SDLK_UNKNOWN;
uint8_t mouse_button = 0;
uint32_t pad_button = 0;
};
static constexpr std::array PAD_BUTTONS = {
std::pair {std::string_view("L3"), Controller::PAD_BUTTON_L3},
std::pair {std::string_view("R3"), Controller::PAD_BUTTON_R3},
std::pair {std::string_view("Options"), Controller::PAD_BUTTON_OPTIONS},
std::pair {std::string_view("Up"), Controller::PAD_BUTTON_UP},
std::pair {std::string_view("Right"), Controller::PAD_BUTTON_RIGHT},
std::pair {std::string_view("Down"), Controller::PAD_BUTTON_DOWN},
std::pair {std::string_view("Left"), Controller::PAD_BUTTON_LEFT},
std::pair {std::string_view("L2"), Controller::PAD_BUTTON_L2},
std::pair {std::string_view("R2"), Controller::PAD_BUTTON_R2},
std::pair {std::string_view("L1"), Controller::PAD_BUTTON_L1},
std::pair {std::string_view("R1"), Controller::PAD_BUTTON_R1},
std::pair {std::string_view("Triangle"), Controller::PAD_BUTTON_TRIANGLE},
std::pair {std::string_view("Circle"), Controller::PAD_BUTTON_CIRCLE},
std::pair {std::string_view("Cross"), Controller::PAD_BUTTON_CROSS},
std::pair {std::string_view("Square"), Controller::PAD_BUTTON_SQUARE},
std::pair {std::string_view("TouchPad"), Controller::PAD_BUTTON_TOUCH_PAD},
};
uint32_t PadButtonFromName(std::string_view name) {
const auto button = std::find_if(PAD_BUTTONS.begin(), PAD_BUTTONS.end(),
[name](const auto& item) { return item.first == name; });
return button != PAD_BUTTONS.end() ? button->second : 0;
}
SDL_Keycode NormalizeKey(SDL_Keycode key) {
switch (key) {
case SDLK_RSHIFT: return SDLK_LSHIFT;
case SDLK_RCTRL: return SDLK_LCTRL;
case SDLK_RALT: return SDLK_LALT;
case SDLK_RGUI: return SDLK_LGUI;
default: return key;
}
}
uint8_t MouseButtonFromName(std::string_view name) {
static constexpr std::array buttons = {
std::pair {std::string_view("Mouse:Left"), uint8_t {SDL_BUTTON_LEFT}},
std::pair {std::string_view("Mouse:Middle"), uint8_t {SDL_BUTTON_MIDDLE}},
std::pair {std::string_view("Mouse:Right"), uint8_t {SDL_BUTTON_RIGHT}},
std::pair {std::string_view("Mouse:X1"), uint8_t {SDL_BUTTON_X1}},
std::pair {std::string_view("Mouse:X2"), uint8_t {SDL_BUTTON_X2}},
};
const auto button = std::find_if(buttons.begin(), buttons.end(),
[name](const auto& item) { return item.first == name; });
return button != buttons.end() ? button->second : 0;
}
bool Conflicts(const Binding& first, const Binding& second) {
return first.pad_button == second.pad_button ||
(first.key != SDLK_UNKNOWN && first.key == second.key) ||
(first.mouse_button != 0 && first.mouse_button == second.mouse_button);
}
class InputMap {
public:
InputMap(): m_custom(!Config::GetKeymap().empty()) {
for (const auto& value: Config::GetKeymap()) {
const std::string_view entry = value;
const auto split = entry.find('=');
Binding binding;
if (split != std::string_view::npos) {
binding.pad_button = PadButtonFromName(entry.substr(0, split));
const auto host_input = entry.substr(split + 1);
binding.mouse_button = MouseButtonFromName(host_input);
if (binding.mouse_button == 0) {
binding.key = NormalizeKey(SDL_GetKeyFromName(std::string(host_input).c_str()));
}
}
const bool reserved =
binding.key == SDLK_ESCAPE || binding.key == SDLK_SPACE || binding.key == SDLK_F1;
if (binding.pad_button == 0 || reserved ||
(binding.key == SDLK_UNKNOWN && binding.mouse_button == 0)) {
EXIT("Invalid input mapping: %s\n", value.c_str());
}
Add(binding);
}
}
[[nodiscard]] bool Custom() const { return m_custom; }
[[nodiscard]] uint32_t FindKey(int key_code) const {
key_code = NormalizeKey(static_cast<SDL_Keycode>(key_code));
const auto binding =
std::find_if(m_bindings.begin(), m_bindings.begin() + m_size,
[key_code](const auto& item) { return item.key == key_code; });
return binding != m_bindings.begin() + m_size ? binding->pad_button : 0;
}
[[nodiscard]] uint32_t FindMouseButton(uint8_t mouse_button) const {
const auto binding = std::find_if(
m_bindings.begin(), m_bindings.begin() + m_size,
[mouse_button](const auto& item) { return item.mouse_button == mouse_button; });
return binding != m_bindings.begin() + m_size ? binding->pad_button : 0;
}
private:
void Add(const Binding& binding) {
for (std::size_t index = 0; index < m_size;) {
if (Conflicts(m_bindings[index], binding)) {
m_bindings[index] = m_bindings[--m_size];
} else {
index++;
}
}
EXIT_IF(m_size >= m_bindings.size());
m_bindings[m_size++] = binding;
}
std::array<Binding, PAD_BUTTONS.size()> m_bindings {};
std::size_t m_size = 0;
bool m_custom;
};
const InputMap& GetInputMap() {
static const InputMap map;
return map;
}
void SetButton(uint32_t button, bool down) {
if (button == Controller::PAD_BUTTON_L2) {
Controller::ControllerAxis(Controller::HOST_INPUT_CONTROLLER_ID,
Controller::Axis::TriggerLeft, down ? 255 : 0);
} else if (button == Controller::PAD_BUTTON_R2) {
Controller::ControllerAxis(Controller::HOST_INPUT_CONTROLLER_ID,
Controller::Axis::TriggerRight, down ? 255 : 0);
} else if (button != 0) {
Controller::ControllerButton(Controller::HOST_INPUT_CONTROLLER_ID, button, down);
}
}
uint32_t DefaultKeyboardButton(int key_code) {
switch (NormalizeKey(static_cast<SDL_Keycode>(key_code))) {
case SDLK_UP: return Controller::PAD_BUTTON_UP;
case SDLK_LEFT: return Controller::PAD_BUTTON_LEFT;
case SDLK_DOWN: return Controller::PAD_BUTTON_DOWN;
case SDLK_RIGHT: return Controller::PAD_BUTTON_RIGHT;
case SDLK_j: return Controller::PAD_BUTTON_CROSS;
case SDLK_i: return Controller::PAD_BUTTON_TRIANGLE;
case SDLK_k: return Controller::PAD_BUTTON_SQUARE;
case SDLK_l: return Controller::PAD_BUTTON_CIRCLE;
case SDLK_q: return Controller::PAD_BUTTON_L1;
case SDLK_e: return Controller::PAD_BUTTON_R1;
case SDLK_LSHIFT: return Controller::PAD_BUTTON_L3;
case SDLK_LCTRL: return Controller::PAD_BUTTON_R3;
case SDLK_RETURN:
case SDLK_RETURN2: return Controller::PAD_BUTTON_OPTIONS;
case SDLK_BACKSPACE:
case SDLK_TAB: return Controller::PAD_BUTTON_TOUCH_PAD;
default: return 0;
}
}
struct StickKeys {
bool left = false;
bool right = false;
bool up = false;
bool down = false;
};
void SetStickAxis(Controller::Axis axis, bool negative, bool positive) {
const int value = negative == positive ? 128 : negative ? 0 : 255;
Controller::ControllerAxis(Controller::HOST_INPUT_CONTROLLER_ID, axis, value);
}
void DefaultKeyboardInput(int key_code, bool down) {
static StickKeys left;
static StickKeys right;
switch (NormalizeKey(static_cast<SDL_Keycode>(key_code))) {
case SDLK_a:
left.left = down;
SetStickAxis(Controller::Axis::LeftX, left.left, left.right);
return;
case SDLK_d:
left.right = down;
SetStickAxis(Controller::Axis::LeftX, left.left, left.right);
return;
case SDLK_w:
left.up = down;
SetStickAxis(Controller::Axis::LeftY, left.up, left.down);
return;
case SDLK_s:
left.down = down;
SetStickAxis(Controller::Axis::LeftY, left.up, left.down);
return;
case SDLK_f:
right.left = down;
SetStickAxis(Controller::Axis::RightX, right.left, right.right);
return;
case SDLK_h:
right.right = down;
SetStickAxis(Controller::Axis::RightX, right.left, right.right);
return;
case SDLK_t:
right.up = down;
SetStickAxis(Controller::Axis::RightY, right.up, right.down);
return;
case SDLK_g:
right.down = down;
SetStickAxis(Controller::Axis::RightY, right.up, right.down);
return;
default: SetButton(DefaultKeyboardButton(key_code), down); return;
}
}
} // namespace
void HostInputInit() {
GetInputMap();
}
void HostInputKey(int key_code, bool down) {
const auto& map = GetInputMap();
if (map.Custom()) {
SetButton(map.FindKey(key_code), down);
} else {
DefaultKeyboardInput(key_code, down);
}
}
void HostInputMouseButton(uint8_t mouse_button, bool down) {
const auto& map = GetInputMap();
if (map.Custom() && mouse_button != 0) {
SetButton(map.FindMouseButton(mouse_button), down);
}
}
} // namespace Libs::Graphics
@@ -1,14 +0,0 @@
#ifndef KYTY_GRAPHICS_PRESENTATION_WINDOW_HOST_INPUT_H_
#define KYTY_GRAPHICS_PRESENTATION_WINDOW_HOST_INPUT_H_
#include <cstdint>
namespace Libs::Graphics {
void HostInputInit();
void HostInputKey(int key_code, bool down);
void HostInputMouseButton(uint8_t mouse_button, bool down);
} // namespace Libs::Graphics
#endif /* KYTY_GRAPHICS_PRESENTATION_WINDOW_HOST_INPUT_H_ */
@@ -789,14 +789,12 @@ void Presenter::Present(Frame& frame, bool reuse) {
[&window] {
window.UpdateIcon();
SDL_ShowWindow(window.window);
SDL_RaiseWindow(window.window);
},
true);
#else
window.UpdateIcon();
SDL_ShowWindow(window.window);
SDL_RaiseWindow(window.window);
#endif
window.window_hidden = false;
@@ -287,6 +287,10 @@ static void VulkanFindPhysicalDevice(vk::Instance instance, vk::SurfaceKHR surfa
LOGF("shaderStorageImageReadWithoutFormat is not supported\n");
skip_device = true;
}
if (features12.shaderStorageImageArrayNonUniformIndexing != VK_TRUE) {
LOGF("shaderStorageImageArrayNonUniformIndexing is not supported\n");
skip_device = true;
}
if (device_features2.features.shaderImageGatherExtended != VK_TRUE) {
LOGF("shaderImageGatherExtended is not supported\n");
@@ -514,6 +518,7 @@ static vk::Device VulkanCreateDevice(vk::PhysicalDevice physical_device, const V
features12.sType = vk::StructureType::ePhysicalDeviceVulkan12Features;
features12.pNext = &depth_clip_control;
features12.samplerMirrorClampToEdge = VK_TRUE;
features12.shaderStorageImageArrayNonUniformIndexing = VK_TRUE;
vk::PhysicalDeviceSubgroupSizeControlFeatures subgroup_size_control {};
subgroup_size_control.sType = vk::StructureType::ePhysicalDeviceSubgroupSizeControlFeatures;
+36 -48
View File
@@ -32,9 +32,7 @@
#include "graphics/host_gpu/vma.h"
#include "graphics/host_gpu/vulkanCommon.h"
#include "graphics/presentation/renderDoc.h"
#include "graphics/presentation/window/hostInput.h"
#include "graphics/presentation/window/windowInternal.h"
#include "kytyGitVersion.h"
#include "libs/controller.h"
#include "loader/systemContent.h"
@@ -60,6 +58,8 @@
namespace Libs::Graphics {
constexpr int KEYBOARD_CONTROLLER_ID = -1000;
struct EventKeyboard {
bool down;
bool up;
@@ -72,6 +72,26 @@ struct EventKeyboard {
double timestamp_seconds;
};
static uint32_t KeyboardKeyToPadButton(int key_code) {
switch (key_code) {
case SDLK_w: return Controller::PAD_BUTTON_UP;
case SDLK_a: return Controller::PAD_BUTTON_LEFT;
case SDLK_s: return Controller::PAD_BUTTON_DOWN;
case SDLK_d: return Controller::PAD_BUTTON_RIGHT;
case SDLK_j: return Controller::PAD_BUTTON_CROSS;
case SDLK_i: return Controller::PAD_BUTTON_TRIANGLE;
case SDLK_k: return Controller::PAD_BUTTON_SQUARE;
case SDLK_l: return Controller::PAD_BUTTON_CIRCLE;
case SDLK_q: return Controller::PAD_BUTTON_L1;
case SDLK_e: return Controller::PAD_BUTTON_R1;
case SDLK_RETURN:
case SDLK_RETURN2: return Controller::PAD_BUTTON_OPTIONS;
case SDLK_BACKSPACE:
case SDLK_TAB: return Controller::PAD_BUTTON_TOUCH_PAD;
default: return 0;
}
}
static uint32_t ControllerButtonToPadButton(int button) {
switch (button) {
case SDL_CONTROLLER_BUTTON_A: return Controller::PAD_BUTTON_CROSS;
@@ -240,11 +260,16 @@ static void GameEventKeyboard(WindowLoopState& game, const EventKeyboard& key) {
}
}
#endif
if ((key.down || key.up) && !key.repeat) {
HostInputKey(key.key_code, key.down);
const auto button = KeyboardKeyToPadButton(key.key_code);
if (button != 0 && (key.down || key.up) && !key.repeat) {
static bool keyboard_connected = false;
if (!keyboard_connected) {
Controller::ControllerConnect(KEYBOARD_CONTROLLER_ID);
keyboard_connected = true;
}
Controller::ControllerButton(KEYBOARD_CONTROLLER_ID, button, key.down);
}
#endif
}
static void GameEventMouse([[maybe_unused]] const EventMouse& mb) {
@@ -266,23 +291,6 @@ static void GameEventMouse([[maybe_unused]] const EventMouse& mb) {
mb.x, mb.y);
}
#endif
if (mb.down || mb.up) {
uint8_t mouse_button = 0;
if (mb.left) {
mouse_button = SDL_BUTTON_LEFT;
} else if (mb.middle) {
mouse_button = SDL_BUTTON_MIDDLE;
} else if (mb.right) {
mouse_button = SDL_BUTTON_RIGHT;
} else if (mb.x1) {
mouse_button = SDL_BUTTON_X1;
} else if (mb.x2) {
mouse_button = SDL_BUTTON_X2;
}
HostInputMouseButton(mouse_button, mb.down);
}
}
static void GameEventFinger([[maybe_unused]] const EventFinger& f) {
@@ -786,14 +794,10 @@ static void WindowCreate(WindowContext& context) {
if (SDL_InitSubSystem(SDL_INIT_VIDEO | SDL_INIT_GAMECONTROLLER) < 0) {
EXIT("%s\n", SDL_GetError());
}
HostInputInit();
LOGF("WindowCreate(): width = %d, height = %d\n", width, height);
uint32_t window_flags = KYTY_SDL_WINDOW_FLAGS;
if (Config::FullscreenEnabled()) {
window_flags |= static_cast<uint32_t>(SDL_WINDOW_FULLSCREEN_DESKTOP);
}
#if defined(__APPLE__)
// macOS 26 window chrome (CoreUI asset decode, SwiftUI titlebar) has been observed
// throwing NSExceptions under Rosetta during the first CATransaction commit. A
@@ -933,22 +937,6 @@ void WindowContext::UpdateTitle() {
static uint64_t fps_frames = 0;
static double current_fps = 0.0;
#if KYTY_BUILD == KYTY_BUILD_DEBUG
static constexpr auto build_type = "Debug";
#elif KYTY_BUILD == KYTY_BUILD_RELEASE
static constexpr auto build_type = "Release";
#else
static constexpr auto build_type = "Unknown";
#endif
#if defined(KYTY_OFFICIAL_BUILD)
static constexpr auto build_label = "Official build " KYTY_RELEASE_TAG;
#elif defined(KYTY_FORK_BUILD)
static constexpr auto build_label = "Fork build " KYTY_BUILD_REPOSITORY " " KYTY_GIT_HASH;
#else
static constexpr auto build_label = "Source build " KYTY_GIT_HASH;
#endif
const auto now = Common::Timer::QueryPerformanceCounter();
const auto frequency = Common::Timer::QueryPerformanceFrequency();
frame_num++;
@@ -960,11 +948,11 @@ void WindowContext::UpdateTitle() {
fps_frames = 0;
}
auto fps = fmt::format("[{} | {}] {}{}{}{}{}{}[{}] [{}], frame: {}, fps: {:f}", build_label,
build_type, (has_title ? title : ""), (has_title ? ", " : ""),
(has_title_id ? title_id : ""), (has_title_id ? ", " : ""),
(has_app_ver ? app_ver : ""), (has_app_ver ? " " : ""), device_name,
processor_name, frame_num, current_fps);
auto fps =
fmt::format("{}{}{}{}{}{}[{}] [{}], frame: {}, fps: {:f}", (has_title ? title : ""),
(has_title ? ", " : ""), (has_title_id ? title_id : ""),
(has_title_id ? ", " : ""), (has_app_ver ? app_ver : ""),
(has_app_ver ? " " : ""), device_name, processor_name, frame_num, current_fps);
#if defined(__APPLE__)
// AppKit traps on title changes off the main thread; fire-and-forget keeps present pacing.
@@ -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(uint32_t version): m_version(version) {
Builder::Builder() {
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(m_version);
module.push_back(0x00010300u);
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:
explicit Builder(uint32_t version = 0x00010300u);
Builder();
~Builder() = default;
KYTY_CLASS_DEFAULT_COPY(Builder);
@@ -39,7 +39,6 @@ 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;
@@ -237,8 +237,14 @@ bool ValidateNativeProgram(const IR::Program& program, std::string* error) {
if (!ImageBinding(program.info.images[i], kind)) {
return Fail(error, "native shader plan has an invalid image class");
}
const auto bindings = program.info.images[i].NumBindings();
if (bindings == 0 || bindings > IR::ImageResource::MaxMipLevels) {
return Fail(error, "native shader plan has an invalid image descriptor count");
}
present[static_cast<size_t>(kind)] = true;
expected[static_cast<size_t>(kind)].push_back(i);
for (uint32_t binding = 0; binding < bindings; binding++) {
expected[static_cast<size_t>(kind)].push_back(i);
}
}
if (!program.info.samplers.empty()) {
Expect(Kind::Samplers, Dense(program.info.samplers.size()));
@@ -316,6 +322,11 @@ bool ValidateNativeProgram(const IR::Program& program, std::string* error) {
inst.memory.image_dimension)) {
return Fail(error, "image instruction has an invalid dense resource");
}
if (inst.op == IR::Opcode::ImageStore &&
((program.info.images[inst.memory.resource].mip_mode ==
IR::ImageMipMode::DynamicStorage) != inst.memory.image_has_mip)) {
return Fail(error, "storage image mip mode does not match the instruction");
}
const bool address =
inst.op == IR::Opcode::SLoadDword || memory == IR::ResourceKind::Flat ||
memory == IR::ResourceKind::Global || memory == IR::ResourceKind::Scratch;
@@ -7,30 +7,51 @@ 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) {
if (state.stage != ShaderType::Pixel || ps == nullptr || attr >= ps->input_num) {
return attr;
}
return ShaderPixelParameterMappedLocation(*ps, 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;
}
uint32_t PixelParameterLocation(const EmitterState& state, uint32_t attr) {
std::array<uint32_t, 32> active_inputs {};
uint32_t active_count = 0;
bool used_locations[32] = {};
for (const auto& input: state.inputs) {
if (input.kind == IR::StageInputKind::Parameter) {
active_inputs[active_count++] = input.location;
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;
}
}
return state.stage == ShaderType::Pixel && state.pixel_input_info != nullptr
? ShaderPixelParameterLocation(*state.pixel_input_info,
{active_inputs.data(), active_count}, attr)
: attr;
return PixelParameterMappedLocation(state, attr);
}
bool PixelParameterIsFlat(const EmitterState& state, uint32_t attr) {
const auto* ps = state.pixel_input_info;
return state.stage == ShaderType::Pixel && ps != nullptr &&
ShaderPixelParameterIsFlat(*ps, attr);
if (state.stage != ShaderType::Pixel || ps == nullptr || attr >= ps->input_num) {
return false;
}
return (ps->interpolator_settings[attr] & PsInputFlatShade) != 0;
}
void SetError(std::string* error, const char* message) {
@@ -539,12 +560,21 @@ uint32_t DescriptorElementPointer(EmitterState& state, uint32_t result_ptr_type,
uint32_t variable_id, uint32_t array_index,
IR::DescriptorBindingKind kind, uint32_t resource,
const char* variable_name) {
return DescriptorElementPointerId(state, result_ptr_type, variable_id,
ConstantU32(state, array_index), kind, resource,
variable_name);
}
uint32_t DescriptorElementPointerId(EmitterState& state, uint32_t result_ptr_type,
uint32_t variable_id, uint32_t array_index_id,
IR::DescriptorBindingKind kind, uint32_t resource,
const char* variable_name) {
if (variable_id == 0) {
ExitDescriptorBindingFailure(state, kind, resource, variable_name);
}
const auto pointer = state.builder.AllocateId();
state.builder.AddFunction(
{OpAccessChain, result_ptr_type, pointer, variable_id, ConstantU32(state, array_index)});
{OpAccessChain, result_ptr_type, pointer, variable_id, array_index_id});
return pointer;
}
@@ -33,13 +33,13 @@ uint32_t ConstantImageGatherHorizontalOffsets(EmitterState& state, ImageViewKind
}
uint32_t LoadStorageImageDescriptorAtIndex(EmitterState& state, uint32_t resource,
uint32_t array_index, bool uint_image,
uint32_t array_index_id, bool uint_image,
ImageViewKind view) {
const auto kind = StorageBindingKind(uint_image, view);
const auto& descriptors = state.storage_images[StorageImageIndex(uint_image, view)];
const auto pointer =
DescriptorElementPointer(state, descriptors.pointer_type, descriptors.variable, array_index,
kind, resource, "storage image descriptor array was not emitted");
const auto pointer = DescriptorElementPointerId(
state, descriptors.pointer_type, descriptors.variable, array_index_id, kind, resource,
"storage image descriptor array was not emitted");
const auto image = state.builder.AllocateId();
state.builder.AddFunction({OpLoad, descriptors.image_type, image, pointer});
return image;
@@ -168,12 +168,38 @@ void EmitImageStore(EmitterState& state, const IR::Instruction& inst) {
const auto view = StorageImageViewKind(state, inst.memory, uint_image, inst.pc);
const auto binding =
ResourceForDescriptor(state, StorageBindingKind(uint_image, view), inst.memory.resource);
const auto image = LoadStorageImageDescriptorAtIndex(state, inst.memory.resource,
binding.array_index, uint_image, view);
const auto emit_write = [&](uint32_t descriptor_index, bool non_uniform) {
if (non_uniform) {
state.builder.AddAnnotation({OpDecorate, descriptor_index, DecorationNonUniform});
}
const auto image = LoadStorageImageDescriptorAtIndex(state, inst.memory.resource,
descriptor_index, uint_image, view);
if (non_uniform) {
state.builder.AddAnnotation({OpDecorate, image, DecorationNonUniform});
}
state.builder.AddFunction({OpImageWrite, image, EmitImageCoordU32(state, inst, view),
uint_image ? EmitImageStoreTexelU32(state, inst)
: EmitImageStoreTexelF32(state, inst)});
};
if (!inst.memory.image_has_mip) {
emit_write(ConstantU32(state, binding.array_index), false);
return;
}
const auto& resource = state.program.info.images[inst.memory.resource];
const auto mip = EmitImageMipLodU32(state, inst, inst.src[1], view);
const auto in_range = state.builder.AllocateId();
state.builder.AddFunction(
{OpImageWrite, image, EmitImageCoordU32(state, inst, view),
uint_image ? EmitImageStoreTexelU32(state, inst) : EmitImageStoreTexelF32(state, inst)});
{OpULessThan, state.bool_type, in_range, mip, ConstantU32(state, resource.mip_levels)});
EmitIfCondition(state, in_range, [&] {
auto descriptor_index = mip;
if (binding.array_index != 0) {
descriptor_index = state.builder.AllocateId();
state.builder.AddFunction({OpIAdd, state.uint_type, descriptor_index,
ConstantU32(state, binding.array_index), mip});
}
emit_write(descriptor_index, true);
});
}
void EmitImageSampleResult(EmitterState& state, const IR::Instruction& inst, uint32_t sample,
@@ -23,38 +23,40 @@
namespace Libs::Graphics::ShaderRecompiler::Spirv::Emitter {
enum : uint32_t {
ExecutionModelVertex = 0,
ExecutionModelFragment = 4,
ExecutionModelGLCompute = 5,
ExecutionModeOriginUpperLeft = 7,
ExecutionModeEarlyFragmentTests = 9,
ExecutionModeDepthReplacing = 12,
ExecutionModeLocalSize = 17,
ExecutionModeDerivativeGroupQuadsKHR = 5289,
AddressingModelLogical = 0,
MemoryModelGLSL450 = 1,
CapabilityShader = 1,
CapabilityImageGatherExtended = 25,
CapabilitySampled1D = 43,
CapabilityImage1D = 44,
CapabilityImageQuery = 50,
CapabilityStorageImageReadWithoutFormat = 55,
CapabilityStorageImageWriteWithoutFormat = 56,
CapabilityGroupNonUniform = 61,
CapabilityGroupNonUniformBallot = 64,
CapabilityGroupNonUniformShuffle = 65,
CapabilityComputeDerivativeGroupQuadsKHR = 5288,
StorageClassUniformConstant = 0,
StorageClassInput = 1,
StorageClassOutput = 3,
StorageClassWorkgroup = 4,
StorageClassFunction = 7,
StorageClassPushConstant = 9,
StorageClassImage = 11,
StorageClassStorageBuffer = 12,
FunctionControlNone = 0,
SelectionControlNone = 0,
LoopControlNone = 0,
ExecutionModelVertex = 0,
ExecutionModelFragment = 4,
ExecutionModelGLCompute = 5,
ExecutionModeOriginUpperLeft = 7,
ExecutionModeEarlyFragmentTests = 9,
ExecutionModeDepthReplacing = 12,
ExecutionModeLocalSize = 17,
ExecutionModeDerivativeGroupQuadsKHR = 5289,
AddressingModelLogical = 0,
MemoryModelGLSL450 = 1,
CapabilityShader = 1,
CapabilityImageGatherExtended = 25,
CapabilitySampled1D = 43,
CapabilityImage1D = 44,
CapabilityImageQuery = 50,
CapabilityStorageImageReadWithoutFormat = 55,
CapabilityStorageImageWriteWithoutFormat = 56,
CapabilityGroupNonUniform = 61,
CapabilityGroupNonUniformBallot = 64,
CapabilityGroupNonUniformShuffle = 65,
CapabilityShaderNonUniform = 5301,
CapabilityStorageImageArrayNonUniformIndexing = 5309,
CapabilityComputeDerivativeGroupQuadsKHR = 5288,
StorageClassUniformConstant = 0,
StorageClassInput = 1,
StorageClassOutput = 3,
StorageClassWorkgroup = 4,
StorageClassFunction = 7,
StorageClassPushConstant = 9,
StorageClassImage = 11,
StorageClassStorageBuffer = 12,
FunctionControlNone = 0,
SelectionControlNone = 0,
LoopControlNone = 0,
};
enum : uint32_t {
@@ -67,6 +69,7 @@ enum : uint32_t {
DecorationBinding = 33,
DecorationDescriptorSet = 34,
DecorationOffset = 35,
DecorationNonUniform = 5300,
};
enum : uint32_t {
@@ -425,6 +428,9 @@ 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;
@@ -676,6 +682,10 @@ uint32_t DescriptorElementPointer(EmitterState& state, uint32_t result_ptr_type,
uint32_t variable_id, uint32_t array_index,
IR::DescriptorBindingKind kind, uint32_t resource,
const char* variable_name);
uint32_t DescriptorElementPointerId(EmitterState& state, uint32_t result_ptr_type,
uint32_t variable_id, uint32_t array_index_id,
IR::DescriptorBindingKind kind, uint32_t resource,
const char* variable_name);
ImageViewKind SampledImageViewKind(const EmitterState& state, const IR::MemoryInfo& mem,
uint32_t use_pc);
@@ -484,6 +484,13 @@ void EmitHeaderAndTypes(EmitterState& state) {
if (state.needs_image_gather_extended) {
state.builder.AddCapability({CapabilityImageGatherExtended});
}
if (std::any_of(
state.program.info.images.begin(), state.program.info.images.end(),
[](const auto& image) { return image.mip_mode == IR::ImageMipMode::DynamicStorage; })) {
state.builder.AddCapability({CapabilityShaderNonUniform});
state.builder.AddCapability({CapabilityStorageImageArrayNonUniformIndexing});
state.builder.AddExtension("SPV_EXT_descriptor_indexing");
}
if (std::any_of(state.storage_images.begin(),
state.storage_images.begin() + StorageImageViewKindCount,
[](const auto& image) { return image.variable != 0; })) {
@@ -269,7 +269,10 @@ bool AllocateBindings(Program& program, const BindingLayoutOptions& options, std
}
return false;
}
image_groups[static_cast<size_t>(group - ImageBindingKinds.begin())].push_back(i);
auto& resources = image_groups[static_cast<size_t>(group - ImageBindingKinds.begin())];
for (uint32_t binding = 0; binding < program.info.images[i].NumBindings(); binding++) {
resources.push_back(i);
}
}
for (uint32_t i = 0; i < image_groups.size(); i++) {
if (!image_groups[i].empty()) {
@@ -71,6 +71,17 @@ bool DescriptorIsCube(const DescriptorValue& descriptor) {
Prospero::ImageType::kCube;
}
bool DescriptorMipRange(const DescriptorValue& descriptor, uint32_t& count) {
const auto base_level = (descriptor.dwords[3] >> 12u) & 0xfu;
const auto last_level = (descriptor.dwords[3] >> 16u) & 0xfu;
const auto max_mip = (descriptor.dwords[5] >> 4u) & 0xfu;
if (base_level > last_level || last_level > max_mip) {
return false;
}
count = last_level - base_level + 1u;
return true;
}
bool DecodeBufferDescriptor(const DescriptorValue& descriptor, ShaderBufferResource& result) {
if (descriptor.dword_count != std::size(result.fields)) {
return false;
@@ -196,6 +207,21 @@ bool ValidateResourceSpecialization(const Program& program, const ResourceSnapsh
}
continue;
}
if (image.mip_mode == ImageMipMode::DynamicStorage) {
uint32_t mip_levels = 0;
if (!DescriptorMipRange(descriptor, mip_levels) || mip_levels != image.mip_levels) {
if (error != nullptr) {
*error = fmt::format(
"image descriptor {} no longer matches specialized storage mip count", i);
}
return false;
}
} else if (image.mip_levels != 1u) {
if (error != nullptr) {
*error = fmt::format("image descriptor {} has invalid non-storage mip count", i);
}
return false;
}
const auto dimension = DescriptorDimension(descriptor, image.dimension);
if (dimension == Decoder::ImageDimension::Unknown || dimension != image.dimension ||
DescriptorIsCube(descriptor) != image.cube) {
@@ -229,12 +255,8 @@ bool ValidateResourceSpecialization(const Program& program, const ResourceSnapsh
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: "
"{: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 format", i);
}
return false;
}
@@ -383,8 +405,9 @@ bool SpecializeResources(Program& program, const ResourceSnapshot& snapshot, std
const auto& descriptor = snapshot.images[i];
auto& image = next.images[i];
if (NullImageDescriptor(descriptor)) {
image.dimension = Decoder::ImageDimension::Dim2D;
image.cube = false;
image.dimension = Decoder::ImageDimension::Dim2D;
image.cube = false;
image.mip_levels = 1;
switch (image.kind) {
case ResourceKind::ImageUint: image.kind = ResourceKind::Image; break;
case ResourceKind::StorageImageUint:
@@ -396,6 +419,16 @@ bool SpecializeResources(Program& program, const ResourceSnapshot& snapshot, std
}
continue;
}
if (image.mip_mode == ImageMipMode::DynamicStorage) {
if (!DescriptorMipRange(descriptor, image.mip_levels)) {
if (error != nullptr) {
*error = fmt::format("image descriptor {} has invalid storage mip range", i);
}
return false;
}
} else {
image.mip_levels = 1;
}
const auto descriptor_dimension = DescriptorDimension(descriptor, image.dimension);
if (descriptor_dimension == Decoder::ImageDimension::Unknown) {
if (error != nullptr) {
@@ -598,11 +598,14 @@ enum class ImageMipMode { None, DynamicStorage };
constexpr uint32_t StorageImageIdentitySwizzle = 0x00000facu;
struct ImageResource {
static constexpr uint32_t MaxMipLevels = 16;
uint32_t source = 0;
uint32_t first_use_pc = 0;
ResourceKind kind = ResourceKind::None;
Decoder::ImageDimension dimension = Decoder::ImageDimension::Unknown;
ImageMipMode mip_mode = ImageMipMode::None;
uint32_t mip_levels = 1;
uint32_t storage_swizzle = StorageImageIdentitySwizzle;
bool read = false;
bool written = false;
@@ -610,6 +613,10 @@ struct ImageResource {
bool depth_compare = false;
bool cube = false;
[[nodiscard]] uint32_t NumBindings() const {
return mip_mode == ImageMipMode::DynamicStorage ? mip_levels : 1u;
}
bool operator==(const ImageResource& other) const = default;
};
-397
View File
@@ -1,397 +0,0 @@
#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
-22
View File
@@ -1,22 +0,0 @@
#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_
+1 -37
View File
@@ -45,42 +45,6 @@
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;
@@ -1054,6 +1018,7 @@ static void ShaderAppendNativeSpecialization(std::vector<uint32_t>&
ids.push_back(static_cast<uint32_t>(image.kind));
ids.push_back(static_cast<uint32_t>(image.dimension));
ids.push_back(static_cast<uint32_t>(image.mip_mode));
ids.push_back(image.mip_levels);
ids.push_back(image.storage_swizzle);
}
ids.push_back(static_cast<uint32_t>(program.info.addresses.size()));
@@ -1642,7 +1607,6 @@ 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));
-5
View File
@@ -122,11 +122,6 @@ 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;
-52
View File
@@ -64,7 +64,6 @@ 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;
@@ -130,7 +129,6 @@ 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;
@@ -276,10 +274,6 @@ static std::filesystem::path ResolvePathIgnoringCase(const std::filesystem::path
return resolved;
}
#else
static bool HasWindowsForbiddenFilenameCharacter(const std::string& relative_path) {
return relative_path.find_first_of("<>:\"|?*") != std::string::npos;
}
#endif
std::filesystem::path MountPoints::GetRealFilename(const std::string& mounted_file_name) {
@@ -299,10 +293,6 @@ std::filesystem::path MountPoints::GetRealFilename(const std::string& mounted_fi
rel_path = Common::RemoveFirst(rel_path, 1);
}
#if KYTY_PLATFORM == KYTY_PLATFORM_WINDOWS
if (HasWindowsForbiddenFilenameCharacter(rel_path)) {
::printf("FileSystem: Windows-incompatible guest filename: %s\n",
mounted_file_name.c_str());
}
return p.dir / rel_path;
#else
return ResolvePathIgnoringCase(p.dir / rel_path);
@@ -418,7 +408,6 @@ 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;
@@ -964,47 +953,6 @@ 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();
-1
View File
@@ -48,7 +48,6 @@ 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);
+1 -1
View File
@@ -65,7 +65,7 @@ constexpr uint64_t PAGE_TABLE_POOL_SIZE = 4ull * 1024ull * 1024ull * 1024ull;
constexpr uint64_t PAGE_TABLE_GRANULARITY = 2ull * 1024ull * 1024ull;
constexpr int PAGE_TABLE_POOL_ENTRIES =
static_cast<int>(PAGE_TABLE_POOL_SIZE / PAGE_TABLE_GRANULARITY);
constexpr uint64_t DEFAULT_FLEXIBLE_MEMORY_SIZE = 1ull * 1024ull * 1024ull * 1024ull;
constexpr uint64_t DEFAULT_FLEXIBLE_MEMORY_SIZE = 4ull * 1024ull * 1024ull * 1024ull;
static uint64_t g_flexible_memory_size = DEFAULT_FLEXIBLE_MEMORY_SIZE;
static bool g_flexible_memory_size_frozen = false;
+50 -41
View File
@@ -365,7 +365,6 @@ struct PthreadAttrPrivate {
uint64_t stack_map_addr;
size_t stack_map_size;
int policy;
int guest_priority;
int inherit_sched;
int solosched;
bool detached;
@@ -876,11 +875,9 @@ 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 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;
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);
auto* guest_root_frame = reinterpret_cast<uintptr_t*>(guest_rbp);
guest_root_frame[0] = 0;
@@ -2131,8 +2128,13 @@ int KYTY_SYSV_ABI PthreadAttrGetschedparam(const PthreadAttr* attr, KernelSchedP
int result = pthread_attr_getschedparam(&(*attr)->p, param);
// Host priority mapping is lossy; return the exact guest value.
param->sched_priority = (*attr)->guest_priority;
if (param->sched_priority <= -2) {
param->sched_priority = 767;
} else if (param->sched_priority >= +2) {
param->sched_priority = 256;
} else {
param->sched_priority = 700;
}
if (result == 0) {
return OK;
@@ -2296,7 +2298,6 @@ 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;
@@ -2306,13 +2307,12 @@ int KYTY_SYSV_ABI PthreadAttrSetschedparam(PthreadAttr* attr, const KernelSchedP
pparam.sched_priority = 0;
}
if (pthread_attr_setschedparam(&attr_value->p, &pparam) != 0) {
return KERNEL_ERROR_EINVAL;
}
#endif
int result = pthread_attr_setschedparam(&attr_value->p, &pparam);
attr_value->guest_priority = param->sched_priority;
return OK;
if (result == 0) {
return OK;
}
return KERNEL_ERROR_EINVAL;
}
int KYTY_SYSV_ABI PthreadAttrSetschedpolicy(PthreadAttr* attr, int policy) {
@@ -3639,15 +3639,26 @@ int KYTY_SYSV_ABI PthreadGetprio(Pthread thread, int* prio) {
EXIT_NOT_IMPLEMENTED(prio == nullptr);
sched_param native_param {};
int native_policy = 0;
if (pthread_getschedparam(thread->p, &native_policy, &native_param) != 0) {
return KERNEL_ERROR_EINVAL;
sched_param param {};
int pol = 0;
int result = pthread_getschedparam(thread->p, &pol, &param);
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;
}
*prio = thread->attr->guest_priority;
LOGF("\t PthreadGetprio: %d, %d\n", thread->unique_id, *prio);
return OK;
return KERNEL_ERROR_EINVAL;
}
int KYTY_SYSV_ABI PthreadSetprio(Pthread thread, int prio) {
@@ -3662,27 +3673,25 @@ int KYTY_SYSV_ABI PthreadSetprio(Pthread thread, int prio) {
int result = pthread_getschedparam(thread->p, &pol, &param);
if (result != 0) {
return KERNEL_ERROR_EINVAL;
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, &param);
if (result == 0) {
LOGF("\t PthreadSetprio: %d, %d\n", thread->unique_id, prio);
return OK;
}
}
#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, &param) != 0) {
return KERNEL_ERROR_EINVAL;
}
#endif
thread->attr->guest_priority = prio;
LOGF("\t PthreadSetprio: %d, %d\n", thread->unique_id, prio);
return OK;
return KERNEL_ERROR_EINVAL;
}
void KYTY_SYSV_ABI PthreadTestcancel() {
-287
View File
@@ -1,287 +0,0 @@
#include "kernel/syncOnAddress.h"
#include "common/threads.h"
#include "libs/errno.h"
#include <algorithm>
#include <chrono>
#include <climits>
#include <cstdint>
#include <list>
#include <memory>
#include <mutex>
#include <unordered_map>
#if KYTY_PLATFORM == KYTY_PLATFORM_LINUX && !defined(__APPLE__)
#include <cerrno>
#include <linux/futex.h>
#include <sys/syscall.h>
#include <unistd.h>
#endif
namespace Libs::LibKernel::SyncOnAddress {
namespace {
constexpr uint32_t SIGNAL_POLL_MICROS = 10000;
using Clock = std::chrono::steady_clock;
template <typename T>
[[nodiscard]] bool IsValidWaitAddress(const volatile T* address) {
return address != nullptr && (reinterpret_cast<uintptr_t>(address) & (alignof(T) - 1u)) == 0;
}
[[nodiscard]] bool IsValidWakeAddress(const volatile void* address) {
return address != nullptr &&
(reinterpret_cast<uintptr_t>(address) & (alignof(uint32_t) - 1u)) == 0;
}
template <typename T>
[[nodiscard]] T ReadWord(const volatile T* address) {
return __atomic_load_n(address, __ATOMIC_ACQUIRE);
}
struct WaitDeadline {
bool finite = false;
Clock::time_point end {};
};
[[nodiscard]] WaitDeadline MakeDeadline(const uint32_t* timeout_micros) {
if (timeout_micros == nullptr) {
return {};
}
return {true, Clock::now() + std::chrono::microseconds(*timeout_micros)};
}
[[nodiscard]] uint32_t GetWaitSliceMicros(const WaitDeadline& deadline, bool first_wait) {
if (!deadline.finite) {
return SIGNAL_POLL_MICROS;
}
const auto now = Clock::now();
if (now >= deadline.end) {
return first_wait ? 0u : UINT32_MAX;
}
const auto remaining =
std::chrono::duration_cast<std::chrono::microseconds>(deadline.end - now).count();
return static_cast<uint32_t>(std::min<int64_t>(remaining, SIGNAL_POLL_MICROS));
}
void PollSignals(signal_poll_func_t signal_poll) {
if (signal_poll != nullptr) {
signal_poll();
}
}
#if KYTY_PLATFORM == KYTY_PLATFORM_LINUX && !defined(__APPLE__)
template <typename T>
int WaitLinux(volatile T* address, T expected, const uint32_t* timeout_micros,
signal_poll_func_t signal_poll) {
const auto deadline = MakeDeadline(timeout_micros);
bool first_wait = true;
for (;;) {
if (ReadWord(address) != expected) {
return OK;
}
const auto slice_micros = GetWaitSliceMicros(deadline, first_wait);
if (slice_micros == UINT32_MAX) {
return ReadWord(address) == expected ? KERNEL_ERROR_ETIMEDOUT : OK;
}
const timespec timeout = {
.tv_sec = static_cast<time_t>(slice_micros / 1000000u),
.tv_nsec = static_cast<long>(slice_micros % 1000000u) * 1000L,
};
long result = 0;
int wait_error = 0;
result = syscall(SYS_futex, const_cast<T*>(address), FUTEX_WAIT_PRIVATE,
static_cast<uint32_t>(expected), &timeout, nullptr, 0);
if (result != 0) {
wait_error = errno;
}
if (result == 0 || wait_error == EAGAIN) {
return OK;
}
if (wait_error != ETIMEDOUT && wait_error != EINTR) {
return KERNEL_ERROR_EINVAL;
}
PollSignals(signal_poll);
if (deadline.finite && Clock::now() >= deadline.end) {
return ReadWord(address) == expected ? KERNEL_ERROR_ETIMEDOUT : OK;
}
first_wait = false;
}
}
int WakeLinux(volatile void* address, int32_t count) {
const auto result = syscall(SYS_futex, const_cast<void*>(address), FUTEX_WAKE_PRIVATE, count,
nullptr, nullptr, 0);
return result < 0 ? KERNEL_ERROR_EINVAL : OK;
}
#endif
struct PortableWaiter {
Common::CondVar condition;
bool wake_requested = false;
};
struct PortableAddressEntry {
Common::Mutex mutex;
std::list<PortableWaiter*> waiters;
};
struct PortableAddressRegistry {
std::mutex mutex;
std::unordered_map<uintptr_t, std::shared_ptr<PortableAddressEntry>> entries;
};
PortableAddressRegistry& GetPortableRegistry() {
static PortableAddressRegistry registry;
return registry;
}
std::shared_ptr<PortableAddressEntry> RegisterPortableWaiter(volatile void* address,
PortableWaiter* waiter) {
auto& registry = GetPortableRegistry();
std::lock_guard registry_lock(registry.mutex);
auto& entry = registry.entries[reinterpret_cast<uintptr_t>(address)];
if (!entry) {
entry = std::make_shared<PortableAddressEntry>();
}
entry->mutex.Lock();
entry->waiters.push_back(waiter);
return entry;
}
void UnregisterPortableWaiter(volatile void* address,
const std::shared_ptr<PortableAddressEntry>& entry,
PortableWaiter* waiter) {
entry->waiters.remove(waiter);
const bool empty = entry->waiters.empty();
entry->mutex.Unlock();
if (!empty) {
return;
}
auto& registry = GetPortableRegistry();
std::lock_guard registry_lock(registry.mutex);
entry->mutex.Lock();
const auto it = registry.entries.find(reinterpret_cast<uintptr_t>(address));
if (it != registry.entries.end() && it->second == entry && entry->waiters.empty()) {
registry.entries.erase(it);
}
entry->mutex.Unlock();
}
template <typename T>
int WaitPortable(volatile T* address, T expected, const uint32_t* timeout_micros,
signal_poll_func_t signal_poll) {
PortableWaiter waiter;
auto entry = RegisterPortableWaiter(address, &waiter);
const auto deadline = MakeDeadline(timeout_micros);
bool first_wait = true;
int result = OK;
while (ReadWord(address) == expected && !waiter.wake_requested) {
const auto slice_micros = GetWaitSliceMicros(deadline, first_wait);
if (slice_micros == UINT32_MAX) {
result = KERNEL_ERROR_ETIMEDOUT;
break;
}
if (slice_micros == 0) {
result = KERNEL_ERROR_ETIMEDOUT;
break;
}
(void)waiter.condition.WaitFor(&entry->mutex, slice_micros);
entry->mutex.Unlock();
PollSignals(signal_poll);
entry->mutex.Lock();
if (deadline.finite && Clock::now() >= deadline.end && ReadWord(address) == expected &&
!waiter.wake_requested) {
result = KERNEL_ERROR_ETIMEDOUT;
break;
}
first_wait = false;
}
UnregisterPortableWaiter(address, entry, &waiter);
return result;
}
int WakePortable(volatile void* address, int32_t count) {
if (count == 0) {
return OK;
}
auto& registry = GetPortableRegistry();
std::unique_lock registry_lock(registry.mutex);
const auto it = registry.entries.find(reinterpret_cast<uintptr_t>(address));
if (it == registry.entries.end()) {
return OK;
}
auto entry = it->second;
entry->mutex.Lock();
registry_lock.unlock();
int32_t remaining = count;
for (auto* waiter: entry->waiters) {
if (!waiter->wake_requested) {
waiter->wake_requested = true;
waiter->condition.Signal();
if (remaining != INT_MAX && --remaining == 0) {
break;
}
}
}
entry->mutex.Unlock();
return OK;
}
template <typename T>
int WaitImpl(volatile T* address, T expected, const uint32_t* timeout_micros,
signal_poll_func_t signal_poll) {
if (!IsValidWaitAddress(address)) {
return KERNEL_ERROR_EINVAL;
}
int result = OK;
#if KYTY_PLATFORM == KYTY_PLATFORM_LINUX && !defined(__APPLE__)
result = WaitLinux(address, expected, timeout_micros, signal_poll);
#else
result = WaitPortable(address, expected, timeout_micros, signal_poll);
#endif
PollSignals(signal_poll);
return result;
}
} // namespace
int Wait32(volatile uint32_t* address, uint32_t expected, const uint32_t* timeout_micros,
signal_poll_func_t signal_poll) {
return WaitImpl(address, expected, timeout_micros, signal_poll);
}
int Wait64(volatile uint64_t* address, uint64_t expected, const uint32_t* timeout_micros,
signal_poll_func_t signal_poll) {
return WaitImpl(address, expected, timeout_micros, signal_poll);
}
int Wake(volatile void* address, int32_t count) {
if (!IsValidWakeAddress(address) || count < 0) {
return KERNEL_ERROR_EINVAL;
}
#if KYTY_PLATFORM == KYTY_PLATFORM_LINUX && !defined(__APPLE__)
return WakeLinux(address, count);
#endif
return WakePortable(address, count);
}
} // namespace Libs::LibKernel::SyncOnAddress
-19
View File
@@ -1,19 +0,0 @@
#ifndef EMULATOR_INCLUDE_EMULATOR_KERNEL_SYNC_ON_ADDRESS_H_
#define EMULATOR_INCLUDE_EMULATOR_KERNEL_SYNC_ON_ADDRESS_H_
#include "common/abi.h"
#include "common/common.h"
namespace Libs::LibKernel::SyncOnAddress {
using signal_poll_func_t = void (*)();
int Wait32(volatile uint32_t* address, uint32_t expected, const uint32_t* timeout_micros,
signal_poll_func_t signal_poll = nullptr);
int Wait64(volatile uint64_t* address, uint64_t expected, const uint32_t* timeout_micros,
signal_poll_func_t signal_poll = nullptr);
int Wake(volatile void* address, int32_t count);
} // namespace Libs::LibKernel::SyncOnAddress
#endif /* EMULATOR_INCLUDE_EMULATOR_KERNEL_SYNC_ON_ADDRESS_H_ */
+1 -2
View File
@@ -1,2 +1 @@
#define KYTY_GIT_VERSION "@KYTY_GIT_VERSION@"
#define KYTY_GIT_HASH "@KYTY_GIT_HASH@"
#define KYTY_GIT_VERSION "@KYTY_GIT_VERSION@"
+5 -5
View File
@@ -16,7 +16,6 @@ file(GLOB launcher_forms
add_executable(launcher WIN32
${launcher_src}
${launcher_forms}
resources.qrc
)
@@ -38,6 +37,7 @@ elseif(LINUX)
set_target_properties(launcher PROPERTIES LINK_FLAGS "${KYTY_LD_OPTIONS}")
endif()
add_dependencies(launcher KytyGitVersion)
add_dependencies(launcher kyty_emulator)
if(KYTY_CLANG_CL)
@@ -141,14 +141,14 @@ elseif(LINUX)
endif()
set(tidy_dirs "${CMAKE_CURRENT_SOURCE_DIR}/include")
set(iwyu_maps "${CMAKE_CURRENT_SOURCE_DIR}/utils/qt6_16.imp")
set(tidy_dirs "${CMAKE_SOURCE_DIR}/launcher/include")
set(iwyu_maps "${CMAKE_SOURCE_DIR}/launcher/utils/qt6_16.imp")
get_property(inc_headers TARGET launcher PROPERTY INCLUDE_DIRECTORIES)
list(APPEND inc_headers
${CMAKE_CURRENT_SOURCE_DIR}
${CMAKE_CURRENT_BINARY_DIR}/launcher_autogen/include
${CMAKE_SOURCE_DIR}/launcher
${CMAKE_BINARY_DIR}/launcher/launcher_autogen/include
${Qt6Widgets_INCLUDE_DIRS}
${Qt6Core_INCLUDE_DIRS}
${Qt6Gui_INCLUDE_DIRS}
+21 -32
View File
@@ -7,7 +7,7 @@
<x>0</x>
<y>0</y>
<width>560</width>
<height>450</height>
<height>420</height>
</rect>
</property>
<property name="windowTitle">
@@ -78,13 +78,16 @@
</property>
</widget>
</item>
<item row="2" column="0">
<widget class="QCheckBox" name="checkBox_fullscreen">
<item row="2" column="0" colspan="2">
<widget class="QCheckBox" name="checkBox_ngg_rectlist_draw">
<property name="toolTip">
<string>Run in borderless desktop fullscreen</string>
<string>Use the NGG 4-vertex path for rect-list DrawIndexAuto primitive 7</string>
</property>
<property name="text">
<string>Fullscreen</string>
<string>Use NGG rect-list draw</string>
</property>
<property name="checked">
<bool>true</bool>
</property>
</widget>
</item>
@@ -129,55 +132,41 @@
</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="4" column="1">
<item row="3" 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="5" column="0">
<item row="4" column="0">
<widget class="QLabel" name="label_21">
<property name="text">
<string>Shader log direction:</string>
</property>
</widget>
</item>
<item row="5" column="1">
<item row="4" 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="6" column="0">
<item row="5" column="0">
<widget class="QLabel" name="label_22">
<property name="text">
<string>Shader log folder:</string>
</property>
</widget>
</item>
<item row="6" column="1">
<item row="5" column="1">
<widget class="MandatoryLineEdit" name="lineEdit_shader_log_folder">
<property name="toolTip">
<string>Specify directory to dump shaders</string>
@@ -187,14 +176,14 @@
</property>
</widget>
</item>
<item row="7" column="0">
<item row="6" column="0">
<widget class="QLabel" name="label_24">
<property name="text">
<string>Command buffer dump folder:</string>
</property>
</widget>
</item>
<item row="7" column="1">
<item row="6" column="1">
<widget class="MandatoryLineEdit" name="lineEdit_cmd_dump_folder">
<property name="toolTip">
<string>Specify directory to dump command buffers</string>
@@ -204,28 +193,28 @@
</property>
</widget>
</item>
<item row="8" column="0">
<item row="7" column="0">
<widget class="QLabel" name="label_25">
<property name="text">
<string>Printf direction:</string>
</property>
</widget>
</item>
<item row="8" column="1">
<item row="7" 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="9" column="0">
<item row="8" column="0">
<widget class="QLabel" name="label_26">
<property name="text">
<string>Printf output file:</string>
</property>
</widget>
</item>
<item row="9" column="1">
<item row="8" column="1">
<widget class="MandatoryLineEdit" name="lineEdit_printf_file">
<property name="toolTip">
<string>Specify file to dump logs</string>
@@ -235,14 +224,14 @@
</property>
</widget>
</item>
<item row="10" column="0">
<item row="9" column="0">
<widget class="QLabel" name="label_27">
<property name="text">
<string>Profiler direction:</string>
</property>
</widget>
</item>
<item row="10" column="1">
<item row="9" column="1">
<widget class="QComboBox" name="comboBox_profiler_direction">
<property name="toolTip">
<string>Enable or disable profiler. If enabled may decrease emulator performance</string>
@@ -69,31 +69,6 @@
</property>
</widget>
</item>
<item>
<widget class="QToolButton" name="input_mapping_button">
<property name="minimumSize">
<size>
<width>36</width>
<height>36</height>
</size>
</property>
<property name="toolTip">
<string>Edit input mapping</string>
</property>
<property name="text">
<string/>
</property>
<property name="iconSize">
<size>
<width>32</width>
<height>32</height>
</size>
</property>
<property name="autoRaise">
<bool>true</bool>
</property>
</widget>
</item>
<item>
<widget class="QToolButton" name="edit_button">
<property name="minimumSize">
@@ -1,7 +0,0 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 32 32">
<g fill="none" stroke="#e8f1ff" stroke-linecap="round" stroke-linejoin="round" stroke-width="2">
<path d="M8 4h11l5 5v7M24 9h-5V4M16 27H8a2 2 0 0 1-2-2V6a2 2 0 0 1 2-2"/>
<path d="m15 24 1-4 8-8 4 4-8 8-4 1Z"/>
<path d="m22 14 4 4" opacity=".8"/>
</g>
</svg>

Before

Width:  |  Height:  |  Size: 336 B

-8
View File
@@ -1,8 +0,0 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 32 32">
<g fill="none" stroke="#e8f1ff" stroke-linecap="round" stroke-linejoin="round" stroke-width="2">
<path d="M5 8h3M14 8h13M5 16h13M24 16h3M5 24h6M17 24h10"/>
<circle cx="11" cy="8" r="3"/>
<circle cx="21" cy="16" r="3"/>
<circle cx="14" cy="24" r="3"/>
</g>
</svg>

Before

Width:  |  Height:  |  Size: 344 B

-9
View File
@@ -1,9 +0,0 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 32 32">
<g fill="none" stroke="#e8f1ff" stroke-linecap="round" stroke-linejoin="round" stroke-width="2">
<path d="M10 10h12c3.6 0 5.8 2.3 6.6 6.8l.9 5.1c.4 2.4-.8 4.1-2.8 4.1-1.4 0-2.5-.7-3.6-2.3L21.3 21H10.7l-1.8 2.7C7.8 25.3 6.7 26 5.3 26c-2 0-3.2-1.7-2.8-4.1l.9-5.1C4.2 12.3 6.4 10 10 10Z"/>
<path d="M8 16h6M11 13v6"/>
<circle cx="22" cy="15" r="1" fill="#e8f1ff" stroke="none"/>
<circle cx="25" cy="18" r="1" fill="#e8f1ff" stroke="none"/>
<path d="M13 7h6M16 4v6" opacity=".8"/>
</g>
</svg>

Before

Width:  |  Height:  |  Size: 574 B

@@ -1,6 +0,0 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 32 32">
<g fill="none" stroke="#e8f1ff" stroke-linecap="round" stroke-linejoin="round" stroke-width="2">
<path d="M6 9h20M12 9V5h8v4M9 9l1 18h12l1-18"/>
<path d="M14 14v8M18 14v8" opacity=".8"/>
</g>
</svg>

Before

Width:  |  Height:  |  Size: 272 B

+6 -19
View File
@@ -48,9 +48,6 @@ 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,
@@ -85,9 +82,7 @@ public:
QString game_comment;
Resolution screen_resolution = Resolution::R1280X720;
bool fullscreen_enabled = false;
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;
@@ -99,15 +94,13 @@ public:
QString printf_output_file = "_kyty.txt";
ProfilerDirection profiler_direction = ProfilerDirection::None;
bool renderdoc_enabled = false;
QStringList host_input_mapping;
bool ngg_rectlist_draw_enabled = true;
QString elf = QStringLiteral("eboot.bin");
void CopyEmulatorSettingsFrom(const Configuration& other) {
screen_resolution = other.screen_resolution;
fullscreen_enabled = other.fullscreen_enabled;
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;
@@ -119,7 +112,7 @@ public:
printf_output_file = other.printf_output_file;
profiler_direction = other.profiler_direction;
renderdoc_enabled = other.renderdoc_enabled;
host_input_mapping = other.host_input_mapping;
ngg_rectlist_draw_enabled = other.ngg_rectlist_draw_enabled;
}
void CopyFrom(const Configuration& other) {
@@ -142,9 +135,7 @@ public:
KYTY_CFG_SET(game_path);
KYTY_CFG_SET(custom_settings);
KYTY_CFG_SET(screen_resolution);
KYTY_CFG_SET(fullscreen_enabled);
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);
@@ -156,7 +147,7 @@ public:
KYTY_CFG_SET(printf_output_file);
KYTY_CFG_SET(profiler_direction);
KYTY_CFG_SET(renderdoc_enabled);
s->setValue("host_input_mapping", host_input_mapping);
KYTY_CFG_SET(ngg_rectlist_draw_enabled);
KYTY_CFG_SET(elf);
}
@@ -166,12 +157,7 @@ public:
KYTY_CFG_GET(game_path);
KYTY_CFG_GET(custom_settings);
KYTY_CFG_GET(screen_resolution);
KYTY_CFG_GET(fullscreen_enabled);
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);
@@ -183,8 +169,9 @@ public:
KYTY_CFG_GET(printf_output_file);
KYTY_CFG_GET(profiler_direction);
KYTY_CFG_GET(renderdoc_enabled);
host_input_mapping = s->value("host_input_mapping", host_input_mapping).toStringList();
elf = s->value("elf", elf).toString();
ngg_rectlist_draw_enabled =
s->value("ngg_rectlist_draw_enabled", ngg_rectlist_draw_enabled).toBool();
elf = s->value("elf", elf).toString();
}
};
@@ -38,10 +38,7 @@ public:
[[nodiscard]] const QString& GetSettingsFile() const { return m_settings_file; }
[[nodiscard]] const QStringList& GetGameDirectories() const { return m_game_dirs; }
[[nodiscard]] const QStringList& GetHostInputMapping() const {
return m_global_info.host_input_mapping;
}
[[nodiscard]] bool CanViewSelectedTrophies() const;
[[nodiscard]] bool CanViewSelectedTrophies() const;
bool EnsureGameDirectory();
void ScanGameDirectory();
@@ -61,7 +58,6 @@ protected slots:
void edit_configuration();
void delete_configuartion();
void edit_global_settings();
void edit_input_mapping();
void run_configuration();
void list_currentItemChanged(QTreeWidgetItem* current, QTreeWidgetItem* previous);
void list_itemDoubleClicked(QTreeWidgetItem* witem, int column);
-31
View File
@@ -1,31 +0,0 @@
#ifndef LAUNCHER_INCLUDE_INPUT_MAPPING_DIALOG_H_
#define LAUNCHER_INCLUDE_INPUT_MAPPING_DIALOG_H_
#include <QDialog>
#include <QStringList>
class QPushButton;
class QTreeWidget;
class QTreeWidgetItem;
class QWidget;
class InputMappingDialog final: public QDialog {
public:
explicit InputMappingDialog(const QStringList& mapping, QWidget* parent = nullptr);
[[nodiscard]] QStringList Mapping() const;
private:
void ChangeBinding();
void ClearBinding();
void RestoreDefaults();
void SetBinding(QTreeWidgetItem* item, const QString& binding);
void UpdateButtons();
QTreeWidget* m_bindings = nullptr;
QPushButton* m_change_button = nullptr;
QPushButton* m_clear_button = nullptr;
bool m_customized = false;
};
#endif /* LAUNCHER_INCLUDE_INPUT_MAPPING_DIALOG_H_ */
-8
View File
@@ -1,8 +0,0 @@
<RCC>
<qresource prefix="/icons">
<file alias="edit-configuration.svg">icons/edit-configuration.svg</file>
<file alias="global-settings.svg">icons/global-settings.svg</file>
<file alias="input-mapping.svg">icons/input-mapping.svg</file>
<file alias="remove-configuration.svg">icons/remove-configuration.svg</file>
</qresource>
</RCC>
+2 -42
View File
@@ -30,39 +30,6 @@ 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()) {
@@ -153,17 +120,11 @@ static void ListInit(QComboBox* combo, T value) {
void ConfigurationEditDialog::Init(const Configuration& info) {
ListInit(m_ui->comboBox_screen_resolution, info.screen_resolution);
m_ui->checkBox_fullscreen->setChecked(info.fullscreen_enabled);
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);
@@ -280,12 +241,11 @@ void ConfigurationEditDialog::moveEvent(QMoveEvent* event) {
static void UpdateInfo(Configuration& info, Ui::ConfigurationEditDialog& ui) {
info.screen_resolution =
TextToEnum<Configuration::Resolution>(ui.comboBox_screen_resolution->currentText());
info.fullscreen_enabled = ui.checkBox_fullscreen->isChecked();
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>(
+3 -17
View File
@@ -6,7 +6,6 @@
#include "configurationEditDialog.h"
#include "configurationItem.h"
#include "gameListTreeWidget.h"
#include "inputMappingDialog.h"
#include "mainDialog.h"
#include "patchesDialog.h"
#include "trophyViewerDialog.h"
@@ -170,7 +169,6 @@ static void ApplyGameListStyle(Ui::ConfigurationListWidget* ui) {
"QToolButton:disabled { background: transparent; }"));
ui->edit_button->setStyleSheet(ui->global_settings_button->styleSheet());
ui->delete_button->setStyleSheet(ui->global_settings_button->styleSheet());
ui->input_mapping_button->setStyleSheet(ui->global_settings_button->styleSheet());
}
static void AddSaveDataDir(QStringList* dirs, QSet<QString>* seen, const QString& root,
@@ -228,12 +226,10 @@ ConfigurationListWidget::ConfigurationListWidget(QWidget* parent)
m_ui->setupUi(this);
ApplyGameListStyle(m_ui);
m_ui->global_settings_button->setIcon(QIcon(QStringLiteral(":/icons/global-settings.svg")));
m_ui->global_settings_button->setIcon(style()->standardIcon(QStyle::SP_FileDialogDetailedView));
m_ui->global_settings_button->setToolTip(tr("Edit global settings and game folders"));
m_ui->input_mapping_button->setIcon(QIcon(QStringLiteral(":/icons/input-mapping.svg")));
m_ui->input_mapping_button->setToolTip(tr("Edit global input mapping"));
m_ui->edit_button->setIcon(QIcon(QStringLiteral(":/icons/edit-configuration.svg")));
m_ui->delete_button->setIcon(QIcon(QStringLiteral(":/icons/remove-configuration.svg")));
m_ui->edit_button->setIcon(style()->standardIcon(QStyle::SP_FileIcon));
m_ui->delete_button->setIcon(style()->standardIcon(QStyle::SP_DialogDiscardButton));
m_ui->delete_button->setEnabled(false);
m_ui->edit_button->setEnabled(false);
@@ -253,8 +249,6 @@ ConfigurationListWidget::ConfigurationListWidget(QWidget* parent)
connect(m_ui->global_settings_button, &QToolButton::clicked, this,
&ConfigurationListWidget::edit_global_settings);
connect(m_ui->input_mapping_button, &QToolButton::clicked, this,
&ConfigurationListWidget::edit_input_mapping);
connect(m_ui->edit_button, &QToolButton::clicked, this,
&ConfigurationListWidget::edit_configuration);
connect(m_ui->delete_button, &QToolButton::clicked, this,
@@ -723,14 +717,6 @@ void ConfigurationListWidget::edit_global_settings() {
}
}
void ConfigurationListWidget::edit_input_mapping() {
InputMappingDialog dialog(m_global_info.host_input_mapping, this);
if (dialog.exec() == QDialog::Accepted) {
m_global_info.host_input_mapping = dialog.Mapping();
WriteSettings();
}
}
void ConfigurationListWidget::ClearCustomSettings(ConfigurationItem* item) {
delete m_custom_infos.take(item->GetInfo().game_path);
}
-311
View File
@@ -1,311 +0,0 @@
#include "inputMappingDialog.h"
#include <QAbstractItemView>
#include <QDialogButtonBox>
#include <QHBoxLayout>
#include <QHash>
#include <QHeaderView>
#include <QKeyEvent>
#include <QKeySequence>
#include <QLabel>
#include <QMouseEvent>
#include <QPushButton>
#include <QStringList>
#include <QTreeWidget>
#include <QVBoxLayout>
namespace {
constexpr int BINDING_COLUMN = 1;
struct PadButton {
const char* id;
const char* label;
const char* default_binding;
};
constexpr PadButton PAD_BUTTONS[] = {
{"Up", "D-pad Up", "Up"},
{"Down", "D-pad Down", "Down"},
{"Left", "D-pad Left", "Left"},
{"Right", "D-pad Right", "Right"},
{"Triangle", "Triangle", "I"},
{"Circle", "Circle", "L"},
{"Cross", "Cross", "J"},
{"Square", "Square", "K"},
{"L1", "L1", "Q"},
{"R1", "R1", "E"},
{"L2", "L2", ""},
{"R2", "R2", ""},
{"L3", "L3", "Left Shift"},
{"R3", "R3", "Left Ctrl"},
{"Options", "Options", "Return"},
{"TouchPad", "Touch pad", "Backspace"},
};
QString KeypadName(int key) {
if (key >= Qt::Key_0 && key <= Qt::Key_9) {
return QStringLiteral("Keypad %1").arg(key - Qt::Key_0);
}
switch (key) {
case Qt::Key_Return:
case Qt::Key_Enter: return QStringLiteral("Keypad Enter");
case Qt::Key_Slash: return QStringLiteral("Keypad /");
case Qt::Key_Asterisk: return QStringLiteral("Keypad *");
case Qt::Key_Minus: return QStringLiteral("Keypad -");
case Qt::Key_Plus: return QStringLiteral("Keypad +");
case Qt::Key_Period: return QStringLiteral("Keypad .");
case Qt::Key_Equal: return QStringLiteral("Keypad =");
case Qt::Key_Comma: return QStringLiteral("Keypad ,");
default: return {};
}
}
QString KeyName(const QKeyEvent& event) {
const int key = event.key();
if (event.modifiers().testFlag(Qt::KeypadModifier)) {
return KeypadName(key);
}
if (key >= Qt::Key_A && key <= Qt::Key_Z) {
return QChar(key);
}
if (key >= Qt::Key_0 && key <= Qt::Key_9) {
return QChar(key);
}
if (key >= Qt::Key_F1 && key <= Qt::Key_F24) {
return QStringLiteral("F%1").arg(key - Qt::Key_F1 + 1);
}
switch (key) {
case Qt::Key_Return:
case Qt::Key_Enter: return QStringLiteral("Return");
case Qt::Key_Backspace: return QStringLiteral("Backspace");
case Qt::Key_Tab: return QStringLiteral("Tab");
case Qt::Key_Shift: return QStringLiteral("Left Shift");
case Qt::Key_Control: return QStringLiteral("Left Ctrl");
case Qt::Key_Alt: return QStringLiteral("Left Alt");
case Qt::Key_Meta: return QStringLiteral("Left GUI");
case Qt::Key_Insert: return QStringLiteral("Insert");
case Qt::Key_Delete: return QStringLiteral("Delete");
case Qt::Key_Home: return QStringLiteral("Home");
case Qt::Key_End: return QStringLiteral("End");
case Qt::Key_PageUp: return QStringLiteral("PageUp");
case Qt::Key_PageDown: return QStringLiteral("PageDown");
case Qt::Key_Left: return QStringLiteral("Left");
case Qt::Key_Right: return QStringLiteral("Right");
case Qt::Key_Up: return QStringLiteral("Up");
case Qt::Key_Down: return QStringLiteral("Down");
case Qt::Key_CapsLock: return QStringLiteral("CapsLock");
case Qt::Key_NumLock: return QStringLiteral("Numlock");
case Qt::Key_ScrollLock: return QStringLiteral("ScrollLock");
case Qt::Key_Pause: return QStringLiteral("Pause");
case Qt::Key_Print: return QStringLiteral("PrintScreen");
default: break;
}
if (event.modifiers() != Qt::NoModifier) {
return {};
}
const auto name = QKeySequence(key).toString(QKeySequence::PortableText);
return name.size() == 1 ? name : QString();
}
class InputCaptureDialog final: public QDialog {
public:
explicit InputCaptureDialog(QWidget* parent): QDialog(parent) {
setWindowTitle(tr("Set Binding"));
setModal(true);
setMinimumWidth(360);
auto* layout = new QVBoxLayout(this);
m_label = new QLabel(
tr("Press a key or mouse button.\nSpace and F1 are reserved; Esc cancels."), this);
m_label->setAlignment(Qt::AlignCenter);
layout->addWidget(m_label);
}
[[nodiscard]] const QString& Binding() const { return m_binding; }
protected:
void keyPressEvent(QKeyEvent* event) override {
if (event->isAutoRepeat()) {
return;
}
if (event->key() == Qt::Key_Escape) {
reject();
return;
}
if (event->key() == Qt::Key_Space || event->key() == Qt::Key_F1) {
m_label->setText(tr("That key is reserved by the emulator."));
return;
}
m_binding = KeyName(*event);
if (!m_binding.isEmpty()) {
accept();
} else {
m_label->setText(tr("That key is not supported."));
}
}
void mousePressEvent(QMouseEvent* event) override {
switch (event->button()) {
case Qt::LeftButton: m_binding = QStringLiteral("Mouse:Left"); break;
case Qt::RightButton: m_binding = QStringLiteral("Mouse:Right"); break;
case Qt::MiddleButton: m_binding = QStringLiteral("Mouse:Middle"); break;
case Qt::BackButton: m_binding = QStringLiteral("Mouse:X1"); break;
case Qt::ForwardButton: m_binding = QStringLiteral("Mouse:X2"); break;
default: return;
}
accept();
}
private:
QLabel* m_label = nullptr;
QString m_binding;
};
QHash<QString, QString> ParseMapping(const QStringList& mapping) {
QHash<QString, QString> result;
for (const auto& entry: mapping) {
const auto separator = entry.indexOf(QLatin1Char('='));
if (separator > 0 && separator + 1 < entry.size()) {
const auto binding = entry.mid(separator + 1);
for (auto item = result.begin(); item != result.end();) {
if (item.value().compare(binding, Qt::CaseInsensitive) == 0) {
item = result.erase(item);
} else {
++item;
}
}
result.insert(entry.left(separator), binding);
}
}
return result;
}
} // namespace
InputMappingDialog::InputMappingDialog(const QStringList& mapping, QWidget* parent)
: QDialog(parent), m_customized(!mapping.isEmpty()) {
setWindowTitle(tr("Input Mapping"));
resize(440, 520);
auto* layout = new QVBoxLayout(this);
layout->addWidget(new QLabel(tr("Map keyboard or mouse input to DualSense buttons.\n"
"Defaults: left stick W/A/S/D, right stick T/F/G/H."),
this));
m_bindings = new QTreeWidget(this);
m_bindings->setColumnCount(2);
m_bindings->setHeaderLabels({tr("DualSense button"), tr("Host input")});
m_bindings->setRootIsDecorated(false);
m_bindings->setSelectionMode(QAbstractItemView::SingleSelection);
m_bindings->header()->setSectionResizeMode(0, QHeaderView::Stretch);
m_bindings->header()->setSectionResizeMode(1, QHeaderView::Stretch);
layout->addWidget(m_bindings);
const auto parsed = ParseMapping(mapping);
for (const auto& button: PAD_BUTTONS) {
auto* item = new QTreeWidgetItem(m_bindings);
item->setText(0, tr(button.label));
item->setData(0, Qt::UserRole, QString::fromLatin1(button.id));
SetBinding(item, m_customized ? parsed.value(QString::fromLatin1(button.id))
: QString::fromLatin1(button.default_binding));
}
m_bindings->setCurrentItem(m_bindings->topLevelItem(0));
auto* controls = new QHBoxLayout;
m_change_button = new QPushButton(tr("Change..."), this);
m_clear_button = new QPushButton(tr("Clear"), this);
auto* defaults = new QPushButton(tr("Defaults"), this);
controls->addWidget(m_change_button);
controls->addWidget(m_clear_button);
controls->addWidget(defaults);
controls->addStretch();
layout->addLayout(controls);
auto* buttons = new QDialogButtonBox(QDialogButtonBox::Ok | QDialogButtonBox::Cancel, this);
layout->addWidget(buttons);
connect(m_bindings, &QTreeWidget::itemDoubleClicked, this,
[this](QTreeWidgetItem*, int) { ChangeBinding(); });
connect(m_bindings, &QTreeWidget::itemSelectionChanged, this, [this]() { UpdateButtons(); });
connect(m_change_button, &QPushButton::clicked, this, [this]() { ChangeBinding(); });
connect(m_clear_button, &QPushButton::clicked, this, [this]() { ClearBinding(); });
connect(defaults, &QPushButton::clicked, this, [this]() { RestoreDefaults(); });
connect(buttons, &QDialogButtonBox::accepted, this, &QDialog::accept);
connect(buttons, &QDialogButtonBox::rejected, this, &QDialog::reject);
UpdateButtons();
}
QStringList InputMappingDialog::Mapping() const {
QStringList result;
if (!m_customized) {
return result;
}
for (int index = 0; index < m_bindings->topLevelItemCount(); index++) {
const auto* item = m_bindings->topLevelItem(index);
const auto binding = item->data(BINDING_COLUMN, Qt::UserRole).toString();
if (!binding.isEmpty()) {
result.append(item->data(0, Qt::UserRole).toString() + QLatin1Char('=') + binding);
}
}
return result;
}
void InputMappingDialog::ChangeBinding() {
auto* item = m_bindings->currentItem();
if (item == nullptr) {
return;
}
InputCaptureDialog dialog(this);
if (dialog.exec() != QDialog::Accepted) {
return;
}
for (int index = 0; index < m_bindings->topLevelItemCount(); index++) {
auto* other = m_bindings->topLevelItem(index);
if (other != item && other->data(BINDING_COLUMN, Qt::UserRole)
.toString()
.compare(dialog.Binding(), Qt::CaseInsensitive) == 0) {
SetBinding(other, {});
}
}
SetBinding(item, dialog.Binding());
m_customized = true;
}
void InputMappingDialog::ClearBinding() {
SetBinding(m_bindings->currentItem(), {});
m_customized = true;
}
void InputMappingDialog::RestoreDefaults() {
for (int index = 0; index < m_bindings->topLevelItemCount(); index++) {
SetBinding(m_bindings->topLevelItem(index),
QString::fromLatin1(PAD_BUTTONS[index].default_binding));
}
m_customized = false;
}
void InputMappingDialog::SetBinding(QTreeWidgetItem* item, const QString& binding) {
if (item == nullptr) {
return;
}
item->setData(BINDING_COLUMN, Qt::UserRole, binding);
item->setText(BINDING_COLUMN, binding.isEmpty() ? tr("None") : binding);
UpdateButtons();
}
void InputMappingDialog::UpdateButtons() {
if (m_change_button == nullptr) {
return;
}
const auto* item = m_bindings->currentItem();
m_change_button->setEnabled(item != nullptr);
m_clear_button->setEnabled(item != nullptr &&
!item->data(BINDING_COLUMN, Qt::UserRole).toString().isEmpty());
}
+2 -11
View File
@@ -204,11 +204,7 @@ static QStringList CreateEmulatorArgs(const Configuration& info) {
args << "--screen-width" << r.at(0);
args << "--screen-height" << r.at(1);
if (info.fullscreen_enabled) {
args << "--fullscreen";
}
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);
@@ -220,9 +216,7 @@ 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";
for (const auto& binding: info.host_input_mapping) {
args << "--keymap" << binding;
}
args << "--ngg-rectlist-draw" << BoolArg(info.ngg_rectlist_draw_enabled);
if (info.renderdoc_enabled) {
args << "--rd";
}
@@ -441,10 +435,7 @@ void MainDialogPrivate::Run() {
m_running_item->SetRunning(true);
Configuration info;
info.CopyFrom(m_running_item->GetInfo());
info.host_input_mapping = m_ui->widget->GetHostInputMapping();
m_main_dialog->RunInterpreter(&m_process, info);
m_main_dialog->RunInterpreter(&m_process, m_running_item->GetInfo());
Update();
}
+12 -56
View File
@@ -6,7 +6,6 @@
#include "common/logging/log.h"
#include "common/stringUtils.h"
#include "common/virtualMemory.h"
#include "graphics/guest_gpu/command_processor/commandProcessor.h"
#include "graphics/guest_gpu/gpu_defs.h"
#include "graphics/guest_gpu/graphicsRun.h"
#include "graphics/guest_gpu/hardwareContext.h"
@@ -1445,72 +1444,29 @@ int KYTY_SYSV_ABI GraphicsSuspendPoint() {
return OK;
}
[[nodiscard]] static constexpr uint32_t context_state_op_size_dw(uint32_t operation) {
switch (static_cast<ContextStateOperation>(operation)) {
case ContextStateOperation::Clear: return 5;
case ContextStateOperation::Push:
case ContextStateOperation::Pop: return 27;
case ContextStateOperation::PushClear: return 32;
default: return 0;
}
}
uint32_t* KYTY_SYSV_ABI GraphicsDcbContextStateOp(CommandBuffer* buf, uint32_t operation) {
uint32_t* KYTY_SYSV_ABI GraphicsUnknownQj7QZpgr9Uw(CommandBuffer* buf, uint32_t mode,
uint32_t value) {
PRINT_NAME();
LOGF("\t operation = 0x%08" PRIx32 "\n", operation);
// ResetContextState?
LOGF("\t mode = 0x%08" PRIx32 "\n"
"\t value = 0x%08" PRIx32 "\n",
mode, value);
const auto size_dw = context_state_op_size_dw(operation);
if (buf == nullptr || size_dw == 0) {
if (buf == nullptr) {
return nullptr;
}
buf->DbgDump();
uint32_t* first = nullptr;
const auto append = [&](uint32_t segment_size_dw) {
auto* segment = buf->AllocateDW(segment_size_dw);
if (segment == nullptr) {
return false;
}
std::fill_n(segment, segment_size_dw, 0u);
if (first == nullptr) {
first = segment;
segment[0] = KYTY_PM4(segment_size_dw, Pm4::IT_NOP, Pm4::R_CONTEXT_STATE);
segment[1] = operation;
} else {
segment[0] = KYTY_PM4(segment_size_dw, Pm4::IT_NOP, Pm4::R_ZERO);
}
return true;
};
const auto append_save_restore = [&] {
// The native helper reserves 22 dwords, then emits 5-, 8-, and 9-dword packets.
return buf->ReserveDW(22) && append(5) && append(8) && append(9);
};
auto* cmd = buf->AllocateDW(1);
// Preserve native packet sizes, allocation boundaries, Push/Pop ordering, and
// returned first-packet address. The native packets reference process-private state buffers,
// so their net Cx operation is represented by the first HLE packet and type-3 NOPs.
bool complete = false;
switch (static_cast<ContextStateOperation>(operation)) {
case ContextStateOperation::Clear: complete = append(5); break;
case ContextStateOperation::Push:
complete = append_save_restore() && append(3) && append(2);
break;
case ContextStateOperation::Pop:
complete = append(3) && append_save_restore() && append(2);
break;
case ContextStateOperation::PushClear:
complete = append_save_restore() && append(3) && append(2) && append(5);
break;
default: break;
if (cmd == nullptr) {
return nullptr;
}
return complete ? first : nullptr;
}
cmd[0] = 0x80000000u;
uint64_t KYTY_SYSV_ABI GraphicsDcbContextStateOpGetSize(uint32_t operation) {
PRINT_NAME();
return static_cast<uint64_t>(context_state_op_size_dw(operation)) * sizeof(uint32_t);
return cmd;
}
uint64_t KYTY_SYSV_ABI GraphicsGetIsTrinityMode() {
+2 -2
View File
@@ -67,8 +67,8 @@ int KYTY_SYSV_ABI GraphicsUnknownJumpPatchSetTarget(uint32_t* cmd, const volatil
int KYTY_SYSV_ABI GraphicsJumpPatchSetTarget(uint32_t* cmd, const volatile uint32_t* target,
uint32_t size_in_dwords);
int KYTY_SYSV_ABI GraphicsSuspendPoint();
uint32_t* KYTY_SYSV_ABI GraphicsDcbContextStateOp(CommandBuffer* buf, uint32_t operation);
uint64_t KYTY_SYSV_ABI GraphicsDcbContextStateOpGetSize(uint32_t operation);
uint32_t* KYTY_SYSV_ABI GraphicsUnknownQj7QZpgr9Uw(CommandBuffer* buf, uint32_t mode,
uint32_t value);
uint64_t KYTY_SYSV_ABI GraphicsGetIsTrinityMode();
uint32_t KYTY_SYSV_ABI GraphicsDriverGetDefaultOwner();
+4 -2
View File
@@ -85,6 +85,8 @@ private:
static GameController* g_controller = nullptr;
static constexpr int KEYBOARD_CONTROLLER_ID = -1000;
static uint8_t pad_connected_count_to_u8(int connected_count) {
return static_cast<uint8_t>(connected_count > 255 ? 255 : connected_count);
}
@@ -115,7 +117,7 @@ KYTY_SUBSYSTEM_INIT(Controller) {
EXIT_IF(g_controller != nullptr);
g_controller = new GameController;
g_controller->Connect(HOST_INPUT_CONTROLLER_ID);
g_controller->Connect(KEYBOARD_CONTROLLER_ID);
}
KYTY_SUBSYSTEM_UNEXPECTED_SHUTDOWN(Controller) {}
@@ -153,7 +155,7 @@ void GameController::CheckActive() {
if (!m_connected_ids.empty()) {
new_active_id = m_connected_ids[0];
for (const auto id: m_connected_ids) {
if (id != HOST_INPUT_CONTROLLER_ID) {
if (id != KEYBOARD_CONTROLLER_ID) {
new_active_id = id;
break;
}
-2
View File
@@ -9,8 +9,6 @@ namespace Libs::Controller {
KYTY_SUBSYSTEM_DEFINE(Controller);
constexpr int HOST_INPUT_CONTROLLER_ID = -1000;
constexpr uint32_t PAD_BUTTON_L3 = 0x00000002;
constexpr uint32_t PAD_BUTTON_R3 = 0x00000004;
constexpr uint32_t PAD_BUTTON_OPTIONS = 0x00000008;
+1 -2
View File
@@ -178,8 +178,7 @@ LIB_DEFINE(InitGraphicsDriver_1) {
LIB_FUNC("bxca0BK4FNg", Gen5::GraphicsUnknownJumpPatchSetTarget);
LIB_FUNC("2BS4EtAaF28", Gen5::GraphicsJumpPatchSetTarget);
LIB_FUNC("h9z6+0hEydk", Gen5::GraphicsSuspendPoint);
LIB_FUNC("qj7QZpgr9Uw", Gen5::GraphicsDcbContextStateOp);
LIB_FUNC("H6vHS5cidSA", Gen5::GraphicsDcbContextStateOpGetSize);
LIB_FUNC("qj7QZpgr9Uw", Gen5::GraphicsUnknownQj7QZpgr9Uw);
LIB_FUNC("BfBDZGbti7A", Gen5::GraphicsGetIsTrinityMode);
LIB_FUNC("dbOlWdppb4o", LibGen5::Graphics5UnknownDb);
+19 -40
View File
@@ -13,7 +13,6 @@
#include "kernel/memory.h"
#include "kernel/pthread.h"
#include "kernel/semaphore.h"
#include "kernel/syncOnAddress.h"
#include "libs/errno.h"
#include "libs/libs.h"
#include "libs/network.h"
@@ -2022,12 +2021,6 @@ 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);
@@ -2135,37 +2128,26 @@ uint64_t KYTY_SYSV_ABI cfwBSQyr5Ys(uint64_t a1, uint64_t a2, uint64_t a3, uint64
return 0;
}
static void LogExperimentalSyncOnAddress(std::atomic_bool& logged, const char* function_name) {
if (!logged.exchange(true, std::memory_order_relaxed)) {
::printf("WARNING: %s is experimental\n", function_name);
LOGF("WARNING: %s is experimental\n", function_name);
uint64_t KYTY_SYSV_ABI KernelSyncOnAddressV1(uint64_t op, uint64_t address, uint64_t value,
uint64_t size, uint64_t timeout, uint64_t flags) {
static std::atomic_uint32_t log_count = 0;
const auto index = log_count.fetch_add(1, std::memory_order_relaxed);
if (index < 16) {
LOGF("\t libkernel_sync_on_address_v1: op=0x%016" PRIx64 ", address=0x%016" PRIx64
", value=0x%016" PRIx64 ", size=0x%016" PRIx64 ", timeout=0x%016" PRIx64
", flags=0x%016" PRIx64 "\n",
op, address, value, size, timeout, flags);
}
}
int KYTY_SYSV_ABI KernelSyncOnAddressWait(volatile uint32_t* address, uint32_t expected,
const uint32_t* timeout_micros) {
return LibKernel::SyncOnAddress::Wait32(address, expected, timeout_micros,
LibKernel::KernelDispatchPendingSignalForCurrentThread);
}
if (op != 0 && address == 0 && value == 0 && size == 0) {
// This unsupported form is used as a yield/wait by some Unity jobs.
// SleepMicro() uses a sub-millisecond busy wait on Windows, which can
// pin every worker thread when the guest polls this path.
Common::Thread::Sleep(timeout == 0 ? 1 : 2);
}
int KYTY_SYSV_ABI KernelSyncOnAddressWait32(volatile uint32_t* address, uint32_t expected,
const uint32_t* timeout_micros) {
static std::atomic_bool logged {false};
LogExperimentalSyncOnAddress(logged, "sceKernelSyncOnAddressWait32");
return LibKernel::SyncOnAddress::Wait32(address, expected, timeout_micros,
LibKernel::KernelDispatchPendingSignalForCurrentThread);
}
int KYTY_SYSV_ABI KernelSyncOnAddressWait64(volatile uint64_t* address, uint64_t expected,
const uint32_t* timeout_micros) {
static std::atomic_bool logged {false};
LogExperimentalSyncOnAddress(logged, "sceKernelSyncOnAddressWait64");
return LibKernel::SyncOnAddress::Wait64(address, expected, timeout_micros,
LibKernel::KernelDispatchPendingSignalForCurrentThread);
}
int KYTY_SYSV_ABI KernelSyncOnAddressWake(volatile void* address, int32_t count) {
return LibKernel::SyncOnAddress::Wake(address, count);
return 0;
}
LIB_DEFINE(InitLibKernel_1_Posix) {
@@ -2177,7 +2159,6 @@ 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);
@@ -3417,10 +3398,8 @@ LIB_DEFINE(InitLibKernel_1) {
LIB_FUNC("Xjoosiw+XPI", LibKernel::KernelUuidCreate);
LIB_FUNC("DLORcroUqbc", LibKernel::KernelGetOpenPsId);
LIB_FUNC("zE-wXIZjLoM", LibKernel::KernelDebugRaiseExceptionOnReleaseMode);
LIB_FUNC("Hc4CaR6JBL0", Posix::KernelSyncOnAddressWait);
LIB_FUNC("B2n8aDorSH4", Posix::KernelSyncOnAddressWait32);
LIB_FUNC("PZQhiiLXRFs", Posix::KernelSyncOnAddressWait64);
LIB_FUNC("q2y-wDIVWZA", Posix::KernelSyncOnAddressWake);
LIB_FUNC("Hc4CaR6JBL0", Posix::KernelSyncOnAddressV1);
LIB_FUNC("q2y-wDIVWZA", Posix::KernelSyncOnAddressV1);
AddLibkernelUnityFunc(s, "Qhv5ARAoOEc",
reinterpret_cast<uint64_t>(LibKernel::KernelRemoveExceptionHandler),
-5
View File
@@ -97,10 +97,6 @@ int KYTY_SYSV_ABI NetShutdown(int s, int how) {
return FinishSocketCall(Net::Shutdown(s, how));
}
int KYTY_SYSV_ABI NetGetsockname(int s, void* addr, uint32_t* addrlen) {
return FinishSocketCall(Net::Getsockname(s, addr, addrlen));
}
int KYTY_SYSV_ABI NetPoolCreate(const char* name, int size, int flags) {
return NET_CALL(Net::NetPoolCreate(name, size, flags));
}
@@ -200,7 +196,6 @@ LIB_DEFINE(InitNet_1_Net) {
LIB_FUNC("v6M4txecCuo", LibNet::NetEtherNtostr);
LIB_FUNC("6Oc0bLsIYe0", LibNet::NetGetMacAddress);
LIB_FUNC("hLuXdjHnhiI", LibNet::NetGetSockInfo);
LIB_FUNC("hoOAofhhRvE", LibNet::NetGetsockname);
LIB_FUNC("SF47kB2MNTo", LibNet::NetEpollCreate);
LIB_FUNC("ZVw46bsasAk", LibNet::NetEpollControl);
LIB_FUNC("drjIbDbA7UQ", LibNet::NetEpollWait);
+32 -2
View File
@@ -1,7 +1,6 @@
#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"
@@ -26,6 +25,37 @@ 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;
@@ -91,7 +121,7 @@ static int KYTY_SYSV_ABI SystemServiceParamGetInt(int param_id, int* value) {
int v = 0;
switch (param_id) {
case PARAM_ID_LANG: v = static_cast<int>(Config::GetConsoleLanguage()); break;
case PARAM_ID_LANG: v = PARAM_LANG_ENGLISH_US; 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;
+5 -16
View File
@@ -360,10 +360,9 @@ 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 aligned_stack_top =
const auto guest_rsp =
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;
const auto guest_rbp = guest_rsp - 4u * sizeof(uint64_t);
auto* guest_root_frame = reinterpret_cast<uintptr_t*>(guest_rbp);
guest_root_frame[0] = 0;
@@ -508,13 +507,6 @@ 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"
@@ -537,8 +529,6 @@ 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;
@@ -568,10 +558,9 @@ bool TestMainEntryUsesGuestStack() {
constexpr bool teb_ok = true;
#endif
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;
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;
}
bool TestModuleRelocationUsesWritableHostMapping() {
+7 -32
View File
@@ -10,7 +10,6 @@
#include "emulator.h"
#include "kytyGitVersion.h"
#include <charconv>
#include <cstdio>
#include <fmt/format.h>
@@ -47,9 +46,7 @@ static void PrintUsage() {
" --game-patch <json> Validated patch plan to apply before entry.\n");
::printf(" --screen-width <num> Window width. Default: 1280.\n");
::printf(" --screen-height <num> Window height. Default: 720.\n");
::printf(" --fullscreen Run in borderless desktop fullscreen.\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");
@@ -62,9 +59,10 @@ 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(" --keymap <Pad=Key> DualSense mapping; may be repeated.\n");
::printf(" --rd Enable RenderDoc capture.\n");
}
@@ -105,17 +103,6 @@ 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;
@@ -133,11 +120,6 @@ static bool ParseArgs(int argc, char* argv[], RunOptions& options, bool& show_he
continue;
}
if (arg == "--fullscreen") {
options.config.fullscreen_enabled = true;
continue;
}
if (!Common::StartsWith(arg, "--")) {
::printf("game input must be provided with --game\n");
return false;
@@ -187,11 +169,6 @@ 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());
@@ -243,18 +220,16 @@ 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());
return false;
}
} else if (arg == "--keymap") {
const auto split = value.find('=');
if (split == std::string::npos || split == 0 || split + 1 == value.size()) {
::printf("invalid keymap: %s\n", value.c_str());
return false;
}
options.config.keymap.push_back(value);
} else {
::printf("unknown option: %s\n", arg.c_str());
return false;
+2
View File
@@ -101,6 +101,8 @@ if(KYTY_CLANG_CL)
string(REGEX REPLACE "/MD" "/MT" CMAKE_C_FLAGS "${CMAKE_C_FLAGS}")
set(KYTY_CPP_FLAGS "${KYTY_CPP_FLAGS} /utf-8 /Oy- /wd4244 /wd4305 /wd4800 /wd4345")
add_link_options("$<$<CONFIG:RELEASE>:/OPT:NOREF>")
if(KYTY_WARNINGS_ARE_ERRORS)
#set(KYTY_CPP_FLAGS "${KYTY_CPP_FLAGS} /WX")
+68 -87
View File
@@ -3,15 +3,13 @@
#include <cstdint>
#include <cstdio>
#include <cstdlib>
#include <utility>
#include <vector>
namespace {
using Owners = std::vector<uint32_t>;
using Table = Libs::Graphics::MultiLevelPageTable<Owners>;
using PageOwners = Libs::Graphics::InlinePageOwnerList<uint32_t, 16>;
using OwnerTable = Libs::Graphics::MultiLevelPageTable<PageOwners, 20, 40, 10>;
using OwnerIndex = Libs::Graphics::MultiRangePageOwnerIndex<uint32_t>;
void Check(bool value, const char* text) {
if (!value) {
@@ -79,93 +77,76 @@ void TestAddressSpaceBoundaries() {
"final page supports allocating and nonallocating access");
}
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");
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");
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");
const auto owners = index.Query(0x100000, 0x10000);
Check(owners.size() == 1 && owners.front() == 7, "multi-page query returns an owner once");
}
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 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 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 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 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");
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");
}
} // namespace
@@ -175,10 +156,10 @@ int main() {
TestCrossBucketRange();
TestQueriesDoNotAllocate();
TestAddressSpaceBoundaries();
TestInlineOwnerStorageAndOverflow();
TestOneMiBRegistrationGranularity();
TestSharedCoarsePageLifecycle();
TestOneMiBBoundaries();
TestMultiRangeRegistrationDeduplicatesPages();
TestSharedPageUnregisterLifecycle();
TestStrictByteFilteringAndPredicate();
TestOwnerIndexAddressSpaceBoundary();
std::printf("ImagePageTableTests: all cases passed\n");
return 0;
}
-51
View File
@@ -6,9 +6,7 @@
#include <cstdio>
#include <cstdlib>
#include <cstring>
#include <semaphore>
#include <string>
#include <thread>
#include <vector>
#if KYTY_PLATFORM == KYTY_PLATFORM_WINDOWS
@@ -267,7 +265,6 @@ void TestRangeInvalidation() {
flushes++;
tracker.ForEachDownloadRange<true>(address + 16, size - 32,
[](uint64_t, uint64_t) noexcept {});
tracker.MarkRegionAsCpuModified(address + 16, size - 32);
});
Check(flushes == 1 && !tracker.IsRegionGpuModified(address, size) &&
tracker.IsRegionCpuModified(address, size) && IsWritable(memory) &&
@@ -280,53 +277,6 @@ void TestRangeInvalidation() {
Release(page_manager, memory, size);
}
void TestGpuReacquisitionAfterInvalidation() {
TrackerHarness harness;
auto &tracker = harness.tracker;
auto &page_manager = harness.page_manager;
const auto page_size = page_manager.GetPageSize();
auto *memory = Allocate(page_manager, 1);
const auto address = reinterpret_cast<uint64_t>(memory);
tracker.ForEachUploadRange(
address, page_size, true, [](uint64_t, uint64_t) noexcept {},
[]() noexcept {});
Check(tracker.IsRegionGpuModified(address, page_size) &&
!tracker.IsRegionCpuModified(address, page_size),
"reacquisition setup did not establish GPU ownership");
uint32_t flushes = 0;
uint32_t uploads = 0;
std::binary_semaphore reacquire{0};
std::binary_semaphore reacquired{0};
std::jthread publisher([&] {
reacquire.acquire();
tracker.ForEachUploadRange(
address + 16, 32, true,
[&](uint64_t, uint64_t) noexcept { uploads++; }, []() noexcept {});
reacquired.release();
});
tracker.InvalidateRegion(address + 16, 32, [&] {
flushes++;
tracker.ForEachDownloadRange<true>(address + 16, 32,
[](uint64_t, uint64_t) noexcept {});
tracker.MarkRegionAsCpuModified(address + 16, 32);
reacquire.release();
reacquired.acquire();
});
publisher.join();
Check(flushes == 1 && uploads == 1 &&
tracker.IsRegionGpuModified(address, page_size) &&
!tracker.IsRegionCpuModified(address, page_size) &&
!IsWritable(memory),
"invalidation rejected a new generation of GPU ownership");
tracker.UnmarkRegionAsGpuModified(address, page_size);
tracker.MarkRegionAsCpuModified(address, page_size);
tracker.UntrackMemory(address, page_size);
Release(page_manager, memory, page_size);
}
void TestGpuDirtyBits() {
TrackerHarness harness;
auto &tracker = harness.tracker;
@@ -685,7 +635,6 @@ int main(int argc, char **argv) {
TestQueriesDoNotRequireMappedOwnership();
TestCpuDirtyUpload();
TestRangeInvalidation();
TestGpuReacquisitionAfterInvalidation();
TestGpuDirtyBits();
TestExactDirtyIntervalsSharingTrackerPage();
TestGpuDownloadProtectionMirrors();
+153 -29
View File
@@ -35,50 +35,171 @@ void YieldMany() {
}
}
void TestOwnership() {
ResourceMutex mutex;
Check(!mutex.IsOwnedByCurrentThread(), "fresh mutex reported an owner");
void TestFaultBlocksPublisher() {
ResourceMutex mutex;
std::atomic_bool publisher_started {false};
std::atomic_bool publisher_entered {false};
std::thread publisher;
{
std::lock_guard lock(mutex);
Check(mutex.IsOwnedByCurrentThread(), "owner tracking was not installed");
ResourceMutex::FaultScope fault(mutex);
publisher = std::thread([&] {
publisher_started.store(true, std::memory_order_release);
std::lock_guard lock(mutex);
publisher_entered.store(true, std::memory_order_release);
});
while (!publisher_started.load(std::memory_order_acquire)) {
std::this_thread::yield();
}
YieldMany();
Check(!publisher_entered.load(std::memory_order_acquire),
"publisher entered during active fault transaction");
}
Check(!mutex.IsOwnedByCurrentThread(), "owner tracking survived unlock");
publisher.join();
Check(publisher_entered.load(std::memory_order_acquire),
"publisher did not resume after fault transaction");
}
void TestSerializesTransactions() {
void TestFaultDrainsExistingOwner() {
ResourceMutex mutex;
std::unique_lock owner(mutex);
std::atomic_bool contender_started {false};
std::atomic_bool contender_entered {false};
std::thread contender([&] {
contender_started.store(true, std::memory_order_release);
std::lock_guard lock(mutex);
contender_entered.store(true, std::memory_order_release);
std::atomic_bool fault_started {false};
std::atomic_bool fault_entered {false};
std::atomic_bool release_fault {false};
std::atomic_bool publisher_started {false};
std::atomic_bool publisher_entered {false};
std::thread fault([&] {
fault_started.store(true, std::memory_order_release);
ResourceMutex::FaultScope scope(mutex);
fault_entered.store(true, std::memory_order_release);
while (!release_fault.load(std::memory_order_acquire)) {
std::this_thread::yield();
}
});
while (!contender_started.load(std::memory_order_acquire)) {
while (!fault_started.load(std::memory_order_acquire)) {
std::this_thread::yield();
}
YieldMany();
Check(!contender_entered.load(std::memory_order_acquire),
"contender entered an active resource transaction");
Check(!fault_entered.load(std::memory_order_acquire),
"fault transaction did not wait for existing owner");
owner.unlock();
contender.join();
Check(contender_entered.load(std::memory_order_acquire),
"contender did not resume after resource transaction");
while (!fault_entered.load(std::memory_order_acquire)) {
std::this_thread::yield();
}
std::thread publisher([&] {
publisher_started.store(true, std::memory_order_release);
std::lock_guard lock(mutex);
publisher_entered.store(true, std::memory_order_release);
});
while (!publisher_started.load(std::memory_order_acquire)) {
std::this_thread::yield();
}
YieldMany();
Check(!publisher_entered.load(std::memory_order_acquire),
"publisher entered before drained fault completed");
release_fault.store(true, std::memory_order_release);
fault.join();
publisher.join();
Check(publisher_entered.load(std::memory_order_acquire),
"publisher did not resume after drained fault");
}
void TestFaultScopesSerialize() {
ResourceMutex mutex;
std::atomic_bool first_entered {false};
std::atomic_bool second_started {false};
std::atomic_bool second_entered {false};
std::atomic_bool release_first {false};
std::atomic_bool release_second {false};
std::atomic_bool publisher_entered {false};
std::thread first([&] {
ResourceMutex::FaultScope scope(mutex);
first_entered.store(true, std::memory_order_release);
while (!release_first.load(std::memory_order_acquire)) {
std::this_thread::yield();
}
});
while (!first_entered.load(std::memory_order_acquire)) {
std::this_thread::yield();
}
std::thread second([&] {
second_started.store(true, std::memory_order_release);
ResourceMutex::FaultScope scope(mutex);
second_entered.store(true, std::memory_order_release);
while (!release_second.load(std::memory_order_acquire)) {
std::this_thread::yield();
}
});
while (!second_started.load(std::memory_order_acquire)) {
std::this_thread::yield();
}
YieldMany();
Check(!second_entered.load(std::memory_order_acquire),
"second fault transaction entered before first completed");
release_first.store(true, std::memory_order_release);
while (!second_entered.load(std::memory_order_acquire)) {
std::this_thread::yield();
}
std::thread publisher([&] {
std::lock_guard lock(mutex);
publisher_entered.store(true, std::memory_order_release);
});
YieldMany();
Check(!publisher_entered.load(std::memory_order_acquire),
"publisher entered during second fault transaction");
release_second.store(true, std::memory_order_release);
first.join();
second.join();
publisher.join();
Check(publisher_entered.load(std::memory_order_acquire),
"publisher did not resume after serialized faults");
}
void TestPreownedFaultKeepsResourceTransaction() {
ResourceMutex mutex;
std::atomic_bool publisher_started {false};
std::atomic_bool publisher_entered {false};
std::thread publisher;
std::unique_lock owner(mutex);
{
ResourceMutex::FaultScope fault(mutex);
publisher = std::thread([&] {
publisher_started.store(true, std::memory_order_release);
std::lock_guard lock(mutex);
publisher_entered.store(true, std::memory_order_release);
});
while (!publisher_started.load(std::memory_order_acquire)) {
std::this_thread::yield();
}
YieldMany();
Check(!publisher_entered.load(std::memory_order_acquire),
"publisher entered during preowned fault transaction");
}
YieldMany();
Check(!publisher_entered.load(std::memory_order_acquire),
"preowned fault released its outer resource transaction");
owner.unlock();
publisher.join();
Check(publisher_entered.load(std::memory_order_acquire),
"publisher did not resume after outer transaction");
}
#if KYTY_PLATFORM == KYTY_PLATFORM_WINDOWS
[[noreturn]] void RunDeathCase() {
[[noreturn]] void RunDeathCase(const char* name) {
ResourceMutex mutex;
std::lock_guard first(mutex);
std::lock_guard second(mutex);
if (std::strcmp(name, "recursive-lock") == 0) {
std::lock_guard first(mutex);
std::lock_guard second(mutex);
} else if (std::strcmp(name, "nested-fault") == 0) {
ResourceMutex::FaultScope first(mutex);
ResourceMutex::FaultScope second(mutex);
}
std::_Exit(0x7f);
}
void CheckDeathCase() {
void CheckDeathCase(const char* name) {
char path[MAX_PATH] {};
Check(GetModuleFileNameA(nullptr, path, MAX_PATH) != 0, "GetModuleFileName failed");
std::string command = std::string("\"") + path + "\" --death";
std::string command = std::string("\"") + path + "\" --death " + name;
std::vector<char> mutable_command(command.begin(), command.end());
mutable_command.push_back('\0');
STARTUPINFOA startup {sizeof(startup)};
@@ -103,17 +224,20 @@ void CheckDeathCase() {
int main(int argc, char** argv) {
#if KYTY_PLATFORM == KYTY_PLATFORM_WINDOWS
if (argc == 2 && std::strcmp(argv[1], "--death") == 0) {
RunDeathCase();
if (argc == 3 && std::strcmp(argv[1], "--death") == 0) {
RunDeathCase(argv[2]);
}
#else
(void)argc;
(void)argv;
#endif
TestOwnership();
TestSerializesTransactions();
TestFaultBlocksPublisher();
TestFaultDrainsExistingOwner();
TestFaultScopesSerialize();
TestPreownedFaultKeepsResourceTransaction();
#if KYTY_PLATFORM == KYTY_PLATFORM_WINDOWS
CheckDeathCase();
CheckDeathCase("recursive-lock");
CheckDeathCase("nested-fault");
#endif
std::puts("ResourceMutexTests: all cases passed");
return 0;
+27
View File
@@ -1468,6 +1468,32 @@ void TestNativeBindingLayoutOneDimensionalImages() {
"binding allocator did not preserve first-class 1D image groups");
}
void TestNativeBindingLayoutExpandsDynamicStorageMips() {
Program program;
program.stage = ShaderType::Compute;
program.blocks.resize(1);
auto dynamic = ImageUse(8, Opcode::ImageStore, ResourceKind::StorageImageUint,
Decoder::ImageDimension::Dim2D, 4);
dynamic.memory.image_has_mip = true;
program.blocks[0].instructions = {
dynamic, ImageUse(12, Opcode::ImageStore, ResourceKind::StorageImageUint,
Decoder::ImageDimension::Dim2D, 6)};
Prepare(program);
ShaderComputeInputInfo compute;
compute.thread_ids_num = 1;
std::string error;
Check(CollectShaderInfo(program, {.compute = &compute}, &error), error.c_str());
Check(program.info.images.size() == 2 &&
program.info.images[0].mip_mode == ImageMipMode::DynamicStorage,
"dynamic storage image was not tracked independently");
program.info.images[0].mip_levels = 3;
Check(AllocateBindings(program, {}, &error), error.c_str());
const auto* storage = FindBinding(program.bindings, DescriptorBindingKind::StorageUint2D);
Check(storage != nullptr && storage->resources == std::vector<uint32_t>({0, 0, 0, 1}),
"dynamic storage mip span overlapped the following logical image");
}
void TestNativeBindingLayoutSrtAndUserDataOverflow() {
Program srt;
srt.stage = ShaderType::Compute;
@@ -1936,6 +1962,7 @@ int main() {
RUN(TestTrackedProgramIsImmutable);
RUN(TestNativeBindingLayout);
RUN(TestNativeBindingLayoutOneDimensionalImages);
RUN(TestNativeBindingLayoutExpandsDynamicStorageMips);
RUN(TestNativeBindingLayoutSrtAndUserDataOverflow);
RUN(TestNativeBindingLayoutGds);
RUN(TestNativeBindingLayoutIsTransactional);
File diff suppressed because it is too large Load Diff
-269
View File
@@ -1,269 +0,0 @@
#include "kernel/syncOnAddress.h"
#include "libs/errno.h"
#include <atomic>
#include <chrono>
#include <climits>
#include <cstdio>
#include <cstdlib>
#include <thread>
#include <vector>
namespace {
using Libs::LibKernel::SyncOnAddress::Wait32;
using Libs::LibKernel::SyncOnAddress::Wait64;
using Libs::LibKernel::SyncOnAddress::Wake;
std::atomic<int> g_signal_poll_count{0};
void Check(bool value, const char *text) {
if (!value) {
std::fprintf(stderr, "SyncOnAddressTests: failed: %s\n", text);
std::abort();
}
}
template <typename T> void Store(T *address, T value) {
std::atomic_ref<T>(*address).store(value, std::memory_order_release);
}
void CountSignalPoll() {
g_signal_poll_count.fetch_add(1, std::memory_order_relaxed);
}
void TestInvalidAddress() {
uint32_t timeout = 1;
Check(Wait32(nullptr, 0, &timeout) == Libs::LibKernel::KERNEL_ERROR_EINVAL,
"wait32 rejects a null address");
Check(Wait64(nullptr, 0, &timeout) == Libs::LibKernel::KERNEL_ERROR_EINVAL,
"wait64 rejects a null address");
Check(Wake(nullptr, 1) == Libs::LibKernel::KERNEL_ERROR_EINVAL,
"wake rejects a null address");
alignas(uint64_t) uint8_t bytes[16] = {};
auto *misaligned = reinterpret_cast<uint32_t *>(bytes + 1);
Check(Wait32(misaligned, 0, &timeout) == Libs::LibKernel::KERNEL_ERROR_EINVAL,
"wait32 rejects a misaligned address");
Check(Wait64(reinterpret_cast<uint64_t *>(bytes + 4), 0, &timeout) ==
Libs::LibKernel::KERNEL_ERROR_EINVAL,
"wait64 rejects a misaligned address");
Check(Wake(misaligned, 1) == Libs::LibKernel::KERNEL_ERROR_EINVAL,
"wake rejects a misaligned address");
uint64_t aligned = 0;
Check(Wake(&aligned, -1) == Libs::LibKernel::KERNEL_ERROR_EINVAL,
"wake rejects a negative count");
}
void TestMismatchReturnsImmediately() {
uint32_t word = 7;
uint64_t word64 = UINT64_C(0x100000000);
uint32_t timeout = 500000;
const auto start = std::chrono::steady_clock::now();
Check(Wait32(&word, 6, &timeout, CountSignalPoll) == OK,
"mismatch succeeds without parking");
Check(Wait64(&word64, 0, &timeout, CountSignalPoll) == OK,
"wait64 compares all 64 bits");
Check(std::chrono::steady_clock::now() - start <
std::chrono::milliseconds(100),
"mismatched value is a fast path");
Check(g_signal_poll_count.load(std::memory_order_relaxed) >= 2,
"mismatch remains a guest signal safe-point");
}
void TestTimeout() {
uint32_t word = 0;
uint32_t timeout = 20000;
const auto start = std::chrono::steady_clock::now();
Check(Wait32(&word, 0, &timeout) == Libs::LibKernel::KERNEL_ERROR_ETIMEDOUT,
"matching value times out");
const auto elapsed = std::chrono::steady_clock::now() - start;
Check(elapsed >= std::chrono::milliseconds(10),
"timeout does not return too early");
Check(elapsed < std::chrono::milliseconds(500), "timeout remains bounded");
timeout = 0;
Check(Wait32(&word, 0, &timeout) == Libs::LibKernel::KERNEL_ERROR_ETIMEDOUT,
"zero timeout polls a matching value");
word = 1;
Check(Wait32(&word, 0, &timeout) == OK,
"zero timeout succeeds for a mismatched value");
}
void TestValueChangeAndWake() {
uint64_t word = 0;
uint32_t timeout = 1000000;
std::atomic<bool> ready{false};
int result = Libs::LibKernel::KERNEL_ERROR_ETIMEDOUT;
std::thread waiter([&] {
ready.store(true, std::memory_order_release);
result = Wait64(&word, 0, &timeout);
});
while (!ready.load(std::memory_order_acquire)) {
std::this_thread::yield();
}
Store(&word, UINT64_C(0x100000000));
Check(Wake(&word, 1) == OK, "wake-one succeeds");
waiter.join();
Check(result == OK, "a value change plus wake releases the waiter");
}
void TestWakeOneThenAll() {
constexpr int WAITER_COUNT = 4;
uint32_t word = 0;
uint32_t timeout = 1000000;
std::atomic<int> ready{0};
std::atomic<int> returned{0};
int results[WAITER_COUNT] = {};
std::vector<std::thread> waiters;
for (int i = 0; i < WAITER_COUNT; i++) {
waiters.emplace_back([&, i] {
ready.fetch_add(1, std::memory_order_release);
results[i] = Wait32(&word, 0, &timeout);
returned.fetch_add(1, std::memory_order_release);
});
}
while (ready.load(std::memory_order_acquire) != WAITER_COUNT) {
std::this_thread::yield();
}
std::this_thread::sleep_for(std::chrono::milliseconds(50));
Check(Wake(&word, 1) == OK, "wake-one succeeds with multiple waiters");
const auto one_deadline =
std::chrono::steady_clock::now() + std::chrono::milliseconds(500);
while (returned.load(std::memory_order_acquire) == 0 &&
std::chrono::steady_clock::now() < one_deadline) {
std::this_thread::yield();
}
Check(returned.load(std::memory_order_acquire) == 1,
"wake-one releases exactly one waiter");
Check(Wake(&word, 2) == OK, "wake-two succeeds");
const auto two_deadline =
std::chrono::steady_clock::now() + std::chrono::milliseconds(500);
while (returned.load(std::memory_order_acquire) < 3 &&
std::chrono::steady_clock::now() < two_deadline) {
std::this_thread::yield();
}
Check(returned.load(std::memory_order_acquire) == 3,
"wake-two releases exactly two more waiters");
Check(Wake(&word, INT_MAX) == OK, "wake-all succeeds");
for (auto &waiter : waiters) {
waiter.join();
}
Check(returned.load(std::memory_order_acquire) == WAITER_COUNT,
"wake-all releases the remaining waiters");
for (int result : results) {
Check(result == OK, "explicitly woken waiters return success");
}
}
void TestAddressesAreIsolated() {
uint32_t first = 0;
uint32_t second = 0;
uint32_t first_timeout = 1000000;
uint32_t second_timeout = 1000000;
std::atomic<int> ready{0};
std::atomic<bool> first_returned{false};
std::atomic<bool> second_returned{false};
int first_result = 0;
int second_result = 0;
std::thread first_waiter([&] {
ready.fetch_add(1, std::memory_order_release);
first_result = Wait32(&first, 0, &first_timeout);
first_returned.store(true, std::memory_order_release);
});
std::thread second_waiter([&] {
ready.fetch_add(1, std::memory_order_release);
second_result = Wait32(&second, 0, &second_timeout);
second_returned.store(true, std::memory_order_release);
});
while (ready.load(std::memory_order_acquire) != 2) {
std::this_thread::yield();
}
std::this_thread::sleep_for(std::chrono::milliseconds(50));
Check(Wake(&first, 1) == OK, "first address wakes");
std::this_thread::sleep_for(std::chrono::milliseconds(50));
Check(first_returned.load(std::memory_order_acquire),
"first address waiter returned");
Check(!second_returned.load(std::memory_order_acquire),
"waking one address does not release another address");
Check(Wake(&second, 1) == OK, "second address wakes");
first_waiter.join();
second_waiter.join();
Check(first_result == OK && second_result == OK,
"isolated waiters return success");
}
void TestCompareRegisterWakeRace() {
for (int i = 0; i < 100; i++) {
uint32_t word = 0;
uint32_t timeout = 500000;
std::atomic<bool> ready{false};
int result = Libs::LibKernel::KERNEL_ERROR_ETIMEDOUT;
std::thread waiter([&] {
ready.store(true, std::memory_order_release);
result = Wait32(&word, 0, &timeout);
});
while (!ready.load(std::memory_order_acquire)) {
std::this_thread::yield();
}
Store(&word, uint32_t{1});
(void)Wake(&word, 1);
waiter.join();
Check(result == OK, "compare/register/wake race never loses progress");
}
}
void TestWakeZeroIsNoOp() {
constexpr int WAITER_COUNT = 2;
uint32_t word = 0;
uint32_t timeout = 1000000;
std::atomic<int> ready{0};
std::atomic<int> returned{0};
int results[WAITER_COUNT] = {};
std::vector<std::thread> waiters;
for (int i = 0; i < WAITER_COUNT; i++) {
waiters.emplace_back([&, i] {
ready.fetch_add(1, std::memory_order_release);
results[i] = Wait32(&word, 0, &timeout);
returned.fetch_add(1, std::memory_order_release);
});
}
while (ready.load(std::memory_order_acquire) != WAITER_COUNT) {
std::this_thread::yield();
}
std::this_thread::sleep_for(std::chrono::milliseconds(50));
Check(Wake(&word, 0) == OK, "zero-count wake succeeds");
std::this_thread::sleep_for(std::chrono::milliseconds(20));
Check(returned.load(std::memory_order_acquire) == 0,
"zero-count wake releases no waiters");
Check(Wake(&word, INT_MAX) == OK, "wake-all succeeds after zero-count wake");
for (auto &waiter : waiters) {
waiter.join();
}
for (int result : results) {
Check(result == OK, "wake-all releases waiters after zero-count no-op");
}
}
} // namespace
int main() {
TestInvalidAddress();
TestMismatchReturnsImmediately();
TestTimeout();
TestValueChangeAndWake();
TestWakeOneThenAll();
TestAddressesAreIsolated();
TestCompareRegisterWakeRace();
TestWakeZeroIsNoOp();
std::printf("SyncOnAddressTests: all passed\n");
return 0;
}